추천시스템은 '아이템이 많고 유저의 취향이 다양할 때 유저가 소비할만한 아이템을 예측하는 모델'이다. 빅데이터를 사용하여 추천시스템을 만들 수 있으며, 추천 방식에는 협업 필터링(collaborative filtering) 과 콘텐츠 기반 필터링(contents-based filtering) 이 있다. 여기서는 사용자간 유사성 및 아이템 간 유사성을 기반으로 한 협업 필터링을 사용한다.
활용할 데이터셋은 Movielens 데이터이다. Movielens 데이터에 대한 설명과 사용하는 방법은 다음과 같다. (노드에서 설명 가져옴)
유저가 영화에 대해 평점을 매긴 데이터가 데이터 크기별로 있다. MovieLens 1M Dataset 사용을 권장한다.
별점 데이터는 대표적인 explicit 데이터(좋아요, 별점 같이 선호도 표시)이지만 implicit 데이터(플레이 횟수 등)로 간주하고 테스트해볼 수 있다. (MF 모델 사용하기 위해)
별점을 시청횟수로 해석해서 생각한다.
유저가 3점 미만으로 준 데이터는 선호하지 않는다고 가정하고 제외한다.
참고: Explicit vs Implicit Feedback Datasets
- 명시적 데이터: 유저가 자신의 선호도를 직접(Explicit) 표현한 데이터. 평점, 좋아요/싫어요, 영화 리뷰, 구독, 차단 데이터 등. 구하기 힘듬.
- 암묵적 데이터: 유저가 간접적(Implicit)으로 선호, 취향을 나타내는 데이터. 검색 기록, 방문 페이지, 구매 내역, 마우스 움직임 기록. 잡음이 많고 수치는 신뢰도를 의미함.
추천시스템의 다양한 모델 중 하나이다. 계산이 쉽고 성능이 준수하며 Scalability가 좋아 자주 사용된다. 행렬분해 모델은 평가행렬 R(m, n)을 두 개의 Feature Matrix P(m, k)와 Q(k, n)로 분해한다. 여기서 P는 사용자 특성, Q는 영화의 특성, R은 사용자의 영화 선호도를 보여준다. 모델의 목표는 모든 유저와 아이템에 대한 k차원 벡터를 잘 만드는 것 이고, 벡터를 잘 만드는 기준은 '유저의 벡터와 아이템의 벡터를 곱했을 때, 유저의 아이템 평가 수치와 비슷한지'이다.
사용모델: Collaborative Filtering for Implicit Feedback Datasets에서 제안한 모델. 사용자와 아이템 벡터를 내적했을 때 1에 가까워져야 한다.
유저의 재생횟수(또는 별점)을 맞추는 것이라면 Recommender System — Matrix Factorization에 나오는 모델을 참고할 것!
참고: 갈아먹는 추천 알고리즘 [3] Matrix Factorization
한 번도 선택하지 않은 아이템도 포함되어 있기 때문에 (유저 X 아이템) 평가행렬을 구하면 너무 큰 행렬이 된다. 이 경우 CSR(Compressed Sparse Row) Matrix를 사용하여 행렬의 크기를 줄일 수 있다.
아래를 누르면 자동으로 데이터가 다운로드된다.
http://files.grouplens.org/datasets/movielens/ml-1m.zip
# 평점 3점 이상만 남기기
data = data[data['rating'] >= 3]
movie_count = df.groupby('movie_id')['user_id'].count()
movie_count.sort_values(ascending=False).head(30)
cols = ['col1', 'col2', 'col3']
data = data[cols]
사용자 취향의 영화 5개를 입력받아 기존 데이터에 넣는다.
favorite_movies = ['title1 (year1)', 'title2 (year2)', ...'title5 (year5)'] # 선호 영화 리스트 생성 (기존 데이터의 형태대로 넣어야 함)
movielist = pd.DataFrame({'id': ['name']*5, 'title': favorite, 'count': [5]*5}) # 위에서 만든 데이터프레임의 형태로 컬럼 5개짜리 데이터프레임을 만든다.
if not data.isin({'id':['name']}['id'].any():
data = data.append(movielist) # 중복되지 않으면 새로 만든 데이터프레임을 기존의 데이터프레임에 추가함
# 고유한 유저 찾기
user_unique = data['id'].unique()
# 새롭게 indexing
user_to_idx = {v:k for k,v in enumerate(user_unique)}
user_data = data['id'].map(user_to_idx.get).dropnna() # 새롭게 인덱싱한 series 구함
if len(user_data) == len(data): # 모든 행이 정상적으로 인덱싱되었으면
data['id'] = user_data # data['id']를 위에서 만든 series로 교체
else:
print('indexing failed.')
csr_matrix((data, (row_ind, col_ind)), [shape=(M, N)])
where data, row_ind and col_ind satisfy the relationship a[row_ind[k], col_ind[k]] = data[k]
위의 코드를 사용하여 직접 만들어보자!
from implicit.als import AlternatingLeastSquares
# implicit 라이브러리에서 권장하고 있다. 학습 내용과는 무관
os.environ['OPENBLAS_NUM_THREADS']='1'
os.environ['KMP_DUPLICATE_LIB_OK']='True'
os.environ['MKL_NUM_THREADS']='1'
als_model = AlternatingLeastSquares(factors=200, regularization=0.3, use_gpu=False, iterations=30
, dtype=np.float32)
# als 모델은 input으로 (item X user) 꼴의 matrix를 받기 때문에 Transpose한다.
csr_data_t = csr_data.T
# 모델 훈련
als_model.fit(csr_data_transpose)
AlternatingLeastSquares 클래스의 __init__ 파라미터
여기서 MF 모델이 벡터를 잘 만들었는지 파악할 수 있다.
user, movie = user_to_idx['user'], title_to_idx['title (year)']
user_vector, movie_vector = als_model.user_factors[user], als_model.item_factors[movie]
np.dot(user_vector, movie_vector)
als_model.similar_items()
를 사용한다. def similar_movies(movie_name: str):
title_id = title_to_idx[movie_name]
similar_movie = als_model.similar_items(title_id)
idx_to_title = {v:k for k, v in title_to_idx.items()} # title_to_idx 뒤집기
similar_movie = [idx_to_title[i[0]] for i in similar_movie] # index에서 영화이름 얻는 dict 생성
return similar_movie
als_model.recommend(user, csr_data, N=20, filter_already_liked_items=True)
를 사용한다. 7번의 코드와 유사하다.
글 정리가 잘 되어있네요! 잘 읽고 갑니다🎉