1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

TTSで作った音声では「互角」だった。生の会議音声で測り直したら、前回の結論がひっくり返った話

1
Posted at

3次記事で、私はさくらのAI Engineの文字起こしとローカルWhisperを比べ、**「一致率88.8%で実質互角」**と書きました。その検証には、自分でTTSに喋らせて作った音声を使いました。
今回、生の会議音声で測り直しました。結論は変わりました。

TL;DR

  • ローカルの議事録パイプライン(faster-whisper medium / CPU)は、15分の音声に10分40秒かかります。遅い。一方 さくらのAI Engineの文字起こしは同じ長さを18.1秒——約35倍速い
  • 速いなら乗り換えたい。でも生の会議音声で精度を測ったら、速度以外はローカルが勝ちました。結果、採用は見送り
  • 決め手になったのは2種類の失敗です。ハルシネーション(言っていないことを書く)と、脱落(言ったことを書かない)。今回さくら側で、同じ語が144回連続で出力される箇所と、発言まるごとの消失が十数箇所ありました。両テキストの一致率は47.9%
  • そして重要なのは、脱落のほうが実害が大きいということです。ハルシネーションは読めば異常だと分かりますが、脱落した議事録は、自然な文章として普通に読めてしまいます
  • なぜ3次記事では気づけなかったのか。検証に使ったのがTTS合成音声=ノイズなし・単一話者・無音区間なしの"きれいすぎる"データだったからです。ハルシネーションは、まさにその"きれいでない部分"で起きます
  • そしてTTS音声を使ったのは、「会議の内容を第三者のサーバーに出さない」という自分の方針の帰結でした。方針を守る限り実音声はAPIに投げられないので、非機密の合成音声で代用するしかない。つまり——機密を守るために本番データを避けた結果、本番でしか起きない失敗が見えなくなっていました。これは私の不注意というより、機密データを扱う人が外部サービスを検証しようとすると必ずぶつかる壁だと思います。
  • おまけに、途中で**「機密データの線引き」の定義そのものを間違えていたことにも気づきました。「クラウドか、ローカルか」ではありません。「自分の管理下から出るか、第三者に渡るか」です**。
  • ※ 本記事の結論は実会議音声2本の実測(N=2)です。追加の1本でも同種のハルシネーション(今度は849回連続)が再現しましたが、それでもサンプル数としては小さく、「さくらのWhisperは精度が低い」という一般化はしていません。理由は後述します。
  • おまけに、N=1をN=2に増やそうとしただけで、大量のエラーに阻まれました。 手元にある複数の実会議音声を次々に試したところ、22回中、成功したのはたった1本(同じファイルで6/6)。それ以外の11種類・16回は、ファイルサイズを変えても分割してもすべて失敗しました。原因は最後まで特定できませんでしたが、**「精度を測る以前に、安定して処理を通すこと自体が壁だった」**という、もう一つの発見になりました。詳しくは後述します。

1. きっかけ:うちの議事録、遅すぎないか

私は自宅のミニPC上で、AIエージェントの"会社"を運用しています(第1弾)。その一機能に議事録の自動生成があります。録音ファイルを所定のフォルダに置くと、こう流れます。

議事録パイプライン:フォルダ投入から話者別逐語録まで(話者判別の詳細)
giji-pipeline-diarization.png

依頼文は書きません。フォルダに音声を置くこと自体が起動の合図です。常駐している watcher が5秒間隔で見張っていて、新しいファイルを見つけたら書き込みが終わるのを待ってから .processing/ へ移動し(二重処理の防止)、別プロセスとして切り離して起動します。文字起こしは重いので、待ってしまうと watcher 本体のチケット監視まで止まるからです。

これがずっと遅いとは思っていました。どのくらい遅いのか、今回きちんと計りました。

音声の長さ ローカル(medium / CPU)の所要時間
15分14秒 約10分40秒
1時間30分 10分以上待っても終わらず、途中で中止

音声の長さの0.7倍前後の時間がかかります。GPUがないので当然といえば当然ですが、1時間半の会議を回すと1時間近く待つ計算になります。実用上、これはつらい。

そこで、さくらのAI Engineの文字起こし(whisper-large-v3-turbo)に投げたらどうなるか、を試すことにしました。

図の④にある話者判別(resemblyzerで声質をクラスタリングし、参照音声と近ければ実名、遠ければ「話者A」のように匿名で分離する仕組み)は、今回の主題ではないので詳細は別記事に譲ります。ここで押さえておきたいのは1点だけです。この話者判別はfaster-whisper自体の機能ではなく、パイプラインが別途足している後処理だということ。理由は後述しますが、さくらとの精度比較ではこの工程を使いません。faster-whisper単体とさくらのWhisper単体を、文字起こしのみで比べます(第4章)。

2. その前に:私は「機密」の定義を間違えていた

ここが今回いちばんの収穫かもしれないので、先に書きます。

議事録は機密です。だからローカルで文字起こししている——私はずっとそう説明してきました。さくらに投げるのはダメだ、と。

ところが、上のパイプラインをよく見てください。[5] で逐語録の全文をGoogle Chatに投稿しています

つまり、「完全ローカル」ではありませんでした。文字起こしはローカルでも、成果物はGoogleのサーバーに渡っています。この時点で「ローカルだから安全」という説明は破綻しています。

では、これはアウトなのか。アウトではない、というのが私の結論です。ただし理由が違いました

実態 判定
ローカルのWhisper 自分のPC内 OK
Google Chat(自分のアカウントのプライベートスペース) Googleのサーバー上だが、自分のアカウント内。第三者には公開されない OK
さくらのAI Engine 他社(さくらインターネット社)の管理下に音声そのものを渡す NG

線引きは「クラウドか、ローカルか」ではありませんでした。「自分の管理下から出るか、第三者に渡るか」です。

この整理をしたことで、逆に判断がはっきりしました。Google Driveも、自分のアカウント内なら同じ扱いでいい。一方で、便利だからといって外部のAPIに機密音声を流すのは、ローカル/クラウドの区別とは無関係にNG

……という方針を立てた上で、今回の検証に限っては、精度を確かめるために一度だけ実音声を送るという判断をしました(自分のデータについて、自分で決めた例外です)。以下の精度比較は、その一度きりの実測結果です。

3. 速度の実測:機密を出さずに測る

まず速度です。ここは機密音声を使わずに測れるので、そうしました。

さくらのAI EngineのTTS(東北イタコ)に架空の会議進行テキストを喋らせて、約14.7分の非機密ダミー音声を合成し、それを文字起こしAPIに投げます。

ここで1つ壁がありました。

400 Bad Request: file too large (>31457280 bytes)

1リクエストあたり30MB(約30分)の上限があります。生成した24kHzのWAVは42.4MBで、そのままでは通りません。16kHzにダウンサンプリングして28.3MBに落とし、通しました。

長い会議を投げるなら、そもそも分割前提で組む必要があります。ここは実装コストとして見込んでおくべきポイントです。

結果です。

音声 所要時間
ローカル(medium / CPU) 実会議 15分14秒 約640秒
さくらのAI Engine ダミー 14.7分 約31秒

約20倍。後述する実会議音声での実測では18.1秒=約35倍でした。

速度に関しては、議論の余地なくさくらの圧勝です。 ここは自宅のCPUがどうこうという話ではなく、単純に土俵が違います。

4. 精度の実測:ここで結論がひっくり返った

さて本題です。実会議の音声(15分14秒)を両者に通し、話者ラベルとタイムスタンプを除いた"地の文"どうしで比較しました。

比較の公平性について(訂正)
最初、私は「ローカル版は話者分離ができる」ことをローカルの長所として比較表に入れていました。これは不公平です。話者分離は faster-whisper の機能ではなく、私のパイプラインが別途 resemblyzer で足している処理だからです。指摘を受けて、文字起こしのみの対等比較に直しました。

4-1. 最初、私は「ほぼ同等」と書いた

ラベルを除いて読み比べた最初の印象は、**「速度以外はだいたい同じ」**でした。そう報告しました。

これも間違いでした。 「ちゃんと読んだか」と指摘され、difflib.SequenceMatcher で機械的に差分を取り直しました。

import difflib
sm = difflib.SequenceMatcher(None, local_text, sakura_text)
print(f"一致率: {sm.ratio():.1%}")
for tag, i1, i2, j1, j2 in sm.get_opcodes():
    if tag != 'equal' and max(i2-i1, j2-j1) > 15:
        print(tag, local_text[i1:i2], '', sakura_text[j1:j2])

一致率 47.9%。 「ほぼ同等」どころではありませんでした。印象で語ってはいけないという、ごく当たり前のことを踏み直しました。

4-2. 出てきた差

差分を1件ずつ見ると、性質の違う失敗が3種類ありました。

項目 結果
速度 さくらが約35倍速い(640秒 vs 18.1秒)
句読点・可読性 ローカル優位。さくらは長区間で句読点がほぼ入らず、一続きの文になる
内容の完全性(脱落) ローカル優位。さくら側に発言まるごとの消失が十数箇所
ハルシネーション ローカル優位。さくら側のみ、同じ語が144回連続

4-3. 山場:ハルシネーションより「脱落」が怖い

ここが今回いちばん書きたかったことです。

ハルシネーションは有名です。今回もはっきり出ました。不明瞭な区間で、同じ短い語が144回連続で出力される——Whisper系で知られている繰り返しループです。ローカル側(vad_filter を有効にしている)では、同じ箇所で発生しませんでした。

追加検証(後日):「N=1では心もとない」と思い、手元にあった別の会議音声(同日の別セッション、約14分)でもう1本試しました。結果はさらに極端でした。「あ」という1文字が849回連続で出力され、一致率は18.8%。同じ音声をローカル(medium)で処理した方には、この反復は一切出ませんでした。2本中2本で、さくら側だけにハルシネーションが発生しています。

でも、より怖いのはもう一方でした。

ハルシネーション 脱落(オミッション)
何が起きるか 言っていないことを書く 言ったことを書かない
見た目 明らかにおかしい。同じ語の連打、文脈から浮いた文 完全に自然。普通の議事録に見える
気づけるか 気づける 気づけない
議事録での実害 「これは誤りだ」と分かるので、被害は限定的 合意事項や条件が丸ごと消えていても、誰も気づかない

今回、さくら側で発言まるごとが消えている箇所が十数箇所ありました。しかも消えたあとのテキストは、日本語として何の違和感もありません。「誰かが何かを言った」という痕跡すら残らない。

議事録の用途では、これは致命的です。 議事録は「何が話されたか」を後から参照するために作ります。参照できない部分があること自体に気づけないのなら、その議事録は信頼できません。

「AIの出力は間違えることがあるから確認しよう」という一般論は、ハルシネーションには効きますが、脱落には効きません。確認しようにも、原文と突き合わせない限り"無いもの"は見えないからです。突き合わせるなら、そもそも自動化した意味が薄れます。

4-4. N=1を増やそうとして分かった、もう一つの壁

「N=1は心もとない」という自覚は最初からありました。幸い、機密性の低い別の会議音声が手元に複数本(同日の別会議、それぞれ独立した内容)あったので、追加で試すことにしました。

最初の8本を続けて投げたところ、成功したのは1本だけでした。残りは429 rate limit exceeded(リクエスト過多)。呼び出し間隔を90秒に広げ、失敗時は30秒→60秒→120秒→240秒と伸ばしながら4回まで再試行しても、状況は変わりませんでした。

念のため、さくらの利用量画面も確認しました。この時点で文字起こしの消費は13リクエスト、料金は0円。月あたりの無料枠(50リクエスト)には全く余裕があります。「送りすぎて怒られた」わけではなさそうでした。

さくらAI Engineの利用量画面。文字起こしは13リクエスト、料金は0円
sakura-usage-panel-04.png

ちょうどこのとき、社内の別の監視の仕組み(さくら経由の判断ルーティングが正常に動いているかを見張っているもの)が「さくら側の障害が続いている」と検知していたので、サービス側の一時的な不調を疑いました。実際、チャット(gpt-oss-120b)と読み上げ(TTS)は問題なく動いていたので、障害は文字起こしのエンドポイントだけに絞られていることも確認できました。

ただ、話はそこで終わりませんでした。

**時間を空けても、エラーの種類を変えても(429500 Upstream server error)、ファイルサイズを変えても、30MBの上限に収まるよう分割しても——**結果はほとんど変わりませんでした。手元にある実会議音声を次々に試した最終集計がこちらです。

対象 試行回数 成功
通常サイズの音声(12〜14MB、6種類) 各1〜2回 0/7
30MB超の音声を2分割したもの(2本×2セット=4ファイル) 各1〜2回 0/8
同じ1本の音声(約13MB) 6回 6/6
合計 22回 6/22

22回中、成功したのは特定の1本だけで、それが6回中6回とも成功しました。 それ以外は、サイズを変えても、分割しても、時間を置いても、一度も通りませんでした。

「じゃあその1本だけ何か特別なのでは」と思い、無音区間の割合や音量レベルを実際に解析して比べました。 「冒頭が無音だと失敗するのでは」という仮説も立てましたが、これは測って否定されました。むしろ成功した音声の方が、無音の割合(冒頭10秒で25% vs 8%)も全体の無音割合(7.7% vs 4.8%)も多かったのです。音量レベルはほぼ同一。単純な音響的特徴では、成功と失敗を説明できませんでした。

正直に言うと、原因は最後まで特定できていません。 ただ、現象としては非常にはっきりしています。同じ形式・同じような長さのファイルでも、通るものと通らないものが、はっきり決まって(毎回同じ結果で)いる。 これはもう「たまたま混雑していた」では片づけられない挙動です。

これは実運用の観点でも重い意味を持ちます。精度が仮に良かったとしても、「送ったファイルが処理されるかどうか自体が読めない」のでは、パイプラインとして組み込めません。 議事録は「今日の会議を今日中に記録する」ことに価値があります。22回中20回失敗するサービスに、大事な会議の記録を預ける判断はできません。

5. なぜ3次記事では気づけなかったのか

冒頭に書いた通り、私は3次記事で**「一致率88.8%で実質互角」**と結論しました。今回は47.9%。同じ2つのエンジンを比べて、なぜこれほど違うのか。

答えは検証データです。

3次記事の検証 今回の検証
音声 TTSで合成した読み上げ 実際の会議の録音
話者 1人 複数人
音質 クリーン(ノイズなし) 生活音・マイク距離のばらつきあり
無音・不明瞭区間 ほぼ無い たくさんある
発話の重なり 無い ある(相槌、かぶせ気味の返事)
正解データ ある(元の原稿) 無い(相対比較のみ)

念のため補足すると、3次記事のTTS検証でローカル側が無傷だったわけでもありません。あちらでは「助成金」を「女性菌」と書くような、意味が壊れる誤りがローカル側に出ていました(さくら側の誤りは「三件→3件」のような表記の正規化で、意味は保たれていました)。同じ2つのエンジンでも、データを変えると出る弱点が変わる——これも今回の趣旨そのものです。

なぜTTS音声を使ったのか——「機密を外に出さない」の帰結

ここは弁解ではなく、構造の話として書いておきたいところです。

私は会議の音声も逐語録も、第三者のサーバーには出さないという方針でやっています(第2章の線引き)。この方針を守る限り、さくらのAPIに実会議音声を投げて精度を測ることは、そもそもできません

だから3次記事では、TTSに喋らせた非機密の合成音声を使いました。正解テキストが既知で、繰り返し再現でき、何より外に出しても誰にも迷惑がかからない。検証データとしては理想的に見えました。判断として間違っていたとは、今でも思いません。

ただ、結果的にこうなりました。

機密を守るために本番データを避けた結果、本番でしか起きない失敗が見えなくなっていた。

ハルシネーションも脱落も、まさに"きれいでない部分"——無音・不明瞭・複数人の重なり——で起きます。そしてTTS合成音声には、それが構造的に存在しません。守るべきものを守った結果として、検証の網に穴が空いていたわけです。

これは私の不注意というより、**「機密データを扱う人が、外部サービスの精度を検証しようとすると必ずぶつかる壁」**だと思います。本番データは出せない。出せるデータは本番と違う。この矛盾は、方針を守る限り消えません

今回それでも測れたのは、「一度だけ、精度確認のために実音声を送る」と自分で決めて例外を切ったからです。自分のデータについて自分で決めた例外なので誰にも咎められませんが、会社の業務データなら同じ手は使えません。その場合は「ダミーではなく、本番に近い雑音・複数話者・言い淀みを含んだ非機密の音声を、検証用にわざわざ作る」くらいしか手がないと思います。そこまでやって初めて、検証は本番の代理になります

つまり私は、自分が想定した失敗しか起きないデータで検証して、「問題ない」と結論していたわけです。

自分で作ったテストデータで測れるのは、自分が想定した失敗だけ。

これは音声認識に限った話ではないと思います。「テストは通っています」と言うとき、そのテストが本番の何を再現していないかのほうが、たぶん重要です。

6. 結論と、正直に書いておくこと

決めたこと

議事録パイプラインのさくら移行は見送ります。 速度は35倍魅力的ですが、脱落する議事録は使えないからです。

そしてこれは、機密性の線引き(第2章)とは独立した判断です。仮に「機密でない会議なら外に出してよい」と整理できたとしても、精度の理由で採用しません。当初は「機密のものはローカル、そうでないものはさくら」と使い分けるつもりでしたが、その分岐自体が不要になりました

「さくらのWhisperは精度が低い」とは書きません

ここは慎重に書きます。私の実測は実会議音声2本・N=2です。

同じ whisper-large-v3-turbo でも、録音環境・話者の数・言い淀みの量が変われば結果は変わり得ます。 私の音声は無音・不明瞭区間が比較的多い録音だった可能性があり、それがハルシネーションを誘発しやすい条件だったのかもしれません。また、私は逐語レベルで全文を突き合わせて脱落を数えましたが、「聞いた印象」だけの評価では、脱落はそもそも見えません(原文と突き合わせない限り、無くなった部分には気づけないからです)。

だから正しい結論は「さくらは精度が低い」ではなく、「私の2回の実測ではハルシネーションと脱落を観測した。一般的な傾向は不明で、複数音声・複数環境での再現確認が必要」です。

この記事を読んで「さくらのWhisperはダメらしい」と受け取らないでください。 読み取ってほしいのは、あなたの用途の本番データで測ってください、ということです。

用途が違えば、判断も違います

今回の結論は「議事録」という用途に紐づいています。

用途 脱落の許容度 35倍の速度 向き
議事録・記録として後から参照 許容できない あれば嬉しい ローカル向き
大量音声のざっくり検索・タグ付け 許容できる(漏れても再検索できる) 効く さくら向き
リアルタイム字幕・下書き(人が直す前提) 許容できる 必須 さくら向き

35倍の速度は、それ自体が機能です。 「多少漏れてもいいから全部の音声に索引をつけたい」なら、ローカルCPUで実時間の0.7倍かけるのは非現実的で、さくら一択でしょう。私が採らなかったのは、議事録という用途では、速さで買えないものが欠けていたからにすぎません。

まとめ

  • ローカル(CPU / medium)は音声の長さの0.7倍かかる。さくらのAI Engineは約35倍速い。速度は勝負にならない。
  • しかし生の会議音声で精度を測ると、速度以外はローカルが優位だった(1本目:一致率47.9%、ハルシネーション144回連続、発言の脱落が十数箇所。追加の2本目:一致率18.8%、ハルシネーション849回連続)。議事録用途としては見送り
  • N=1を増やそうとしただけで22回中16回が失敗した(成功はすべて特定の1本、6/6)。エラーの種類(429→500)もサイズ(12MB台〜22MB分割)も様々に変えたが改善せず、無音区間・音量を解析しても違いを説明できなかった。原因不明のまま、成功・失敗がファイルごとに固定されるという不可解な挙動が残った。
  • ハルシネーションより脱落のほうが怖い。 前者は読めば分かるが、後者は自然な文章として読めてしまうので、消えたことに気づけない。「AIは間違えるから確認しよう」は、脱落には効かない。
  • 3次記事で「互角」と書けたのは、TTS合成音声で検証していたから。 ノイズも無音も話者の重なりも無いデータでは、そこで起きる失敗は原理的に観測できない自分で作ったテストデータで測れるのは、自分が想定した失敗だけ
  • そのTTS音声を使った理由は「会議の内容を第三者に渡さない」という方針だった。 守るべきものは守ったが、その結果本番でしか出ない失敗が検証の網から漏れた機密を扱う人が外部サービスを検証しようとすると、必ずこの矛盾にぶつかります——本番データは出せない、出せるデータは本番と違う。抜けるとしたら「本番に近い雑音・複数話者・言い淀みを含んだ、非機密の検証用音声をわざわざ作る」あたりだと思います。
  • 機密の線引きは「クラウドかローカルか」ではなく「自分の管理下から出るか、第三者に渡るか」。この定義に直したら、自分のパイプラインが最初から"完全ローカル"ではなかったことに気づいた。
  • そして今回、私は3回間違えて、3回撤回しました。「ローカルは話者分離できる」を長所として比較に混ぜたこと、読んだ印象で「ほぼ同等」と言ったこと(difflibで測ったら47.9%)、そして固有名詞の正確さを「ローカルの強み」として比較に入れたこと(実際は後処理辞書の効果で、さくら側に同等の後処理を与えていない不公平な比較だった)。印象で書いて、測って撤回する——この工程が要るということ自体が、たぶん今回いちばん実務的な学びです。

質問・ツッコミ歓迎です。特に次の2つは、ぜひ教えていただきたいです。

  • 「うちの会議音声では違う結果だった」という報告。 サンプル数をもっと増やしたいので。
  • 話者判別(第1章)の改善案。 ここはまだ模索中です。合成音声では綺麗に分かれるのに実会議では崩れる、という典型的なところで止まっています。pyannote.audio に移すべきか、そもそもアプローチから変えるべきか、実運用されている方の知見をいただけると助かります。

余興:「見送り」と書かれた側に、講評してもらった

このシリーズでは毎回、記事そのものを gpt-oss-120b(さくらのAI Engine上のモデル)に読ませて講評をもらっています。今回は特に意地の悪いお願いになりました。

同じサービス群の一員に、「お前のところの文字起こしは、精度で見送られた上に、22回中16回も処理自体が失敗した」という記事を読ませて、感想を聞く——という構図です。身内をかばうのか、それとも卑下するのか。

条件はいつも通り(temperature=0 / max_tokens=1500)。今回は、精度検証の直後(N=1の時点)と、可用性の調査まで終えた最終版の、2段階で講評をもらいました

1回目(精度検証の直後・N=1時点):

筆者はTTSと実音声で2回測定し、結果が大きく変わった点を正直に示している点は評価できるが、N=1かつ比較対象の前処理差が残るため統計的妥当性は低い。読者は、テストデータは本番に近いノイズ・多話者条件を必ず含め、評価指標に脱落率も加えて検証すべきだと学ぶべきである。

2回目(22回の可用性検証を終えた最終版):

本件で示された22回中6回しか成功しないという可用性の低さは、実運用で致命的です。API側の安定性とエラーハンドリング、リトライ・バックオフ設定の改善が必須です。読者は、速度だけでなく成功率・障害耐性を評価指標に入れ、実データでの耐障害性を検証することを教訓とすべきです。

— gpt-oss-120b(さくらのAI Engine)

両方とも、かばいも卑下もしませんでした。 それどころか、1回目は私の検証の弱点(N=1、前処理差の不公平さ、脱落率を独立指標にしていないこと)を、2回目は自分たちのサービス側の問題(可用性・エラーハンドリング)を「致命的」と言い切っています

  • 1回目の「評価指標に脱落率も加えて」は、今回まさに刺さりました。可用性という、精度以前の指標が抜け落ちていたことに気づいたのは、その後の追加検証があったからです
  • 2回目の「実運用で致命的」は、身内をかばうどころか、私よりも踏み込んだ評価です。私は「議事録用途では見送り」と書きましたが、gpt-oss-120bは**用途を限定せず「実運用で致命的」**と言っています

自分の同僚(同じ基盤の音声認識)に、精度の欠点も可用性の欠点も両方読ませて、返ってきたのが2回とも擁護でも謝罪でもなく、具体的な弱点の指摘だった——これが一番実務的な反応だと思います。

関連記事

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?