본문 바로가기

NPL with ML

문서 유사도 측정 - 코사인 유사도(Cosine Similarity)와 실전 연습 코드

반응형

문서와 문서 간의 유사도 비교는 일반적으로 코사인 유사도(Cosine Similarity)를 사용합니다. 코사인 유사도는 벡터와 벡터 간의 유사도를 비교할 때 벡터의 크기보다는 벡터의 상호 방향성이 얼마나 유사한지에 기반합니다. 즉 코사인 유사도는 두 벡터 사이의 사잇각을 구해서 얼마나 유사한지 수치로 적용한 것입니다.

 

코사인 유사도가 문서의 유사도 비교에 가장 많이 사용되는 이유는 문서를 피처 벡터화 변환하면 차원이 매우 많은 희소 행렬이 되기 쉽습니다. 이러한 희소 행렬 기반에서 문서와 문서 벡터간의 크기에 기반한 유사도 지표는 정확도가 떨어지기 쉽습니다.  

 

또한 문서가 매우 긴 경우 단어의 빈도수도 더 많을 것이기 때문에 이러한 빈도수에만 기반해서는 공정한 비교를 할 수 없습니다.

 

- 코사인 유사도(Cosine Similirity)를 구하는 함수는 아래와 같습니다.

def cos_similiarity(v1, v2):
    dot_product = np.dot(v1, v2)
    l2_norm = (np.sqrt(sum(np.square(v1)))*np.sqrt(sum(np.square(v2))))
    similarity = dot_product/l2_norm

    return similarity

 

아래와 같이 3가지 문장이 있다고 하고 각 문장들간 유사도를 측정하는 Code는 다음과 같이 표시할 수 있습니다.

from sklearn.feature_extraction.text import TfidfVectorizer
import numpy as np
import Common_Module.CMNLP as CMMLP

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

thidf_vect_simple = TfidfVectorizer()
feature_vect_simple = thidf_vect_simple.fit_transform(doc_list)
# print(feature_vect_simple)
# print(feature_vect_simple.shape)

#TfidfVectorizer로 transform()한 결과는 희소 행렬이므로 밀집 행렬로 변환
feature_vect_dense = feature_vect_simple.todense()
# print(feature_vect_dense)

#첫 번쨰 문장과 두 번째 문장의 피처 벡터 추출
vect1 = np.array(feature_vect_dense[0]).reshape(-1,)
vect2 = np.array(feature_vect_dense[1]).reshape(-1,)
similirity_simple = CMMLP.cos_similiarity(vect1, vect2)
print(similirity_simple)

#첫 번쨰 문장과 세 번째 문장의 피처 벡터 추출
vect1 = np.array(feature_vect_dense[0]).reshape(-1,)
vect3 = np.array(feature_vect_dense[2]).reshape(-1,)
similirity_simple = CMMLP.cos_similiarity(vect1, vect3)
print(similirity_simple)

#두 번쨰 문장과 세 번째 문장의 피처 벡터 추출
vect2 = np.array(feature_vect_dense[1]).reshape(-1,)
vect3 = np.array(feature_vect_dense[2]).reshape(-1,)
similirity_simple = CMMLP.cos_similiarity(vect2, vect3)
print(similirity_simple)

 

사이킷런은 코사인 유사도를 측정하기 위한 cosine_similirarity API를 제공하고 있습니다.

from sklearn.metrics.pairwise import cosine_similarity
similirity_simple_pair = cosine_similarity(feature_vect_simple[0], feature_vect_simple)
print(similirity_simple_pair)

similirity_simple_pair = cosine_similarity(feature_vect_simple, feature_vect_simple)
print(similirity_simple_pair)

 

위와 같이 실행하는 경우 첫 번째 결괏값은 첫 번째 값과 나머지 두 번째, 세 번째와의 유사도를 비교합니다.  3개의 값은 각 각 (자신과의 비교, 두 번째와의 유사도, 세 번째와의 유사도)를 나타냅니다.

 

전체 feature를 넣으면 각 문장들과의 관계를 나타내고 있습니다.

[[1.         0.43792392 0.36013824]]


[[1.         0.43792392 0.36013824]
 [0.43792392 1.         0.40477917]
 [0.36013824 0.40477917 1.        ]]

 


Opinion Review의 Cosine Simiarity

앞에서 진행한 Opinion Review의 코드를 이용해 Jupyter Notebook에서 실행을 해보겠습니다.

import pandas as pd
import glob, os
from nltk import word_tokenize, sent_tokenize
from konlpy.tag import Twitter
import nltk
import string
import numpy as np
import sys  

module_path = os.path.abspath(os.path.join('..'))
if module_path not in sys.path:
    sys.path.append(module_path+"\\Common_Module")
    
import CMNLP as CMNLP
import pandas as pd
import glob, os

pd.set_option('display.max_rows', None)
pd.set_option('display.max_colwidth', None)

path = r'C:\\Users\\HANA\\PycharmProjects\\HANATOUR\\NLP\\TEXT_Example\\topic'
all_files = glob.glob(os.path.join(path,"*.data"))
filename_list = []
opinion_text = []

for file_ in all_files:
    df = pd.read_table(file_, index_col=None, header=0, encoding='latin1')
    filename_ = file_.split('\\')[-1]
    filename = filename_.split('.')[0]

    filename_list.append(filename)
    opinion_text.append(df.to_string())

document_df = pd.DataFrame({'filename':filename_list, 'opinion_text':opinion_text})
# print(document_df.head())

from sklearn.feature_extraction.text import TfidfVectorizer

tfodf_vect = TfidfVectorizer(tokenizer=CMNLP.LemNormalize, stop_words='english', ngram_range=(1,2), min_df=0.05, max_df=0.85)
feature_vect = tfodf_vect.fit_transform(document_df['opinion_text'])
# print(feature_vect)

from sklearn.cluster import KMeans

km_cluster = KMeans(n_clusters=5, max_iter=1000, random_state=0)
km_cluster.fit(feature_vect)
cluster_label = km_cluster.labels_
cluster_centers = km_cluster.cluster_centers_

document_df['cluster_label'] = cluster_label

# print(document_df.head())
# print(document_df[document_df['cluster_label']==0].sort_values(by='filename'))
# print(document_df[document_df['cluster_label']==1].sort_values(by='filename'))
# print(document_df[document_df['cluster_label']==2].sort_values(by='filename'))
# print(document_df[document_df['cluster_label']==3].sort_values(by='filename'))
# print(document_df[document_df['cluster_label']==4].sort_values(by='filename'))

cluster_centers = km_cluster.cluster_centers_
print('cluster centers shape :', cluster_centers.shape)
print(cluster_centers)

feature_names = tfodf_vect.get_feature_names()
cluster_details = CMNLP.get_cluster_details(cluster_model=km_cluster, cluster_data=document_df, feature_names=feature_names, clusters_num=5, top_n_features=10)
CMNLP.print_cluster_details(cluster_details)

 

첫 번째 문서와 다른 문서와의 유사성을 비교하였습니다.

from sklearn.metrics.pairwise import cosine_similarity

hotel_indexes = document_df[document_df['cluster_label']==1].index
print(hotel_indexes)

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)

 

하기 결과로 각 문서와의 유사성을 확인하였습니다. 단순히 숫자로만 표시해서는 직관적으로 문서가 어느 정도 유사도를 가지는지 이해하기 어려울 수 있으므로 첫 번째 문서와 다른 문서 간에 유사도가 높은 순으로 이를 정렬하고 시각화해 보겠습니다. 

[[1.         0.0430688  0.05221059 0.06189595 0.05846178 0.06193118
  0.03638665 0.11742762 0.38038865 0.32619948 0.51442299 0.11282857
  0.13989623 0.1386783  0.09518068 0.07049362]]

 

하기와 같이 그래프로 그리면 parking_bestwester_hotel_sfo가 가장 유사한 문서임을 그래프로 알 수 있습니다.

import seaborn as sns
import numpy as np
import matplotlib.pylab as plt

#첫 번째 문서와 타 문서 간 유사도가 큰 순으로 정렬한 인덱스를 추출하되 자기 자신은 제외
sorted_index = similarity_pair.argsort()[:, ::-1]
sorted_index = sorted_index[:,1:]

#유사도가 큰 순으로 hotel_indexes를 추출해 재정렬
hotel_sorted_indexes = hotel_indexes[sorted_index.reshape(-1)]

#유사도가 큰 순으로 유사도 값을 재정렬하되 자기 자신은 제외
hotel_1_sim_value = np.sort(similarity_pair.reshape(-1)[::-1])
hotel_1_sim_value = hotel_1_sim_value[1:]

#유사도가 큰 순으로 정렬된 인덱스와 유사도 값을 이용해 파일명과 유사도 값을 막대 그래프로 시각화
hotel_1_sim_df = pd.DataFrame()
hotel_1_sim_df['filename'] = document_df.iloc[hotel_sorted_indexes]['filename']
hotel_1_sim_df['similarity'] = hotel_1_sim_value

sns.barplot(x='similarity', y='filename', data=hotel_1_sim_df)
plt.title(comparison_docname)
plt.show()

 

반응형