Cosine Similarity & Recommendation System
( 참고 : “딥러닝을 이용한 자연어 처리 입문” (https://wikidocs.net/book/2155) )
1. Cosine Similarity
아마 이미 많이들 코사인 유사도(cosine similarity)에 대해 알고 있을 것이다.
간단히 얘기해서, 두 벡터 사이의 거리(유사도)를 나타내는 것이다.
이는 nlp에서 두 word vector간에 얼마나 유사한지 계산하는데에 자주 사용된다.
아래 예시를 통해 3개의 vector[0,1,1,1], [1,0,1,1],[2,0,2,2] 가 서로 얼마나 유사한지를 확인해보겠다.
from numpy import dot
from numpy.linalg import norm
import numpy as np
def cos_sim(A,B):
return dot(A,B) / (norm(A)*norm(B))
doc1 = np.array([0,1,1,1])
doc2 = np.array([1,0,1,1])
doc3 = np.array([2,0,2,2])
2번째와 3번째 벡터의 유사도가 1 이라는 것을 확인할 수 있다. 사실 이는 우리가 코사인 유사도를 계산해보기 전에 알 수 있었다. 2번째 벡터 [1,0,1,1]의 길이만 2배를 하면, (방향은 동일) 3번째 벡터인 [2,0,2,2]가 나오는 것을 알 수 있다. (cosine similarity는 방향에만 영향을 받고, 길이에는 영향을 받지 않는다)
cos_sim(doc1,doc2), cos_sim(doc1,doc3), cos_sim(doc2,doc3)
(0.6666666666666667, 0.6666666666666667, 1.0000000000000002)
2. Recommendation System using Cosine Similarity
Cosine Similarity를 통해 서로 다른 두 벡터의 유사도를 확인할 수 있다는 점을 활용하면 추천시스템도 쉽게 만들 수 있다. 아래의 데이터는 영화 데이터로 영화의 줄거리(overview)를 담고 있다. 우리는 이 줄거리를 TF-IDF matrix로 표현하여 영화들 사이의 유사도를 구한 뒤, 영화 추천 시스템을 만들 것이다.
(1) Import Dataset
import pandas as pd
movie = pd.read_csv('movies_metadata.csv', low_memory=False)
총 45466편의 영화 데이터이다.
movie.shape
(45466, 24)
우리는 이 중 2만편의 영화를 대상으로 추천 시스템을 만들 것이다.
movie = movie.head(20000)
줄거리가 없는 영화가 135편 있다. 이 na값들을 공백으로 채워준다,
# overview of the movie ( text data )
movie['overview'] = movie['overview'].fillna(' ')
(2) TF-IDF
이 영화들의 줄거리를 TF-IDF matrix로 표현한다.
from sklearn.feature_extraction.text import TfidfVectorizer
tfidf = TfidfVectorizer(stop_words='english')
tfidf_matrix = tfidf.fit_transform(movie['overview'])
총 47478 종류의 단어가 사용된 것을 확인할 수 있다
tfidf_matrix.shape
(20000, 47487)
(3) Cosine Similarity
이렇게 구한 TF-IDF 행렬에서 우리는 코사인 유사도를 구할 수 있다 ( 20000 x 20000 행렬 )
from sklearn.metrics.pairwise import linear_kernel
cosine_sim = linear_kernel(tfidf_matrix, tfidf_matrix)
유사도 행렬의 결과는 다음과 같다.
이를 통해, 0번 영화와 1번 영화 사이에 0.0157만큼의 유사도가 있다는 것을 확인긴할 수 있다.
array([[1. , 0.01575748, 0. , ..., 0. , 0. ,
0. ],
[0.01575748, 1. , 0.04907345, ..., 0. , 0. ,
0. ],
[0. , 0.04907345, 1. , ..., 0. , 0. ,
0. ],
...,
[0. , 0. , 0. , ..., 1. , 0. ,
0.08375766],
[0. , 0. , 0. , ..., 0. , 1. ,
0. ],
[0. , 0. , 0. , ..., 0.08375766, 0. ,
1. ]])
하나의 예시로, 0번 영화가 나머지 19999편의 영화와 얼마나 유사한지를 확인해보자.
# (0번영화인 Toystory) & (1~19999영화) 사이의 similarity
cosine_sim[0]
array([1. , 0.01575748, 0. , ..., 0. , 0. ,
0. ])
(4) Recommendation
영화를 추천해주기 위한 모든 준비는 끝났다.
우선, 영화 제목을 입력하면 해당 영화가 matrix에서 몇 번째 row (index)에 있는지 알려주는 indices를 만든다.
# 영화 타이틀 입력 시, 인덱스 return
indices = pd.Series(movie.index, index=movie['title']).drop_duplicates()
indices.head()
title
Toy Story 0
Jumanji 1
Grumpier Old Men 2
Waiting to Exhale 3
Father of the Bride Part II 4
dtype: int64
다음과 같이, 영화 제목을 입력하면, 해당 영화의 줄거리와 가장 유사도가 높은 줄거리를 가진 영화 상위 10개를 보여주는 영화 추천 시스템을 만들었다!
def get_rec(title, cosine_sim=cosine_sim):
idx = indices[title]
sim_scores = list(enumerate(cosine_sim[idx]))
sim_scores = sorted(sim_scores, key=lambda x :x[1], reverse=True)
sim_scores = sim_scores[1:11]
movie_indices = [i[0] for i in sim_scores] # 가장 유사한 TOP 10영화의 index
return movie['title'].iloc[movie_indices]
get_rec('The Dark Knight Rises')
12481 The Dark Knight
150 Batman Forever
1328 Batman Returns
15511 Batman: Under the Red Hood
585 Batman
9230 Batman Beyond: Return of the Joker
18035 Batman: Year One
19792 Batman: The Dark Knight Returns, Part 1
3095 Batman: Mask of the Phantasm
10122 Batman Begins
Name: title, dtype: object