주제

1.  LDA(Latent Dirichlet Allocation)
2. 워드 클라우드
3. 자연어 처리
4. 문장 분류 문제
5. 실습

 

1.  LDA(Latent Dirichlet Allocation)

  • LDA는 문서에 내재되어 있는 토픽을 찾아가는 알고리즘
  • 전체 문서는 여러 문서들의 집합이고, 하나의 문서는 여러 개의 주제로 구성되고, 하나의 주제는 여러 단어들로 구성된다고 가정
  • 문서 내에 몇 개의 토픽이 있을지는 사용자가 정의함
  • 확률이나 비율의 집합을 분포로 표현한 것 : Dirichlet 분포
  • LDA는 문서 표면에 드러나지 않은 숨어있는 토픽의 확률 분포를 가정하고 각 단어를 토픽에 할당하는 분석 방법

1.1 LDA 알고리즘

  • 알고리즘을 결정하는 중요한 두 개의 확률값이 존재함 :
    1. 문서에 어떤 토픽이 들어있는가 P(토픽 t | 문서 d)
    2. 각 토픽에 어떤 단어가 들어있는가 P(단어 w | 토픽 t)
  • P(토픽 t | 문서 d)
    • 특정 문서 d에서 토픽 t가 차지하는 비율
    • 문서에서 각 토픽이 얼마나 중요한지 나타냄
  • P(단어 w | 토픽 t)
    • 특정 토픽 t에서 단어 w가 차지하는 비율
    • 토픽에 특정 단어가 나타낼 확률
  • P(토픽 t | 문서 d, 단어 w)
    • 특정 단어가 어떤 문서의 주제에 속할 확률
    • 어떤 단어가 문서의 주제와 얼마나 잘 맞는지를 나타냄
    • 이 값이 크면 특정 단어가 그 문서의 주제에 매우 밀접한 관련이 있음을 의미
    • LDA에서 최종적으로 유추해야하는 값이지만 직접적으로 구하기 어려우니 P(토픽 t | 문서 d) x P(단어 w | 토픽 t)를 한 값과 비례한다고 가정

 

1.2 알고리즘 적용 과정

  1. 토픽 개수 K 설정 (사용자가 지정)
  2. 문서 내 모든 단어에 무작위로 K 토픽 중 하나를 할당함
  3. 단어 w의 토픽 할당을 결정하기 위해 나머지 단어들의 할당 결과를 활용  P(토픽 t | 문서 d) xP(단어 w | 토픽 t)값을 계산
    • 이 값이 제일 커지는 t를 w에 재할당
    • 전체 문서의 모든 단어들을 대상으로 연산 진행
  4. 종료 시점에 도달할 때까지 반복
    • 종료 시점 : 더이상의 변화가 없을 때까지 혹은 정해진 업데이트 횟수 도달까지 등
  5. 최종 결과 분석
    • 토픽에 존재하는 단어를 보고 토픽이 의미하는 주제를 사용자가 정의
    • 할당된 토픽을 기준으로 문서에 존재하는 토픽을 분석

 

2. 워드 클라우드

  • 텍스트 데이터를 시각적으로 표현한 것
  • 문서나 데이터에서 가장 빈번하게 등장하는 단어들을 돋보이게 하는 법
  • 자주 등장하는 단어는 더 크고 두드러지게 표시됨

워드 클라우드 예시

2.1 워드 클라우드 생성 실습

  • 데이터는 이전에 사용한 Yelp 리뷰 데이터 사용
  • 데이터 전처리는 동일하게 진행
  • 모든 토큰을 하나의 리스트에 추가
  • WorldCloud를 이용하여 워드 클라우드 생성하고 시각화
from wordcloud import WordCloud

wordcloud = WordCloud(width=800, height=800,
                      background_color='white',
                      min_font_size=10,
                      max_font_size=200).generate(combined_text)

# 시각화
import matplotlib.pyplot as plt

plt.figure(figsize=(8, 8), facecolor=None)
plt.imshow(wordcloud, interpolation='bilinear')
plt.axis('off')
plt.tight_layout(pad=0)
plt.show()

생성된 워드 클라우드

  • food, service, good, great 등 음식에 대한 긍정적인 키워드가 주를 이루고 있다.

 

3. 자연어 처리

  • 컴퓨터가 인간의 언어를 이해하고 해석하는데 사용되는 분야
  • 글을 활용한 문제를 해결하고 향상된 사용자 경험을 제공하고자 함

3.1 자연어 처리의 다양한 문제

  • 텍스트 이해(Text Understanding)
    • 질의응답(QA)
    • 문장 이해(Reading Comprehension)
    • 정보 검색(Information Retrieval)
  • 텍스트 생성(Text Generation)
    • 문장 생성(Text Generation)
    • 요약(Text Summarization)
    • 번역(Neural Machine Translation)
  • 텍스트 분류 및 태깅(Text Classification & Tagging)
    • 문장 분류(Text Classification)
    • 개체명 인식(NER, Named Entity Recognition)
    • 품사 태깅(POS tagging, Part of Speech tagging)
  • 텍스트 관계 추출(Text Relation Extraction)
    • 문자 관계 추출(Relation Extraction)

 

3.2 주요 프레임워크

1. Natural Language Tool Kit (NLTK)

  • 전통적인 NLP 기법을 구현한 패키지 모음
  • 전처리, 딥러닝 이전의 NLP 방법들이 많이 활용됨

2. PyTorch, TorchText

  • 딥러닝에 특화된 머신 러닝 오픈 소스 라이브러리
  • TorchText는 NLP에 특화된 내부 라이브러리
  • (초기 ~ 최신) 딥러닝 모델을 쉽게 구현할 수 있는 인터페이스 제공

3. HuggingFace

  • 자연어 처리에 특화된 커뮤니티 기반의 라이브러리
  • 다양한 연구 분야(이미지, 음성, 생성 등)의 연구 성과를 공유 & 활용 가능
  • 매우 다양한 연구 결과물이 빠르게 업데이트
  • 연구 결과물을 공통된 인터페이스로 강제

 

4. 문장 분류 문제

  • 문장 분류(Sentence Classification) : 텍스트 데이터를 활용해 분류 문제를 푸는 것. 정해진 클래스 중 어떤 클래스에 속하는지를 판단
  • 텍스트의 의미를 이해하고 구조화된 방식으로 분류를 하는 것이 목표
  • 분류 이외의 다른 복잡한 문제에서 문장 분류에 특화된 기술 모델을 사용
  • 하위 문제 :
    • 감정 분석 (Sentiment Analysis)
    • 주제 분류 (Topic Classification)
    • 의도 분석 (Intent Detection)
    • 언어 감지 (Language Detection) 등 
  • 복잡한 문제를 풀기 위한 베이스 모델로 사용
    • 텍스트 요약
    • 텍스트 생성
    • 챗봇 등

 

4.1 딥러닝 모델을 활용한 문장 분류 접근

1. 순환 신경망(Recurrent Neural Network, RNN)

  • 사람이 글을 읽고 이해하는 과정을 모방해 모델을 설계
  • 단어를 하나씩 입력받고 이전에 이해한 내용을 바탕으로 새로운 정보를 생성
  • 모든 단어를 처리하는 과정까지 반복 순환
  • 마지막 생성된 정보를 바탕으로 분류 진행

2. 주의 매커니즘(Attention Mechanism)

  • 입력으로 받은 텍스트 정보에서 딥러닝 모델이 주의를 집중할 단어를 자동으로 판단
  • 집중된 단어를 바탕으로 NLP 문제를 처리
  • 단어의 정보를 모델 스스로 판단. 높은 성능
  • 분류 문제를 풀기 위해서는 문장 임베딩 값을 활용
  • 정리된 문장의 정보를 바탕으로 원하는 타겟 클래스를 예측
  • 중요도가 적용된 전반적인 문장의 의미를 생성

4.2 분류 문제를 넘어

  • 분류는 가장 기본이 되는 문제
  • 학습을 위한 데이터 준비 입장에서도 분류 데이터가 만들기 쉬움
  • 따라서 분류 문제로 데이터의 특성을 익힌 모델을 분류보다 복잡한 문제에 적용하는 상황이 많음
  • 이를 전이 학습(Transfer Learning) 혹은 파인-튜닝(Fine-Tuning)이라고 함

 

5. 실습

5.1 LDA 실습

  • Yelp 리뷰 데이터 사용
  • LDA의 결과 분석에 단어 해석이 사용되므로 단어의 레벨로 토큰을 설정함
  • 데이터 전처리 후 LDA 모델 적용
  • Document Term Matrix(DTM) 생성
    • 문서-단어 행렬로, 전처리된 데이터에서 각 단어의 빈도를 나타내는 행렬을 뜻함
    • 행의 방향으로 문서를 나타내고, 열의 방향으로 단어를 나타냄
    • Gensim의 Dictionary Class를 이용하여 어떤 열 번호(index)에 어떤 단어(term)가 들어갈지 정함
    • 단어-번호 객체를 활용하여 DTM 생성 (단어 ID, 빈도수) 형태
from gensim import corpora

# Gensim의 Dictionary 객체를 생성
dictionary = corpora.Dictionary(preproc_data)

# 문서-단어 행렬을 생성
corpus = [dictionary.doc2bow(text) for text in preproc_data]
  • LDA 적용
    • corpus : 전체 텍스트 집합
    • num_topics : 선정할 토픽의 수
    • id2word : 단어와 인덱스 데이터
    • passes : 학습 횟수
from gensim.models import ldamodel

topicK = 3
num_trains = 10

lda_model = ldamodel.LdaModel(corpus,
                              num_topics=topicK, # 선정한 토픽 수
                              id2word=dictionary,
                              passes=num_trains, # 학습 횟수
                              random_state=42)
  • 문서 별 토픽 분포 확인
# 문서 별 토픽 분포 확인
for document in corpus[:5]:
    origin_doc = [dictionary[word_idx] for word_idx, word_num in document]
    print(f'{origin_doc}에 속한 토픽의 분포는 아래와 같습니다.')
    for topic_idx, topic_dist in lda_model[document]:
        print(f'{topic_idx} 번째 토픽 : {topic_dist*100:.2f}% 확률')

토픽 분포 확인

  • 토픽 별 단어 분포 확인
# 토픽 별 단어 분포 확인
for k in range(topicK):
    print(f'{k}번째 토픽을 구성하는 단어의 분포는...')
    for word, prob in lda_model.show_topic(k, topn=5) :
        print(f'{word} : {prob*100:.2f}%', end= ' ')
    print()
    print()

토픽 별 단어 분포 확인

  • LDA 모델 결과 확인
    • 각 토픽을 구성하는 단어를 확인하여 분포도를 보고 토픽의 의미는 사람이 지정
# 원문장과 토픽 분포를 보고 해석하기

target_idx = 10

print('원 문장 : ', data[target_idx])
print('전처리 문장 : ', preproc_data[target_idx])
print('문장 내 토픽 분포 : ')
for topic_idx, prob in lda_model[corpus[target_idx]]:
    print(f'  - {topic_idx}번 토픽 : {prob*100:.2f}%')

  • 0번째 토픽 : 
    • 주요 단어 : service, good, disappoint, like, best, great, place, staff
    • 식당 서비스 품질과 고객 경험
  • 1번째 토픽 :
    • 주요 단어 : food, back, good, service, place, go, time, wait, would
    • 음식 품질과 재방문에 관련된 의사 표현
  • 2번째 토픽 :
    • 주요 단어 : place, great, restaurant, like, salad, delicious, time, get
    • 식당 전반의 분위기와 음식의 퀄리티에 대한 내용

 

5.2 HuggingFace를 이용한 딥러닝 모델의 문장 분류 실습

  • HuggingFace에서 SNS 데이터로 학습한 감정 분석 모델을 사용(링크)
  • 문장 분류에 기본이 되는 BERT 모델을 개선한 RoBERTa 모델을 기본으로 사용
  • 데이터 셋은 Yelp 데이터 사용
  • HuggingFace는 특정 모델에 맞는 전처리 코드를 제공함
    • Auto : HuggingFace 페이지 이름만으로도 목표하는 것들을 자동으로 가져올 수 있음
    • Stop words, Stemming 사용하지 않음
# 모델 가져오기
from transformers import AutoTokenizer

MODEL = f"cardiffnlp/twitter-roberta-base-sentiment-latest"
tokenizer = AutoTokenizer.from_pretrained(MODEL)
  • Token Index를 사용 → 토큰과 임베딩 벡터 사이를 이어주는 매핑값
    • 장점 : 메모리적 이득(글의 형태가 아닌 숫자의 형태)
    • 특정 토큰이 몇번째인지만 약속해두면 됨
    • 임베딩 벡터를 모델에 귀속시켜 관리가 용이함
    • 인덱스는 임베딩 벡터가 쌓여 있는 순서를 의미
  • "Wow... Loved this place." 라는 리뷰를 Pytorch 텐서 형태로 토큰화
tokenized = tokenizer(text, return_tensors='pt')
print(tokenized)

텐서 형식

  • 각 단어와 특수문자, 시작점의 공백까지 총 9개의 토큰으로 나뉨
  • 실제 토큰의 값을 글자로 확인하고 싶다면 conver_ids_tokens 함수 사용
tokenizer.convert_ids_to_tokens(tokenized['input_ids'].tolist()[0])
# 이 모델의 Token은 단어의 시작을 나타내는 글자에도 추가 처리를 진행!
  • 모델 호출
from transformers import AutoModelForSequenceClassification

model = AutoModelForSequenceClassification.from_pretrained(MODEL)
  • index를 바탕으로 임베딩 값 알아보기
    • 9개의 토큰이 입력되었고, 각 토큰은 768개짜리 벡터로 변환
# [참고] index를 바탕으로 embedding 값을 알아보기
# 모델의 구조를 바탕으로 embedding 부분만 추론 진행

emb = model.roberta.embeddings(**{'input_ids':tokenized['input_ids']})
print('Embedding 값 : ')
print(emb)

# 9개의 Token이 입력되었고,
# 각 Token은 768짜리 vector로 변환
print('Embedding 출력의 크기 : ', emb.shape)

임베딩값 확인

  • 모델 사용
    • 토큰 값으로 나온 input_ids를 key_value 형태로 입력
    • 혹은 unpacking 문법을 사용
    • 모델 사용법은 보통 모델 페이지에 나와있음!
# 모델을 사용한다면 Token 값으로 나온
# input_ids를 key_value 형태로 넣어주면 됨
output = model(input_ids = tokenized['input_ids'])

# 혹은 사용 예제에서 처럼
# Unpacking 사용도 가능
output = model(**tokenized)
  • 모델의 출력 결과가 확률의 형태로 나오지 않으면 해석하기 어려워 softmax 함수를 사용하여 확률의 형태로 변환
# 타겟하는 정보인 점수 값을 가져옴
scores = output[0][0].detach().numpy()

# 점수를 확률의 형태로 변환
# 과정에성 softmax 함수를 사용하고
# 외부 패키지의 함수를 활용

from scipy.special import softmax

scores = softmax(scores)
print(scores)

확률의 형태로 가져온 모델 점수 값

  • 모델의 학습 정보를 확인하기 위해서는 config를 확인하면 된다
  • 여기에 id2label를 보면 어떤 인덱스가 어떤 분류인지 확인할 수 있다
    • 이 경우 0은 부정적, 1은 중립적, 2는 긍정적이다
# 모델 학습 정보를 확인하기 위한 config 값 로딩

from transformers import AutoConfig
config = AutoConfig.from_pretrained(MODEL)

config

인덱스 분류 확인

  • 최종적으로 확률이 제일 큰 값의 인덱스를 바탕으로 감정을 판단한다
import numpy as np

max_prob_index = np.argmax(scores)
results = config.id2label[max_prob_index]
results

긍정적으로 분류

  • 위 과정을 함수로 표현하면 다음과 같다 :
# 최종 함수로 표현
def sentiment_analysis(sentence, tokenizer, model, config):
    tokenized = tokenizer(sentence, return_tensors='pt')
    output = model(input_ids = tokenized['input_ids'])

    scores = output[0][0].detach().numpy()
    scores = softmax(scores)

    max_prob_index = np.argmax(scores)
    results = config.id2label[max_prob_index]
    return results, max_prob_index
  • 다른 리뷰 확인
sent = data['text'][2]
res, idx = sentiment_analysis(sent, tokenizer, model, config)
print(sent)
print('-> ', idx, res)

부정적인 리뷰를 부정적이라고 분류함

  • 평가 데이터를 생성하고 추론을 진행한 결과를 다음과 같다 :
    • 실제로 부정적인 것을 긍정적으로 분류한 것은 1건
    • 실제로 긍정적인 것을 부정적이라고 분류한 것은 0건으로 아주 잘 분류했다고 볼 수 있다.

평가 결과

  • 정확도는 0.91, F1 score는 0.91로 높은 성능을 보였다.

 

 


느낀점

딥러닝 모델을 사용해서 문장 분류 실습을 진행해보니 확실히 이커머스 분야에서 많이 사용될 수 있는 스킬인 것 같았다. 고객 리뷰를 딥러닝 모델을 통해서 분류해낼 수 있으면 일의 효율도 오르고, 결과를 토대로 서비스 개선을 이루어내기 좋을 것 같다. HuggingFace는 처음 접해봤는데 예전에 리액트 라이브러리 찾아서 다운 받던 것처럼 다양한 모델들이 있는 것 같다. 시간날때 한번씩 둘러보면 좋을 것 같다는 생각이 들었다.

+ Recent posts