본문으로 건너뛰기

정성 코딩을 위한 인터뷰 전사문 준비하기

자신의 컴퓨터에서 인터뷰와 포커스 그룹을 전사하고, 텍스트를 비식별화하고, 코딩과 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가 읽고 쓰는 교환 표준입니다.

이 가이드는 코딩 전후의 준비 작업을 다룹니다. 두 코더로 인터뷰 전사문을 코딩하고 알파 보고하기는 두 코더 사이의 신뢰도를, Whisper 전사문 어노테이션하는 방법은 음성 인식 출력을 오디오와 대조해 수정하는 방법을 다룹니다. 정성 연구를 위한 코드북 개발은 코드 작성을, 정성 데이터 분석 소프트웨어 비교는 도구 선택을 다룹니다.

먼저 정할 전사 관례

McLellan, MacQueen, Neidig(2003)는 모든 종류의 정성 데이터, 환경, 이론적 접근에 맞는 단일 전사 형식은 없으며 준비가 미흡한 전사문은 분석을 지연시키거나 훼손할 수 있다고 지적합니다(참고문헌 1). 문서로 작성한 관례는 모든 전사자와 모든 수정 단계가 같은 규칙을 따르게 하며, 팀은 첫 녹음을 전사하기 전에 그 관례에 합의합니다. 관례는 세 가지 결정을 다룹니다.

  • 상세 수준. 군말, 쉼, 겹치는 발화, 웃음을 표시할지, 표시한다면 어떤 표기법으로 할지 관례가 정합니다.
  • 화자 라벨. 각 화자에게는 실명이 아니라 Interviewer나 P07처럼 역할이나 참가자 ID를 부여합니다.
  • 타임스탬프. 각 발화 시작 부분의 시간 덕분에 코더가 녹음에서 해당 구절을 찾을 수 있습니다.

Potato의 로컬 전사는 Whisper 모델이 생성한 텍스트를 그대로 기록하며, 모델에게 쉼, 겹침, 군말을 표시하도록 요청하는 Potato 설정은 없습니다. 따라서 자연화 전사문은 녹음과 대조하는 수정 단계가 필요합니다. Whisper 전사문 어노테이션하는 방법은 Potato에서 수정 단계를 설정하는 방법을 설명합니다.

자신의 컴퓨터에서 녹음 전사하기

동의서와 윤리 승인은 흔히 누가 인터뷰 녹음을 들을 수 있는지, 오디오가 어디로 갈 수 있는지를 제한합니다. potato transcripts --transcribe는 sherpa-onnx로 화자 분리를 실행하고, 음성 인식은 WhisperX가 설치되어 있으면 WhisperX로, 그렇지 않으면 faster-whisper로 실행합니다. transcribe 추가 패키지는 faster-whisper와 sherpa-onnx를 설치하며, WhisperX는 별도 설치(pip install whisperx)입니다. 세 도구 모두 클라우드 서비스 없이 자신의 컴퓨터에서 실행됩니다.

bash
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의 도움말은 2인 인터뷰에서는 --num-speakers로 화자 수를 지정하면 화자 분리가 눈에 띄게 좋아진다고 밝힙니다. 두 목소리로 합성한 21초 길이의 인터뷰로 시험했을 때 다음과 같은 요약이 출력되었습니다.

text
Scanned 1 file(s):
  int07.wav                                local ASR + diarization     5 turns     21.4s  2 speaker(s): SPEAKER_00, SPEAKER_01

Potato 2.14.0은 local ASR 대신 엔진을 ASR (whisperx) 또는 ASR (faster-whisper)로 출력합니다.

화자 분리는 화자 이름을 SPEAKER_00, SPEAKER_01 등으로 붙이며 순서는 고정되어 있지 않습니다. 시험에서는 SPEAKER_01이 면접자였고, 아래의 준비 스크립트가 각 라벨을 역할이나 참가자 ID로 매핑합니다. Potato 자체 소스는 클러스터링 기반 화자 분리가 크로스토크에서 성능이 나쁘다고 밝힙니다. 코딩을 시작하기 전에 모든 포커스 그룹의 화자 라벨을 녹음과 대조해 확인하세요.

모델 가중치는 처음 사용할 때 내려받습니다. 화자 분리 모델 파일 두 개는 합쳐서 약 47MB이고, 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에는 전사문에서 이름, 장소, 그 밖의 식별 정보를 찾거나 대체하는 도구가 없습니다. 모든 코더 화면, 메모, 인용 보고서, .qdpx 내보내기가 불러온 그대로의 전사문을 재현하므로 비식별화는 Potato가 텍스트를 불러오기 전에 이루어져야 합니다. 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 파일에 담습니다.

text
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를 쓰면 스크립트는 대신 참가자 발화당 하나의 항목을 씁니다.

python
"""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}))

시험 녹음에서 스크립트는 하나의 항목을 썼으며, 여기서는 텍스트를 줄여 보였습니다.

json
{"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는 전사 형식에 나열된 대로 WhisperX JSON, SubRip, WebVTT, Praat TextGrid, ELAN EAF를 포함해 21개 전사 및 자막 형식을 읽습니다. 다른 QDA 프로그램에서 코딩한 프로젝트는 프로젝트 가져오기에 설명된 대로 potato import로 가져옵니다.

QDA 모드에서 코딩하기

아래 구성은 참가자당 하나의 케이스로 전체 전사문의 개방 코딩을 설정합니다. 이 구성은 Potato 2.13.1에서 python -m potato.validate_cli config.yaml --strict를 통과합니다.

yaml
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가 케이스 속성별로 코드를 집계하며, 이는 케이스와 케이스 속성에 설명되어 있습니다.

메모는 각 전사문의 노트 패널에서 열립니다. 새 메모는 비공개로 시작합니다. 나머지 팀이 메모를 읽도록 하려는 코더는 분석 메모에 설명된 대로 메모의 공개 범위 메뉴에서 Shared를 선택합니다. QDA 모드는 전체 말뭉치를 한 명의 분석가가 다룬다고 가정합니다. 같은 발췌문을 두 코더가 다루려면 두 코더 튜토리얼과 질적 연구의 코더 간 신뢰도를 따르세요. 언어 모델을 두 번째 코더로 쓰려면 LLM 보조 정성 코딩을 참조하세요.

코딩된 전사문 내보내기

세 가지 내보내기가 일반적인 산출물을 다룹니다.

bash
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를 명시적으로 설정했으므로, 내보내기는 메모와 케이스를 거론하는 경고를 출력합니다. 두 번째 명령은 강조 표시된 구절당 한 행으로 코드, 인용문, 문자 오프셋, 전사문 ID, 코더를 담고, 이어서 메모당 한 행을 담은 quotations.csv를 씁니다. 세 번째 명령은 모든 코더의 어노테이션을 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를 유지합니다. 모델 가중치는 처음 사용할 때 내려받으며, 첫 다운로드 이후에는 전사에 네트워크가 필요하지 않습니다.

더 읽을거리

참고문헌

  1. 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
  2. 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
  3. 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
  4. 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/