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