ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Product category classification
    개발/AI 2020. 1. 9. 14:50

    "전체 <Source code>는 맨아래 github링크를 따라가세요."

     

    매월 초 하나더 앱의 상품정보를 각 편의점 홈페이지에서 crawling하여 업데이트를 한다. 이때 상품의 카테고리정보는 제공되지 않기 때문에 직접 수작업으로 분류를 해줘야 한다.

    매달 등록되는 행사상품의 갯수는 약 2000여개이고, 이를 수작업으로 분류시 1건당 2~5초의 시간이 소요된다고 했을때 약 2시간의 작업 시간이 소요된다.

    이를 해소하기 위하여 우선 기존 전통적인 방법으로, 단어 유사도 검색을 통한 1차 분류를 다음과 같이 진행했다.

    def similar(a, b):
        return SequenceMatcher(None, a, b).ratio()
        
    simalarity = similar('기존등록품명', '신규등록품명')
    
    if ( simalarity > 0.9 ) :
    	#기존정보로 등록
    elif (0.9 >= simalarity > 0.8 ) :
    	#기존정보로 등록하되 한번 확인
    else :
    	#새로운품목으로 판단하여 분류대상

    Python의 difflib 라이브러리의 SequenceMatcher를 이용하여 기존정보와 90% 이상 일치할 때 기존 등록정보를 활용하고, 90% 미만일 시 수작업으로 분류대상으로 지정 후, 2차 수작업 분류를 진행했다.

     

    2차 수작업 분류 대상은 월평균 300건~500건 정도 발생했으며, 약 30분정도 소요가 되는 작업으로 줄었다.

    하지만, 배치작업 사이에 수작업이 들어가기때문에 전체 프로세스를 자동화할 수 없고, 집중하여 30여분 분류를 하다보면 상당한 기력이 소모되었다.

     

    이에 Deep learning을 활용하여 Classification을 진행해보았다.

     

    1. 학습 데이터 전처리

    A. 상품명 자연어 처리

    Classification 모델 trainning을 위한 trainning set은 품명, 카테고리 모두 한글로 이루어져 있고, 품명은 규격이 없이 정의되어있다. 브랜드명, 품목, 용량, 단위 등 여러종류의 문구가 포함될 수 있고 되지 않을 수 있다. 또한 순서도 규격화되어있지 않다.

    Trainning Data

    이를 정형화 하기 위한 방법으로 한글 자연어 처리 라이브러리인 KoNLPy를 사용하여 자연어 문장안에 형태소들을 분리하고, Bag of Words 알고리즘을 이용하여 정형화된 Numerical data 로 변환을 하였다.

    Trainning data에서 추출된 형태소들

    추출된 형태소들로 Word bag을 구성하고, 품명에 Word bag의 형태소가 몇번 발생하는지를 수치화 하였다.

    numericalProdName = bagofwords('오가니스트)샴푸200ml', vocabulary)
    print(numericalProdName)
    print(type(numericalProdName))
    print(numericalProdName.shape)
    [0. 0. 0. ... 0. 0. 0.]
    <class 'numpy.ndarray'>
    (5274,)

    '오가니스트)샴푸200ml'라는 한글은 5274열의 데이터 1행으로 변환된다.

     

    B. Label 데이터 (상품품목) 수치화

    하나더에서 제공하는 상품품목 분류는 모두 33종으로 다음과 같다.

    category = ['파이', '탄산음료', '쿠키', '케잌', '커피', '초콜릿',
                '차', '즉석식품', '주스', '주류', '젤리', '장난감',
                '의류', '음료수', '유제품', '위생용품', '원물간식',
                '에너지드링크', '애견용품', '아이스크림', '시리얼',
                '소시지', '생활용품', '생수', '사탕', '빵', '미용용품',
                '라면', '두유', '껌', '과채주스', '과자', '건강음료'
    ]

    이를 학습 Label로 사용하기 위하여 Numerical data로 변환 후 다시 One-hot 벡터로 변환하였다.

    # 레이블데이터 숫자화
    train_y = df['prod_category'].apply(category.index)
    train_y = np.array(train_y)
    
    # One-Hot 벡터 인코딩
    train_y = np_utils.to_categorical(train_y)

     

    2. 모델 구성

    우선 분류모델 활용 가능성을 평가 하기 위한 파일럿으로 모델의 세부적인것을 조정하며 튜닝을 하진 않았다.

    적당히 Deep한 Layer을 적당히 구성하여 적당히 트레이닝한 결과를 뽑아보는게 우선이었고, 실제 상용에서 활용하며 튜닝을 진행할 예정이다.

    #모델 클리어
    keras.backend.clear_session()
    model = models.Sequential()
    
    model.add( Dense(4096, input_shape=(5274,), name = 'Hidden1', # activation = 'relu',
                    kernel_initializer = 'glorot_uniform',
                    bias_initializer = 'glorot_uniform'))
                    
    model.add(BatchNormalization())
    model.add(Activation('elu'))
    model.add(Dropout(0.1))
    
    model.add( Dense(4096, name = 'Hidden2', # activation = 'relu',
                    kernel_initializer = 'glorot_uniform',
                    bias_initializer = 'glorot_uniform'))
                    
    model.add(BatchNormalization())
    model.add(Activation('elu'))
    model.add(Dropout(0.1))
    
    model.add( Dense(512, name = 'Hidden3', # activation = 'relu',
                    kernel_initializer = 'glorot_uniform',
                    bias_initializer = 'glorot_uniform'))
                    
    model.add(BatchNormalization())
    model.add(Activation('elu'))
    model.add(Dropout(0.1))
    
    #output Layer
    model.add(layers.Dense(33, name = 'OutputLayer',
                           activation = 'softmax',
                          kernel_initializer = 'glorot_uniform',
                          bias_initializer = 'glorot_uniform'))
                          
    model.add(BatchNormalization())
    model.add(Activation('softmax'))
    
    from keras import optimizers
    
    # 러닝 레잇 조절
    adam = optimizers.Adam(lr=0.0001)
    
    # 컴파일
    model.compile(loss = 'categorical_crossentropy', optimizer = adam,
                  metrics =['accuracy'])
                  
    from keras.callbacks import EarlyStopping
    es = EarlyStopping(monitor = 'val_loss',
                      min_delta = 0, # 개선되고 있다고 판단하기 위한 최소 변화량
                      patience = 10, # 개선 없는 epoch 얼마나 기달려 줄거야?
                      verbose = 1
                      )
    
    history = model.fit(final_train_x, final_train_y, epochs=10, batch_size = 2000,
                       validation_split = 0.2,
                       verbose = 1,
                       callbacks = [es])

    모델 Summary는 다음과 같다.

    Model: "sequential_1"
    _________________________________________________________________
    Layer (type)                 Output Shape              Param #   
    =================================================================
    Hidden1 (Dense)              (None, 4096)              21606400  
    _________________________________________________________________
    batch_normalization_1 (Batch (None, 4096)              16384     
    _________________________________________________________________
    activation_1 (Activation)    (None, 4096)              0         
    _________________________________________________________________
    dropout_1 (Dropout)          (None, 4096)              0         
    _________________________________________________________________
    Hidden2 (Dense)              (None, 4096)              16781312  
    _________________________________________________________________
    batch_normalization_2 (Batch (None, 4096)              16384     
    _________________________________________________________________
    activation_2 (Activation)    (None, 4096)              0         
    _________________________________________________________________
    dropout_2 (Dropout)          (None, 4096)              0         
    _________________________________________________________________
    Hidden3 (Dense)              (None, 512)               2097664   
    _________________________________________________________________
    batch_normalization_3 (Batch (None, 512)               2048      
    _________________________________________________________________
    activation_3 (Activation)    (None, 512)               0         
    _________________________________________________________________
    dropout_3 (Dropout)          (None, 512)               0         
    _________________________________________________________________
    OutputLayer (Dense)          (None, 33)                16929     
    _________________________________________________________________
    batch_normalization_4 (Batch (None, 33)                132       
    _________________________________________________________________
    activation_4 (Activation)    (None, 33)                0         
    =================================================================
    Total params: 40,537,253
    Trainable params: 40,519,779
    Non-trainable params: 17,474
    _________________________________________________________________

    구성한 모델에서 training 되는 weight은 총 4천만개정도가 된다.

    Input, Output Layer가 각각 한개씩, 중간의 hidden layer두개, 그 사이사이에 학습효율 향상을위해 batch_normalization 및 activation, dropout 레이어를 구성했다.

     

    3. 평가

    트레이닝셋 총 3만여건에 10 ephoch를 학습시킨 결과는 다음과 같았고,

     - loss: 0.4471 - acc: 0.9866 - val_loss: 0.5661 - val_acc: 0.9473

    10%정도 남겨둔 테스트셋으로 한 최종평가는 다음과 같다.

    Test Loss : 0.600667,  Test Accuracy : 93.662%

    오류 유형은 다음과 같다.

    id = 119
    품명 = 썬키스트허니유자280병
    이 상품은 : 음료수 입니다.
    모델의 예측 : 차
    id = 124
    품명 = 천호도라지배즙70ml
    이 상품은 : 건강음료 입니다.
    모델의 예측 : 아이스크림
    id = 126
    품명 = 영진구론산오리지날150ml
    이 상품은 : 음료수 입니다.
    모델의 예측 : 건강음료
    id = 150
    품명 = 천호웰스H도라지110ml
    이 상품은 : 건강음료 입니다.
    모델의 예측 : 주스
    id = 156
    품명 = 매일아몬드브리즈바나나190ml
    이 상품은 : 두유 입니다.
    모델의 예측 : 음료수
    id = 184
    품명 = 마다가스카르바닐라콘
    이 상품은 : 아이스크림 입니다.
    모델의 예측 : 과자

    어떤 품목은 수작업으로 분류한것보다 더 정확하게 분류한듯한 case도 보였고, 영 딴판의 분류결과도 분명 존재했다.

    정확도를 99프로 이상만 끌어올리게되면, 매월 상품분류작업에 큰 노력을 들이지 않아도 될것 같다.

     

    4. 남겨진과제

    A. 자연어 처리 오차

    상품 품명 정보에 띄어쓰기가 되어있지 않아 브랜드 구분, 상품 구성요소 등 구분이 정확히 되지 않고, 한글짜씩 어긋나거나 잘못된 형태소가 분리된것이 몇몇 눈에 띈다. KoNLPy 특성상 완성된 문장 등의 분석을 위한 라이브러리이기 때문에 품명정보 분석에 잘 맞지 않는것으로 파악된다.

    또한 상품을 나타내는 품명에 순서도 영향이 큰데 BoW 알고리즘은 순서 정보는 생략이 된다.

    가령 아몬드초콜릿(초콜릿), 초콜릿아몬드(원물간식) 과 같이 서로 다른 분류이지만, BoW알고리즘은 동일한 분류로 구분이 될수밖에 없다.

    B. 대분류, 중분류, 소분류

    분류의 정확도를 더욱 정교화 하기위해서는, 분류의 Depth를 정의하여 학습에 활용하면, trainning data가 적더라도 조금더 정확한 모델을 만들어낼 수 있을것 같다. 

     

     

    Source Code : https://github.com/PlatiB/Deep_product_category_classification

     

    PlatiB/Deep_product_category_classification

    Contribute to PlatiB/Deep_product_category_classification development by creating an account on GitHub.

    github.com

     

    '개발 > AI' 카테고리의 다른 글

    Pyspark feature engineering. Robust Scaler  (0) 2020.03.31

    댓글

Designed by Tistory.