[Python] 머신러닝 완벽가이드 - 08. 텍스트 분석[문서 유사도]

Updated:

파이썬 머신러닝 완벽가이드 교재를 토대로 공부한 내용입니다.

실습과정에서 필요에 따라 내용의 누락 및 추가, 수정사항이 있습니다.


기본 세팅

import numpy as np
import pandas as pd

import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns

import warnings
%matplotlib inline
%config InlineBackend.figure_format = 'retina'

mpl.rc('font', family='NanumGothic') # 폰트 설정
mpl.rc('axes', unicode_minus=False) # 유니코드에서 음수 부호 설정

# 차트 스타일 설정
sns.set(font="NanumGothic", rc={"axes.unicode_minus":False}, style='darkgrid')
plt.rc("figure", figsize=(10,8))

warnings.filterwarnings("ignore")

6. 문서 유사도

6.1 문서 유사도 이해하기

문서 간의 유사도 비교는 일반적으로 코사인 유사도를 사용한다.

코사인 유사도는 벡터와 벡터 간의 유사도를 비교할 때 벡터의 크기보다는 상호 방향성이 얼마나 유사한지에 기반한다.

두 벡터 A와 B의 내적 값은 다음과 같이 표현 가능하다.

\[A\cdot B = \Vert A \Vert \Vert B \Vert \cos\theta\]

따라서 유사도 $\cos\theta$는 다음과 같다.

\[\text{Similarity} = \cos\theta = \dfrac{A\cdot B}{\Vert A \Vert \Vert B \Vert} = \dfrac{\sum_{i}^n A_{i}B_{i}}{\sqrt{\sum_{i}^n A_{i}^2}\sqrt{\sum_{i}^n B_{i}^2}}\]

간단한 문서에 대해 코사인 유사도를 구해보자.

# 두 벡터의 코사인 유사도
def cos_similarity(v1, v2):
    dot_product = v1 @ v2
    ab_norm = np.sqrt(sum(np.square(v1))) * np.sqrt(sum(np.square(v2)))
    similarity = dot_product / ab_norm
    
    return similarity
from sklearn.feature_extraction.text import TfidfVectorizer

doc_list = ['if you take the blue pill, the story ends' ,
            'if you take the red pill, you stay in Wonderland',
            'if you take the red pill, I show you how deep the rabbit hole goes']

tfidf_vect = TfidfVectorizer()
feature_vect = tfidf_vect.fit_transform(doc_list)

print(feature_vect.shape)
(3, 18)
  • 3개의 간단한 문서를 TF-IDF 피처 벡터화하였다.

  • 각 문서별로 18개의 word 피처를 가지고 있다.

# Sparse Matrix형태를 Dense Matrix로 변환
feature_vect_dense = feature_vect.todense() # toarray()도 가능

# 첫 번째, 두 번째 문서 피처 벡터 추출
vect1 = np.array(feature_vect_dense[0]).reshape(-1,)
vect2 = np.array(feature_vect_dense[1]).reshape(-1,)

# 코사인 유사도 
similarity_simple = cos_similarity(vect1, vect2 )
print(f"문서 1, 문서 2 Cosine 유사도: {similarity_simple:.3f}")
문서 1, 문서 2 Cosine 유사도: 0.402
  • 문서 1과 문서 2의 코사인 유사도는 0.402로 나타난다.

  • 다른 문서끼리도 같은 방법으로 코사인 유도를 구할 수 있다.

  • 이번엔 이를 보다 편하게 구할 수 있는 사이킷런 패키지를 사용해보자.

from sklearn.metrics.pairwise import cosine_similarity

similarity_simple_pair = cosine_similarity(feature_vect[0] , feature_vect[1:])
print(similarity_simple_pair)
[[0.40207758 0.40425045]]
  • sklearn.metrics.pairwisecosine_similarity()를 이용하여 코사인 유사도를 구할 수 있다.

  • 인수로는 희소 행렬, 밀집 행렬, 배열 등 모두 가능하여 따로 변환이 필요없다.

  • 문서 1과 문서 2, 문서 1과 문서 3의 유사도를 구하였다.

similarity_simple_pair = cosine_similarity(feature_vect , feature_vect)
print(similarity_simple_pair)
[[1.         0.40207758 0.40425045]
 [0.40207758 1.         0.45647296]
 [0.40425045 0.45647296 1.        ]]
  • 상관관계 행렬처럼 모든 문서의 쌍(pair)로 코사인 유사도를 구할 수 있다.

6.2 Opinion Review 실습

Opinion Review 데이터로 문서 유사도를 구해보자.

데이터, 데이터 불러오기, 피처 벡터화, K-Means는 모두 이전 포스팅 문서 군집화와 동일하다.

6.2.1 데이터 불러오기

import glob, os

# 경로 지정 (r string으로 탈출문자 그대로 인식)
path = r'C:\Users\ekzm3\Desktop\Github_kkd\Python_Study_ML\08.텍스트분석\OpinosisDataset1.0\topics'

# path에 존재하는 .data 파일들의 파일명을 리스트로 취합
all_files = glob.glob(os.path.join(path, "*.data"))

filename_list = []
opinion_text = []

for file in all_files:
    # 경로 등 제거 후 순수 파일명만 저장
    filename_ = file.split('\\')[-1]
    filename = filename_.split('.')[0]
    filename_list.append(filename)
    
    # 각 파일 데이터 프레임으로 생성 후 to_string으로 text화
    df = pd.read_table(file, index_col=None, header=0, encoding='latin1')
    opiniontext = df.to_string().replace("   ", "") # 첫 공백 제거
    opinion_text.append(opiniontext)
    
# 파일명, 파일내용을 데이터 프레임으로 생성
document_df = pd.DataFrame({'filename':filename_list, 'opinion_text':opinion_text})

6.2.2 피처 벡터화

tokenizer 함수

from nltk.stem import WordNetLemmatizer
import nltk
import string

# 단어 원형 추출 함수
lemmar = WordNetLemmatizer()
def LemTokens(tokens):
    return [lemmar.lemmatize(token) for token in tokens]

# 특수 문자 사전 생성: {33: None ...}
# ord(): 아스키 코드 생성
remove_punct_dict = dict((ord(punct), None) for punct in string.punctuation)

# 특수 문자 제거 및 단어 원형 추출
def LemNormalize(text):
    # 텍스트 소문자 변경 후 특수 문자 제거
    text_new = text.lower().translate(remove_punct_dict)
    
    # 단어 토큰화
    word_tokens = nltk.word_tokenize(text_new)
    
    # 단어 원형 추출
    return LemTokens(word_tokens)
from sklearn.feature_extraction.text import TfidfVectorizer

tfidf_vect = TfidfVectorizer(stop_words='english' , ngram_range=(1,2), 
                             tokenizer = LemNormalize, min_df=0.05, max_df=0.85)

# 피처 벡터화: TF-IDF
feature_vect = tfidf_vect.fit_transform(document_df['opinion_text'])

6.2.3 K-Means(3)

from sklearn.cluster import KMeans

# KMeans: 3
km_cluster = KMeans(n_clusters=3, max_iter=10000, random_state=0)
km_cluster.fit(feature_vect)

# cluster 및 중심 좌표 정보
cluster_label = km_cluster.labels_
cluster_centers = km_cluster.cluster_centers_

# cluster 라벨 추가
document_df['cluster_label'] = cluster_label
document_df.head()
filename opinion_text cluster_label
0 accuracy_garmin_nuvi_255W_gps , and is very, very accurate .\n0but for the m... 0
1 bathroom_bestwestern_hotel_sfo The room was not overly big, but clean and ve... 1
2 battery-life_amazon_kindle After I plugged it in to my USB hub on my com... 0
3 battery-life_ipod_nano_8gb short battery life I moved up from an 8gb .... 0
4 battery-life_netbook_1005ha 6GHz 533FSB cpu, glossy display, 3, Cell 23Wh... 0

6.2.4 문서 유사도

from sklearn.metrics.pairwise import cosine_similarity

# cluster_label=1(호텔)인 인덱스
hotel_indexes = document_df[document_df['cluster_label']==1].index

# 호텔 군집 중 첫 번째 문서 파일명
comparison_docname = document_df.iloc[hotel_indexes[0]]["filename"]
print("호텔 군집 첫 문서:", comparison_docname)

# 호텔 군집 첫 번째 문서와 호텔 군집 문서 전체간의 코사인 유사도
similarity_pair = cosine_similarity(feature_vect[hotel_indexes[0]] , feature_vect[hotel_indexes])
print(similarity_pair)
호텔 군집 첫 문서: bathroom_bestwestern_hotel_sfo
[[1.         0.04419259 0.05058336 0.0646409  0.05989081 0.06286964
  0.03761874 0.1206444  0.38005031 0.32437906 0.51782913 0.11250381
  0.1385535  0.13647283 0.09744676 0.07216368]]
  • 군집 1은 호텔에 대한 리뷰로 군집화되어 있다.

  • index를 활용해 호텔 군집 첫 번째 문서, 호텔 군집 문서 전체의 코사인 유사도를 확인하였다.

  • 이를 조금 더 직관적으로 보기 위해 시각화 해보자.

# 호텔 군집 문서 파일명 데이터 프레임
hotel = document_df.iloc[hotel_indexes][["filename"]].reset_index(drop=True)

# 코사인 유사도 추가
hotel["similarity"] = similarity_pair.reshape(-1,1)

# 첫 번째 문서 제외 후 내림차순 정렬
hotel_fin = hotel.iloc[1:].sort_values(by="similarity", ascending=False)

# 시각화
sns.barplot(x="similarity", y="filename", data=hotel_fin)
plt.title(comparison_docname)
plt.show()

png

  • 첫 번째 문서가 어떤 문서와 유사한지 보다 편하게 확인 가능하다.

  • 교재 코드가 복잡해서 간략하게 수정하였다.

Leave a comment