왜 처음부터 이것을 하는가?
저는 꽤 오랫동안 Kaggle에 열광해왔습니다. 대회가 가져다주는 순수한 즐거움과 긴장감에 푹 빠져 지내왔지만, 그 과정에서 레벨업하거나 배움의 기회를 크게 얻지 못했습니다. 왜 그럴까요? 이유는 간단합니다: Kaggle에 시간을 충분히 할애할 수 없었기 때문에 지름길만 찾으려 했습니다. Kaggle 대회에 참여하는 것이 그 자체로 풀타임 직업처럼 느껴진다는 말을 종종 들었고, 정말 그 말이 사실임을 느꼈습니다. 배경을 조금 설명하자면, 저는 평범한 사람으로 풀타임 직장을 다니며 가족의 책임도 지고 있습니다. 가끔씩 생기는 짧은 시간에 저는 Kaggle 대회에 손을 대보곤 했습니다. 지금까지 저의 방식은 흥미를 느끼는 대회를 찾고, 그 대회의 공개 커널/노트북을 가능한 한 최대한 수정해 보는 것이었습니다. 이 방식을 계속 반복했지만, 솔직히 아무런 성과도 없었습니다—당연한 결과였죠! 이 시점에서 저는 Kaggle에 대한 접근 방식을 바꿔야 한다고 깨달았습니다. 많은 Kaggle 마스터들과 그랜드마스터들이 블로그나 튜토리얼을 통해 양질의 조언을 제공하고 있지만, 그들 중에는 종종 시간과 자원이 부족한 초보자나 열정가가 대회를 통해 무엇을 겪었고, 어떤 문제를 겪었으며, 어떻게 좌절을 극복했는지에 대한 생생한 경험이 빠져있었습니다. 예를 들어, 무엇이 잘못되었는지? 무엇이 효과가 없었는지? 좌절을 어떻게 극복했는지? 이런 경험들이 빠져 있죠. 그래서 이 블로그 글을 쓰게 되었습니다: 저는 몇 가지 규칙을 세우고, "올바른" 방식으로 대회에서 얼마나 멀리 갈 수 있는지 시도해 보기로 했습니다.
규칙들 역사적으로, 제가 Kaggle 대회에서 더 잘하지 못한 두 가지 변명은 여유 시간이 부족하다는 것과 전용 GPU가 없다는 것이었습니다. 그런데 이것이 과연 정확한 이유였을까요? 이것이 사실인지 알아보기 위해, 대회에 참가하면서 저 자신에게 다음과 같은 규칙을 설정했습니다:
- 하루에 단 1시간만 일하기 (총 60일 동안). 여기서 1시간은 모델을 학습시키는 시간이 아니라 코딩, 분석 등에 능동적으로 사용한 시간을 의미합니다.
- 하루를 놓치면 어쩔 수 없습니다. 그 시간은 사라지는 것이고, 이후에 추가로 보충할 수 없습니다.
- 모든 단계를 기록하고 문서화하며, 참조를 수집하고 적절한 경우 공을 돌리기
- 기존 커널을 맹목적으로 수정하거나 재사용하지 않기 (특히 상위 점수를 받은 커널은 더욱)
- 계산 자원으로는 Kaggle 커널이나 Google Colab만 사용하기
- 현재 위치와 전략을 평가하기 위해서만 공개 리더보드를 사용하기
- 대회의 규칙을 철저히 준수하기
- 토론 포럼을 사용하기
- 충분한 휴식을 취하기
즉, 공개적으로 사용 가능한 계산 자원만 사용하여 60시간의 능동적 시간을 대회에 투자하는 것입니다.
대회 선택 장시간 작업하고 싶은 유혹이나 압박을 피하기 위해, 저는 이미 종료된 대회를 선택했습니다. 이번에는 혹등고래 식별 챌린지를 선택했죠. 왜냐하면 이 대회는 도전적이면서도 공개적으로 사용 가능한 계산 자원으로 데이터셋 크기 면에서 관리 가능한 수준이었기 때문입니다. 또한, few-shot 분류에 대해 배울 기회도 제공했죠. 이 챌린지의 기본 개념은 겉으로 보기에 단순합니다: "fluke"(기본적으로 고래의 꼬리 부분) 이미지로부터 고래를 식별하는 것이죠. 평가 기준은 Mean Average Precision @5 (mAP@5) 점수입니다. 기본적으로, 모델의 상위 5개 추측에 해당 고래가 포함되어 있다면 보상을 받지만, 모델의 순위가 실제 정답과 유사하게 정렬되어 있을수록 더 많은 보상을 받습니다. 따라서 예측의 순서가 중요합니다. 이 챌린지를 선택한 후, 저는 일일 진행 상황과 앞으로 해야 할 일을 기록하기 위한 저널을 만들기로 했습니다.
설정 다섯 단계로 구성된 저의 설정은 다음과 같습니다:
- 계산 자원: Google Colab + Kaggle 커널 (주당 약 30시간의 GPU 시간. 코딩 시간이 아님!)
- 저널링: Notion 페이지 (위의 GIF 참고). 이곳에 액티브 할 일 목록, 일일 저널 섹션, 참고자료 섹션을 유지했습니다.
- 소프트웨어 자원: PyTorch, FastAI(결국 사용함), 그리고 Weights & Biases
- 많은 ☕
- 포모도로 스타일 타이머: 매일 60분이 되면 멈추도록 설정.
과정 이 섹션에서는 매주 어떤 시도를 했는지, 배운 점, 결과 및 관찰자로서 흥미로울 수 있는 것들을 설명하려고 합니다. 대체적으로, 저의 개인적인 도전은 아홉 주 동안 지속되었으며, 사실상 마지막 주는 단 3일만 진행되었습니다. 각 주마다 가능한 간결한 할 일 목록과 그날의 작업을 기록한 일일 저널이 있었습니다. 타이머는 매일 60분이 지나면 멈추게 해주었습니다. 매주 끝날 때마다, 그 주의 할 일 목록에서 완료한 것을 체크하고, 완료하지 못한 것은 다음 주로 이월했습니다. 그 과정에서 놀라울 정도로 많은 것들을 시도했으며, 지금 되돌아보면 시간을 더 잘 활용할 수 있었을 거란 생각도 듭니다 (다들 그렇지 않나요?).
1주차 - 탐색적 데이터 분석 (Exploratory Data Analysis): 첫 주에는 간단하게 시작하려 했습니다. 주요 목표는 데이터를 불러오고, W&B에 로그를 남기고, 데이터를 분석하여 유용한 인사이트를 얻는 것이었습니다. 초기에는 PyTorch와 Pandas 등을 사용해 파이프라인을 처음부터 작성하고자 했습니다. 결국 이 연습의 목표가 학습이 아니었나요? 초기의 낙관에 힘입어 데이터 로더와 로그 훅을 설정하는 작업을 진행했습니다. 추가적으로 데이터를 더 잘 이해하기 위해 몇 가지 실험도 진행했습니다.
예를 들어, 위의 이미지들은 학습 이미지 중 무작위 샘플입니다. 바로 알 수 있는 것은 이미지들이 다양한 포즈에서 촬영되었고, 때로는 흑백이며, 텍스트가 들어 있기도 하고 항상 선명하지는 않다는 것입니다. 또한, 데이터셋은 극도로 불균형하다는 것을 알았습니다. 5005개의 클래스 중 하나는 9,000장이 넘는 이미지를 가지고 있는 반면, 일부 클래스는 단 하나의 이미지만 가지고 있었습니다. 이를 통해 다음과 같은 선택지를 생각해볼 수 있었습니다:
- 샘플 수가 적은 클래스는 버리기 (시도했으나 오히려 상황이 악화됨)
- 블러, 포즈, 종횡비 등을 고려하기
- 흑백 이미지를 별도로 처리하기
- 데이터셋을 증강을 통해 리밸런스하기 (최종적으로 실행한 방법)
- 'new_whale' 클래스를 제거하고 이 클래스를 예측할 다른 방법 사용하기 (최종적으로 실행한 방법)
2주차 & 3주차 - 파이프라인 구축 및 데이터셋 정리: 다음 몇 주 동안 저는 바닥에 주저앉아 순수한 PyTorch를 사용해 학습 및 추론 파이프라인을 작성했습니다. 주요 계산 자원이 Kaggle과 Colab이었기 때문에 Jupyter 노트북에 작업을 작성한 후, 셀들을 스크립트로 리팩토링해야 했습니다. 이 접근 방식에는 장점과 단점이 있었습니다. 우선, 모든 것을 철저히 테스트하고 변경 사항을 깔끔하게 Git 리포지토리에 유지할 수 있었습니다. 또한, 이러한 함수들 중 일부는 미래의 대회에서도 재사용할 수 있었습니다. 그러나 Kaggle 커널과 Colab만 사용했기 때문에 Git 리포지토리와 통합하고 변경 사항을 관리하기가 매우 어려웠습니다 (적어도 대회를 시도할 당시에는 그랬습니다). 또 하나의 어려운 점은 하루에 한 시간밖에 없다는 것과 스크립트와 노트북 간의 이동 시간이 누적되어 결국 시간 낭비가 되는 것이었습니다. 시간을 최대한 활용하기 위해, 어쩔 수 없이 Jupyter 노트북 사용에 전념했습니다. 세 가지 주요 인사이트는 다음과 같습니다:
- 전체 학습 세트로 학습된 모델은 거의 항상 'new_whale'을 추측 중 하나로 예측했습니다. 왜 그럴까요? 데이터에 이 클래스가 9천 개 이상의 예제로 포함되어 있었고, 다른 클래스에 비해 지나치게 많았습니다. 그래서 모델은 무작정 검증 및 테스트 세트의 거의 모든 예제에 대해 이 클래스를 예측하기 시작했습니다 (공개 mAP@5 0.541). 이것은 토론 포럼에서도 흔한 경향으로 보였기 때문에, 군중의 지혜를 활용하여 모든 'new_whale' 이미지를 제거했습니다. 추론 시간에는 모델의 신뢰도 점수에 대한 임계값을 사용하여 주어진 예제가 'new_whale'인지 확인했습니다 (이 부분은 이후 더 설명할 예정입니다).
- 층화된 K-fold 교차 검증(CV)을 사용하고 싶다면 샘플 수가 적은 클래스들을 처리해야 했습니다. 일부 클래스는 이미지가 단 한 장뿐이었기 때문입니다. 이를 위해, 5개 미만의 이미지를 가진 모든 클래스를 제거한 뒤 K-fold CV를 사용해보았습니다. 하지만 이는 점수를 크게 악화시켰습니다 (공개 mAP@5 0.452). 토론 포럼에서 고래 주변의 좁은 영역을 탐지하고 배경을 생략하는 방법으로 객체 탐지기를 사용하는 여러 게시글이 있었습니다. 조사 끝에 Martin Piotte의 훌륭한 노트북을 발견했고, 이 노트북은 이를 수행하기 위해 모델을 훈련시켰습니다. 대회가 몇 년 전에 진행된 것을 고려하여 이 노트북을 업데이트하여 제대로 작동하도록 했고, 고래를 탐지하고 이미지를 잘라내는 모델을 훈련했습니다. 이로 인해 점수가 0.486으로 향상되었습니다.
- 낮은 샘플 수의 클래스를 제거하는 대신, Albumentations를 사용하여 데이터 증강을 통해 샘플 수가 적은 클래스의 샘플 수를 늘렸습니다. 이 방법은 상당한 개선을 가져왔고, 단 20 에포크의 학습으로 제 모델은 mAP@5 점수 0.611을 기록했습니다.
정리하자면, 저의 데이터 전처리 과정은 다음과 같습니다:
- 모든 'new_whale' 이미지를 학습 세트에서 제거했습니다.
- 학습된 바운딩 박스 탐지기 모델을 사용하여 나머지 모든 이미지를 잘라냈습니다 (참고: 테스트 이미지도 잘라내어 모델이 배경 장면에 놀라지 않도록 했습니다 😅).
- 20개 미만의 이미지를 가진 모든 클래스를 최소 20개의 이미지로 증강했습니다.
- 층화된 5-fold 교차 검증(CV)을 사용했습니다.
4주차 & 5주차 - 정체된 진전과 디버깅:
쉬운 접근 데이터 전처리가 어느 정도 확립된 후, 다음으로 빠르게 실험을 시도하며 가장 큰 성과를 얻을 수 있는 지점을 찾는 데 집중했습니다. "공정한" 비교를 위해, 학습을 20 에포크로 고정하고 각 설정을 모든 폴드에서 평가했습니다. 예를 들어, Resnet-18, Resnet-34, Resnet-50, VGG-16 (물론 모두 ImageNet으로 사전 학습된 모델들) 등의 다양한 아키텍처를 시도했습니다. 여러분은 왜 최신의 훌륭한 모델을 사용하지 않았는지 궁금할 수 있습니다. 유한한 계산 자원과 제한된 시간 (~Kaggle에서 약 9시간, Colab에서 약 12시간) 때문에, 합리적인 배치 크기로 메모리에 적합한 모델만 사용해도 실험에서 의미 있는 결과를 얻을 수 있었습니다. 또한, 다양한 손실 함수(일반적인 교차 엔트로피, focal loss 등)와 이미지 크기(224, 384, 512)도 시도했습니다. 다음은 이러한 실험을 통해 얻은 결과입니다:
- 이미지 크기는 mAP@5가 이미지 크기가 증가할 때마다 점진적으로 상승하며 큰 영향을 미쳤습니다. 따라서 모든 실험을 가장 작은 크기(224)로 시도하고, 마지막에는 더 큰 크기로 모델을 재훈련하여 점수를 높이기로 했습니다.
- 모델 아키텍처는 더 큰 모델(예: Resnet-50)이 작은 모델보다 성능이 더 좋으며 작지만 여전히 중요한 역할을 했습니다.
- 흥미롭게도, 보통 클래스 불균형 문제에 사용하는 focal loss는 전혀 도움이 되지 않았습니다.
원하는 목록 이러한 실험 외에도 mixup, label smoothing, progressive resizing과 같은 기술들을 시도해 추가로 도움이 되는지 확인하고 싶었습니다. 상위 점수의 대부분이 앙상블의 결과임을 고려할 때, 나중에 앙상블할 다양한 모델들을 확보하고자 했습니다. 포럼에서는 메트릭 학습과 대조 학습(contrastive learning)에 대한 많은 논의가 있었으며, 시간이 허락한다면 이 방향도 시도해보고 싶었습니다.
예기치 않은 시간 낭비 탐색할 유망한 방향이 있었음에도 불구하고 두 가지로 인해 방해를 받았습니다.
첫째: 제가 구현한 대회 메트릭이 지나치게 낙관적이었습니다. 예를 들어, 공개 점수가 0.54일 때 제가 예측한 CV 점수는 0.8이었습니다. 이는 코드와 CV 분할에 대한 의문을 불러일으켰습니다. 둘째 문제는, 어떤 시도를 해도 공개 점수가 0.7을 넘지 않는다는 점이었습니다. 반면에 토론 포럼의 많은 게시글에서는 제가 시도한 조합으로도 가능하다고 언급하고 있었습니다. 첫 번째 문제의 경우, 공개 커널과 GitHub에서 두 가지 더 나은 메트릭 구현을 찾았고 (감사합니다 Radek!), 이를 사용하여 CV 점수를 더 의미 있게 만들었습니다. 두 번째 문제의 경우, 증강 후 이미지를 플롯해보았고 패딩 선택으로 인한 여러 아티팩트가 발생했다는 것을 발견하고 충격을 받았습니다. 아래는 아티팩트를 수정하기 전후의 두 세트의 이미지입니다.
이로 인해 저는 상당한 후퇴를 경험하게 되었으며, 이제 데이터셋을 재생성하고 그 위에 이전 실험들을 다시 시도하여 제 결과가 유효한지 확인해야 했습니다. 이 두 주 동안의 나머지 시간은 제 발견을 검증하고 데이터셋을 수정하는 데에만 할애할 수 있었습니다 😣.
6주차 & 7주차 - FastAI의 구원: 좌절을 극복하고 나니, 앞서 언급한 유용한 트릭들을 시도하고 더 큰 해상도의 이미지로 최종 모델을 재훈련하는 어려운 과제가 남아 있었습니다. 이 시점에서 저는 몇 가지 어려운 결정을 내려야 했습니다. 메트릭 및 대조 학습의 추구를 포기하고 순수 분류 모델을 개선하는 데만 집중하기로 했습니다. 또한, 제가 직접 몇 가지 복잡한 트릭을 구현하고 테스트하고 사용하는 데 걸릴 시간이 너무 많을 것이라는 것도 알았습니다. 그래서 FastAI를 활용하여 이번 챌린지의 남은 부분을 진행하기로 결정했습니다. FastAI 라이브러리에 대해 들어본 적이 없는 극소수의 여러분을 위해 설명하자면, FastAI는 Pytorch 위에 구축된 프레임워크로, TensorFlow의 Keras와 유사합니다. 학습과 추론을 단순화하는 고급 추상화를 제공할 뿐만 아니라, 다양한 기법(예: mixup, label smoothing, progressive resizing, 가중치 동결 및 해동 등)을 매우 쉽게 사용할 수 있게 구현해 놓았습니다. 이 결정을 내리고 "Deep Learning for Coders" 책과 함께, 저는 feverishly (열정적으로) 모델을 개선하려 노력했습니다. 몇 시간 만에 저는 FastAI에서 제 기본 파이프라인을 복제하고, 모델 디버깅을 위해 필요한 모든 요소들을 통합했습니다. 새로운 데이터는 훨씬 더 나아졌으며, 20 에포크의 학습 후 모든 5개의 폴드에서 점수가 0.72-0.73으로 향상되었습니다. 다음은 이 두 주 동안 시도한 사항들의 요약입니다:
- Label smoothing (생각만큼 큰 도움은 되지 않음)
- Mixup (저의 경우 오히려 상황이 악화됨)
- Progressive resizing (최종적으로 사용하게 됨)
- Test time augmentation (도움되지 않음)
- Discriminative learning rates (또한 최종적으로 사용하게 됨)
아직 언급하지 않은 한 가지는 'new_whale' 클래스를 어떻게 예측했는가 하는 것입니다. 이제 이를 설명하겠습니다. 모든 'new_whale' 클래스의 인스턴스를 제거했기 때문에, 모델은 추론 시 이 클래스를 예측하지 않았습니다. 이를 다시 예측에 포함시키기 위해, 예측의 softmax 점수를 가이드로 사용했습니다. 상위 5개의 예측과 그 softmax 점수를 고려하여, 이 점수들 중 어느 하나가 임계값보다 낮은지 확인했습니다. 만약 그렇다면, 이 임계값보다 낮은 점수 바로 전에 'new_whale' 클래스를 예측으로 추가했습니다. 이 전략은 토론 포럼에서 제안된 것이지 제가 직접 고안한 것이 아님을 알려드립니다. 최적의 임계값을 찾기 위해 각 폴드에 대해 그리드 검색을 수행했고, 이 값을 최종 예측에 사용했습니다. 이 두 주가 끝날 때쯤, 제 최종 점수는 20 에포크 학습 후 384 x 384 해상도에서 0.8이었습니다.
8주차 & 9주차 - 마지막 카운트다운: 마지막 두 주 동안 저는 더 긴 학습을 진행하고 모델의 해상도를 224 x 224에서 512 x 512로 점진적으로 키우는 데 집중했습니다. 이 과정은 수많은 시행착오를 거쳤고, 결국 제 경우 최적의 접근 방식은 384 x 384 해상도에서 65 에포크를 학습한 후 512 x 512 해상도로 미세 조정하는 것이었습니다. 여기에는 많은 수고로운 체크포인트 설정이 필요했습니다. 시간 초과 문제 없이 384 x 384 해상도에서 모델을 65 에포크 동안 학습시키기 위해서는 특정 유형의 GPU를 Colab에서 얻어야 했는데, 이는 항상 가능한 일이 아니었습니다. 그래서 GPU에 따라 학습할 에포크 수를 선택하고, 그 모델을 체크포인트로 저장해 손실되지 않도록 했습니다. 그런 다음 가중치를 복원하고 남은 에포크를 학습했습니다. 번거롭고 귀찮은 방법이지만 어쩔 수 없었습니다. 이제 이 과정을 모든 5개의 폴드에 대해 반복하고, 각 폴드를 512 x 512에서 5 에포크 동안 미세 조정하면 지금의 제 상태가 되는 것입니다. 이 과정이 끝날 무렵, 제 최고의 모델(공개 리더보드 기준)은 위에서 언급한 대로 5-fold 층화 CV로 학습된 Resnet-34였습니다. 저는 이 모델과 이 모델의 최고의 단일 폴드를 대회의 최종 제출물로 제출했습니다. 이 시점에서 저는 지쳤지만, 도전에 끝까지 매달려 완수한 것에 대해 뿌듯했습니다.
결과 다음은 제 최고의 점수입니다:
- 최고의 단일 폴드 결과:
- 5개 폴드의 평균 결과:
가상 리더보드 순위: 258/2120 -> 상위 12.16%, 동메달까지 단 0.012 포인트 차이로 놓침
배운 점 매일 조금씩 꾸준히 투자한 시간이 어떻게 누적되어 저를 보드 상위로 올려줬는지 보고 정말 기뻤습니다. 비록 제가 이 도전을 시도했을 때는 이미 종료된 대회였지만, 대회에 참가하는 동안 여전히 경쟁의 흥분을 느꼈습니다. 제가 다르게 했어야 한다고 느끼는 점은 다음과 같습니다:
- 주당 7시간의 유연성을 가졌어야 했습니다. 매일 한 시간씩 사용하기보다는 제가 적절하다고 생각하는 대로 사용했어야 했습니다. 창의성과 동기 부여는 한 번에 몰려오는 경우가 많았고, 더 오래 작업하고 싶은 날도 많았지만 제가 스스로 정한 규칙 때문에 그럴 수 없었습니다. 반대로, 지치고 대회에 몰입할 의욕이 없는 날도 있었습니다.
- 데이터 처리 과정을 훨씬 더 철저히 검증했어야 했습니다. 이 때문에 몇 시간을 디버깅에 소비했고, 제 경우 이는 매우 비쌌습니다. 이를 일찍 했더라면, 더 많은 것들을 시도할 시간이 있었을 것이고, 아마도 점수를 더 올릴 수 있었을 것입니다.
- FastAI와 같은 라이브러리는 신의 선물입니다. 이들은 프로토타이핑을 순식간에 가속시켜줍니다. 하지만, 최대의 결과를 얻기 위해서는 라이브러리를 잘 배우는 데 시간 투자를 해야 한다는 대가가 따릅니다.
- 얼마나 많은 코드를 처음부터 작성할 것인지 미리 고려했어야 했습니다. 학습과 높은 점수 사이의 균형은 어려운 문제이며, 하루에 한 시간만 쓸 수 있었고 Colab과 Kaggle 커널을 사용한 상황에서는, 대회를 위해 스크립트로 이루어진 자체 리포지토리를 만들기보다는 다른 접근 방식을 취했어야 했습니다.
마지막으로 말씀드리고 싶은 것은, Kaggle 커널과 Google Colab은 개인 워크스테이션이나 클라우드 설정이 없는 많은 사람들에게 무료로 계산 자원을 제공합니다. 하지만 무료인 데에는 이유가 있습니다. 이들은 실험과 연구를 장려하기 위한 것이지 대회용으로 사용하기 위한 것이 아니라고 생각합니다. 시간 초과 없이 모델을 훈련하고 결과를 추론할 수 있도록 하는 것은 힘든 과정이었습니다. 또한, 더 큰 모델(EfficientNet 등)을 훈련하는 것도 매우 어려웠습니다. 메모리에 맞추기 힘들기 때문에 보통 이미지 해상도나 배치 크기를 조정해야 몇 에포크라도 훈련할 수 있었기 때문입니다. Kaggle에서 우승하고 싶다면, 이런 자원들을 사용할 수는 있지만 얼마나 집요하고 결단력이 있는지에 따라 다릅니다. 저처럼 제한된 시간을 가진 사람들에게는 확실히 큰 어려움입니다.
마지막 생각 이 도전은 저를 진정으로 밀어붙였고, Kaggle과 관련하여 배우고 다시 배울 수 있도록 도와주었습니다. 앞으로는 이번에 스스로 설정한 규칙들의 좀 더 유연한 버전을 향후 Kaggle 대회에 채택하려 합니다. 이 도전을 직접 시도해 보고 싶다면, 제가 만든 데이터셋(많은 Kagglers 덕분입니다)을 여기 공개해 두었습니다. 행운을 빌며, 포스가 여러분과 함께 하기를!
'개인용' 카테고리의 다른 글
neurips-day1 (2) | 2024.12.11 |
---|---|
Time-Series-Library (0) | 2024.12.01 |
Hailuo AI minimax (0) | 2024.11.26 |
wonderai (0) | 2024.11.26 |
luma-ai (0) | 2024.11.26 |