Post

RSNA 무릎 ① — 0.944에서 멈춰 선 이유를 찾기까지: 잣대부터 만들고 축을 하나씩 닫았다

RSNA 무릎 ① — 0.944에서 멈춰 선 이유를 찾기까지: 잣대부터 만들고 축을 하나씩 닫았다

RSNA Knee Abnormality Detection 대회를 진행하면서 어떤 순서로 무엇을 했는지를 단계별로 정리했습니다. 결론부터 적으면, 지금 public LB 0.944(4,753팀 중 230위)에서 더 올라가지 못하고 있고, 이번 주에 왜 못 올라가는지를 숫자로 특정했습니다. 병목은 모델이 아니라 라벨이었습니다.

대회 진행 중이라 코드와 대회 이미지는 싣지 않습니다. 이 글에서 ‘실측’은 다른 조건을 그대로 두고 한 가지만 바꾼 비교로 확인한 값이고, 나머지는 ‘추정’이라고 적었습니다.

0. 한 장 요약

항목값
지표12개 소견에 대한 macro ROC-AUC (순위만 의미, 유병률 불변)
우리 최고public 0.944 · 4,753팀 중 230위
1위0.961 · 0.957 이상 19팀
공개 노트북 천장0.943 — 여기 727팀이 몰려 있다
우리 모델의 순기여+0.003 (멤버를 끈 제출 0.941 vs 켠 0.944, 은닉 테스트셋 실측)
이번 주 발견판독문 유래 라벨의 천장이 0.9032 — 우리가 만지던 축들은 전부 그 아래였다

1. 단계 ①: 점수를 올리기 전에 잣대부터 만들었다

처음에 가장 많이 한 실수는 잡음을 신호로 읽은 것이었습니다. 두 번 크게 당했습니다.

  • 2 fold 에서 +0.0031 을 보고 “첫 양성 결과”라고 적었는데, 4 fold 로 채우니 +0.0006 이었습니다.
  • 2 fold 에서 −0.0058 을 보고 “이 축은 중요하다는 것이 확정됐다”고 적었는데, 4 fold 에서 −0.0036, 바닥 안이었습니다. 같은 실수를 교훈을 적어둔 뒤에 또 했습니다.

그래서 설정을 바꾸는 대신 잡음 바닥을 직접 쟀습니다. 다른 모든 것을 고정하고 난수 시드만 바꾼 복제 실험을 돌렸습니다.

1
2
3
4
시드만 다른 복제 실험
  fold 간 표준편차        0.0027
  4-fold 95% 신뢰구간     ±0.0043
  평균 차이               +0.0013   ← 아무것도 안 바꿨는데 이만큼 나온다

더 중요한 것은 study 단위 부트스트랩이 불확실성을 2.9배 과소평가한다는 사실이었습니다. 부트스트랩은 study 를 다시 뽑을 뿐 fold 를 다시 뽑지 않기 때문입니다. 여기서 세 가지 규칙이 나왔고, 이후 모든 판정에 적용했습니다.

  1. 점수는 통합값에서, 구간은 fold 수준에서 낸다.
  2. fold 4개 미만이면 판정하지 않는다 — 부호조차 읽지 않는다.
  3. 기대 효과가 0.005 미만이면 애초에 실험을 돌리지 않는다. 재도 안 보이고 LB 해상도(0.001)에서도 안 보인다.

이 잣대가 이후 모든 결정의 기준이 됩니다. 그리고 이 잣대 때문에 대부분의 아이디어가 기각됐습니다.


2. 단계 ②: 축을 하나씩 닫았다

“무엇을 해볼까”가 아니라 “무엇이 죽었는지 확정하자” 로 방향을 바꿨습니다. 살아 있는 가설을 줄여야 남은 시간을 어디에 쓸지 정해지기 때문입니다.

축결과판정
추론 설정서로 다른 구성 10개 이상이 전부 0.944평지
멤버 가중치0배 0.941 · 1배 0.944 · 2배 0.942역U자, 현재가 꼭짓점
모델 개수12모델 = 9모델 = 0.944포화
모델 용량+0.0017바닥 미만
입력 슬라이스 12→24+0.0013 [−0.0008, +0.0040]바닥 미만
계열 다양성 (후보 9개)최고 +0.0004, 최악 −0.0172무효/유해
좌우반전 증강−0.0064 (좌우 구분이 있는 6개 소견 전패)유해
해상도 320→4480무효
사이트 조건부 순위 보정오라클 유병률을 줘도 +0.0008바닥의 1/5
슬롯 보조손실 풀링완주본에서 +0.0003바닥의 1/14
학습률 2배448 +0.0038 · 320 +0.0032 → 통합 +0.0035 [+0.0014, +0.0056], 7/8 fold유일한 확정 이득

닫는 과정에서 배운 것 두 가지를 적어둡니다.

(1) 판정 도구가 보지 못하는 축이 있다. 우리 판정 도구는 사이트 내부 가중 macro 를 씁니다. 그런데 이 지표는 사이트별 점수 이동에 구조적으로 불변입니다. 즉 “사이트 조건부 보정” 같은 가설은 그 도구에 원리적으로 보이지 않습니다. 이걸 깨닫고 전역 지표로 따로 쟀고, 멤버 선택 결정 전체도 전역 지표로 다시 판정했습니다 — 다행히 결론은 같았습니다.

(2) 천장부터 재면 싸게 끝난다. 사이트 보정은 “유병률이 사이트마다 최대 4배 다르다”는 사실에서 출발해 꽤 유망해 보였습니다. 그래서 추정기를 만들기 전에 정답 유병률(오라클)을 줬을 때의 상한을 먼저 쟀습니다. 상한이 +0.0008 이었고, 거기서 끝냈습니다. 현실 추정기는 오라클보다 못하니 만들 이유가 없습니다.


3. 단계 ③: 잃지 않는 일에도 시간이 든다

제출 한도는 하루 5장입니다. 빈 슬롯은 그 자체로 손실이라 자동 제출기를 돌리는데, 이번 주에 그 경로에서 버그를 세 개 잡았습니다. 셋 다 조용히 슬롯을 날리는 종류였습니다.

  • 슬러그를 문자열로 조립하다 보니, 기반이 다른 커널 하나가 존재하지 않는 이름으로 조회되고 있었습니다.
  • 가드가 커널 로그를 find … | head -1 로 하나만 골랐는데, 산출물에 로그가 5개인 커널에서 엉뚱한 파일을 집어 “흔적 없음”으로 건너뛸 참이었습니다. 하필 그 장이 그날 유일하게 상승 여지가 있는 제출이었습니다.
  • 융합 영수증의 weights 필드를 “적재한 모델 수”로 읽고 있었는데, 실제로는 데이터셋에 들어 있는 파일 목록이었습니다. 즉 그 가드는 원래 막으려던 사고를 못 막습니다.

두 번째는 제가 손으로 확인했을 때는 통과했습니다. 저는 로그 파일을 직접 지정해 봤고, 가드는 그렇게 고르지 않았기 때문입니다. 그래서 규칙을 하나 더 만들었습니다 — 점검은 도구가 실제로 하는 그대로 해야 한다. 제출 없이 가드만 돌려보는 예행 스크립트를 만들고 나서야 잡혔습니다.

비슷한 함정이 하나 더 있었습니다. 체크포인트 파일의 존재를 “학습 완주”로 읽으면 안 됩니다. best.pt 는 검증 점수가 개선될 때마다 쓰이므로 학습 도중에도 존재합니다. 이걸 완주 표지로 쓴 탓에 18 에폭 중 5~6 에폭짜리 모델로 판정이 돌았고, 완주본으로 다시 재니 1·2위가 뒤집혔습니다. 완주 표지는 예측 산출물 쪽이어야 합니다.


4. 단계 ④: 왜 안 오르는지 — 라벨이 천장이었다

여기가 이번 주의 핵심입니다.

우리 파이프라인은 판독문 → LLM 교사 → 약라벨 → 영상모델 입니다. 대회가 주는 정답은 58건뿐이고, 나머지 4,407건은 판독문에서 뽑은 약라벨로 학습합니다. 그런데 정답 58건을 들여다보니 이런 숫자가 나왔습니다.

1
2
3
4
5
6
양성 셀의 52.1% 는 판독문에 단서가 아예 없다

  판독문 유래 교사의 천장       0.9032
  제3자가 같은 텍스트를 재판독  0.8348   ← 텍스트에서 더 짜낼 것이 없다
  공개 노트북 최고              0.922
  1위                           0.952 (당시)

임상 판독문은 임상적으로 중요한 것만 적습니다. 반면 대회 정답은 12개 소견을 빠짐없이 체계적으로 판독한 것입니다. 애초에 다른 작업입니다.

그러니까 제가 2절에서 닫은 축들 — 학습률·슬라이스·풀링·해상도·사이트 보정 — 은 전부 이 천장 아래에서의 미세조정이었습니다. 그래서 하나같이 측정 바닥 근처에 몰려 있었던 것입니다. 축을 잘못 고른 게 아니라, 축이 있는 층이 잘못됐습니다.

그럼 더 좋은 라벨을 구하면 되지 않나

공개된 LLM 라벨셋이 여럿 있어서 전수로 받아 정답 58건에 대조했습니다. 전부 CC0 입니다.

라벨셋정답 58건 macro AUC
우리 약라벨0.9055
공개 A (blend 판)0.8927
공개 A (v2)0.8873
공개 A (full)0.8780
공개 B0.8700
공개 C (최신 상용 모델)0.8352

정직하게 읽으면, 우리 우위 0.0128 은 n=58 의 잡음 안입니다(이 데이터에서 승자의 저주 +0.0107, 후보 폭 0.0068). 그러니 “우리가 더 낫다”가 아니라 “더 나쁘지 않다” 가 맞는 결론입니다. 다만 최신 상용 모델로 만든 공개 C 의 −0.0703 은 잡음 밖이라 확실히 열위입니다. 모델이 새롭다고 라벨이 좋아지지 않습니다.

더 중요한 것은 분포입니다. 서로 독립인 다섯 팀의 LLM 라벨이 전부 0.835~0.893 에 몰려 있습니다. 제3자 재판독 실측 0.8348 과 같은 자리입니다.

판독문 경로는 커뮤니티 전체가 같은 벽에 부딪혀 있다. 따라서 0.957 이상인 19팀은 더 좋은 판독문 라벨로 이기는 것이 아니다.

라벨셋 간 차이를 4,407건 전체에서 보면 정보 차이가 아니라 보정 차이였습니다. 어떤 셋은 우리가 음성으로 본 셀 3,968개를 양성이라 하고(훨씬 관대), 다른 셋은 우리가 양성으로 본 셀 5,496개를 음성이라 합니다(훨씬 보수적). 같은 텍스트를 다르게 자를 뿐, 없는 정보가 생기지는 않습니다.


5. 지금 서 있는 자리와 다음 수

숫자를 정직하게 늘어놓으면 이렇습니다.

  • 공개 기반만으로 0.941~0.943, 거기에 우리 모델이 +0.003 을 얹어 0.944.
  • 멤버 가중치 반응곡선이 현재값에서 꼭짓점이므로, 지금 모델 품질에서는 더 세게 섞어도 손해입니다.
  • 남은 확정 수단(학습률 멤버 교체 등)을 다 더해도 +0.003~0.005 규모입니다.
  • 1위까지는 0.017 입니다.

그래서 남은 선택지는 사실상 하나로 좁혀집니다 — 판독문 밖에서 신호를 가져오는 것. 구체적으로는 전문의가 영상을 직접 보고 단 병변 라벨을 보조 과제로 함께 학습시키는 방향입니다. 공개된 무릎 MRI 병변 주석 중에는 16,167개의 병변 상자가 달린 것이 있고, 우리 12개 소견 중 10개에 대응이 있습니다(활액막염만 대응이 없습니다). 대응이 거친 항목 — 예를 들어 반월상 연골이 내측/외측으로 안 나뉜 것 — 은 상자의 좌표로 가를 수 있고, 그 공간 정보는 판독문 라벨이 원리적으로 가질 수 없는 종류입니다.

반대 증거도 같이 적어둡니다. 같은 데이터를 초기화로만 썼을 때는 앙상블 기여가 +0.0004, 사실상 무효였습니다. 초기화는 미세조정에 씻겨 나가고 co-training 은 학습 내내 신호를 유지한다는 점에서 기전이 다르지만, 사전 확률이 낙관적이지만은 않다는 뜻입니다.

그리고 이 경로에는 기술 외적인 선행 조건이 있습니다. 라벨은 MIT 로 자유롭지만 원본 영상은 별도의 데이터 사용 계약에 묶여 있습니다. 상금이 걸린 대회에 쓸 가중치를 그 영상으로 학습하는 문제라, 공식 등록 절차를 밟을지부터 정해야 합니다. 기술적 타당성과 별개의 결정입니다.


6. 이번 대회에서 다시 확인한 것들

이 대회에서 반복해서 되돌린 판단들을 남겨둡니다. 전부 제가 실제로 틀렸던 것들입니다.

  • fold 2개로는 아무것도 말하지 않는다. 부호조차. 두 번 어기고 두 번 철회했습니다.
  • AUC 를 읽기 전에 양성·음성 개수를 본다. 어떤 소견의 AUC 0.250 을 “역상관”이라 적었는데 양성이 1건이었습니다.
  • 감사하려는 대상을 다시 구현하지 않는다. fold 배정 함수를 직접 다시 짜서 비교했다가 88.8% 누수라는 허위 경보를 봤습니다. 실제 누수는 0% 였고, 제가 짠 쪽이 틀렸습니다.
  • 로그는 거짓말을 한다. 영수증을 본다. 로그에 찍힌 파라미터 값이 포맷 문자열에 하드코딩된 상수인 경우가 있었습니다.
  • 요약 줄 위를 읽는다. 어떤 판정 로그는 마지막 줄이 “판정 완료”인데 바로 위에 예외가 찍혀 있었습니다. 그 축은 사흘간 판정된 적이 없었습니다.
  • 기본값과 하드코딩은 다르다. 오류 메시지가 기본값 경로를 찍은 걸 보고 “하드코딩이라 고쳐야 한다”고 적었는데, 그 스크립트는 처음부터 인자를 받고 있었습니다.

다음 글에서는 영상 유래 라벨 경로를 실제로 돌려본 결과를 적겠습니다.

This post is licensed under CC BY 4.0 by the author.