본문 바로가기
Data

[NLP] Word2vec 유사도분석 및 시각화 연습

by hyemjjang 2021. 8. 24.

회사에서 요구하는 기능을 구현하기 이전에 유사도 분석의 목적을 명확히 하고자 한다. 유사도 분석이란 결국 단어들 간의 유사도를 구하는 것이고, 그렇기에 단순히 그 유사도를 구하는 것은 의미가 없기 때문이다.

 

회사에서 부여한 과제는 정확하게 말하면 유사도 분석을 통한 카테고라이징이다.

처음 회사가 내게 언급한 과제는 '유사도분석' 이 한 가지 뿐이었다^^;; 추가적으로 언급한 것은 '상품 입력 시 대분류 및 중분류를 출력하게 만드는 것'. 결국 유사도분석을 통해 카테고라이징을 구현하라는 뜻이라고 생각했다. 

다만 헷갈리는 부분은, 유사도를 분석하여 가까운 단어끼리 군집화하고, 해당 군집을 라벨링하라는 뜻인지? 그리고 그런 식으로 중분류와 대분류를 함께 출력하려면 어떻게 해야하는지이다. 이럴 땐 차라리 이 프로젝트에 대해 잘 아는 윗사람이 계셨으면 하지만 적어도 이 회사엔 존재하지 않는다는 점....ㅎ 

어쨌든 일단 해 봐야 어떤 것인지 알 것 같아 해보면서 정리하고자 한다. 블로그를 쓰는 이유도 바로 이것임..


유사도분석을 통한 군집화라는 개념에 꽂혀있으시기 때문에 Word2vec을 통해 '상품명' 변수의 단어들에 대한 유사도 분석 및 군집화를 시도하였다

코드는 많은 자료를 참고하였다....

 

주로 참고한 블로그

 

모듈 설치

!pip install sklearn
!pip install konlpy
!pip install gensim
!pip install nltk
!pip install soynlp
!pip show soynlp
!pip install gensim
!pip show gensim

# 그래프에 retina display 적용
%config InlineBackend.figure_format = 'retina'

# 나눔고딕 설치
#!apt -qq -y install fonts-nanum > /dev/null
import matplotlib.font_manager as fm
fontpath = '/usr/share/fonts/truetype/nanum/NanumBarunGothic.ttf'
font = fm.FontProperties(fname=fontpath, size=9)

import (구질구질함 주의..)

%matplotlib inline
import nltk
nltk.download('punkt')
import pandas as pd
import matplotlib.pyplot as plt
import urllib.request
from konlpy.tag import Okt
import csv
import warnings
warnings.filterwarnings("ignore")
from nltk.tokenize import word_tokenize, sent_tokenize
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import BernoulliNB
import pickle
import re
import numpy as np
import konlpy

코랩에서 드라이브 연결

from google.colab import drive
drive.mount('/content/drive')
data = pd.read_excel("drive/MyDrive/similar_data.xlsx", sheet_name='요식')
print(data)

전처리

def preprocessing(text):
    # 개행문자 제거
    text = re.sub('\\\\n', ' ', text)
    # 특수문자 제거
    # 특수문자나 이모티콘 등은 때로는 의미를 갖기도 하지만 여기에서는 제거했습니다.
    # text = re.sub('[?.,;:|\)*~`’!^\-_+<>@\#$%&-=#}※]', '', text)
    # 한글, 영문, 숫자만 남기고 모두 제거하도록 합니다.
    # text = re.sub('[^가-힣ㄱ-ㅎㅏ-ㅣa-zA-Z0-9]', ' ', text)
    # 한글, 영문만 남기고 모두 제거하도록 합니다.
    text = re.sub('[^가-힣ㄱ-ㅎㅏ-ㅣa-zA-Z]', ' ', text)
    return text
    
    %time sentences = data['상품'].apply(preprocessing)

토크나이징

from soynlp.tokenizer import RegexTokenizer

tokenizer = RegexTokenizer()
tokenizer

%time tokens = sentences.apply(tokenizer.tokenize)
tokens[:]

Word2vec 모델 학습에 로그 찍도록

import logging
logging.basicConfig(
    format='%(asctime)s : %(levelname)s : %(message)s', 
    level=logging.INFO)

모델 학습

# 초기화 및 모델 학습
from gensim.models import word2vec

# 모델 학습
%time model = word2vec.Word2Vec(tokens, min_count=1)

model

모델 이름 지정, 저장

model_name = '1minwords'
model.save(model_name)

각종 word2vec 기능

# 단어 사전 수
len(model.wv.vocab)

# 단어 사전에서 상위 10개만 보기
vocab = model.wv.vocab
sorted(vocab, key=vocab.get, reverse=True)[:30]

# Counter로 자주 등장하는 단어 보기
from collections import Counter
dict(Counter(vocab).most_common(20))

# 가장 적게 등장하는 단어
min(vocab, key=vocab.get)

model.wv['아이스']
model.wv.most_similar('아이스')
model.wv.similarity('딸기', '아이스')

이제부터는 Word2vec 시각화를 해 볼 것이다

 

나는 konlpy를 사용하여 한국어로 모델학습 시켰기 때문에, 나눔고딕을 설치해야 그래프에 요소가 제대로 표출된다

# 그래프에 retina display 적용
%config InlineBackend.figure_format = 'retina'

# 나눔고딕 설치
#!apt -qq -y install fonts-nanum > /dev/null
import matplotlib.font_manager as fm
fontpath = '/usr/share/fonts/truetype/nanum/NanumBarunGothic.ttf'
font = fm.FontProperties(fname=fontpath, size=9)
!sudo apt-get install -y fonts-nanum
!sudo fc-cache -fv
!rm ~/.cache/matplotlib -rf
from sklearn.manifold import TSNE 
from sklearn.decomposition import PCA 
import matplotlib as mpl 
import matplotlib.pyplot as plt 
import pandas as pd 
from gensim.models import KeyedVectors 

# 그래프에서 마이너스 폰트 깨지는 문제에 대한 대처 
mpl.rcParams['axes.unicode_minus'] = False 

plt.rc('font', family='NanumBarunGothic') 
def show_tsne(): 
  tsne = TSNE(n_components=2) 
  X = tsne.fit_transform(X_show) 
  df = pd.DataFrame(X, index=vocab_show, columns=['x', 'y']) 
  fig = plt.figure() 
  fig.set_size_inches(30, 20) 
  ax = fig.add_subplot(1, 1, 1) 
  ax.scatter(df['x'], df['y']) 
  
  for word, pos in df.iterrows(): 
    ax.annotate(word, pos, fontsize=10) 
    
  plt.xlabel("t-SNE 특성 0") 
  plt.ylabel("t-SNE 특성 1") 
  plt.show() 
  
def show_pca(): 
  # PCA 모델을 생성합니다 
  pca = PCA(n_components=2) 
  pca.fit(X_show) 
  # 처음 두 개의 주성분으로 숫자 데이터를 변환합니다 
  
  x_pca = pca.transform(X_show) 
  
  plt.figure(figsize=(30, 20)) 
  plt.xlim(x_pca[:, 0].min(), x_pca[:, 0].max()) 
  plt.ylim(x_pca[:, 1].min(), x_pca[:, 1].max()) 
  
  for i in range(len(X_show)): 
    plt.text(x_pca[i, 0], x_pca[i, 1], str(vocab_show[i]), 
             fontdict={'weight': 'bold', 'size': 9}) 
    
  plt.xlabel("첫 번째 주성분") 
  plt.ylabel("두 번째 주성분") 
  plt.show()

model_name = '1minwords'
model = g.Doc2Vec.load(model_name)

vocab = list(model.wv.vocab) 
X = model[vocab] 

# sz개의 단어에 대해서만 시각화 
sz = 800 
X_show = X[:sz,:] 
vocab_show = vocab[:sz] 

show_tsne() 

show_pca()

이 코드를 실행하면 결과는 이렇게 나온다

 

상품명이라는 데이터 특성 상 단순한 Word2vec만으로는 유사도를 탐색하기 굉장히 힘들다. 결과도 별로 유의미해 보이지 않고.. 화이트리스트를 적용한다고 해도 흠.. 일일히 지정해줄 수 없는 노릇이고.

 

이 부분에 대해서는 정말 많은 공부가 필요할 듯 하다^^;;