為質性編碼準備訪談轉錄稿
在你自己的電腦上轉寫訪談和焦點小組錄音,對文本去標識化,並在編碼和匯出到 NVivo 之前為每位參與者設定一個案例。
訪談和焦點小組錄音要經過五個步驟才能變成已編碼的摘錄,依次是:帶說話人標籤和時間戳的轉寫、去標識化、在整份轉錄稿與摘錄之間做出選擇、按每位參與者一個案例載入資料,以及編碼和匯出。Potato 用 WhisperX 或 faster-whisper 在你自己的電腦上轉寫錄音,在 QDA 模式中藉助備忘錄和案例為文本編碼,並把已編碼的項目以 REFI-QDA .qdpx 檔案匯出到 NVivo、ATLAS.ti 或 MAXQDA。Potato 不會對轉錄稿去標識化,所以你的團隊需要在載入文本之前替換姓名和其他標識資訊。
轉寫把錄下的語音變成文字,轉錄稿記錄到多細的程度是一項分析上的決定。Oliver、Serovich 和 Mason(2005)區分了兩種慣例(參考文獻 2)。自然化轉寫儘可能詳細地記錄每一句話語。去自然化轉寫會修正語法,刪去口吃和停頓,並把非標準口音規範化。說話人分離標出誰在什麼時候說話,去標識化則刪除或替換可能識別出參與者的細節。在 Potato 中,案例把屬於同一位參與者的條目歸為一組,備忘錄是附在一份轉錄稿或一段高亮文字上的分析筆記。REFI-QDA 是 NVivo、ATLAS.ti 和 MAXQDA 都能讀寫的交換標準。
本指南介紹編碼前後的準備工作。兩名編碼者編碼訪談轉錄稿並報告 alpha 介紹兩名編碼者之間的信度,如何標註 Whisper 轉寫文本介紹如何對照音訊校正語音識別的輸出。為質性研究編制碼本介紹如何撰寫程式碼,質性資料分析軟體對比介紹如何選擇工具。
先確定轉寫慣例
McLellan、MacQueen 和 Neidig(2003)指出,沒有哪一種轉寫格式適合所有類型的質性資料、場景或理論取向,準備不當的轉錄稿可能拖延分析,甚至損害分析(參考文獻 1)。一份成文的慣例讓每位轉寫者和每一輪校正都遵循相同的規則,你的團隊要在轉寫第一份錄音之前商定這份慣例。慣例涵蓋三項決定:
- 細節。 慣例規定是否標註填充詞、停頓、重疊話語和笑聲,以及用什麼記號標註。
- 說話人標籤。 每位說話人用一個角色或參與者 ID 表示,例如
Interviewer或P07,絕不用真實姓名。 - 時間戳。 每個話輪開頭的時間讓編碼者能在錄音中找到這段話。
Potato 的本地轉寫寫出的是 Whisper 模型生成的文字,Potato 也沒有任何設定能讓模型標出停頓、重疊或填充詞。因此,自然化轉錄稿需要對照錄音再做一輪校正。如何標註 Whisper 轉寫文本介紹如何在 Potato 中設定這輪校正。
在你自己的電腦上轉寫錄音
知情同意書和倫理審批常常限定誰可以聽訪談錄音,以及音訊可以傳到哪裡。potato transcripts --transcribe 用 sherpa-onnx 做說話人分離,語音識別在 WhisperX 已安裝時用 WhisperX,否則用 faster-whisper。transcribe 附加依賴會安裝 faster-whisper 和 sherpa-onnx,WhisperX 需要單獨安裝(pip install whisperx)。這三個工具都在你自己的電腦上執行,不經過任何雲服務:
pip install 'potato-annotation[transcribe]'
potato transcripts ./recordings --transcribe --asr-model small.en --diarize --num-speakers 2 -o data/transcripts.json該命令為每份錄音寫出一個條目,其中包含每個話輪的說話人、開始時間、結束時間和文字。Whisper 模型從 tiny 到 large-v3 不等,預設是 base。模型越大,執行越慢,轉寫也越準確。Potato 的幫助文本提到,用 --num-speakers 給出說話人數量後,兩人訪談的說話人分離效果會明顯更好。在一段 21 秒、有兩個聲音的合成訪談上測試時,命令輸出了下面的摘要:
Scanned 1 file(s):
int07.wav local ASR + diarization 5 turns 21.4s 2 speaker(s): SPEAKER_00, SPEAKER_01Potato 2.14.0 會把引擎名寫在 local ASR 的位置,例如 ASR (whisperx) 或 ASR (faster-whisper)。
說話人分離把說話人命名為 SPEAKER_00、SPEAKER_01 等,順序不固定。在測試中,SPEAKER_01 是訪談者,下文的準備指令碼會把每個標籤對映為一個角色或參與者 ID。Potato 自己的原始碼註明,基於聚類的說話人分離在多人同時說話時表現很差。編碼開始前,請對照錄音檢查每場焦點小組的說話人標籤。
模型權重在首次使用時下載。兩個說話人分離模型檔案合計約 47 MB,Whisper 的下載量隨模型大小增加。權重放到機器上之後,轉寫可以在氣隙環境中執行,詳見氣隙與離線部署。對於離線機器,請把一個 CTranslate2 格式的 Whisper 模型目錄複製過去,並把該目錄的路徑傳給 --asr-model,再把 POTATO_MODEL_CACHE 設為存放說話人分離模型的資料夾。
Potato 會把每份原始轉錄稿以 <name>.whisper.json 快取在錄音旁邊,快取中保留了所有姓名。請把快取檔案和錄音放在一起,或者傳入 --asr-cache,把快取檔案寫到一個同樣受保護的資料夾。轉寫之前會先執行語音活動檢測,防止 Whisper 在靜音片段上憑空生成文字。這項檢測也可能在沒有任何警告的情況下丟掉輕聲或耳語。如果轉錄稿比錄音短,請加上 --asr-no-vad 重新執行命令。WhisperX 始終執行語音活動檢測,所以安裝了 WhisperX 時,要讓 --asr-no-vad 生效,請再加上 --asr-engine faster-whisper。
編碼前對轉錄稿去標識化
Potato 沒有能在轉錄稿中查詢或替換姓名、地名或其他標識資訊的工具。因此,去標識化必須在 Potato 載入文本之前完成,因為每個編碼者介面、每條備忘錄、每份引文報告和每個 .qdpx 匯出都會按載入時的原樣呈現轉錄稿。Potato 唯一的文本清理功能在項目結束時執行。python -m potato.publish --scrub-pii 會在釋出的資料集中把電子郵件地址和電話號碼替換為 [EMAIL] 和 [PHONE],這個選項不會處理姓名。
英國資料服務(UK Data Service)建議用化名或描述性標籤(例如“[colleague 1]”)替換識別性細節,而不是刪除這些內容。同一份指南要求在所有轉錄稿中一致地應用每一處替換,併為改動留下日誌。該指南還要求有所剋制,因為過度匿名化會刪去情境細節,降低資料的詮釋價值(參考文獻 4)。Saunders、Kitzinger 和 Kitzinger(2015)在對植物狀態和最低意識狀態患者家屬的訪談中描述了這種取捨(參考文獻 3)。幾位作者把醫院名稱替換為“Hospital 1”這樣的標籤,並報告說某些保護措施讓分析失去了資料的一部分豐富性。
下一節的準備指令碼會把替換表 pseudonyms.csv 應用到每份轉錄稿上,這張表同時也是匿名化日誌。由於這張表能把化名映射回真實姓名,請把這張表存放在 Potato 所服務的項目資料夾之外。替換表只能捕捉表中列出的拼寫。因此,替換之後仍要有人通讀每份轉錄稿,並判斷工作單位或罕見職業這類間接標識資訊。在測試轉錄稿中,替換表把參與者姐妹的名字替換為 Ana,把一個街道名替換為“[street name]”,而這位姐妹工作的麵包店則留給讀者判斷。載入之後,用 Potato 的全文搜尋在語料中逐一搜索日誌裡的每個姓名。每次搜尋都不應返回任何轉錄稿。
整份轉錄稿還是摘錄
編碼者看到的單位是資料檔案中的一個條目。把整份轉錄稿作為一個條目,能讓編碼者看到訪談的完整脈絡,.qdpx 匯出也會為每場訪談寫出一個來源。項目也可以把每份轉錄稿拆成摘錄,例如每個參與者回答一個條目,這時匯出會為每條摘錄寫出一個來源。摘錄更適合以下兩種情況:
- 編碼者間信度。 一致性是在兩名編碼者都作答的單元上計算的,固定的摘錄讓兩名編碼者面對同一個單元。雙編碼者教程正是出於這個原因把轉錄稿拆成一個個回答。
- 焦點小組。 Potato 把每個條目最多歸入一個案例,因此作為一個條目載入的小組轉錄稿只能屬於一個案例。每個參與者話輪一個條目,才能讓每位參與者的話輪組成該參與者自己的案例。
把轉錄稿展平為帶說話人標籤的文本
potato transcripts 的輸出把每份轉錄稿儲存為一個巢狀的話輪列表,而 .qdpx 匯出會跳過文本不是純字串的條目。因此,要匯入 NVivo、ATLAS.ti 或 MAXQDA 的項目需要先把每份轉錄稿展平為帶說話人標籤的文本。指令碼所依據的決定儲存在三個小型 CSV 檔案中:
speakers.csv transcript,speaker,name
int07,SPEAKER_01,Interviewer
int07,SPEAKER_00,P07
participants.csv participant_id,site,age_group
P07,rural,35-49
pseudonyms.csv original,replacement
Maria,Ana
Main Street,[street name]指令碼把同一說話人的連續片段合併為一個話輪,在每個話輪開頭寫入時間戳,應用替換表,並把參與者的屬性複製到條目上。加上 --excerpts 後,指令碼改為每個參與者話輪寫出一個條目:
"""Turn `potato transcripts` output into speaker-labeled text for coding.
Usage:
python prepare_transcripts.py data/transcripts.json speakers.csv participants.csv \
pseudonyms.csv > data/interviews.jsonl
python prepare_transcripts.py ... --excerpts > data/excerpts.jsonl
"""
import csv
import json
import re
import sys
transcripts, speakers_csv, participants_csv, pseudonyms_csv = sys.argv[1:5]
excerpts = "--excerpts" in sys.argv
names = {(r["transcript"], r["speaker"]): r["name"] for r in csv.DictReader(open(speakers_csv))}
people = {r["participant_id"]: r for r in csv.DictReader(open(participants_csv))}
swaps = [(re.compile(rf"\b{re.escape(r['original'])}\b"), r["replacement"])
for r in csv.DictReader(open(pseudonyms_csv))]
def clock(seconds):
m, s = divmod(int(seconds), 60)
return f"{m // 60:02d}:{m % 60:02d}:{s:02d}"
def pseudonymize(text):
for pattern, replacement in swaps:
text = pattern.sub(replacement, text)
return text
for item in json.load(open(transcripts, encoding="utf-8")):
tid = item["id"]
turns = [] # merge consecutive segments from the same speaker into one turn
for t in item["conversation"]["turns"]:
name = names.get((tid, t.get("speaker")), t.get("speaker") or "Unknown")
if turns and turns[-1]["name"] == name:
turns[-1]["text"] += " " + t["text"]
else:
turns.append({"name": name, "start": t.get("start") or 0, "text": t["text"]})
for t in turns:
t["text"] = pseudonymize(t["text"])
if excerpts: # one item per participant turn, for focus groups
for n, t in enumerate(turns):
if t["name"] in people:
print(json.dumps({"id": f"{tid}-{n:03d}", "transcript": tid,
**people[t["name"]],
"text": f"[{clock(t['start'])}] {t['name']}: {t['text']}"}))
continue
participant = next(t["name"] for t in turns if t["name"] in people)
text = "\n\n".join(f"[{clock(t['start'])}] {t['name']}: {t['text']}" for t in turns)
print(json.dumps({"id": tid, **people[participant], "text": text}))在測試錄音上,指令碼寫出了一個條目,下面展示時縮短了文本:
{"id": "int07", "participant_id": "P07", "site": "rural", "age_group": "35-49", "text": "[00:00:00] Interviewer: Thanks for joining, can you tell me how you get to the clinic?\n\n[00:00:04] P07: I take two buses, and it takes most of the morning, my sister Ana drives me when she can, but she works at the bakery on [street name].\n\n..."}已經以檔案形式存在的轉錄稿也走同一個指令碼。不加 --transcribe 時,potato transcripts 可讀取 21 種轉寫和字幕格式,包括 WhisperX JSON、SubRip、WebVTT、Praat TextGrid 和 ELAN EAF,完整列表見轉寫格式。在其他 QDA 程式中編碼過的項目可以通過 potato import 遷移過來,詳見匯入項目。
在 QDA 模式中編碼
下面的配置為整份轉錄稿設定開放式編碼,每位參與者一個案例。該配置在 Potato 2.13.1 上通過了 python -m potato.validate_cli config.yaml --strict:
annotation_task_name: Clinic access interviews
task_dir: .
output_annotation_dir: annotation_output/
data_files:
- data/interviews.jsonl
item_properties:
id_key: id
text_key: text
user_config:
allow_all_users: true
users: []
# Open coding by one analyst: a codebook that grows while reading, memos, cases, search
qda_mode:
enabled: true
# One case per participant, with the participant's site and age group on the case
cases:
enabled: true
key: participant_id
attributes: [site, age_group]
annotation_ui:
memos: true
search:
enabled: true
annotator_claim: true
annotation_schemes:
- annotation_type: span
name: codes
description: Highlight a passage and apply a code, or press i to create a code from the highlighted words.
codebook: true
labels: [travel to the clinic, cost of care, family support, delaying care]QDA 模式把碼本設為 open。分析者在閱讀時新增程式碼,這四個標籤只是碼本的初始程式碼。選中一段文字後按 i,會用所選的詞語建立一個原位程式碼。之後重新命名或合併某個程式碼時,此前的編碼仍留在舊名稱下,詳見質性編碼的共享碼本。search 塊開啟全文搜尋,也就是上文檢查姓名時用到的功能。用 potato start config.yaml -p 8000 啟動伺服器。
cases 塊按 participant_id 把條目歸組,並在每個案例上儲存 site 和 age_group。Potato 2.13.1 只根據條目欄位建立案例,沒有任何手動把條目分配給案例的方法。因此,每個條目都需要帶有參與者 ID。測試項目的伺服器日誌報告了 Cases auto-detect: 1 case(s), 1 instance(s) assigned。編碼進行期間,管理員 API 端點 /admin/api/code_crosstab?attribute=site 可按案例屬性統計程式碼,詳見質性編碼中的案例與案例屬性。
備忘錄在每份轉錄稿的 Notes 面板中開啟。新建的備忘錄預設是私有的。編碼者如果希望團隊其他成員閱讀某條備忘錄,就在該備忘錄的可見性選單中選擇 Shared,詳見質性編碼中的分析備忘錄。QDA 模式假定由一位分析者處理整個語料。如果兩名編碼者編碼同一批摘錄,請參照雙編碼者教程和質性研究中的編碼者間信度。如果要讓語言模型擔任第二編碼者,請參閱大語言模型輔助的質性編碼。
匯出已編碼的轉錄稿
下面三種匯出涵蓋了常見的交付物:
python -m potato.export -c config.yaml -f qdpx -o export/
python -m potato.export -c config.yaml -f quotation_report --option include_memos=true -o export/
python -m potato.export -c config.yaml -f csv -o export/第一條命令寫出一個 .qdpx 檔案,其中每份轉錄稿是一個文本來源,程式碼是沒有層級的扁平列表,每條編碼都附有其編碼者。該匯出不包含備忘錄和案例。由於配置中顯式設定了 cases.enabled 和 annotation_ui.memos,匯出會列印一條警告,點名備忘錄和案例。第二條命令寫出 quotations.csv,每個高亮段落一行,給出程式碼、引文、字元偏移量、轉錄稿 ID 和編碼者,之後每條備忘錄一行。第三條命令把每位編碼者的標註寫成一張表格,供 R 或 Python 使用。案例屬性保留在 participants.csv 中,資料檔案的 id 和 participant_id 欄位可以把這些屬性與引文報告連線起來。
常見問題
訪談轉錄稿應該逐字轉寫,還是做清理後的逐字轉寫?
根據分析需要選擇細節程度。Oliver、Serovich 和 Mason(2005)指出,自然化的完全逐字轉寫在會話分析中最為常見。研究參與者就某個話題說了什麼的研究,通常可以使用去自然化的清理版轉錄稿。Potato 的本地轉寫寫出的是 Whisper 模型生成的文字。因此,逐字轉寫的慣例需要對照錄音再做一輪校正。
訪談轉錄稿應該在什麼時候去標識化?
在編碼開始之前對轉錄稿去標識化,因為 Potato 會完全按載入時的樣子顯示和匯出文本。Potato 沒有去標識化工具。請在載入之前應用一張化名前後一致的替換表,通讀每份轉錄稿查詢間接標識資訊,並在載入後的語料中搜索每個原始姓名。
faster-whisper 能否在不上傳音訊的情況下轉寫訪談?
能,faster-whisper 在你自己的電腦上執行 Whisper,potato transcripts --transcribe 在未安裝 WhisperX 時用 faster-whisper 把一個資料夾的錄音轉成一個 Potato 資料檔案。安裝了 WhisperX 時,該命令改在你自己的電腦上執行 WhisperX,--asr-engine faster-whisper 則保留 faster-whisper。模型權重在首次使用時下載,首次下載之後,轉寫不再需要網路。
延伸閱讀
- 兩名編碼者編碼訪談轉錄稿並報告 alpha:兩名編碼者之間的信度與裁定
- 如何標註 Whisper 轉寫文本:對照音訊校正機器轉寫
- 轉寫格式:Potato 可讀取的每種轉寫格式及本地轉寫選項
- QDA 模式、共享碼本、分析備忘錄、案例與案例屬性和全文搜尋
- 項目快照:凍結已編碼的項目,作為審計軌跡
- 為質性研究編制碼本
- 質性研究中的編碼者間信度
- 氣隙與離線部署
- Potato 中的質性研究:一處彙總所有質性研究功能
參考文獻
- McLellan, E., MacQueen, K. M., & Neidig, J. L. (2003). Beyond the Qualitative Interview: Data Preparation and Transcription. Field Methods, 15(1), 63-84. https://doi.org/10.1177/1525822X02239573
- Oliver, D. G., Serovich, J. M., & Mason, T. L. (2005). Constraints and Opportunities with Interview Transcription: Towards Reflection in Qualitative Research. Social Forces, 84(2), 1273-1289. https://doi.org/10.1353/sof.2006.0023
- Saunders, B., Kitzinger, J., & Kitzinger, C. (2015). Anonymising interview data: challenges and compromise in practice. Qualitative Research, 15(5), 616-632. https://doi.org/10.1177/1468794114550439
- UK Data Service. Anonymisation for text data. Retrieved November 2, 2026, from https://ukdataservice.ac.uk/learning-hub/data-producer-support/preparing-data-for-sharing-and-reuse/anonymising-data-and-protecting-participants/anonymisationfortextdata/