Metaの新型音声認識モデル「omnilingual-asr」を試してみた(1B/3B/7B比較)
最近、Metaから新しい音声認識モデル「omnilingual-asr」が公開されました。
多言語に対応したモデルということで、日本語の認識精度がどれほどのものか気になったので、早速手元の環境で試してみることにしました。
モデルの詳細はGitHubリポジトリで公開されています。
https://github.com/facebookresearch/omnilingual-asr
検証の概要
今回は、公開されているモデルのうち、パラメータ数の異なる以下の3種類を比較しました。
- omniASR_LLM_1B
- omniASR_LLM_3B
- omniASR_LLM_7B
これらのモデルに対し、手元にあった2種類の日本語のナレーション音声ファイル(test1.mp3, test2.mp3)を認識させて、その結果を比較します。
40秒制限への対応(Pythonコード)
検証を始めたところ、現在のomnilingual-asrモデルは約40秒までの音声しか一度に処理できないという制約があることがわかりました。
そこで、長い音声ファイルでも処理できるように、Python (librosa) を使って音声を事前に分割(チャンキング)するスクリプトを用意しました。
主な処理内容は以下の通りです。
- 音声を30秒のチャンクに分割する。
- チャンク間は5秒のオーバーラップを持たせ、認識漏れを防ぐ。
- VAD(無音区間検出)を使い、できるだけ「会話の途中」で途切れないよう、無音区間を優先して分割点を設定する。
実行環境はApple Silicon (M4Pro) のため、device="mps"を指定してGPUで動作させています。
使用したPythonコード
※コード内のoriginal_audio_fileは、処理対象のファイル(test1.mp3やtest2.mp3をWAVに変換したもの)に適宜変更して実行しました。
import librosa
import soundfile as sf
import numpy as np
import os
import math
from omnilingual_asr.models.inference.pipeline import ASRInferencePipeline
# --- 1. パイプラインの初期化 ---
try:
# device="mps" を追加してApple Silicon GPUを指定
# pipeline = ASRInferencePipeline(model_card="omniASR_LLM_1B", device="mps")
# pipeline = ASRInferencePipeline(model_card="omniASR_LLM_3B", device="mps")
pipeline = ASRInferencePipeline(model_card="omniASR_LLM_7B", device="mps")
# processor属性が存在しないエラーに対応するため、サンプリングレートをハードコード
TARGET_SR = 16000
print(f"モデルの要求サンプリングレートを {TARGET_SR} Hz に設定します。")
except Exception as e:
print(f"パイプラインの初期化に失敗しました: {e}")
# 初期化失敗時も、以降の処理のためにSRを設定
TARGET_SR = 16000
# --- 2. チャンキング(分割)の設定 ---
CHUNK_DURATION_S = 30.0 # 各チャンクの *最大* の長さ(秒)
OVERLAP_DURATION_S = 5.0 # チャンク間のオーバーラップ(秒)
### VAD設定 ###
# VAD_TOP_DB: 無音と判断するデシベル。数値が *大きい* ほど、小さな音も「発声」とみなします。
# (例: 60 = 静かな環境、 40 = 少しノイズがある環境、 20 = ノイズが多い環境)
# この値は音声ファイルに合わせて調整してください。
VAD_TOP_DB = 40
# VAD_SEARCH_WINDOW_S: 30秒地点の手前、何秒間を無音探索の対象にするか (例: 5秒 -> 25秒〜30秒の間で探す)
VAD_SEARCH_WINDOW_S = 5.0
# VAD_MIN_SILENCE_S: 分割点として採用する無音区間の最短長(秒)
VAD_MIN_SILENCE_S = 0.5
# --- 3. 音声ファイルの読み込みとVAD準備 ---
original_audio_file = "test1.mp3" # ここを書き換えて使用
lang = ["jpn_Jpan"] # 認識させたい言語
try:
# librosaで音声を読み込み (ターゲットサンプリングレートに変換)
y, sr = librosa.load(original_audio_file, sr=TARGET_SR, mono=True)
duration = librosa.get_duration(y=y, sr=sr)
print(f"元の音声ファイル '{original_audio_file}' を読み込みました。")
print(f" 長さ: {duration:.2f} 秒")
total_samples = len(y)
### VAD処理 ###
print(f"VAD(無音区間検出)を実行します (top_db={VAD_TOP_DB})...")
# VAD: 発声区間(音が大きい区間)を検出
# (frame_length, hop_length は librosa のデフォルト値を使います)
intervals = librosa.effects.split(y, top_db=VAD_TOP_DB)
# VADの結果から「無音区間」のリストを作成
silence_intervals = []
last_end = 0
for start, end in intervals:
# 前回の発声終了地点 (last_end) と今回の発声開始地点 (start) の間が無音区間
if start > last_end:
silence_intervals.append((last_end, start))
last_end = end
# 最後の発声区間からファイルの終わりまでが無音区間
if last_end < total_samples:
silence_intervals.append((last_end, total_samples))
print(f"{len(silence_intervals)} 個の無音区間を検出しました。")
### VAD処理 終了 ###
# サンプル数に変換
target_chunk_samples = int(CHUNK_DURATION_S * sr)
overlap_samples = int(OVERLAP_DURATION_S * sr)
stride_samples = target_chunk_samples - overlap_samples # 次のチャンク開始までの移動量
# VAD探索開始位置(デフォルトの分割地点の手前)
# (例: 30s * 16000 - 5s * 16000 = 25s * 16000)
vad_search_start_samples = target_chunk_samples - int(VAD_SEARCH_WINDOW_S * sr)
min_silence_samples = int(VAD_MIN_SILENCE_S * sr)
# 分割したチャンクのファイルパスを保存するリスト
chunk_files = []
temp_dir = "temp_audio_chunks"
os.makedirs(temp_dir, exist_ok=True) # 一時ディレクトリ作成
num_chunks = 0
start_sample = 0
print(f"音声を最大 {CHUNK_DURATION_S} 秒のチャンク({OVERLAP_DURATION_S} 秒オーバーラップ)に分割します...")
print(f" 分割点は {CHUNK_DURATION_S - VAD_SEARCH_WINDOW_S:.1f} 秒から {CHUNK_DURATION_S:.1f} 秒の間で無音区間を探します。")
while start_sample < total_samples:
target_end_sample = start_sample + target_chunk_samples
# 最後のチャンクか?
if target_end_sample >= total_samples:
end_sample = total_samples
else:
# --- VADによる分割点探索 ---
# 探索範囲 (例: 25秒地点から30秒地点まで)
search_start = start_sample + vad_search_start_samples
search_end = target_end_sample
best_split_point = target_end_sample # デフォルト (無音が見つからない場合)
found_silence = False
# 30秒地点に *一番近い* (手前の) 無音区間を探すため、逆順 (reversed) でループ
for s_start, s_end in reversed(silence_intervals):
# [s_start, s_end] は無音区間
# 理想的な分割点は「無音区間の開始点 (s_start)」
# s_start が [search_start, search_end] の間にあればよい
if search_start <= s_start < search_end:
# 無音区間が短すぎないかチェック
if (s_end - s_start) >= min_silence_samples:
best_split_point = s_start # 無音区間の *開始点* で切る
found_silence = True
break # 30秒に一番近いものを見つけたので終了
if found_silence:
print(f" [チャンク {num_chunks+1}] VAD検出: {target_end_sample/sr:.2f}秒地点の代わりに {best_split_point/sr:.2f}秒地点で分割。")
else:
print(f" [チャンク {num_chunks+1}] VAD: {CHUNK_DURATION_S - VAD_SEARCH_WINDOW_S:.1f}s-{CHUNK_DURATION_S:.1f}s に無音区間なし。{target_end_sample/sr:.2f}秒地点で強制分割。")
end_sample = best_split_point
# --- VAD探索終了 ---
chunk_data = y[start_sample:end_sample]
# チャンクが短すぎる場合(例:最後の余りや、VAD分割の隙間)
if len(chunk_data) > (sr * 0.1): # 0.1秒未満のチャンクは無視
num_chunks += 1
chunk_filename = os.path.join(temp_dir, f"chunk_{num_chunks:04d}.wav")
# soundfileで一時ファイルとして書き出し
sf.write(chunk_filename, chunk_data, sr)
chunk_files.append(chunk_filename)
# 次のチャンクの開始位置 (固定ストライド)
# VADでチャンクが短くなっても、次の開始位置は元の 25秒地点から
start_sample += stride_samples
# 最後のチャンクが配列の終端に達していたら終了
if end_sample == total_samples:
break
print(f"{num_chunks}個のチャンクを作成しました。")
# --- 4. 分割した音声ファイルで推論を実行 ---
if chunk_files:
print("音声認識パイプラインを実行中...")
# 言語指定リストもチャンクの数だけ用意する
langs_list = [lang[0]] * len(chunk_files)
# ★★★ 修正点 ★★★
# 'audio_files=' というキーワード引数を削除し、
# chunk_files を最初の「位置引数」として渡す
transcriptions = pipeline.transcribe(
chunk_files, # キーワード引数 'audio_files=' を削除
lang=langs_list,
batch_size=1 # ご自身のVRAMに合わせて調整してください
)
print("\n--- 認識結果(チャンク別) ---")
for i, (file, text) in enumerate(zip(chunk_files, transcriptions)):
print(f"[{i+1:02d}] {text}")
# --- 5. 結果の結合 ---
full_transcription = " ".join(transcriptions)
print("\n--- 最終的な認識結果(単純結合) ---")
print(full_transcription)
else:
print("処理する音声チャンクがありませんでした。")
# --- 6. 一時ファイルのクリーンアップ ---
print(f"\n一時ファイル ({temp_dir}) をクリーンアップします...")
for f in chunk_files:
try:
os.remove(f)
except OSError as e:
print(f"ファイル削除エラー ({f}): {e}")
try:
os.rmdir(temp_dir)
print(f"一時ディレクトリ ({temp_dir}) を削除しました。")
except OSError as e:
print(f"ディレクトリ削除エラー ({temp_dir}): {e} (注: 空でない可能性があります)")
except sf.SoundFileError:
print(f"エラー: 音声ファイル '{original_audio_file}' が見つからないか、破損しています。")
except FileNotFoundError:
print(f"エラー: 音声ファイル '{original_audio_file}' が見つかりません。")
except Exception as e:
print(f"処理中に予期せぬエラーが発生しました: {e}")
検証結果: test1.mp3
まずは test1.mp3 の結果です。Power Automateの操作解説のようなナレーションです。
(⁇ はモデルが認識できなかった部分のようです)
7Bモデル (test1.mp3)
--- 認識結果(チャンク別) ---
[01] テストする 作成したフロ は想定通りに動作するかの確認が必要です テストボタンはフロ 編集画面の右上部にあります テストボタンを ⁇ 歌すると 図のようなポップアップが表示されます ここで主導もしくは児童を選択してポップアップ株のテストボタンを ⁇ 歌し フロ の実行
[02] ポップアップ株のテストボタンを ⁇ 歌し フロ の実行ボタンを ⁇ 歌するとフロ を起動できます フロ のテストが完了すると 各アクションの結果及び処理時間が表示されます アクションをクリックすると インプットやアウトプットなど詳細結果も確認できます もしアクションで失敗すると
[03] もしアクションで失敗すると 図のような状態になります 失敗したアクション部分の詳細が画面に表示されるので 出力部分でどのようなエラ かを確認します なお 未科工の出力を表示する をクリックすることで 画面右側に出力結果のジェイソンを表示させることができます
[04] 側に出力結果のジェイソンを表示させることができます 数はズワ 失敗メッセ ジの例です テスト結果 もしくは通常の実行結果は フロ のホ ム画面で確認ができます 実行結果の日付部分をクリックすることで 先ほどのテスト実行結果画面と同様の
[05] クリックすることで 先ほどのテスト実行結果画面と同様の画面が開きます 失敗している場合には 各アクションの結果詳細を確認しながら修正を行います なおふるい なおほう なお不労のホ ム画面で表示される実行結果は一部だけです 古い実行結果も確認したい
[06] 実行結果は一部だけです 古い実行結果も確認したい場合は フロ 一覧で対象のフロ の三点マ クから 実行履歴をクリックします
3Bモデル (test1.mp3)
--- 認識結果(チャンク別) ---
[01] テストする 作成したフロ は 想定通りに動作するかの確認が必要です テストボタンはフロ 編集画面の右上部にあります テストボタンを ⁇ 歌すると 図のようなポップアップが表示されます ここで 主導 もしくは自動を選択してポップアップカブのテストボタンを ⁇ 歌し フロ の実行
[02] ポップアップカブのテストボタンを横化し フロ の実行ボタンを ⁇ 歌すると フロ を起動できます フロ のテストが完了すると 各アクションの結果 および処理時間が表示されます アクションをクリックすると インプットやアウトプットなど詳細結果も確認できます もし アクションで失敗すると
[03] もし アクションで失敗すると 図のような状態になります 失敗したアクション部分の詳細が画面に表示されるので 出力部分でどのようなエラ かを確認します なおミカコ の出力を表示する をクリックすることで 画面右側に出力結果のジェイソンを表示させることができます
[04] 側に出力結果のジェイソンを表示させることができます 数は ズワ 失敗メッセ ジ の礼です テスト結果 もしくは通常の実行結果はフロ のホ ム画面で確認ができます 実行結果の日付部分をクリックすることで 先ほどのテスト実行結果画面と同様の
[05] クリックすることで さきほどのテスト実行結果画面と同様の画面が開きます 失敗している場合には各アクションの結果詳細を確認しながら修正を行います なお古いなおほうなおフロ のホ ム画面で表示される実行結果は一部だけです 古い実行結果も確認したい
[06] 古い実行結果も確認したい場合は 不労一覧で 対象の不労の三点マ クから実行履歴をクリックします
1Bモデル (test1.mp3)
--- 認識結果(チャンク別) ---
[01] テストする 作成したフロ は想定通りに動作するかの確認が必要です テストボタンはフロ 編集画面の右上部にあります テストボタンを横科すると 図のようなポップアップが表示されます ここで 主導 もしくは自動を選択してポップアップカブのテストボタンを横科し フロ の実行
[02] ポップアップカブのテストボタンを横跨し フロ の実行ボタンを横跨するとフロ を起動できます フロ のテストが完了すると各アクションの結果および処理時間が表示されます アクションをクリックすると インプットやアウトプットなど詳細結果も確認できます もしアクションで失敗すると
[03] もしアクションで失敗すると 図のような状態になります 失敗したアクション部分の詳細が画面に表示されるので 出力部分でどのようなエラ 化を確認します なお 未加工の出力を表示する オクリック することで 画面右側に 出力結果のジェイソンを表示させることができます
[04] するとの学校に出力結果のジェイソンを表示させることができます 数は ズワ 失敗メッセ ジの例です テスト結果 もしくは通常の実行結果はフロ のホ ム画面で確認ができます 実行結果の日付部分をクリックすることで 先ほどのテスト実行結果画面と同様の
[05] クリックすることで 先ほどのテスト実行結果画面と同様の画面が開きます 失敗している場合には各アクションの結果詳細を確認しながら修正を行います なお古いなお法なおフロ のホ ム画面で表示される実行結果は一部だけです 古い実行結果も確認した
[06] 古い実行結果も確認したい場合は フロ 一覧で対象のフロ の酸甜マ クから実行利歴をクリックします
検証結果: test2.mp3
続いて test2.mp3 の結果です。こちらもPower Automateのトリガーに関する解説ナレーションです。
7Bモデル (test2.mp3)
--- 認識結果(チャンク別) ---
[01] 鳥川の紹介 ここではよく使う鳥川を三つ紹介します まずは繰り返しです スケジュ ルしたタイミングで起動する鳥川です 毎日 丸児 や月曜日の丸児 丸児管沖 などが設定可能です タイムゾ ンは任意ですが設定しておいた方が安全です
[02] ですが設定しておいた方が安全です 得ぬ時間を機能機動の場合 頻度を時間にして 頻度を時間に設定し 感覚に数字を入力します 開始時間は未来にしておくことが可能です 特定の容備の特定の時間に機動の場合は 頻度を州に設定することで
[03] 人口市の地区の場合は 頻度を州に設定することで 容備などの項目が設定可能になります 起動したい容備はプルダウンから複数選択が可能です 起動時間も設定可能です つづいてパワ アプス起動です パワ アプスから起動する取り側です この取り側を設定
[04] パワ アプスから起動する鳥川です この鳥川を設定することでパワ アプス側にフロ を組み込むことが可能です ひき数なしの場合はデフォルトのままでよいです ひき数ありの場合は入力を追加する を選択し デ タタイプを選択します 左のわくには ひき数目 右のわくにはひき数目
[05] 左のわくには ひき数目 右のわくには ひき数の説明を記載します なお ひき数は複数設定可能です 最後は自動化 最後は自動化フロ です 何かの動作を検知して起動する鳥側です 鳥側はメ ルの受診やファイルの作成 更新 チ ムズのチャット受診などが
[06] ファイルの作成 更新 チ ムズのチャット受信などが チ ムズのチャット受信など複数存在します アウトルックのメ ル受信の場合は 監視する受信フォルザ を指定できます その他店舗ファイルの有無や県目などの指定も可能です 理質で項目が作成されたときの場合は 監視する理性
[07] 理質で項目が作成されたときの場合は 監視する理質を指定できます 項目が作成されたら起動し チ ムズのチャネルに投稿するなどの通知につなげることが可能です
3Bモデル (test2.mp3)
--- 認識結果(チャンク別) ---
[01] 鳥川の紹介 ここではよく使う鳥川を三つ紹介します まずは繰り返しです スケジュ ルしたタイミングで起動する鳥川です 毎日 丸辞 や月曜日の丸辞 丸辞官を起き などが設定可能です タイムゾ ンは任意ですが 設定しておいた方が安全です
[02] ですが 設定しておいた方が安全です エヌ時間沖の軌道の場合 頻度を時間にして 頻度を時間に設定し 感覚に数字を入力します開始時間は未来にしておくことがかるのです 特定の曜日の特定の時間に軌道の場合は 頻度を週に設定することで
[03] に軌道の場合は 頻度を 週 に設定することで 曜日などの項目が設定可能になります 軌道したい曜日はプルダウンから複数選択が可能です 軌道時間も設定可能です 続いてパワ アプス軌道です パワ アプスから軌道する鳥側です この鳥側を設定
[04] パワ アプスから起動する鳥川です この鳥川を設定することでパワ アプス側に不労を組み込むことが可能です 匹数なしの場合はデフォルトのままで良いです 匹数ありの場合は入力を追加する を選択し デ タタイプを選択します 左の枠には 引き数名 右の枠には 引き数の説明
[05] 左の枠には 引き数名 右の枠には 引き数の説明を記載します なお 引き数は複数設定可能です 最後は自動化フロ です 何かの動作を検知して起動する鳥側です 鳥側はメ ルの受診やファイルの作製 更新 チ ムズのチャット受診などが
[06] ファイルの作成 更新 チ ムズのチャット受信などが チ ムズのチャット受信など複数存在します アウトルックのメ ル受信の場合は 監視する受信フォルダ を指定できます その他テンプファイルの有無や権名などの指定も可能です 理質で項目が作成された時の場合は監視する
[07] 理室で項目が作成された時の場合は 監視する理室を指定できます 項目が作成されたら軌道し チ ムズのチャネルに投稿するなどの通知につなげることが可能です
1Bモデル (test2.mp3)
--- 認識結果(チャンク別) ---
[01] 鳥川の紹介 ここではよく使う鳥川を三つ紹介します まずは繰り返しです スケジュ ルしたタイミングで起動する鳥川です 毎日 マルジ や月曜日のマルジ マルジ間沖 などが設定可能です タイムゾ ンは任意ですが 設定しておいた方が安全です
[02] ですが 設定しておいた方が安全です エヌ時間沖の軌道の場合 頻度を時間 にして 頻度を時間に設定し 感覚に数字を入力します 開始時間は未来にしておくことが可能です 特定の曜日の特定の時間に軌道の場合は 頻度を週 に設定することで
[03] からに機動の場合は 頻度を週 に設定することで 曜日などの項目が設定可能になります 機動したい曜日はプルダウンから複数選択が可能です 機動時間も設定可能です
[04] パワ アプスから起動する取り側です この取り側を設定することで パワ アプス側に不労を組み込むことが可能です 匹数なしの場合はデフォルトのままで良いです 匹数ありの場合は入力を追加する を選択し デ タタイプを選択します 左の枠には匹数名 右の枠には匹数の説明
[05] 左の枠には 引き数名 右の枠には引き数の説明を記載します なお 引き数は複数設定可能です 最後は自動化 最後は自動化 フロ です 何かの動作を検知して起動する鳥側です 鳥側はメ ルの受診やファイルの作製 更新 チ ムズのチャット受診などが
[06] ファイルの作成 更新 ジ ムズのチャット受信などが ジ ムズのチャット受信など複数存在します アウトルックのメ ル受信の場合は 監視する受信フォルダ を指定できます その他テンプファイルの有無や懸命などの指定も可能です 理質で項目が作成された時の場合は 監視する理質で項目が作成された時の場合は 監視する理質で
[07] 理質で項目が作成された時の場合は 監視する理質を指定できます 項目が作成されたら起動し チ ムズのチャネルに投稿するなどの通知につなげることが可能です
感想・考察
3種類のモデルを試してみた率直な感想です。
- モデル間の差異はあまり感じない
1B, 3B, 7Bとパラメータ数を変えてみましたが、今回のナレーション音声に関する限り、認識結果に大きな差は見られませんでした。どれも「トリガー」を「鳥川」、「フロー」を「フロ」と認識しています。 - 長音符号(伸ばし棒)が出力されない?
「フロー」→「フロ」、「チームズ」→「チ ムズ」、「スケジュール」→「スケジュ ル」のように、カタカナの伸ばし棒が抜け落ちる(またはスペースになる)傾向が強く見られました。 - 7Bモデルは処理時間がものすごい
mps(GPU) を使っても、7Bモデルは他のモデルに比べて圧倒的に処理時間がかかりました。今回のテスト音声(合計数分程度)の処理に数十分を要しており、正直なところ実用的ではない速度でした。 - Whisperの方が精度が高いかも
これは個人の感想ですが、日本語のナレーション認識に関しては、現時点では Whisper (large-V3) の方が、長音符号や専門用語(フロー、トリガーなど)の認識率が高いように感じます。
さいごに
omnilingual-asrはまだ公開されたばかりのモデルであり、40秒の制限など、これから改善されていく部分も多いと思います。
特に多言語対応モデルとしての今後に期待しつつ、引き続きアップデートをチェックしていきたいと思います。
今回はこの辺で、ではまた!
コメント
コメントを投稿