dukim's blog

[WK05] P-Stage level 1 마스크 이미지 분류 대회 도전기 본문

Boostcamp AI Tech 2th

[WK05] P-Stage level 1 마스크 이미지 분류 대회 도전기

eliza.dukim 2021. 9. 6. 23:12

Intro

8월 23일 ~ 9월 2일(약 2주) 동안 진행된 이미지 분류 대회가 끝났습니다.
해당 프로젝트의 진행 과정을 시간 순서대로 서술하면서 겪은 시행착오, 시도한 문제해결 전략, 결과에 대한 내용을 담았습니다.
결과적으로는 23등으로 그리 좋은 성적은 얻지 못 하였지만 팀원들과 합이 잘 맞았고, 팀원 개개인이 많이 배우고 성장할 수 있었던 프로젝트라 이렇게 정리해봅니다.


1. 프로젝트의 목표

다음 단계를 위한 기초 체력 다지기

물론 리더보드 상위권을 찍지 못한 것에 대한 비겁한 변명이라해도 할 말 없다. 그렇지만 우리 팀은 프로젝트 시작 전부터 점수 보단 과정에, 모두의 성장에 초점을 맞추자고 합의를 보았다. 지금이 아니면 ML Workflow 전체 과정을 꼼꼼히 정리할 기회가 많지 않을 것이며, 팀에 DL이 처음인 사람도 전체 과정을 빠짐없이 경험하게 해주어야 한다는 의견들이 모여 프로젝트의 목표를 학습에 잡고 진행하게 되었다. 요약하면

  • ML Workflow의 각 과정을 겪어보고 정리하기
  • 다음에 재사용할 수 있는 자신만의 템플릿 코드를 갖추기

1. 데이터셋의 문제: 엄청난 불균형 데이터셋

가장 먼저 할 일은 제시된 문제를 파악하고, 데이터셋을 살펴보면서 적절한 접근 방법을 선택하는 것. 그런데 제시된 문제가 좀 이상했다. 얼굴 이미지를 입력받아 마스크 착용여부 뿐만 아니라 성별과 나이까지 예측해야했던 것. 마스크 착용여부 3개 클래스(착용, 미착용, 불량 착용), 성별 2개 클래스, 그리고 나이 3개 클래스(29세 이하, 30세 이상 59세 이하, 60세 이상)의 조합으로 표현되는 18개 클래스를 예측해야했다. 단일 모델이 서로 얽혀있는 레이블의 조합을 잘 해결할 수 있을지에 대한 의문, 각 클래스에 대한 별도의 모델을 만들어 접근해야할 수도 있겠다는 생각을 했다.
또한 EDA를 진행해보니 연령별 분포가 심각하게 깨져있었다. 30세 이상 59세 이하의 그룹에서 대부분이 40대 후반의 데이터로 이루어져있는데다 60세 이상의 데이터가 부족한 상황. 따라서 나이 집단 간의 구분이 제대로 학습되지 않을 것이라 예상하고 이에 대한 해결책이 필요했다.

 

연령별 샘플의 분포

연령별 샘플의 분포

 

 

또한 문제에서 주어진 데이터셋은 메타 데이터만으로 레이블을 직접 만들어야하는 수고로움도 덤. 여차 저차 만들고 보니 너무나 당연하게도 클래스 간 분포도 박살나있었다. 2, 5, 8, 11, 14, 17은 60세 이상에 해당하는 클래스 조합으로 마스크 착용 여부와 성별에 따른 6개 조합의 클래스인데 모두 눈에 띄게 낮은 빈도수를 보인다. 일단은 단순하게 접근해서 클래스 불균형 문제를 다루는 일반적인 방법을 적용하면서 연령 분포에 대한 근본적인 해결책으로 데이터셋 수집을 고려해봐야겠다고 생각했다.

 

 

클래스별 샘플의 분포

클래스별 샘플의 분포

 

2. 모두가 쉽게 실험을 진행할 수 있는 프로젝트 템플릿

8월 23일 월요일. 대회 첫날에 제일 먼저 한 일은 모두가 사용할 수 있는 프로젝트 템플릿을 만들어 팀원에게 배포하는 것이었다. WK3에서 소개된 파이토치 프로젝트 템플릿을 사용하여 대회 데이터셋에 맞게 프로젝트 템플릿 코드를 작성하였고, 다음날인 8월 24일 화요일, 팀원들에게 공유할 수 있었다. 이를 통해 당장 팀원들이 일단 리더보드에 제출하기 위한 학습 절차를 경험해보고, 모델명만 바꾸는 것 만으로 다양한 pretrained model의 학습이 가능하게 했다. 나요한 캠퍼님의 도움으로 실험 가능한 pretraeind model을 추가하고, 클래스 불균형 해결을 위한 cross entropy loss의 class weight를 반영하고, 데이터 부족을 해결하고 모델의 일반화 성능을 높이기 위한 다양한 augmentation 조합을 쉽게 실험하도록 기능을 추가하는 등 계속해서 코드를 업데이트 하였다. 이를 통해 DL을 잘 모르는 팀원도 각 모델의 성능을 측정하고 적절한 Augentation을 탐색하면서 팀에 기여할 수 있도록 하였다.

 

 

각 모델의 기본 성능 테스트 결과를 깃헙 이슈로 체크

각 모델의 기본 성능 테스트 결과를 깃헙 이슈로 체크

 

 

각 모델의 기본 성능 테스트 결과를 깃헙 이슈로 체크

협업의 흔적

 

3. 베이스라인 모델의 선택, 성능저하의 원인 파악

 

잠깐 이야기가 옆길로 새긴 하지만 우리 팀에서 자랑하고 싶었던 점을 조금 남기자면 자유로운 팀 분위기였다. 모두가 템플릿 코드에서 시작하긴 했지만, 각자 시도해보고 싶은 것을 시도하면서, 대신에 Pretrained model의 성능을 측정하는 것만큼은 모두 다같이 하였고, 그 결과 나요한님은 대량의 augmentation 실험을, 이호영님, 최한준님은 템플릿 코드에 대한 학습을, 그리고 한진님과 창한님은 클래스의 종류별(마스크, 나이, 성별) 별도의 모델을 사용하는 멀티 모델을 시도하였다. 그 결과 최종 적용할 Augmenetation 조합과 pretrained model을 선택할 수 있었고, 한진님과 창한님의 멀티모델 시도 과정에서 모델이 30세 이상 59세 이하 집단과 60세 이상 집단의 나이 분류를 제대로 하지 못하는 문제가 있다는 것을 파악하게 되었다. 앞에서도 예견된 문제점이었으나 클래스 불균형에 대해 일반적으로 적용하는 기법인 loss function에 class weight를 이용한 기법(focal loss 포함), oversampling으로 어느 정도 커버 가능할 줄 알았다. 그러나 결국 이러한 기법들로는 해결 불가능하고, 근본적인 해결책으로 데이터셋을 추가해야한다는 결론에 이르렀다.

 

 

최종 선택된 Augmentation과 모델(Vision Transformer, EfficientNet)(나요한 님의 실험결과 채택 각각 LB F1 Score 0.732, 0.7292 기록

최종 선택된 Augmentation과 모델(Vision Transformer, EfficientNet)(나요한 님의 실험결과 채택 각각 LB F1 Score 0.732, 0.7292 기록

 

 

EfficientNet으로 18개 클래스에 대해 학습한 결과 얻어진 각 클래스에 대한 confusion matrix, 빨간 정사각형 안의 각 칸은 나이에 대한 3개의 클래스를 말한다. 오분류 케이스는 주로 나이에 대해 발생하는 것을 확인할 수 있다(추창한님의 분석결과)

EfficientNet으로 18개 클래스에 대해 학습한 결과 얻어진 각 클래스에 대한 confusion matrix, 빨간 정사각형 안의 각 칸은 나이에 대한 3개의 클래스를 말한다. 오분류 케이스는 주로 나이에 대해 발생하는 것을 확인할 수 있다(추창한님의 분석결과)

 

4. 템플릿 코드의 한계: 오히려 빠른 실험을 막는 걸림돌 -> 노트북 파일 병행 체제

8월 30일 월요일, 대회 2주차에 접어들었고, 템플릿 코드에 실험 로깅 툴인 Weights& Biases를 뒤늦게 추가하면서, 오히려 템플릿 코드에 맞게 깔끔하게 작성하려다 실험할 시간을 버리게 되고, 추가 기능을 기다리는 팀원들로 인해 프로젝트에 병목이 발생한다는 것을 인지했다. 따라서 빠른 실험을 위해 주피터 노트북으로 별도의 실험을 진행하려하는 요한님의 방식을 채택하여, 여기에 붙어 새 코드 작성을 도왔다. 템플릿 코드의 각종 로깅을 위한 군더더기가 빠져서 학습에 소요되는 시간도 감소했고, 더 짦은 시간에 더 많은 실험이 가능하게 되었다. 최종 코드에서는 다시 노트북 파일을 템플릿 형태로 변환하였다.

 

5. 추가 데이터셋 수집: MaskTheFace 활용하기

1에서 예견되었고, 3에서 실체를 확인한 이 문제를 해결하려면 데이터셋을 추가로 수집해야했다. 적절한 데이터셋으로 이것을 발견했지만 500GB나 되는 큰 사이즈로 인해 100GB로 용량이 제한된 서버에선 사용하기 어렵다는 점, 그리고 인종 또한 다르기 때문에 오히려 모델의 성능 저하를 가져올 수 있다는 판단에 선택하지 않았다(후에 다른 팀의 이야기를 들어보니 이 데이터셋을 사용하여 성능을 향상시켰다고 들었다).
다른 대안으로 All-Age-Faces(AAF) Dataset과 Mask를 씌운 얼굴을 생성해주는 오픈소스인 MaskTheFace를 사용할 것을 제안하고, 최한준님과 이호영님에게 데이터셋 제작 방법을 노션으로 작성하여 작업을 요청드렸다(링크). 그러나 colab의 최대 파일 생성 개수 문제와 및 라이브러리 기능 파악, 일부 데이터 누락 등의 문제가 생겨 최종적으로 대회에서 제공된 형태와 같은 데이터셋을 얻기 어렵게 되었다. 이를 validation set으로라도 활용하려 하였으나 최종제출 2시간 전에 데이터셋이 완성되어 미처 데이터셋을 활용하지는 못했다.

 

 

AAF Dataset에 MaskTheFace를 적용해 생성한 데이터

AAF Dataset에 MaskTheFace를 적용해 생성한 데이터

 

6. 성능 향상을 위한 시도

데이터셋을 수집하는 와중에도 데이터셋 없이 이룰 수 있는 성능향상은 최대한 얻어내려했다. 이 과정에서 나름 원칙이 하나 있었는데 learning rate나 optimizer의 선택 등의 하이퍼파라미터 탐색은 성능 향상 폭이 크지 않을테니 제일 나중에 하고(돌아보면 이게 패착 중 하나였다), 일반화 성능을 높이거나 데이터셋 불균형으로 인한 문제에 적용하기 좋다고 알려진 다양한 학습 기법들을 적용해보는 것. 박준수님과 나요한님과 함꼐 다음 기법들을 테스트했다. 그러나 어느 모델도 기존 ViT 모델의 LB F1 Score 0.732를 넘지는 못했고, 대신 이 과정에서 3에서 언급한 EfficientNet이 0.7292로 채택되었다. metric learning을 이용한 모델의 경우 0.725의 성능을 보이며 꽤나 좋은 성능을 보였지만 EfficientNet 보다 더 낮은 성능에 사용하기 무거워 채택하지 않았다. Voting의 경우 Hard Voting은 LB 0.7이상을 기록한 모델 10가지를 voting한 것으로 Hard Voting한 결과 0.723의 성능을 기록했다.

  • Feature Engineering(박준수)
    • FaceNet Dropout
  • Augmentation(박준수)
    • Cutmix
  • Ensemble
    • K-Fold Cross validation 후 각 fold의 모델을 Ensemble(Bagging)(나요한)
    • Voting(Soft, Hard)(박준수, 김대웅)
    • Multi-sample dropout(박준수)
    • Test Time Augmentation(박준수)
  • Loss function(김대웅)
    • Cross Entropy Loss with Class Weights
    • Focal Loss
    • Label Smoothing Loss
    • Angular Additive Margin Loss(ArcFace에서 제안된 loss function)
  • Metric Learning(김대웅)
    • cosine similarity based classifier(Arc Face 논문의 방식으로 학습한 모델)
  • Multi-model(한진, 추창한)

 

 

Weights & Biases 를 이용한 실험관리, 사실 이것보다 더 많은 실험을 했지만. 해당 툴 적용이 늦어 여기엔 누락되었다.

Weights & Biases 를 이용한 실험관리, 사실 이것보다 더 많은 실험을 했지만. 해당 툴 적용이 늦어 여기엔 누락되었다.

 

 

실험을 진행하면서 각 인자들의 영향력을 파악하기 위해 나머지 조건을 고정시켜 실험하였어야 했는데 한 번에 여러개의 요소를 바꾸어 실험하다보니 각 인자들이 성능에 끼치는 영향을 제대로 파악하지 못 했던 점이 실수였다. 이후 Angular Additive Margin Loss를 테스트할 때는 loss function에 따른 차이(Label Smoothing Loss, Focal Loss, Cross Entropy Loss)를 비교하는 실험을 수행하였다. 또한 각각의 방법을 적용할 떄, 촉박한 시간으로 인해 코드를 올바르게 작성하고 있는지에 대한 검증이 제대로 이루어지지 않았다. 실험 속도가 조금 느리더라도 신뢰할만한 소스코드를 찾고, 방법에 대한 이해를 한 뒤에 자신의 모델과 데이터에 맞게끔 적용하도록 해야했었다.

 

 

Angular Additive Margin Loss의 loss function 조건에 따른 비교 실험, 여기서 Label Smoothing Loss를 적용한 조건은 input size가 384로 달라, 변인 통제가 제대로 이뤄지지 못 하였다.

Angular Additive Margin Loss의 loss function 조건에 따른 비교 실험, 여기서 Label Smoothing Loss를 적용한 조건은 input size가 384로 달라, 변인 통제가 제대로 이뤄지지 못 하였다.

 

 

Multi-model의 경우엔 LB Score 0.68대를 기록하며 최종 모델에서 탈락하였다. 이 모델 역시 나이 분류가 문제였고, 데이터셋이 추가 확보되지 않는 이상 성능향상은 어려운 상황이었다.

 

7. 데이터셋이 추가확보되지 못한 상황애서의 최선의 선택은?: Boosting 활용하기

최종 제출까지는 2시간 여 남은 상황. 이 상황에서 Hard Voting을 해보았지만 여전히 성능이 좋지 않았다. 따라서 짧은 시간안에 시도해볼만한 방법 중에 가장 간단한 방법으로 일종의 boosting을 이용한 방법을 고안했다. 아래 그림을 보면 전체 모델은 메인 모델(ViT)과 서브모델로 이루어져있는데 메인 모델은 ViT로 기존 18개 클래스로 1차 분류를 수행한다. 그 다음 잘 분류하지 못 하는 클래스(1과 2, 4와 5, 7과 8 등)의 샘플들만을 모아 서브 모델(Efficient Net)로 한 번 더 분류하여 1차 예측 결과의 일부 레이블을 수정한 결과를 최종 출력한다. 이 모델은 동일한 데이터셋으로 학습하더라도 모델이 한 번에 처리해야할 태스크가 더 단순한 경우 더 높은 성능을 낼 수 있다는 가정 하에서 제안되었다(김태진 멘토님도 오피스 아워에서 언급한 가정).
서브 모델인 age classifier는 이미지를 입력받아 성별과 나이의 8개 조합으로 이루어진 클래스(0:30대 남성, 1:40대 남성, 2: 50대 남성, 3: 60대 남성, ..., 7: 60대 여성)로 분류한 뒤, 1차로 메인 모델이 예측한 레이블에서 나이에 2차 서브 모델이 예측한 나이만을 반영하여 레이블을 수정한다. 이렇게 하는 이유는 촉박한 시간으로 인한 구현의 단순화를 위해서였으며, 입력 이미지와 1차 예측결과의 성별을 입력받아 나이를 예측하는 모델로 설계했다면 더 나은 결과를 얻었을 것으로 예상된다(Abdolrashidi et al., 2021).

 

 

최종 모델 구조

최종 모델 구조

 

 

해당 모델을 구현한 뒤 최종 제출을 하였으나, 이상하게도 리더보드 점수가 바뀌지 않고 이전 ViT와 100%로 동일한 결과가 나왔다. 모든 제출 기회를 써버려서 더 이상 제출하지 못 하고 최종 스코어 F1 0.732를 얻게 되었다(최종 리더보드 점수는 F1 0.723). 이후에 코드를 검토해보니, 최종 submission data frame에서 age classifier의 예측결과를 반영하는 코드가 잘못된 것이 발견되었고, 코드 오류를 수정하고 다시 실행하여보니, 레이블이 아래 그림과 같이 정상 수정되는 것을 확인하였다. 다시 제출을 하여 성능을 확인할 수 없다는 것이 안타까웠다.

 

 

ans는 1차 예측결과, ans2는  ans_trans는 ans2의 나이 예측 결과가 반영된 최종 예측 결과를 의미한다.

ans는 1차 예측결과, ans2는 ans_trans는 ans2의 나이 예측 결과가 반영된 최종 예측 결과를 의미한다.

 

 

 

최종 리더보드 스코어

최종 리더보드 스코어

 

8. 프로젝트에서 얻은 교훈

  1. 데이터의 중요성
    • 모델을 아무리 지지고 볶아도 양질의 데이터를 더 구하는 것에 비하면 성능향상 폭이 크지 않다는 것을 뼈저리게 느꼈다.
  2. 프로젝트 관리 시엔 팀원의 역량을 고려하고, 제한내 완수하지 못할 때를 고려해 여유있게 업무를 분배할 것
    • 1과도 연결되는 내용인데, 시간이 오래 걸리고 실패 가능성이 높은 일은 충분히 여유를 두고 업무를 부여해야 한다는 것을 깨달았다.
  3. 달성도 및 진행상황 공유를 체계적으로 할 것
    • 다른 팀의 좋은 사례. 매일 시작할 때 자신의 그날 목표를 깃헙으로 작성하고, 하루가 끝날 때 자신인 달성 정도를 팀원과 공유하면 지지부진하게 일이 밀리는 것을 방지할 수 있다.
  4. 템플릿 코드의 중요성
    • 대회 시작시에 제대로 준비가 되어있지 않은 상태에서 만들면서 참여하다보니 각종 기능 추가에만도 일주일 가량을 날렸다. 잘 만든 기존의 템플릿 코드가 있었다면 금방 적용하여 더 빠른 실험이 가능했을 것. 이번 대회를 통해 마련할 수 있었다.
  5. 체계적인 실험관리
    • 변인통제에 주의할 것, 특정 조건의 유무에 따른 성능 변화 파악시 나머지 조건을 고정할 것
  6. 잘 되는 베이스라인을 찾아두고 나머지 기법들을 추가 적용하자
    • 다른 팀의 좋은 사례, 성능이 잘 나오는 베이스라인 모델을 하이퍼파라미터 튜닝으로 미리 찾아두고, 여기에 새로 시도할 방법을 적용

 

마치며

성적은 그렇게 좋지는 못 하지만 이 프로젝트를 하면서 이미지 분류 문제에 대해서 기본적인 프로세스를 파악하고 실험을 진행할 수 있었고, 앞으로의 프로젝트에서 재활용 가능한 기본 템플릿을 얻었습니다. 또한 실험관리나 프로젝트 관리에 대한 경험적 지식을 얻게 되었으며 깃과 깃헙을 활용한 협업 방법을 숙지하였습니다. 함께 고생한 팀원들에게 감사하며, 다른 팀에 가서도 잘 해내길 바랍니다.

Comments