コンテンツにスキップ

第9回 音声認識システムの構成要素と評価指標(WER, CER, SLUEベンチマーク)

この回の資料


本日の到達目標

  1. 音声認識システムの全体構成を整理し、各要素の役割を説明できる
  2. WERとCERを数式に基づいて正確に計算できる
  3. 編集距離(Levenshtein距離)とWER/CERの関係を理解する
  4. SLUEベンチマークの概念を理解し、評価指標の使い分けを説明できる
  5. 発展:WERが編集距離を正解語数で割ったものであること、そしてWERが100%を超えうることを、式から自分で導ける

進化の視点:何を「誤り」と数えるかが、評価を決める

WER は、評価用に定めた正解の書き起こしと認識結果の単語列を比較する。ヒトによる書き起こしにも不一致や誤りがあるため、「人間並み」という比較には、同じ音声・正解文・分かち書き・正規化・採点方法が必要である。人間の誤り率も、音声や転写作業の条件によって変わる。

ヒトは、声の方向や声質、口の動き、文脈などを手がかりに、複数の声から対象の声を聞き取る(カクテルパーティ効果、第2回)。ただし、すべての手がかりが常に必要なわけではなく、人間も雑音や発話の重なりで聞き取りを誤る。これらは知覚機能の説明であり、その機能の進化的起源を直接示すものではない。

技術への接続:評価指標を変えると、良いモデルの定義が変わる。単語単位か文字単位か、雑音下の条件を含めるか、話者が重なる状況を含めるか。ベンチマークの設計は、何を模倣したいのかの宣言でもある。

問い:あなたの作るシステムでは、どんな誤りが致命的か。



スライド1: 音声認識システムの全体構成(復習と整理)

第7〜8回の学習内容の整理

構成 音声から文字列を得る流れ
従来のHMM系 特徴量 → 音響モデル。デコーダが音響モデル・発音辞書・言語モデルを組み合わせて語列を探索する
End-to-End系 波形または特徴量 → エンコーダ → CTC、Transducer、Attention-based Encoder-Decoder(AED)などで文字・サブワード列を推定する
評価 正解文と比較してWER/CERを計算し、用途に応じてSLUEなどの理解タスクも評価する

外部言語モデルは任意に併用できる。CTCやAEDはモデル・学習方式、Viterbiやビーム探索は探索アルゴリズムであり、同じ分類ではない。wav2vec 2.0やHuBERTをASRに使う場合は、認識用の出力層などを追加して微調整する。

なぜ評価が重要か

  • モデルを改良したとき、本当に良くなったか?を客観的に測る
  • 異なるシステムを公平に比較する
  • 誤りのパターンを把握し、次の改善の方向性を決める

スライド2: 音響モデル(AM)の役割と種類

音響モデルの役割

入力: 音声の特徴量列、または生の波形から学習した表現。 出力: モデルによって異なる。GMM-HMMは状態ごとの観測の尤度、DNN-HMMは状態の事後確率などを推定する。CTCの出力はフレームごとの文字・サブワードとblankの確率、AEDは過去の出力と音声を条件とした次のトークンの確率である。

代表的なモデルの位置づけ

構成・学習方法 モデル例 特徴
HMM系 GMM-HMM、DNN-HMM 音響モデル・発音辞書・言語モデルを探索で組み合わせる
End-to-End CTC、Transducer、AED 音声と書き起こしから学習し、手作業のフレーム対応ラベルを必須としない
自己教師あり事前学習 wav2vec 2.0、HuBERT ラベルなし音声で表現を学び、認識用に微調整する
大規模な音声・テキスト対による学習 Whisper 多言語・多様な条件の音声と書き起こしを使う

これらは一列に置き換わる「世代」ではなく、組み合わせられる設計軸である。WERを比較するときは、データセットの分割(test-clean / test-otherなど)、学習データ、言語モデル、正規化、探索条件をそろえる。モデルの種類だけから一律のWERは決まらない。

ファインチューニング後の活用

第7回で学んだwav2vec 2.0やHuBERTは、音声エンコーダの初期化として利用できる。少量の文字起こし付きデータで微調整できる場合も、事前学習では大量の音声を使っている点を区別する。

参考:CTC原論文、wav2vec 2.0原論文、Whisper原論文。


スライド3: 言語モデル(LM)の役割

言語モデルの役割

単語・トークン列の確率を与え、デコーダが音響情報と組み合わせて候補を選ぶために使う。自然に見える文でも、入力音声と一致するとは限らない。

\[P(W) = P(w_1, w_2, \ldots, w_L)\]

N-gramモデル(従来)

マルコフ仮定により、前\(N-1\)単語だけで次の単語を予測:

\[P(w_t \mid w_1, \ldots, w_{t-1}) \approx P(w_t \mid w_{t-N+1}, \ldots, w_{t-1})\]

例(trigram, \(N=3\)):

\[P(\text{です} \mid \text{今日},\text{は},\text{いい},\text{天気}) \approx P(\text{です} \mid \text{いい},\text{天気})\]

ニューラル言語モデル(現代)

Transformer言語モデル(GPT系)により、長距離の文脈を考慮した予測が可能。

End-to-Endシステムでの扱い

  • CTCなど: 必要に応じて外部言語モデルを併用する
  • Attention-based Decoder: 音声と過去の出力を条件に予測し、言語的な規則性も学ぶ。外部言語モデルを追加することもできる

シャロウフュージョン(浅い統合)では、例えば次の探索スコアを用いる。

\[\operatorname{score}(Y;X) = \log P_{\text{ASR}}(Y \mid X) + \lambda \log P_{\text{LM}}(Y)\]

\(\lambda\) は言語モデルのウェイト(ハイパーパラメータ)。これは探索に使うスコアであり、そのまま正規化された対数確率ではない。

参考:Kannanほか:外部言語モデルの統合。


スライド4: デコーダの役割(ビーム探索)

デコーダの役割

認識モデルと、併用する場合は外部言語モデルのスコアを使い、最もスコアの高いテキスト列を探索する。

\[\hat{Y} = \arg\max_Y \left[\log P_{\text{ASR}}(Y \mid X) + \lambda \log P_{\text{LM}}(Y)\right]\]

自己回帰型デコーダでは、既に選んだ出力を条件に、各ステップで最も確率の高いトークンを選ぶ。簡単だが、系列全体の最適解とは限らない。CTCの貪欲復号では、各フレームの最大確率ラベルを選んだ後、連続重複とblankを除く。

t=1: a(0.7), b(0.2), c(0.1) → "a"を選ぶ
t=2: b(0.6), c(0.3), a(0.1) → "b"を選ぶ
...
結果: "ab..." (各ステップで最大の候補を選択)

上位\(B\)個(ビーム幅)の候補を常に保持しながら探索。

ビーム幅 B=3 の例:

t=1: 上位3候補を残す
  "a"(0.7), "b"(0.2), "c"(0.1)

t=2: 各候補を展開 → 9候補 → 上位3を残す
  "ab"(0.7×0.6=0.42), "ac"(0.7×0.3=0.21), "ba"(0.2×0.5=0.10), ...

...

最終: 最もスコアが高い系列を出力

直感: 探索の網を広げることで、貪欲法よりも良い解を見つけやすい。ただし\(B\)が大きいほど計算コストも増大。


スライド5: 評価指標 — WER(Word Error Rate)

WERの定義

音声認識の最も基本的な評価指標。単語レベルの誤り率。

\[\text{WER} = \frac{S + D + I}{N} \times 100\%\]

ここで: - \(S\)(Substitutions): 置換 — 正解単語が別の単語に認識された数 - \(D\)(Deletions): 削除 — 正解単語が認識されなかった(消えた)数 - \(I\)(Insertions): 挿入 — 存在しない単語が認識に現れた数 - \(N\): 正解文の総単語数

注意: \(\text{WER} > 100\%\) になることもある(挿入が多い場合)。

計算例1

内容
正解文 今日 は 天気 が いい
認識結果 今日 は 天候 いい

単語ごとに対応を確認:

正解:   今日  は  天気  が  いい
認識:   今日  は  天候      いい

差分:
  今日 → 今日  (正解)
  は   → は    (正解)
  天気 → 天候  (置換: S+1)
  が   → (なし) (削除: D+1)
  いい → いい  (正解)
\[S=1, \quad D=1, \quad I=0, \quad N=5\]
\[\text{WER} = \frac{1+1+0}{5} = \frac{2}{5} = 40\%\]

計算例2(挿入あり)

正解:   今日  は  晴れ
認識:   今日  は  今日  は  晴れ

差分: 「今日 は」が挿入された
  S=0, D=0, I=2, N=3
\[\text{WER} = \frac{0+0+2}{3} = \frac{2}{3} \approx 66.7\%\]
導出①:\(S\)・\(D\)・\(I\) の3つで数え、正解語数で割る理由

正解文を認識結果に「作り替える」と考える。使える操作は置換・削除・挿入の3つである。この3つがあれば、どんな語列からどんな語列へも必ず変換できる。 全部削ってから全部挿入すればよいからである。だから「変換できるか」ではなく「何回で変換できるか」が問題になり、その最小回数が誤りの量になる。これが次のスライドで扱う編集距離である。

最適な対応づけのもとで、正解文の各単語は「一致・置換・削除」のいずれか1つに必ず分類される。したがって

\[(\text{一致数}) + S + D = N\]

同じように、認識結果の各単語は「一致・置換・挿入」のいずれかである。

\[(\text{一致数}) + S + I = (\text{認識結果の語数})\]

誤った箇所は \(S + D + I\) 箇所で、これが分子になる。

なぜ分母が正解文の語数なのか。 誤りの絶対数は長い文ほど多くなりやすいため、そのままでは長さの違う文を比較しにくい。基準として固定されているのは正解文のほうである。認識結果の語数はシステムの出来によって変わるので、分母に使うと、余分な挿入によって分母も増え、正解語数を基準とする誤りの多さを表せなくなる。テスト問題の選択肢に「1/5」があるのは、ここを取り違えていないかを問うためである。

使った数学:場合分けによる数え上げ。

導出②:なぜ WER は 100% を超えうるのか

導出①の2つの式を引き算する。\((\text{一致数}) + S + D = N\) から \((\text{一致数}) + S + I = (\text{認識結果の語数})\) を引くと、一致数と \(S\) が消えて

\[D - I = N - (\text{認識結果の語数})\]

つまり

\[I = (\text{認識結果の語数}) - N + D\]

ここが要点である。 \(S\) と \(D\) は正解文の単語を分類したものなので \(S + D \leq N\) であり、この2つだけなら WER は 100% を超えない。ところが \(I\) は認識結果の語数に従って増え、上限が \(N\) と無関係である。認識結果をいくらでも長くできる以上、\(I\) もいくらでも大きくできる。

数値で確かめる:正解文が はい(\(N=1\))で、認識結果が はい はい はい(3語)のとき、一致数1、\(S=0\)、\(D=0\)、\(I = 3 - 1 + 0 = 2\) となり

\[\text{WER} = \frac{0+0+2}{1} = 2 = 200\%\]

「精度 = 100 − WER」とは書けない

WER は 0% から 100% のあいだに収まる量ではないので、100 から引いても「正解率」にはならない。WER は率という名前がついているが、確率ではなく比である。 上限がないことを知らずに「精度87%」などと書き換えると、意味の違う数字を報告することになる。

同じ理由で、挿入が多く出るシステムは WER が極端に悪く出る。誤りの内訳(\(S\)・\(D\)・\(I\) の別)を見ずに WER だけを比べると、何が起きているか分からない。スライド10のエラー分析が必要なのはこのためである。

使った数学:2つの数え上げの式の差を取る操作。


スライド6: 編集距離(Levenshtein距離)との関係

編集距離とは

2つの文字列(または単語列)を一致させるために必要な最小操作回数。 操作は3種類: 置換(S)・削除(D)・挿入(I)。

\[\text{WER} = \frac{\text{編集距離(単語レベル)}}{\text{正解の総単語数}}\]

つまり、WERは単語列の(正規化)編集距離。

動的計画法による計算

\(\text{ref} = [r_1, r_2, \ldots, r_M]\)(正解)、\(\text{hyp} = [h_1, h_2, \ldots, h_N]\)(認識)とする。

\(d[i][j]\): \(\text{ref}[1..i]\) と \(\text{hyp}[1..j]\) の編集距離

漸化式:

\[d[i][j] = \min\begin{cases} d[i-1][j] + 1 & \text{(削除: refのi番目を削除)} \\ d[i][j-1] + 1 & \text{(挿入: hypのj番目を挿入)} \\ d[i-1][j-1] + \mathbf{1}[r_i \neq h_j] & \text{(一致 or 置換)} \end{cases}\]

初期条件: \(d[0][j] = j\), \(d[i][0] = i\)

計算例(文字レベルでの例)

ref = cat, hyp = car

c a r
0 1 2 3
c 1 0 1 2
a 2 1 0 1
t 3 2 1 1

編集距離 = 1(置換: t→r)

導出③:なぜ表を埋めるだけで最小回数が求まるのか

最小回数を素朴に求めようとすると、どの単語をどの単語に対応させるかの組み合わせをすべて試すことになり、長さとともに爆発する。動的計画法はこれを避ける。

鍵は、\(d[i][j]\) を決めるのに必要なのは3つの小さい答えだけという点である。\(\text{ref}[1..i]\) と \(\text{hyp}[1..j]\) をそろえる最後の1手は、次の3通りしかない。

  • \(r_i\) を削除した → 残りは \(\text{ref}[1..i-1]\) と \(\text{hyp}[1..j]\) の問題、つまり \(d[i-1][j] + 1\)
  • \(h_j\) を挿入した → 残りは \(\text{ref}[1..i]\) と \(\text{hyp}[1..j-1]\) の問題、つまり \(d[i][j-1] + 1\)
  • \(r_i\) と \(h_j\) を対応させた → 残りは \(d[i-1][j-1]\) に、文字が違えば置換の1を足す

最後の1手として可能なのはこの3つだけなので、最小値はこの3つの最小値になる。これが上の漸化式である。一致・置換・削除・挿入という誤りの種類が、漸化式の枝にそのまま対応している。

初期条件 \(d[0][j] = j\) は「正解が空なら、認識結果の語数だけ挿入するしかない」ことを、\(d[i][0] = i\) は「認識結果が空なら、正解の語数だけ削除するしかない」ことを表している。

境界の行と列を含む表の大きさは \((M+1)\times(N+1)\) で、内側の \(M\times N\) マスを計算する。各マスで比べる候補は3つなので、計算量は長さの積に比例する。cat と car では4行4列の表を使い、内側9マスを計算して右下の1を得る。

第7回の前向きアルゴリズムと同じ構造である。 どちらも「部分問題の答えを表に持ち、次の答えをそこから作る」ことで、素朴には爆発する計算を多項式に落としている。確率の和を取る前向きアルゴリズムと、操作回数の最小値を取る編集距離では、状態・遷移・計算する量が異なるが、部分問題の結果を再利用する点は共通している。

この授業で扱う統計数理の範囲外

操作ごとに重みを変える(置換は2、挿入は1など)重み付き編集距離の導出は扱わない。本文の学習目標は、WER の分子が「最小の操作回数」として一意に決まることを理解することである。後の発展コードにはバックトレースも示す。最小距離は一意でも、最適な対応づけやS・D・Iの内訳は複数ありうる。

使った数学:場合分けと、最小値についての漸化式。


スライド7: CER(Character Error Rate)

CERの定義

文字レベルの誤り率。日本語など、空白で単語を区切らない言語でよく用いられる。日本語でも分かち書き規則を決めればWERを計算できる。

\[\text{CER} = \frac{S_c + D_c + I_c}{N_c} \times 100\%\]
  • \(S_c, D_c, I_c\): 文字レベルの置換・削除・挿入数
  • \(N_c\): 正解文の総文字数

日本語でのCER計算例

例題(CBTに出題):

正解文: 今日は天気がいい
認識結果: 今日はてんきがいい

文字単位のアライメント:

正解:   今 日 は 天 気 が い い
認識:   今 日 は て ん き が い い

差分を動的計画法で計算:
  今 → 今 (一致)
  日 → 日 (一致)
  は → は (一致)
  天 → て (置換: S+1)
  気 → ん (置換: S+1)
     → き (挿入: I+1)
  が → が (一致)
  い → い (一致)
  い → い (一致)
\[S_c=2, \quad D_c=0, \quad I_c=1, \quad N_c=8\]
\[\text{CER} = \frac{2+0+1}{8} = \frac{3}{8} = 37.5\%\]

別の解釈: 認識結果「てんき」(3文字)と正解「天気」(2文字)の編集距離は3(天→て、気→ん、+きの挿入)なので、その部分だけで \(3/8 = 37.5\%\)。

WERとCERの使い分け

指標 対象言語 使う理由
WER 英語など。日本語でも使用可能 単語の誤りを評価する。分かち書きの規則を固定する
CER 日本語・中国語など 単語への分割に依存せず、文字の誤りを評価する

日本語の問題: 「今日は良い天気だ」を単語に分けると「今日/は/良い/天気/だ」など形態素解析が必要。文字単位なら単語分割の問題を避けられる。ただし、句読点・空白・大文字小文字・表記ゆれをどう扱うかは、どちらの指標でも事前に定める必要がある。


スライド8: SLUEベンチマークとは

SLUE(Spoken Language Understanding Evaluation)

Shonほか(2022)が提案した、音声言語理解(SLU)の評価ベンチマーク。初期版は英語の自然発話を対象とする。

書き起こしの正確さに加えて、特定の意味理解タスクの性能を評価する。初期版(Phase-1)はNER・感情分析の2つのSLUタスクと、同じデータでのASR評価を含む。

初期版のSLUタスク

タスク1: 固有表現認識(NER: Named Entity Recognition) - 音声から人名・地名・組織名などの表現と種類を抽出する - 主な評価指標: \(F_1\)スコア(適合率と再現率の調和平均)

以下は課題の概念を示す日本語の例であり、SLUEの収録例ではない。

入力音声: "田中太郎さんが東京大学で発表した"
出力:
  田中太郎 → PER(人名)
  東京大学 → ORG(組織名)

タスク2: 感情分析(Sentiment Analysis: SA) - 発話のセンチメントをポジティブ・ネガティブ・ニュートラルに分類する - 評価指標: macro(クラスを等しく重みづけした)\(F_1\)など。出現頻度で重みづけするweighted \(F_1\)とは異なる - 喜び・怒りなどの感情カテゴリの推定や、話者の内的状態そのものの測定とは区別する

評価指標から分かること

評価の種類 測るもの 解釈上の注意
WER/CER 正解の書き起こしとの一致度 意味の正しさや用途上の重大さを直接は重みづけしない
SLUE-NER 固有表現の内容・種類の抽出性能 文字起こしや意味理解全体の正確さを表す指標ではない
SLUE-SA 与えられたセンチメントラベルの分類性能 WERから一意に予測できず、別に評価する必要がある

同じ「すごい」という語でも、韻律や文脈によって解釈が変わることがある。ただし、上昇・下降調だけから感情や極性が一意に決まるわけではない。

ベンチマークを比較するとき

事前学習済み音声モデルを用いたEnd-to-End方式と、ASR→テキスト解析のパイプライン方式を比較できる。どちらが高性能かはタスク・モデル・データ条件による。後続のPhase-2(2023)では課題が拡張されているため、報告時には版とタスクを明記する。

参考:SLUE原論文、公式タスク・評価方法、Phase-2原論文。


スライド9: 混同行列による分析

音素レベルの混同行列

どの音素が他のどの音素に誤認識されやすいかを可視化する。

音素混同行列の例(行=正解音素、列=認識音素):

         /a/  /i/  /u/  /e/  /o/  ...
正解/a/ [  95   2    1    1    1  ...]
正解/i/ [   3  90    3    2    2  ...]
正解/u/ [   2   4   88    2    4  ...]
...
(架空の例。行ごとに正規化した割合 %。ここでは置換だけに注目)

対角成分が高いほど一致が多い

実際の連続音声では、系列を対応づけたうえで、挿入・削除も別に数える。音素分類の混同行列だけでは単語列全体のWERを表せない。

混同行列から読み取れる情報

  1. 系統的誤り: 特定の音素が同じ別の音素に誤られやすい 例: 同じ評価ラベル集合の中で /s/ が /ʃ/ に混同されるなど
  2. 音響的近さ: 混同されやすい音素は音響的に似ている傾向がある
  3. 改善方向: 特定の音素ペアの学習データを増やす、特徴量設計を見直すなど

単語レベルの混同例(日本語)

正解単語 よくある誤認識 原因
橋(はし) 箸(はし) 音素列は同じ。アクセントや文脈の手がかりも地域・発話条件に依存
音楽 音学 同じ読みからの表記選択の誤り。入力だけから原因を一つに断定できない
北星学園大学 北西学園大学 固有名詞の表記・語彙や音響的な混同など(例示)

スライド10: エラー分析の方法論

体系的なエラー分析のステップ

Step 1: 量的分析 - WER/CERの全体値を計算 - 条件別(騒音レベル、話者、発話速度)に分類

Step 2: エラーの分類

エラー種別 説明 対処法
音響的誤り 類似音の混同(/s/→/z/等) 音響モデルの改善、データ増強
語彙外単語(OOV) 辞書に存在しない単語 辞書拡張、文字モデル使用
言語モデルエラー 音響的には正しいが文脈と合わない LM更新、ドメイン適応
ハルシネーション 入力に無い内容を出力 無音区間・音声の分割・復号条件を確認し、対策後も評価する

Step 3: 対話型デバッグ - 誤りの多いファイルを個別に聴取・分析 - 必要に応じてアテンションを可視化する。ただし、注意重みだけから誤りの原因や意味理解を断定しない - 音響モデルの確率分布確認

Step 4: 改善策の立案と評価 - データ増強、ファインチューニング等の施策を実施 - 開発用データで調整し、最終評価は調整に使っていないテストデータで行う - 同一条件で改善前後のWER/CERを比較し、全体値だけでなく話者群・録音条件別の値も確認する


Python実装:WER/CER計算

評価の前提と正規化

以下はNotebook/Colabで上から順に実行する例である。WER/CER関数は比(例:0.375)を返し、:.1%で百分率に表示する。空の正解文では通常の式の分母が0になるため、扱いを明示する必要がある。

この教材の日本語CERでは、UnicodeをNFCに正規化し、空白を除き、句読点と漢字・かなの違いは残す。正解文と認識結果の両方に、同じ処理を適用する。

import unicodedata

def normalize_ja(text: str) -> str:
    return "".join(unicodedata.normalize("NFC", text).split())

方法1:jiwerライブラリ(推奨)

!pip install "jiwer>=4,<5"

from jiwer import wer, cer

# WERの計算(英語)
reference_en = "the cat sat on the mat"
hypothesis_en = "the cat sat on the mat at"

word_error_rate = wer(reference_en, hypothesis_en)
print(f"WER: {word_error_rate:.1%}")
# S=0, D=0, I=1 ("at"が挿入), N=6
# WER = 1/6 = 16.7%

# CERの計算(日本語)
reference_ja = "今日は天気がいい"
hypothesis_ja = "今日はてんきがいい"

char_error_rate = cer(normalize_ja(reference_ja), normalize_ja(hypothesis_ja))
print(f"CER: {char_error_rate:.1%}")
# 期待値: 37.5%

JiWER 4では、正解も認識結果も空なら0、正解だけが空なら挿入数を返すという規約を設けている。これは分母0の式をそのまま計算した値ではない。参考:JiWER公式資料。

方法2:WER/CERを自前で実装(アルゴリズム理解のため)

ここでは通常の式の定義域を明確にするため、空の正解文にはValueErrorを出す。対応づけが複数ある場合、この実装は置換→削除→挿入の順で選ぶため、別の実装と内訳が異なる場合があるが、最小距離は一致する。

import numpy as np

def edit_distance(reference: list, hypothesis: list) -> tuple[int, int, int, int]:
    """
    動的計画法で編集距離を計算し、S/D/Iの内訳も返す。

    Args:
        reference: 正解の単語リスト(または文字リスト)
        hypothesis: 認識結果の単語リスト(または文字リスト)

    Returns:
        (S, D, I, total_distance): 置換数、削除数、挿入数、総編集距離
    """
    m = len(reference)
    n = len(hypothesis)

    # DP テーブル(コスト)
    dp = np.zeros((m + 1, n + 1), dtype=int)

    # 初期条件
    for i in range(m + 1):
        dp[i][0] = i
    for j in range(n + 1):
        dp[0][j] = j

    # DP テーブルを埋める
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if reference[i - 1] == hypothesis[j - 1]:
                dp[i][j] = dp[i - 1][j - 1]  # 一致
            else:
                dp[i][j] = min(
                    dp[i - 1][j] + 1,      # 削除
                    dp[i][j - 1] + 1,      # 挿入
                    dp[i - 1][j - 1] + 1   # 置換
                )

    # バックトレースでS/D/Iを数える
    i, j = m, n
    S, D, I = 0, 0, 0
    while i > 0 or j > 0:
        if i > 0 and j > 0 and reference[i - 1] == hypothesis[j - 1]:
            i -= 1
            j -= 1
        elif i > 0 and j > 0 and dp[i][j] == dp[i - 1][j - 1] + 1:
            S += 1  # 置換
            i -= 1
            j -= 1
        elif i > 0 and dp[i][j] == dp[i - 1][j] + 1:
            D += 1  # 削除
            i -= 1
        else:
            I += 1  # 挿入
            j -= 1

    return S, D, I, int(dp[m][n])

def compute_wer(reference_text: str, hypothesis_text: str) -> float:
    """単語レベルのWERを計算する"""
    ref_words = reference_text.split()
    hyp_words = hypothesis_text.split()
    S, D, I, _ = edit_distance(ref_words, hyp_words)
    N = len(ref_words)
    if N == 0:
        raise ValueError("正解の長さが0のため、通常のWER/CERは定義できません")
    wer_score = (S + D + I) / N
    print(f"  正解単語数 N={N}, S={S}, D={D}, I={I}")
    return wer_score

def compute_cer(reference_text: str, hypothesis_text: str) -> float:
    """文字レベルのCERを計算する(日本語対応)"""
    ref_chars = list(normalize_ja(reference_text))
    hyp_chars = list(normalize_ja(hypothesis_text))
    S, D, I, _ = edit_distance(ref_chars, hyp_chars)
    N = len(ref_chars)
    if N == 0:
        raise ValueError("正解の長さが0のため、通常のWER/CERは定義できません")
    cer_score = (S + D + I) / N
    print(f"  正解文字数 N={N}, S={S}, D={D}, I={I}")
    return cer_score

# ────────── 実行例 ──────────

# 例1: 英語WER
print("=== 例1: 英語WER ===")
ref = "the quick brown fox jumps over the lazy dog"
hyp = "the quick brown fox jump over a lazy dog"
w = compute_wer(ref, hyp)
print(f"WER = {w:.1%}\n")

# 例2: 日本語CER
print("=== 例2: 日本語CER(スライド7の例題)===")
ref_ja = "今日は天気がいい"
hyp_ja = "今日はてんきがいい"
c = compute_cer(ref_ja, hyp_ja)
print(f"CER = {c:.1%}\n")
# 期待値: S=2, D=0, I=1, N=8 → 3/8 = 37.5%

# 例3: WER > 100% のケース
print("=== 例3: WER が 100% を超えるケース ===")
ref2 = "今日 晴れ"                # 2単語
hyp2 = "今日 は 晴れ です ね"     # 5単語(3挿入)
w2 = compute_wer(ref2, hyp2)
print(f"WER = {w2:.1%}")    # (0+0+3)/2 = 150%

方法3:Whisperの認識結果を直接評価

第8回で用意したWhisper・FFmpeg環境を使い、weather_news.wavと、その音声に対応する正解文を用意する。下の正解文は例なので、実際の録音内容に合わせて変更する。

import whisper
from jiwer import cer as compute_cer_jiwer

# Whisperで音声を認識
model = whisper.load_model("small")

# 正解文(自分で用意)
reference = "今日の東京の天気は晴れのち曇りでしょう"

# 音声を認識
result = model.transcribe("weather_news.wav", language="ja")
hypothesis = result["text"].strip()

print(f"正解文: {reference}")
print(f"認識結果: {hypothesis}")
print(f"CER: {compute_cer_jiwer(normalize_ja(reference), normalize_ja(hypothesis)):.1%}")

複数ファイルの一括評価

上で定義したnormalize_jaとedit_distanceを使う。全体CERは誤り数の合計÷正解文字数の合計で計算し、ファイルごとのCERの単純平均とは区別する。空の正解文に対する誤出力も挿入として合計に含めるが、そのファイル単体のCERはNone(未定義)とする。

import os

def evaluate_asr_system(audio_dir: str, reference_dir: str, model) -> dict:
    """
    音声ファイルのディレクトリに対してASRシステムを評価する。

    Args:
        audio_dir: 音声ファイルが格納されたディレクトリ
        reference_dir: 正解テキストファイルが格納されたディレクトリ
        model: Whisperモデルオブジェクト

    Returns:
        評価結果の辞書
    """
    total_s = total_d = total_i = total_n = 0
    results = []

    for audio_file in sorted(os.listdir(audio_dir)):
        if not audio_file.endswith(".wav"):
            continue

        base = os.path.splitext(audio_file)[0]
        ref_file = os.path.join(reference_dir, base + ".txt")

        if not os.path.exists(ref_file):
            raise FileNotFoundError(f"正解テキストがありません: {ref_file}")

        with open(ref_file, "r", encoding="utf-8") as f:
            reference = f.read().strip()

        audio_path = os.path.join(audio_dir, audio_file)
        result = model.transcribe(audio_path, language="ja")
        hypothesis = result["text"].strip()

        # 文字レベルの編集距離
        ref_chars = list(normalize_ja(reference))
        hyp_chars = list(normalize_ja(hypothesis))
        S, D, I, _ = edit_distance(ref_chars, hyp_chars)
        N = len(ref_chars)

        total_s += S
        total_d += D
        total_i += I
        total_n += N

        results.append({
            "file": audio_file,
            "reference": reference,
            "hypothesis": hypothesis,
            "cer": (S + D + I) / N if N > 0 else None,
            "insertions": I  # 空の正解文でも誤出力の挿入数は記録する
        })

    if total_n == 0:
        raise ValueError("正解の総文字数が0のため、全体CERは定義できません")
    overall_cer = (total_s + total_d + total_i) / total_n

    return {
        "overall_cer": overall_cer,
        "total_S": total_s,
        "total_D": total_d,
        "total_I": total_i,
        "total_N": total_n,
        "per_file": results
    }

# 使用例(ディレクトリを用意した場合)
# evaluation = evaluate_asr_system("./audio", "./references", model)
# print(f"全体CER: {evaluation['overall_cer']:.1%}")

Google Forms テスト問題(第9回)


問1: WERの計算(基礎)

正解文: 私 は 毎日 学校 へ 行く(6単語)
認識結果: 私 は 毎日 学校 行く(5単語)

このときのWERはいくらか。

  1. 1/6 ≈ 16.7%
  2. 2/6 ≈ 33.3%
  3. 1/5 = 20.0%
  4. 0%

正解: 1

解説:
正解文と認識結果を対応づける。

正解:   私  は  毎日  学校  へ  行く
認識:   私  は  毎日  学校      行く

「へ」が削除されている: D=1
S=0, I=0
N=6(正解の総単語数)
\[\text{WER} = \frac{0 + 1 + 0}{6} = \frac{1}{6} \approx 16.7\%\]

選択肢1が正解。選択肢3(1/5)は間違い — 分母は正解文の単語数であり、認識結果の単語数(5)ではない。


問2: CERの計算(実践)

正解文: 今日は天気がいい(8文字)
認識結果: 今日はてんきがいい(9文字)

このときのCERはいくらか。

  1. 1/8 = 12.5%
  2. 2/8 = 25.0%
  3. 3/8 = 37.5%
  4. 4/8 = 50.0%

正解: 3

解説:

文字レベルのアライメント(スライド7の例題と同じ):

正解:   今 日 は 天 気 が い い
認識:   今 日 は て ん き が い い

操作:
「天」→「て」: 置換 (S+1)
「気」→「ん」: 置換 (S+1)
(なし)→「き」: 挿入 (I+1)
\[S_c=2, \quad D_c=0, \quad I_c=1, \quad N_c=8\]
\[\text{CER} = \frac{2+0+1}{8} = \frac{3}{8} = 37.5\%\]

正解は選択肢3。


問3: WERとCERの使い分け

単語分割の規則の違いを避けて評価したいとき、WERよりもCERを選ぶ理由として最も適切なものはどれか。

  1. 英語の認識結果を評価するとき
  2. 日本語の認識結果を評価するとき(単語分かち書きが難しいため)
  3. 認識結果に挿入エラーが多いとき
  4. 言語モデルを使わないEnd-to-Endシステムを評価するとき

正解: 2

解説:
- 選択肢1は誤り。英語はスペースで単語が区切られるため、WERが標準的に使われる。
- 選択肢2が正しい。日本語・中国語などでは通常は単語間に空白を置かない。CERは文字単位で計算するため、単語分割の規則の違いを避けられる。日本語でも、分かち書きの規則を固定してWERを用いることはできる。 - 選択肢3は誤り。挿入エラーの多さはCERに切り替える理由にはならない。WERでも挿入は計上される。
- 選択肢4は誤り。システムの種類(E2E vs パイプライン)はWER/CERの選択基準とは別の話。


問4: 編集距離の理解

reference = [A, B, C, D](4単語)、hypothesis = [A, C, D, E](4単語)のとき、最小編集距離(単語レベル)はいくらか。

  1. 1
  2. 2
  3. 3
  4. 4

正解: 2

解説:

動的計画法で編集距離を計算するか、直感的に考える。

参照: A  B  C  D
仮説: A     C  D  E

最短変換:
  B を削除 (D+1)
  E を挿入 (I+1)

合計: D=1, I=1, S=0 → 編集距離 = 2

同じ長さの異なる列なので、1回の挿入・削除だけでは一致しない。また3つの位置が異なるため、1回の置換だけでも一致しない。2回で変換できる上の例があるので、最小は2である。

\[\text{WER} = \frac{0+1+1}{4} = \frac{2}{4} = 50\%\]

問5: SLUEベンチマークの意義

SLUEベンチマークに関する説明として最も適切なものはどれか。

  1. WERとCERを統一的に評価するためのベンチマークである
  2. 音声言語理解(意味理解)の能力を評価するためのベンチマークであり、NERや感情分析を含む
  3. 日本語特有の評価指標であり、漢字変換精度を評価する
  4. 大規模言語モデル(LLM)の音声理解能力を評価するベンチマークである

正解: 2

解説:
- 選択肢1は誤り。WERとCERは文字・単語レベルの評価。SLUEはASR評価に加えて特定の意味理解タスクを評価する。 - 選択肢2が正しい。SLUE(Spoken Language Understanding Evaluation)はShonほかが提案した評価ベンチマーク。初期版は固有表現認識とセンチメント分析の2つのSLUタスク、および同じデータのASR評価を含む。書き起こしの正確さと下流タスク性能を区別して調べられる。 - 選択肢3は誤り。SLUEは英語を対象としたベンチマークとして開始された。漢字変換精度を測るものではない。
- 選択肢4は誤り。SLUEはEnd-to-End方式とパイプライン方式の双方を評価でき、特定のモデル(LLM)を前提としない。


まとめ

本日の学習内容

  1. 音声認識システム全体構成: HMM系とEnd-to-End系を区別し、モデルと探索アルゴリズムの役割を整理する
  2. WER: \(\frac{S+D+I}{N}\)。単語レベルの誤り率。英語の標準指標。
  3. CER: \(\frac{S_c+D_c+I_c}{N_c}\)。文字レベルの誤り率。日本語でよく用いられる。
  4. 編集距離: WER/CERの計算基盤。動的計画法で \(O(MN)\) で求まる。
  5. SLUEベンチマーク: NER・感情分析による意味理解の評価。WER/CERを超えた評価。
  6. エラー分析: 混同行列・エラー種別分類・改善策立案の方法論。

第7〜9回の位置づけ

第7回: 音声と統計モデル(GMM-HMM, wav2vec 2.0, HuBERT)
  ↓ モデルを学んだ
第8回: 最新E2Eモデル(Conformer, Whisper)と実践実習
  ↓ 使い方を学んだ
第9回: 評価指標とエラー分析
  ↓ 評価方法を学んだ
第10回: 音声インタフェースとUX設計
第11回: 自然言語処理との接続(NLUと音声)