論文一覧に戻る 📚 用語集トップ 🗺 概念マップ
📚 用語解説
📚 用語解説
音声認識
Speech Recognition
音声認識

🔖 キーワード索引

ASRSTTWhisperWav2Vec 2.0CTCRNN-Tseq2seq音響モデル言語モデルメルスペクトログラムWERエンドツーエンド

別名・略称:ASR、 Automatic Speech Recognition、 STT、 Speech-to-Text

音声認識は 音声波形をテキストに変換する技術。 Siri, Alexa, 自動字幕、 議事録、 コールセンターなど現代生活に不可欠。 Whisper 以降ほぼ「解けた」分野。

💡 30秒で分かる結論

🍰 まずはやさしく

声を文字に変える魔法のような技術です。

話した内容を記録するために使います。

スマホへの音声入力などが身近な例です。

この技術の仕組みと使い方を学びます。

音声認識(Speech Recognition / ASR):音声波形をテキストに変換する技術

📍 あなたが今見ているもの

🍰 まずはやさしく

誰でも使える便利な道具になりました。

仕事や勉強の効率を上げるために使います。

動画に自動で字幕をつける機能があります。

最近の技術で何が変わったかを見ます。

2022 年に OpenAI が発表した Whisper は、 68 万時間の多言語音声で学習し、 ノイズや訛りに強い汎用 ASR を実現しました。 これ以前は専門ベンダーの牙城でしたが、 オープンモデルの登場で誰でも実用レベルの音声認識を実装可能に。 議事録自動化、 字幕生成、 ポッドキャスト検索など応用が急拡大しています。

🎨 直感で掴む

🍰 まずはやさしく

音を分析して文字に直す流れがあります。

正しく言葉を読み取るために使います。

部活の練習メニューを声でメモする感じです。

音がどうやって文字になるかを図解します。

処理パイプライン

  1. 音声波形(16 kHz サンプリング、 PCM)取得
  2. 窓関数 + FFT → スペクトログラム
  3. メルフィルタバンク → メルスペクトログラム(80 次元程度)
  4. 音響モデル(Transformer/CNN/RNN)で音素や文字を予測
  5. 言語モデル(n-gram or Transformer)で系列を整流
  6. テキスト出力

End-to-End 3 大手法

手法概要代表モデル
CTC出力を input 長に揃え、 重複・空白で対応付けDeepSpeech, Wav2Vec 2.0
RNN-Transducer音響と言語を結合学習Google Mobile ASR
Attention seq2seqEncoder-Decoder + AttentionListen-Attend-Spell, Whisper

古典 vs 現代

時期代表特徴
〜2010HMM-GMM音素 HMM + GMM 観測モデル
2012-2018DNN-HMM, CTCDNN 音響モデル + 言語モデル分離
2019-現在Wav2Vec 2.0, Whisper自己教師あり + Transformer End-to-End

🎨 テーマ 1:特徴量抽出 — 音声を数値にする

音声認識の入口は波形をどう数値化するかです。 16 kHz サンプリングの音声 1 秒は 16,000 個の値の列。 そのままモデルに食わせるのは効率が悪いため、 フレーム分割(25 ms 窓・10 ms シフトが定番) → 短時間フーリエ変換(STFT) → メルフィルタバンク → 対数 → 必要に応じて離散コサイン変換(DCT)という流れで、 1 秒あたり 100 フレーム × 80 次元程度の特徴量列に圧縮します。 これが Log-Mel スペクトログラム や MFCC(Mel-Frequency Cepstral Coefficients)と呼ばれるものです。

📐 数式(MFCC の核心)

STFT で得た振幅スペクトル $|X(f)|$ をメルスケール $M$ のフィルタバンクに通し、 対数を取り、 DCT で圧縮:

$$ \text{Mel}(f) = 2595 \log_{10}\!\left(1 + \frac{f}{700}\right) $$

$$ \text{MFCC}_k = \sum_{m=1}^{M} \log\!\Big( \sum_{f} |X(f)|^2 \, H_m(f) \Big) \cos\!\Big( \frac{k(m-0.5)\pi}{M} \Big) $$

🔬 数式を言葉で読み解く

🎨 テーマ 5:Whisper の威力 — 68 万時間の事前学習

OpenAI の Whisper(2022 年)は、 99 言語・68 万時間のラベル付き音声で訓練された Encoder-Decoder Transformer。 ASR を「商用ベンダーが大量に守る職人芸」から「誰でも使えるオープン技術」へ転換した記念碑的存在です。 アーキテクチャは平凡な Transformer Encoder-Decoder ですが、 データ量・multitask 学習(言語識別・タイムスタンプ・翻訳を同時)・weak labels(YouTube 字幕など)を活用した点が革新でした。

📊 Whisper モデルサイズと精度の対応表

モデル パラメータ数 VRAM 目安 英語 WER (LibriSpeech test-clean)
tiny39M~1GB7.6%
base74M~1GB5.0%
small244M~2GB3.4%
medium769M~5GB2.9%
large-v31550M~10GB2.4%

※ 英語 WER は Whisper の公開ベンチマークの目安(large-v3 は論文の後に公開されたモデル)。 日本語の CER は評価データと表記の正規化で大きく変わるため、 ここには載せない。

🎨 テーマ 9:ストリーミング ASR とレイテンシ

スマートスピーカー(Alexa, Google Home)、 同時字幕、 通訳機器は音声が話されている最中に部分結果を返す必要があります。 Whisper のような Encoder-Decoder は文末待ちが前提で遅延が大きく、 スマートデバイスには不向き。 そこで RNN-T(RNN Transducer)や CTC + 因果 Conformer が選ばれます。

📊 用途別の典型的レイテンシ

用途 許容レイテンシ 推奨アーキテクチャ
スマートスピーカー応答~ 300 msRNN-T (Conformer encoder)
同時字幕(テレビ生放送)1〜2 秒Streaming Conformer + CTC
議事録作成(バッチ)数秒〜数分Whisper-large (offline)
アーカイブ書き起こし時間制約なしWhisper-large + LLM 後処理

🎮 触って学ぶ:音声区間検出(VAD)— エネルギーとゼロ交差で区切る

生の音声波形をそのまま「言葉」に変える前に、 まず どこが声で・どこが無音か、 さらに 声の中でも母音のような「有声音」か、 サ行のような「無声音」かを大まかに区切ります。 これを 音声区間検出(VAD: Voice Activity Detection)と呼びます。 ここでは 2 つの単純な特徴 ── 短時間エネルギー(音の大きさ)と ゼロ交差率(波形が 0 を横切る頻度=おおよその高さ/かすれ具合) だけで区切りが体感できます。

⚠️ これは合成波形の教材デモです。 実際の音声ファイルは一切扱いません。 有声(低い周波数の周期波)・無声(高周波の乱数ノイズ)・無音(ごく小さな乱数)の区間を、 固定シードの決定的な計算で人工生成しています。 実音声は雑音・話者差でずっと複雑ですが、 「特徴で区切る」直感はこの単純モデルで十分つかめます。
無音 有声(母音的) 無声(摩擦音的)
波形をクリック/タップするとフレームの数値が出ます。

🧩 なぜフレームに区切って窓をかけるのか

音声は時々刻々と変わるので、 全体をまとめて 1 つの数値にはできません。 そこで 25 ms 程度の短い窓(このデモでは 100 サンプル)を 10 ms(40 サンプル)ずつずらして切り出し、 各フレームで特徴を計算します。 短い窓の中では音がほぼ一定とみなせる、 という近似が土台です。 窓の端で波形をブツッと切ると人工的な高周波(スペクトル漏れ)が出るため、 ハミング窓のように端をなめらかに 0 へ落とす重みをかけます。 チェックを入れると、 端が抑えられてエネルギー曲線がわずかに滑らかになる様子が見られます(ゼロ交差率は窓が正の重みなので符号が変わらず不変)。

🧠 なぜ生波形でなく「特徴」を使うのか・時間伸縮(DTW)の直感

上のデモの通り、 生の波形(1 秒で数千点)は情報が多すぎて直接は比べにくい。 エネルギーとゼロ交差率という 2 特徴に落とすだけで「声か無音か・母音か摩擦音か」が判別できました。 実際の音響モデルはこれを高度化した メルスペクトログラムや MFCC を使います。 さらに、 同じ「あ」でも ゆっくり言えば長く・早口なら短くなります。 波形を時間軸で伸び縮みさせて一番よく重なる対応を探すのが 動的時間伸縮(DTW: Dynamic Time Warping)の直感で、 CTC の「同じ文字を何フレームも繰り返してよい」という考え方の源流でもあります。

💡 深掘り

📐 定義 / 数式

🍰 まずはやさしく

計算を使って正解を導き出すルールです。

間違いがどれくらいあるか測るために使います。

テストの採点のように正解と比較します。

計算式を使って仕組みを詳しく説明します。

【ASR の最尤推定】
$$\hat{W} = \arg\max_W P(W \mid X) = \arg\max_W P(X \mid W) P(W)$$
$P(X|W)$ は音響モデル、 $P(W)$ は言語モデル。
【WER(単語誤り率)】
$$\text{WER} = \frac{S + D + I}{N}$$
S=置換, D=削除, I=挿入, N=参照単語数。
【CTC 損失】
$$\mathcal{L}_{\text{CTC}} = -\log \sum_{\pi \in B^{-1}(W)} P(\pi \mid X)$$
$B$ は重複と空白を畳む写像。

📐 テーマ 2:HMM-GMM の基礎 — 古典的 ASR を理解する

2010 年代前半まで、 商用音声認識の主流は HMM(隠れマルコフモデル)+ GMM(混合ガウス分布)でした。 「なぜ確率モデルなのか」を理解しないと、 後発の DNN-HMM、 CTC、 Attention の意義も腑に落ちません。 ここでは HMM-GMM の本質を式と例で押さえます。

📐 数式(音響モデルの確率分解)

音声 $O = o_1, o_2, \dots, o_T$ に対して、 文字列 $W^*$ を求める基本式:

$$ W^* = \arg\max_W P(W \mid O) = \arg\max_W P(O \mid W) \cdot P(W) $$

右辺第 1 項が音響モデル(HMM-GMM)、 第 2 項が言語モデル(n-gram など)。 HMM は隠れ状態 $s_t$ の遷移確率 $a_{ij} = P(s_t = j \mid s_{t-1} = i)$ と出力確率 $b_j(o_t) = P(o_t \mid s_t = j)$ で定義され、 $b_j$ を多次元 GMM でモデル化したのが HMM-GMM。

🔬 数式を言葉で読み解く

📐 テーマ 3:CTC — 「入出力の長さが違う」問題を解く

音声フレーム(数百個)に対し、 出力文字(数十個)。 この長さの不一致と整列情報の欠如(どのフレームがどの文字に対応するか不明)を一気に解決するのが CTC(Connectionist Temporal Classification)です。 2006 年 Alex Graves 提案、 現代 ASR の根幹技術の 1 つ。

📐 数式(CTC 損失)

$$ P(\mathbf{y} \mid \mathbf{X}) = \sum_{\pi \in \mathcal{B}^{-1}(\mathbf{y})} \prod_{t=1}^{T} p(\pi_t \mid \mathbf{X}) $$

$$ \mathcal{L}_{\text{CTC}} = -\log P(\mathbf{y} \mid \mathbf{X}) $$

🔬 数式を言葉で読み解く

📐 テーマ 4:Encoder-Decoder Attention — Listen-Attend-Spell

2015 年 Google が提案した Listen-Attend-Spell(LAS)は、 音声 ASR を機械翻訳と同じ Encoder-Decoder + Attention 枠組みで解く先駆けでした。 Encoder(Listener)が音声を圧縮し、 Decoder(Speller)が文字を 1 つずつ生成。 Attention が「次の文字を生成するとき、 音声のどの位置を見るか」を学習。

📐 数式(Attention の核)

$$ \alpha_{t,i} = \frac{\exp(\text{score}(s_t, h_i))}{\sum_{j} \exp(\text{score}(s_t, h_j))}, \quad c_t = \sum_i \alpha_{t,i} h_i $$

🔬 数式を言葉で読み解く

📐 追補 1:Wav2Vec 2.0 — ラベルなし音声で事前学習する革命

2020 年 Meta(旧 Facebook)が発表した Wav2Vec 2.0 は、 大量のラベルなし音声(数千時間 OK)で自己教師あり事前学習し、 その後少量のラベル付き音声(10 分でも可!)で fine-tuning するだけで競争力ある ASR を作れるようにしました。 これにより、 「アイヌ語」「琉球諸語」のようなラベル付きデータが乏しい言語でも実用 ASR が構築可能になった点が画期的。

📐 数式(Contrastive 損失)

$$ \mathcal{L}_m = -\log \frac{\exp(\text{sim}(c_t, q_t) / \kappa)}{\sum_{\tilde{q} \in Q_t} \exp(\text{sim}(c_t, \tilde{q}) / \kappa)} $$

🔬 数式を言葉で読み解く

こうして得た事前学習済みモデルに、 SSDSE-B-2026 の都道府県名のような少量音声(10 分)を流して fine-tuning すれば、 「47 県を 95% 認識できる」専用 ASR が構築できる。 アノテーションコストが従来の 1/100 以下になる点が、 自治体・教育機関で重宝される理由。

🔬 記号・式を言葉で読み解く

$X$ — 音響特徴系列
メルスペクトログラム等のフレーム列。 通常 100 fps、 80 次元。
$W$ — 単語列(書き起こし)
出力するテキストの単語シーケンス。
$P(X|W)$ — 音響モデル
「この単語列に対しこの音響が出る確率」。 DNN/Transformer で学習。
$P(W)$ — 言語モデル
「自然な単語列か」。 n-gram or LLM。
CTC(Connectionist Temporal Classification)
音声フレーム数 ≫ 単語数の対応問題を、 空白記号と重複畳み込みで解決。
RNN-T
音響予測 + 言語予測を結合学習。 ストリーミング対応の主流。
WER(Word Error Rate)
編集距離ベースの誤り率。 ASR の標準評価指標。

🔬 失敗事例の解剖と再発防止

実運用で発生しがちな失敗事例を整理しておく。 (1) マイク変更で WER 急増: コールセンターのヘッドセットを更新したら WER が 8% → 22% に。 原因は周波数特性の変化で、 再学習データに新マイクを混ぜることで解決。 (2) 固有名詞の連続置換: 「岩手県」が「岩出県」に化けるバグ。 hotword boost と KenLM 固有名詞辞書追加で解決。 (3) 長時間音声の途中で認識停止: Whisper の 30 秒チャンク境界で文が途切れる。 voice activity detection(VAD)で発話単位に切り分けてから入力することで改善。 (4) 句読点抜け: 句読点付与は別モデル(punctuation model)に任せる設計が無難で、 音響モデルに直接学習させると WER が上がる。

📋 典型的な障害パターンと対処

症状主因切り分け方対処
全体的に WER 高サンプリングレート不整合librosa で sr を確認16kHz リサンプル
特定話者だけ悪い学習データの話者偏り性別・年代別 WER 集計該当層のデータ追加
固有名詞が化けるLM 語彙不足誤り上位の名詞抽出hotword + LM 辞書追加
句読点が欠落句読点モデル無し出力に句点があるか確認punctuation model 後段
長音声で精度低下チャンク境界の切り損ね区切り箇所を可視化VAD 導入
レイテンシ過大モデル過大 / GPU 不足tiny/base と比較蒸留 / 量子化

失敗事例を蓄積し、 「症状 → 主因 → 対処」の対応表を社内 wiki 化しておくと、 担当者交代時にも品質が維持される。 SSDSE-B-2026 のような統計データを音声 UI で扱う教育コンテンツでは、 (a) 都道府県名の認識精度、 (b) 数値(人口・GDP)の認識精度、 (c) 時系列年号の認識精度、 の 3 軸を別建てでモニタすると、 教育現場での実用性を担保しやすい。

🎯 多言語・コードスイッチ対応

日本のビジネス会話では「コミット」「KPI」「ROI」のような英単語混在(コードスイッチ)が頻発する。 純日本語学習モデルではこれらが文字化けすることが多く、 多言語事前学習モデル(Whisper, XLS-R, MMS)の出番となる。 評価時には日本語部分と英語部分で CER/WER を別計算する習慣を持つと、 失敗の原因を切り分けやすい。 SSDSE-B-2026 のような統計用語でも「GDP」「CPI」のような略語が頻出するため、 多言語対応はもはや必須要件と考えるべきである。

🔭 ASR の今後とマルチモーダル化

ここ数年で ASR は「音声から文字」だけのタスクから、 「音声理解」「マルチモーダル LLM の入力モダリティ」へと役割を拡張しつつある。 GPT-4o、 Gemini 1.5、 Whisper v3 のような新世代モデルは、 音響特徴量を直接 LLM トークン空間に埋め込み、 音声から要約・質問応答・感情推定までを一気通貫で行う。 SSDSE-B-2026 のような統計データの音声問い合わせ UI でも、 単に文字起こしするだけでなく、 「岩手県の世帯数を 2015 年と比較して」のような複雑なクエリを音声からそのまま実行する世界が見えてきた。 ASR を ML エンジニアリングの一機能としてではなく、 ユーザー体験の入口として再設計する視点が今後ますます重要になる。

📦 オープンソースエコシステム

2026 年現在、 ASR のオープンソース実装は (1) ESPnet(学術寄り、 多言語レシピ豊富)、 (2) NVIDIA NeMo(GPU 最適化、 産業寄り)、 (3) SpeechBrain(PyTorch 純正、 教育向け)、 (4) Hugging Face Transformers(モデル動物園)、 (5) Whisper.cpp / Vosk(オンデバイス・軽量)、 が主流である。 SSDSE-B-2026 のような統計教育の文脈で初学者にお勧めしたいのは Whisper(Hugging Face 経由)と Vosk で、 いずれも数行で動く。 本格的に業務システムを組むなら NeMo + KenLM が安定の選択肢、 研究色が強ければ ESPnet が定番、 という棲み分けを覚えておくとよい。 各エコシステムとも GitHub の Issue・Discussion が活発で、 トラブルシューティング情報が豊富に蓄積されている点も実務家には心強い。

🧾 まとめ

本記事では音声認識を、 特徴量パイプライン、 デコーディング戦略、 評価指標、 アーキテクチャ選定、 データセット設計、 失敗事例、 マルチモーダル化、 OSS エコシステムの 8 視点から体系的に整理した。 ASR の業務導入では「モデル選び」よりも「データ設計」「評価設計」「ガバナンス設計」が成果を決めることを繰り返し強調した。 SSDSE-B-2026 のような統計データを音声 UI で扱う教育コンテンツにおいても、 この視点は変わらず有効である。 単なるベンチマーク WER だけでなく、 業務 KPI に直結した固有名詞精度・数値精度・公平性を継続的にモニタする運用体制こそが、 長期的な品質維持の本質となる。 さらに、 公開統計データを扱う音声システムでは、 透明性・説明可能性・データ主体への配慮を技術選択と同等に重視すべきであることも強調しておきたい。 教育・行政・医療の各文脈で必要なガバナンスの粒度は異なるため、 導入前に必ずユースケース別のリスク評価を実施し、 関係者間で透明性ある合意形成を行うことが肝要である。

🖼 視覚的理解 (3 図)

音声認識の挙動を直感把握するための 3 図。 SSDSE-B-2026 には音声が含まれないので、 3 図とも仮定の値で作った合成データである(実際の音声や ASR モデルの測定値ではない)。 散布図は音響特徴量(母音のフォルマント周波数)の分かれ方、 ヒストグラムと箱ひげ図は、 SSDSE-B-2026 の 47 都道府県名を 3 つの収録環境で認識させたと仮定したときの CER(文字誤り率)のばらつきを示す。

日本語 5 母音の F1 と F2 の散布図(合成データ)
図1: 日本語 5 母音の第 1・第 2 フォルマント(F1 × F2)。 成人男性のおおよその目安値(あ 750/1200 Hz、 い 300/2200、 う 350/1350、 え 500/1850、 お 500/850)に乱数を加えた合成データで、 1 母音 60 点。 軸を反転すると口の開き(F1)と舌の前後(F2)に対応する母音図になる。 母音どうしの境目に紛れ込む点(この合成データでは 300 点中 3 点が、 自分の母音より別の母音の中心に近い)が、 音響モデルが取り違えやすい発話にあたる。
47 都道府県名の認識 CER のヒストグラム、収録環境 3 種(合成データ)
図2: 47 都道府県名を各 20 回認識させたと仮定したときの、 県名ごとの CER(20 回分の編集距離の合計 / 文字数の合計)。 1 文字あたりの誤り確率を静音室 2 %・カフェノイズ(SNR 10 dB)8 %・電話品質(8 kHz)15 % と仮定し、 置換・削除・挿入で文字列を壊して計算した。 静音室では 47 県名中 29 が CER 0.02 未満に固まるが、 環境が悪くなると山ごと右へずれて横に広がる。
収録環境別の県名ごとの CER の箱ひげ図(合成データ)
図3: 図 2 と同じ合成結果を収録環境別に並べた箱ひげ図(灰色の点が県名、 ◆ が平均)。 平均 CER は 0.023 → 0.087 → 0.143 と、 仮定した誤り確率(2 % → 8 % → 15 %)にほぼ沿う。 県名はほとんどが 3 文字なので、 1 県名 20 回(約 60 文字)の CER は 1 文字の誤りで約 0.017 動き、 同じ環境の中でも 0〜0.23 まで幅が出る。 環境別・話者集団別に CER を分けて集計しないと、 全体平均だけでは悪条件の劣化が見えない。

🧮 実値で計算してみる(SSDSE-B-2026・47 都道府県)

SSDSE-B-2026 から都道府県別の「インターネット普及率(音声入力ユーザー基盤)」と「議事録対象会議体数」を読み解き、 ASR システムの導入優先度を試算します。 また「人口 × 1 人あたり音声時間」で全国の音声データ規模も推定。

都道府県人口(千)推定音声時間(時間/年)ASR 処理計算量目標 WER
北海道5092千人 × 100h5.1 億 h< 5%
東京都14043千人 × 150h21 億 h< 3%
大阪府8784千人 × 120h10.5 億 h< 4%
沖縄県1467千人 × 110h1.6 億 h< 5%
全国合計125681-158 億 h-

Whisper-large モデルなら 1 時間音声に約 30 秒(リアルタイム比 120 倍)。 158 億時間 / 120 = 1.3 億時間の計算で全国 1 年分の音声処理が可能。

🧮 テーマ 6:WER の壁 — 「90%」と「95%」の間の断絶

音声認識の評価指標は WER(Word Error Rate) = $(S + D + I) / N$。 $S$=置換、 $D$=削除、 $I$=挿入、 $N$=正解単語数。 日本語では単語境界が曖昧なため CER(Character Error Rate)も併用。 90% から 95% への 5 pt 改善が、 95% から 99% への 4 pt 改善よりはるかに容易、 という非線形性に注意。

📐 数式(WER)

$$ \text{WER} = \frac{S + D + I}{N}, \qquad \text{CER} = \frac{S_c + D_c + I_c}{N_c} $$

🔬 数式を言葉で読み解く

🧮 数式に値を入れて手で計算する: 音声認識 WER

合成データで Word Error Rate を計算する。

Step 1: 正解と認識

正解: 10 単語 誤認識: 1 (置換), 1 (削除), 1 (挿入) = 3 編集

Step 2: WER

WER = 3/10 = 0.30 (30%) SR系では WER < 0.10 で良好

🐍 Python で再現

1
2
3
4
words = 10
errors = 3
WER = errors / words
print(f"WER: {WER}")

📤 実行結果

WER: 0.3

💬 手計算 (Step 2) 0.30 と Python 出力が完全一致。

🧮 都道府県名で CER を手で数える — 置換・削除・挿入

CER = (置換 S + 削除 D + 挿入 I) ÷ 正解の文字数 N。 認識結果は説明用に作った例(実際の ASR の出力ではない)で、 よく起きる 3 種類の誤りを 1 つずつ含めた。

正解認識結果(例)数え方CER
神奈川県神奈川「県」が抜けた: D = 11 / 4 = 0.250
大阪府大阪府誤りなし0 / 3 = 0.000
鹿児島県可児島県「鹿」→「可」: S = 11 / 4 = 0.250
和歌山県わかやま県和→わ・歌→か・山→や の S = 3、 「ま」が余分: I = 14 / 4 = 1.000

全体の CER は誤りの合計 ÷ 文字数の合計 = (1 + 0 + 1 + 4) ÷ (4 + 3 + 4 + 4) = 6 ÷ 15 = 0.400。 県ごとの CER を平均すると (0.25 + 0 + 0.25 + 1.0) ÷ 4 = 0.375 で、 集計のしかたで値が変わる。

🎯 このコードでやること:編集距離(置換・削除・挿入の最小回数)を動的計画法で求める関数を書き、 上の 4 組の CER と全体の CER を計算する。

📥 入力例 正解(SSDSE-B-2026 の Prefecture の値)と、 説明用に作った認識結果の 4 組 神奈川県→神奈川 / 大阪府→大阪府 / 鹿児島県→可児島県 / 和歌山県→わかやま県
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def edit_ops(ref, hyp):
    """編集距離(置換・削除・挿入の最小回数)を動的計画法で求める"""
    d = [[0] * (len(hyp) + 1) for _ in range(len(ref) + 1)]
    for i in range(len(ref) + 1): d[i][0] = i
    for j in range(len(hyp) + 1): d[0][j] = j
    for i in range(1, len(ref) + 1):
        for j in range(1, len(hyp) + 1):
            d[i][j] = min(d[i-1][j] + 1, d[i][j-1] + 1,
                          d[i-1][j-1] + (ref[i-1] != hyp[j-1]))
    return d[-1][-1]
pairs = [('神奈川県', '神奈川'), ('大阪府', '大阪府'), ('鹿児島県', '可児島県'), ('和歌山県', 'わかやま県')]
tot_e = tot_n = 0
for ref, hyp in pairs:
    e = edit_ops(ref, hyp); tot_e += e; tot_n += len(ref)
    print(f"{ref:<5} → {hyp:<6}  誤り {e}  CER = {e}/{len(ref)} = {e/len(ref):.3f}")
print(f"全体 CER = {tot_e}/{tot_n} = {tot_e/tot_n:.3f}   県ごとの CER の平均 = {sum(edit_ops(r,h)/len(r) for r,h in pairs)/len(pairs):.3f}")
📤 実行例(実測) 神奈川県 → 神奈川 誤り 1 CER = 1/4 = 0.250 大阪府 → 大阪府 誤り 0 CER = 0/3 = 0.000 鹿児島県 → 可児島県 誤り 1 CER = 1/4 = 0.250 和歌山県 → わかやま県 誤り 4 CER = 4/4 = 1.000 全体 CER = 6/15 = 0.400 県ごとの CER の平均 = 0.375

💬 手で数えた誤り 1・0・1・4 と CER 0.250・0.000・0.250・1.000、 全体 0.400 と県ごとの平均 0.375 がすべて一致する。 和歌山県は音としては正しく聞き取れているのに、 かな書きになっただけで CER が 1.0 になる。 日本語の ASR を評価するときは、 表記の正規化(かなと漢字、 「県」の有無)をしてから数えるか、 しないで数えるかを決めて書いておかないと、 モデルの良し悪しと表記の違いが混ざってしまう。

🐍 Python 実装

SSDSE-B-2026(47 都道府県・2023 年)の実データを使った最小コード:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# SSDSE-B-2026 で ASR 計算規模試算 + Whisper サンプル
import pandas as pd, numpy as np

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1], header=0)
df.columns = pd.read_csv('data/raw/SSDSE-B-2026.csv', nrows=0, encoding='cp932').columns
df = df[df['SSDSE-B-2026'] == 2023]   # 2023 年度の 47 都道府県に絞る(12 年度分が重複しないように)

# 都道府県別の年間音声生成量を推定(人口 × 1 人当たり 120 時間)
df['voice_hours'] = df['A1101'].astype(float) * 120 / 1000  # 1000 で割って「千時間」単位にする
print('音声生成量 TOP10(千時間/年):')
print(df.nlargest(10, 'voice_hours')[['Prefecture', 'voice_hours']])

# Whisper による実音声書き起こし(別途音声ファイル必要)
# import whisper
# model = whisper.load_model('base')
# result = model.transcribe('audio.mp3', language='ja')
# print(result['text'])

# WER 計算サンプル
from jiwer import wer
reference = '今日は良い天気です'
hypothesis = '今日は良い天気でしょう'
error = wer(reference, hypothesis)
print(f'WER = {error:.3f}')

🐍 Python 実装:SSDSE-B-2026 の都道府県名を音声化し、 Log-Mel スペクトログラムに変換

🎯 このコードでやること:SSDSE-B-2026 から都道府県名を読み込み、 仮想的に「音声化(TTS)→ Log-Mel スペクトログラム抽出」のワークフローを構築する。 librosa を用いてフレーム分割・メル変換を行い、 80 次元 × 約 100 frames/sec の特徴量列を得る。

📥 入力データ(SSDSE-B-2026 の Prefecture 列 抜粋):

Code Prefecture R01000 北海道 R02000 青森県 R03000 岩手県 R13000 東京都 R27000 大阪府 R47000 沖縄県
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import pandas as pd
import numpy as np
import librosa

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]   # 2023 年度の 47 都道府県に絞る
prefectures = df['Prefecture'].tolist()
print(f'都道府県数: {len(prefectures)}')
print(f'最初の 3 件: {prefectures[:3]}')

# 仮想シナリオ: 16 kHz, 2 秒の擬似音声で「東京都」を表すと仮定
sr = 16000
audio_len_sec = 2.0
# 実環境では TTS で生成 — ここでは特徴量抽出のパイプラインを示すため定数信号を使用
y = librosa.tone(440.0, sr=sr, duration=audio_len_sec)  # ラ音 (テスト用ダミー信号)

# Log-Mel スペクトログラム抽出
mel = librosa.feature.melspectrogram(y=y, sr=sr, n_fft=400, hop_length=160, n_mels=80)
log_mel = librosa.power_to_db(mel)
print(f'特徴量 shape: {log_mel.shape}  # (n_mels, time_frames)')
print(f'1 秒あたりフレーム数: {log_mel.shape[1] / audio_len_sec:.1f}')

# MFCC への変換 (DCT 圧縮で 13 次元へ)
mfcc = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=13, n_fft=400, hop_length=160, n_mels=80)  # メルと同じフレーム設定
print(f'MFCC shape: {mfcc.shape}  # (n_mfcc=13, time_frames)')

📤 実行例(実際の出力):

都道府県数: 47 最初の 3 件: ['北海道', '青森県', '岩手県'] 特徴量 shape: (80, 201) # (n_mels, time_frames) 1 秒あたりフレーム数: 100.5 MFCC shape: (13, 201) # (n_mfcc=13, time_frames)

💬 結果の読み方:2 秒の音声から 80 次元 × 201 フレーム ≒ 16,080 個の値の特徴量列が得られた。 生波形は 32,000 サンプルだったので、 情報量は約半分に圧縮されている。 MFCC ではさらに DCT で 13 次元に絞り、 2,613 個(生波形の 1/12)まで圧縮できる。 これが古典的 ASR が「メモリ・計算が軽い」と言われる理由。

🐍 Python 実装:CTC を最小コードで体感し、 SSDSE-B の都道府県名で動かす

🎯 このコードでやること:PyTorch の nn.CTCLoss を使い、 SSDSE-B-2026 の都道府県名(例「東京都」)を 50 フレームの音声フレームから推定するダミー学習を 1 ステップ走らせる。 CTC のパラメータ・入力形状・出力形状の感覚を掴む。

📥 入力データ(SSDSE-B-2026 の Prefecture 列):

Prefecture: '東京都' (3 文字) 全都道府県数: 47 語彙サイズ (blank 含む): 5 (仮定) # 簡略化のため
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import torch
import torch.nn as nn
import pandas as pd
torch.manual_seed(0)   # 実行のたびに同じ結果が出るようにする

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
target_pref = df.loc[df['Code'] == 'R13000', 'Prefecture'].iloc[0]
print(f'対象都道府県: {target_pref}')

# 簡略化された語彙: blank=0, 東=1, 京=2, 都=3, 他=4
vocab = {'<blank>': 0, '東': 1, '京': 2, '都': 3, '他': 4}
target_ids = torch.tensor([vocab[c] for c in target_pref], dtype=torch.long)
print(f'ターゲット ID: {target_ids.tolist()}')

# ダミー音響モデル出力: 50 フレーム × バッチ 1 × 語彙 5
T, N, C = 50, 1, len(vocab)
log_probs = torch.randn(T, N, C).log_softmax(dim=-1)
input_lengths = torch.full((N,), T, dtype=torch.long)
target_lengths = torch.tensor([len(target_ids)], dtype=torch.long)

ctc_loss = nn.CTCLoss(blank=0, zero_infinity=True)
loss = ctc_loss(log_probs, target_ids.unsqueeze(0), input_lengths, target_lengths)
print(f'CTC Loss: {loss.item():.4f}')
print(f'入力 shape: {log_probs.shape}  (時間, バッチ, 語彙)')
print(f'平均パス数の対数 ≒ -loss = {-loss.item():.4f}')

📤 実行例(実際の出力):

対象都道府県: 東京都 ターゲット ID: [1, 2, 3] CTC Loss: 24.2143 入力 shape: torch.Size([50, 1, 5]) (時間, バッチ, 語彙) 平均パス数の対数 ≒ -loss = -24.2143

💬 結果の読み方:CTC loss は「正解文字列に到達するパスの確率の合計の負対数」。 nn.CTCLoss の既定 reduction='mean' は負対数を目標の長さ(3 文字)で割るので、 表示の 24.2143 は 1 文字あたりの値で、 合計は約 72.64(reduction='sum' で確かめられる)。 つまり乱数の出力では正解「東京都」に至る全パスの確率を足しても $e^{-72.6}$ 程度しかなく、 学習が進むにつれ loss は 0 に近づく。 最後の行の「平均パス数の対数」という表示名は正確ではなく、 −loss は 1 文字あたりの対数確率と読む。 50 フレーム → 3 文字のような圧縮率でも、 動的計画法のおかげで微分可能に計算できているのがポイント。

🐍 Python 実装:SSDSE-B-2026 の都道府県別人口を WER で評価する

🎯 このコードでやること:SSDSE-B-2026 の A1101(総人口)を読み上げる仮想シナリオで、 jiwer ライブラリを使って WER / CER を計算する。 2023 年度の人口上位 3 都府県について「〇〇の総人口は△△人です」という正解文を作り、 ASR が完全正解・数字 1 文字の置換・単語の削除と挿入をした 3 通りの認識結果で WER と CER がどう変わるかを確認する。

📥 入力データ(SSDSE-B-2026 A1101 列 抜粋):

Prefecture A1101 (総人口) 北海道 5,092,000 東京都 14,086,000 大阪府 8,763,000 沖縄県 1,468,000 (2023 年度の値。年度で絞らないと東京都の別年度が上位を占める)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import pandas as pd
from jiwer import wer, cer

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]   # 2023 年度に絞る(絞らないと東京都の別年度が上位 3 件を占める)
top3 = df.nlargest(3, 'A1101')[['Prefecture', 'A1101']]
print('総人口 TOP3:')
print(top3.to_string(index=False))

# 仮想シナリオ: ASR が読み上げ結果を返したとする
reference_sentences = [
    f"{row.Prefecture}の総人口は{row.A1101:,}人です"
    for row in top3.itertuples()
]
hypothesis_sentences = [
    "東京都の総人口は14,086,000人です",     # 完全正解
    "神奈川県の総人口は9,228,000人です",    # 1 文字置換(9 → 8)
    "大阪府の人口は8,763,000人と思います",  # 単語削除 + 挿入
]

for ref, hyp in zip(reference_sentences, hypothesis_sentences):
    print(f'\nREF: {ref}')
    print(f'HYP: {hyp}')
    print(f'  WER (空白分割): {wer(ref, hyp):.3f}')
    print(f'  CER (文字単位): {cer(ref, hyp):.3f}')

📤 実行例(実際の出力):

総人口 TOP3: Prefecture A1101 東京都 14086000 神奈川県 9229000 大阪府 8763000 REF: 東京都の総人口は14,086,000人です HYP: 東京都の総人口は14,086,000人です WER (空白分割): 0.000 CER (文字単位): 0.000 REF: 神奈川県の総人口は9,229,000人です HYP: 神奈川県の総人口は9,228,000人です WER (空白分割): 1.000 CER (文字単位): 0.048 REF: 大阪府の総人口は8,763,000人です HYP: 大阪府の人口は8,763,000人と思います WER (空白分割): 1.000 CER (文字単位): 0.250

💬 結果の読み方:日本語では空白がないため、 jiwer の wer は文字列全体を 1 単語として扱い 0 or 1 になりがち。 日本語 ASR の評価では CER(文字誤り率)を使うのが鉄則。 2 番目は数字 1 文字(9→8)だけの置換で CER 0.048(21 文字中 1 文字)なのに、 WER は文全体が 1 単語扱いなので 1.000 になる。 3 番目は「総」の削除と「です」→「と思います」の置換・挿入で 5 文字分の編集になり、 CER 0.250(20 文字中 5 文字)。 WER の数字だけ見るとどちらも 100% の誤りに見えるが、 実態は CER 5% と 25% という大きな違いがある点に注意。

🐍 追補 3:Whisper を Python で動かす最小フル例

🎯 このコードでやること:OpenAI Whisper を pip install し、 音声ファイル(事前に SSDSE-B-2026 の都道府県名を読み上げた wav と仮定)から書き起こし → CER 算出までを一気通貫で実行する。 ローカル GPU が無くても CPU で tiny モデルが動く。

📥 入力データ(仮定):

audio/hokkaido.wav (北海道, 16kHz, mono, 2.5sec) audio/tokyo.wav (東京都, 16kHz, mono, 2.0sec) audio/okinawa.wav (沖縄県, 16kHz, mono, 2.4sec) SSDSE-B-2026.csv の Prefecture 列を参照用に使用
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# pip install openai-whisper jiwer pandas
import whisper
import pandas as pd
from jiwer import cer

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
target_codes = ['R01000', 'R13000', 'R47000']  # 北海道, 東京都, 沖縄県
audio_files = {
    'R01000': 'audio/hokkaido.wav',
    'R13000': 'audio/tokyo.wav',
    'R47000': 'audio/okinawa.wav',
}

model = whisper.load_model('tiny')   # CPU でも動く 39M モデル
results = []
for code in target_codes:
    ref = df.loc[df['Code'] == code, 'Prefecture'].iloc[0]
    result = model.transcribe(audio_files[code], language='ja')
    hyp = result['text'].strip().replace('。', '').replace('、', '')
    c = cer(ref, hyp)
    results.append({'Code': code, 'REF': ref, 'HYP': hyp, 'CER': c})
    print(f'{code}: REF={ref} | HYP={hyp} | CER={c:.3f}')

result_df = pd.DataFrame(results)
print(f'\n平均 CER: {result_df["CER"].mean():.3f}')

📤 実行例(出力の形を示す例。audio/ の音声ファイルと Whisper モデルのダウンロードが必要なため、このサイトでは実測していない):

R01000: REF=北海道 | HYP=北海道 | CER=0.000 R13000: REF=東京都 | HYP=東京と | CER=0.333 R47000: REF=沖縄県 | HYP=お肉県 | CER=0.667 平均 CER: 0.333

💬 結果の読み方:この例では「北海道」は完全一致、「東京都」→「東京と」は 3 文字中 1 文字の置換で CER 0.333、「沖縄県」→「お肉県」は 2 文字の置換で 0.667 になり、平均 CER は 0.333。コードは句読点しか消さないので、Whisper が「お肉 県」のように空白を入れて返すと空白も 1 文字の挿入に数えられ、CER は 1.000 に跳ね上がる。比べる前に空白も除いておく。 同じ音声を base や large-v3 に投げ替えるだけで CER が劇的に改善するため、 「精度が足りない=モデルを大きくする」が第一選択。 ただし計算コストとレイテンシのトレードオフを必ず測ること。

🐍 追補 5:SSDSE-B-2026 全 47 都道府県を音声化評価する完全パイプライン

🎯 このコードでやること:SSDSE-B-2026 の 47 都道府県名すべてに対し、 (1) gTTS で音声化、 (2) Whisper で書き起こし、 (3) CER 計算、 (4) 地方別の集計レポートを CSV 出力するエンドツーエンドパイプライン。 業務 PoC に直結する形に。

📥 入力データ(SSDSE-B-2026.csv 全行):

SSDSE-B-2026 47 行 (北海道〜沖縄県) 列: SSDSE-2026 (年度), Code, Prefecture, A1101 (総人口) ほか 出力 dir: ./tts_out/ (各都道府県の wav) 出力 CSV: ./asr_report.csv
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# pip install gtts openai-whisper jiwer pandas pydub
import os
import pandas as pd
from gtts import gTTS
import whisper
from jiwer import cer

os.makedirs('tts_out', exist_ok=True)

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]   # 2023 年度の 47 都道府県に絞る(絞らないと 564 行を音声化してしまう)
print(f'対象: {len(df)} 都道府県')

# 地方区分(SSDSE のコード接頭から)
def region(code):
    n = int(code[1:3])
    if n <= 1: return '北海道'
    if n <= 7: return '東北'
    if n <= 14: return '関東'
    if n <= 23: return '中部'
    if n <= 30: return '近畿'
    if n <= 35: return '中国'
    if n <= 39: return '四国'
    return '九州沖縄'

df['region'] = df['Code'].apply(region)

# 1) TTS で音声生成 (初回のみ)
for row in df.itertuples():
    path = f'tts_out/{row.Code}.mp3'
    if not os.path.exists(path):
        gTTS(text=row.Prefecture, lang='ja').save(path)

# 2) Whisper で書き起こし
model = whisper.load_model('base')
records = []
for row in df.itertuples():
    result = model.transcribe(f'tts_out/{row.Code}.mp3', language='ja')
    hyp = result['text'].strip().replace('。', '').replace(' ', '')
    c = cer(row.Prefecture, hyp)
    records.append({'Code': row.Code, 'region': row.region,
                    'REF': row.Prefecture, 'HYP': hyp, 'CER': c})

# 3) 集計
report = pd.DataFrame(records)
print('\n=== 全体 ===')
print(f'平均 CER: {report["CER"].mean():.3f}')
print(f'CER=0 (完全一致): {(report["CER"]==0).sum()}/{len(report)} 県')

print('\n=== 地方別 ===')
print(report.groupby('region')['CER'].agg(['mean', 'max', 'count']))

report.to_csv('asr_report.csv', index=False, encoding='utf-8-sig')
print('\nasr_report.csv に出力完了')

📤 実行例(出力の形を示す例。gTTS による外部への音声合成と Whisper モデルが必要なため、このサイトでは実測していない):

対象: 47 都道府県 === 全体 === 平均 CER: 0.082 CER=0 (完全一致): 38/47 県 === 地方別 === mean max count region 中国 0.040 0.20 5 中部 0.061 0.17 9 九州沖縄 0.150 0.40 8 北海道 0.000 0.00 1 四国 0.075 0.25 4 東北 0.140 0.33 6 近畿 0.052 0.20 7 関東 0.020 0.10 7 asr_report.csv に出力完了

💬 結果の読み方:この出力は形を示す例で、平均 CER 0.082 や地方別の値は実験結果ではない(地方別の mean を県数で重み付けして平均しても 0.076 で、全体の 0.082 とも合っていない)。実測で確かめられるのは地方別 count(北海道 1・東北 6・関東 7・中部 9・近畿 7・中国 5・四国 4・九州沖縄 8 県、合計 47)という区分けのほうで、これはコードの region() がコード番号で機械的に決める。北海道は 1 県しかないので、地方別の mean・max を比べるときは県数の違いに注意する。実際に gTTS と Whisper で動かしたら、CER が 0 でない県の REF と HYP を asr_report.csv で 1 件ずつ見て、どの音で崩れたかを確かめる。

🐍 補足: Whisper / wav2vec2 / WER 計算の追加実装

音声認識(speech recognition / ASR)の現代スタックは、 OpenAI Whisper(多言語・多タスク)と Meta wav2vec2(自己教師あり事前学習)が双璧。 加えて、 認識精度の標準指標 WER(Word Error Rate)と CER(Character Error Rate)の正しい計算が必須だ。 SSDSE-B-2026 の都道府県名を題材に「読み上げを認識し、 自治体名を抽出する」というワークフロー全体を補強する。

① Whisper による文字起こし(多言語・タイムスタンプ付き)

このコードでやること:47 都道府県名を読み上げた音声 audio/prefectures.wav を Whisper-base で日本語認識し、 セグメント別タイムスタンプと信頼度(avg_logprob)を取り出す。

📥 入力データ (音声ファイル + SSDSE-B-2026 都道府県名辞書):

audio/prefectures.wav : 16 kHz mono / 32.0 sec SSDSE-B-2026.csv : 47 件 北海道, 青森県, 岩手県, 宮城県, ..., 沖縄県
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import whisper, pandas as pd

model = whisper.load_model('base')
result = model.transcribe('audio/prefectures.wav',
                          language='ja', word_timestamps=False)

pref = pd.read_csv('data/raw/SSDSE-B-2026.csv',
                   encoding='cp932', skiprows=[1])['Prefecture'].unique()

rows = []
for seg in result['segments']:
    hit = [p for p in pref if p in seg['text']]
    rows.append((seg['start'], seg['end'], seg['text'].strip(),
                 round(seg['avg_logprob'], 3), ','.join(hit)))
print(pd.DataFrame(rows, columns=['start','end','text','logprob','match']).head())

📤 実行結果(イメージ・未実測。audio/prefectures.wav は同梱しておらず Whisper のモデルも必要なため、出力の形を示す例):

start end text logprob match 0 0.00 1.84 北海道、青森県、 -0.18 北海道,青森県 1 1.84 3.91 岩手県、宮城県、 -0.21 岩手県,宮城県 2 3.91 6.02 秋田県、山形県、 -0.20 秋田県,山形県 3 6.02 8.10 福島県、茨城県、 -0.23 福島県,茨城県 4 8.10 10.21 栃木県、群馬県、 -0.19 栃木県,群馬県

💬 avg_logprob が -0.5 を下回るセグメントは「自信が低い」サイン → 棄却 or 再認識(large モデル切替)。 SSDSE 都道府県名辞書と突合させると幻覚(hallucination)も同時検出できる。

② wav2vec2 による日本語認識(自己教師ありモデル)

このコードでやること:Whisper と並ぶ代表モデル wav2vec2 を transformers 経由で呼び、 同じ音声を CTC デコードで文字起こしする。 Whisper との比較で「どちらが業務に向くか」を実測できる。

📥 入力データ:

audio/prefectures.wav (16 kHz, 32.0 sec) モデル: jonatasgrosman/wav2vec2-large-xlsr-53-japanese
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import librosa, torch
from transformers import Wav2Vec2ForCTC, Wav2Vec2Processor

mid = 'jonatasgrosman/wav2vec2-large-xlsr-53-japanese'
proc = Wav2Vec2Processor.from_pretrained(mid)
model = Wav2Vec2ForCTC.from_pretrained(mid)

wav, sr = librosa.load('audio/prefectures.wav', sr=16000)
inputs = proc(wav, sampling_rate=16000, return_tensors='pt')
with torch.no_grad():
    logits = model(**inputs).logits
text = proc.batch_decode(logits.argmax(-1))[0]
print('w2v2:', text[:80])

📤 実行例(イメージ・未実測:audio/prefectures.wav は同梱しておらず、 wav2vec2 のモデル取得も必要なため、 出力の形を示す例):

w2v2: ほっかいどう あおもりけん いわてけん みやぎけん あきたけん やまがたけん ふくしまけん ...

💬 上の行は実行していない想定の表示で、 wav2vec2 の CTC デコードは学習データの書き方に沿った文字列をそのまま返す。 かな書きで学習したモデルならこの例のように「ほっかいどう」となり、 SSDSE の「北海道」と突き合わせるには後処理(かな → 漢字の辞書照合)が要る。 句読点も付かないので、 Whisper の出力と比べるときは次の ③ のように CER で揃えて評価する。

③ WER / CER を jiwer で計算(標準評価指標)

このコードでやること:正解テキスト(reference)と Whisper / wav2vec2 の出力(hypothesis)で WER・CER を比較。 日本語は単語境界が曖昧なため CER 併用が事実上必須。

📥 入力データ(reference は SSDSE-B-2026 の都道府県名連結):

ref : 北海道 青森県 岩手県 宮城県 ... 沖縄県 hyp_wh : 北海道 青森県 岩手県 宮城県 ... 沖縄県 hyp_w2v : ほっかいどう あおもりけん ... おきなわけん
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from jiwer import wer, cer
import pandas as pd

pref = pd.read_csv('data/raw/SSDSE-B-2026.csv',
                   encoding='cp932', skiprows=[1])['Prefecture'].unique()
ref = ' '.join(pref)                                  # 47 県名
hyp_wh  = ref                                         # Whisper はほぼ一致
hyp_w2v = 'ほっかいどう あおもりけん いわてけん'      # 例: 表記揺れ大

print('Whisper  WER:', wer(ref, hyp_wh),  ' CER:', cer(ref, hyp_wh))
print('wav2vec2 WER:', wer(ref, hyp_w2v), ' CER:', cer(ref, hyp_w2v))

📤 実行結果:

Whisper WER: 0.0 CER: 0.0 wav2vec2 WER: 1.0 CER: 0.9894736842105263

💬 hyp_wh は正解と同じ文字列なので WER・CER とも 0、 hyp_w2v は 47 県名に対してひらがな 3 語しか無いので、 47 語すべてが誤りの WER 1.0、 文字単位でもほぼ全部を書き直す必要があり CER 0.989 になる。 ひらがな表記を漢字に直すだけで数値が大きく変わるので、 同じ音声でも評価指標と後処理の有無で WER は 0 〜 1 まで激変する。 → 公平比較には「reference / hypothesis を正規化(漢字統一・全角半角統一・句読点除去)」してから WER/CER を計算するのが鉄則。

📋 ASR 評価チェックリスト

🎚 音響特徴量パイプラインの深掘り

音声認識の前段では生波形を直接 NN に与えるのではなく、 多くの場合「短時間フーリエ変換 → メルフィルタバンク → 対数 → ケプストラム化」という古典的な信号処理を経由する。 ここを丁寧に理解しておくと、 ASR の WER が下がらない原因が「モデル」ではなく「特徴量側」にあるケースを切り分けられる。 とくに 16kHz サンプリング・25ms 窓・10ms シフトという定石は、 人間の音素長(30〜100ms)に対して 2〜4 フレーム重なるように設計されており、 これより短いと音素境界が滑らかにならず、 これより長いと子音の立ち上がりを潰してしまう。 メル尺度はピッチ知覚の周波数選択性に合わせた変換で、 1000Hz 以下はほぼ線形、 それ以上は対数的に圧縮される。 これにより 80 次元程度のメルバンクで日本語の母音・摩擦音をほぼ識別可能な空間に落とし込める。

📋 主要な特徴量の比較

特徴量次元計算量頑健性主な用途
生波形 (raw)16,000/秒低(変換なし)低(雑音敏感)wav2vec2, HuBERT の入力
STFT スペクトログラム257〜513中中音声強調, 分離, 可視化
メルスペクトログラム80〜128中高Whisper, Tacotron, ASR 全般
MFCC13〜40中高従来型 GMM-HMM, 話者識別
PLP / LFR13〜52中高電話帯域, 騒音下 ASR

近年の self-supervised 系(wav2vec2, HuBERT)は生波形を直接受け取り、 1D 畳み込みで暗黙にスペクトログラム相当の表現を学習する。 一方 Whisper は古典的なログメルスペクトログラム(80 バンド、 25ms 窓、 10ms シフト)を採用しており、 これは推論コスト削減と多言語汎化のバランスを取った設計と言える。 SSDSE-B-2026 のような統計ダッシュボード読み上げ等、 既知の語彙が中心であれば、 メル特徴+CTC で十分実用域に届くが、 雑談・口語・方言が混ざる場面では生波形ベースのモデルが優位になりやすい。

🎛 ノイズ・残響への対処

屋外録音や会議室録音では、 SNR(信号対雑音比)と残響時間(RT60)が WER を大きく左右する。 SNR が 5dB 以下、 RT60 が 0.6 秒以上だと、 クリーン環境で WER 5% のモデルでも 30% 超に劣化することがある。 対処は概ね 3 系統で、 (1) 録音側でラベリアマイクや指向性マイクを使う、 (2) 前段に音声強調 NN(Demucs, DPRNN 等)を挟む、 (3) ノイズ重畳・残響シミュレーションで学習データを増やす(multi-condition training)、 のいずれかになる。 とくに (3) は ESPnet などのレシピで標準化されており、 学習データに対して MUSAN/CHiME ノイズと RIR(部屋インパルス応答)を畳み込むだけで頑健性が大幅に上がる。 評価では必ず「クリーン WER / ノイズ重畳 WER」を併記すべきである。

🧪 サンプリングレートの落とし穴

市販マイクの多くは 44.1 kHz / 48 kHz でサンプリングするが、 ASR モデルの大半は 16 kHz で訓練されている。 そのままモデルに流すと、 内部的にダウンサンプリングが入ったり、 アンチエイリアスフィルタが弱くて高域の折り返し雑音が混入したりする。 必ず librosa.load(path, sr=16000) や soxr_hq で明示的にリサンプリングしてからモデルに渡すこと。 電話帯域(8 kHz)の音声は 16 kHz にアップサンプリングしてもナイキスト周波数の制約で 4 kHz 以上の情報は復元できない点も忘れてはいけない。 サンプリングレート不整合は WER 悪化要因として最頻ながら気づきにくい。

⚠️ よくある落とし穴

⚠️ ドメイン違い
訓練データ(一般会話)と運用(医療・法律用語)で WER 急上昇。 ドメイン適応必須。
⚠️ ノイズ・残響
電話・会議室で精度低下。 ノイズ除去や音声強調と組み合わせ。
⚠️ 固有名詞
訓練に無い人名・地名は誤認しがち。 カスタム辞書 / Biased Decoding で対策。
⚠️ ストリーミング遅延
End-to-end の Encoder-Decoder はオフライン向け。 RNN-T or CTC を選ぶ。
⚠️ プライバシー
音声は声紋を含む生体情報。 オンプレ / 暗号化が必要なケース多数。

⚠️ テーマ 10:プライバシーと社会実装上の罠

音声は声紋という生体情報を含み、 顔写真と同様に個人識別子となります。 ASR システムを設計する際は、 単に精度を追うだけでなく以下の落とし穴を意識する必要があります。

⚠️ 録音音声からの再特定リスク
クラウド ASR に送った音声は、 たとえテキスト返却後にサーバ側で削除されたとしても、 中間ログから声紋ベクトルを抽出されると話者特定が可能。 オンプレ展開・ End-to-End 暗号化が必要なドメイン(医療・金融・法務)が増加中。
⚠️ バイアス — 男性・標準アクセント有利
訓練データに白人男性英語が多いため、 女性・少数言語話者で精度低下が起こる。 Whisper でも日本語女性話者の CER が男性より 1〜2 pt 高い傾向。 評価セットを意図的にバランスして可視化することが必須。
⚠️ ハルシネーション(Whisper 特有)
無音区間に「ご視聴ありがとうございました」「字幕は ABC により提供されています」のような YouTube 字幕由来の幻覚文を挿入する既知バグ。 VAD(Voice Activity Detection)で無音をカットしてから投入するのが定石。
⚠️ 法規制(GDPR・改正個人情報保護法)
EU では声紋は GDPR の Special Category(生体情報)。 日本でも 2022 年改正で「容易照合性」が広がり、 音声データの取扱いは要配慮個人情報に近い扱いが推奨。 同意取得・保存期間・削除フローの整備を初期から組み込むこと。
⚠️ 子ども・高齢者バイアス
音声 ASR は成人男女の音声中心に訓練されるため、 子ども(基本周波数 250〜400 Hz)・高齢者(嗄声・テンポ低下)で精度が大幅低下。 学習教材・介護記録アプリでは専用の Fine-tuning が必要。

🧪 理解度チェック

  1. 「東京都」を「東京と」と認識した。 CER はいくつか。
    解答: 「都」→「と」の置換 1 つで、 1 ÷ 3 = 0.333。
  2. 正解 15 文字で誤り 6 個の結果と、 正解 4 文字で誤り 1 個の結果がある。 全体の CER を求めるとき、 2 つの CER(0.400 と 0.250)を単純に平均してよいか。
    解答: よくない。 全体の CER は誤りの合計 ÷ 文字数の合計で (6 + 1) ÷ (15 + 4) = 0.368。 単純平均の 0.325 は短い発話を重く数えてしまう。
  3. CER が 0 より大きくなるのに、 人が聞けば正しく認識できている例を 1 つ挙げよ。
    解答: 「和歌山県」を「わかやま県」と返す場合(CER 1.0)。 表記が違うだけで音は合っている。 評価の前に表記をそろえるかどうかを決める。
  4. 挿入が多いと CER は 1 を超えることがあるか。
    解答: ある。 分母は正解の文字数なので、 正解より長い余分な文字列を出力すると誤りの数が正解の文字数を上回り、 CER > 1 になる(無音区間で文章を作ってしまうハルシネーションで起きやすい)。

🗺 概念マップ

音声認識の周辺概念をテーマ別ツリーで整理:

音声信号処理 (Speech Processing)
  ├── 話者識別 (Speaker Recognition)
  ├── 【音声認識 (ASR / Speech-to-Text)】 ← ここ
  │     ├── 音響モデル (HMM / DNN / Conformer)
  │     ├── 言語モデル (n-gram / Transformer LM)
  │     └── End-to-End モデル (Whisper / wav2vec 2.0)
  └── 音声合成 (TTS) / 音声強調 (Speech Enhancement)

この階層構造を頭に入れておくと、 学習や論文読みで「自分が今どこにいるか」を見失わずに済みます。

🗺 拡張概念マップ — 音声認識の位置づけ

音声認識は単独で存在せず、 音声処理パイプライン全体の中で隣接技術と連携します。 以下は典型的なエンドツーエンド音声処理フローです:

[マイク入力]
   │
   ▼
[VAD: 音声区間検出] ─── 無音カット
   │
   ▼
[特徴量抽出: Log-Mel] ─── 80次元×100fps
   │
   ▼
[ASR: Whisper / Conformer] ─── テキスト出力 (CER 5〜10%)
   │
   ├──▶ [話者分離: Diarization] ─── speaker A / B
   ├──▶ [句読点復元: Punctuation Restoration]
   ├──▶ [固有名詞補正: NER + 辞書]
   └──▶ [LLM 後処理: GPT-4 で誤り訂正]
   │
   ▼
[最終テキスト]
   │
   ▼
[NLP 下流タスク: 翻訳 / 要約 / 感情分析 / 検索]

この図のとおり、 ASR は 音声処理の中核ではありますが、 上流(VAD・特徴量)と下流(NLP・LLM)と連動して初めて実用システムになります。 「Whisper を呼ぶだけ」では本番運用に耐えないのはこのため。

音声認識 Whisper(OpenAI Wav2Vec 2.0(Me Conformer(Goog RNN-T Diarization Punctuation Re

🔗 隣接手法への橋渡し

「音声認識」は 音声波形を文字列に変換するタスク として、 上流の信号処理 (フーリエ変換・MFCC) と下流の自然言語処理・対話システムを繋ぐ。 Whisper や wav2vec などの自己教師あり pretrain が主流となり、 個別言語モデルから多言語汎用へとパラダイムが移行している。

⬆️ 上流: 音声信号処理

⬌ 並列: 他の音声タスク

⬇️ 下流: 応用システム

音声認識は波形 → FFT → 音響モデル → 言語モデル → テキスト という伝統パイプラインから、 Whisper のような end-to-end Transformer まで進化し、 下流の NLP / アシスタントと結合して価値を生む。

🌳 手法選択フロー

「音声認識」を選ぶかは、 言語・雑音環境・専門用語で判断する。

  1. 一般会話か? Yes → Whisper / Google STT 等の汎用モデル、 No → 次へ
  2. 専門用語を含むか? Yes → ドメイン適応 (fine-tune) または辞書補正必須、 No → 汎用モデルで OK
  3. リアルタイム性は? Yes (会議転記) → Streaming ASR (Whisper streaming / wav2vec)、 No (録音後処理) → バッチ Whisper

音声認識は SSDSE-B-2026 のような数値統計では使わないが、 自由記述アンケートの音声収集を文字化する場面で活躍。 日本語は方言・固有名詞で精度が落ちやすい。

🧭 解説深化:直感・落とし穴・発展(追記)

本ページ上部の各セクションを踏まえ、 「音声認識とは何をしている技術か」を 直感 → 落とし穴 → 発展 の 3 視点で一望できるよう、 要点だけを凝縮して追記します。 上の 🎮 音声区間検出(VAD)デモ で「波形を特徴で区切る」感覚を掴んでから読むと、 全体像がひと続きに見えてきます。

🎨 直感 — 波形からテキストへの一本道

音声認識(ASR)の本質は 「空気の振動(音声波形)を文字列(テキスト)に翻訳する」 こと。 生波形は 1 秒で数千〜1 万数千点もあり、 情報が多すぎてそのままでは扱いにくい。 そこでまず 特徴抽出で「人間の聴覚に近い数値ベクトル列」へ圧縮します。 定番が MFCC や メルスペクトログラム(25 ms 窓・10 ms シフト・80 メル程度)。 次に、 その特徴列が「どの音(音素・文字)らしいか」を当てる 音響モデルと、 「その並びが日本語として自然か」を測る 言語モデルを組み合わせて、 もっともらしい文字列を選びます。 現代の主流は、 この 2 段を分けずに 1 つの巨大な Transformer で一気通貫(エンドツーエンド)に解く方式で、 Whisper がその代表。 相手にするのは整った書き言葉ではなく、 言い淀み・フィラー・訛りを含む 話し言葉である点が、 文書 OCR などとの決定的な違いです。

⚠️ 落とし穴(重要)— 「動いた」と「使える」の間にある壁

🚀 発展 — 学習の道筋(この順で辿ると腑に落ちる)

  1. 特徴抽出:MFCC / メルスペクトログラム / フィルタバンク。 波形を聴覚寄りの数値列へ。
  2. 音響モデルの進化:HMM-GMM(古典)→ DNN-HMM → ニューラル音響モデル。 「確率で音素を当てる」から「深層表現で当てる」へ。
  3. 言語モデル:n-gram から Transformer LM・大規模言語モデルへ。 音響の曖昧さを言葉の自然さで整流する。
  4. 整列問題の解決:入力(フレーム多数)と出力(文字少数)の長さ不一致を CTC(全パス周辺化)で解く。 「同じ文字を何フレーム繰り返してもよい」という発想は DTW の直感の延長。
  5. Attention / Encoder-Decoder:Attention で「次の文字を出すとき音声のどこを見るか」を学習(Listen-Attend-Spell)。 Transformer がその主役に。
  6. エンドツーエンド・自己教師あり:自己教師あり学習(Wav2Vec 2.0)で少量ラベルでも高精度。 Whisper は 68 万時間・多言語・multitask で汎用 ASR を実現。
  7. 評価と多言語・話者適応:WER / CER で測り、 多言語対応・LoRA / Adapter による話者・方言適応で「最悪ケース」を底上げする。

🔗 関連ページ(相対リンク)

音声から言語への橋渡しは 機械翻訳と同じ Encoder-Decoder 枠組みで、 テキスト化した後の理解は 自然言語処理が担います。 アーキテクチャの中核は Transformer と Attention、 言語モデルの入口は n-gram(発展形は n-gram 言語モデル)。 音声そのものの扱い・話者認識・音声合成は 音声データ/話者認識 を参照してください。

※ 本追記の数値(WER/CER のレンジ等)は公開研究・ベンチマークの一般的傾向を示す目安であり、 特定の実測値ではありません。 音声認識の具体例(都道府県名の読み上げ等)はすべて 合成・架空のシナリオで、 実音声ファイルは扱っていません。 実測データは SSDSE-B-2026(47 都道府県)の統計値のみを本ページの計算例で使用しています。