본문 바로가기

ML Recommendation

아이템 기반 최근접 이웃 협업 필터링 실습 - MovieLens

반응형

최근접 이웃 협업 필터링은 사용자 기반과 아이템 기반으로 분류합니다. 이 중 일반적으로 추천 정확도가 더 뛰어난 아이템 기반의 협업 필터링을 구현해 보겠습니다. 

 

하기 Site에서 ml-latest-small.zip 파일을 다운로드합니다.

 

MovieLens Latest Datasets

These datasets will change over time, and are not appropriate for reporting research results. We will keep the download links stable for automated downloads. We will not archive or make available p…

grouplens.org

 

데이터 로딩 및 가공

import pandas as pd
import numpy as np

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

movies = pd.read_csv(r'C:\Users\HANA\PycharmProjects\HANATOUR\Recommendation\TEXT\movies.csv')
ratings = pd.read_csv(r'C:\Users\HANA\PycharmProjects\HANATOUR\Recommendation\TEXT\ratings.csv')
print(movies.shape)
print(ratings.shape)

print(movies.head(3))
print(ratings.head(3))

movies.csv파일은 영화에 대한 메타 정보인 title과 genres를 가지고 있는 영화 정보입니다. ratings.csv파일은 사용자별로 영화에 대한 평점을 매긴 데이터 세트입니다.

(9742, 3)
(100836, 4)
   movieId  ...                                       genres
0        1  ...  Adventure|Animation|Children|Comedy|Fantasy
1        2  ...                   Adventure|Children|Fantasy
2        3  ...                               Comedy|Romance

[3 rows x 3 columns]
   userId  movieId  rating  timestamp
0       1        1     4.0  964982703
1       1        3     4.0  964981247
2       1        6     4.0  964982224

Process finished with exit code 0

 

협업 필터링은 이 ratings.csv데이터 세트와 같이 사용자와 아이템 간의 평점(또는 다른 유형의 액션에 기반한 추천하는 시스템입니다. ratings.csv의 DataFrame인 ratings를 이용해 아이템 기반의 최근접 이웃 협업 필터링을 구현해 보겠습니다.

 

먼저 로우(행) 레벨 형태의 원본 데이터 세트를 다음 그림과 같이 모든 사용자를 로우로, 모든 영화를 칼럼으로 구성한 데이터 세트로 변경해야 합니다.

 

 

이 같은 변환은 DataFrame의 pivot_table() 함수를 이용하면 쉽게 할 수 있습니다. pivot_table() 함수는 로우 레벨의 값을 칼럼으로 변경하는데 효과적입니다.

 

ratings = ratings[['userId', 'movieId', 'rating']]
ratings_matrix = ratings.pivot_table('rating', index='userId', columns='movieId')
print(ratings_matrix.head(3))

 

상기의 의미는 로우(행) 레벨은 userId, 칼럼은 모두 movieId칼럼에 있는 값으로 칼럼 이름이 바뀌고, 데이터는 rating칼럼에 있는 값이 할당됩니다.

#기존
userId  movieId  rating  timestamp
0       1        1     4.0  964982703
1       1        3     4.0  964981247
2       1        6     4.0  964982224

#pivot_table 적용시
movieId  1       2       3       4       ...  193583  193585  193587  193609
userId                                   ...                                
1           4.0     NaN     4.0     NaN  ...     NaN     NaN     NaN     NaN
2           NaN     NaN     NaN     NaN  ...     NaN     NaN     NaN     NaN
3           NaN     NaN     NaN     NaN  ...     NaN     NaN     NaN     NaN

 

가독성을 높이기 위해 칼럼명을 movieId가 아닌 영화명 title로 변경하겠습니다. 영화명은 ratings에 존재하지 않고 movies데이터 세트에 존재합니다. ratings와 movies를 조인해 title칼럼을 가져온 뒤에 pivot_table()의 인자로 columns에 'movieId'가 아닌 'title'을 입력해 title pivot 하겠습니다. 이후 NaN은 0으로 반환합니다.

rating_movies = pd.merge(ratings, movies, on='movieId')
ratings_matrix = rating_movies.pivot_table('rating', index='userId', columns='title')

ratings_matrix = ratings_matrix.fillna(0)
print(ratings_matrix.head(3))

결과는 하기와 같습니다.

#기존
movieId  1       2       3       4       ...  193583  193585  193587  193609
userId                                   ...                                
1           4.0     NaN     4.0     NaN  ...     NaN     NaN     NaN     NaN
2           NaN     NaN     NaN     NaN  ...     NaN     NaN     NaN     NaN
3           NaN     NaN     NaN     NaN  ...     NaN     NaN     NaN     NaN

[3 rows x 9724 columns]

#가공
title   '71 (2014)  ...  À nous la liberté (Freedom for Us) (1931)
userId              ...                                           
1              0.0  ...                                        0.0
2              0.0  ...                                        0.0
3              0.0  ...                                        0.0

[3 rows x 9719 columns]

Process finished with exit code 0

 

영화 간 유사도 산출

이제 변환된 사용자-영화 평점 행렬 데이터 세트를 이용해 영화 간의 유사도를 측정하겠습니다. 영화간의 유사도는 코사인 유사도를 기반으로 하고 사이킷런의 cosine_similiarity()를 이용해 측정합니다.

 

지금 만든 ratings_matrix 데이터 세트에 cosine_similarity()를 적용하면 영화간 유사도를 측정할 수 없습니다. 이유는 cosine_similiarity() 함수는 함수는 행렬 기준으로 서로 다른 행을 비교해 유사도를 산출합니다. 그런데 ratings_matrix는 userId가 기준인 행 레벨 데이터이므로 여기에 cosine_similarity()를 적용하면 영화간의 유사도가 아닌 사용자 간의 유사도가 만들어집니다. 

 

영화를 기준으로 cosine_similarity()를 적용하려면 현재의 ratings_matrix가 행 기준이 영화가 되고 열 기준이 사용자가 돼야 합니다. 그렇게 하려면 ratings_matrix데이터의 행과 열의 위치를 변경하면 되는데, 판다스는 이 같은 전치 행렬 변경을 위해 transpose() 함수를 제공합니다. ratings_matrix에 transpose()를 적용해 행과 열을 서로 바꾼 새로운 행렬을 만들어 보겠습니다.

 

rating_movies_T = ratings_matrix.transpose()
rating_movies_T.head(3)

rating_matrix를 전치 행렬 형식으로 변경한 데이터 세트를 기반으로 영화의 코사인 유사도를 구해보겠습니다. 그리고 좀 더 직관적인 영화의 유사도 값을 표현하기 위해 cosine_similarity()로 반환된 넘 파이 행렬에 영화명을 매핑해 DataFrame으로 변환하겠습니다.

 

.. To be continue()

 

 

 

 

 

 

반응형