12
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Whisper API】無音時に「ご視聴ありがとうございました」?ハルシネーション対策まとめ

12
Last updated at Posted at 2026-04-08

はじめに

私は、MENRENというAI面接練習アプリを開発していて、ユーザーが話した内容をWhisper APIで文字起こしする機能を作っています。

音声の書き出しの能力はとても高い。問題は、無音時のデータ送信の時です。

無音データの送信後に返ってきた文字起こし結果がこちら👇

「ご視聴ありがとうございました」

!?......こわ、ご視聴されたいマンがいるやん!

調べてみると「ハルシネーション(幻覚)」でした。無音の音声データを渡すと、存在しない言葉を勝手にでっちあげるみたいです。

ということで、この記事では、ハルシネーションで何が起きるのか、どうやって対策したのかをまとめてみました。

同じ問題で困っている方の参考になると幸いです!

そもそもWhisper APIのハルシネーションって何?

Whisper APIは、OpenAIが提供している音声を文字に変換してくれるAPIです。普通に音声を渡せばかなり正確に文字起こししてくれますが、無音やBGMだけの音声を渡すと、ハルシネーション(何も話していないのに、それっぽい文章を勝手に生成する)問題が起こります。

ここでポイントなのですが、「完全に無音」だとハルシネーションは起こりません。「階段を上がるトントントン」とう音や「PCから出るBGM」 がマイクに入り込むとハルシネーションが起こります。

どんな言葉を生成するの?

実際に無音の音声を何度もWhisper APIに投げてみて、どんな言葉が返ってくるか集めてみました。

生成された幻覚テキスト 出現頻度
ご視聴ありがとうございました よく出る
ご視聴ありがとうございます よく出る
ありがとうございました よく出る
チャンネル登録お願いします たまに出る
おやすみなさい たまに出る
お疲れ様でした たまに出る
ご清聴ありがとうございました たまに出る
最後までご視聴ありがとうございました たまに出る

... YouTube動画っぽいフレーズばっかだな :thinking:

調べたところ、Whisperの学習データにYouTubeの動画が大量に含まれているそうです。無音部分で「動画の終わりだな」と判断して、それっぽい締めの言葉を生成しちゃうそう。

[参考]
What OpenAI's Whisper Teaches Us About The Dependence Of The Large Models Revolution On YouTube
AI Model Biases: What went wrong with Whisper (Gladia)

繰り返しパターンもある

やっかいなのが、同じフレーズを何度も繰り返すパターン。

ご視聴ありがとうございました。ご視聴ありがとうございました。ご視聴ありがとうございました。

Whisper APIでの対策方法

MENRENでは、APIから返ってきた結果を見て、「これは幻覚っぽいな」と判断した箇所をフィルタリングする対策を取りました。

大きく分けて2つの検出方法を組み合わせています。

対策1: no_speech_prob を使った検出

Whisper API に response_format: "verbose_json" を指定すると、文字起こし結果にセグメント(区間)ごとの詳しい情報がついてきます。

response = client.audio.transcribe(
  parameters: {
    model: "whisper-1",
    file: audio_file,
    language: "ja",
    response_format: "verbose_json"
  }
)

返ってくるJSONはこんな感じです。

{
  "text": "ご視聴ありがとうございました",
  "segments": [
    {
      "text": "ご視聴ありがとうございました",
      "start": 0.0,
      "end": 5.0,
      "no_speech_prob": 0.95
    }
  ]
}

この中にある no_speech_prob がポイントです。これは「このセグメントに音声が含まれていない確率」を0〜1の数値で表しています。

  • 0.95 → 95%の確率で音声なし → ほぼ確実に幻覚
  • 0.1 → 10%の確率で音声なし → ちゃんと話してる

なので、全てのセグメントの no_speech_prob が高かったら幻覚と判定するようにしました。

NO_SPEECH_PROB_THRESHOLD = 0.5

def detect_no_speech!(response)
  segments = response["segments"]
  return if segments.blank?

  all_no_speech = segments.all? { |seg| seg["no_speech_prob"].to_f >= NO_SPEECH_PROB_THRESHOLD }
  if all_no_speech
    raise NoSpeechError, "音声が検出できませんでした。もう一度録音を開始してください。"
  end
end

閾値は 0.5(50%)にしています。全セグメントが「まあ音声ないっぽいな」だったらエラーにする、という考え方です。

対策2: パターンマッチによる検出(安全網)

ただ、no_speech_prob だけだとすり抜けるケースがありました。

テストしてみると、無音なのに no_speech_prob0.1 とか低い値で返ってくることがあって、「え、話してないけど!?」となりました。

なので、既知の幻覚パターンとの文字列マッチングを安全網として追加しました。

HALLUCINATION_PATTERNS = %w[
  ご視聴ありがとうございました
  ご視聴ありがとうございます
  チャンネル登録お願いします
  おやすみなさい
  お疲れ様でした
  ありがとうございました
  ご清聴ありがとうございました
  最後までご視聴ありがとうございました
].freeze

文字起こし結果がこのリストに完全一致したら、幻覚と判定します。

def detect_hallucination!(text)
  # 句読点とスペースを除去して比較
  stripped = text.gsub(/[。、\s\u3000]/, "")

  if HALLUCINATION_PATTERNS.include?(stripped)
    raise NoSpeechError, "音声が検出できませんでした。もう一度録音を開始してください。"
  end

  # 繰り返しパターンの検出
  sentences = text.split(/[。、]/).map(&:strip).reject(&:empty?)
  return if sentences.size < 3

  unique_sentences = sentences.uniq
  if unique_sentences.size <= 2 && unique_sentences.any? { |s| HALLUCINATION_PATTERNS.include?(s.gsub(/[\s\u3000]/, "")) }
    raise NoSpeechError, "音声が検出できませんでした。もう一度録音を開始してください。"
  end
end

ここでのポイントは3つあります。

1. 句読点を除去してから比較する

Whisperは同じ幻覚でも「ご視聴ありがとうございました」「ご視聴ありがとうございました。」と句点の有無がバラバラなので、句読点を除去してから比較します。

2. 繰り返しも検出する

「ご視聴ありがとうございました。ご視聴ありがとうございました。ご視聴ありがとうございました。」みたいな繰り返しパターンも検出します。句点や読点で分割して、同じフレーズが3回以上繰り返されていたらアウトです。

3. 正常な回答を間違えて弾かないようにする

「ありがとうございました」は幻覚パターンに入っていますが、「本日はお時間をいただきありがとうございました。私は田中太郎と申します。前職では〜」みたいに長い文章の一部に含まれているだけなら正常な回答です。完全一致チェックなので、長い文章がマッチすることはありません。

2段構えにすることで、no_speech_prob がすり抜けても幻覚パターンでキャッチできますし、未知の幻覚パターンでも no_speech_prob が高ければキャッチできます。

テストもちゃんと書こう

幻覚検出は「正常な回答を間違えて弾いてしまう」のが一番こわいので、テストはしっかり書きました。

# 幻覚パターンに完全一致 → エラーになる
it "既知の幻覚パターンに完全一致する場合は NoSpeechError を raise する" do
  allow(@audio_double).to receive(:transcribe).and_return(
    verbose_json_response("ご視聴ありがとうございました", no_speech_prob: 0.1)
  )
  expect { described_class.call(file) }.to raise_error(
    described_class::NoSpeechError
  )
end

# 幻覚フレーズを含むが長い回答 → スルーする
it "幻覚フレーズを含むが長い回答はスルーする" do
  long_answer = "本日はお時間をいただきありがとうございました。私は田中太郎と申します。前職では大手IT企業でバックエンドエンジニアとして5年間勤務しておりました。"
  allow(@audio_double).to receive(:transcribe).and_return(
    verbose_json_response(long_answer, no_speech_prob: 0.02)
  )
  result = described_class.call(file)
  expect(result).to eq(long_answer)
end

特に大事なのが2つ目のテストです。「ありがとうございました」を含む正常な面接回答がちゃんとスルーされることを確認しています。

フロントエンドでのエラー表示

バックエンドで幻覚を検出したら、フロントエンドには422エラーとしてエラーメッセージを返します。ユーザーには「音声が検出できませんでした。もう一度録音を開始してください。」と表示されます。

「ハルシネーションが検出されました」なんて言われてもユーザーは困りますからね。ユーザー目線では「音声が拾えなかった」という説明がわかりやすいです。

まとめ

対策 方法 向いているケース
no_speech_prob チェック APIレスポンスの数値で判定 Whisper APIを使っている場合
パターンマッチ 既知の幻覚テキストと照合 no_speech_probの安全網として

Whisper APIを使っている場合は、no_speech_prob + パターンマッチの2段構えがおすすめです。


宣伝:MENREN — AI面接練習アプリ

今回の対策を実装したのは、MENREN(めんれん) というAI面接練習アプリです。

エンジニアの転職面接に特化していて、AIの面接官が質問を出してくれて、あなたの回答を音声で録音 → Whisper APIで文字起こし → AIがフィードバックしてくれます。

  • 面接官AIが回答内容を踏まえて深掘り質問してくれる
  • 練習を重ねるとスコアが上がって成長が見える
  • エンジニアなら「技術説明力」「具体性」など専門的な軸で評価してくれる

無料で使えるので、転職活動中のエンジニアの方はぜひ試してみてください!

👉 MENREN - AI面接練習アプリ


間違っている内容やもっと良い対策方法があれば、コメント欄にて(優しく)ご指摘いただけるととても嬉しいです!

12
7
4

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
12
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?