はじめに
第1回 では「Whisper系のSTTがどう動いているか」を学びました。Encoderで音響特徴量に変換し、Decoderがトークンを順に出力する、というあの話です。
今回はいよいよ手を動かします。
ゴールは 手元のWAVファイルを日本語テキストに変換するPythonスクリプトを書くこと です。エンジンは faster-whisper、モデルは日本語特化の kotoba-whisper を使います。第1回で「日本語に強い」と紹介したあのモデルです。
対象読者
- Pythonの基本文法(仮想環境、
pipのインストール、関数定義)が分かる方 - 自分のPCで音声をテキスト化してみたい方
- クラウドAPIに音声を送るのが業務上難しい方
- 第1回を読み、仕組みは何となく分かったので動かしてみたい方
この記事で分かること
- faster-whisper の最小コードで音声を文字起こしする方法
- kotoba-whisper にモデルを差し替える具体的な手順
- 推論パラメータ(
beam_size、vad_filter、compute_typeなど)の使い分け - 複数ファイルを一括処理するCLIスクリプトの作り方
- GPUまわりでハマったときの典型的な対処法
コードはすべて Windows 11 + NVIDIA GeForce RTX 3070 8GB + Python 3.11 で動作確認しています。CPUでもそのまま動きますが、その場合は device="cpu" に変更してください(速度は1/10程度まで落ちます)。
完成イメージ
最終的には、こんなコマンド一発でWAVファイルを文字起こしできるようになります。
python transcribe.py sample.wav --model kotoba-tech/kotoba-whisper-v2.0-faster --output result.txt
実行すると、こんな出力が得られます。
[00:00.00 --> 00:03.20] 本日は晴天なり、本日は晴天なり。
[00:03.20 --> 00:07.40] マイクのテストをしています。
result.txt には文字起こし結果が、result.srt には字幕ファイル形式の結果が保存されます。
前提条件
環境
| 項目 | バージョン |
|---|---|
| OS | Windows 11(macOS / Linux でも基本同じ) |
| GPU | NVIDIA GeForce RTX 3070 8GB(CUDA Compute Capability 8.6) |
| Python | 3.11.x |
| CUDA Toolkit | 12.x |
| cuDNN | 9.x(faster-whisper 1.1 以降は cuDNN 9 系を要求) |
| faster-whisper | 1.1.0 |
| ffmpeg | 7.x(音声デコードに使用) |
faster-whisper はバージョンによって要求する cuDNN のバージョンが変わります。本記事執筆時点では faster-whisper 1.1 系 = cuDNN 9 です。手元のバージョンと合わない場合は 公式 README の Requirements を必ず確認してください。
事前知識
- Python の仮想環境(
venv)の作り方 - pip でのライブラリインストール
- コマンドプロンプト or PowerShell の基本操作
第1回で説明した「Encoder / Decoder」「VAD」「トークン」あたりの用語はそのまま使います。忘れた方は第1回をざっと読み返すと理解が早いです。
なぜ faster-whisper と kotoba-whisper なのか
選定理由を第1回からの繋がりで簡単に整理します。
faster-whisper:Whisperの高速実行ランタイム
OpenAI公式の whisper パッケージは PyTorch を直接叩く実装で、推論が比較的遅めです。faster-whisper は同じモデルを CTranslate2 という推論エンジンで動かし直したもので、ざっくり以下のメリットがあります。
- 公式実装より 2〜4倍速い(同じハードウェアで)
- メモリ使用量が少ない(同じモデルなら半分以下になることも)
-
int8量子化に対応しており、CPUでもある程度実用速度が出る
「同じモデルを軽く・速く動かすラッパー」と理解すれば十分です。
kotoba-whisper:日本語特化のWhisper派生
第1回で触れた通り、OpenAIの公式モデル(large-v3 など)は多言語データで学習されており、日本語ではどうしても精度・速度に妥協があります。kotoba-whisper-v2.0 は distil-whisper-large-v3 をベースに、約 7,000 時間の日本語音声で追加学習されたモデルです。
特徴は次の3点です。
- 日本語 ASR ベンチマークで
large-v3級の精度を出す - 蒸留モデルなのでパラメータ数が少なく、6倍以上高速
- 推論時のVRAM使用量も小さく、8GBクラスのGPUで快適に動く
8GBしかない RTX 3070 でも、ストレスなく動かせるのは大きいです。
kotoba-whisper は kotoba-tech が HuggingFace で公開しています。faster-whisper(CTranslate2形式)用に変換済みの kotoba-tech/kotoba-whisper-v2.0-faster が配布されているので、本記事ではこれを使います。
プロジェクトのセットアップ
順番に進めます。
1. 作業ディレクトリと仮想環境
mkdir local-stt
cd local-stt
python -m venv .venv
.\.venv\Scripts\Activate.ps1
macOS / Linux の場合は source .venv/bin/activate に読み替えてください。
2. faster-whisper をインストール
pip install -U pip
pip install faster-whisper==1.1.0
faster-whisper は内部で ctranslate2、huggingface_hub、tokenizers などを引っ張ってきます。pip のログを眺めていると依存関係が見えて面白いです。
3. ffmpeg を準備する
faster-whisper は音声ファイルのデコードに ffmpeg を呼びます。ffmpeg にPATHが通っていないと 「音声を読み込めない」エラーで一瞬で詰まる ので、最初に必ず確認しましょう。
ffmpeg -version
入っていない場合は winget で入れるのが楽です。
winget install Gyan.FFmpeg
インストール後にシェルを開き直し、もう一度 ffmpeg -version でPATHが通っているか確認します。
4. CUDA / cuDNN を確認する
PyTorch のように pip で完結はしません。CUDA Toolkit と cuDNN はOS側にインストールされている必要があります。
nvidia-smi
nvcc --version
nvidia-smi が動かない場合はドライバから入れ直し、nvcc --version が見つからない場合はCUDA Toolkitを入れ直してください。cuDNN のDLL(例: cudnn_ops64_9.dll)が見つからないとロード時に落ちます。
「Could not load library cudnn_ops_infer64_9.dll でクラッシュする」というエラーは cuDNN 9 がインストールされていない、またはPATHが通っていない ことが原因の典型例です。後述のトラブルシューティングで詳しく扱います。
5. サンプル音声を用意する
何でも構いませんが、手元になければ自分の声を10〜30秒くらい録音した sample.wav を1つ用意してください。Windows標準の「サウンドレコーダー」アプリで十分です。
実装
Step 1: 最小コードで動かす
まずは標準的な Whisper モデル(small)で「動くこと」を確認します。
from faster_whisper import WhisperModel
model = WhisperModel("small", device="cuda", compute_type="float16")
segments, info = model.transcribe(
"sample.wav",
language="ja",
beam_size=5,
)
print(f"検出言語: {info.language} (確率 {info.language_probability:.2f})")
for seg in segments:
print(f"[{seg.start:6.2f}s --> {seg.end:6.2f}s] {seg.text}")
実行します。
python hello_stt.py
初回は HuggingFace Hub からモデルがダウンロードされる(数百MB〜数GB)ので少し待ちます。2回目以降はキャッシュ(~/.cache/huggingface/hub)から読み込まれるので一瞬です。
ポイント:
-
model.transcribe()の返り値segmentsは ジェネレータです。forで回したりlist()で展開した瞬間に推論が走ります。print文の手前で待たされるのが嫌ならlist(segments)で先に消化してください。 -
language="ja"を明示すると言語検出をスキップでき、わずかに速くなります。第1回で説明した「最初の30秒で言語を推定する」処理が省かれます。
Step 2: モデルを kotoba-whisper に切り替える
ここからが本命です。モデル名を差し替えるだけで切り替わります。
from faster_whisper import WhisperModel
- model = WhisperModel("small", device="cuda", compute_type="float16")
+ model = WhisperModel(
+ "kotoba-tech/kotoba-whisper-v2.0-faster",
+ device="cuda",
+ compute_type="float16",
+ )
segments, info = model.transcribe(
"sample.wav",
language="ja",
beam_size=5,
)
faster-whisper は CTranslate2 形式のモデルを HuggingFace Hub から直接読み込めます。small、medium、large-v3 のような Whisper 公式のショートカット名以外に、<organization>/<model_name> の形式で任意のリポジトリを指定できる、ということです。
同じ sample.wav に対して両方を実行し、文字起こし結果を比較してみてください。固有名詞の表記や句読点の入り方で、日本語に特化した違いが体感できるはずです。
初回はモデルのダウンロードに5〜10分かかります(v2.0-faster は1GB弱)。HF_HUB_DOWNLOAD_TIMEOUT=300 を環境変数に設定しておくと、遅い回線でもタイムアウトしにくくなります。
Step 3: パラメータを理解する
transcribe() には大量の引数がありますが、最初に押さえるべきは次の5つです。
| パラメータ | 意味 | 推奨値 |
|---|---|---|
language |
入力言語の指定。明示すると言語検出をスキップ。 | "ja" |
beam_size |
ビームサーチの幅。大きいほど精度↑・速度↓。 | 5 |
vad_filter |
Silero VADで無音区間を除外してから推論する |
True 推奨 |
compute_type |
推論の数値精度 |
float16 (GPU) / int8(CPU) |
condition_on_previous_text |
前のチャンクのテキストを次の推論にプロンプトとして渡すか | デフォルト True。ハルシネーション抑制目的で False にすることも |
これらを盛り込んだ実用版がこちらです。
from faster_whisper import WhisperModel
def load_model(model_name: str = "kotoba-tech/kotoba-whisper-v2.0-faster") -> WhisperModel:
return WhisperModel(
model_name,
device="cuda",
compute_type="float16",
)
def transcribe(model: WhisperModel, audio_path: str) -> list[dict]:
segments, info = model.transcribe(
audio_path,
language="ja",
beam_size=5,
vad_filter=True,
vad_parameters={"min_silence_duration_ms": 500},
condition_on_previous_text=False,
)
results = []
for seg in segments:
results.append(
{
"start": seg.start,
"end": seg.end,
"text": seg.text.strip(),
}
)
return results
ポイント:
-
vad_filter=Trueは「無音区間で文字起こしを暴走させない」ための保険です。Whisper系は無音入力に対して幻覚(実在しないテキスト)を吐く癖があり、これを抑えられます。VADの詳細は第3回で詳しく扱います。 -
condition_on_previous_text=Falseにすると、1セグメントごとに独立して推論されます。短い音声では精度が上がる一方、長い会話では文脈が切れることもあるので、用途次第で切り替えてください。
Step 4: 複数ファイルをまとめて処理するCLI
「フォルダにあるWAVを全部文字起こしして、結果をテキストと字幕ファイルで出す」までを1コマンドで完結させます。
import argparse
import json
import sys
from datetime import timedelta
from pathlib import Path
from transcribe_core import load_model, transcribe
def to_srt_timestamp(seconds: float) -> str:
td = timedelta(seconds=seconds)
total_seconds = int(td.total_seconds())
millis = int((td.total_seconds() - total_seconds) * 1000)
h, rem = divmod(total_seconds, 3600)
m, s = divmod(rem, 60)
return f"{h:02d}:{m:02d}:{s:02d},{millis:03d}"
def save_outputs(segments: list[dict], audio_path: Path, out_dir: Path) -> None:
base = out_dir / audio_path.stem
with open(base.with_suffix(".txt"), "w", encoding="utf-8") as f:
for seg in segments:
f.write(seg["text"] + "\n")
with open(base.with_suffix(".srt"), "w", encoding="utf-8") as f:
for i, seg in enumerate(segments, start=1):
f.write(f"{i}\n")
f.write(f"{to_srt_timestamp(seg['start'])} --> {to_srt_timestamp(seg['end'])}\n")
f.write(seg["text"] + "\n\n")
with open(base.with_suffix(".json"), "w", encoding="utf-8") as f:
json.dump(segments, f, ensure_ascii=False, indent=2)
def main() -> int:
parser = argparse.ArgumentParser(description="ローカル日本語STT (faster-whisper + kotoba-whisper)")
parser.add_argument("inputs", nargs="+", help="WAVファイル or それを含むディレクトリ")
parser.add_argument("--model", default="kotoba-tech/kotoba-whisper-v2.0-faster")
parser.add_argument("--out", default="./out", help="結果の出力ディレクトリ")
args = parser.parse_args()
out_dir = Path(args.out)
out_dir.mkdir(parents=True, exist_ok=True)
targets: list[Path] = []
for raw in args.inputs:
p = Path(raw)
if p.is_dir():
targets.extend(sorted(p.glob("*.wav")))
elif p.is_file():
targets.append(p)
else:
print(f"[skip] 見つかりません: {p}", file=sys.stderr)
if not targets:
print("対象ファイルがありません。", file=sys.stderr)
return 1
print(f"モデルをロード中: {args.model}")
model = load_model(args.model)
for audio_path in targets:
print(f"\n--- {audio_path.name} ---")
segments = transcribe(model, str(audio_path))
save_outputs(segments, audio_path, out_dir)
print(f" {len(segments)} セグメント書き出し: {out_dir / audio_path.stem}.*")
return 0
if __name__ == "__main__":
sys.exit(main())
使い方はこんな感じです。
python transcribe.py .\samples\ --out .\out\
samples\ の中にある *.wav がすべて文字起こしされ、out\ 配下に .txt / .srt / .json の3形式で保存されます。会議の議事録、講演の文字起こし、動画への字幕付けあたりにそのまま使えます。
動作確認と速度の目安
RTX 3070 8GB で、30秒のクリーンな日本語スピーチを処理した実測値(参考値)です。
| モデル | compute_type |
推論時間(30秒音声に対して) | VRAM使用量 |
|---|---|---|---|
Whisper small
|
float16 |
約 2 秒 | 約 1.0 GB |
Whisper large-v3
|
float16 |
約 8 秒 | 約 4.7 GB |
| kotoba-whisper v2.0 | float16 |
約 1.5 秒 | 約 1.6 GB |
large-v3 並みの精度を small 並みの速度で出せる、というのが kotoba-whisper の魅力です。8GBのVRAMにも余裕で収まります。
精度の数値(CER / WER)が気になる方は、第4回で評価データを使って測定する予定です。本記事ではまず「自分の音声で違いを体感する」ことを優先しています。
よくある問題と対処法
エラー1: Could not load library cudnn_ops_infer64_9.dll
原因: cuDNN 9 系がインストールされていない、または DLL の場所が PATH に含まれていない。
対処:
- NVIDIA Developer から cuDNN 9.x をダウンロード
- ZIPを展開し、
bin\配下のDLLを CUDA Toolkit のインストール先(例:C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.x\bin)にコピー - シェルを再起動
ライセンス的な手間を避けたい場合は、Pythonパッケージとして公開されている nvidia-cudnn-cu12 を入れる方法もあります。
pip install nvidia-cudnn-cu12==9.*
エラー2: CUDA out of memory
原因: VRAM不足。large-v3 をそのまま読むと 5GB 近く使うため、他のGPUプロセス(ブラウザ、Stable Diffusion 系)と競合します。
対処:
- model = WhisperModel("large-v3", device="cuda", compute_type="float16")
+ model = WhisperModel("large-v3", device="cuda", compute_type="int8_float16")
int8_float16 にすると、メモリ使用量が概ね半分になります。精度はわずかに落ちますが、実用上は気にならないレベルです。
エラー3: ffmpeg was not found
原因: ffmpegにPATHが通っていない。
対処: 前述の winget install Gyan.FFmpeg 実行後、シェルを開き直す のを忘れがちです。PATHは新規シェルで初めて反映されます。
エラー4: 文字起こし結果が「ご視聴ありがとうございました」だけ
原因: Whisper系モデルの典型的なハルシネーション。無音や音楽だけの区間で、学習データに頻出したフレーズを吐く現象です。
対処:
-
vad_filter=Trueを必ず有効にする - それでも止まらない場合は
condition_on_previous_text=Falseを試す - 根本的な対策は第4回で扱います
まとめ
本記事では以下を実装しました。
- faster-whisper の最小構成で日本語音声を文字起こし
- モデルを kotoba-whisper に差し替え、日本語精度と速度を両立
- 推論パラメータの意味と推奨値の整理
- 複数ファイルを一括処理するCLIスクリプト
- GPUまわりのハマりどころと対処法
ここまでで「手元のオフライン環境で、日本語をテキスト化するバッチ処理基盤」が完成しました。クラウドAPIに音声を送らずに済むため、プライバシー要件のある現場でも使えます。
次回予告
第3回では、いま作ったバッチ処理を マイク入力からのリアルタイム文字起こし に進化させます。鍵になるのは第1回で軽く触れた VAD(Voice Activity Detection) とストリーミング処理です。「話している間にどんどん文字が出てくる」体験を作っていきます。
参考資料