AWS 기술 블로그

Amazon SageMaker로 컬리(Kurly) 상품 후기 분류 모델 개발하기

컬리는 신선식품으로 출발하여 화장품을 품어 뷰티컬리로 새로운 서비스를 출시하게 되었습니다. 새로운 서비스가 나오면서 더욱 중요하게 된 영역은 바로 고객의 상품 후기(feedback) 입니다. 사용자가 작성한 제품 리뷰는 다른 사용자에게 중요한 정보가되는데, 작성된 리뷰는 정형화 되어 있지 않기 때문에 모든 리뷰를 읽지 않는 이상 제품 정보를 파악하기 힘들고, 내용이 길기에 중간에 포기 할 수도 있습니다. 이에 리뷰에서 제품의 주요한 속성정보만 사용자에게 알리면 더 나은 경험을 만들지 않을까? 그래서 컬리에서는 상품 후기개선을 위한 다양한 변화들을 적용, 준비하는 과정과 더불어 데이터의 영역에서는 후기에 라벨을 붙이는 기능을 검토하고 있었습니다. 후기에 라벨을 붙이는 것은 자연어 처리에서 주로 다루는 분류 알고리즘 개발의 과정으로 텍스트 데이터의 전처리, 훈련, 분류 과정을 진행해야 합니다. 이번 게시글 에서는 상품 후기 분류 모델 개발이라는 현업의 문제를 풀기 위해서 컬리와 AWS SA 분들과 어떻게 협업하고 Amazon SageMaker를 활용했는지 소개합니다.

문제 정의

한 개의 상품 후기는 여러 라벨로 표기가 가능합니다.  즉 한개의 상품 후기에서 여러 개의 라벨로 아래의 예시처럼 분류가 가능해야 합니다.

Label: “보습력”, “사이즈”, “가격”

Review: 이 제품은 피부에 잘 스며드는 효과가 좋아요. 그리고 포켓 사이즈에 맞추어 가격도 적당 합니다.

이러한 문제는 머신 러닝에서 Multi-Labeling Classification로 정의 합니다. 이 문제를 풀기 위해서, 데이터 전처리, 모델 네트워크 구성,  모델 훈련, 평가의 과정을 설명하겠습니다.

12개 라벨의 상품 후기 데이터 세트

총 12개의 라벨 (가격 / 거품/ 만족도 / 발림성 / 보습력/ 사이즈/ 용량 / 지속력/ 클렌징 / 트러블/ 향기/ 효과 )의 데이터로 분석을 진행했습니다. 세부 데이터를 확인해 보면 만족도, 향기와 같이 1만 개 이상의 데이터가 있는 라벨도 있지만 거품, 사이즈, 용량과 같이 1,000개 수준의 데이터가 있는 라벨도 있었습니다.아래는 하나의 후기 예시 입니다. 아래의 같은 후기에 ‘보습력’ 이라는 라벨이 부여가 되어 있습니다.

데이터 전처리

들어가기 앞서 KoELECTRA는 한국어를 위한 사전 학습된 언어 모델입니다. 이는 제너레이터(generator)와 판별자(discriminator) 네트워크를 사용하여 언어 모델을 사전 훈련하는 매우 효율적이고 효과적인 방법인 ELECTRA 아키텍처를 기반으로 합니다.

KoELECTRA는 대규모 한국어 텍스트 데이터 말뭉치를 훈련하여 텍스트 분류, 명명 개체 인식, 감성 분석 등 다양한 자연어 처리 작업에서 최고 수준의 성능을 달성했습니다. KoELECTRA는 오픈소스 프로젝트로 연구 커뮤니티에 공개되어 한국어 자연어 처리 작업의 정확도와 효율성을 높이기 위해 다양한 응용 분야에서 활용되고 있습니다.

우리는 Hugging Face에 등록된 monologg/koelectra-base-v3-discriminator 의 토큰나이저 및 PreTrained 모델을 사용했습니다. 아래의 코드는 해당 모델의 토큰나이저를 불러오는 코드 이고 간단한 예시 입니다. 상세한 사항은  KoELECTRA 의 상세 사항은 Pre-Training 한 GitHub를 참조하세요.

from transformers import AutoTokenizer, AutoModel
tokenizer_id = 'monologg/koelectra-small-v3-discriminator'
tokenizer = AutoTokenizer.from_pretrained(tokenizer_id,   
                                          model_max_length = 64)
text = '저렴하게 잘 구매한거 같아요'
tokenizer.tokenize(text)

NLP 에서는 전처리가 성능을 좌우한다는 말이 있는데, 토크나이저의 특성에 맞춰서 의미가 잘 구분되고, 모델이 잘 훈련될 수 있는 방식의 전처리를 진행했습니다. 띄어쓰기를 하는가 안 하는가에 따라서 단어를 인식하는 기준이 달라집니다. 이번 분석에서는 koelectra-small-v3-discriminator의 토크나이저를 사용했는데요, 문장의 띄어쓰기의 존재 유무에 따라서 단어의 인식 방식이 다른 점을 확인했습니다. 예를 들어 ‘저렴하게 잘 구매한거 같아요’라는 문장에서 아래와 같이 띄어쓰기가 없다면 ‘저렴’이라는 단어를 제외하고 이후에 등장하는 단어는 모두 연결단어로 인식하여 총 11개의 토큰으로 분리가 됩니다. 즉 위의 토크나이저의 훈련시 생성된 단어집 (Vocabulary) 에 ‘저렴’ 의 1개의 단어만 존재하고, 나머지는 분절하여 서브워드로 인식이 됩니다. 반면 띄어쓰기가 있는 문장의 경우, 단어의 의미를 인식하여 연결단어와 개별단어를 분리하여 인식하게 됩니다. 이 경우는 “저렴’, ‘잘’ , ‘구매’, ‘같’ 의 4개의 단어가 단어집에 존재 합니다. 이 두개의 경우만 봐도, 후자가 4개의 의미 있는 단어(형태소)로서 모델 파인 튜닝에 사용될 수 있기에, 더욱 많은 정보를 사용할 수 있습니다. 이런 특성을 확인하여 띄어쓰기가 너무 없거나 너무 많은 문장은 훈련과정에서 제외 하였습니다.상세 내용은 여기 버트(BERT) 및 토큰나이저 에서 링크를 눌러서 확인하세요.

일반적인 텍스트 분석과정에서는 특수문자나 숫자를 제외하고 전처리를 합니다. 그러나 이번 분석과정에서는 ‘만족도’라는 라벨이 있어서 아래와 같은 하트나 엄지와 같은 특수문자를 따로 제외하지 않았습니다.

모델 파인 튜닝 하기

KoELECTRA 모델을 분류 문제로 파인 튜닝하기 위해서, 파인 튜닝의 입력에 제공될 토큰의 갯수인 model_max_length 를 설정을 해야 합니다. 어떤 길이가 적절한지 찾기 위해서 리뷰의 단어가 몇 개나 들어있는지 확인했습니다.

위 이미지의 X축은 단어의 개수, Y축은 후기의 개수로 60개 정도면 대부분의 문장을 포함할 수 있는 수준이라고 판단하여, model_max_lenghth 값을 64로 설정했습니다.

그래서 아래와 같은 코드로 model_max_length 를 설정 하였습니다.

tokenizer_id = 'monologg/koelectra-small-v3-discriminator' 
tokenizer = AutoTokenizer.from_pretrained(tokenizer_id, model_max_length = 64)

모델은 KoELECRA 모델을 베이스로 하여, dropout layer, custom classifier 인 linear layer 추가하여 최종 모델을 만들었습니다.

class CustomTransformerModel(nn.Module):
    def __init__(self,model ,num_labels, num_cls_vector): 
        super(CustomTransformerModel,self).__init__() 
        self.num_labels = num_labels 

        self.model = model
        self.dropout = nn.Dropout(0.1) 
        self.classifier = nn.Linear(num_cls_vector,num_labels) # load and initialize weights    

    def forward(self, input_ids=None, attention_mask=None,labels=None):
        #Extract outputs from the body
        outputs = self.model(input_ids=input_ids, attention_mask=attention_mask)

        #Add custom layers
        sequence_output = self.dropout(outputs[0]) #outputs[0]=last hidden state
	# 제공할 최종logits 계산
        logits = self.classifier(sequence_output[:,0,:].view(-1,num_cls_vector))   

        return logits

 

위의 모델 네트워크 정의를 기반으로 모델을 생성하여 가시화하면 아래와 같이 나옵니다. (내부 자세한 네트워크는 생략하였습니다) 크게 Electra Model의 (1) embeddings layer (2) 12 개의 Electra Encoder (3) Custom Classifier 로 구성되어 있습니다.

CustomTransformerModel(
  (model): ElectraModel(
    (embeddings): ElectraEmbeddings(
      (word_embeddings): Embedding(35000, 128, padding_idx=0)
      (position_embeddings): Embedding(512, 128)
      (token_type_embeddings): Embedding(2, 128)
      (LayerNorm): LayerNorm((128,), eps=1e-12, elementwise_affine=True)
      (dropout): Dropout(p=0.1, inplace=False)
    )
    (embeddings_project): Linear(in_features=128, out_features=256, bias=True)
    (encoder): ElectraEncoder(
      (layer): ModuleList(
        (0): ElectraLayer()
        (1): ElectraLayer()
        (2): ElectraLayer( )
	…
        (11): ElectraLayer()
  )
  (dropout): Dropout(p=0.1, inplace=False)
  (classifier): Linear(in_features=256, out_features=11, bias=True)
)

모델 훈련을 하기 위해서는 실제 레이블과 모델의 예측치를 가지고 Loss를 구하는 Loss 함수가 필요한데요, 하나의 상품 후기에 여러 개의 라벨을 추출하기 위한 Multi-Labeling Classification 문제를 풀기 위하여 BCEWithLogitsLoss 를 사용하였습니다.

def loss_fn(outputs, targets):
    loss = torch.nn.BCEWithLogitsLoss()
    return loss(outputs, targets)

테스트 데이터 세트로 모델 평가

모델의 파인 튜닝 후에 테스트 데이터 셋으로 라벨별로 precision, recall, f1-score를 계산을 하여 성능을 확인 하였습니다.

전체 12개의 라벨을 종합하여 76.10%의 정확도를 갖는 모델을 개발했습니다. 모델이 분류한 후기를 확인해보니 같은 ‘가격’이라고 분류된 후기라도 정말 ‘가격’의 의미를 가지고 있는 후기와 애매한 후기가 있는 것을 확인했습니다.

모델 파인 튜닝에 사용한 라벨 별 데이터 수의 비율과 f1-score 를 비교 하면, 아래의 그림 처럼 f1-score 가 0.8 이 넘는 가격, 클렌징, 향기는 모두 데이터 수의 비율이 높았습니다. 추가적인 데이터를 파인 튜닝에 사용하면. 더 나은 결과를 예상합니다.

 그리고 고객에게 노출할 때, 확실한 정보를 제공하는 것이 더 적절하겠다고 판단하여, 모델 추론시 제공하는 probability 값을 일정 threshold 이상 (예: 0.8) 인 후기만 라벨을 부여하기로 했습니다. 그 결과 약 16%의 무 라벨 후기가 생성되었고 기존 모델의 정확도보다 5%정도 향상된 결과를 만들어 냈습니다.

Why SageMaker?

Amazon SageMaker는기계학습 플랫폼으로 데이터 분석가, 데이터 사이언티스트, MLOps 엔지니어가 모두 사용하는 플랫폼입니다. 이번 모델 개발 과정은 모두 SageMaker studio 환경으로 진행했는데요. 기존에는 GPU 인스턴스를 띄워서 분석에 필요한 패키지를 개별적으로 설치하고 버전에 맞는 코드작업을 하는데, 오랜 시간이 들었습니다. 그런데 SageMaker를 활용하니 자주 사용하는 패키지와 환경이 세팅되어 있어서 번거로운 준비과정을 따로 진행하지 않아도 된다는 장점이 있었습니다. 또한 전처리/코드 개발하는 과정은 CPU 기반의 인스턴스를 사용하고 훈련에 필요한 시간 만큼만 GPU를 사용하여 효율적으로 자원을 사용할 수 있다는 장점이 있었습니다. 모델을 개발과정에서 필요한 모니터링과 평가과정에서 필요한 성능체크는 연결된 CloudWatch를 활용 SageMaker의 연결된 기능들을 활용했습니다.

SageMaker는 Amazon S3에서 데이터를 로드되면 모델의 훈련작업을 시작합니다. 모든 훈련작업이 완료되는 즉시 SageMaker는 훈련된 모델을 S3에 업로드하고 로그와 지표(Matrics)를 Amazon CloudWatch에 기록합니다. 이제 Amazon CloudWatch에 익숙하지 않은 분들도 CloudWatch는 AWS 리소스와 성능을 모니터링 합니다. 중요한 것은 모델이 S3에 저장되는 즉시 SageMaker가 훈련 작업을 종료하고 훈련 작업에 대해 프로비저닝된 모든 EC2 인스턴스를 서비스 해제하므로 훈련 작업이 실행된 시간에 대해서만 요금이 부과됨으로서 코드를 작성하는 환경은 CPU이지만 훈련과정에서만 GPU를 사용하여 비용과 속도를 모두 챙길 수 있었습니다. 물론 SageMaker는 처음 사용하는 과정에서 약간의 허들이 있는데, 이 부분은 AWS SA 분들과 별도로 진행해 준 Amazon SageMaker Workshop 교육과 프로젝트를 진행하며 받은 가이드로 해결할 수 있었습니다.

마무리

Phase 1. 단계는 비즈니스에서 요구하는 수준의 모델을 구현하는 단계로 했습니다. 이후 Phase 2. 단계에서는 추론/배포를 Pipeline으로 구성할 MLOps가 남아있습니다. 뷰티 컬리라는 새로운 서비스를 도입하며, 고객의 후기를 더 잘 분석할 수 있는 기술, 플랫폼이 필요했고 Phase1 에서는 AWS의 SageMaker와 함께 했습니다. 앞으로 Phase2 단계도 AWS SA분들과 함께 협업하여 고객의 후기를 더 잘 들을 수 있는 기술을 도입할 수 있기를 기대합니다.

Jicheol Kim

Jicheol Kim

김지철 소프트웨어 엔지니어는 프로덕트-상품탐색팀 소속으로 컬리에서 데이터 플랫폼 구축을 진행하였고 검색서비스 백엔드 개발을 담당하고 있습니다. 다양한 분야의 문제를 해결하는 것에 도전 중입니다.

Minsik Lee

Minsik Lee

이민식 데이터 분석가는 데이터농장-데이터프로덕트팀 소속으로 컬리의 다양한 데이터를 활용하여 슬랙봇, 대시보드, ML/DL 모델 등 다양한 프로덕트를 개발하고 있습니다.

Youngjin Kim

Youngjin Kim

김영진 솔루션 아키텍트는 소프트웨어 개발자, DevOps 엔지니어 및 소프트웨어 아키텍트의 경험을 통해 엔터프라이즈 고객이 높은 수준의 아키텍처 설계 선택을 하고 AWS 서비스를 사용하여 사용 사례를 구축할 수 있도록 지원하고 있으며 현재는 AWS 기술 블로그 리딩과 삼성전자 무선사업부 서비스의 AWS 클라우드 전환을 지원하는 업무를 담당하고 있습니다.

Gonsoo Moon

Gonsoo Moon

AWS AIML 스페셜리스트 솔루션즈 아키텍트로 일하고 있습니다. AI/ML 의 다양한 유스케이스 및 프러뎍션 경험을 바탕으로 고객의 AI/ML 문제를 해결하기 위해 고객과 함께 고민하고 협업하는 일을 주로 하고 있습니다. AI/ML 기술을 데이터 과학자, 개발자, 분석가 분에게 전파하여, 글로벌 및 한국 사회가 발전될 수 있게 기여를 하고자 합니다.