コンテンツにスキップ

第11回 自然言語処理との接続(NLUと音声)

この回の資料

CBT(Computer-Based Testing)・動画形式


この回の学習目標

  1. 音声処理パイプライン(ASR → NLU → DM → NLG → TTS)の全体像を説明できる
  2. NLU(自然言語理解)の主要タスク(意図推定・スロット抽出・NER)を区別できる
  3. SLU(音声言語理解)がNLUとどう異なるかを理解できる
  4. 対話管理(Dialogue Management)のルールベース・機械学習アプローチを比較できる
  5. LLMと音声処理の統合方法と、構成ごとの違いを説明できる
  6. PythonでSpeechRecognition・Whisper・transformersを使った実装ができる

進化の視点:語と意味の結びつきは、どこまで恣意的か

語の形と意味の結びつきには恣意的なものが多い一方、非恣意的な傾向もある。Blasiほか(2016)は、多数の言語の基礎語彙を比較し、特定の意味に特定の音が偏る傾向を報告した。丸い形に「ブーバ」、尖った形に「キキ」を対応づけやすい効果は、音と形の関連を調べる別の実験的な例であり、Blasiらの語彙調査そのものではない。

Fort・Schwartz(2022)は、物体を叩いた音や計算モデル・知覚実験を用い、スペクトルのバランスと時間的な連続性からこの対応を説明する仮説を検討した。物体の形や接触条件に由来する環境の音と形の規則性が手がかりになるという提案である。この結果だけで、言語の起源や、すべての音象徴の仕組みが確定したわけではない。

技術への接続:テキストから意味ラベルを予測するモデルが、音声の響きまで明示的に利用しているとは限らない。音象徴を利用できるか調べるには、入力表現・学習データ・評価課題を決めて検証する必要がある。

問い:新しい製品名を機械に考えさせるとき、音の印象をどう扱うか。


参加方法と配慮事項

神経多様性・多様な参加スタイルへの配慮

この回はCBT・動画形式のため、自分のペースで進めてください。

  • 動画は一時停止・巻き戻し・字幕表示を活用してください
  • CBTテストは何度でも挑戦できます(最高点を記録)
  • Python実習は動作させることより「読んで理解すること」でも可
  • 不明点はメールで質問してください
  • 実習中に発生したエラーもそのままメールで送ってOKです

学習の進め方

[1] スライド講義を読む(または動画を視聴)
        ↓
[2] Python実習(Google Colab)を実行する
        ↓
[3] CBTテスト(5問)に挑戦する
        ↓
[4] 小課題を提出する(任意・加点)

スライド講義


スライド1:音声処理パイプラインの全体像

音声対話システムの代表的な構成の一つが、複数のモジュールをつなぐパイプラインです。以下は、意図・スロットを扱うタスク指向対話の基本例です。すべてのシステムがこの分割を採用するわけではありません。

音声入力
    ↓
[ASR] 音声認識(Automatic Speech Recognition)
    ↓ テキスト
[NLU] 自然言語理解(Natural Language Understanding)
    ↓ 意図・スロット・エンティティ
[DM]  対話管理(Dialogue Management)
    ↓ 行動指示・応答内容
[NLG] 自然言語生成(Natural Language Generation)
    ↓ テキスト応答
[TTS] 音声合成(Text-to-Speech)
    ↓
音声出力

各モジュールの役割

モジュール 入力 出力 例
ASR 音声波形 テキスト 「東京から大阪まで新幹線を予約して」
NLU テキスト 意図・スロット 意図:鉄道予約, 出発地:東京, 目的地:大阪, 交通手段:新幹線
DM 意図・スロット・履歴 行動計画 日時が未確定 → 日時を質問
NLG 行動計画 テキスト 「出発日時をお教えください」
TTS テキスト 音声 合成音声で読み上げ

パイプラインのエラー伝播

前段の誤りが後段の判断に影響することをエラー伝播と呼びます。必ずそのまま伝わるわけではなく、文脈・候補の照合・聞き返しなどで回復できる場合もあります。

ASR誤認識の例:「大阪」→「大津」
         ↓
NLU:目的地:大津(利用者が意図した場所と異なる)
         ↓
DM:エラー処理 or 誤った応答

このため、各モジュールの精度と、エラーを検出・回復する仕組みの両方が重要です。

End-to-End(E2E)アプローチ

音声入力から応答音声までを一体として学習するE2Eアプローチもあります。ここでの「E2E」は対話全体を指します。WhisperがASRとしてE2Eでも、その前後を別のLLM・TTSにつなげば、対話システム全体は複数モデルのパイプラインです。

音声入力 → [音声入出力を扱うモデル(例:2024年発表のGPT-4o)] → 音声出力

後のスライドで詳述します。


スライド2:NLU — 自然言語理解とは

NLUは、自然言語から意味に関する情報を取り出す技術群です。この授業では、テキストから意図・スロット・固有表現などを推定する処理に注目します。これらはNLUの代表例であり、課題はこの3つに限られません。

タスク1:意図推定(Intent Classification)

ユーザーが「何をしたいか」を分類するタスク。

例:スマートホームアシスタント

入力:「リビングの電気を消して」
出力:Intent = TurnOff_Light, Room = リビング

入力:「今日の天気は?」
出力:Intent = GetWeather, Date = 今日

入力:「アラームを明日の7時にセット」
出力:Intent = SetAlarm, Time = 07:00, Date = 明日

上の例のRoom・Date・Timeはスロットの出力であり、意図ラベル(Intent)とは分けて扱います。

意図推定のアプローチ

アプローチ 手法 特徴
ルールベース 正規表現・キーワードマッチ 高速・解釈可能、カバレッジに限界
機械学習 SVM, ロジスティック回帰 単語特徴や事前学習済み埋め込みなどを入力
深層学習 BERT, RoBERTa等 文脈を反映した表現で分類。性能は学習・評価条件による
LLMベース 対話型LLM等 指示や例をプロンプトに与えて分類。出力の検証が必要

タスク2:スロット抽出(Slot Filling)

意図を実現するために必要な情報を発話から抽出するタスク。

例:飛行機予約システム

入力:「来週の金曜日に東京から福岡まで飛行機を予約したい」

スロット抽出結果:
{
  "departure_city": "東京",
  "destination_city": "福岡",
  "travel_date": "来週の金曜日",
  "transport_mode": "飛行機"
}

未充填スロット:
{
  "passenger_count": None,  → 質問が必要
  "seat_class": None        → 質問が必要(またはデフォルト値)
}

スロット充填の難しさ - 「明後日」「来週末」等の相対的な時間表現の解決(時間正規化) - 省略・共参照:「そこから京都にも行きたい」(「そこ」がどこを指すかは、それまでの対話から解決する) - スロット間の依存関係(往復便なら帰りの日付も必要)

タスク3:固有表現認識(Named Entity Recognition / NER)

テキスト中の対象表現の範囲を見つけ、「人名・地名・組織名」などの種類を付けるタスク。日時・数値を含める設定もあり、対象が文法上の固有名詞だけとは限りません。種類と境界の規則はデータセットごとに定義します。

例:

入力:「山田太郎さんが2024年3月に東京大学で発表した論文を探して」

NER結果:
[山田太郎]PER が [2024年3月]DATE に [東京大学]ORG で発表した論文を探して

主要なエンティティタイプ

タイプ 説明 例
PER(Person) 人名 山田太郎、松本さん
LOC(Location) 地名 東京、北海道、渋谷駅
ORG(Organization) 組織名 トヨタ、文部科学省
DATE/TIME 日時表現 3月15日、来週月曜
NUM 数値・金額 3枚、5,000円

NERとスロット抽出の違い:「東京から大阪へ」では、NERなら両方を地名として検出できます。予約のスロット抽出では、さらに「東京=出発地」「大阪=目的地」という役割を割り当てます。上表の種類は一例で、CoNLL-2003では人名・場所・組織・その他の4種類を扱います。


スライド3:SLU — 音声言語理解との違い

SLU(Spoken Language Understanding)は、発話音声から意図・スロットなどの意味情報を取り出すことです。ASRとテキストNLUを組み合わせる方式に加え、音声から直接意味ラベルを推定する方式もあります(Lugoschほか2019)。

テキストNLUとSLUの構成

観点 テキストを扱うNLU カスケード型SLU 直接型SLU
処理の流れ テキスト → 意味情報 音声 → ASRの文字列 → NLU 音声 → 意味情報
中間の書き起こし 入力として用いる 用いる 推論時に必須とはしない
主な課題 曖昧さ、省略、文脈、表記の乱れなど 左の課題に加え、ASR誤りなど 音響条件の変動、音声と意味ラベルの学習など
音声の手がかり 文字列だけでは韻律を直接利用できない 韻律等を別途入力する構成も可能 モデルの入力から利用しうる

NLUの入力は書き言葉に限らず、書き起こしやチャットも含みます。句読点・フィラーの有無は、NLUとSLUを分ける定義ではありません。

話し言葉特有の課題

フィラーと不流暢性

書き言葉の例:「明日の会議室を予約したい」

話し言葉の例:「えーと、あしたの、あのー、かいぎしつ、をーっと、よやくしたいんですけど」

ASRが句読点を補う場合や、フィラーを省略して出力する場合もあります。フィラーの除去や正規化は一つの前処理ですが、SLUが必ずこの処理を行うわけではありません。言い直しや韻律が意図の手がかりになる場合もあります。

ASR誤認識への耐性

正しい発話:「新宿から渋谷まで」
ASR出力の例:「新宿から新橋まで」(目的地の誤認識)

ASRが提供するN-bestリスト・信頼度や対話履歴を活用する構成があります。すべてのSLUがこれらを入力するわけではなく、最良の文字列1つを使う構成もあります。

音声からの直接的な情報

音声からは、文字列だけでは表しきれない情報を利用できる場合があります。

  • 韻律情報(Prosody):ピッチ・強調・発話速度など。発話の区切りや話し方の手がかり
  • 話者情報:話者の区別など。ただしSLUの必須機能ではない
  • パラ言語情報:笑い声・ため息など

これらと感情・意図の対応は文脈や話者などに依存します。音声から得た分類ラベルを、その人の内面の確定的な説明とみなさないようにします。


スライド4:対話管理(Dialogue Management)

対話管理(DM)は、会話の「流れ」をコントロールするモジュールです。ユーザーの意図を達成するためにどんな行動をとるかを決定します。

DMの役割

入力:NLUの出力(意図・スロット)+対話履歴
出力:次の行動(質問する・検索する・予約する・応答する等)

ルールベースDM

if intent == "鉄道予約":
    if departure_city is None:
        action = "出発地を質問する"
    elif destination_city is None:
        action = "目的地を質問する"
    elif travel_date is None:
        action = "日時を質問する"
    else:
        action = "予約APIを呼び出す"

利点:動作が予測可能、デバッグ容易、ドメイン知識が明示的に組み込める
欠点:ルールの数が増えると管理困難、想定外の入力に弱い

機械学習DM

ベイズ的対話状態追跡(Bayesian DST)

スロット値の候補に対する不確実性を分布として管理する概念例:
{
  "departure_city": {"東京": 0.9, "京都": 0.1},
  "travel_date": {"明日": 0.7, "明後日": 0.3}
}

強化学習DM

  • タスク成功・対話の長さなどから設計した報酬を用いて、方策を学習する
  • 対話全体の累積報酬を改善することを目指す。最適解が常に得られるとは限らない
  • 対話ログ・ユーザーシミュレータ・実利用者との対話などを利用する。必要なデータ量は方式による

DSTは「状態の推定」、方策は「次の行動の選択」であり、同じものではない。参考:Youngほか2013。

対話状態トラッキング(Dialogue State Tracking / DST)

ターン1:
ユーザー:「東京から大阪に行きたい」
State:{departure: 東京, destination: 大阪, date: ?, count: ?}

ターン2:
ユーザー:「来週の土曜」
State:{departure: 東京, destination: 大阪, date: 来週土曜, count: ?}

ターン3:
ユーザー:「2人で」
State:{departure: 東京, destination: 大阪, date: 来週土曜, count: 2}
→ この例で追跡するスロットが充填 → 空席・料金照会や候補提示へ

スライド5:大規模言語モデル(LLM)との接続

2022年以降の対話型LLMの普及により、音声対話でLLMを利用する構成が広がりました。LLMそのものが2022年に初めて登場したわけではありません。

Whisper + LLM + TTSによるカスケード型の対話

従来のパイプライン型

音声 → ASR(Whisper) → テキスト → NLU → DM → NLG → TTS → 音声

テキストLLM統合型

音声 → ASR(Whisper) → テキスト → テキストLLM → テキスト → TTS → 音声
                                     ↑
                            NLU・DM・NLGの機能の一部または複数を担当

LLMが担う機能 - 意図推定(NLU):指示や少数の入出力例をプロンプトに与えて分類する - 状態・行動の推定(DM):対話履歴や現在の状態から、次の行動を提案する - 応答生成(NLG):与えた情報から応答文を生成する

ここでのfew-shotプロンプトは、例を入力に含める文脈内学習を指し、通常はモデルの重みを更新しません。履歴を渡すだけで状態が必ず正確に保持されるわけではありません。明示的な状態管理や外部APIと組み合わせることもあります。

音声を直接扱うモデルの例:GPT-4o(2024年発表)

OpenAIは2024年のGPT-4o発表で、テキスト・画像・音声を同じモデルで扱い、音声入力から音声応答を生成する構成を説明しました。

カスケード型:音声 → ASR → テキストLLM → TTS → 音声
直接音声を扱う構成:音声 → 音声入出力を学習したモデル → 音声
  • 書き起こし文字列だけでは渡しにくい韻律・笑い声などの手がかりを扱える
  • ただし、情報が一切失われないことや、感情・意味を常に正しく推定できることを保証するものではない
  • 発表時の資料は平均応答時間320ミリ秒を報告した。これは当時の公表値であり、あらゆる回線・設定でのサービスの待ち時間を保証する値ではない

参考:OpenAI:Hello GPT-4o(2024)。この節は構成を理解するための歴史的な例であり、現在提供されるモデル・機能の一覧ではありません。

Google Geminiの音声機能

音声ファイルから書き起こし・翻訳・要約などを生成する機能と、リアルタイム対話用のLive APIを区別します。音声を入力できるモデルが、同じAPIで音声を出力できるとは限りません。対応形式・入力長・出力・提供状況は、使うモデルとAPIで確認します(音声理解の公式説明)。

構成を比較するときの観点

観点 NLU・DM・NLGを分ける構成 テキストLLMを統合する構成
処理の分担 各モジュールの入出力・役割を定める LLMが意図推定・状態推定・生成などを兼ねられる
調整・検証 モジュールごとに検証できる。接続時の誤りも確認 プロンプト・モデル・外部処理の組合せを検証する
文脈 明示的な状態や履歴を渡す 履歴や状態を入力する。保持の正確さは要評価
コスト・速度 モデル、実装、端末・サーバー、通信による モデル、入出力長、実装、端末・サーバー、通信による
処理場所 端末内・クラウド・混在の構成がある 小型LLMの端末内実行を含め、複数の構成がある

LLMを使うことと、音声から音声までE2Eで処理することは別の設計軸です。構成名だけで、精度・価格・遅延の優劣を決めることはできません。


スライド6:自然言語生成(NLG)と音声合成(TTS)

NLG(Natural Language Generation)

対話管理の出力(行動・情報)を自然な文に変換するモジュールです。

テンプレートベース

templates = {
    "weather_response": "今日の{city}の天気は{weather}です。最高気温は{temp}度です。",
}
# 例:「今日の東京の天気は晴れです。最高気温は25度です。」

ニューラルNLG

  • Seq2Seq モデル・Transformer による自然な応答生成
  • LLM(GPT等)による高品質な応答生成

NLGの評価指標

指標 説明
BLEU 参照文との修正n-gram適合率をまとめ、短すぎる出力へのペナルティを加える
METEOR 単語の一致・語幹・同義語などで対応づけ、適合率・再現率と語順のペナルティから計算
BERTScore 文脈を反映したトークン埋め込みを対応づけ、類似度を集約する
人手評価 自然さ・流暢さ・情報の正確さを、決めた基準に沿って人が評価

参考:BLEU、METEOR、BERTScore。語の分割・言語資源・参照文の選び方でも値は変わります。高い自動スコアだけで、応答の事実性や対話としての適切さは保証されません。

TTS(Text-to-Speech)の方式

以下は、単純な「第1〜第4世代」ではなく、構成や学習方式の違いです。

方式・構成要素 例・役割
フォルマント合成 共鳴特性などを制御して音声を合成
波形接続合成 録音した音声単位を選び、つなぎ合わせる
統計的パラメトリック合成 HMMなどで音響パラメータを推定し、ボコーダで波形を生成。波形接続合成とは別の方式
ニューラルな音響モデル・ボコーダ Tacotron 2はテキストからメルスペクトログラムを予測し、WaveNetボコーダで波形を生成する構成の例
統合型ニューラルTTS VITSは変分推論・フロー・敵対的学習などを組み合わせる。標準のVITSが未知話者を数秒の音声だけで再現するとは限らない
音声コーデックのトークンを使う方式 VALL-Eは参照音声を条件とした音声合成の研究例。SoundStormは音響トークンの高速な生成を扱うモデルで、単体がそのまま完全なテキスト読み上げシステムとは限らない

声質の再現や表現の制御には、対応するモデル・学習データ・入力条件が必要です。参考:統計的パラメトリック合成、Tacotron 2、VITS、VALL-E、SoundStorm。

日本語TTSの例 - VOICEVOX(日本語向け音声合成ソフト) - Google Cloud Text-to-Speech(日本語音声を含むクラウドサービス)


スライド7:評価指標

音声対話システム全体の評価には、複数の指標を組み合わせることが重要です。

モジュールごとの評価指標

指標 対象モジュール 計算方法
WER(Word Error Rate) ASR (S+D+I) / N × 100%。Nは参照の単語数(N > 0)
Intent Accuracy NLU 正解intent数 / 全テスト数
Slot F1 NLU スロットの適合率・再現率の調和平均。境界・種類・値など、一致の単位を定める
BLEU / ROUGE NLG BLEUは修正n-gram適合率など。ROUGE-Nはn-gram、ROUGE-Lは最長共通部分列に基づく
MOS(Mean Opinion Score) TTS 聴取者による評点の平均(例:5段階)。自動評価ではない

対話レベルの評価指標

指標 説明
Task Completion Rate(TCR) 成功したタスク試行数÷全試行数。1会話1試行なら、タスクを完了した会話の割合
Average Turns per Dialogue 会話あたりの平均ターン数。数え方と成功・失敗の扱いを定める。少なさだけで良否を判断しない
Dialogue Success Rate 事前に定めた対話の成功条件を満たした割合。満足度とは別に定義・測定する
Joint Goal Accuracy(JGA) 評価対象の全スロット・値が、そのターンの正解状態と完全一致したターンの割合。F1ではない

参考:ROUGE、MultiWOZ公式評価資料、ITU-T P.800:主観評価法。指標名だけでなく、単位・集計方法・データをそろえて比較します。

人手評価(Human Evaluation)

自動指標では捉えられない側面の評価: - 自然さ(Naturalness):応答は人間らしいか - 一貫性(Coherence):対話の流れは矛盾していないか - 情報正確性(Factuality):生成した情報は正しいか - 安全性(Safety):有害・差別的な応答はないか


Python実習(Google Colab)

以下の2つの実習を順番に実行してください。


実習1:SpeechRecognition + 簡易意図分類

まず、テキスト例から意図と情報を取り出す処理を確認します。その後、Colabにアップロードした音声を認識してつなげられます。

セットアップセル(初回)

%pip install -q SpeechRecognition

SpeechRecognitionは複数の認識エンジンを呼び出すライブラリです。以下のrecognize_googleは音声を外部サービスに送る処理で、Google Cloud Speech-to-Text用のrecognize_google_cloudとは別です。接続やサービスの利用制限により失敗する場合があります(公式README)。

import json
import re
import unicodedata
import speech_recognition as sr

# Google音声認識サービスへ音声を送信する例。
# Google Cloud Speech-to-Text用の recognize_google_cloud とは別の関数。
def recognize_from_file(audio_file_path: str) -> str | None:
    """PCM WAV・AIFF・FLACなど、AudioFileが対応するファイルを認識する。"""
    recognizer = sr.Recognizer()
    recognizer.operation_timeout = 20
    try:
        with sr.AudioFile(audio_file_path) as source:
            audio = recognizer.record(source)
        return recognizer.recognize_google(audio, language="ja-JP")
    except sr.UnknownValueError:
        print("音声を認識できませんでした")
    except sr.RequestError as error:
        print(f"認識サービスへの接続エラー: {error}")
    except (OSError, ValueError) as error:
        print(f"ファイルを読み込めません: {error}")
    return None

# この関数はローカルPC用。Colabの通常のクラウドランタイムでは使わない。
# ローカルPCではPyAudio・PortAudioと利用可能なマイクが必要。
def recognize_from_microphone() -> str | None:
    recognizer = sr.Recognizer()
    recognizer.operation_timeout = 20
    try:
        with sr.Microphone() as source:
            print("周囲の音を測ります。1秒ほど発話を控えてください")
            recognizer.adjust_for_ambient_noise(source, duration=1)
            print("話してください...")
            audio = recognizer.listen(source, timeout=5, phrase_time_limit=10)
        return recognizer.recognize_google(audio, language="ja-JP")
    except sr.WaitTimeoutError:
        print("待ち時間内に発話が始まりませんでした")
    except sr.UnknownValueError:
        print("音声を認識できませんでした")
    except sr.RequestError as error:
        print(f"認識サービスへの接続エラー: {error}")
    except OSError as error:
        print(f"マイクを利用できません: {error}")
    return None

# 特定の用途のキーワードを先に調べ、「教えて」等の一般的な検索語は後で使う。
INTENT_PATTERNS = {
    "天気確認": ["天気", "気温", "雨", "晴れ", "曇り"],
    "時刻確認": ["今何時", "現在の時刻", "今の時間"],
    "アラーム設定": ["アラーム", "目覚まし", "起こして"],
    "ミュージック再生": ["音楽", "曲", "再生", "聴かせて", "歌"],
    "電気操作": ["電気", "ライト", "照明"],
}
SEARCH_KEYWORDS = ["検索", "調べて", "教えて", "どこ", "いつ", "誰"]
LOCATION_KEYWORDS = ["東京", "大阪", "札幌", "福岡", "名古屋", "京都", "横浜", "神戸"]
TIME_KEYWORDS = ["明後日", "明日", "今日", "来週", "今週", "午前", "午後", "朝", "夜"]

def keyword_mentions(text: str, keywords: list[str]) -> list[str]:
    # 文中での出現順に抽出し、重複を除く。出発地・目的地などの役割は未推定。
    pattern = "|".join(re.escape(word) for word in sorted(keywords, key=len, reverse=True))
    return list(dict.fromkeys(match.group() for match in re.finditer(pattern, text)))

def classify_intent(text: str) -> dict:
    normalized = unicodedata.normalize("NFKC", text).strip()
    result = {
        "text": text, "intent": "不明", "rule_score": 0,
        "candidates": [], "slots": {}
    }
    if not normalized:
        return result

    # 一致数はルールのスコアであり、正解確率や校正済みの信頼度ではない。
    scores = {
        intent: sum(keyword in normalized for keyword in keywords)
        for intent, keywords in INTENT_PATTERNS.items()
    }
    best_score = max(scores.values(), default=0)
    if best_score > 0:
        candidates = [intent for intent, score in scores.items() if score == best_score]
        result["candidates"] = candidates
        result["rule_score"] = best_score
        if len(candidates) == 1:
            result["intent"] = candidates[0]
        # 同点では辞書の登録順で断定せず、「不明」と候補一覧を返す。
    elif any(keyword in normalized for keyword in SEARCH_KEYWORDS):
        result.update(intent="検索", rule_score=1, candidates=["検索"])

    locations = keyword_mentions(normalized, LOCATION_KEYWORDS)
    if locations:
        result["slots"]["location_mentions"] = locations

    time_pattern = "|".join(map(re.escape, TIME_KEYWORDS)) + r"|\d{1,2}時(?:\d{1,2}分)?"
    times = list(dict.fromkeys(match.group() for match in re.finditer(time_pattern, normalized)))
    if times:
        result["slots"]["time_mentions"] = times
    return result

# テキストだけでも、ASR以降の処理を確認できる。
test_utterances = [
    "明日の東京の天気を教えて",
    "朝7時にアラームをセットして",
    "リビングの電気を消して",
    "お気に入りの音楽を再生して",
    "今何時ですか",
    "量子コンピュータについて調べて",
    "電気と音楽を操作したい",  # 同点の例
]
for utterance in test_utterances:
    print(json.dumps(classify_intent(utterance), ensure_ascii=False, indent=2))

# 対話的に試すときだけTrueに変更する。
RUN_INTERACTIVE = False
if RUN_INTERACTIVE:
    while True:
        user_input = input("入力(quitで終了): ")
        if user_input.lower() in ["quit", "exit", "終了"]:
            break
        print(json.dumps(classify_intent(user_input), ensure_ascii=False, indent=2))

Colabで音声ファイルを使う場合

以下は上のセルの後に実行します。短いPCM WAVなど、sr.AudioFileが対応する形式を選んでください。MP3やM4Aをそのまま渡す例ではありません。

from google.colab import files

uploaded = files.upload()
audio_path = next(iter(uploaded), None)
if audio_path is not None:
    recognized = recognize_from_file(audio_path)
    print(classify_intent(recognized or ""))

実行のポイント

  • 通常のColabはクラウド上の仮想マシンでPythonを実行します。sr.Microphone()はブラウザのマイクに直接つながりません。Colabでは録音済みファイルを使い、ローカルPCではPyAudio等を準備してマイク関数を使います(Colab FAQ、SpeechRecognitionの必要環境)。
  • rule_scoreはキーワード一致のスコアであり、正解確率ではありません。キーワードの追加や同点への対応を変えて結果を比べてください。
  • 抽出するのは場所や時刻の表現の候補です。「東京から大阪へ」の出発地・目的地の区別や、「明日」の日付への変換はまだ行いません。
  • 否定・言い直し・複数の意図は、この簡易なルールでは十分に扱えません。例えば「音楽を再生しないで」で何が起こるかを確認し、改善案を考えてください。

実習2:Whisper + テキストのセンチメント分析

Whisperの文字起こしを、ポジティブ・ネガティブ・ニュートラルに分類するモデルへ渡します。ここで分類するのは発話内容のセンチメントです。韻律や声質を入力する感情認識ではなく、話者の実際の感情を確定するものでもありません。

セットアップセル(Colab・初回)

%pip install -q openai-whisper transformers torch gTTS matplotlib
!apt-get -qq update
!apt-get -qq install -y ffmpeg

モデルの初回読み込みにはダウンロードが必要です。テスト音声を作るgTTSはテキストをオンラインの音声合成サービスへ送り、MP3形式で保存します(Whisper公式、gTTS公式)。

import os
import shutil
import torch
import whisper
from gtts import gTTS, gTTSError
from transformers import pipeline
import matplotlib.pyplot as plt

if shutil.which("ffmpeg") is None:
    raise RuntimeError("ffmpegが必要です。上のセットアップセルを実行してください。")

device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"使用デバイス: {device}")
# まずtinyで確認する。base・smallなどに変えて精度と速度を比較できる。
whisper_model = whisper.load_model("tiny", device=device)
sentiment_analyzer = pipeline(
    "sentiment-analysis",
    model="lxyuan/distilbert-base-multilingual-cased-sentiments-student",
    device=0 if device == "cuda" else -1,
)

# gTTSはテキストをGoogle Translateの音声合成サービスへ送信する。
# 出力はMP3。拡張子を.wavに変えてもWAVへ変換されるわけではない。
def create_test_audio_from_text(text: str, filename: str = "test_audio.mp3") -> str | None:
    try:
        gTTS(text=text, lang="ja", timeout=20).save(filename)
        return filename
    except gTTSError as error:
        print(f"テスト音声を生成できません: {error}")
        return None

LABEL_JA = {"positive": "ポジティブ", "negative": "ネガティブ", "neutral": "ニュートラル"}
MAX_SENTIMENT_TOKENS = 512

def speech_to_sentiment(audio_file: str) -> dict:
    """音声を文字起こしし、そのテキストのセンチメントを分類する。"""
    if not os.path.isfile(audio_file):
        raise FileNotFoundError(audio_file)

    transcription = whisper_model.transcribe(
        audio_file,
        language="ja",          # 指定言語。Noneに変えると自動言語検出。
        fp16=(device == "cuda"),
        verbose=False,
    )
    transcript = transcription["text"].strip()
    result = {
        "transcript": transcript,
        "language": transcription["language"],  # この例では指定したja
        "sentiment": None,
        "sentiment_score": None,
        "sentiment_ja": "分析不可",
        "truncated": False,
    }
    if not transcript:
        return result

    # 512「文字」ではなく、特殊トークンを含む512「トークン」までを分類。
    token_ids = sentiment_analyzer.tokenizer(
        transcript, add_special_tokens=True, truncation=False
    )["input_ids"]
    result["truncated"] = len(token_ids) > MAX_SENTIMENT_TOKENS
    sentiment = sentiment_analyzer(
        transcript, truncation=True, max_length=MAX_SENTIMENT_TOKENS
    )[0]
    label = sentiment["label"].lower()
    result.update(
        sentiment=label,
        sentiment_score=float(sentiment["score"]),
        sentiment_ja=LABEL_JA.get(label, label),
    )
    return result

# TTS → ASR → テキスト分類の接続を確認するデモ。
# 声の韻律による感情認識や、実際の利用者音声の精度評価ではない。
test_texts = [
    "今日はとても良い天気で、散歩をしていたらとても気持ちよかったです",
    "電車が遅れてしまって、会議に間に合わなかったので困りました",
    "明日の会議は10時からです。資料を準備してください",
]
test_results = []
for i, text in enumerate(test_texts):
    audio_path = create_test_audio_from_text(text, f"test_audio_{i}.mp3")
    if audio_path is None:
        continue
    result = speech_to_sentiment(audio_path)
    test_results.append({"original_text": text, **result})
    print(f"元テキスト: {text}")
    print(f"認識テキスト: {result['transcript']}")
    print(f"認識に用いた言語: {result['language']}")
    print(f"センチメント: {result['sentiment_ja']} / モデルスコア: {result['sentiment_score']}")
    if result["truncated"]:
        print("長いため先頭の最大512トークンのみを分類しました")

# 特殊な日本語フォントの事前インストールを要求しないよう、図は英語で表示。
plottable = [(i, result) for i, result in enumerate(test_results) if result["sentiment_score"] is not None]
if plottable:
    labels = [f"Utterance {i + 1}" for i, _ in plottable]
    scores = [result["sentiment_score"] for _, result in plottable]
    sentiments = [result["sentiment"] for _, result in plottable]
    palette = {"positive": "steelblue", "negative": "tomato", "neutral": "gray"}
    fig, ax = plt.subplots(figsize=(8, 4))
    bars = ax.bar(labels, scores, color=[palette.get(label, "gray") for label in sentiments])
    ax.set_ylim(0, 1.12)
    ax.set_ylabel("Predicted-class score")
    ax.set_title("Text sentiment after ASR")
    for bar, label in zip(bars, sentiments):
        ax.text(bar.get_x() + bar.get_width() / 2, bar.get_height() + 0.02, label, ha="center")
    plt.tight_layout()
    plt.savefig("sentiment_results.png", dpi=150)
    plt.show()

実習のポイント

  • language="ja"は日本語を指定しています。この場合、返り値のlanguageを「自動検出した言語」とは呼びません。Noneに変えると言語検出を使えます。
  • tinyより大きいモデルに変える場合は、速度・メモリと認識結果を同じ音声で比べてください。
  • 使用モデルは日本語を含む多言語の3クラス分類モデルです。公開モデルカードにも日本語例があります。「英語中心だから日本語精度が低い」とは決めつけず、対象の日本語発話で評価します(モデルカード)。
  • transcript[:512]は文字数での切り出しです。上のコードではトークナイザを通した長さを調べ、truncation=True, max_length=512を使います。長文は先頭部分のみの分類となるため、全文の結果とは区別します(TransformersのPipeline API)。
  • 出力のモデルスコアは、実際の正解率と校正されているとは限りません。分類の正確さは、人が付けたラベルなどと比較して測定します。
  • 合成した音声は処理の接続を確認するデモです。元テキストを直接分類した結果とASR出力を分類した結果を比べると、認識誤りの影響を調べられます。実利用での性能は、対象の話者・録音条件の別データで評価します。

CBTテスト(5問)

以下の問題に答えてください。何度でも挑戦できます。解説を読んで理解を深めてください。


問題1

音声対話パイプラインにおいて、「自然言語理解(NLU)」の主な役割として最も正確に説明しているものはどれか。

a) 音声波形をテキストに変換する
b) 対話の文脈を追跡して次のシステム行動を決定する
c) テキストの意味を解析し、意図やスロットを抽出する
d) テキストを自然な音声に変換する

解答と解説 **正解:c)** **解説**: a) は ASR(音声認識)の役割です。 b) は DM(対話管理)の役割です。 c) が NLU の正しい説明です。NLU はテキストを意味的に解析し、ユーザーの「意図(Intent)」と、その意図を実現するための具体的な情報「スロット(Slot)」を抽出します。例:「明日の東京の天気を教えて」→ 意図:天気確認、スロット:日付=明日、場所=東京。 d) は TTS(音声合成)の役割です。

問題2

SLU(Spoken Language Understanding)が NLU と異なる主な理由はどれか。

a) SLU は画像も同時に処理できる
b) SLU は発話音声から意味情報を得る課題であり、ASR+NLUを使う方式と、音声から直接推定する方式があるから
c) SLU は文書検索システム専用だから
d) SLU は感情分析のみを行うモジュールだから

解答と解説 **正解:b)** **解説**: SLUは、発話音声から意図・スロットなどを取り出します。カスケード型ではASR出力をNLUへ渡し、直接型では書き起こしを推論時の必須の中間表現としません。NLUは話し言葉の書き起こしも扱います。また、句読点やフィラーの有無はASRの出力設定などに依存し、NLUとSLUを区別する定義ではありません。

問題3

エッジ推論における「知識蒸留(Knowledge Distillation)」の説明として正しいものはどれか。

a) モデルのパラメータを低精度(int8等)に変換することでメモリを削減する技術
b) 重要度の低い重み・チャネルなどをゼロ化または削除してモデルを軽量化する技術
c) 大規模な教師モデルの出力確率分布を使って、小規模な生徒モデルを訓練する技術
d) 複数のモデルを組み合わせて精度を向上させるアンサンブル学習の一種

解答と解説 **正解:c)** **解説**: a) は量子化(Quantization)の説明です。 b) はプルーニング(Pruning)の説明です。 c) は知識蒸留の代表的な方法です。教師モデルの出力確率分布(Soft Labels)から候補間の相対関係などを学び、生徒モデルの性能改善を目指します。性能向上が必ず得られるわけではありません。教師が生成した文字列を学習に使う方法などもあります。
d) はアンサンブル学習の説明です。

問題4

2024年に発表されたGPT-4oの音声を直接扱う構成について、最も適切な説明はどれか。

a) 書き起こし文字列だけを中継する構成と異なり、音声から音声へ処理し、韻律などの手がかりも利用できる
b) 音声認識精度が100%になった
c) インターネット接続なしで動作するオンデバイスシステムである
d) 日本語のみに特化した音声処理システムである

解答と解説 **正解:a)** **解説**: a) が正しい。GPT-4oの発表では、音声を直接入力・出力として学習する構成が示されました。文字列だけでは渡しにくい音声の手がかりを利用できますが、情報を損失なく保持することや、感情・意味を必ず正しく推定することは保証しません。
b) 任意の音声で認識精度が100%になるという保証はありません。
c) GPT-4oはクラウドベースのシステムです。 d) GPT-4oは多言語対応のシステムです。

問題5

音声対話システムの評価指標に関する次の記述のうち、誤っているものはどれか。

a) WERは、同じ評価データ・単語分割・正規化で比べると、低いほど参照語列に対する編集誤りが少ないことを示す
b) 1会話1タスク試行の評価では、TCRはタスクを完了できた会話の割合を示す
c) BLEU スコアは NLG(自然言語生成)の評価に使われ、生成文と参照文のn-gramの一致を測る
d) WER が 0% であれば、対話システム全体の性能も必ず最高になる

解答と解説 **正解:d)(誤りの記述)** **解説**: d) が誤りです。WERが0%でも、それは指定した評価対象・前処理で参照語列と一致したということです。NLU・DM・NLG・TTSに問題があれば、タスクを達成できないことがあります。例えば、文字起こしが一致していてもNLUが誤った意図を推定すれば、応答を誤りえます。**認識の正確さと、対話全体で目的を達成できるかは別に評価します**。
a)〜c) はすべて正しい記述です。

補足資料・参考文献

学術参考文献

ウェブリソース

  • Rasa Open Source(対話システムのオープンソースフレームワーク): https://rasa.com/docs/
  • Hugging Face Transformers(NLPモデルのライブラリ): https://huggingface.co/docs/transformers/
  • OpenAI Whisper: https://github.com/openai/whisper

動画リソース

  • Stanford CS224N: Natural Language Processing with Deep Learning(YouTube)
  • Andrej Karpathy: "Let's build GPT"(YouTube)

次回予告

第12回(オンライン同期):音声処理と認知科学・言語学:発達・神経多様性研究の最新動向

  • 言語発達と音声知覚(乳児の音韻発達・臨界期仮説)
  • 神経多様性と音声処理(ディスレクシア・APD・ASD)
  • 機械と人間の音声認識の比較
  • ディスカッション:「音声インタフェースはバリアかツールか?」

事前学習(任意・推奨):
以下のいずれかを事前に読んでおくと、次回のディスカッションがより深まります。やれる範囲で取り組んでください。

  1. 国立障害者リハビリテーションセンター ICT機器活用マニュアル 第4巻 発達障害児者編 の「拡大・代替コミュニケーション」(冊子7ページ)
  2. 日本LD学会 LD等の用語解説
  3. WHO Disability(障害について、英語)

どれか1つ読んで、気づいたことをメモしておいてください(提出は不要)。