이 글은 아래 링크의 글에 이어서 작성되는 글입니다.

https://dsbook.tistory.com/80

 

자연어 처리를 이용한 IMDB 영화 리뷰 감정분석-Part 2

이 글은 아래 링크의 글에 이어서 작성되는 글입니다. https://dsbook.tistory.com/74 앞서 우리는 리뷰 텍스트를 정제한 후 BoW기법을 이용하여 데이터를 벡터화 해준 뒤 랜덤 포레스트를 이용해 학습 및

dsbook.tistory.com

 part2 에서는 Word2Vec를 이용해 모델을 만든 후 단순히 벡터들의 평균을 이용하여 정제한 리뷰데이터를 벡터화 해주었다면
 part3 에서는 Word2Vec은 의미론적으로 연관된 단어의 클리스터(군집)을 만들기 때문에 , 클리스터 내 단어의 유사성을 이용할 수 있다. 이러한 방식으로 벡터를 그룹화하는 것을 "벡터 양자화"(vector quantization)라고 한다. 이를 위해서는 우선  K-Means와 같은 클러스터링 알고리즘을 사용하여 할 수 있는 워드클러스터의 중심을 찾아야 한다.

k-평균 알고리즘(K-means algorithm)이란?

-k-평균 알고리즘 위키백과

Word2Vec을 이용하면 단어를 벡터로 표시 할 수 있고 유사한 단어끼리 모여있는 그룹을 만든다. 이를 클리스터링이라고 한다. 

아래는 k-means가 실행되는 과정을 그림으로 나타낸 것이다.

출처:https://ko.wikipedia.org/wiki/K-%ED%8F%89%EA%B7%A0_%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98

그림으로 보는 클리스터링 순서

출처:By Incheol - 자작, CC BY-SA 4.0, https://commons.wikimedia.org/w/index.php?curid=39732666

점들이 하나의 데이터이고 세모가 중심점(cetroid)을 의미한다.

K-means 클러스터링에서 좋은 성능을 내기 위해서 가장 중요한 "k"값(클러스터의 개수)를 정하는 것이다.
적절한 k값을 주었는지에 따라 성능이 천차만별로 달라진다.

적절한 클러스터의 수를 구하는 방법은 여러가지 방법이 있다.
-Rule of thumb
-Elbow Method
-정보 기준 접근법

K-means 클러스터링으로 데이터 묶기

먼저 필요한 라이브러리들을 import해주자

import numpy as np
from gensim.models import Word2Vec 
from sklearn.ensemble import RandomForestClassifier

import pandas as pd
from bs4 import BeautifulSoup
import re
import time

from nltk.corpus import stopwords
import nltk.data
import warnings
warnings.filterwarnings('ignore')

part2 에서 Word2Vec로 학습한 모델을 불러오자

model = Word2Vec.load('300features_40minwords_10text')
model

 

Part 2에서 훈련된 Word2Vec 모델은 어휘의 각 단어에 대한 특징(feature) 벡터로 구성되어 있으며, "syn0"이라고 불리는 numpy 배열에 저장되어 있다.

#syn0의 type을 알아보자
type(model.wv.syn0)

# syn0의 행의 수는 모델 어휘의 단어 수,열의 수는 part2에서 설정한 feature 벡터의 크기이다.
model.wv.syn0.shape

part2 에서 size=300을 해주었기 때문에 열의 수가 300이고총 11986개의 단어가 모델에 들어가 있음을 알 수있다.

아래 코드를 통해 개별 단어 벡터에 접근 할 수 있다.

# 개별 단어 벡터 접근
model.wv['flower'].shape

feature가 300개인 모습

맨 앞 10개만 출력해보았다.

model.wv['flower'][:10]

이제 K-means를 적용하자.
우리는 sklean에서 지원하는 KMeans 모듈을 이용할 것이다. 여기서 가장 중요한 변수인 얼마나 많은 클러스터를 생성할 지 정해 주어야 하는데 캐글 튜토리얼에서는 많은 시행착오를 겪어 클러스터당 평균 5단어 내외에 불과한 작은 클러스터가 단어가 많은 대형 클러스터 보다 더 좋은 성능을 가진다고 한다. 참고로 K-means 클러스터링은 시간이 꽤 걸린다.

from sklearn.cluster import KMeans
# 단어 벡터에서 k-means를 실행하고 일부 클러스터를 찍어본다.
start = time.time() # 시작시간

# "k"(num_clusters)를 어휘 크기의 1/5로 설정하거나 클러스터당 평균 5단어로 설정한다.
word_vectors = model.wv.syn0 # 어휘의 feature vector
num_clusters = word_vectors.shape[0] / 5  # 1/5로 설정
num_clusters = int(num_clusters) # int 형태로 변환

# K means 를 정의하고 학습시킨다.
kmeans_clustering = KMeans( n_clusters = num_clusters )
idx = kmeans_clustering.fit_predict( word_vectors )

# 끝난시간-시작시간
end = time.time()
elapsed = end - start
print("Time taken for K Means clustering: ", elapsed, "seconds.")

이제 우리는 클러스터를 만들었고 이 클러스터의 어떤 단어들이 포함되어있는지 알 수 있다. 이때 클러스터를 만들때 
램덤 시드를 이용하기 때문에 새로 학습할때마다 달라진다.

# 각 어휘 단어를 클러스터 번호에 매핑되게 word/Index 사전을 만든다.
idx = list(idx)
names = model.wv.index2word
word_centroid_map = {names[i]: idx[i] for i in range(len(names))}
#     word_centroid_map = dict(zip( model.wv.index2word, idx ))

#클러스터의 맨 처음 10개를 출력
for cluster in range(0,10):
    # 클러스터 번호를 출력
    print("\nCluster {}".format(cluster))
    
    # 클러스터번호와 클러스터에 있는 단어를 찍는다.
    words = []
    for i in range(0,len(list(word_centroid_map.values()))):
        if( list(word_centroid_map.values())[i] == cluster ):
            words.append(list(word_centroid_map.keys())[i])
    print(words)

모든 cluster가 학습이 잘 되어 명확하게 단어가 구분 된것은 아니지만 cluster 3에 dad,mum,grandma 등등 비슷한 종류의 단어가 묶여있고 cluster 9에는 다양한 도시,지역명이 묶여 있는것을 확인 할 수 있다.

클러스터링을 이용한 벡터화

이제 학습한 클러스터링을 이용하여 train ,test의 리뷰데이터를 정제하고 정제한 데이터를 벡터화 할 것이다.

train = pd.read_csv('nlp_data/labeledTrainData.tsv', 
                    header=0, delimiter='\t', quoting=3)
test = pd.read_csv('nlp_data/testData.tsv', 
                   header=0, delimiter='\t', quoting=3)
from KaggleWord2Vec import KaggleWord2Vec
# train 리뷰 데이터 정제
clean_train_reviews = []
for review in train["review"]:
    clean_train_reviews.append(
        KaggleWord2Vec.review_to_wordlist( review, \
        remove_stopwords=True ))
# test 리뷰 데이터 정제
clean_test_reviews = []
for review in test["review"]:
    clean_test_reviews.append(
        KaggleWord2Vec.review_to_wordlist( review, \
        remove_stopwords=True ))
# bags of centroids 생성
# 속도를 위해 centroid 학습 세트 bag을 0값을 가지는 벡터로 미리 할당 한다.
train_centroids = np.zeros((train["review"].size, num_clusters), \
    dtype="float32" )

train_centroids[:5]

# centroid 는 두 클러스터의 중심점을 정의 한 다음 중심점의 거리를 측정한 것
def create_bag_of_centroids( wordlist, word_centroid_map ):
    
    # 클러스터의 수는 word / centroid map에서 가장 높은 클러스트 인덱스와 같다.
    num_centroids = max( word_centroid_map.values() ) + 1
    
    # 속도를 위해 bag of centroids vector를 0값을 가지는 벡터로 미리 할당한다.
    bag_of_centroids = np.zeros( num_centroids, dtype="float32" )
    

    #루프를 돌면서 단어가 사전(word_centroid_map)에 있다면
    #단어가 속한 클러스터를 찾고 해당 클러스터의 수를 하나씩 증가 시킨다.
    for word in wordlist:
        if word in word_centroid_map:
            index = word_centroid_map[word]
            bag_of_centroids[index] += 1
    
    # bag of centroids를 반환
    return bag_of_centroids
# 정제한 trian 리뷰를 bags of centroids 로 변환한다.
counter = 0
for review in clean_train_reviews:
    train_centroids[counter] = create_bag_of_centroids( review, \
        word_centroid_map )
    counter += 1

# 정제한 test 리뷰도 같은 방법으로 bags of centroids 로 변환한다.
test_centroids = np.zeros(( test["review"].size, num_clusters), \
    dtype="float32" )

counter = 0
for review in clean_test_reviews:
    test_centroids[counter] = create_bag_of_centroids( review, \
        word_centroid_map )
    counter += 1

학습 및 예측

이제 벡터화 된 데이터를 이용해 랜덤 포레스트로 학습 후 예측하자 이 과정은 part1,part2과정과 똑같으므로
설명은 생략 하겠다.

forest = RandomForestClassifier(n_estimators = 100)
print("Fitting a random forest to labeled training data...")
%time forest = forest.fit(train_centroids, train["sentiment"])
from sklearn.model_selection import cross_val_score
%time score = np.mean(cross_val_score(\
    forest, train_centroids, train['sentiment'], cv=10,\
    scoring='roc_auc'))

score

result = forest.predict(test_centroids)
output = pd.DataFrame(data={"id":test["id"], "sentiment":result})
output.to_csv('nlp_data/part3_{0:.5f}.csv'.format(score),index=False, quoting=3 

캐글 제출 결과

577팀중 495등 정도인 모습이다.

Word2Vec 기법보다 BoW기법이 점수가 더 잘 나오는 이유

 가장 큰 이유는 우리의 튜토리얼에서 벡터를 평균화하여 센트로이드를 사용하면 단어 순서가 없어져 BoW기법의 개념과 매우 유사하기 때문이다. 성능이 유사하다는 사실(표준 오차 범위 이내)은 세 가지 방법을 모두 실질적으로 동등하게 만들게 된다.

 첫째 훨씬 많은 텍스트로 학습을 해야한다. 구글에서는 10억 개 이상의 단어 말뭉치를 기반으로 하는데 우리의 학습 데이터는 1800만 단어 정도 밖에 되지 않는다. 만약 원한다면 Word2Vec은 Google의 원래 C도구에서 출력되는 사전 학습 된 모델을 가져오는 기능을 제공하기 때문에 C에서 학습시킨 모델을 Python으로 가져올 수도 있다.

 둘째 현재 여러 출판된 문헌,논문에서는 분산 워드 벡터 기법은 Bag of Words 모델보다 우수한 것으로 나타났다. 이 논문에서는 IMDB 데이터 집합에 문단 벡터 (Paragraph Vector)라는 알고리즘을 사용하여 현재까지의 최첨단 결과 중 일부를 생성한다. 이때 벡터 평균화와 군집화는 단어 순서 정보를 보존하는 반면, 문단 벡터는 단어 순서 정보를 보존하기 때문에 우리가 여기서 시도하는 접근법보다 더 효과적이다.

튜토리얼에서 진행한 기법들보다 뛰어난 NLP 관련 기법들이 많으니 다양한 방법으로 시도해보길 바란다.

728x90
반응형
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 라이프코리아트위터 공유하기
  • shared
  • 카카오스토리 공유하기