Prostate cANcer graDe Assessment (PANDA) 는 전립선 생검 WSI에서 Gleason grading을 자동화하는 Kaggle 대회다. 참가자는 슬라이드 전체에 대한 ISUP grade(0~5) 를 예측하고, 성능은 Quadratic Weighted Kappa(QWK) 로 평가했다.
이번 글은 대회 개요와 함께, 1등팀 Team PND가 공개한 Google Slides(Rist Kaggle Workshop, 2020-09-24, 발표: fam_taro) · Discussion write-up · GitHub를 함께 읽고 “어떻게 이 문제를 풀었는지” 를 정리한다.
1. PANDA 챌린지가 무엇인가
대회 목표
- 병리 슬라이드(WSI) 한 장에서 전립선암 유무와 ISUP grade group을 예측
- Gleason pattern(3, 4, 5) 조합 → Gleason score → ISUP 1~5로 환산하는 임상 워크플로를 AI로 보조하는 것이 목표
- PANDA Challenge (Kaggle)
데이터·규모 (요약)
| 항목 | 내용 |
|---|---|
| 이미지 | 최대 약 1만 장 규모의 H&E 염색 생검 WSI (radboud, karolinska 등 다기관) |
| 라벨 | 슬라이드 단위 ISUP grade (암 없음 = 0) |
| 특징 | TMA가 아니라 실제 진단용 생검 전체 슬라이드 |
| 검증 | 대회 후 Nature Medicine 등에서 다기관 외부 검증 수행 |
| WSI 해상도 | level 0·1·2 피라미드 (대략 16× / 4× / 1× 다운샘플). 많은 참가자가 level 1 사용 |
| 테스트 | 약 940장, Public:Private ≈ 42:58 (Public 약 395, Private 약 545) |
라벨·주석자 이슈 (슬라이드 강조)
슬라이드는 공식 문서의 주석 체계를 근거로 radboud train 라벨 노이즈를 반복해서 짚는다.
| Karolinska | Radboud | |
|---|---|---|
| Train | 전문의 1명 | 진단 리포트 기반 학생 판독 |
| Test | 전문의 3명 | 전문의 3명 |
Radboud에서 학생이 test를 맞춘 경우 Acc 0.720, QWK 0.853 수준이라, train 라벨이 test 기준보다 불안정할 수 있다는 해석이 PND 접근(기관별 denoise)의 배경이 된다.
또 train에 동일 생검·다른 슬라이스 중복이 500~1,500장 추정(discussion)된다. 호스트는 train에만 해당한다고 했으나 중복 ID 목록은 제공하지 않았다.
Grand Challenge PANDA와 Google Research 블로그에도 동일 취지가 정리되어 있다. 2020년 4~7월 Kaggle에서 진행되었고, 1,010팀 규모의 code competition이었다 (GPU 제출 6시간, 인터넷·커스텀 패키지 불가).
평가 지표: Quadratic Weighted Kappa
ISUP grade는 순서형(ordinal) 라벨이다. 단순 accuracy보다, 틀린 정도에 가중을 두는 QWK가 메인 지표로 쓰였다.
- 예: 2등급을 3으로 예측 vs 2를 5로 예측 → 후자가 더 큰 패널티
- 대회 초반 10일 안에 일부 팀이 내부 검증에서 pathologist 수준(κ 약 0.90 근처)에 도달했다는 보고가 있다 (Nature Medicine 논문)
약한 지도 학습(Weak supervision)
픽셀 단위 Gleason 마스크 없이 슬라이드 라벨만으로 학습하는 방식이 상위권 공통 패턴이다.
- WSI에서 조직이 많은 타일(패치) 를 여러 장 샘플링
- CNN으로 타일 특징 추출
- concat / pooling 으로 슬라이드 단위 표현을 만든 뒤 ISUP grade 분류
Nature Medicine 논문에서는 이를 “concatenate tile pooling”류 접근으로 묶어 설명한다. PANDA 1등 코드도 같은 계열이다.
2. Team PND 1등 결과와 공개 자료
Team PND 멤버: arutema47, fam_taro, poteman (GitHub: @yukkyo, @kentaroy47). 슬라이드 기준 대회 중 Public 22위(0.910) → 최종 Private 1위(0.940).
| 구분 | 링크·내용 |
|---|---|
| 대회 | Prostate Cancer Grade Assessment |
| Write-up | 1st Place Solution [PND] (Discussion #169143) |
| 슬라이드 | RistKaggleWorkshop_20200924_PANDA_1st |
| 코드 | kentaroy47/Kaggle-PANDA-1st-place-solution |
단계별 점수 (슬라이드 “Our solution: Summary”)
| 단계 | Public | Private |
|---|---|---|
| Model 0 (노이즈 제거용 B1, 원본 라벨) | 0.882 | 0.936 |
| Model 1 (arutema, B0, 클린 라벨) | 0.900 | 0.934 |
| Model 1 + Model 2 앙상블 (최종) | 0.904 | 0.940 |
README 재현 노트: 시드 고정 시 private 0.939, 라벨 클리닝만 적용한 5-fold 단순 모델은 private 0.935(3위 수준).
코드는 CC-BY-NC 4.0이며, 이후 Nature Medicine 등 후속 논문에서 인용되었다.
TOP 4 Solution (Kaggle Discussion)
이 글은 1등(PND) 파이프라인을 중심으로 정리했지만, 2~4등 write-up도 같은 대회 맥락에서 함께 보면 좋다. PND 슬라이드에서는 shake-up이 작았던 상위팀(2, 4, 6, 11위 등)이 라벨 노이즈 제거에 공을 들였다고 짚는다.
| 순위 | 팀(제목 기준) | Discussion |
|---|---|---|
| 1st | PND | 1st Place Solution [PND] |
| 2nd | Save the Prostate | 2nd Place Solution [Save the Prostate] |
| 3rd | (write-up 제목: 3rd place solution) | 3rd place solution |
| 4th | NS Pathology | 4th place solution [NS Pathology] |
2~4등 세부 아키텍처·점수는 각 Discussion 본문을 기준으로 확인하면 된다. (Kaggle 페이지는 로그인 후 읽는 것이 안전하다.)
3. 1등 파이프라인 한눈에 보기
GitHub README의 재현 순서는 대략 “노이즈 라벨 제거 → 깨끗한 라벨로 재학습 → 두 종류 모델 앙상블” 이다.

핵심은 Model 0으로 라벨 클리닝 → Model 1·2만 최종 추론에 쓰는 2단계 구조다. 슬라이드는 총 3개 모델만 만들었다고 명시한다.
슬라이드 5단계 요약:
- imghash 유사도 ≥ 0.9 인
image_id는 같은 fold에 배치 (networkx로 그룹핑, imagehash 노트) - 원본(노이즈 포함) 라벨로 학습
- OOF 예측 vs 원본 라벨 gap이 큰 샘플 제거
- 클린 라벨로 재학습
- Model 1 + Model 2 앙상블
3-1. 상위권 공통 베이스라인 (슬라이드 “Basic approach”)
많은 팀이 참고한 public kernel:
타일링 요지:
(tile_size, tile_size)격자 분할 후, 타일별 픽셀 합으로 조직 많은 순 정렬- 예: tile 256 × 36타일 → 입력 약 1536×1536
- bin label: ISUP 2 →
[1,1,0,0,0], ISUP 4 →[1,1,1,1,0](순서형을 multi-hot으로) - TTA: tile mode, h/v flip, transpose
공개 LB 0.87대를 노렸으나 재현이 어려웠다는 코멘트도 슬라이드에 있다.
3-2. Local CV는 높은데 Public LB는 낮았던 이유
슬라이드 제목 그대로 “Why Local CV ≠ Public LB?” 가 PND 설계의 출발점이다.
| 요인 | 설명 |
|---|---|
| 중복 이미지 | 같은 생검이 다른 fold에 들어가면 CV가 과대평가 |
| 라벨 노이즈 | Radboud CV 1.0에 가까워도 Public LB는 radboud에서 ~0.85 수준 보고 |
| 작은 test set | Public/Private 모두 수백 장 → QWK 변동 큼 |
| shake-up | Private가 Public보다 점수가 높게 나온 팀 다수 |
PND 가설: denoise가 (1) 잘못된 라벨과 (2) 어려운 샘플을 함께 제거 → 쉬운 예제에 강하고 어려운 예제에 약한 모델 → Private(상대적으로 easy 비중↑)에서 유리했을 수 있다.
상위권(2, 4, 6, 11위 등)은 대체로 noise reduction을 했다고 슬라이드에서 짚는다.
4. 데이터 준비: 중복 슬라이드·K-fold·타일 PNG
4.1 중복 이미지 그룹핑 (선택)
- perceptual hash로 유사 슬라이드를 묶는 스크립트: imagehash grouping 노트
- 저장 예:
input/duplicate_imgids_imghash_thres_090.csv - README에서는
input에 결과를 넣어 두고 스킵 가능하다고 안내
4.2 5-fold 분할
cd src
python data_process/s00_make_k_fold.py
- 고정 seed →
input/train-5kfold.csv
4.3 학습용 타일 PNG 생성
python data_process/s07_simple_tile.py --mode 0
python data_process/s07_simple_tile.py --mode 2
python data_process/a00_save_tiles.py
| 설정 | 값 (README) |
|---|---|
| 타일 개수 | 64 (8×8 그리드) |
| 타일 크기 | 192 px |
| resolution level | 1 |
| mode | 0과 2 두 가지 (패딩/오프셋 방식 차이) |
출력 디렉터리 예: numtile-64-tilesize-192-res-1-mode-0, ...-mode-2
제출 노트북의 get_tiles는 WSI에서 배경(흰색)을 제외하고 조직이 많은 타일을 고르는 로직이다. 패딩·mode에 따라 타일 시작 위치가 달라져, 같은 슬라이드도 서로 다른 뷰를 학습에 넣을 수 있다.
# submitted_notebook.ipynb 요지
def get_tiles(img, tile_size, n_tiles, mode=0):
pad_h = (tile_size - h % tile_size) % tile_size + ((tile_size * mode) // 2)
# ... 타일 분할 후 픽셀 합이 작은(조직 많은) 순으로 n_tiles 선택
실습에서 tifffile/OpenSlide로 패치를 직접 잘라 본 경험과 연결하면, 대회 1등팀은 오프라인에서 PNG 타일을 미리 뽑아 두고 학습 속도를 올린 구조라고 보면 된다.
5. 1단계: Base 모델로 “라벨 노이즈” 찾기
5.1 Base 학습 (final_1, EfficientNet-B1)
python train.py --config configs/final_1.yaml --kfold 1 # 1~5
- fold당 약 18시간 (Titan RTX 1장 기준)
- 출력:
output/model/final_1/
5.2 Hold-out 예측
python kernel.py --kfold 1 # 1~5
- 각 fold 검증셋 예측 →
local_preds~~~.csv
5.3 노이즈 제거 스크립트
python data_process/s12_remove_noise_by_local_preds.py
생성 CSV 예:
| 파일 | 용도 |
|---|---|
local_preds_final_1_efficientnet-b1.csv |
hold-out 전체 예측 |
..._removed_noise_thresh_16.csv |
Model 1 학습용 (기본 클리닝) |
..._removed_noise_thresh_rad_13_08_ka_15_10.csv |
Model 2 학습용 (radboud 라벨 추가 제거) |
최종 제출에 쓴 fold CSV 예: train-5kfold_remove_noisy_by_0622_rad_13_08_ka_15_10.csv
라벨 클리닝 규칙 (슬라이드 수치)
OOF 예측 ISUP와 원본 라벨 차이(gap)가 threshold를 넘으면 제거.
- 예: 예측 4.1 vs 라벨 4 → gap 0.1 → 유지 / 예측 0.5 vs 라벨 4 → gap 3.5 → 제거
| 용도 | gap threshold | 제거 비율 | 제거 수 (Total / Rad / Ka) |
|---|---|---|---|
| Model 1 (arutema) | 1.6 | 5.6% | 596 / 445 / 151 |
| Model 2 (fam_taro) | 기관·등급별 (radboud 약 20% 목표) | 14.0% | 1,488 / 1,153 / 335 |
Ablation (Model 2 계열): denoise 전 Public 0.892 / Private 0.916 → denoise 후 0.901(+0.009) / 0.932(+0.016).
6. 2단계: 클린 라벨로 두 모델 재학습
Model 0 — fam_taro (노이즈 탐지 전용, 슬라이드 Model 0)
- EfficientNet-B1, 원본 라벨, 5-fold
- OOF 예측만으로 클린 CSV 생성 (최종 제출에는 미사용)
Model 2 — fam_taro (EfficientNet-B1, GeM pooling)
python train.py --config configs/final_2.yaml --kfold 1
python train.py --config configs/final_2.yaml --kfold 4
python train.py --config configs/final_2.yaml --kfold 5
- fold 1, 4, 5만 최종 inference에 사용 (LB 성능 좋은 fold 선별)
- fold당 약 15시간
- 제출 노트:
final_2_efficientnet-b1_kfold_{k}_latest.pt - 타일: 64 × 192 × 192 (입력 약 1536×1536), GeM pooling, cosine 30 epoch
- 학습 시 ISUP + 첫 번째 Gleason 점수 동시 예측 → 출력 10차원 (예: 3+4 → 1st gleason 3). 추론에는 ISUP만 사용
- Public LB 상위 fold 3개만 학습·추론 (제출 시간 제한)
Model 1 — arutema47 (EfficientNet-B0, avgpool, tile 36)
train_famdata-kfolds.ipynb(또는.py)- 타일 36 × 256 × 256, avg pooling, bin label, mixup·cutout, cosine 20 epoch
- 더 큰 backbone(ResNeXt 등)은 overfitting으로 기각
- 가중치 예:
efficientnet-b0famlabelsmodelsub_avgpool_tile36_imsize256_mixup_final_epoch20_fold*.pth
두 모델은 백본·타일 수·pooling·보조 타깃이 달라 오류 상관이 낮은 앙상블을 노린 구성이다.
7. 추론·앙상블 (Kaggle Notebook)
submitted_notebook.ipynb 흐름 요약:
- 테스트 WSI마다 OpenSlide로 타일 추출 (
get_tiles→concat_tiles로 8×8 몽타주) - Model 2 (EfficientNet-B1, GeM) 예측
- Model 1 5-fold EfficientNet-B0 예측 평균/결합
- 기관별 처리·pseudo label threshold 등 Config로 미세 조정
- 최종 ISUP grade 제출
로컬 검증 시 karolinska / radboud를 나눠 QWK를 출력하는 코드도 포함되어 있다 (기관별 성능 갭 확인용).
재현용 커널 예:
- late-famrepro-fam-reproaru-ensemble-0725 — private 0.939
- latesub-pote-fam-aru-ensemble-0722 — private 0.940
학습된 가중치는 repo의 ./final_models에 포함되어 있다.
8. 슬라이드 “Why did we win?”·결론
Google Slides 결론 파트 요약:
- Private test가 Public보다 easy 비율이 높았을 가능성 — denoise 후 easy sample에 강한 모델이 유리
- imghash 기반 fold — 중복 슬라이드가 fold를 넘나들지 않게 해 CV·denoise 안정화. seed만 바꿔도 점수가 크게 흔들리는 팀이 있었던 이유로, random split leakage를 지적
- 공식 문서의 주석자(annotator) 정의를 읽는 것이 의료 영상 과제에서 중요 — “Train annotator가 Test를 얼마나 맞추는지”를 보면 라벨 신뢰도를 가늠할 수 있다
팀 회고(슬라이드): CV와 LB가 불안정할 때 라벨 정의를 더 일찍 의심했어야 했다, PyTorch Lightning seed 설정 미숙지, imghash clustering(17위 솔루션)도 유용해 보였다.
효과 없었던 시도 (Appendix)
- denoise 전 Mixup/CutMix만으로는 한계
- NMS·K-means 기반 타일링 등 다른 타일 방식
- Karolinska ↔ Radboud CycleGAN
- segmentation head 부착 분류 (FP32 이슈)
- Class balanced loss, first-gleason loss weight 0.5
- CleanLab — 당시(2020.09) 분류 라벨만 지원, denoise에는 참고만
Discussion #169143은 GitHub README가 공식 write-up으로 연결하는 스레드다.
9. 재현 시 체크리스트 (GitHub README 기준)
- Docker 권장:
docker/build.sh→run.sh→exec.sh train_images,train_masks다운로드- (선택) imagehash 중복 그룹 /
train-5kfold.csv - 타일 PNG 생성 (mode 0, 2)
final_15-fold 학습 →kernel.py→s12_remove_noisefinal_2(fold 1,4,5) +train_famdata-kfolds- Kaggle Dataset에 가중치 업로드 후
submitted_notebook.ipynb경로 수정
일부 중간 산출물은 input/에 이미 있어 스킵 가능하다고 README에 명시되어 있다.
마치며
- PANDA는 전립선 생검 WSI에서 ISUP grade를 맞추는 대규모 병리 AI 챌린지이며, QWK와 weak supervision이 표준 설정이다.
- Team PND 1등은 타일 기반 CNN + 라벨 클리닝 2단계 + 이종 EfficientNet 앙상블로 private 0.940을 기록했다.
- 실무적으로는 OpenSlide EDA로 기관·등급 분포를 본 뒤, 타일 샘플링·fold·노이즈 라벨 전략까지 연결하는 흐름이 자연스럽다.
- 워크샵 슬라이드는 README보다 Local CV vs LB, annotator, denoise 수치·ablation 설명이 풍부하다.
- 다음에는
s07_simple_tile/get_tiles로직을 직접 따라 하거나, 클린 라벨 CSV만 바꿔 소형 EfficientNet baseline을 돌려 보는 것도 좋다.
참고
대회·1등 자료
- Kaggle - Prostate Cancer Grade Assessment
- Team PND Solution Slides (Google Slides)
- GitHub - Kaggle-PANDA-1st-place-solution
TOP Solution (Discussion)
대회·연구 배경
- Nature Medicine - PANDA challenge paper (GitHub README “Used in”)
- Grand Challenge - PANDA
- Google Research - PANDA challenge blog
'AI·머신러닝 > 딥러닝·비전' 카테고리의 다른 글
| 전립선암 WSI EDA - Gleason·ISUP, OpenSlide 패치·마스크 시각화 (0) | 2026.04.28 |
|---|---|
| 의료영상 신호처리와 MONAI - Spatial·Frequency, X-ray·CT·MRI, 전처리 실습 (0) | 2026.04.23 |
| 병리 WSI 실습 - OpenSlide, read_region, 피라미드 레벨 (0) | 2026.04.23 |
| 메디컬 이미지 NIfTI 실습 - DICOM montage, 변환, 3D 슬라이스 GIF (0) | 2026.04.21 |
| 메디컬 이미지 - 일반 이미지 차이, DICOM·NIfTI·WSI, pydicom 실습 (0) | 2026.04.20 |