テックブログ
無音の録音から医療AIが診療会話を作るとき、何ができるか
目次
音声から記録の下書きを作るとき、いちばん怖いのはAIが間違えることではありません。間違えたことが分からないことです。
録音が最初から最後まで無音だったとき、AIは何を返すか。直感的には「何も書けません」と返しそうなものですが、実際は違います。流暢で、体裁の整った、読んだ人が本物と区別できない診療会話を返してきます。
無音から、何が返ってくるのか
手元で確かめました。信号がまったく無い音声を用意し、記録を作る処理にそのまま流します。同じ音声を繰り返すと結果が使い回されるので、患者名のヒントだけを毎回変えて独立した試行にしています。
| モデル | 試行 | 診療会話が作られた | 生成された文字数 |
|---|---|---|---|
| 現行モデル | 8回 | 8回すべて | 410 / 421 / 422 / 434 / 447 / 496 / 603 / 775 |
| ひとつ前のモデル | 8回 | 成功した6回すべて | 63 / 170 / 171 / 240 / 263 / 414 |
成功した14回すべてで、無音から診療会話が作られました。 実際に返ってきた文章はこういうものです(氏名はダミー、実在する薬剤名が出た箇所はそのまま引用しています)。
山田さん、こんにちは。最近の体調はいかがですか? こんにちは。最近は、夜中に何度も目が覚めてしまって、なかなかぐっすり眠れないんです。 中途覚醒があるのですね。日中の眠気や倦怠感はどうでしょうか? ……現在、デエビゴを服用されていますが、効果はあまり感じられませんか?
読んでいただくと分かりますが、症状の訴え・医師の問い返し・実在する薬剤名・効果の確認まで揃っています。 睡眠障害の外来として自然な流れになっていて、これが無音から出てきたとは読み取れません。
この経路のどこにもエラーが立ちません。 文字起こしは成功し、様式に沿った項目がすべて埋まり、保存も通ります。人が読んで気づく手がかりは、その日その患者さんとそんな話をしていない、という記憶だけです。
「量」では見分けられない
最初に考えるのは「短すぎる出力を疑う」という方法です。これは成立しません。
上の表をもう一度見てください。同じ長さの、同じ無音から、63字のときも775字のときもあります。12倍の開きです。書かれた量は入力とほとんど関係がなく、その回にモデルがどれだけ書く気になったかで決まります。
つまり、文字数でも、1秒あたりの密度でも、閾値を引けません。 引けば、寡黙な患者さんの短い診察が引っかかり、饒舌に捏造された記録が素通りします。
見分け方の候補は他にもありました。実際に試した結果を並べます。
| 見分け方の案 | 何を見るか | 試した結果 |
|---|---|---|
| 出力の文字数・密度 | AIが書いた量 | ❌ 同じ無音から63字にも775字にもなる。入力と関係がない |
| ファイルのバイト率 | 1秒あたりのデータ量 | ❌ 無音でも毎秒3,627バイトの録音があり、実際の会話で毎秒1,072バイトの録音もある。大小が逆転する |
| 音量の最大値だけを見る | ピークが小さければ無音 | ❌ 単発のクリック音1発で、ほぼフルスケールに達する無音録音がある |
| 一定より小さい時間の割合 | −35dBを下回る割合 | ❌ 小声の実際の発話も100%が無音と判定される |
| モデルを替える | 別のAIに切り替える | ❌ 現行・ひとつ前とも、成功した試行はすべて捏造した |
| 薄い出力をまとめて失敗させる | 出力が短ければエラーにする | ❌ 実際の録音で試すと、静かで短い正常な記録がまとまった数だけ巻き添えになった |
これは、1つの製品の話ではない
同じ現象は、音声AIの分野で繰り返し報告されています。数字はすべて公開されている一次資料から取りました。
| 出典 | 何を測ったか | 結果 |
|---|---|---|
| Barański et al. 2025(AGH Kraków) | 非音声の音 301,317 件を Whisper に入力 | 40.3% で何かが生成された |
| 同上 | 実際の発話に無音を継ぎ足した場合 | 17.1% で幻覚。継ぎ足すだけで起きる |
| Koenecke et al. 2024(ACM FAccT) | 失語症のある話者と対照群の発話(AphasiaBank)13,140 区間 | 幻覚を確実に起こす区間 187。幻覚のうち38%が有害な内容(暴力の描写・架空の名前や健康状態・偽のURL) |
| Calm-Whisper 2025(Interspeech) | 環境音のみ 8,732 件 | Whisper は 99.97% で何かを出力。空を返せたのは3件 |
| When Silence Matters 2025(NTU) | 音声対応の大規模言語モデルに無関係な5秒の無音を足す | 正答率が一貫して低下。無音は中立な入力ではない |
医療の現場でも実例が出ています。カナダ・オンタリオ州の監査局は 2026年5月、承認済みのAIスクライブ20社を模擬診察で調達時テストし、20社すべてで不正確さを検出しました。うち9社は話されていない治療方針を作り、12社は医師の処方と違う薬剤を記録しています。
オーストラリアでは 2026年8月、術後に専門医からかかりつけ医へ送られた手紙にまったく身に覚えのない薬物使用歴が書き加えられていた事例が報じられました(患者さんが気づいたのは同年3月)。この事例で重いのは捏造そのものよりも、医師が発生源を特定できなかったことです。元の音声が残っていなければ、何が起きたのかを後から確かめる手立てがありません。
だから私たちは、元の音声を消しません。 記録が疑わしいと思ったとき、聞き直せる状態にしておくことが、この種の誤りに対する最後の砦になります。
「無音なら空を返して」は、指示では効かない
次に思いつくのは、AIへの指示文で頼む方法です。これは実証で否定されています。 独立した3つの証拠があります。
- Whisper の事前指示では、「言葉を作るな」と書くとその指示文そのものが文字起こしとして出力されることが報告されています。指示は文脈としてモデルに入るので、繰り返す対象が1つ増えるだけです。
- 音声対応の大規模言語モデルの実測(NTU 2025)では、緩和のための指示を入れても改善は一貫せず、悪化した条件もありました。論文は「単一の明示的な指示では、モダリティをまたいだ干渉を体系的に打ち消せない」と結論しています。
- 音の幻覚を狙った攻撃の研究(arXiv:2603.29263)では、思考の手順を書かせる指示は明示的な攻撃には一部効いたものの、暗黙の攻撃には効かず、ある条件では攻撃の成功率が 68.74% から 82.90% に上がりました。大きく効いたのは学習時の介入(DPO)でした。
幻覚対策をプロンプトに担わせない。 これは私たちが実装で採った前提です。指示文は書き換えられるし、モデルを替えれば効き方も変わります。指示に依存する守りは、守りではありません。
だから、AIに渡す前に音を測る
見分けようとするのをやめて、そもそも無音をAIに見せないことにしました。前段で音のあるなしを判定する方法は、Whisper でも幻覚率 21.3% → 0.2%という実測があり、効果がもっとも大きい打ち手です。
判定式はこうしました。
「連続して1秒以上」ではなく「合計で1秒以上」にしたのが要点です。 最初は連続で書いていました。ところが、遠いマイクで拾った減衰の大きい会話を通すと、1語ずつが1秒に届かず、実際の会話が無音と判定されます。会話は本来とぎれとぎれです。連続を条件にすると、静かに話す人ほど止められます。
判定そのものは短い関数です。1ミリ秒ぶんの二乗平均(RMS)を取り、しきい値を超えた時間を足していくだけで、合計が1秒に達した時点で「有音」と決めて読むのをやめます。
export const SILENCE_LEVEL_DBFS = -50; // 有音とみなす下限
export const SILENCE_MIN_VOICED_SEC = 1.0; // 合計でこれだけ超えたら有音
const threshold = Math.pow(10, SILENCE_LEVEL_DBFS / 20); // dBFS を振幅に直す
const win = Math.round(sampleRate * SILENCE_RMS_WINDOW_MS / 1000);
const need = Math.round(sampleRate * SILENCE_MIN_VOICED_SEC);
for (let i = 0; i + win <= pcm.length; i += win) {
let sum = 0;
for (let j = i; j < i + win; j++) { const v = pcm[j]; sum += v * v; }
if (Math.sqrt(sum / win) > threshold) { // この窓は有音
voicedSamples += win;
if (voicedSamples >= need) return true; // 合計1秒に達したので打ち切り
}
}同じ関数を録音中のブラウザと、保存後のサーバの両方が使います。前者は AudioWorklet から、後者は復号したデータから、同じ配列を食わせるだけです。定数もコードの中核も、機械が両者の一致を毎回検査しています。
しきい値の −50dBFS と、合計1秒という長さは、勘で決めていません。手元の音声で4つの論点を1つずつ測りました。
| 論点 | 実測 |
|---|---|
| −50dBFS で足りるか | 無音側は一度も超えない(最大でも −68.7dBFS。単発のクリック音は超えるが1秒続かない) |
| 小声を誤検知しないか | 実際の発話でもっとも短いものでも有音が11.6秒ある。0秒とは断絶している |
| 1秒という長さは妥当か | 単発のクリック音(ピーク −0.089dBFS)を無音と正しく判定できる最小の長さ |
| もっと厳しくできるか | できるがする必要が無い。実発話の最小ピークは −15.2dBFS で、−50dBFS までは 35dB の余裕がある |
手元の音声で通した結果は、無音の検出 20/20・実発話の誤検知 0/85 です。捕まえたいのは「信号がまったく無い」録音だけで、小声を捕まえにいってはいけないからです。
測れなかったことを「異常なし」と書かない
判定は2値ではなく3つにしました。有音・無音・測れなかったの3つです。
対応していない形式、音声トラックの無いファイル、復号に失敗したもの、決めた時間内に読み切れなかったもの——これらを「無音」に丸めると正常な録音を止めてしまい、「有音」に丸めると測っていないのに合格させてしまう。どちらも間違いです。だから3つ目を作り、理由まで記録に残しました。「対応していない形式だった」のか「時間内に読み切れなかった」のかが、後から区別できます。
記録にはこの形で残ります。状態と理由が別々の列に入るので、後から「対応していない形式が何件あったか」を数えられます。
type SilenceVerdict = 'voiced' | 'silent' | 'unmeasured';
type AudioCheck =
| { verdict: 'voiced' } // AI に渡す
| { verdict: 'silent'; reason: 'no_voiced_run' } // 止める
| { verdict: 'unmeasured'; reason: 'unsupported_format' // 測れなかった
| 'no_audio_track'
| 'decode_failed'
| 'budget_exhausted' };原因が違うのに同じ結果を返すと、その先で原因に辿り着けなくなります。
守りは、置いた場所によって呼び忘れられる
もうひとつ、設計で重く見た点があります。
音声を取り出す処理は、記録様式ごとに5か所ありました。ここに「音を測る関数」を用意して、5か所から呼ぶ形にすると——いつか1か所で呼び忘れます。 そして呼び忘れたことは、その様式で事故が起きるまで誰も気づきません。
同じ様式が5つ並んでいる構造では、1つを直すときに残り4つがコピー元になります。コピーで増えたものは、コピーで直し忘れます。 守りを何層も足しても同じ1か所だけが全部の層を素通りする——これは私たちが検査の有効範囲を測ったときに実際に見た形でした。
そこで、音声を取り出す入口そのものを1つにまとめました。
「呼ぶことになっている」ではなく「通らないと読めない」形にする。 前者は人の注意力に頼る仕組みで、後者は構造です。新しい記録様式を足しても、音声を読む以上この入口を通るので、守りが自動的に付いてきます。
無音と判定したときは、AIを呼ばず、記録は空のまま、音声はそのまま残します。 現場には「録音できていません」と伝わり、聞き直すこともできます。捏造より欠落に倒す、というのがここでの判断です。
長い録音を、どう測るか
40分の録音を頭から全部復号すると、判定だけで時間がかかりすぎます。そこで時間を散らして拾い読みします。ここで2つ踏みました。
1つ目。 最初は「一定間隔で1秒ぶんだけ復号する」形にしていました。測ってみると、62分の録音の中に15秒だけ会話があるという難しい音声で、5回中3回「無音」と判定されました。窓が1つだと、その1秒が会話とすれ違うと外れます。
同じ量を読むなら、1か所にまとめるより散らしたほうが当たります。 0.2秒の窓を5つに分けて同じ間隔に散らす形へ変えたところ、同じ音声で5回中5回「有音」になりました。読む総量は変えていません。
| 拾い読みの方式 | 読む総量 | 62分中15秒だけ会話がある音声 | 完全な無音(陰性対照) |
|---|---|---|---|
| 1秒の窓を1つ | 同じ | 5回中2回しか当たらない | 正しく無音 |
| 0.2秒の窓を5つに散らす | 同じ | 5回すべて当たる | 正しく無音 |
判定は「どのフレームを復号するか」を決めるだけの関数に落としてあります。間引く幅(stride)は、尺の見積もりと残り時間から毎回その場で決まります。
const SAMPLE_WINDOW_FRAMES = 50; // 20ms × 50 = 1秒ぶん。1周期に復号する量
const SAMPLE_SUB_WINDOWS = 5; // その1秒を5回に分けて置く(= 0.2秒 × 5)
export function shouldDecodeFrame(seen: number, stride: number): boolean {
if (stride <= 1) return true; // 間引く必要が無ければ全部読む
const sub = Math.round(SAMPLE_WINDOW_FRAMES / SAMPLE_SUB_WINDOWS); // 0.2秒 = 10フレーム
return (seen % (stride * sub)) < sub; // 0.2秒読んで、あいだを飛ばす
}読む総量は変えずに置き方だけを変えた結果が上の表です。誤検知は増えていません(62分の完全な無音・1時間42分の全編無音とも正しく無音のまま、実音声89本でも判定は1件も変わりませんでした)。所要時間も最長 1,343ms で、予算の 1,500ms に収まっています。
2つ目は、守りを足したことで新しく生まれた壊れ方でした。 音声を読み進める処理に「読みすぎないための上限」を入れていたのですが、実行環境の仕様で音声全体が一度に1かたまりとして渡ってきます。上限を1かたまり目で超えるので、上限を超えた録音が丸ごと読めなくなる——つまり、大きい録音ほど落ちる形になっていました。本番に出す前に見つけて、決まった大きさずつ切り出して読む形に直しています。
これは「守りを足すと、守る対象を壊す経路が新しくできる」典型でした。入れたガードが成立しない条件を必ず1つ考える。 そう決めて、上限のちょうど前後の大きさの音声を作り、実際に落ちることを確かめてから直しました。
法令の側から見ると、どうなるか
日本では、AIを使った診療の責任の所在はすでに整理されています。厚生労働省の通知(医政医発1219第1号・平成30年12月19日)は、研究報告書の「判断の主体は少なくとも当面は医師である」を引き、「診断、治療等を行う主体は医師であり、医師はその最終的な判断の責任を負う」としています。
この前提は、医師が確認できる状態があって初めて成り立ちます。 生成されたものが本物と見分けられないなら、確認しようがありません。「医師が確認することになっている」という建前だけでは、責任の所在を決めたことにはなりません。
同じ考え方は運用側の整理にも現れています。医療情報システムの安全管理に関するガイドライン第6.0版のQ&A(令和7年5月)は、「誤入力等を問題ないレベルにまで低減する技術的方法は存在しないため、入力ミス等は必ず発生するとの認識の下」対策を講じることを求めていました。誤りは起きる前提で、起きたときに気づける形にしておく——今回の設計はその線に沿っています。
まとめ
録音した音声が、あとで聞き直せる状態になっているか。記録が空になったとき、現場にそれが伝わるか。この2つが揃っていれば、誤りは必ず追いつけます。 MENTRAは診察・看護・面談・福祉訪問・議事録の5様式すべてで、音声を読む入口をひとつにまとめ、そこで毎回この判定を通しています。毎晩の自動点検で結果も見ています。
いまお使いの録音の様式を見せていただくところからで構いません。AI音声カルテをご覧いただき、気になる点はお問い合わせからお気軽にご相談ください。
よくあるご質問
静かな面談や、小声の患者さんの録音まで止まってしまいませんか
止まりません。捕まえるのは「信号がまったく無い」録音だけです。手元の実発話の音声で最も静かだったものでも、しきい値の10倍以上の有音時間がありました。小声・遠いマイク・長い沈黙のある面談は、いずれも通ります。
音を測るぶん、記録ができるまで遅くなりませんか
実測で数十ミリ秒です。長い録音は全体を復号せず、頭から時間を散らして拾い読みします。上限の時間内に「音がある」と分かった時点で読むのをやめるので、通常の録音では最初のわずかな区間で判定が終わります。
測りきれなかった録音はどう扱われますか
「無音」でも「有音」でもない3つ目の状態として記録し、AIには渡します。測れなかったことを「異常なし」と書かないためです。どちらか一方に丸めると、あとから「本当に測れていたのか」を追えなくなります。
この対策は、どの記録様式に入っていますか
診察・看護・面談・福祉訪問・議事録の5種類すべてです。音声を取り出す経路をひとつにまとめ、そこを通らないと音声を読めない形にしてあります。様式ごとに入れる方式にすると、追加した様式で入れ忘れが起きるためです。
