如何標註 Whisper 轉寫文本
如何把 Whisper 或 WhisperX 的輸出直接變成一個可執行的標註項目:該保留哪個輸出檔案、什麼時候需要說話人分離、怎樣分配說話人,以及如何在匯出時保留時間對齊。
Whisper 給出的是帶時間的片段,標註工具本來就該直接讀它。保留 .json 輸出而不是 .txt,需要說話人標籤就跑一遍 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"
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: right當存在需要分配的未分離話輪時,auto 會開啟選擇器。如果源資料本來就帶說話人標籤,但你仍想允許重新分配,就設成 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,把六種格式並排展示的可執行示例
- 標註者間一致性,用於衡量話輪級標籤的一致程度