본문 바로가기

문돌이 존버/데이터 분석

서프라이즈 SVD Collaborative Filtering 파이썬 예제

반응형

구글 코랩을 사용했기 때문에 아래 코드를 통해 드라이브 접근 권한을 허용합니다. 

from google.colab import drive
drive.mount('/content/drive')

일단 기본적으로 필요한 모듈을 불러옵니다. 

import pandas as pd 
import numpy as np
import re
import math
import matplotlib.pyplot as plt
import seaborn as sns

데이터는 캐글이든, 다른 곳이든 어디에서나 볼 수 있는 영화 평점 데이터를 사용했습니다.  

rating_data = pd.read_csv('/content/drive/My Drive/Colab Notebooks/RS/ratings.csv')
movie_data = pd.read_csv('/content/drive/My Drive/Colab Notebooks/RS/movies.csv')

평점 데이터와 영화 정보 데이터를 살펴봅시다. 

print(rating_data.shape)
rating_data.head()

print(movie_data.shape)
movie_data.head()

위의 두 데이터를 합쳐보겠습니다. 이때 판다스의 merge 함수를 사용하면 특정 컬럼 기준으로 세로로 합쳐줍니다. 이때 어떤 데이터를 기준으로 할지에 따라 merge() 안에 먼저 써주면 됩니다. 저는 rating_data 를 기준으로 했습니다. 

df_combined = pd.merge(rating_data, movie_data, on='movieId')
print(df_combined.shape)
df_combined.head()

시각화를 통해 데이터를 자세히 살펴보려고 합니다. 먼저 장르 분포를 알아보기 위해 아래 함수를 통해 "|" 기호를 제거합니다. 

genres = {}
def find_genres():
  for genre in movie_data['genres']:
    words = genre.split('|')
    for word in words:
      genres[word] = genres.get(word, 0) + 1 # dictionary 안에 word가 없으면 0으로 초기화, 이후 반복되면 + 1

find_genres()
plt.figure(figsize=(13, 5))
key_list = []
value_list = []
for key, value in genres.items():
  key_list.append(key)
  value_list.append(value)
plt.bar(range(len(genres)), value_list)
plt.xticks(range(len(genres)), key_list, rotation=50)
plt.show()

드라마, 코미디, 스릴러, 액션, 로맨스 순으로 많은 영화가 존재하네요. 

아래는 영화별로 평균 평점이 얼마인지, 총 몇 명이 평점을 매겼는지 확인한 것입니다. 

df_n_ratings = pd.DataFrame(df_combined.groupby('title')['rating'].mean())
df_n_ratings['total ratings'] = pd.DataFrame(df_combined.groupby('title')['rating'].count())
df_n_ratings.rename(columns={'rating': 'mean_rating'}, inplace=True)
df_n_ratings.sort_values('total ratings', ascending=False).head(10)

# 많은 영화의 평균 평점이 3~4점 주변에 위치함
plt.figure(figsize=(8, 4))
sns.displot(df_n_ratings['mean_rating'], bins=30, kde=True)
plt.xlabel('Mean Ratings')
plt.ylabel('Probability')
plt.show()

sns.jointplot(x = 'mean_rating', y = 'total ratings', data = df_n_ratings)

위 그래프의 각 노드는 영화를 가리키며, total ratings(y축)를 살펴보면 대부분의 영화가 50개 미만의 평점을 받았고, mean_rating을 살펴보면 대체적으로 3~4점 사이의 평점을 기록하고 있음을 알 수 있다.


본격적으로 추천 모델을 생성해보겠습니다. 저는 사이킷런의 surprise 모듈을 사용했고, 설치가 안되어있다면 아래 명령어를 통해 설치해줍니다.  

!pip install scikit-surprise
from surprise import Reader, Dataset, SVD
from surprise.model_selection import GridSearchCV, cross_validate

surprise의 함수 Reader, Dataset을 통해 모델에 사용할 데이터 형식을 맞춰줍니다. 파라미터는 공식 홈페이지에서 참고해주세요.

Reader: 데이터 형식이 아래 구조를 따라야 한다
user ; item ; rating ; [timestamp]
reader = Reader(rating_scale=(0.5, 5)) # 소수점 없는 평점이라면 (1, 5)
data = Dataset.load_from_df(rating_data[['userId', 'movieId', 'rating']], reader=reader)

surprise의 순수 SVD() 함수를 사용하여 교차 검증으로 성능(RMSE 기준)을 측정해봅시다. 

algo = SVD()
cross_validate(algo=algo, data=data, measures=['RMSE'], cv=5, verbose=True)

정말 간단한 그리드서치 탐색을 해봅니다. 파라미터로 더 많은 것들을 넣을 수 있으며 더 다양한 파라미터는 surprise 공식 홈페이지를 참고해주세요. 

param_grid = {'n_factors': [50, 75], 'lr_all': [0.5, 0.05], 'reg_all': [0.06, 0.04]}

gs = GridSearchCV(algo_class=SVD, measures=['RMSE'], param_grid=param_grid)
gs.fit(data)

print('\n###################')
print('Best Score :', gs.best_score['rmse'])

print('Best Parameters :', gs.best_params['rmse'])
print('#####################')

참고로 교차 검증 이외에 아래 코드를 통해 SVD를 학습하고 테스트셋에 대해 평가를 하는 방법도 있습니다. surprise 공식 홈페이지에 잘 나와있습니다. 

from surprise import accuracy
from surprise.model_selection import train_test_split

trainset, testset = train_test_split(data, test_size=.25)
algo.fit(trainset)
predictions = algo.test(testset)
# predictions = algo.fit(trainset).test(testset)

accuracy.rmse(predictions)

서프라이즈 패키지가 잘 구성되어 있기 때문에 초반 추천 시스템을 구축하는데 많은 도움이 될 것 같습니다^^ 위에서는 SVD 모델만 사용했는데 이외에도 아래의 다양한 모델을 사용해볼 수 있습니다. 

prediction_algorithms package — Surprise 1 documentation

 

prediction_algorithms package — Surprise 1 documentation

© Copyright 2015, Nicolas Hug Revision fa745588.

surprise.readthedocs.io

 

728x90
반응형