ストーリー
田
田中VPoE
Step 2で画像AIを学んだ。次は音声と動画の処理だ。まずは音声認識(STT: Speech-to-Text)から始めよう
あなた
会議の議事録作成や、コールセンターの通話分析に使えそうですね
あ
田
田中VPoE
その通り。Whisperの登場で音声認識の精度は飛躍的に向上した。APIとローカル実行の両方を押さえておけば、コストと精度を最適化できる
音声認識(STT)の概要
主要なSTTモデル・サービス
| モデル/サービス | 提供元 | 特徴 | コスト目安 |
|---|
| Whisper API | OpenAI | 高精度、多言語対応、日本語に強い | $0.006/分 |
| Whisper(OSS) | OpenAI | ローカル実行可能、GPU推奨 | 無料(GPU費用のみ) |
| Google Speech-to-Text | Google | リアルタイム対応、ストリーミング | $0.006/15秒 |
| Amazon Transcribe | AWS | AWS統合、カスタム語彙 | $0.024/分 |
| Azure Speech | Microsoft | リアルタイム対応、カスタムモデル | $1/音声1時間 |
Whisperのアーキテクチャ
音声入力(WAV/MP3/M4A)
│
▼
[メルスペクトログラム変換] ← 音声を周波数×時間の2D表現に
│
▼
[Encoder(Transformer)] ← 音声特徴量を抽出
│
▼
[Decoder(Transformer)] ← テキストを自己回帰的に生成
│
▼
テキスト出力(タイムスタンプ付き)
Whisperモデルのサイズ比較
| モデル | パラメータ数 | 必要VRAM | 処理速度 | 精度 |
|---|
| tiny | 39M | ~1GB | 最速 | 低 |
| base | 74M | ~1GB | 速い | 中低 |
| small | 244M | ~2GB | 中 | 中 |
| medium | 769M | ~5GB | やや遅い | 中高 |
| large-v3 | 1.55B | ~10GB | 遅い | 最高 |
Whisper APIでの音声認識
基本実装
from openai import OpenAI
client = OpenAI()
def transcribe_audio(audio_path: str, language: str = "ja") -> dict:
"""Whisper APIで音声をテキストに変換"""
with open(audio_path, "rb") as audio_file:
transcript = client.audio.transcriptions.create(
model="whisper-1",
file=audio_file,
language=language,
response_format="verbose_json",
timestamp_granularities=["segment"]
)
return {
"text": transcript.text,
"segments": [
{
"start": seg.start,
"end": seg.end,
"text": seg.text
}
for seg in transcript.segments
]
}
# 使用例
result = transcribe_audio("meeting_recording.mp3")
print(result["text"])
for seg in result["segments"]:
print(f"[{seg['start']:.1f}s - {seg['end']:.1f}s] {seg['text']}")
ローカルWhisperの実行
import whisper
import torch
def transcribe_local(
audio_path: str,
model_size: str = "large-v3",
language: str = "ja"
) -> dict:
"""ローカルWhisperで音声認識"""
device = "cuda" if torch.cuda.is_available() else "cpu"
model = whisper.load_model(model_size, device=device)
result = model.transcribe(
audio_path,
language=language,
task="transcribe",
verbose=False,
beam_size=5,
best_of=5,
temperature=0.0
)
return {
"text": result["text"],
"segments": [
{
"start": seg["start"],
"end": seg["end"],
"text": seg["text"]
}
for seg in result["segments"]
],
"language": result["language"]
}
音声の前処理
なぜ前処理が重要か
| 問題 | 影響 | 対処法 |
|---|
| ノイズ混入 | 認識精度の低下 | ノイズリダクション |
| 音量が小さい | 認識漏れ | 音量正規化 |
| サンプルレート不一致 | モデル入力エラー | 16kHzにリサンプリング |
| 長時間音声 | メモリ不足 | チャンク分割 |
| 複数話者 | 話者の区別不可 | 話者分離(ダイアライゼーション) |
前処理パイプライン
from pydub import AudioSegment
def preprocess_audio(
input_path: str,
output_path: str,
target_sample_rate: int = 16000,
normalize_dbfs: float = -20.0
) -> str:
"""音声ファイルの前処理"""
# 読み込み
audio = AudioSegment.from_file(input_path)
# モノラルに変換
audio = audio.set_channels(1)
# サンプルレート変換
audio = audio.set_frame_rate(target_sample_rate)
# 音量正規化
change_in_dbfs = normalize_dbfs - audio.dBFS
audio = audio.apply_gain(change_in_dbfs)
# 無音区間の除去(先頭・末尾)
from pydub.silence import detect_leading_silence
start_trim = detect_leading_silence(audio, silence_threshold=-40)
end_trim = detect_leading_silence(audio.reverse(), silence_threshold=-40)
audio = audio[start_trim:len(audio) - end_trim]
# 出力
audio.export(output_path, format="wav")
return output_path
def split_audio_chunks(
audio_path: str,
chunk_duration_ms: int = 30 * 60 * 1000, # 30分
overlap_ms: int = 5000 # 5秒のオーバーラップ
) -> list[str]:
"""長時間音声をチャンクに分割"""
audio = AudioSegment.from_file(audio_path)
chunks = []
for i in range(0, len(audio), chunk_duration_ms - overlap_ms):
chunk = audio[i:i + chunk_duration_ms]
chunk_path = f"chunk_{i // chunk_duration_ms}.wav"
chunk.export(chunk_path, format="wav")
chunks.append(chunk_path)
return chunks
話者分離(ダイアライゼーション)
概要
会議録音のように複数人が話す音声で、「誰がいつ話したか」を特定する技術です。
音声ストリーム
│
▼
[話者分離モデル] → Speaker A: 0:00-0:15, 0:45-1:00
(pyannote.audio) Speaker B: 0:15-0:45, 1:00-1:20
│
▼
[STT] → 各話者の発言をテキスト化
│
▼
話者ラベル付きテキスト
実装例
from pyannote.audio import Pipeline
def diarize_audio(audio_path: str, num_speakers: int = None) -> list[dict]:
"""話者分離を実行"""
pipeline = Pipeline.from_pretrained(
"pyannote/speaker-diarization-3.1",
use_auth_token="YOUR_HF_TOKEN"
)
if num_speakers:
diarization = pipeline(audio_path, num_speakers=num_speakers)
else:
diarization = pipeline(audio_path)
segments = []
for turn, _, speaker in diarization.itertracks(yield_label=True):
segments.append({
"start": turn.start,
"end": turn.end,
"speaker": speaker
})
return segments
def transcribe_with_speakers(
audio_path: str,
num_speakers: int = None
) -> list[dict]:
"""話者分離 + 音声認識"""
# 1. 話者分離
speaker_segments = diarize_audio(audio_path, num_speakers)
# 2. 音声認識
transcript = transcribe_local(audio_path)
# 3. 話者ラベルとテキストを統合
result = []
for t_seg in transcript["segments"]:
best_speaker = "Unknown"
best_overlap = 0
for s_seg in speaker_segments:
overlap = min(t_seg["end"], s_seg["end"]) - max(t_seg["start"], s_seg["start"])
if overlap > best_overlap:
best_overlap = overlap
best_speaker = s_seg["speaker"]
result.append({
"start": t_seg["start"],
"end": t_seg["end"],
"speaker": best_speaker,
"text": t_seg["text"]
})
return result
まとめ
| 技術 | 概要 | 推奨ツール |
|---|
| STT基本 | 音声をテキストに変換 | Whisper API / ローカルWhisper |
| 前処理 | ノイズ除去、正規化、チャンク分割 | pydub, ffmpeg |
| 話者分離 | 誰がいつ話したかを特定 | pyannote.audio |
| 統合処理 | STT + 話者分離の結果を統合 | カスタムパイプライン |
チェックリスト
次のステップへ
次は音声合成(TTS)を学び、テキストから音声を生成する技術を習得します。
推定読了時間: 30分