완전한 중복을 넘어서: 대규모 데이터셋을 위한 적응형 어노테이터 커버리지
모든 항목을 이중 라벨링하면 비용이 많이 들고, 모든 항목을 단일 라벨링하면 품질을 알 길이 없습니다. Potato 2.6은 대부분의 항목에 어노테이터 한 명을, 층화 표본에는 세 명을 배정하게 해 주며, 적응형 부스트와 자동 판정 라우팅을 지원합니다.
규모가 있는 어노테이션 프로젝트에는 으레 따라붙는 긴장이 있습니다. 모든 항목에 두세 명의 어노테이터를 붙이면 일치도를 측정하고 라벨을 신뢰할 수 있지만, 예산은 두세 배로 불어납니다. 모든 항목에 한 명만 붙이면 같은 돈으로 세 배 많은 데이터에 라벨을 달 수 있지만, 그중 어느 것이 얼마나 믿을 만한지는 전혀 알 수 없습니다.
연구를 진행해 본 사람이라면 누구나 익숙한 통상적인 절충안이 있습니다. 코퍼스의 대부분은 단일 어노테이션을 하고, 품질을 살피기 위해 작은 표본만 이중 또는 삼중으로 어노테이션하는 것입니다. 문제는 늘 그 작업을 도구가 깔끔하게 해내도록 만드는 것, 그리고 중복을 손에 넣은 뒤 그것으로 무언가를 하는 것이었습니다. Potato 2.6은 두 개의 설정 블록(num_annotators_per_item과 per_annotator_quota)에 더해 적응형 부스트와 판정 라우팅을 통해 이 설계를 기본으로 내장합니다.
이 글은 단순한 경우에서 적응형 경우까지 커버리지 설계를 차례로 짚어 봅니다. 전체 레퍼런스는 이종 커버리지 문서에 있습니다.
Potato의 적응형 어노테이터 커버리지
중복 표본을 곁들인 항목별 상한
num_annotators_per_item은 균일한 상한으로 단일 정수를 받기도 하고, 항목마다 다르게 커버하고 싶을 때는 구조화된 매핑을 받기도 합니다. 흔한 형태는 기본값을 1로 두고 층화 표본을 3으로 올리는 것입니다.
num_annotators_per_item:
default: 1
overlap_sample:
fraction: 0.1
count: 3
stratify_by: domain
seed: 42
min: 1overlap_sample 블록은 결정론적으로 선택된 항목의 부분집합에 대해 상한을 올립니다. 표집은 시작 시 한 번만 일어나며, 선택된 항목에는 내부적으로 표시가 찍혀 그 이후로 배정 로직이 이를 고커버리지로 취급합니다. 필드는 명료합니다. fraction은 표집되는 비율, count는 올라간 상한(기본값보다 커야 합니다), seed는 재시작을 넘나들며 선택을 재현 가능하게 만듭니다.
곱씹어 볼 만한 세부 사항은 stratify_by입니다. 이를 데이터의 한 필드(여기서는 domain)로 향하게 하면, 비율이 풀 전체가 아니라 층별로 적용됩니다. 모든 범주가 중복 표본에 비례적으로 기여하므로, 우연히 90%가 한 도메인인 표본 위에서 일치도를 측정하는 일이 없습니다. 코퍼스가 뉴스, 소셜 미디어, 임상 텍스트를 뒤섞고 있다면, 각각이 그 규모에 비례해 품질 표본에 나타납니다.
적응형 부스트: 어려운 곳에 더 쓰기
고정된 중복 표본은 누구도 아직 아무것도 어노테이션하지 않은 상태에서, 깜깜이로 선택됩니다. 그러나 두 번째와 세 번째 검토가 가장 절실한 항목은 어노테이터들이 실제로 불일치하는 항목이며, 그것이 어떤 것인지는 첫 패스 이후에야 알게 됩니다. 적응형 부스트가 바로 그 점을 처리합니다.
num_annotators_per_item:
default: 1
adaptive:
enabled: true
disagreement_threshold: 0.5
boost_to: 3어떤 항목이 적어도 두 건의 어노테이션을 얻고 그 불일치 점수가 disagreement_threshold를 넘어서면, 상한이 boost_to로 올라가고 그 항목은 또 한 번의 패스를 위해 배정 큐로 다시 들어갑니다. 부스트는 항목당 한 번뿐이라, 논쟁적인 항목은 끝없이 부풀어 오르는 대신 한 차례 격상됩니다. 이는 데이터의 난이도를 미리 추측하는 대신, 그 난이도를 따라가는 커버리지입니다.
어노테이터별 할당량
커버리지 상한은 각 항목이 몇 명의 어노테이터를 받는지를 제어합니다. 별개의 블록은 각 어노테이터가 몇 개의 항목을 받는지를 제어하는데, 보통은 전문성이나 계약에 따라 달리하고 싶은 부분입니다.
per_annotator_quota:
default: 100
by_user:
alice: 30
by_user_role:
expert: 30
novice: 200
user_roles:
alice: expert
carol: novice해석은 가장 구체적인 것부터 진행됩니다. 먼저 by_user[uid], 다음 by_user_role[user_roles[uid]], 마지막으로 default입니다. 따라서 특정 전문가 한 명을 30개 항목으로, 다른 모든 전문가는 역할에 따라 30개로, 초심자는 200개로 제한할 수 있으며, 이 두 체계는 위의 항목별 상한과 서로 간섭하지 않습니다.
중복을 결정으로 바꾸기
중복을 모으는 것은 일의 절반일 뿐이며, 핵심은 불일치에 대해 행동하는 것입니다. 판정 블록을 활성화하면, 상한에 도달한 중복 표본 항목이 자동으로 채점되고, 일치도가 임계값 아래로 떨어지면 판정 큐로 밀려 들어갑니다.
adjudication:
enabled: true
adjudicator_users: [admin]
min_annotations: 2
agreement_threshold: 0.75그 효과로, 일치도가 낮은 항목은 누군가 큐를 손으로 다시 만들기를 떠올리기를 기다리는 대신, 표본이 포화되는 바로 그 순간에 떠오릅니다. 판정자가 큐를 열면, 모두가 동의한 다수에서 이미 걸러진, 정말로 다투어지는 항목들이 눈에 들어옵니다.
일치도 읽기
중복 표본 항목이 포화되면, 일치도 통계를 /admin/iaa에서 볼 수 있습니다. 이 엔드포인트는 모든 것에 하나의 숫자를 강요하는 대신, 각 스킴의 유형에 맞는 지표를 계산합니다. 명목형 스킴에는 Cohen's와 Fleiss' kappa, 순서형에는 weighted kappa, span에는 토큰 수준 kappa에 더해 span F1을 씁니다. 이것이 중요한 이유는, 순서가 있는 Likert 평정을 순서 없는 범주인 양 계산한 κ는 실제 일치도를 과소평가하기 때문입니다.
직접 해보기
실행 가능한 시연이 릴리스에 함께 제공됩니다. 리포지토리 루트에서:
python potato/flask_server.py start examples/advanced/heterogeneous-coverage/config.yaml -p 8000이는 두 도메인에 걸친 20개 항목을 사용하고, 도메인으로 층화하여 20%를 세 어노테이터 중복용으로 표집하며, 0.5 임계값에서 적응형 부스트를 활성화하고, 두 개의 전문성 등급을 정의하며, 일치도가 낮은 항목을 판정으로 라우팅합니다. 위의 설계 전체를, 처음부터 끝까지 담은 것입니다.
좋은 커버리지 계획의 모습
이를 한데 모으면, 이 설계는 어노테이션 예산을 고르게 펼치는 대신 어디에 쓸지를 결정하게 해 줍니다. 대부분의 항목은 한 번 거칩니다. 층화된 한 조각은 세 번 거치므로, 코퍼스의 한 귀퉁이가 아니라 전체에 대해 신뢰도를 보고할 수 있습니다. 정말로 어려운 것으로 드러난 항목은 자동으로 격상되고, 다투어진 것은 판정자에게 라우팅됩니다. 가장 불확실한 데이터에 가장 많이 쓰게 되며, 모든 커버리지 결정을 방법(methods) 섹션에서 방어할 수 있습니다.
주어진 과제에 실제로 몇 명의 어노테이터가 필요한지는 그 자체로 별개의 물음입니다. 어노테이터가 몇 명 필요한가 글이 경험 법칙을 차근차근 짚어 줍니다. 이번 릴리스의 핵심은, 당신이 어떤 답에 다다르든 그것을 표현하기 쉽게 만드는 데 있습니다. 이종 커버리지는 Potato 2.6에 포함되어 제공됩니다. 위의 블록들이 할 수 있는 모든 것은 이종 커버리지 문서와 작업 배정 레퍼런스를 참고하세요.