メインコンテンツへスキップ
友田 陽大
生成AI・LLM・RAG
Python
音声認識
OpenAI API
アーキテクチャ設計
パフォーマンス
可観測性

OpenAI Whisper 本番運用ガイド:セルフホスト(large-v3-turbo)と Audio API(gpt-4o-transcribe)を使い分ける文字起こし設計

OpenAI Whisper を本番品質で使うための実装ガイド。公式ドキュメントに忠実なモデル一覧(large-v3 / turbo)と Audio API(whisper-1 / gpt-4o-transcribe / gpt-4o-mini-transcribe)を整理し、セルフホスト vs API の選定フレームワーク、25MB制限の回避、SRT字幕生成、固有名詞のprompt誘導、幻覚(hallucination)対策、冪等・再開・可観測性まで、実コードで解説します。

公開日
読了時間
16分
著者
友田 陽大
シェア

「音声を文字起こししたい」——要件としては一行です。けれど本番に載せようとした瞬間、判断すべきことが一気に増えます。セルフホストするのか API を叩くのか。どのモデルを選ぶのか。25MB を超える長尺音声はどうするのか。固有名詞の誤変換をどう抑えるのか。無音区間で湧く「幻覚(hallucination)」をどう殺すのか。

この記事は、OpenAI Whisper を本番品質で運用するための実装ガイドです。題材として、私が国内大手放送事業者向けに構築した社内AIプラットフォーム(テロップ誤字検出パイプラインで「発話の文字起こし=耳」として音声認識を使用)での設計判断も交えます。

この記事のルール:モデル仕様・API仕様・価格は OpenAI公式ドキュメント(2026年6月時点) に基づきます。価格やモデル名は改定されるため、本番投入前に必ず公式の料金ページで最新値を確認してください。コードは実運用で使える形に整えていますが、シークレットは環境変数前提です(ハードコード厳禁)。


0. 最初の分岐:「Whisper」という言葉には2つの実体がある

ここを混同したまま設計を始めると、後で必ず詰まります。Whisper には性質の異なる2つの提供形態があります。

① セルフホスト(OSS)② OpenAI Audio API
実体openai-whisper(PyPI)/ Hugging Face の重みapi.openai.com のホスト型エンドポイント
代表モデルlarge-v3 / turbo(large-v3-turbo)whisper-1 / gpt-4o-transcribe / gpt-4o-mini-transcribe
実行場所自前の GPU / CPU(音声が外に出ない)OpenAI のサーバー(音声を送信する)
課金計算リソース(GPU時間)分課金 or トークン課金
ファイル上限なし(メモリ次第)25MB / 1リクエスト
主な強みプライバシー・固定費・無制限長運用ゼロ・高精度・即日導入

「Whisper を使う」と言ったとき、この①と②のどちらを指しているのかを最初に確定させてください。本記事は両方を扱い、選定基準(第3章)まで示します。


1. セルフホスト版:最新モデルと正しい使い方

1.1 モデル一覧(公式の数値)

公式 README のモデル表は次のとおりです。Relative speedlarge を 1x とした相対値です。

SizeParameters必要VRAM(目安)相対速度English-only (.en)
tiny39M~1GB~10xあり
base74M~1GB~7xあり
small244M~2GB~4xあり
medium769M~5GB~2xあり
large1550M~10GB1xなし(多言語のみ)
turbo809M~6GB~8xなし

turbo(= large-v3-turbo)が現行のスイートスポットです。large-v3 のデコーダ層を 32層 → 4層 に削った蒸留・剪定モデルで、精度の劣化を最小限に抑えつつ約8倍速を実現しています。日本語を含む多言語の「文字起こし」用途なら、まず turbo を試すのが定石です。

公式が明記する turbo の落とし穴:turbo は翻訳データを除いて学習されているため、--task translate を指定しても元言語のまま返ることがあります。「日本語音声 → 英語テキスト」の翻訳が欲しいときは mediumlarge を使ってください。英語のみのアプリでは .en モデルの方が精度が出る、とも公式は述べています。

1.2 インストール(ffmpeg必須)

# Python パッケージ
pip install -U openai-whisper

# 音声デコードに ffmpeg が必須(OSごとに導入)
# macOS:        brew install ffmpeg
# Ubuntu/Debian: sudo apt update && sudo apt install ffmpeg

1.3 CLI:まず動かす

# turbo で文字起こし(複数ファイルもまとめて渡せる)
whisper audio.mp3 --model turbo --language Japanese --output_format srt

# 日本語音声を「英語に翻訳」したい場合は medium/large + translate
whisper meeting.wav --model large --language Japanese --task translate

--output_formatsrt / vtt / txt / json を指定でき、字幕ファイルがそのまま出力されます。字幕生成が目的なら、自前でタイムスタンプを組み立てる前に CLI の出力形式で足りないかを先に確認してください(YAGNI)。

1.4 Python API:本番で使う形

最小例は3行ですが、本番では言語固定・単語タイムスタンプ・幻覚抑制を入れます。

import whisper

# モデルは一度だけロードして使い回す(プロセス内で再利用 = コールドスタート回避)
model = whisper.load_model("turbo")

result = model.transcribe(
    "audio.mp3",
    language="ja",            # 言語を固定 → 言語自動検出のブレと初動コストを排除
    word_timestamps=True,     # 単語単位の時刻(字幕・検索・ハイライトに必須)
    # --- 幻覚(hallucination)抑制の三点セット(詳細は第6章) ---
    condition_on_previous_text=False,  # 直前文への引きずられを断つ
    no_speech_threshold=0.6,           # 無音判定を厳しめに
    temperature=0,                     # 決定的に(再現性とテスト容易性)
)

print(result["text"])
for seg in result["segments"]:
    print(f"[{seg['start']:6.2f}{seg['end']:6.2f}] {seg['text'].strip()}")

word_timestamps=True を付けると、各 segmentwordsword / start / end / probability)が入ります。probability の単語=モデルが自信のない箇所なので、校正UIで黄色くハイライトする、といった「人間の確認ゲート」を設計に組み込めます。

設計のコツmodel はリクエストごとにロードしないこと。large 系は読み込みだけで数秒〜十数秒かかります。Web サーバーならプロセス起動時に一度ロードし、ワーカーで共有します(SRP:ロードと推論を分離)。


2. OpenAI Audio API 版:運用ゼロで高精度を取りに行く

GPU を持ちたくない・即日で精度の高い文字起こしが欲しい——その場合はホスト型 API が最短です。

2.1 モデルとエンドポイント(公式の対応表)

モデル用途対応 response_formatストリーミング
gpt-4o-transcribe最高精度の文字起こしjson / text✅ (stream=True)
gpt-4o-mini-transcribe低コスト・高速json / text
gpt-4o-transcribe-diarize話者分離つきdiarized_json ほか
whisper-1旧来の万能型・字幕形式が豊富json / text / srt / verbose_json / vtt

gpt-4o-transcribe 系は GPT-4o を基盤に WER(単語誤り率)を改善した新世代で、認識精度と言語判定が向上しています。一方で SRT/VTT などの字幕形式や verbose_json の細粒度タイムスタンプが必要なら whisper-1 を選びます。「精度重視=gpt-4o系、字幕の機械可読フォーマット重視=whisper-1」と覚えると迷いません。

  • Transcriptions(文字起こし):音声 →「元言語の」テキスト。
  • Translations(翻訳):音声 → 英語テキスト。whisper-1 専用です。

2.2 基本:Python と TypeScript

from openai import OpenAI

client = OpenAI()  # APIキーは環境変数 OPENAI_API_KEY から(ハードコード禁止)

with open("speech.mp3", "rb") as audio_file:
    transcription = client.audio.transcriptions.create(
        model="gpt-4o-transcribe",
        file=audio_file,
        language="ja",  # 言語を渡すと精度とレイテンシが安定する
    )

print(transcription.text)

Next.js / Node(このサイトと同じ TypeScript)なら:

import OpenAI from "openai";
import { createReadStream } from "node:fs";

const openai = new OpenAI(); // process.env.OPENAI_API_KEY を自動参照

const transcription = await openai.audio.transcriptions.create({
  file: createReadStream("audio.mp3"),
  model: "gpt-4o-transcribe",
  language: "ja",
});

console.log(transcription.text);

2.3 字幕(SRT)と単語タイムスタンプ:whisper-1

機械可読な字幕や単語単位の時刻が要るときは whisper-1 + verbose_json を使います。

with open("lecture.mp3", "rb") as audio_file:
    result = client.audio.transcriptions.create(
        model="whisper-1",
        file=audio_file,
        language="ja",
        response_format="verbose_json",
        timestamp_granularities=["segment", "word"],  # verbose_json 必須
    )

# セグメント単位(字幕のかたまり)
for seg in result.segments:
    print(f"[{seg.start:.2f}-{seg.end:.2f}] {seg.text}")

# 単語単位(検索インデックス・ハイライトに)
for w in result.words:
    print(w.word, w.start, w.end)

SRT ファイルそのものが欲しいだけなら response_format="srt" を指定すれば、変換コードを書かずに字幕テキストが返ります(DRY:自前のSRTシリアライザを書かない)。

2.4 固有名詞・専門用語を正しく綴らせる:prompt

文字起こしで最も多いクレームは固有名詞・社名・専門用語の誤変換です。prompt パラメータに「期待する綴り」を渡すと、モデルがそれに寄せてくれます。

GLOSSARY = "登場する固有名詞: 友田陽大, ハコキット, Next.js, Supabase, RLS, gpt-4o-transcribe"

with open("podcast.mp3", "rb") as audio_file:
    transcription = client.audio.transcriptions.create(
        model="gpt-4o-transcribe",
        file=audio_file,
        language="ja",
        prompt=GLOSSARY,  # ドメイン語彙を事前に教える
    )

これは安価で効果が大きいチューニング手段です。番組制作の文字起こしでは「人名・番組名・商品名」の表記ゆれが事故に直結するため、案件ごとに用語集を prompt に流し込む運用が効きます。

2.5 ストリーミング:話しながら出す(gpt-4o系)

会議の議事録やライブ字幕のように「待たせたくない」UXでは、stream=True で部分結果を逐次受け取れます。

stream = client.audio.transcriptions.create(
    model="gpt-4o-transcribe",
    file=open("speech.mp3", "rb"),
    response_format="text",
    stream=True,  # gpt-4o-transcribe / mini で利用可
)

for event in stream:
    # delta: 増分テキスト / done: 確定
    if event.type == "transcript.text.delta":
        print(event.delta, end="", flush=True)
    elif event.type == "transcript.text.done":
        print("\n--- 確定 ---")

マイク入力をリアルタイムに文字化する用途では、ファイルストリーミングではなく Realtime API(WebSocket / WebRTC)を使うのが本筋です。「録音済みファイルの逐次返却」と「ライブの逐次認識」は別物なので、要件を取り違えないでください。


3. 選定フレームワーク:セルフホスト vs API をどう決めるか

「どっちが正解」ではなく、4つの軸で要件に合う方を選びます。

判断軸セルフホスト(turbo/large)が有利Audio API が有利
プライバシー医療・行政・社外秘で音声を外に出せない外部送信が許容される
コスト構造大量・常時稼働で固定費にしたい(GPU償却)少量・スパイク的で変動費が合理的
長さ数時間の長尺を1本で回したい(上限なし)25MB に収まる/分割前提でよい
運用体制GPU・モデル更新・スケールを運用できる運用したくない/即日で精度が欲しい

コストの直感

公式の分課金(2026年6月時点・要確認)はおおむね次の水準です。

  • whisper-1 / gpt-4o-transcribe$0.006/分(≒ $0.36/時)
  • gpt-4o-mini-transcribe$0.003/分(≒ $0.18/時)

月に数十時間程度ならAPIが圧倒的に安い(GPUを1枚借りるより安く、運用ゼロ)。逆に、毎日数百時間を常時処理するようなワークロードでは、turbo をGPUインスタンスで回した方が単価が下がる損益分岐点が現れます。「まずAPIで価値検証 → 量が読めてからセルフホスト移行を検討」が、多くの案件で正しい順序です(YAGNI / コスト効率)。


4. 25MB の壁を越える:長尺音声のチャンク分割

API のファイル上限は 25MB。1時間の会議録音は普通に超えます。無音区間(VAD)で切るのが定石で、文の途中でぶつ切りにしないため精度劣化を抑えられます。

"""長尺音声を無音で分割し、各チャンクを文字起こしして連結する。
固定長で切ると単語が割れるため、無音境界で切るのが要点。"""
from openai import OpenAI
from pydub import AudioSegment, silence

client = OpenAI()

def split_on_silence_bounded(path: str, max_ms: int = 15 * 60_000) -> list[AudioSegment]:
    """無音を境界に、各チャンクを max_ms 以下に収める(25MB制限の安全側)。"""
    audio = AudioSegment.from_file(path)
    silences = silence.detect_silence(audio, min_silence_len=700, silence_thresh=-40)
    cut_points = [s[0] for s in silences] + [len(audio)]

    chunks: list[AudioSegment] = []
    start = 0
    for point in cut_points:
        if point - start >= max_ms:
            chunks.append(audio[start:point])
            start = point
    if start < len(audio):
        chunks.append(audio[start:])
    return chunks

def transcribe_long(path: str, language: str = "ja") -> str:
    parts: list[str] = []
    context = ""  # 直前チャンク末尾を次の prompt に渡し、境界の文脈を維持
    for i, chunk in enumerate(split_on_silence_bounded(path)):
        buf = chunk.export(format="mp3", bitrate="64k")  # 軽量化で25MBに余裕
        res = client.audio.transcriptions.create(
            model="gpt-4o-transcribe",
            file=("chunk.mp3", buf, "audio/mpeg"),
            language=language,
            prompt=context[-200:],  # 境界をまたぐ固有名詞・文脈のヒント
        )
        parts.append(res.text)
        context = res.text
    return "\n".join(parts)

ポイントは3つです。

  1. 無音で切る:固定長分割は単語を割って精度を落とす。VAD境界で切る。
  2. ビットレートを落とす:文字起こしに高音質は不要。64kbps mp3 で十分小さくなり、25MB の心配が消える(コスト効率)。
  3. チャンク間の文脈を渡す:前チャンク末尾を次の prompt に入れ、境界をまたぐ固有名詞・話題の連続性を保つ。

5. 本番運用設計:冪等・リトライ・可観測性

文字起こしは「長時間・外部API・課金あり」のジョブです。素朴に呼ぶと、途中失敗で全部やり直し&二重課金になります。放送局向けプラットフォームで実際に効いた設計を、最小の形で示します。

5.1 チャンク単位の冪等キャッシュ

各チャンクを内容ハッシュをキーにキャッシュすれば、再実行で完了済みチャンクをスキップでき、リトライが冪等になります(=部分失敗からの再開=二重課金の回避)。

import hashlib
from pathlib import Path

def chunk_key(data: bytes, model: str, language: str) -> str:
    """音声内容+パラメータで決まる決定的キー。同入力 → 同キー → キャッシュヒット。"""
    h = hashlib.sha256()
    h.update(data)
    h.update(f"{model}:{language}".encode())
    return h.hexdigest()

def transcribe_chunk_idempotent(data: bytes, model: str, language: str, cache_dir: Path) -> str:
    key = chunk_key(data, model, language)
    cached = cache_dir / f"{key}.txt"
    if cached.exists():
        return cached.read_text(encoding="utf-8")  # 再実行はAPIを叩かない(冪等・節約)

    res = client.audio.transcriptions.create(
        model=model, file=("c.mp3", data, "audio/mpeg"), language=language,
    )
    cached.write_text(res.text, encoding="utf-8")
    return res.text

5.2 指数バックオフ付きリトライ

外部APIはレート制限・一時障害で必ず失敗します。冪等な操作にだけリトライを掛けます。

import time
from openai import APIConnectionError, RateLimitError, APIStatusError

def with_retry(fn, *, max_attempts: int = 4, base: float = 1.0):
    """指数バックオフ。リトライ対象を限定し、4xx(=入力不正)は即時失敗させる。"""
    for attempt in range(1, max_attempts + 1):
        try:
            return fn()
        except (RateLimitError, APIConnectionError) as e:
            if attempt == max_attempts:
                raise
            time.sleep(base * (2 ** (attempt - 1)))  # 1s, 2s, 4s, ...
        except APIStatusError as e:
            if 400 <= e.status_code < 500:
                raise  # 入力不正はリトライしても無駄 → 即失敗(fail fast)
            if attempt == max_attempts:
                raise
            time.sleep(base * (2 ** (attempt - 1)))

5.3 可観測性:何を必ず記録するか

文字起こしジョブでは、本文(PII)ではなくメタデータを記録します(プライバシーと可観測性の両立)。

  • ジョブID / チャンク番号 / 入力ハッシュ / モデル / 言語
  • 処理時間(音声長に対するリアルタイム比 = RTF)
  • 課金量(分 or トークン)と推定コスト
  • 失敗種別(レート制限 / タイムアウト / 入力不正)とリトライ回数

これらを構造化ログ(OpenTelemetry 等)で出すと、「どのチャンクで詰まったか」「コストが妥当か」が一目で追えます。本文ログにPII(個人名・連絡先)を残さないのは、内部統制案件の絶対条件です。


6. 幻覚(Hallucination)と無音問題:精度の最後の1割

Whisper の既知の弱点は、無音・雑音・BGMだけの区間で「ありもしない文」を生成することです。「ご視聴ありがとうございました」のような定型句が湧くのは典型例です。本番では次の多層対策で殺します。

  1. 前処理でVAD(音声区間検出):発話のない区間を推論前に落とす。silero-vad などで無音を除けば、幻覚の温床自体が消える。
  2. condition_on_previous_text=False:直前テキストへの引きずられ(一度湧いた幻覚の連鎖)を断つ。
  3. しきい値で捨てるno_speech_threshold を上げ、logprob_threshold で低確信セグメントを破棄。
  4. 独立した第二の根拠で照合:最強の対策はクロスチェックです。放送局のテロップ誤字検出では、「画面のテロップ(OCR=目)」と「発話の文字起こし(ASR=耳)」という独立した2系統を突き合わせ、食い違いを検出しました。単一情報源の確信度ではなく、2つの情報源の不一致を手がかりにすると、幻覚も誤変換も浮かび上がります。

1情報源の「自信」は当てになりません。**独立した2つの経路の「食い違い」**こそが信頼できる検出シグナルになる——これは音声認識に限らず、AIを本番に載せるときの一般原則です。


7. セキュリティとプライバシー:どこで線を引くか

  • 音声は個人情報になり得る:声・氏名・連絡先・病歴が含まれ得ます。社外秘・要配慮個人情報を含む音声はAPIに送れないことが多い。その場合はセルフホスト(turbo/large)一択です。
  • APIキーはサーバー側に:ブラウザから直接 OpenAI を叩かない(キー漏洩)。Next.js なら Route Handler / Server Action 経由で、キーは環境変数に。
  • 入力の検証:ファイル形式(mp3/mp4/mpeg/mpga/m4a/wav/webm)・サイズ(25MB)・長さを境界でバリデーションしてから API に渡す。ユーザー由来の入力を素通しにしない。
  • 保存方針の明示:文字起こし結果を保存するなら、保持期間・暗号化・削除フローを最初に決める。PIIは AES-256-GCM 等で暗号化し、検索は復号せずトークン化(HMAC)で部分一致させる、といった設計が内部統制では標準です。

8. まとめ:選定チートシート

最後に、迷ったときの早見表です。

  • とりあえず精度高く・即日でgpt-4o-transcribe(API)。言語を渡し、固有名詞は prompt で誘導。
  • コストを最優先gpt-4o-mini-transcribe(API、$0.003/分)。
  • SRT/VTT字幕・単語タイムスタンプが要るwhisper-1 + verbose_json
  • 音声を外に出せない/長尺を大量に回す:セルフホスト turbo(多言語)/ large(翻訳)。
  • ライブ字幕:Realtime API。録音ファイルの逐次返却なら stream=True
  • 本番化の共通装備:無音分割・冪等キャッシュ・指数バックオフ・PIIを出さない可観測性・幻覚の多層対策。

文字起こしは「一行の要件」に見えて、コスト・精度・プライバシー・信頼性のトレードオフを設計する仕事です。私は放送事業者向けの社内AIプラットフォームで、音声認識を「テロップ誤字検出の第二の目」として本番運用し、冪等・再開・可観測性を担保した長時間ジョブとして組み上げました。

「自社の音声をどう文字起こしし、どう業務に組み込むか」——その設計から実装・運用まで、一気通貫で伴走できます。 要件の整理段階からでも、お気軽にご相談ください。


参考(公式ドキュメント)

友田

友田 陽大

経済産業大臣賞 受賞プロダクト開発者。TypeScript + Python + AWS で、SaaS・業界DX・ 実用レベルの生成AI(RAG)を、要件定義からインフラ・運用まで一人で完遂します。

この記事で解説した技術の適用事例

国内大手放送事業者の番組制作を支援する社内AIプラットフォーム(テロップ誤字をOCR×音声認識で照合)

ケーススタディを見る