일치도 드리프트와 재보정 라운드
Potato는 /admin/iaa에서 완료된 항목의 구간별로 일치도를 채점하고, 프로젝트 기준선 아래로 떨어지면 표시하며, 코더를 다시 훈련으로 보냅니다.
Potato는 완료된 항목의 연속적인 구간에서 코더 간 일치도를 채점하므로, 최근 코딩의 하락은 하나의 프로젝트 전체 수치에 평균되어 묻히지 않고 일치도 보고서에 낮은 구간으로 나타납니다. 프로젝트 전체 계수는 첫 주의 코딩과 마지막 주의 코딩을 섞으므로, 최근 작업이 나빠진 팀도 여전히 허용 가능한 α를 보고할 수 있습니다. /admin/iaa의 Agreement over time 절은 각 어노테이션 스킴의 계수를 구간별로 보여 주며, 최근 구간이 프로젝트 기준선보다 정해진 비율 아래로 떨어지면 재보정 프롬프트를 띄웁니다. 그러면 재보정 라운드가 코더를 훈련 항목으로 다시 보내고 알려진 정답과 대조해 응답을 채점합니다. 타임라인과 라운드는 v2.9.0부터 Potato에 있으며, 타임라인은 기본으로 켜져 있습니다.
여기서 드리프트는 연구가 진행되는 동안 코더가 코드북을 적용하는 방식이 점진적으로 바뀌는 것을 뜻합니다. 드리프트는 새로운 경계 사례가 도착하고 각 코더가 정의에 대한 자기만의 해석에 정착하면서 쌓입니다. 코딩이 시작되기 전의 단 한 번의 코더 간 신뢰도 점검으로는 드리프트를 감지할 수 없습니다. Hennessy, Bleakley, and Ellithorpe (2022)가 인용한 대목에서 Lacy와 동료들(2015)은 "코더 간 신뢰도 점검은 연구 내용이 코딩되는 동안 그 내용으로 이루어져야 한다"고 씁니다. 라벨 집합이 커지면서 지침이 바뀌는 ML 라벨링 팀에도 같은 우려가 적용됩니다. 이 페이지는 연구가 진행되는 동안의 일치도를 다루며, 시간 구간에 대한 일치도는 어노테이터가 오디오와 비디오에서 시작 및 종료 시각을 표시하는 경우를 다룹니다.
구간 경계와 항목 타임스탬프
일치도는 한 코더의 응답이 아니라 코더들에 걸친 항목에 속합니다. 따라서 Potato는 각 항목을 그 항목의 마지막 코더가 끝낸 시점을 포함하는 구간에 배치합니다. 그 시점은 항목에 비교할 응답이 처음으로 모두 갖춰진 때입니다. 어노테이터 상한에 도달한 항목만 타임라인에 들어가며, 상한은 둘 이상이어야 합니다(예를 들어 num_annotators_per_item: 2). 프로젝트 전체 일치도 표도 같은 항목에서 계산됩니다. 따라서 각 구간은 기준선을 이루는 항목의 일부를 담고 있습니다.
코더가 항목을 끝낸 시점은 그 스킴의 응답에 기록된 마지막 변경입니다. 항목에 기록된 변경이 없으면 Potato는 코더가 항목을 떠난 시각으로, 그다음에는 코더가 항목을 연 시각으로 대체합니다. 큐를 훑어보고 나중에 응답하는 코더는 응답하기 훨씬 전에 각 항목을 열었으므로, 연 시각은 최후의 수단입니다. 타임스탬프가 전혀 없는 항목은 타임라인에 들어가지 않으며, 이 절은 그 수를 보고합니다. 다른 도구에서 가져온 어노테이션이 보통 이 경우입니다.
항목은 그 시점을 기준으로 정렬되고 두 가지 방식 중 하나로 구간으로 나뉩니다.
- 같은 개수(
window_by: count, 기본값). 모든 구간이 같은 수의 항목을 담습니다. 구간의 수는 항목 수로 제한됩니다. 따라서 다섯 항목짜리 시범 적용은 빈 구간을 만들지 않습니다. - 같은 기간(
window_by: time). 모든 구간이 같은 길이의 시계 시간에 걸칩니다. 한 달 간격의 두 주말처럼 코딩이 몰아서 이루어지면 그 사이의 구간은 비어 있습니다. 빈 구간에는 일치도 값이 전혀 없습니다. 일치도 페이지의 표에서 빈 구간은 코딩이 없었는데도 일치도가 붕괴한 것처럼 보일 수 있으며, 같은 개수가 기본값인 이유가 그것입니다.
각 구간이 보고하는 계수
Potato는 각 구간 자체의 항목에 대해 프로젝트 전체 일치도 계산을 다시 실행합니다. 따라서 주어진 스킴에 대해 모든 구간과 기준선이 같은 계수를 사용합니다. Potato는 스킴당 하나의 대표 계수를 고르며 우연 보정 척도를 선호합니다. 퍼센트 일치도는 클래스 불균형만으로도 올라가며, 코더들이 우연이 예측하는 것보다 덜 일치하는 동안에도 상승 추세를 보일 수 있습니다. 계수는 스킴 유형에 따라 달라집니다.
| 스킴 유형 | 타임라인의 계수 |
|---|---|
radio, select(발췌문당 코드 하나) | Krippendorff의 α, 명목 |
likert | Krippendorff의 α, 순서형 |
slider, number | Krippendorff의 α, 등간 |
multiselect, hierarchical_multiselect(발췌문당 여러 코드) | MASI 거리를 쓴 Krippendorff의 α |
span(코딩된 구절) | 위치 α, 위치 α가 정의되지 않으면 토큰 수준 κ |
multirate 및 기타 그리드 스킴 | 모든 행에 걸쳐 합친 α |
| α가 없는 스킴 | Fleiss의 κ, Cohen의 κ, 가중 κ, 상관 또는 ICC, 스팬 F1, 퍼센트 일치도 중 처음으로 사용 가능한 것 |
Krippendorff의 α는 완전 일치에서 1이고 우연이 만들어 낼 수준의 일치에서 0입니다. textbox 같은 자유 텍스트 스킴에는 일치도 척도가 없으므로 타임라인에 행이 없습니다. 구간의 데이터가 기준선이 쓰는 계수를 만들어 낼 수 없으면 셀은 다른 척도 대신 값을 표시하지 않습니다. 서로 다른 두 척도 사이의 하락은 아무 의미가 없기 때문입니다.
타임라인 셀과 셀 메모
각 스킴은 한 행을 얻습니다. 행은 기준선으로서의 프로젝트 전체 계수로 시작하고, 이어서 구간당 하나의 셀(W1, W2 등)에 구간의 계수와 항목 수가 나옵니다. 숫자가 없는 셀은 대신 짧은 메모를 보여 주며, Potato는 타임라인 표 아래에서 그 메모를 각각 설명합니다. 항목이 너무 적은 구간은 값을 보여 주고 표시를 덧붙입니다. 메모와 표시의 의미는 다음과 같습니다.
| 메모 | 의미 |
|---|---|
total agreement | 구간의 모든 항목에서 모든 코더가 같은 코드를 선택했습니다. 우연 보정 계수는 보정할 것이 없으므로 정의되지 않습니다. 이 메모는 일치도의 최선의 경우를 나타냅니다. |
undefined | 구간의 데이터에 대해 계수가 정의되지 않으며, 가장 흔한 이유는 하나의 코드가 모든 판단을 차지하기 때문입니다. |
no overlap | 구간의 항목에서 겹친 코더가 두 명 미만입니다. |
no items | 구간에 속한 항목이 없습니다(같은 기간 구간만 해당). |
too few to judge | 구간에 항목이 다섯 개 미만입니다. 값은 표시되지만 재보정 프롬프트는 그 구간을 무시합니다. |
프로젝트가 QDA 모드 코드북을 사용하면 표 아래의 한 줄이 코드북 수정본(r1 · r2 · r3)을 나열합니다. Potato는 코드북이 바뀔 때마다 수정 번호를 올립니다. 코드를 만들기, 이름 바꾸기, 색 바꾸기, 이동, 삭제는 각각 변경으로 칩니다. v2.9.0부터 기록된 수정본은 정확한 시각이 있습니다. 이전 수정본은 그 수정본이 찍힌 가장 이른 어노테이션으로 날짜를 매기며, 그 날짜는 하한이고 수정본에는 approximate라는 표시가 붙습니다. HTML 페이지는 수정본을 번호로 나열하고, 아래의 JSON 보고서는 각 수정본의 시각을 제공합니다. 스크립트는 각 코드북 편집을 구간 경계와 대조해 지침 변경이 일치도를 움직였는지 보일 수 있습니다.
구성
타임라인에는 구성이 필요 없습니다. 항목당 코더가 둘 이상이고 일치도 보고가 켜진 프로젝트는 /admin/iaa에서 이 절을 얻습니다. 관리자 대시보드는 이 페이지를 전체 일치도 보고서로 링크합니다. 기본값을 바꾸려면 기존 구성에 calibration 블록을 추가하세요.
calibration:
enabled: true
windows: 6
window_by: count
drop_threshold: 0.15calibration은 정확히 이 네 개의 키를 받습니다.
| 키 | 유형 | 기본값 | 키가 하는 일 |
|---|---|---|---|
enabled | 불리언 | true | /admin/iaa를 불러올 때마다 타임라인을 계산합니다. false만 이 절을 끕니다. |
windows | 정수 | 6 | 구간의 수. 여섯 구간은 추세를 보여 주면서 각 구간에 우연 보정 계수를 위한 충분한 항목을 남깁니다. 같은 개수 모드에서는 이 수가 항목 수로 제한됩니다. |
window_by | 문자열 | count | 같은 크기의 구간은 count, 같은 기간의 구간은 time. 다른 값은 count로 취급됩니다. |
drop_threshold | 숫자 | 0.15 | 재보정 프롬프트를 띄우는, 기준선 아래로의 상대적 하락. 0.15는 프로젝트 전체 계수보다 15% 낮다는 뜻입니다. |
타임라인은 /admin/iaa를 불러올 때마다 구간당 한 번의 추가 일치도 계산 비용이 듭니다. enabled: false로 설정하면 대규모 프로젝트에서 그 비용이 사라집니다. 그러면 타임라인은 /admin/iaa와 /admin/iaa/drift JSON 보고서 양쪽에서 사라집니다.
재보정 라운드는 훈련 단계를 다시 실행합니다. 따라서 라운드를 운영할 프로젝트는 training을 활성화하고 phases에 training 항목을 두어야 합니다. 아래 구성은 답변당 코드 하나로 개방형 설문 응답을 다루는 완전한 두 코더 연구입니다.
annotation_task_name: Open-ended responses, two coders
task_dir: .
output_annotation_dir: annotation_output/
data_files: [data/responses.json]
item_properties: {id_key: id, text_key: text}
annotation_schemes:
- annotation_type: radio
name: barrier
description: Which barrier to care does the respondent describe?
labels: [Cost, Distance, Wait time, Trust, None]
num_annotators_per_item: 2
training:
enabled: true
data_file: data/training.json
allow_retry: true
failure_action: repeat_training
phases:
order: [training, annotation]
training: {type: training}
annotation: {type: annotation}
agreement_metrics: {enabled: true}
calibration:
windows: 6
window_by: count
drop_threshold: 0.15num_annotators_per_item: 2는 모든 발췌문을 두 코더 앞에 놓습니다. 발췌문당 코더 두 명이 각 구간에 일치도 값을 줍니다. failure_action: repeat_training은 라운드에 실패한 코더의 참여를 끝내는 대신 그 코더의 훈련 항목을 다시 시작합니다. 훈련 파일에는 합의된 코드와 설명이 있는 발췌문이 들어 있으며, 형식은 훈련 단계 페이지에 문서화되어 있습니다.
재보정 프롬프트
Potato는 각 스킴의 최근 구간을 기준선과 비교하여 상대적 하락이 drop_threshold에 이르면 프롬프트를 띄웁니다. 상대적 하락은 (기준선 − 최근) / 기준선입니다. 기준선은 첫 구간이 아니라 프로젝트 전체 계수인데, 새로 훈련된 팀의 일치도는 첫 구간에서 가장 불안정하기 때문입니다. 그렇지 않으면 이례적으로 좋은 첫 구간이 연구 전체의 기준을 정하게 됩니다. 기준선 α가 0.720이고 최근 구간이 0.580이면 하락은 19%이며, 이 절은 다음을 보여 줍니다.
Re-calibration suggested — barrier 19% below the project baseline (alpha_nominal 0.580 vs 0.720).프롬프트는 팀 책임자에 대한 조언입니다. 프롬프트는 코더에게 알리지 않으며 라운드를 시작하지도 않습니다. Potato는 세 가지 경우에는 프롬프트를 띄우지 않습니다.
- 이전의 하락. Potato는 항목이 다섯 개 이상인 최근 구간만 판단합니다. 팀이 이미 회복한 하락에 대한 프롬프트는 팀이 프롬프트를 무시하도록 가르칠 것입니다.
- 정의되지 않은 최근 구간. 최근 구간이
total agreement나undefined를 보이면 Potato는 더 오래된 구간으로 대체하지 않습니다. - 0 이하의 기준선. 이미 우연 수준인 일치도는 프로젝트 전체 표가 보고하는 문제이며, 0에서의 퍼센트 하락은 의미가 없습니다.
관리자 API로 보정 라운드 시작하기
라운드는 지정된 각 코더의 훈련 진행 상황을 초기화하고 코더를 훈련 단계로 되돌립니다. 코더는 같은 훈련 항목을 다시 풀어 나갑니다. Potato는 첫 번째 통과에서처럼 같은 정답에 대해 응답을 채점하고 틀린 응답마다 설명을 보여 줍니다. 통과하면 코더는 phases.order에서 훈련 다음에 오는 단계로 이동하여 중단했던 곳에서 재개합니다. 라운드는 배정이나 저장된 어노테이션을 바꾸지 않습니다. Potato는 코더에게 라운드가 시작되었다는 메시지를 보여 주지 않습니다. 라운드를 시작하기 전에 팀에게 알리세요.
라운드는 관리자 API 키로 관리자 API를 통해 시작합니다. 관리자 페이지에는 라운드를 시작하는 버튼이 없습니다. 키의 출처는 관리자 대시보드 페이지가 설명합니다. 누가 대상인지, 마지막 라운드가 어떻게 되었는지 확인하세요.
curl -H "X-API-Key: $KEY" http://localhost:8000/admin/calibration응답은 라운드가 다시 불러올 수 있는 코더, 과거 라운드, 가장 최근 라운드에서 불러온 각 코더의 진행 상황을 나열합니다. 진행 상황은 passed, failed, in_progress, unknown 중 하나이며 응답 수, 전체 수, 정답 수가 함께 나옵니다. 어노테이션 단계, 연구 후 단계, 완료 단계에 있는 코더가 대상입니다. 대상이 되는 모든 코더에 대해 라운드를 시작하세요.
curl -X POST -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{"reason": "barrier alpha fell 19%"}' \
http://localhost:8000/admin/calibration/start이 호출은 라운드의 ID, 불러온 코더, 사유와 함께 건너뛴 코더를 반환합니다. 지정한 코더만 불러오려면 "usernames": ["coder1", "coder2"]를 전달하세요. usernames를 생략하면 대상이 되는 모든 코더를 불러옵니다. 지침 드리프트는 팀 전체에 영향을 줍니다. 따라서 모두를 불러오는 것이 보통 올바른 선택입니다.
아직 동의 또는 안내 페이지에 있는 코더는 응답의 skipped 아래에 나열됩니다. training이 활성화되어 있지 않으면 호출은 누구도 불러오지 않고 HTTP 400을 반환합니다. 훈련 항목이 없으면 불려온 코더가 빈 훈련 단계를 그대로 통과해 버리기 때문입니다.
두 가지 이유로 첫 번째 라운드 전에 훈련 설정을 계획하세요. 첫째, 라운드에 실패한 코더는 training.failure_action이 처리합니다. 기본값 move_to_done에서는 라운드에 실패하면 그 코더의 연구 참여가 끝납니다. 첫 번째 통과에서 실패해도 같은 효과가 납니다. 대신 훈련 항목을 다시 시작하려면 failure_action: repeat_training을 설정하세요. 둘째, 라운드는 훈련 항목을 ID로 선택하고 각 코더에게 처음에 본 항목을 보여 줍니다. 코더의 첫 번째 통과 이후 파일에 추가된 훈련 항목은 라운드에서 그 코더에게 도달하지 않습니다. 어려운 사례는 코딩이 시작되기 전에 훈련 파일에 넣으세요.
팀이 불일치를 논의하는 곳
라운드가 훈련 항목을 다시 실행하는 이유는 그 항목에 채점의 기준이 되는 알려진 정답이 있기 때문입니다. 연구 자체의 불일치 항목에는 정답이 없습니다. 따라서 Potato는 그 항목을 라운드에서 제외합니다. 재보정의 논의 부분은 Potato의 다른 곳에서 이루어집니다. 멀티플레이어 룸의 허들 룸은 팀이 현재 의견이 갈리는 항목을 차례로 살펴보고 모든 코더의 원래 코드를 보여 줍니다. 판정 큐에서는 판정자가 분쟁이 있는 항목을 하나씩 해소합니다. 정성 연구 팀은 보통 코드북의 코드 정의를 수정하는 것으로 그 논의를 마무리하며, 다음 수정본은 이후 구간 옆의 타임라인에 나타납니다.
드리프트 보고서와 라운드 기록
드리프트 보고서와 라운드 기록은 어노테이션 내보내기와 별개입니다. 어떤 python -m potato.export 형식도 드리프트 보고서나 라운드 기록을 포함하지 않습니다. 타임라인을 JSON으로 가져오세요.
curl -H "X-API-Key: $KEY" http://localhost:8000/admin/iaa/driftJSON 보고서에는 다음 필드가 있습니다.
windows는 각 구간의 인덱스, 시작 및 종료 시각, 항목 수,sparse플래그, 항목 ID, 그리고 구간으로 제한한 전체 일치도 보고서를 나열합니다.schemas는 스킴별로 대표 척도, 기준선, 그리고 구간의 값, 메모, 항목 수가 담긴 구간당 하나의 점을 제공합니다.triggers는 프롬프트를 띄운 모든 스킴을 기준선, 최근 값, 상대적 하락과 함께 나열합니다.markers는 코드북 수정본을 각 수정본의 시각과 대략적인 날짜를 나타내는inferred플래그와 함께 나열합니다.n_items와n_untimed_items는 타임라인에 있는 항목과 없는 항목을 세며,reason은 비어 있는 보고서를 설명합니다.
시각은 초 단위의 Unix 타임스탬프입니다. 어노테이터 상한에 도달한 항목이 없거나 타임스탬프가 있는 모든 항목이 하나의 구간에 속할 때처럼 비교할 것이 없으면 보고서는 비어 있습니다. 그러면 reason 필드가 이유를 알려 줍니다.
Potato는 각 라운드를 <task_dir>/project.sqlite의 calibration_rounds 테이블에 라운드의 시작 시각, 라운드를 시작한 관리자(API 키 호출은 admin), 사유 텍스트, 불려온 코더와 함께 기록합니다. 라운드 기록과 구간별 계수는 방법 절에 각 재보정의 날짜와 각 라운드 전후의 일치도를 제공합니다. 정성 연구의 감사 추적에는 두 기록이 모두 필요합니다. 코더의 훈련 응답은 user_state.json에 있으며 각 라운드는 그 응답을 초기화합니다. 첫 번째 통과의 훈련 점수를 보관하려면 라운드 전에 어노테이션 출력 디렉터리의 사본을 저장하세요.
더 읽을거리
- 훈련 단계, 훈련 파일, 통과 기준,
failure_action - 멀티플레이어 룸, 노밍 세션과 분쟁 항목에 대한 허들
- 질적 연구의 코더 간 신뢰도, 어떤 계수를 언제 보고할지
- 어노테이터 간 일치도, 어노테이션 유형별 κ와 α
- 시간 구간에 대한 일치도, 오디오와 비디오 구간
구현 세부 사항은 소스 문서를 참조하세요.
참고문헌
- Hennessy, M., Bleakley, A., & Ellithorpe, M. E. (2022). Evaluating and tracking qualitative content coder performance using item response theory. Quality & Quantity, 57(2), 1231-1245. https://doi.org/10.1007/s11135-022-01397-7
- Lacy, S., Watson, B. R., Riffe, D., & Lovejoy, J. (2015). Issues and best practices in content analysis. Journalism & Mass Communication Quarterly, 92(4), 791-811. https://doi.org/10.1177/1077699015607338 (quoted from Hennessy et al., 2022)