행렬 분해를 이용한 잠재 요인 협업 필터링 실습¶
일반적으로 행렬 분해에는 SVD가 자주 사용되지만, 사용자-아이템 평점 행렬에는 사용자가 평점을 매기지 않은 null 데이터가 많기 때문에 SGD나 ALS 기반의 행렬 분해를 사용합니다. 여기서는 SGD를 이용하겠습니다.
앞의 잠재 요인 협업 필터링 절의 경사 하강법을 이용한 행렬 분해에서 사용한 함수 get_rmse()를 다시 활용하고, 행렬 분해 로직을 matrix_factorization()함수로 정리합니다. 파라미터 R은 사용자-아이템 평점 행렬, K는 잠재 요인의 차원 수, steps는 SGD의 반복 횟수, learning_rate는 학습률, r_lambda는 L2 규제 계수입니다.
In [9]:
import numpy as np
from sklearn.metrics import mean_squared_error
def get_rmse(R, P, Q, non_zeros):
error = 0
# 두 개의 분해된 행렬 P와 Q.T의 내적으로 예측 R 행렬 생성
full_pred_matrix = np.dot(P, Q.T)
# 실제 R 행렬에서 null이 아닌 값의 위치 인덱스를 추출해 실제 R 행렬과 예측 행렬의 RMSE 추출.
x_non_zero_ind = [non_zero[0] for non_zero in non_zeros]
y_non_zero_ind = [non_zero[1] for non_zero in non_zeros]
R_non_zeros = R[x_non_zero_ind, y_non_zero_ind]
full_pred_matrix_non_zeros = full_pred_matrix[x_non_zero_ind, y_non_zero_ind]
mse = mean_squared_error(R_non_zeros, full_pred_matrix_non_zeros)
rmse = np.sqrt(mse)
return rmse
def matrix_factorization(R, K, steps=200, learning_rate=0.01, r_lambda=0.01):
num_users, num_items = R.shape
# P와 Q 매트릭스의 크기를 지정하고 정규 분포를 가진 랜덤한 값으로 입력합니다.
np.random.seed(1)
P = np.random.normal(scale=1./K, size=(num_users, K))
Q = np.random.normal(scale=1./K, size=(num_items, K))
prev_rmse = 10000
break_count = 0
# R > 0 인 행 위치, 열 위치, 값을 non_zeros 리스트 객체에 저장.
non_zeros = [ (i, j, R[i, j]) for i in range(num_users) for j in range(num_items) if R[i, j] > 0]
# SGD 기법으로 P와 Q 매트릭스를 계속 업데이트.
for step in range(steps):
for i, j, r in non_zeros:
# 실제 값과 예측 값의 차이인 오류 값 구함
eij = r - np.dot(P[i, :], Q[j, :].T)
# Regularization을 반영한 SGD 업데이트 공식 적용
P[i, :] = P[i, :] + learning_rate*(eij*Q[j, :] - r_lambda*P[i, :])
Q[j, :] = Q[j, :] + learning_rate*(eij*P[i, :] - r_lambda*Q[j, :])
rmse = get_rmse(R, P, Q, non_zeros)
if (step % 10) == 0:
print("### iteration step : ", step, " rmse : ", rmse)
return P, Q
영화 평점 행렬 데이터를 다시 사용자-아이템 평점 행렬로 만들겠습니다.
In [5]:
import pandas as pd
movies = pd.read_csv('/content/drive/MyDrive/military/grouplens/movies.csv')
ratings = pd.read_csv('/content/drive/MyDrive/military/grouplens/ratings.csv')
ratings = ratings[['userId', 'movieId', 'rating']]
ratings_matrix = ratings.pivot_table('rating', index='userId', columns='movieId')
# title 칼럼을 얻기 위해 movies와 조인 수행
rating_movies = pd.merge(ratings, movies, on='movieId')
# columns='title'로 title 칼럼으로 pivot 수행.
ratings_matrix = rating_movies.pivot_table('rating', index='userId', columns='title')
사용자-아이템 행렬을 matrix_factorization()함수를 이용해 행렬 분해하겠습니다.
In [10]:
P, Q = matrix_factorization(ratings_matrix.values, K=50, steps=200, learning_rate=0.01,\
r_lambda = 0.01)
pred_matrix = np.dot(P, Q.T)
더 직관적으로 아이템 칼럼을 표현하고자 칼럼명을 영화 타이틀로 바꾸겠습니다.
In [11]:
ratings_pred_matrix = pd.DataFrame(data=pred_matrix, index=ratings_matrix.index,\
columns=ratings_matrix.columns)
ratings_pred_matrix.head(3)
Out[11]:
예측 사용자-아이템 평점 행렬 정보를 이용해 개인화된 영화 추천을 해보겠습니다. 아이템 기반 최근접 이웃 협업 필터링 실습과 마찬가지로 9번 사용자에 대해 추천해보겠습니다.
In [13]:
def get_unseen_movies(rating_matrix, userId):
# userId로 입력받은 사용자의 모든 영화 정보를 추출해 Series로 반환함.
# 반환된 user_rating은 영화명(title)을 인덱스로 가지는 Series 객체임.
user_rating = ratings_matrix.loc[userId, :]
# user_rating이 0보다 크면 기존에 관람한 영화임. 대상 인덱스를 추출해 list 객체로 만듦.
already_seen = user_rating[user_rating > 0].index.tolist()
# 모든 영화명을 list 객체로 만듦.
movies_list = ratings_matrix.columns.tolist()
# list comprehension으로 already_seen에 해당하는 영화는 movies_list에서 제외함.
unseen_list = [ movie for movie in movies_list if movie not in already_seen ]
return unseen_list
def recomm_movie_by_userid(pred_df, userId, unseen_list, top_n=10):
# 예측 평점 DataFrame에서 사용자id 인덱스와 unseen_list로 들어온 영화명 칼럼을 추출해
# 가장 예측 평점이 높은 순으로 정렬함.
recomm_movies = pred_df.loc[userId, unseen_list].sort_values(ascending=False)[:top_n]
return recomm_movies
In [14]:
# 사용자가 관람하지 않은 영화명 추출
unseen_list = get_unseen_movies(ratings_matrix, 9)
# 잠재 요인 협업 필터링으로 영화 추천.
recomm_movies = recomm_movie_by_userid(ratings_pred_matrix, 9, unseen_list, top_n=10)
# 평점 데이터를 DataFrame으로 생성.
recomm_movies = pd.DataFrame(data=recomm_movies.values, index=recomm_movies.index, columns=['pred_score'])
recomm_movies
Out[14]:
아이템 기반 협업 필터링 결과와는 다릅니다. 모두 훌륭하지만 어둡거나 무거운 영화가 추천됐습니다.
출처: 파이썬 머신러닝 완벽가이드(권철민)
사진 출처:
'파이썬 머신 러닝 완벽 가이드' 카테고리의 다른 글
아이템 기반 최근접 이웃 협업 필터링 실습 (0) | 2021.12.14 |
---|---|
콘텐츠 기반 필터링 실습 - TMDB 5000 영화 데이터 세트 (0) | 2021.12.13 |
추천 시스템 (0) | 2021.12.11 |
텍스트 분석 실습 - 캐글 Mercari Price Suggestion Challenge (0) | 2021.12.10 |
텍스트 분류 실습 - 20 뉴스그룹 분류 (0) | 2021.12.05 |