[번역] 머신러닝을 활용한 제품 카테고리 분류하기

“Boosting Product Categorization with Machine Learning” by A. Magrabi

최근 우연한 기회에 제품 카테고리 분류에 관심을 가지게 되어, 비슷한 일을 하고 있는 “Commercetools”의 기술블로그에 올라온 데이터 사이언티스트 Amadeus Magrabi의 글, Boosting Product Categorization with Machine Learning을 번역해보았습니다.
필요에 따라 의역을 하기도 하였는데, 일부 오역이 있을 수 있습니다. 코멘트 주시면 수정하겠습니다 :)

제품 카테고리는 모든 온라인 쇼핑몰의 구조적 뼈대를 이루지만, 이커머스 매니저에게 모든 제품이 적절한 카테고리로 분류되었는지 확인하는 작업은 악몽과 같은 일일 수 있습니다. 제품을 분류할 수 있는 카테고리는 매우 다양하하면서도(아마존에는 대략 50,000 개가 넘는 카테고리가 있습니다), 지속적으로 바뀌며, 매일 새로운 제품이 들어오기도 합니다. 카테고리 분류에서 실수가 일어나면, 비용이 클 수 있습니다. 카테고리가 잘못 분류된 제품은 혼선을 주고 전문성이 떨어져 보일 뿐만 아니라, 고객이 제품을 찾을 수 없으면 팔 수도 없기 때문입니다.

패션 웹사이트의 제품 카테고리

제품 카테고리 분류의 프로세스를 개선하기 위해, 저희는 머신러닝 기법을 주의 깊게 살펴보았습니다. 저희 목표는 모든 프로세스를 쉽고, 빠르고, 오류 발생률을 낮출 수 있는 카테고리 예측 머신러닝 시스템을 개발하는 것이었습니다. 이번 글에서는 그 개발 과정에서 저희가 직면했던 문제들과 어떻게 해결하기로 결정했는지에 대해 이야기해보려고 합니다.

Challenges 도전과제

머신러닝 관점에서 보았을 때, 제품 카테고리를 예측하는 문제에는 몇 가지 독특한 도전 과제가 있습니다.

  1. 분류할 카테고리가 아주 다양합니다. 일반적으로 머신러닝을 적용하는 분류 문제들은 클래스가 많지 않습니다. 예를 들어 스팸 분류나 파산 여부 예측은 모두 정답 클래스가 2 가지 뿐입니다. 그에 반해 이커머스에는 분류해야 할 카테고리가 수 백, 수 천 가지가 있습니다. 이런 경우, 로버스트하게 모델을 트레이닝 하기 위해서는 트레이닝 데이터가 더 많이 필요합니다.
  2. 제품 데이터가 매우 다양하며 불균형합니다. 어떤 제품에는 세세하게 나온 특성정보인데, 다른 제품에는 아예 없는 정보가 있을 수 있습니다. (예를 들어 식품류 제품에는 유통기한이나 성분표시 등이 들어가는 반면, 의류 제품에는 사이즈나 색상 등이 들어갈 것입니다.) 제품마다 들어가는 특성 변수를 전부 고려하여 데이터셋을 만들게 되면 결측치가 매우 많아질 것이며, 이는 모델 컨버전스를 훨씬 어렵게 만듭니다. 이 문제를 해결하기 위해 저희는 간단히 제품명, 이미지, 제품 설명만을 예측변수로 사용하기로 하였습니다. 이 세 가지는 대부분의 제품에서 얻을 수 있는 정보이면서 가장 중요한 정보이기 때문입니다.
  3. 온라인 매장마다 서로 다른 카테고리 구조를 가지고 있습니다. 저희 commercetools에서는 커머스 플랫폼(제품, 주문, 고객, 장바구니, 결제 등과 관련된 처리)을 관리할 수 있는 클라우드 기반 API를 제공하고 있습니다. 저희 API는 유연성(flexibility)에 초점을 맞춰 만들어졌기 때문에, 매우 다양한 업계의 고객분들(가령 패션, 식료품, 농산물, 가정용품, 겨울스포츠, 주얼리 등)이 저희 API를 사용하고 있습니다. 때문에 머신러닝 시스템을 만들 때, 해당 고객사와 관련있는 카테고리만 추천되도록 만들어야 했습니다. 
    이 문제를 해결하는 한 가지 방법은 각 고객사의 온라인 쇼핑몰에서 판매하는 제품과 카테고리에 맞게끔 모델을 따로 따로 트레이닝 하는 것이겠지만, 이 접근법에는 많은 문제점이 있습니다:
  • 어떤 쇼핑몰에는 모델을 트레이닝할 만큼 충분한 데이터가 없기도 함
  • 클래스가 불균형(unbalanced)해질 확률이 더 높아짐(예: 패션 쇼핑몰의 경우엔 ‘티셔츠’ 카테고리에는 데이터가 많이 있겠지만, ‘반다나’ 카테고리에는 데이터가 많지 않을 것입니다)
  • 새로 카테고리가 추가될 수 있으니 자주 모델을 업데이트 해야 함
  • 이 모든 모델과 버전을 관리하고 인프라스트럭쳐는 매우 복잡해질 것

이러한 이유로, 저희는 다른 접근법을 선택하여, 넓은 범주의 카테고리를 커버하고, 모든 고객이 이용할 수 있는 범용 모델(general purpose model)을 만들었습니다. 고객사마다 서로 다른 카테고리를 일반적인 카테고리(general categories)와 연결하기 위해, 단어간 유사도를 정량화하는 머신러닝 모델을 고객사마 다따로 따로 만들었습니다. (즉, “jeans”라는 일반적인 카테고리가 A쇼핑몰의 “Fashion> Men> Jeans”에 해당하는 반면, B쇼핑몰에서는 “Clothing> Pants”에 해당하는 것을 알아내기 위한 머신러닝 모델을 쇼핑몰마다 따로 따로 만들었습니다.)

다시 한 번 저희 목표를 상기해보자면: 저희는 제품의 이름, 이미지, 설명을 보고 수 많은 종류의 제품 카테고리 중에서 적절한 카테고리를 예측할 수 있는 머신러닝 모델을 만드는 것이었습니다. 이렇게 머신러닝을 통혜 카테고리를 예측한 후, 예측한 카테고리를 다시 고객사마다 가지고 있는 카테고리 구조에서 어디에 해당하는지 매핑할 수 있는 고객사별 머신러닝 모델을 만들어 예측하였습니다. 이러한 시스템을 만들기 위한 저희의 주요 코딩 언어는 파이썬(Python)이었습니다.

Class Set

이 문제에 있어서 적절한 클래스셋(class set, 카테고리 모음 정도로 생각하면 될 것 같습니다-역주)을 정하는 일은 굉장한 노력이 필요한 일입니다. 셋이 너무 작으면, 어떤 쇼핑몰에는 중요한 카테고리가 빠질 수도 있습니다. 하지만 그렇다고 셋이 너무 커지면 정확도가 굉장히 떨어질 것입니다. 저희가 큰 규모의 셋과 작은 규모의 셋을 모두 테스트해본 결과, 저희 현 버전에서는 723개의 카테고리로 셋을 구성하는 것이 적절하다는 결론을 내리게 되었습니다. 이 셋은 상대적으로 넓은 의미의 용어들로 구성되어 있으며, 주로 각 카테고리명은 한 두 단어로 구성되어 있습니다. 아래는 그 중 일부입니다:

모델 카테고리 예시

초반에는 저희가 만든 모델로 넓은 카테고리 범위를 커버할 있다는 점을 확인하기 위해 최대한 많은 카테고리를 예측해보려고 했었습니다. 하지만 저희 고객들은 모든 카테고리 영역에 골고루 분포되어 있지 않다는 점을 인지하는 것이 매우 중요했습니다. 오히려 마치 클러스터 된 섬처럼(clustered islands) 특정 산업군에 몰려있는 편이죠. 데이터 사이언스의 본성(nature)에는 반하긴 하지만, 이 문제에 있어서 만큼은 그런 섬(고객이 많은 산업군의 카테고리)에 모델을 “오버핏(overfit)”하는 것이 좋은 아이디어라는 것을 알게 되었습니다. 실제 이미 저희 고객들이 사용하고 있는 사용 사례(use cases)를 모아서 모델을 만든 것이기 때문입니다. 만일 저희가 완전히 새로운 업계의 고객을 만나게 된다해도, 이 셋을 활용하되, 고객의 니즈에 맞게 발전시키면 될 것입니다.

이제 무엇을 예측해야 하는지 알았으니, 지금부턴 예측에 사용할 변수들을 살펴보겠습니다.

이미지 분류기 Image Classifier

이미지 분류기에 대해 얘기하자면, CNN(convolutional neural networks)이 단연 기준이 되는데, 선, 모서리, 색상 등 저수준 피쳐(low-level features)를 식별하고 이를 사각형, 원, 물체, 얼굴 등 보다 추상적인 피쳐(more and more abstract features)와 결합하여 인식하는 능력이 탁월하기 때문입니다. 사람의 뇌에서 시각피질도 비슷한 메커니즘으로 돌아가기 때문에, 제대로 돌아가는 알고리즘이라고 볼 수 있습니다. 2012년 CNN과 관련하여 중대한 발전이 있은 후, 매년 열리는 ImageNet 대회를 통해 연구자들은 지속적으로 CNN의 구조를 개선시켜 왔습니다. 그리고 지난 2017년, 이 대회의 우승자는 ~97.8%라는 놀라운 정확도에 도달하기도 했습니다.

제가 앞서 모든 제품의 카테고리를 로버스트하게 예측할 수 있는 모델을 트레이닝하려면 특히나 많은 양의 데이터가 필요하다고 했던 점을 기억하십니까? 여기서 transfer learning라는 접근법을 약간 치팅(cheating)해보겠습니다. 최신 CNN을 밑바닥부터 트레이닝하는 일은 많은 시간과 데이터, 컴퓨터 자원 등이 소요되는 일이기 때문에, 저희는 이미 엄청난 이미지 데이터셋으로 사전에 트리이닝 된 뉴럴네트워크(Inception v3)을 사용하였습니다. 이 모델은 이미지에서 특성을 추출하고 결합하는 것에 대해서는 이미 많은 학습을 하긴 했지만, 아직 저희가 가지고 있는 카테고리 분류 사례를 가지고 저희가 원하는 대로 카테고리를 분류할줄은 모르는 상태입니다. 저희의 니즈에 맞게 네트워크를 활용하기 위해, 저희 카테고리와 관련된 723개의 유닛으로 구성된 새로운 레이어를 추가한 후, 기존 모델의 가중치는 그대로 두고, 저희 데이터셋에 가중치를 추가로 두어 모델을 다시 트레이닝 하였습니다. 이렇게 한 결과, 저희는 상대적으로 적은 노력과 데이터(130,000개의 이미지. 카테고리별 100~200개 정도)로 로버스트하면서도 커스터마이징 된 분류기를 만들 수 있었습니다.

뉴럴 네트워크 Inception v3의 구조

이 이러한 접근법을 적용하고 모델 트레이닝에 Google Cloud ML Engine을 활용하기 위해 파이썬에 있는 TensorFlow 라이브러리를 활용하였습니다(이 사례를 약간 변형한 코드 사용). 이 코드는 크게 url로부터 이미지를 다운로드 받고, 이를 jpeg로 변환하고, 리스케일링 하고, 각 카테고리의 하위 폴더로 분류하고, 중복된 것이나 이상한 파일(invalid files)를 제거하고, 트레이닝 된 모델을 Google Storage에 업로드하는 작업을 담고 있습니다. 모든 이미지를 더블체크하는 등의 작업등은 여전히 사람이 직접 해야 합니다. 불행히도 제품에 딸려있는 모든 이미지가 (뉴럴넷이 학습했으면 하는) 카테고리의 대표 이미지가 아닐 수도 있기 때문입니다. 가령 사용 방법이나 회사 로고, 저화질 이미지 등등이 여기에 해당할 것입니다.

문자 분류기 Text Classifier

그 다음에는, 제품 이름으로 카테고리를 분류할 수 있는 분류기를 만들었습니다.

제품 이름 사례

중복값이나 정보가 없는 이름은 제거하고 데이터셋의 균형을 맞추고 나니 230,000개(카테고리 별 최대 300개) 정도의 샘플이 만들어졌습니다. 이름을 다루기 쉽게 만들고, 차원을 줄이기 위해, 먼저 전처리를 하는 파이프라인을 만들었습니다(주로 respacy 라이브러리를 사용하였습니다):

  1. 모든 단어를 소문자로 바꾼다
  2. 마침표나 쉼표, 특수문자 등(*,|,. 등)은 제거한다. “t-shirts” 같은 경우가 있어 정보를 유지하기 위해 하이픈(-)은 제거하지 않는다.
  3. and, the, in 등과 같은 stopwords는 예측에 있어서 중요하지 않다고 보고 제거한다.
  4. 단어를 분류하여 정리(lemmatizing) 한다. 즉, “apples”와 “apple”이 같은 단어임을 모델이 알 수 있도록 단어의 어미변화에 따른 변동성을 제거하는 작업을 한다.

매우 짧은 단어(1–3 글자로 된 단어)를 제거하고 자동 스펠링 확인을 추가하는 등의 실험을 해보았지만, 결국 더 나은 퍼포먼스를 내는 데 도움이 되지 않아 최종 단계에선 제외했습니다.

이 다음에는, 전처리 된 텍스트 샘플을 숫자로 변환하였습니다. 그래야 머신러닝 모델이 이해할 수 있기 때문입니다. 이를 위해 다음과 같은 몇 가지 방법을 시도해보았습니다:

  1. Bag-of-words: 각 카테고리 샘플은 데이터셋에 있는 단어 집합을 나타내는 n-차원의 벡터로 변환되며, 해당 샘플이 나온 빈도수를 값으로 가지게 됩니다. 쉬운 방법이지만, 신택스를 고려하지 않게 되고, 매우 희소한 벡터(sparse vector)를 내놓게 된다는 단점이 있습니다. 즉 고차원의 공간에 0이 엄청 많아져 모델 트레이닝이 복잡해집니다.
  2. TF-IDF(term frequency-inverse document frequency): bag-of-words랑 유사하지만, 나머지 데이터셋에서 드물게 등장하는 단어인데 특정 텍스트 샘플에서 많이 등장하는 단어가 있다면, 여기에 더 큰 가중치를 줍니다. 이런 단어는 샘플을 좀 더 잘 표현하는 단어일 것으로 보는 것이죠. 심지어, 데이터셋에서 전체적인 빈도가 높은 단어는 어휘사전(lexicon)에서 아예 제거할 수도 있습니다. 그렇게 하면 정보성이 없는 단어의 영향력을 줄이면서도, 벡터 공간의 차우너을 줄일 수 있기 때문입니다.
  3. Word2Vec: 주어진 단어의 맥락(가령 단어 “Nike”는 “bananas” 보다는 “shoes”라는 단어 옆에 더 많이 나온다는 것과 같은 맥락)을 예측하는 2단 뉴럴 레이어를 트레이닝함으로 희소문제(sparsity problem)을 해결합니다. 계산이 좀 더 복잡하지만, 단어간 미묘한 맥락적 유사도를 인코딩해주는 저차원의 텍스트 리프레젠테이션(representation)을 만들어낼 수 있으며 분류기를 트레이닝하기 좀 더 쉽습니다.

저희는 TF-IDF를 통해 최선의 결과를 얻을 수 있었습니다. Word2Vec이 분명 텍스트 샘플간의 의미론적이고 복잡한 관계를 파악해내는 태스크에서는 더 좋은 결과를 내지만, 저희 목표를 달성함에 있어서는 오버스킬(overskill)이라고 볼 수 있습니다. 제품명은 간단하고 그 자체에 아무런 신택스트도 없는 편이기 때문입니다.

전처리와 벡터화 과정을 거친 후, 드디어 실제 텍스트 분류기를 만들어 보았습니다. 파이썬의 scikit-learn 라이브러리에 있는 나이브 베이즈, 로지스틱 회귀, k-NN, 랜덤포레스트, SVM, 그래디언트 부스팅 등 다양한 머신러닝 모델을 통해 예측 정확도를 테스트해보았습니다. 그 결과 TF-IDF로 벡터화하여 로지스틱 회귀 모델을 이용하는 것이 가장 좋은 결과를 냈는데, 이는 다시 한 번 최고의 결과를 내기 위해 복잡하고 정교한 모델을 사용해야 하는 것은 아님을 보여줍니다.

제품 설명에 대한 분류기를 개발하는 데도 동일한 단계를 거쳤습니다. 제품설명은 제품명보단 좀 더 복잡한 신택스트를 가지긴 하지만, 여전히 TF-IDF와 로지스틱 회귀모델의 조합에서 최고의 정확도가 나왔습니다 .

Category Matching 카테고리 매칭

이제 이미지와 제품명, 제품 설명을 가지고 723가지 카테고리에 대한 확률을 계산할 수 있는 분류기가 만들어졌습니다. 이 세 가지 모델로부터 얻은 예측 내용을 어떻게 통합할 수 있을까요? 소위 앙상블 모델(ensemble model)이라는 멋진 방법을 사용해볼 수 있는데, 기본적으로 앙상블 모델은 다른 모델들의 아웃풋을 자신의 잇풋으로 삼는 뛰어난 머신러닝 모델입니다. 하지만 저희 목적에는, 각 모델이 최종 예측해내는 클래스 확률의 평균을 계산하는 것으로 충분했습니다.

이제 일반적인 카테고리 예측을 할 수 있게 되었는데요, 여전히 각 고객 쇼핑몰의 카테고리에 맞게 매치해줄 메커니즘이 필요합니다. 고객이 가지고 있지도 않은 카테고리를 추천하여 강요(가령 모든 고객사가 저희가 가진 API 한 가지만 쓰라는 등)할 수는 없겠죠. 가령 저희가 만든 모델이 어떤 제품이 “팔찌”에 속한다고 예측한다면, 고객사의 카테고리에서 이 분류가 가장 잘 맞는 카테고리가 어디인지 찾아야 합니다. 여기에는 약간의 변동성이 있을 수 있습니다. 이 작업을 하기 위해, Google 뉴스 기사의 큰 말뭉치(corpus)에서 Word2Vec 모델을 트레이닝 하기 위해 gensim라이브러리를 사용하였는데, 이는 단어의 유사성을 측정하는 데 많이 사용되는 방법입니다.

이 모델로 매번 모든 모델 카테고리간 유사도를 계산하는 데 시간이 좀 걸리기 때문에 미리 유사도를 계산하여 데이터베이스에 저장해두었으며, 매일 밤 업데이트 하였습니다.

클래스 확률과 마찬가지로, 카테고리 유사도 역시 0과 1 사이로 스케일을 조절하였으며, 0.6 정도면 모델이 예측한 카테고리를 특정 쇼핑몰의 카테고리에 매치하기에 “충분히 유사(similar enough)” 정도라고 판단했습니다. 매치 간 유사도의 분산을 고려하기 위해, 클래스 예측 확률에 유사도 점수를 곱하여 최종 카테고리 예측에 대한 신뢰도를 정량화하였습니다.

패션 또는 주얼리 산업의 쇼핑몰에서는 90% 이상의 정확도를 달성한 반면, 식료품이나 가정용품 산업의 쇼핑몰에서는 최저 70–80%의 정확도가 나타나기도 했습니다. 이는 주로 후자에 해당하는 산업이 더 큰 카테고리셋을 자기도 있고, 데이터도 더 다양하기 때문인 것으로 보입니다.

API

저희 어플리케이션을 보여주는 데는 flask 라이브러리의 HTTP API를 만들었습니다. 저희가 산출하는 최종 결과물은 두 가지 입니다. 하나는 일반적인 카테고리 예측 결과이고, 다른 하나는 쇼핑몰별 특성을 반영한 예측 결과입니다. 일반적인 카테고리를 알려주는 부분은 주로 서로 다른 이미지나 제품명, 제품 설명 분류기가 어떻게 작동하는지 테스트하기 위해 사용하였습니다.

일반 카테고리 추천의 API 결과물

반면, 쇼핑몰별 카테고리를 분류해서 보여줘야 하는 경우 다음과 같은 워크플로우가 생깁니다: 먼저, 카테고리 추천을 하기 위해서 고객사 쇼핑몰의 프로젝트 키와 제품의 ID가 인풋 파라미터로 필요합니다. 그 다음에는 이미지, 상품명, 상품 설명에 대한 데이터를 저희 데이터베이스에서 찾아본 후, 머신러닝 분류기로 데이터를 넘깁니다. 그 다음, 고객사별 카테고리에 매치해줄 수 있는 모델로 예측하여 가장 가능성이 높은 카테고리를 반환합니다.

일반 카테고리 추천의 API 결과물

클래스 셋이 크고 유사도 매칭 과정을 거치기 때문에 신뢰점수는 낮을 수 있지만, 가장 관련 있는 카테고리를 찾아내기 위해서는 상대적인 점수가 중요합니다.

Merchant Center라는 유저 인터페이스를 통해 추천결과를 보여주기 위해 고객사별 API를 사용하였으며, 이 인터페이스에서 고객은 자신의 커머스 플랫폼을 관리할 수 있는 다양한 기능을 이용하게 됩니다.

“Merchant Center” UI에 API 통합하기

이 기능은 현재 베타테스팅 단계에 있으며, API자체를 테크 새비한 고객들이 사용할 수 있게 하는 작업을 진행중에 있습니다. 고객들로부터 피드백을 받아 API를 개선하려 하고 있으며, 아직 개발 파이프라인에 모난 부분이 많이 있어 정교화작업을 진행하여 개선할 것입니다.


필요에 따라 의역을 하기도 하였는데, 일부 오역이 있을 수 있습니다. 코멘트 주시면 수정하겠습니다.

감사합니다!