Skip to content

如何標註 Whisper 轉寫文本

如何把 Whisper 或 WhisperX 的輸出直接變成一個可執行的標註項目:該保留哪個輸出檔案、什麼時候需要說話人分離、怎樣分配說話人,以及如何在匯出時保留時間對齊。

Whisper 給出的是帶時間的片段,標註工具本來就該直接讀它。保留 .json 輸出而不是 .txt,需要說話人標籤就跑一遍 WhisperX,然後把 Potato 指向那個目錄即可。 不需要轉換指令碼,也不需要重新排版。逐個格式的細節見轉寫格式

Whisper 是 OpenAI 的開源語音識別模型,已經成為手上有一堆音訊時預設的第一步。它產出的是一組帶時間的片段,標註項目需要的東西大半都在裡面。缺的是從"我有轉寫文本"到"標註員正在打標籤"之間的所有環節。

Note: Potato 不做轉寫。語音識別在上游執行,Potato 讀取它的輸出。本文說明上游的哪些選擇會產生影響,但轉寫本身在 Potato 看到任何東西之前就已經完成了。

Whisper 的哪個輸出檔案該保留?

Whisper 會寫出好幾個檔案,選哪個比看上去更重要:

bash
whisper interview_01.mp3 --model medium --output_format json --word_timestamps True
檔案內容能用嗎
.json帶起止時間的片段,可選逐詞時間用這個
.srt / .vtt帶時間的片段,沒有後設資料可以,工作正常
.tsv毫秒為單位的起止時間加文本可以
.txt只有文本,沒有時間資訊不行,沒有任何可與音訊同步的依據

如果只留下了 .txt,對齊資訊就已經丟了,不重新跑模型就找不回來。轉寫在 Potato 裡顯示成一整塊不分段的文字,絕大多數情況都是這個原因。

Whisper 會標註說話人嗎?

不會,這一點經常出乎意料。Whisper 只做轉寫,不告訴你是誰在說話。所有話輪都以未分配的狀態到達,做內容標註沒問題,做任何與說話人相關的事情就很麻煩。

說話人分離是另一個環節。有三種處理方式。

執行 WhisperX,它在 Whisper 外面包了一層 pyannote 的說話人分離:

bash
whisperx interview_01.mp3 --model medium --diarize --output_format json

它的輸出裡每個片段都帶 speaker 欄位(SPEAKER_00SPEAKER_01 等),Potato 直接讀取。

使用開啟了說話人分離的雲端 API。diarize=true 的 Deepgram、帶 speaker_labels 的 AssemblyAI、AWS Transcribe 和 Rev.ai 都會輸出說話人標籤,Potato 四種都能原生讀取。

三個帶灰色斜紋背景的轉寫話輪,每個都標著 Unassigned 並帶有下拉箭頭。未做說話人分離的話輪會顯示為 Unassigned,並帶有說話人選擇器

交給標註員。 話輪未做說話人分離時,每個氣泡會顯示為帶選擇器的 Unassigned。在音質差、發言重疊或背景嘈雜的情況下,人耳判斷往往比自動分離更準。對小規模語料來說,這是一個合理的取捨,而不是退而求其次。

資料檔案怎麼生成?

把轉換工具指向你的 Whisper 輸出目錄:

bash
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。

在往下走之前,先確認它讀懂了什麼:

bash
potato transcripts ./whisper_out --dry-run
text
Scanned 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

能跳過轉換這一步嗎?

可以。資料檔案可以直接指向磁碟上的轉寫檔案:

json
{"id": "int_001", "conversation": {"audio": "media/int_001.mp3",
                                   "transcript": "media/int_001.srt"}}

Potato 會讀取檔案、根據內容判斷格式,並在渲染實例時完成歸一化。你的轉寫仍然是可以做差分、可以再匯出的檔案,而不會被烘進一坨資料裡。

配置長什麼樣?

生成的資料檔案是這個形狀:

json
{
  "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 直接打印出來:

yaml
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 提供雙軌時間線。兩者讀的是同一批檔案。

標註員怎麼分配說話人?

定義一份名單,讓說話人的名稱、顏色和左右位置保持穩定:

yaml
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:

bash
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 裡標註,在 ELANPraat 裡精修,再把結果讀回來。

延伸閱讀