Movielens 데이터는 rating.dat 안에 인덱싱까지 완료된 사용자-영화-평점 데이터가 정리되어있습니다.
import pandas as pd
import os
rating_file_path=os.getenv('HOME') + '/aiffel/recommendata_iu/data/ml-1m/ratings.dat'
ratings_cols = ['user_id', 'movie_id', 'rating', 'timestamp']
ratings = pd.read_csv(rating_file_path, sep='::', names=ratings_cols, engine='python', encoding = "ISO-8859-1")
orginal_data_size = len(ratings)
ratings.head()
3점 이상의 점수를 받은 데이터를 이용하겠습니다.
# 3점 이상만 남깁니다.
ratings = ratings[ratings['rating']>=3]
filtered_data_size = len(ratings)
print(f'orginal_data_size: {orginal_data_size}, filtered_data_size: {filtered_data_size}')
print(f'Ratio of Remaining Data is {filtered_data_size / orginal_data_size:.2%}')
orginal_data_size: 1000209, filtered_data_size: 836478
Ratio of Remaining Data is 83.63%
# rating 컬럼의 이름을 count로 바꿉니다.
ratings.rename(columns={'rating':'count'}, inplace=True)
ratings['count']
# 영화 제목을 보기 위해 메타 데이터를 읽어옵니다.
movie_file_path=os.getenv('HOME') + '/aiffel/recommendata_iu/data/ml-1m/movies.dat'
cols = ['movie_id', 'title', 'genre']
movies = pd.read_csv(movie_file_path, sep='::', names=cols, engine='python', encoding='ISO-8859-1')
movies.head()
from implicit.als import AlternatingLeastSquares
import os
import numpy as np
# implicit 라이브러리에서 권장하고 있는 부분입니다. 학습 내용과는 무관합니다.
os.environ['OPENBLAS_NUM_THREADS']='1'
os.environ['KMP_DUPLICATE_LIB_OK']='True'
os.environ['MKL_NUM_THREADS']='1'
# 고유한 유저, 아티스트를 찾아내는 코드
movies_unique = movies['title'].unique()
# 유저, 아티스트 indexing 하는 코드 idx는 index의 약자입니다.
title_to_idx = {k:v for k,v in enumerate(movies_unique)}
# 영화개수
print(ratings['movie_id'].nunique())
print(ratings['user_id'].nunique())
# 인기 많은 영화 30개
movie_count = ratings.groupby('movie_id')['user_id'].count()
movie_count.sort_values(ascending=False).head(30)
3628
6039
제 아이디는 9999로 했습니다. 영화는 아무거나 다섯개 했습니다
# 본인이 좋아하시는 아티스트 데이터로 바꿔서 추가하셔도 됩니다! 단, 이름은 꼭 데이터셋에 있는 것과 동일하게 맞춰주세요.
my_favorite = ['1193' , '661' ,'914' ,'3408' ,'2355']
# 'zimin'이라는 user_id가 위 아티스트의 노래를 30회씩 들었다고 가정하겠습니다.
my_playlist = pd.DataFrame({'user_id': ['9999']*5, 'movie_id': my_favorite, 'count':[5]*5,'timestamp':[0]*5})
if not ratings.isin({'user_id':['9999']})['user_id'].any(): # user_id에 'zimin'이라는 데이터가 없다면
ratings = ratings.append(my_playlist) # 위에 임의로 만든 my_favorite 데이터를 추가해 줍니다.
# 잘 추가되었는지 확인해 봅시다.
ratings.tail(10)
from scipy.sparse import csr_matrix
num_user = ratings['user_id'].nunique()
num_movie = ratings['movie_id'].nunique()
print(num_user)
num_movie
데이터를 수정하면서 str과 int가 섞여있습니다. 그렇기 때문에 모두 int로 변환합니다. 이때 문자열도 '5'이런식으로 숫자로 변경할수 있어야합니다.
ratings=ratings.astype('int64')
ratings.head(3)
csr_data = csr_matrix((ratings["count"], (ratings.user_id, ratings.movie_id)))
csr_data
# Implicit AlternatingLeastSquares 모델의 선언
als_model = AlternatingLeastSquares(factors=100, regularization=0.01, use_gpu=False, iterations=15, dtype=np.float32)
# als 모델은 input으로 (item X user 꼴의 matrix를 받기 때문에 Transpose해줍니다.)
csr_data_transpose = csr_data.T
csr_data_transpose
# 모델 훈련
als_model.fit(csr_data_transpose)
name_vector, movie_vector = als_model.user_factors[9999], als_model.item_factors[1193]
나온 값은 선호도를 의미합니다.
np.dot(name_vector, movie_vector)
0.6661142
similar_artist = als_model.similar_items(1193, N=15)
similar_artist
1에 가까울수록 제가 선호하는 영화와 비슷하다는 의미인데 0.5 조차도 넘기 힘드네요... 사용한 데이터 기반으로 했는데도 힘들면.. 늘릴방법은 데이터 양을 늘리는 것일까요?
[title_to_idx[i[0]] for i in similar_artist]
artist_recommended = als_model.recommend(9999, csr_data, N=20, filter_already_liked_items=True)
artist_recommended
추천받는게 더 낮네요,,, ㅜㅜ 1과 가까운 비슷한 영화를 추천받는 것은 생각보다 어려운 것일지도 모르겠습니다.
[title_to_idx[i[0]] for i in artist_recommended]
위에서 진행한 것처럼 M명의 사용자 N개의 영화에 대해 평가한 데이터를 포함한 m,n사이즈의 평가행렬을 만듭니다.
사용자가 영화를 좋게 평가했더라도 모델이 근사하고자 하는 것이 영화를 선호하는지 안하는지 이기 때문에 두 벡터의 곱은 count의 최고치인 5가 아니라 1에 가까워져야 합니다.
모든 사용자가 모든 영화를 보고 평가하는 것이 아니기 때문에 정확한 답을 내지는 못하지만 실제로 처음 영화를 보는데 있어서 어느정도 선호도를 평가해서 추천할 수 있다는 것에 의미가 있을 것 같습니다.