[머신러닝] 추천 시스템 알고리즘 - 아이템 기반 최근접 이웃 필터링
데이터 셋 다운로드
사용자-영화 평점 행렬 데이터 필요
Grouplens 사이트에서 만든 MoviesLens 데이터 셋 사용 (축소 버전 사용)
https://grouplens.org/datasetsmovielens/latest
import numpy as np
import pandas as pd
movies = pd.read_csv('./dataset/movies.csv')
ratings = pd.read_csv('./dataset/ratings.csv')
print(movies.shape)
print(ratings.shape)
(9742, 3)
(100836, 4)
movies.head()
ratings.head()
영화 평점에 참여한 전체 인원수 확인
ratings['userId'].unique().size # 전체 user = 610명
610
데이터 전처리
평점 데이터가 행단위 데이터로 되어 있어서 사용자 - 아이템(영화) 평점행렬로 변환 필요데이터 전처리 해야한다.
아이템 기반의 최근접 이웃 협업 필터링은 이처럼 사용자가 행에, 아이템이 열에 위치해 있어야 한다.
ratings = ratings.drop('timestamp', axis= 1)
ratings_matrix = ratings.pivot_table(index= 'userId', columns='movieId', values = 'rating')
print(ratings_matrix.shape)
ratings_matrix.head() # 사용자가 평점을 매기지 않은 행은 NaN
1. 영화 아이디 (movieId) 를 영화 제목으로 변환하기 위해 ratings 데이터와 movies 데이터를 병합
rating_movies = pd.merge(ratings, movies, on= 'movieId')
print(rating_movies.shape)
rating_movies.head(2)
2. 사용자 - 아이템 평점 행렬로 변환
ratings_matrix = rating_movies.pivot_table(index= 'userId', columns='title', values = 'rating')
print(ratings_matrix.shape)
ratings_matrix.head(2)
3. NaN 값은 0으로 변경
ratings_matrix = ratings_matrix.fillna(0)
ratings_matrix.head(2)
영화와 영화 간 평점 유사도 계산
cosine_similarity() 를 이용하여 영화와 영화간 유사도를 산출하기 위해서는 rating_matrix 데이터를 영화를 행 기준으로 만들기 위해 전치를 시킨다. 현재의 최종 데이터 셋으로 코사인 유사도를 계산하게 되면 사용자 - 사용자간의 유사도를 만들게 되기 때문이다.
사용자와 영화의 행과 열의 위치를 전치 시킨다.
ratings_matrix_T = ratings_matrix.T
ratings_matrix_T.head(3)
영화간의 코사인 유사도를 계산한다.
from sklearn.metrics.pairwise import cosine_similarity
item_sim = cosine_similarity(ratings_matrix_T, ratings_matrix_T)
item_sim_df = pd.DataFrame(item_sim, index = ratings_matrix.columns, columns=ratings_matrix.columns)
print(item_sim_df.shape)
item_sim_df.head(3)
개인화된 영화 추천을 위한 예측 평점 계산
매개 변수 설명
ratings_arr : 사용자-영화 평점 행렬 (ratings_matrix), shape -> (610,9719)
item_sim_arr : 영화간 평점 유사도 행렬 (item_sim_df), shape -> (9719,9719)
def predict_rating_topsim(ratings_arr, item_sim_arr, n=20):
pred = np.zeros_like(ratings_arr) # 예측평점 행렬 R_hat
for col in range(ratings_arr.shape[1]): # 영화의 수만큼 반복문을 돈다.
# 아이템 유사도 행렬에서 col번째 영화와 유사도가 큰 n개 영화의 인덱스를 반환한다.
top_n_items = np.argsort(item_sim_arr[:,col])[::-1][:n] # col 번째 영화의 평점 유사도 행렬값을 가져와 정렬
for row in range(ratings_arr.shape[0]): # 사용자 수만큼 반복문을 돈다.
# item_sim_arr[col,:][top_n_items] -> col 번째 영화와 유사도가 가장 높은 상위 n개 영화의 유사도 벡터 : S_i,n
# ratings_arr[row,:][top_n_items] -> row 번째 사용자에 col번째 영화와 유사도가 가장 높은 상위 n개 영화에 대한 실제 평점 벡터: R_u,n
pred[row,col] = item_sim_arr[col,:][top_n_items].dot(ratings_arr[row,:][top_n_items].T)
pred[row,col] /= np.sum(np.abs(item_sim_arr[col,:][top_n_items])) # np.abs : 절댓값
return pred
함수 내용 테스트
pred[row,col] = item_sim_arr[col,:][top_n_items].dot(ratings_arr[row,:][top_n_items].T)
-> 이러한 과정으로 전체 사용자 610 명의 예측평점을 구함
ratings_arr = ratings_matrix.values
item_sim_arr = item_sim_df.values
top_n_items = np.argsort(item_sim_arr[:,0])[::-1][:20]
print(top_n_items) # 0번째 영화와 평점 유사도가 높은 상위 20개의 인덱스 값
print('-'*30)
print(item_sim_arr[0,:][top_n_items])
# 실제 평점유사도가 무엇인가 -> 전부 1 : S_i,n
print('-'*30)
print(ratings_arr[0,:][top_n_items])
# 실제 평점데이터 -> 전부 0: 평점이 없을수도 있음 : R_u,n
# 표준화 전
print('-' * 30)
print(item_sim_arr[0,:][top_n_items].dot(ratings_arr[0,:][top_n_items].T))
[ 0 179 7085 6471 2253 5591 7674 7095 2247 3584 4925 3565 7537 8267
7676 5111 183 8251 3990 199]
------------------------------
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
------------------------------
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
------------------------------
0.0
함수 사용
ratings_pred = predict_rating_topsim(ratings_matrix.values, item_sim_df.values, n = 20)
ratings_pred_matrix = pd.DataFrame(ratings_pred, index= ratings_matrix.index, columns= ratings_matrix.columns)
print(ratings_pred_matrix.shape)
(610, 9719)
함수를 사용하여 만들어낸 사용자 - 아이템 유사도로 계산하여 만들어진 예측 평점 데이터 모양이다.
함수 사용 후 유저 1번 예시
user_1 = ratings_pred_matrix.loc[1]
print(np.max(user_1), ',', np.min(user_1))
4.595776354211019 , 0.0
# 첫 번째 유저의 9719 개의 영화에 대한 예측 평점 중 0점이 아닌 영화의 개수
user_1[user_1 != 0].size # 실제 평점에 대한 정보가 담긴
1133
평점을 주지 않은 영화 목록 반환 함수
영화 추천은 개인이 아직 관람하지 않은 영화를 추천하는 방식
def get_unseen_movies(ratings_matrix, userId): # ratings_matrix : 평점 정보가 0인 것을 찾음
user_rating = ratings_matrix.loc[userId] # Series타입. 인덱스: 영화의 제목, 값: 평점
unseen_list = user_rating[user_rating == 0].index.values
return unseen_list
특정 사용자의 관람하지 않은 영화에 대한 예측 평점 기반 추천
아직 관람하지 않은 영화의 리스트를 제작한다. (9는 임의의 유저를 선택)
unseen_list = get_unseen_movies(ratings_matrix, 9)
ratings_pred_matrix.loc[9,unseen_list].size
추천 결과
def recomm_movie_by_userid(pred_df, userId, unseen_list, top_n = 10):
return pred_df.loc[userId,unseen_list].sort_values(ascending=False)[:top_n]
unseen_list = get_unseen_movies(ratings_matrix, 9)
recomm_movies = recomm_movie_by_userid(ratings_pred_matrix, 9, unseen_list)
#print(recomm_movies)
recomm_movies = pd.DataFrame(recomm_movies.values, index= recomm_movies.index, columns= ['pred_score'])
recomm_movies
비교 : userId = 9 가 평점을 준 영화 중 평점이 높은 10개 영화 추출
user_rating = ratings_matrix.loc[9]
user_rating[user_rating>0].sort_values(ascending = False)[:10]
title
Adaptation (2002) 5.0
Citizen Kane (1941) 5.0
Raiders of the Lost Ark (Indiana Jones and the Raiders of the Lost Ark) (1981) 5.0
Producers, The (1968) 5.0
Lord of the Rings: The Two Towers, The (2002) 5.0
Lord of the Rings: The Fellowship of the Ring, The (2001) 5.0
Back to the Future (1985) 5.0
Austin Powers in Goldmember (2002) 5.0
Minority Report (2002) 4.0
Witness (1985) 4.0
Name: 9, dtype: float64
하지만, 사용자의 평점 기반으로 보지 않은 영화중에 예측 평점이 높은 영화를 추천했기에 제목만 봐서는 유사성의 유무를 따지기 어렵다.