< 목차 >
- 용어정의
- 데이터 가공 및 변환
- 영화간 유사도 산출
- 최근접 이웃 협업 필터링을 이용한 개인화 영화 추천시스템 개발
1. 용어정의
- 피드백 후 작성예정입니다.
- MovieLens 데이터셋은 데이터셋 자료실 에 있습니다.
- 전체코드는 https://github.com/pjh5672/Recommendation_System 에 올려두었습니다.
2. 데이터 가공 및 변환
협업 필터링 기반의 영화 추천을 위해서는 사용자가 영화 평점을 매긴 사용자ㅡ영화 평점 매트릭스 데이터셋이 필요하다. 이를 위해 MovieLens 데이터셋을 다운받아 pandas.DataFrame으로 로딩한다.
영화에 대한 정보인 movies.csv 파일은 9742개 영화에 대한 movieId, title, genres 등으로 구성되며, 영화 평점 정보인 ratings.csv 파일은 100836명의 사용자가 영화에 대해 평점을 준 것으로 userId, movieId, rating, timestamp 로 구성된다. 이때, 평점 범위는 0.5부터 5.0 사이이며, 0.5 단위로 평점이 부여된다.
import pandas as pd
import numpy as np
movies = pd.read_csv('./ml-latest-small/movies.csv')
ratings = pd.read_csv('./ml-latest-small/ratings.csv')
print("movies shape : {}".format(movies.shape))
print("movies shape : {}".format(ratings.shape))
display(movies.head(2))
display(ratings.head(2))
협업 필터링은 이 ratings.csv 세트와 같이 사용자와 아이템(영화) 간의 평점에 기반해 추천하는 시스템이므로, 먼저 로우(행) 레벨 형태의 원본 데이터셋을 아래의 형태와 같이 모든 사용자를 로우로, 모든 영화를 컬럼으로 재구성한다.
ratings = ratings[['userId', 'movieId', 'rating']]
ratings_matrix = ratings.pivot_table('rating', index='userId', columns='movieId')
ratings_matrix.head(3)
여기서, 알아보기 쉽게 위의 테이블에서 각 컬럼을 movieId가 아닌 movie title로 변경하고, NaN 값들을 0 으로 채운다.
# title 칼럼을 얻기 위해 movies와 조인
rating_movies = pd.merge(ratings, movies, on='movieId')
# columns='title'로 title 칼럼으로 피벗 수행
ratings_matrix = rating_movies.pivot_table('rating', index='userId', columns='title')
# NaN 값을 모두 0 으로 변환
ratings_matrix = ratings_matrix.fillna(0)
ratings_matrix.head(3)
3. 영화간 유사도 산출
이제 변환된 사용자ㅡ영화 평점 매트릭스를 이용해 영화간의 평점 유사도를 계산할 수 있다.
ratings_matrix_T = ratings_matrix.transpose()
ratings_matrix_T.head(3)
유사도는 코사인 유사도를 기반으로 하고 cosine_similarity() 를 이용해 측정하며 행 기준으로 서로 다른 행을 비교하여 유사도를 산출하기 때문에, 여기서는 영화가 행 기준이 되어야 하므로 매트릭스를 전치시킨다.
그리고 코사인 유사도를 계산해보면, 대각성분이 1.0 인 아래 매트릭스와 같이 나타난다.
from sklearn.metrics.pairwise import cosine_similarity
item_sim = cosine_similarity(ratings_matrix_T, ratings_matrix_T)
# cosine_similarity() 로 반환된 넘파이 행렬을 영화명을 매핑해 DataFrame으로 변환
item_sim_df = pd.DataFrame(data=item_sim, index=ratings_matrix.columns
,columns=ratings_matrix.columns)
print(item_sim_df.shape)
item_sim_df.head(3)
여기서 명작인 '인셉션(Inception)' 가장 평점 유사도가 높은 영화를 5개 찾아보면 아래와 같이 나타난다.
'다크나이트'가 가장 유사도가 높으며, 그 뒤를 이어 주로 스릴러와 액션이 가미된 영화들이 높은 유사도를 나타낸다.
이렇게 만들어진 아이템 기반 유사도 데이터는 사용자의 평점 정보를 모두 취합해 영화에 따라 유사한 다른 영화를 추천할 수 있게 해준다.
item_sim_df["Inception (2010)"].sort_values(ascending=False)[1:6]
4. 최근접 이웃 협업 필터링을 이용한 개인화 영화 추천시스템 개발
위에서 계산한 아이템 기반 유사도 데이터는 개인적인 취향을 반영하지 않고 영화간의 평점 유사도만 가지고 추천한 것이다. 따라서, 위의 유사도 데이터를 가지고 개인에게 특화된(Personalized) 영화 추천 알고리즘을 아래와 같이 구현할 수 있다.
이는 특정한 개인이 아직 관람하지 않은 영화에 대해 아이템 유사도와 기존 관람한 영화의 평점데이터를 기반으로 해 새롭게 모든 영화의 예측 평점을 구한 후 높은 예측 평점을 가진 영화를 추천하는 방식이다.
아이템 기반 협업 필터링에서 개인화된 예측 평점은 다음 공식으로 구할 수 있다.
S_i,n 와 R_u,n에 나오는 N 값은 아이템의 최근접 이웃 범위 계수(item neighbor)를 의미하고, 특정 아이템과 유사도가 가장 높은 Top-N개의 다른 아이템을 추출하는 데 사용된다. 앞에서 생성된 영화 간의 유사도에 대한 DataFrame인 item_sim_df 와 사용자ㅡ평점 DataFrame 인 ratings_matrix 를 활용해 사용자별로 최적화된 평점 스코어를 예측하는 함수를 아래와 같이 만들어 새로운 예측 평점을 계산한다.
def predict_rating(ratings_arr, item_sim_arr ):
ratings_pred = ratings_arr.dot(item_sim_arr)/ np.array([np.abs(item_sim_arr).sum(axis=1)])
return ratings_pred
ratings_pred = predict_rating(ratings_matrix.values , item_sim_df.values)
ratings_pred_matrix = pd.DataFrame(data=ratings_pred, index= ratings_matrix.index,
columns = ratings_matrix.columns)
ratings_pred_matrix.head(3)
이때 예측 평점이 사용자별 영화의 실제 평점과 영화의 코사인 유사도를 내적한 값이기 대문에 기존에 영화를 관람하지 않아 0에 해당했던 실제 영화 평점이 예측에서 값이 부여되는 경우가 많이 발생하므로, 예측 평점이 실제보다 다소 작을 수 있다.
예측 결과가 원래 실제 평점보다 얼마나 차이가 있는지 MSE(Mean Squared Error) 지표를 통해 확인할 수 있고, 9.89 정도 차이 남을 알 수 있다.
from sklearn.metrics import mean_squared_error
# 사용자가 평점을 부여한 영화에 대해서만 예측 성능 평가 MSE 를 구함.
def get_mse(pred, actual):
# Ignore nonzero terms.
pred = pred[actual.nonzero()].flatten()
actual = actual[actual.nonzero()].flatten()
return mean_squared_error(pred, actual)
print('아이템 기반 모든 인접 이웃 MSE: ', get_mse(ratings_pred, ratings_matrix.values ))
MSE를 최소화 하는 방향으로 개선시키기 위해, 영화 전체에 대한 유사도를 계산하지 않고, 특정 영화와 가장 비슷한 유사도를 가지는 영화에 대해서만 유사도 벡터를 적용하여 MSE를 구하는 함수로 변경하면 아래와 같다.
이때 행, 열 별로 for 루프를 반복 수행하며 Top-N개의 유사도 벡터를 계산하기 때문에 수행시간이 꽤 오래 걸릴 수 있다.
def predict_rating_topsim(ratings_arr, item_sim_arr, n=20):
# 사용자-아이템 평점 행렬 크기만큼 0으로 채운 예측 행렬 초기화
pred = np.zeros(ratings_arr.shape)
# 사용자-아이템 평점 행렬의 열 크기만큼 Loop 수행.
for col in range(ratings_arr.shape[1]):
# 유사도 행렬에서 유사도가 큰 순으로 n개 데이터 행렬의 index 반환
top_n_items = [np.argsort(item_sim_arr[:, col])[:-n-1:-1]]
# 개인화된 예측 평점을 계산
for row in range(ratings_arr.shape[0]):
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]))
return pred
ratings_pred = predict_rating_topsim(ratings_matrix.values , item_sim_df.values, n=20)
print('아이템 기반 인접 TOP-20 이웃 MSE: ', get_mse(ratings_pred, ratings_matrix.values ))
# 계산된 예측 평점 데이터는 DataFrame으로 재생성
ratings_pred_matrix = pd.DataFrame(data=ratings_pred, index= ratings_matrix.index,
columns = ratings_matrix.columns)
이제 특정 사용자(userId=9)에 대해 영화를 추천해보면 다음과 같이 나열된다. 여기서 사용자가 이미 관람한 영화를 제외하고 추천을 해야하기 때문에 아래의 get_unseen_movies 함수를 통해서 사용자가 관람하지 않은 영화에 대해서만 추천을 수행하도록 한다.
user_rating_id = ratings_matrix.loc[9, :]
user_rating_id[ user_rating_id > 0].sort_values(ascending=False)[:10]
def get_unseen_movies(ratings_matrix, userId):
# userId로 입력받은 사용자의 모든 영화정보 추출하여 Series로 반환함.
# 반환된 user_rating 은 영화명(title)을 index로 가지는 Series 객체임.
user_rating = ratings_matrix.loc[userId,:]
# user_rating이 0보다 크면 기존에 관람한 영화임. 대상 index를 추출하여 list 객체로 만듬
already_seen = user_rating[ user_rating > 0].index.tolist()
# 모든 영화명을 list 객체로 만듬.
movies_list = ratings_matrix.columns.tolist()
# list comprehension으로 already_seen에 해당하는 movie는 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 index와 unseen_list로 들어온 영화명 컬럼을 추출하여
# 가장 예측 평점이 높은 순으로 정렬함.
recomm_movies = pred_df.loc[userId, unseen_list].sort_values(ascending=False)[:top_n]
return recomm_movies
# 사용자가 관람하지 않는 영화명 추출
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
결과로 '슈렉', '스파이더 맨', '인디아나 존스-2편', '매트릭스' 등 다양하지만 높은 흥행성을 가진 작품이 추천되는 것을 알 수 있다.
'인공지능 > 추천시스템' 카테고리의 다른 글
5. Surprise 라이브러리를 이용한 추천시스템 개발 (3) | 2021.04.10 |
---|---|
4. 행렬 분해를 이용한 잠재요인 협업 필터링 (4) | 2021.04.07 |
2. 콘텐츠 기반 필터링 (0) | 2021.04.06 |
1. 추천시스템 개요 (2) | 2021.04.06 |