0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ローカルで動く日本語STTを作る (2) faster-whisperでハンズオン

0
Posted at

はじめに

第1回 では「Whisper系のSTTがどう動いているか」を学びました。Encoderで音響特徴量に変換し、Decoderがトークンを順に出力する、というあの話です。

今回はいよいよ手を動かします。

ゴールは 手元のWAVファイルを日本語テキストに変換するPythonスクリプトを書くこと です。エンジンは faster-whisper、モデルは日本語特化の kotoba-whisper を使います。第1回で「日本語に強い」と紹介したあのモデルです。

対象読者

  • Pythonの基本文法(仮想環境、pip のインストール、関数定義)が分かる方
  • 自分のPCで音声をテキスト化してみたい方
  • クラウドAPIに音声を送るのが業務上難しい方
  • 第1回を読み、仕組みは何となく分かったので動かしてみたい方

この記事で分かること

  • faster-whisper の最小コードで音声を文字起こしする方法
  • kotoba-whisper にモデルを差し替える具体的な手順
  • 推論パラメータ(beam_sizevad_filtercompute_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.0distil-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 は内部で ctranslate2huggingface_hubtokenizers などを引っ張ってきます。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)で「動くこと」を確認します。

hello_stt.py
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 から直接読み込めますsmallmediumlarge-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 にすることも

これらを盛り込んだ実用版がこちらです。

transcribe_core.py
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コマンドで完結させます。

transcribe.py
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 に含まれていない。

対処:

  1. NVIDIA Developer から cuDNN 9.x をダウンロード
  2. ZIPを展開し、bin\ 配下のDLLを CUDA Toolkit のインストール先(例: C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.x\bin)にコピー
  3. シェルを再起動

ライセンス的な手間を避けたい場合は、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) とストリーミング処理です。「話している間にどんどん文字が出てくる」体験を作っていきます。

参考資料

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?