이미지만으로 내 중고물품의 카테고리를 자동으로 분류해준다면? (feat. Keras)

모든 코드는 https://github.com/JeonCS/article-category-classifier-keras에서 보실 수 있어요.

안녕하세요 당근마켓 인턴 Jeon입니다.

당근마켓에서는 정말 다양한 중고거래가 이뤄지고 있어요. 그만큼 사용자가 많은 중고 거래 게시물을 작성하고 있어요. 만약 사용자가 좀 더 쉽게 중고 거래 게시물을 등록할 수 있다면 더 많은 중고 물품이 거래될 수 있을 거에요. 그런 취지에서 사용자가 중고 물품을 올릴 때 자동으로 물품의 카테고리를 예측하는 프로젝트를 시작했어요.

그리고 아래와 같이 현재 중고거래 글쓰기를 하려면 2단계에 걸쳐야 하지만 사용자가 중고 물품을 올릴 때 자동으로 물품의 카테고리를 예측 한다면 1단계로 축소 되는 효과도 줄 수 있어요.

왼쪽: 현재 글쓰기 화면 오른쪽: 변경 후 예상 화면

1. 데이터

카테고리 별 비율

한눈에 봐도 여성의류, 여성잡화, 유아 제품이 많죠?

딥러닝은 데이터가 많을수록 더 정확하게 예측하도록 학습할 수 있어요. 따라서 데이터 수가 많은 여성의류, 여성잡화, 유아 제품 카테고리는 딥러닝 모델이 정확하게 예측할 수 있어요.

그리고 이미지 특징이 분명한 중고차/오토바이, 도서/티켓/음반 같은 카테고리 또한 특징 추출이 잘되어 정확하게 예측할 수 있어요.

하지만 과외/클래스, 구인/구직, 전시/공연/행사, 지역업체는 사진만으로 특징을 추출하기 어려워 카테고리 추천을 정확하게 예측하기 힘들어요.

또한 삽니다, 기타와 같은 카테고리는 이미지만으로는 분류할 수 없기 때문에 훈련에서 제외했어요.

데이터를 정제하고 train/test 데이터로 8대2 비율로 나눈뒤, 아래와 같이 train/test 데이터 각각 카테고리별 폴더를 만들었어요.

/training
가구/인테리어
게임/취미
과외/클래스
...
지역업체
/testing
가구/인테리어
게임/취미
과외/클래스
...
지역업체

이런식의 폴더 정리는 보기 직관적일 뿐만 아니라 keras에서 제공하는 ImageDataGenerator 모듈을 십분 활용할 수 있어요.

keras에서 제공하는 ImageDataGenerator라는 모듈을 사용하면 이미지 변환/전처리가 용이한데요, 이미지 rescale, resize, rotate, flip 등 데이터 증강에 사용할 수 있는 이미지 변환 기법도 손쉽게 사용할 수 있습니다. 저는 ImageDataGenerator를 사용하여 이미지 rescale을 하고 train 데이터 셋을 train/validation 데이터로 9대1로 한번 더 나눴어요.


2. 모델 구축

이미지 분류기를 만들때 자체적으로 CNN등의 네트워크를 구축해 모델을 만들 수 있어요. 하지만 이미지 분류에 이미 좋은 성능을 낸 pretrained(미리 훈련된) 이미지 분류 모델을 활용한다면 pretrained 모델의 구조를 이용하여 성능 좋은 모델을 최소한의 노력으로 만들 수 있어요.

아래 표는 keras에서 제공하는 ImageNet 데이터셋으로 훈련된 다양한 pretrained 이미지 분류 모델과 각각 성능을 나타내는 차트에요.

참조: https://keras.io/applications/

정확도도 중요하지만 모델의 복잡도를 생각을 안할 수 가 없었어요. 모델이 복잡하면 자칫 분류까지 걸리는 시간이 너무 길어질 수 있어요. 유저 입장에서 아무리 잘 분류해주더라도 느리게 분류하면 좋은 경험이 되지 않을것 같아요. 그래서 Top-5 Accuracy 성능 상위 5개 중 Parameters가 가장 적은 InceptionV3와 Xception 모델이 가장 적합하다고 생각되어 선택하였어요.

ImageNet 데이터셋으로 훈련된 두 pretrained 모델은 1000개의 카테고리를 분류하기 위해 훈련되었는데요. 저희는 삽니다, 기타를 제외한 19개의 카테고리만을 분류하기 때문에 pretrained 모델의 구조를 약간 변경했어요.

아래는 변경되기 전/후의 pretrained 모델 구조에요.

변경전:
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
inception_v3 (Model) (None, None, None, 2048) 21802784
_________________________________________________________________
global_average_pooling2d_4 ( (None, 2048) 0
_________________________________________________________________
dense (Dense) (None, 1000) 2049000
=================================================================
Total params: 23,851,784
Trainable params: 23,817,352
Non-trainable params: 34,432
_________________________________________________________________
변경후:
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
inception_v3 (Model) (None, None, None, 2048) 21802784
_________________________________________________________________
global_average_pooling2d_4 ( (None, 2048) 0
_________________________________________________________________
dense (Dense) (None, 19) 38931
=================================================================
Total params: 21,841,715
Trainable params: 21,807,283
Non-trainable params: 34,432
_________________________________________________________________

모델이 카테고리를 분류할때 위 dense layer(fully connected layer)의 출력값(logit)을 softmax activation 함수에 입력하여 각 카테고리의 확률을 구하는데요. 변경전에는 dense layer의 출력 크기가 1000이였지만 변경후에는 출력 크기가 19로 분류하고자 하는 카테고리 수와 일치하는것을 볼 수 있네요.

아래 코드는 Model 클래스를 만들고 keras에서 제공하는 applications 모듈을 사용하여 모델을 구축하는 코드에요.


3. 모델 훈련

두 pre-trained 모델을 훈련시킬때는 feature extractionfine-tuning 두가지 방법을 사용하여 비교했어요.

Feature extraction은 ImageNet 데이터셋으로 훈련된 pretrained 모델의 dense layer를 제외한 다른 layer의 weight는 고정(freeze)시키고 dense layer의 weight만 update하여 모델을 훈련시키는 방식이에요. Pretrained 모델이 ImageNet 데이터셋으로 이미지 특징을 잘 추출했기 때문에 dense layer의 weight만 update 하여도 좋은 성능을 얻을 수 있거라 기대 할 수 있어요.

Fine-tuning은 feature extraction에서 더 나아가 dense layer뿐만 아니라 네트워크내 다른 layer의 weight를 update 하여 훈련시키는 방식입니다. Feature extraction은 목표 feature를 잘 추출했다는 전제하에 좋은 성능을 낼 수 있는데 그렇지 못할경우, 예컨데 ImageNet 데이터셋의 이미지 특징과 당근마켓 중고물품의 이미지 특징이 상이할 경우, fine tuning은 중고물품 이미지로 네트워크 layer의 weight를 다시 update함으로서 feature를 추출하는식이에요.

1. 기본 Pretained model 2. Feature Extraction 방법 3. Fine-tuning 방법https://link.springer.com/article/10.1007/s13244-018-0639-9

모델 훈련에 앞서 모델의 성능을 측정할 metric을 정해야겠죠. 사용자에게 카테고리 추천을 할때 최대 3개의 카테고리를 추천해줄 계획이였어서 top-1 accuracy(상위 1개 정확도)와 top-3 accuracy(상위 3개 정확도)로 성능을 측정했어요.

또한 카테고리별 데이터 수가 불균형한 데이터셋을 보정하기 위하여 훈련시 카테고리별 가중치를 줘서 상대적으로 적은 데이터를 갖는 카테고리에 더 큰 가중치를 주고 많은 데이터를 갖는 카테고리에 더 작은 가중치를 주게끔 했어요.

아래는 모델을 컴파일하고 훈련 중간 중간 모델을 저장할 모델 체크포인팅과 모델 훈련 성능을 로깅할 csvlogger callback을 정의하고 이미지 데이터를 입력하여 모델을 훈련시킨뒤 훈련을 마친 모델을 저장하기 까지 하는 코드에요.

이제 아래 코드와 같이 모델 파라미터를 정의하고 모델 오브젝트를 생성한뒤 훈련을 진행하였어요.

훈련 결과:

Valiadation 셋으로 훈련 모델 성능을 측정한 결과 fine tuning(xception_ft)으로 훈련한 모델의 성능이 top-1 validation accuracy가 약 74프로, top-3 validation accuracy가 약 93프로로 가장 좋게 나왔어요. Feature extraction보다는 fine-tuning이 대체적으로 더 좋은 성능을 보이네요


4. 모델 성능 측정

Precision & Recall

훈련을 마쳤으니 성능을 확인해봤어요. 모델 성능의 더 자세한 insight를 얻기 위해서 각 카테고리별 recall & precision을 구해봤어요.

아래 표는 위 훈련한 모델중에 가장 성능이 좋았던 fine-tuning으로 훈련된 Xception 모델을 기준으로 나온 성능표에요. (중간에 들어간 vehicle은 훈련 단계에서 잘못 들어간 빈 카테고리에요. 성능에는 영향이 없으니 무시하시면 돼요.)

각 카테고리별 precision과 recall

Recall이 높다는 뜻은 예를 들어 여성의류인 카테고리를 다른 카테고리로 분류하는 비율이 낮다는 뜻이에요. 반대로 낮으면 여성의류인 카테고리를 다른 카테고리로 분류하는 비율이 높다는 뜻이죠.

Precision이 높다는 뜻은 예를 들어 여성의류가 아닌것을 여성의류로 분류하는 비율이 낮다는 뜻이에요. 반대로 낮으면 여성의류가 아닌것을 여성의류로 분류하는 비율이 높다는 뜻이죠.

처음 예상과 같이 여성의류(18), 여성 잡화(19) 유아 제품(8)에 대한 성능이 상대적으로 좋네요. 중고차(11)과 가전제품(4)와 같이 이미지에 특징이 뚜렷하게 나타나는 중고물품에 대한 분류 또한 상대적으로 성능이 좋게 나왔네요.

반면 직관적으로 봤을때 이미지만으로는 분류하기 힘들고 데이터가 상대적으로 적은 과외/클래스(16), 구인/구직(7), 전시/공연/행사(3)등의 분류 성능은 낮게 나왔네요. 하지만 생각보다 지역업체(14)의 분류 성능이 생각보다 높게 나왔네요!

Confusion Matrix

모든 카테고리에 대한 confusion matrix

모델이 실제 카테고리 대비 어떤 카테고리로 분류했는지 나타내는 confusion matrix를 시각화 해봤어요. 매트릭스 대각선 이외 셀의 색이 짙은 파랑색(0에 가까운)일 수 록 잘 분류 하고 있다고 얘기 할 수 있어요. 매트릭스 우측 중간 연한 파란 셀을 보면 유아 제품(유아 옷으로 추정)인 것을 여성의류로 분류한다던가 남성의류를 여성의류로 분류하는 케이스가 많은 것을 볼 수 있네요.

분류 시각화

모델의 성능을 좀 더 깊게 보고 싶어 모델이 잘못 분류한 중고 물품의 이미지를 시각화 해봤어요. 모델이 예측한 카테고리 3개와 각 예측의 스코어(확률), 실제 해당하는 카테고리를 한눈에 쉽게 볼 수 있게 코드를 짜봤어요. 그리고 모델이 잘못 분류한 물품을 카테고리별로 몇개 뽑아봤어요.

디지털/가전제품으로 잘못 분류된 물품:

디지털/가전제품이라고 예측한 물품들. 아래 전기 밥솥, 저울기는 디지털/가전으로 잘 분류 했지만 첫 라벨링이 잘못된 케이스네요.

여성의류로 잘못 분류된 물품:

여성의류라고 예측한 물품들. 남성/아동 물품을 여성의류로 예측한 케이스 많네요. 이미지만 보고 남성/여성/아동의류까지 잘 분류 할 그날까지!

도서/티켓/음반으로 잘못 분류된 물품:

유아/유아도서지만 도서/티켓/음반 카테고리로 분류된 케이스가 보이네요.

마치며

이렇게 해서 간단한 데이터 전처리, 모델 구축, 모델 훈련 그리고 모델 성능까지 측정해봤네요. 결론적으로 feature extraction보다 finetuning의 성능이 월등히 좋았네요. 아무래도 ImageNet 데이터셋의 특징과 중고물품의 특징이 어느정도 상이하여 이런 결과가 나오지 않았나 생각해요. 그리고 앞서 제시한 표와 같이 InceptionV3 모델보다는 Xception모델이 조금 더 좋은 성능을 발휘했어요.

데이터가 많은 여성의류나 유아 제품과 이미지 특성이 뚜렷하고 일관된 중고차량이나 디지털/가전제품을 분류하는 성능은 좋았지만 데이터가 적고 이미지만으로는 분류가 힘든 과외/클래스, 구인/구직과 같은 카테고리 분류 성능은 좋지 않았네요.

개선점

모델을 개선하기 위해서 몇가지 방법을 더 시도 해볼 수 있을거같아요.

  1. Segmentation을 통해 배경에 의한 영향을 최소화하기
  2. 훈련 이미지중 잘못 라벨링된 데이터를 손수 필터링하기
  3. data augmentation을 하여 훈련 데이터 증강하기
  4. 훈련 데이터에 상대적으로 데이터가 적은 카테고리의 데이터 늘리기

모델의 성능을 이러한 수치로 확인하는것도 좋지만 모델을 서빙하여 눈으로 직접 경험하는것도 좋은 방법인거같아요. 그래서 다음편은 Flask 서버에 모델을 올려 서빙하기까지에 대한 글을 쓸 예정이에요!


당근마켓 서비스에서 머신러닝은 중요한 역할을 맡고 있어요. 머신러닝을 통해 사용자는 더 다양한 컨텐츠를 좀 더 쉽고 편하게 접할 수 있어요. 또한 당근마켓만의 따뜻한 문화를 유지하는데 머신러닝은 큰 역할을 하고 있어요. (당근마켓에서 딥러닝 활용하기)
당근마켓에서 서비스가 성장하면서 생기는 여러 문제를 같이 해결할 머신러닝 개발자를 찾고 있어요.