프로그래머스 데브매칭 머신러닝 과제 후기(with 텐서플로우)

Ryan Kim
15 min readSep 24, 2021

--

프로그래머스 [머신러닝] 미술 작품 분류하기 후기

Google Machine Learning Bootcamp 2021의 커리큘럼에 따라 텐서플로우 자격증을 취득 (후기는 이 링크를 따라가면 되고, 학습하면서 작성한 코드는 이 링크에서 확인이 가능하다)을 하고 나서 배운 지식을 활용해 볼 수 있는 프로젝트를 탐색하게 되었고, 다행히 요즘 인공지능 관련 배울 수 있는 환경이 충분히 구축되어 있어서 프로그래머스에서 제공하는 dev-match 미술 작품 분류하기라는 과제를 참여해보게 되었다.

곧 참여하게 될 Kaggle 프로젝트의 경우, 조금 난이도 있는 프로젝트를 참여하고 싶은데 그 전에 워밍업이 필요해서 첫 번째 나의 인공지능 프로젝트로 프로그래머스에 올라와 있는 미술 작품 분류하기 프로젝트를 참여해보게 되었다.

코세라 수업 이후 바로 머신러닝 프로젝트 참여해보기

일단 작성한 코드에 대해 충분한 해설을 하기에 앞서서 프로그래머스에 제출한 나의 최종 점수는 22점이다.

매우 낮은 편이고, 계속 개선해 나갈 예정이지만, 프로젝트 기간을 7일로 지정한 점 (2021.09.17 ~ 2021.09.24로 하루를 초과해서 최종적으로 8일 간 프로젝트를 수행했다), 그리고 코세라에서 배운 내용을 토대로 처음부터 끝까지 데이터 분류 ~ 모델링까지 경험해 본 것이기 때문에 내게 매우 의미가 크다.

프로젝트는 아래와 같은 순서로 진행되었다.

  1. 정말 정직하게 CNN을 적용해보기

2. CNN 외의 컴퓨터 비전 전이 학습 모델 사용해보기 (ResNet50, ResNet50V2, Xception)

CNN을 적용하는 것 외에 왜 수 많은 전이 학습 모델들 중 ResNet50, ResNet50V2, Xception을 사용했는지 문의를 할 수 있을텐데 이는 아래의 케라스 공식 홈페이지에 설명되어 있는 전이학습 API 모델들에 대한 설명을 참고하고 적용했다.

케라스 API 설명서를 잘 참고하자.

ResNet은 코세라에서 Residual Network라는 형태로 논문과 작동 원리를 설명한 적이 있어 내가 어느 정도 이해력이 있다는 바탕을 전제로 시도했고, Xception은 2018년에 포스팅된 당근 마켓 인턴의 글을 보고 시도해 보고 싶어서 적용해 보게 되었다.

그러나 둘 다 일단 텐서플로우의 API를 따와서 사용하는 형태이다 보니 입력하는 매개 변수가 큰 차이는 없었고, 초반에는 프로젝트의 이미지 분류 자체를 잘 이해하는 것이 중요하다고 생각해서 우선 기본 개념인 CNN을 그대로 적용하는 형태로 수행했다.

1. CNN 정석으로 적용해보기 (크게 3번 시도)

처음에는 정말 막막했다.

항상 코세라에서 강의를 꾸리는 사람들이 미리 코드와 데이터를 준비했었고, 나는 이 사람들이 준비한 코드에 정답만 채워 넣는 형태였기 때문에 처음부터 프로젝트를 준비한다는 게 얼마나 막막한 일인지 잘 알고 있다.

그래서 무리하지 않고, 텐서플로우 자격증 시험을 치뤘던 코드들을 토대로 먼저 다운로드 받은 데이터 압축 파일들을 풀어서 하나의 폴더에 모두 옮겨 넣었다.

참고로 프로젝트 수행하는 과정에서 Colab은 사용하지 않았다.

현재 Backend.ai라는 회사의 GPU 머신을 무료로 사용하고 있어서 가상머신을 이 회사의 것을 사용하고 있어 구글 클라우드 경로 언급이 없다는 점을 참고하길.

그 후, 위에서 압축을 풀었던 폴더의 경로 속에서 이미지를 원활하게 갖고 와야하기 때문에 파이썬 os 모듈을 이용해서 경로에 접근해 이미지 파일들을 확인한다.

이 방식을 따라 오면 이미지들을 정상적으로 가져오는 것을 볼 수 있고 이렇게 코드 상에서 경로 상의 이미지들을 가져오게 되면 텐서플로우를 사용해 이미지들을 훈련시킬 준비가 된 것이다.

그러나 그 전에 이미지들이 하자는 없는지 확인하는 작업이 필요해서 나름대로의 간단한 EDA를 실행했다.

추후에 알게 된 내용이지만, 이미지 파일은 있지만 사이즈는 0이라던지 (즉, 훈련 상황에서 소거해야하는 이미지) 아니면 각 클래스 별로 이미지 파일들이 불균형하게 있는지 등을 꼭 확인해야한다.

EDA를 진행하면, 불필요한 데이터는 제외하고 학습을 진행해주면 된다.

이미지를 Grid 형태로 몇 개씩 보여줄지 설정하고

위와 같이 실행하면 이미지들이 어떤 형태로 입력되는지 볼 수 있다.

꼭 테스트 이미지도 함께 확인하자.

모두 확인해보면 훈련용 이미지는 총 1698개, 테스트 이미지는 350개를 받는 것을 볼 수 있고, 아래와 같이 ImageDataGenerator를 쓰면 해당 경로에 몇 개의 폴더 (즉, 클래스)가 있고 이미지 장수는 몇 개인지 확인이 가능하다.

Image Generator에 매개변수 설정을 추가적으로 몇 개 더 작업했는데 이미지 회전, 가로 세로 이동, 확대 및 가로 반전 등 옵션을 적용해서 Data augmentation을 작업했다.

기본적으로 현재 주어진 이미지 양이 학습 목적으로 쓰기에는 매우 적다.

따라서, 충분한 학습을 위해 위와 같은 설정을 해줬고, 다중 이미지 분류 모델이기 때문에 flow_from_directory 메소드 설정을 할 땐 categorical 옵션은 넣어줬다.

이미지는 과제에서 설명하는 것처럼 (227,227)로 작업했으며 이는 뒷 부분에서 작성하지만 ResNet이나 Xception 모델을 사용할 땐 조금 변경해주는 작업이 필요하다는 것을 알게 된다.

훈련 목적의 이미지 클래스 분류가 되었다면, 이제 본격 CNN 모델을 작업해보자.

위 코드는 deep learning specializaion, tensorflow_in_practice를 기반으로 작업했고, 과제에서 실습할 때 어느 정도 성능이 보장되었던 구조를 가져와서 진행한 것이다.

추후에 여기서 적용한 모델이 왜 성능 개선에 큰 효과를 안보이는지 알게 되는데, 컨볼루션 층과 풀링이 많다고 무조건 성능 개선이 되는 것도 아니고, Fully Connected Layer도 (Dense 층) 층이 많아지면 훈련용 데이터에 과적합 될 가능성이 높고, 시간 복잡도가 커지기 때문에 이걸 적절히 조절해주는 것이 중요하다.

마지막으로 컴파일을 실행할 때, 다중 분류 목적이기 때문에 categorical_crossentropy를 손실함수로 사용했고, 최적화 모델에는 처음엔 rmsprop을 사용했다 (하지만, 보편적으로 초기에 뭘 사용할지 모를 때는 Adam을 써보는 것이 좋다는 글들이 많아서 변경하게 된다)

여기서 내가 크게 한 가지 놓친 것이 있다.

이미 딥러닝을 어느 정도 배운 사람이라면 바로 눈치 채게 되는 부분이 valid 데이터 분류를 하지 않고 작업한다는 점이다.

그렇다. 처음 프로젝트이다보니 나는 test 데이터를 valid 데이터로 인지하고 이 상태로 훈련을 시킨다.

여기서 훈련 결과도 개판 오분 전이지만, 훈련 시키고 나니 테스트 데이터로 검증 하려 보니까, “어? 왜 테스트 데이터가 없지?” 하는 내 모습이 프로젝트 마무리할 때 너무 웃겼다.

뭔가 잘못되었다는 것을 제대로 깨달은 것은 아래의 그래프를 보고서였고, 이 때부터 사놓고 틈틈이 보던 케라스 창시자에게서 배우는 딥러닝, Do It! 딥러닝 프로그래밍을 정독하게 된다.

1번째 시도

2. CNN 1차 시도 개선하고 훈련 & 검증 데이터 분리, 그리고 정확도 높이기

1차 시도에서 엉망의 결과를 얻고 나서 책 2권을 참고하고 나니, 코세라에서 배웠던 내용들 중에 내가 생략한 내용들이 너무 많았다는 사실을 알게 된다.

위에서 언급하지만 valid 데이터를 분류해주지 않았다는 점, 그리고 미니 배치 사이즈를 충분히 조절해주지 않은 점, 초반에 Dropout 비율이 너무 높았던 점 등과 레이블을 설정해주지 않은 점, Fully Connected Layer의 뉴런 갯수 등도 조절해주는 방법으로 다시 접근하게 되었다.

아래는 수정한 코드의 전문이다.

(위의 코드를 수정한 사항이기 때문에 전문을 하나의 파이썬 코드로 넣었다)

위 코드에서 39 ~ 62 번째 라인을 보면 valid 데이터를 손수 코드로 나누고 있다. (… 이것도 잘못했다)

사이킷런을 사용해서 from sklearn.model_selection import train_test_split 로 분리하는 것도 가능한데 그렇게 많이 이 모듈을 봤음에도 내가 막상 프로젝트를 하려고 보니 생각이 나지 않았으며, 손수 분류해보는 일을 경험하게 되었다.

여기서 분류를 마치고 나서 Image Data Generator를 사용해서 배치 사이즈를 48로 조절해준다.

이미지가 기본적으로 적기 때문에 너무 크게 배치 사이즈를 잡으면 안되고, 초기에 설정 값이 1이어서 16, 32, 48, 64 등으로 조절해보면서 시도했고, 48이 그래도 정확도 향상이 제일 높아서 48로 진행했다.

그리고 배치 정규화와 dropout을 좀 많이 사용했는데 배치 정규화를 하면서 활성 함수의 입력값으로 사용하고, 최종 출력값을 다음 층의 입력으로 사용하게 설정한 것이다.

dropout은 과적합 방지와 이미지 간의 연관성이 적은 클래스를 제거하는 목적으로 3회 추가 했으나 여기서도 결과는 좋지 못했다.

아마 컨볼루션 학습 과정에서 배치 정규화와 dropout을 사용할 수 있는데 왜 거기서 사용안했냐는 물음을 할 수 있지만, 컨볼루션 위치에서 이 2가지를 모두 적용하거나 각각 따로 적용했을 때를 확인해보니, validation loss가 기하급수적으로 커져서 위치를 변경해서 적용한 것이다.

그래도 1차 때 test를 검증 데이터로 넣는 어처구니 없는 행동을 제거하고, 몇 몇 값들을 조정하면서 테스트를 꾸준히 돌려보니 (크게 2번째 시도였지만, 세세하게 5번 정도 코드를 다시 적용해본 것 같고, 에폭도 30 → 50 → 100으로키워가면서 적용해봤다) validation 정확도가 훈련 반복 때마다 1차 때보다 튀는 것을 줄일 수 있었다.

하지만 여전히 정확도와 손실율은 엉망이었고, 검증 데이터 정확도가 훈련 반복 때마다 지속적으로 줄어드는 잘못된 상황을 피해갈 수는 없었다.

2번째 시도

3. CNN 2차 시도 개선하고 훈련 & 검증 정확도 높이기

아직까지 포함되지 않은 내용은 모델 채점을 위한 레이블링 작업 및 csv 파일 저장 등이고, 가장 기본적인 훈련 & 검증 성능이 안나오니 테스트 성능은 기대할 필요도 없었다.

2차 시도에서 테스트 성능이 7% 였으니 할 말이 없었다.

3차에서는 전반적인 구조 수정보다는 모델의 fully connected 층의 뉴런 갯수를 조절하는 방식으로 접근했고, 각 층을 넘어갈 때 마다 너무 많은 뉴런을 전달해주는 것이 문제는 아닐까? 라는 생각에서 접근하게 되었다.

아직까지 과적합 상태를 근본적으로 해결한 것은 아니고, 오히려 이렇게 은닉층을 늘리는 것은 결과적으로 과적합을 더 강화시킬 수 있었기 때문에 조절을 시도했고 한 가지 얻은 수확은 validation 정확도가 매 epoch의 격차가 2번재 학습에서 크게 나왔던 것에 반해 이전보다 조금 더 일정하게 정확도를 얻은 점이다.

3번째 시도

3번째 시도에서 dropout이 과한 것 같아서 줄이고 컨볼루션도 너무 많은 것같아서 갯수를 줄이니 이제 조금 더 정확한 형태의 validation 정확도가 나오는 것을 볼 수 있었다.

그런데 아직까지 이 시점에서 의문인 것은 왜 에폭을 100번이나 반복하는데 훈련 정확도는 기하급수적으로 개선이 되지 않을까에 대한 것이고, 100번 이상 에폭을 늘린다고 해서 더 높은 성능이 기대되지는 않아서 CNN으로 이용한 것은 여기서 멈추고 보다 효율적인 학습을 위해 CNN 기반 응용 모델들을 찾아보기 시작했다.

4번째 시도

4. 방향 전환 : 모델 변경을 통한 학습 성능 개선하기

분명 CNN을 잘 적용하는 것만으로도 어느 정도 성능이 나올텐데 내가 잘못하고 있다는 생각은 들고 있었고, 프로젝트 수행을 위해 개인적으로 지정한 시간이 있다보니 앞의 3개의 시도에서 벌써 프로젝트 기간을 5일이나 쓰면서 시간이 촉박한 시점이었다.

따라서 이 시점에 어느 정도 학습이 되어 있는 모델을 활용해서 학습하면 편법인가? 라는 생각이 들었고 코세라 강의에서 분명 비슷한 내용을 배웠는데 기억이 안나서 찾아보니 Transfer Learning(전이학습)이 있다는 것을 알게 되었다.

기본적으로 ResNet, ResNet50, Xception API가 사용방법이 거의 동일해서 가장 높은 점수를 받았던 (그래도 저점이다..ㅎ) ResNet50 코드를 토대로 설명을 진행해본다.

코드가 정말 많이 깔금해졌다.

여기서 정갈해진 느낌이 드는 이유는 3가지인데,

  1. Keras API 공식문서 읽기 (다중 분류에 관한 문서들)
  2. 케라스 창시자에게서 배우는 딥러닝
  3. Do It! 딥러닝 프로그래밍

의 공이 컸다.

이 4번째 프로젝트 작업하면서 backend.ai 서버가 잠깐 작동 안해서 Colab을 쓰다가 다시 넘어왔는데 62 ~ 67번째 줄을 보면 데이터별로 labeling을 진행했으며 (정답 제출 목적)

이 데이터들을 경로와 함께 판다스 데이터로 csv파일로 만들어 작업을 계속했다.

flow_from_directory를 쓰는 것을 먼저 배웠지만, 여기서 labeling을 작업한 것을 해당 이미지와 함께 표기해서 사용하는 것이 중요했고, 2개를 병행 표기하는 것에 판다스가 많이 쓰여서 flow_from_dataframe으로 메소드를 변경했다.

이 코드에서 주목할 곳은 첫번째로 99 ~ 105번째 ResNet50이 사용된 지점이다.

코드 사용 상황에 따라 다르지만 도저히 데이터를 train_test_split을 작업할 때 현재 주어진 train 폴더만으로 어떻게 적용할지 몰라서 한~참 해메다가 먼저 pretrained_data 형태로 학습한 값과 기존에 주어진 데이터를 각각 x,y 매개변수로 넣어주니 데이터가 잘 분리되는 것을 확인할 수 있었다.

(이 부분은 추후에 프로젝트 리뷰하면서 조금 더 검증이 필요하다)

그리고 118 ~ 132번째 줄이 두번째로 주목할 부분인데, 입력값을 1000으로 변경한 이유는 resnet50을 사용해서 학습할 경우 결과 값이 1000개의 값으로 이뤄진 벡터로 반환되어서 그걸 입력으로 받아야하기 때문에 지정했으며, 하단의 Dense는 동일하게 지정했다.

그리고 최적화 함수도 Adam으로 변경하고 최종적으로 모델을 생성해 작업을 진행하니, 훈련 & 검증 정확도가 말도 안되게 개선되었다!!

드디어 내가 해냈구나! 생각보다 어렵지 않구나 생각했다.

워낙 CNN만 적용할 때 생각하면 이 프로젝트 마무리는 제대로 할 수 있을까 싶었는데 에폭을 30회로 제한하고 훈련 속도도 CNN을 쓸 때보다 월등하게 빨랐기 때문에 결과도 반드시 좋을 것이라는 큰 기대를 하고 있었다.

5. 결과 제출 그러나 테스트 정확도가 매우 떨어짐

다른 프로젝트를 해보면서 머리를 좀 식힌 뒤에 다시 돌아와 확인해봐야겠지만 현재 점수로 절대 만족할 생각이 없다.

프로그래머스에 해당 코드를 제출한 시점에 다른 사람들의 코드를 확인해보니 텐서플로우로 작성한 코드가 안보였다.

전부 파이 토치로 작업했고, 아주 잠깐 스치듯이 ‘파이토치를 써야 테스트 정확도도 개선되는건가?’ 라는 생각이 들었다.

그러나 이건 목수가 연장 탓하는 상황이나 다름없었고 분명 개선하는 방법이 있을텐데 (특히 train/valid에서 높은 성능이 나오는 것을 봤다면 분명히 test 데이터 검증 과정에서 뭔가 빠진 게 있을 가능성이 높다) 하루 종일 고민하다가 일단 프로젝트 기간을 초과했으므로 kaggle 프로젝트로 넘어가는 판단을 하게 되었다.

조금 다른 프로젝트 보다 보면 분명히 이 프로젝트를 다시 볼 때 잘못된 부분을 판단할 수 있을 것이고 개선할 수 있을 것이라 믿었기 때문이다.

6. 후기

생각보다 엄청 어렵지 않다.

처음부터 빌드하는 과정이 삽질의 연속이어서 그걸 겪기가 두려워서 그렇지, 조금씩 빌드하다보면 결국 내 코드로 잡힌다는 느낌이 든다.

경험이 부족한 것은 노력으로 매꿀 수 있는데, 중간에 포기하는 건 정말 이도 저도 아닌 상황에 봉착한다.

어떤 인공지능 학습을 주로 다루게 될지는 모르겠지만, 이번에 컴퓨터 비전 학습을 해봤으니 다음에는 NLP 관련 프로젝트를 해보자.

커리어 쌓기 전이라 다양한 프로젝트를 경험해보면서 시행 착오를 쌓는 게 정말 중요하다.

커리어는 버티면서 나아가는 것이다.

10월 말 전까지 꼭 점수 90점을 넘겨보자!

--

--

Ryan Kim
Ryan Kim

No responses yet