세그멘테이션 모델이 문제가 아니라 레이블에 문제일 수 있습니다: Python을 이용한 어노테이션 품질 측정
요약
세그멘테이션 모델의 성능 저하가 모델 자체의 문제가 아닐 수 있으며, 레이블링 과정에서 발생하는 '레이블 합의(Label Agreement)' 문제일 가능성이 높습니다. 본 글은 Python 코드를 통해 두 명 이상의 어노테이터 간의 데이터 불일치 정도를 측정하는 방법을 안내합니다.
핵심 포인트
- 모델 성능 저하 원인으로 레이블 품질을 의심해야 합니다.
- Inter-annotator agreement (IAA)는 레이블링 지침의 모호성을 파악하는 핵심입니다.
- Dice 계수와 IoU 같은 중첩 지표 외에 HD95로 경계 불일치를 측정할 수 있습니다.
- Python 코드를 활용하여 어노테이터 간 마스크 비교 및 정량적 측정이 가능합니다.
의료 영상 데이터셋으로 세그멘테이션 모델을 훈련했습니다. Dice 점수가 정체되고, 검증 결과가 노이즈가 많으며, 일부 사례는 설명할 수 없을 정도로 잘못된 것처럼 보입니다.
학습률(learning rate)을 건드리기 전에 다른 질문을 던져보세요. 즉, 두 명의 숙련된 어노테이터가 이 레이블에 대해 서로 동의할까요?
만약 그렇지 않다면, 모델은 존재하지 않는 목표를 맞추도록 요청받고 있는 것입니다. 본 게시물에서는 몇 줄의 Python 코드로 이를 측정하는 방법을 안내합니다.
레이블 합의(Label Agreement)가 중요한 이유
의료 영상 레이블은 인간의 판단입니다. 장기 경계는 흐릿하고, 작은 병변은 가시성 한계 근처에 위치하며, 지침에는 간극이 생깁니다. 모든 어노테이터는 그 간극을 약간씩 다르게 해결합니다.
이러한 변화(variation)는 어떤 모델도 학습할 수 있는 상한선을 설정하며, 측정하지 않으면 눈에 보이지 않습니다.
**Inter-annotator agreement (IAA)**는 독립적인 어노테이터들이 동일한 사례에서 얼마나 근접하게 일치하는지를 정량화합니다. 낮은 합의도는 부주의한 어노테이터를 가리키기보다는 불분명한 어노테이션 지침을 가리키는 경우가 많습니다.
설정 (Setup)
pip install numpy scipy scikit-learn nibabel
두 명의 어노테이터가 만든 이진 마스크(binary masks)를 동일한 모양과 간격으로 NIfTI 파일로 저장했다고 가정합니다.
import numpy as np
import nibabel as nib
def load_mask(path):
img = nib.load(path)
return img.get_fdata() > 0.5, img.header.get_zooms()
mask_a, spacing = load_mask("case001_annotator_A.nii.gz")
mask_b, _ = load_mask("case001_annotator_B.nii.gz")
지표 1: Dice 계수 (Dice coefficient)
Dice는 중첩(overlap)을 측정하며, 0(없음)부터 1(동일함)까지의 값을 가집니다.
def dice(a, b):
a, b = a.astype(bool), b.astype(bool)
total = a.sum() + b.sum()
if total == 0:
return np.nan # 둘 다 비어있을 때: 정의되지 않음, 명시적으로 처리
return 2.0 * np.logical_and(a, b).sum() / total
두 개의 빈 마스크에 대해 np.nan을 반환하는 것은, 조용히 1이나 0을 반환하는 것보다 평균값을 더 정직하게 유지시켜 줍니다.
Metric 2: IoU (Jaccard index)
python
def iou(a, b):
a, b = a.astype(bool), b.astype(bool)
union = np.logical_or(a, b).sum()
if union == 0:
return np.nan
return np.logical_and(a, b).sum() / union
IoU는 동일한 마스크 쌍에 대해 항상 Dice보다 작거나 같으므로 두 숫자를 직접 비교하지 마세요. 둘 중 하나를 대표 지표로 선택하고 일관성을 유지하세요.
Metric 3: 95th percentile Hausdorff distance
겹침(Overlap) 측정 지표는 괜찮아 보일 수 있지만 경계가 특정 지점에서 여전히 다를 수 있습니다. 표면 거리(Surface distance)가 이를 포착합니다. 95번째 백분위수 버전(HD95)은 최악의 5% 이상치(outliers)를 무시하므로, 원본 최대값보다 더 안정적입니다.
python
from scipy.ndimage import distance_transform_edt, binary_erosion
def surface(mask):
return mask & ~binary_erosion(mask)
def hd95(a, b, spacing=(1.0, 1.0, 1.0)):
a, b = a.astype(bool), b.astype(bool)
if not a.any() or not b.any():
return np.nan
sa, sb = surface(a), surface(b)
dist_to_b = distance_transform_edt(~sb, sampling=spacing)
dist_to_a = distance_transform_edt(~sa, sampling=spacing)
distances = np.concatenate([dist_to_b[sa], dist_to_a[sb]])
return np.percentile(distances, 95)
sampling=spacing이 전달되므로 결과는 복셀 단위가 아닌 실제 세계 단위(일반적으로 밀리미터)로 나옵니다.
Metric 4: 분류 레이블에 대한 Cohen's kappa
어노테이터들이 범주를 할당하는 경우(예: 병변별 양성 대 의심), 우연히 예상되는 일치도를 보정해 주는 카파(kappa)를 사용하세요.
python
from sklearn.metrics import cohen_kappa_score
rater_a = ["benign", "suspicious", "benign", "suspicious", "benign"]
rater_b = ["benign", "suspicious", "suspicious", "suspicious", "benign"]
kappa = cohen_kappa_score(rater_a, rater_b)
print(f"Cohen's kappa: {kappa:.2f}")
세 명 이상의 어노테이터의 경우 Fleiss' kappa (statsmodels에서 사용 가능) 또는 Krippendorff's alpha를 확인하세요.
모든 어노테이터 쌍 비교
python
from itertools import combinations
def pairwise_report(masks, spacing):
masks: {"A": ndarray, "B": ndarray, "C": ndarray}
rows = []
for x, y in combinations(masks, 2):
rows.append({
"pair": f"{x}-{y}",
"dice": dice(masks[x], masks[y]),
"iou": iou(masks[x], masks[y]),
"hd95_mm": hd95(masks[x], masks[y], spacing),
})
return rows
전체 그룹에서 눈에 띄는 한 쌍은 종종 한 어노테이터가 규칙을 다르게 해석했음을 의미합니다. 이는 대화할 가치가 있는 교육 또는 지침상의 문제입니다.
어노테이터 간의 불일치 지점 찾기
단일 수치는 유용한 세부 정보를 숨깁니다. 슬라이스별 Dice(Slice-wise Dice)는 어느 부분에서 의견 불일치가 발생하는지 보여줍니다.
python
def slicewise_dice(a, b, axis=2):
scores = {}
for i in range(a.shape[axis]):
sa = np.take(a, i, axis=axis)
sb = np.take(b, i, axis=axis)
if sa.any() or sb.any():
scores[i] = dice(sa, sb)
return scores
per_slice = slicewise_dice(mask_a, mask_b)
worst = sorted(per_slice.items(), key=lambda kv: kv[1])[::5]
print("Lowest-agreement slices:", worst)
불일치는 일반적으로 구조물의 첫 번째 및 마지막 슬라이스, 즉 해부학적 시작점과 끝 지표가 중요한 부분, 그리고 인접 장기와 경계면에서 집중되는 경향이 있습니다. 이러한 패턴은 어떤 규칙을 강화해야 하는지 정확히 알려줍니다.
수치 해석하기
만능의 "좋은" 점수는 없습니다. 다음 값들은 대략적인 방향 설정용으로만 사용하십시오:
| Metric | Rough reading |
|---|---|
| Dice | ~0.90 이상: 강력함; 크고 잘 정의된 장기에 흔함 |
| ~0.70 ~ 0.90: 합리적임; 구조물 크기와 대비에 따라 다름 | |
| 0.70 미만: 지침 및 교육 검토 필요 | |
| Kappa | 0.80 이상: 거의 완벽한 일치 |
| 0.61 ~ 0.80: 상당함 (Substantial) | |
| 0.41 ~ 0.60: 보통 (Moderate) |
주의할 점 두 가지가 있습니다:
작은 구조물과 병변(lesions)이 큰 장기들보다 점수가 낮은 경우가 있는데, 이는 심지어 숙련된 어노테이터를 사용하더라도 한 픽셀의 이동만으로도 중첩 영역(overlap)이 크게 변하기 때문입니다. 따라서 전역적인 하나의 임계값(global threshold)을 설정하는 대신 레이블별로 목표치를 설정해야 합니다. 합의(Agreement)가 정확도(Accuracy)는 아닙니다. 어노테이터들은 동일한 잘못된 관행에 대해 합의할 수 있습니다. 따라서 임상 검토(Clinical review)는 여전히 필요합니다.
측정 결과를 개선으로 전환하기
효과적인 간단한 루프:
- 파일럿 배치(pilot batch, 몇십 건의 케이스)를 이중 어노테이션(Double-annotate) 합니다.
- 위에서 언급된 지표들을 레이블별, 그리고 어노테이터 쌍별로 계산합니다.
- 합의도가 낮은 슬라이스 및 케이스를 검사합니다.
- 가이드라인을 명확히 하고, 예시를 추가하며, 엣지 케이스 파일(edge-case file)에 결정 사항을 기록합니다.
- 어노테이터들을 재교육하고 새로운 샘플로 재측정합니다.
- 운영 과정 중에도 작은 비율만큼 이중 어노테이션을 유지하여 드리프트(drift)를 포착합니다.
어노테이션 가이드라인을 코드처럼 취급하세요: 버전을 관리하고, 검토하며, 테스트해야 합니다.
실용적인 팁
- 비교하기 전에 마스크들이 모양(shape), 간격(spacing), 방향(orientation)을 공유하는지 확인하세요. 조용한 리샘플링 불일치(silent resampling mismatch)는 수치를 망칠 것입니다.
- 모든 어노테이터에게 동일한 전처리 과정(window/level, orientation)을 사용하세요.
- 레이블별 결과를 유지하세요. 좋은 평균값이라도 정의가 미흡한 구조물 하나를 가릴 수 있습니다.
- 단순히 평균값뿐만 아니라 표본 크기(sample sizes)와 변화량(variation)과 함께 지표를 보고하세요.
- 식별 가능한 환자 데이터는 공용 저장소, 노트북 또는 스크린샷에 절대 공유하지 마세요.
마지막 생각
모델 성능이 보통 주목받지만, 레이블 품질이 상한선을 결정합니다. 몇 백 줄의 측정 코드는 여러분의 데이터가 학습 준비가 되었는지, 아니면 다음 개선 사항이 아키텍처(architecture)가 아닌 가이드라인에서 나와야 하는지를 알려줄 수 있습니다.
저는 GitHub 저장소에 오픈 SOP, QC 체크리스트, 합의 템플릿을 모아두었으니 자유롭게 사용하고 개선해 보시기 바랍니다: medical-image-annotation-guidelines
저는 Pareidolia Systems LLP 팀에서 근무하며, 의료 이미지 어노테이션, 세그멘테이션 및 헬스케어 AI 팀을 위한 품질 관리를 수행합니다. 만약 여러분이 어노테이터 간의 의견 불일치(annotator disagreement)를 다루는 방식이 다르다면, 댓글로 어떻게 하는지 듣고 싶습니다.
Github-
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기