Whisper の文字起こしをアノテーションする方法
Whisper や WhisperX の出力をそのままアノテーションプロジェクトにする手順。どの出力ファイルを残すか、話者分離が必要になる場面、話者の割り当て方、時間情報を保ったままエクスポートする方法。
Whisper は時間付きのセグメントを返すので、アノテーションツールはそれをそのまま読むべきです。.txt ではなく .json 出力を残し、話者ラベルが必要なら WhisperX を実行して、あとは Potato をそのフォルダに向けるだけです。 変換スクリプトも整形処理も要りません。形式ごとの詳細は文字起こし形式を参照してください。
Whisper は OpenAI のオープンソース音声認識モデルで、大量の音声を扱う人の最初の一手として定着しました。出力されるのは時間付きセグメントの集合で、アノテーションプロジェクトが必要とするものの大部分がそこにあります。足りないのは「文字起こしはある」と「アノテーターがラベルを付けている」の間にあるすべてです。
Note: Potato は文字起こしをしません。音声認識は上流で実行され、Potato はその出力を取り込みます。このガイドは上流のどの選択が効いてくるかを扱いますが、文字起こし自体は Potato が何かを見る前に終わっています。
Whisper のどの出力ファイルを残すべきか
Whisper は複数のファイルを書き出し、その選択は見た目以上に重要です。
whisper interview_01.mp3 --model medium --output_format json --word_timestamps True| ファイル | 中身 | 使えるか |
|---|---|---|
.json | 開始・終了時刻付きのセグメント、必要なら単語単位の時間も | これを使う |
.srt / .vtt | 時間付きセグメント、メタデータなし | 使える |
.tsv | ミリ秒単位の開始・終了とテキスト | 使える |
.txt | テキストのみ、時間情報なし | 使えない。音声と同期させる手がかりがない |
.txt しか残っていなければ、時間の対応関係は失われており、モデルを再実行しない限り復元できません。文字起こしが Potato で一つの塊のテキストとして表示される原因は、ほとんどがこれです。
Whisper は話者ラベルを付けるか
付けません。ここで驚く人が非常に多いところです。Whisper は文字起こしをするだけで、誰が話しているかは教えてくれません。すべてのターンが未割り当てで届きます。内容のアノテーションには支障ありませんが、話者に関わる作業では厄介です。
話者分離は別の工程です。対処法は三つあります。
WhisperX を実行する。 Whisper を pyannote の話者分離で包んだものです。
whisperx interview_01.mp3 --model medium --diarize --output_format json出力にはセグメントごとの speaker フィールド(SPEAKER_00、SPEAKER_01 など)が入り、Potato はそれを直接読みます。
話者分離を有効にしたクラウド API を使う。 diarize=true の Deepgram、speaker_labels の AssemblyAI、AWS Transcribe、Rev.ai はいずれも話者ラベルを出力し、Potato は四つとも直接読めます。
話者分離されていないターンは、話者選択メニュー付きの Unassigned として表示される
アノテーターに任せる。 話者分離されていないターンは、各バブルが選択メニュー付きの Unassigned として表示されます。音質が悪い、発話が重なる、背景音が多いといった状況では、人が聴いて判断するほうが自動の話者分離より正確なことがよくあります。小規模なコーパスなら、これは妥協ではなく妥当な選択です。
データファイルはどう作るか
変換ツールを Whisper の出力フォルダに向けます。
potato transcripts ./whisper_out --media-dir ./audio -o data/interviews.json文字起こしはベース名で音声と対応付けられるので、interview_01.json が interview_01.mp3 を見つけます。項目 ID はファイル名から取られ、Whisper の二重拡張子 interview_01.mp3.json も interview_01.mp3 のような ID にならないよう処理されます。
進める前に、何がどう読まれたか確認してください。
potato transcripts ./whisper_out --dry-runScanned 3 file(s):
interview_01.json Whisper JSON 42 turns 891.4s undiarized
interview_02.json WhisperX JSON 51 turns 1120.8s 2 speaker(s): SPEAKER_00, SPEAKER_01
interview_03.json Whisper JSON 38 turns 754.2s undiarized
3 item(s), 131 turn(s).
plain text やターン数ゼロと報告されるファイルが問題のファイルです。ほぼ確実に、.json 出力に紛れ込んだ .txt です。
変換の工程は省けるか
省けます。データファイルからディスク上の文字起こしを直接指すことができます。
{"id": "int_001", "conversation": {"audio": "media/int_001.mp3",
"transcript": "media/int_001.srt"}}Potato はそのファイルを読み、中身から形式を判定し、表示時に正規化します。文字起こしは差分が取れて再エクスポートもできるファイルのまま残り、データの塊に埋め込まれずに済みます。
設定はどうなるか
生成されるデータファイルはこの形です。
{
"id": "interview_01",
"conversation": {
"audio": "audio/interview_01.mp3",
"turns": [
{"turn_id": "t0", "speaker": "SPEAKER_00", "start": 0.0, "end": 6.5,
"text": "Welcome back."}
]
}
}対応する設定は potato transcripts --emit-config が出力してくれます。
annotation_task_name: "Interview Annotation"
task_dir: .
data_files:
- data/interviews.json
item_properties:
id_key: id
text_key: conversation
instance_display:
fields:
- key: conversation
type: audio_dialogue
label: "Transcript"
span_target: true
display_options:
show_timestamps: true
allow_speaker_assignment: auto
annotation_schemes:
- annotation_type: span
name: topics
description: "Highlight topic mentions"
target_field: conversation
labels:
- name: policy
- name: personal
audio_dialogue 表示。各ターンにターン単位の質問が付く
audio_dialogue 表示はターンを音声と同期した話者バブルとして描き、各ターンにはそのターンだけを再生するボタンが付きます。span_target: true にするとスパンがターンの境界をまたげるようになり、話者を割り当て直してもオフセットは安定したままです。
内容にラベルを付けるより文字起こし自体を点検してほしい場合は、speech_transcript がエラータグと修正欄付きのセグメントカードを提供します。発話の重なりや割り込みを扱うなら voice_interaction の二段タイムラインが使えます。どちらも同じファイルを読みます。
アノテーターはどう話者を割り当てるか
話者リストを定義すると、名前・色・左右の位置が安定します。
display_options:
allow_speaker_assignment: auto
speakers:
- id: interviewer
name: "Interviewer"
color: "#7c3aed"
side: left
- id: participant
name: "Participant"
color: "#059669"
side: rightauto は、割り当てるべき未分離のターンがあるときに選択メニューを有効にします。元データに話者ラベルがある場合でも割り当て直しを許可したいときは true にします。空欄を埋めるのではなく、話者分離の誤りを直したい場合はこちらです。
割り当ては安定したターン ID に紐づいてアノテーションと一緒に保存されるので、再読み込みしても残ります。
アノテーションはどう取り出すか
JSON、JSONL、CSV へのエクスポートはこれまでどおりです。時間の対応関係を残したい場合は、階層アノテーションを ELAN EAF か Praat TextGrid にエクスポートします。
python -m potato.export --config config.yaml --format eaf --output ./out/
python -m potato.export --config config.yaml --format textgrid --output ./out/どちらも往復できます。Potato は EAF と TextGrid を入力としても読むので、Potato でアノテーションし、ELAN や Praat で仕上げ、その結果をまた読み込めます。
関連文書
- 文字起こし形式、対応するすべての形式と判定方法
- YouTube 字幕をアノテーションする方法、字幕ファイル版のこのワークフロー
- 音声アノテーション、文字起こしから始めない波形作業向け
- Transcript Format Ingestion、六つの形式を並べた実行可能なショーケース
- アノテーター間一致度、ターン単位ラベルの一致を測るために