본문 바로가기

블로그/머신러닝

[추천 시스템] 협업 필터링 Collaborative Filtering - User based

  • 추천시스템 개요
    추천시스템은 아이템에 대한 사용자의 예상 선호도를 추정하는 것이다. 따라서 어떻게 하면 아이템에 대한 사용자의 선호도를 정확히 예측할 것인지에 대한 알고리즘

    그렇다면 추천 시스템의 모델도 특정 유저에 대한 평점을 "예측"하고, 실제 값과 비교하며 Loss 줄여가겠다.
  • 데이터의 종류
    • Explicit data (ex. 별점, 좋아요, 구독)
      • 취향이나 선호도를 표시한 데이터
    • lmplicit data (ex. click, 체류 시간 등, skip 여부, 시청시간)
      • 사용자들의 행동으로부터 취향이나 선호도를 추론할 수 있는 데이터
  • 추천시스템의 평가
    MAE, MSE, RMSE 등을 활용하기도 하지만, 상위 K를 활용하는 Precision@K, Recall@K, MAP@5 등을 활용함.
    => 어떻게 보면 Ranking 평가와 유사한것 같다.

  • Collaborative Filtering Recommendation System

    취향이 비슷한 사람끼리 추천하는 알고리즘. 사용자로부터 아이템에 대한 Explicit 혹은 implicit 평가를 활용해서 취향이 비슷한 사람을 찾아낼 수 있다는 가정으로부터 시작한다.
    • "취향이 비슷한 사람"을 Mesarue 하는 방법에는 정해진 방법이 없지만 대표적으로 Correlation, Cosine 유사도, Binary일 때 자카드 유사도, Tanimoto Coefficient 등을 활용하여 유저간의 유사도를 구함.
  • Correlation을 활용한 유저간 유사도 측정 예시

Correlation을 활용한 유저간 유사도 측정

U1,U2,U3~U5의 유저가 있고, 유저가 평가한 영화가 M1,M2,M3..M5가 있다고 하자. 여기서 각 유저의 row 값들은 해당 영화에 대한 별점 평가이다. 유저 U1은 영화 M4,M5에 대하여 평가를 하지 않았고, 유저 U2는 모든 영화에 대하여 평가를 진행하였다. 

 

여기서 우리는 어떻게 각 유저에 대한 유사도를 계산할 수 있을까? 가장 간단하게 점수에 대한 Correlation을 활용할 수 있을 것이다. 가장 오른쪽의 컬럼 Correlation with U1은 유저 U1과 다른 유저간의 상관계수를 계산한 값이다. 

 

상관계수를 기준으로 U1과 가장 유사한 두 유저 U3,U4가 선정되었다. 영화 M4, M5 중 U1에게 어떠한 영화를 추천할 수 있을까? 간단한 방법으로 U3, U4가 각각 M4,M5에 부여한 별점의 평균을 가지고 U1에게 영화를 추천할 수 있을 것이다. 그럼 U3,U4에 대한 별점 평균이 5점인 M5가 U1에게 추천될 것이다.

 

본 포스팅에서 활용한 데이터셋 : Movie lens 100K

https://www.kaggle.com/datasets/imkushwaha/movielens-100k-dataset

movie lens u.data

u.data를 불러와서 r_cols로 columns 명을 정해주자. 시간은 고려하지 않을 것이기 때문에 Drop 시킨다.

import numpy as np
import pandas as pd

r_cols = ['user_id', 'movie_id', 'rating', 'timestamp']
ratings = pd.read_csv('C:/RecoSys/Data/u.data', names=r_cols,  sep='\t',encoding='latin-1')
ratings = ratings.drop('timestamp', axis=1)

알고리즘을 학습시키기 위해 데이터셋을 아래와 같이 Full matrix로 변환하는 것이 계산하기 편하다.

따라서, pandas의 pivot API를 활용한다 (값 = rating, index = 유저 Id, columns = 영화 Id)

# Rating 데이터를 test, train으로 나누고 train을 full matrix로 변환
from sklearn.model_selection import train_test_split
x = ratings.copy()
y = ratings['user_id']
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.25, stratify=y, random_state=12)
rating_matrix = x_train.pivot(values='rating', index='user_id', columns='movie_id')

모든 사람이 모든 영화를 평가하지 않았으므로, NaN값이 나옴

  • fillna(0)로 0을 채워주고, user_similarity를 구함
  • 이는 사용자 간의 유사도이기 때문에 대칭행렬이 된다.

# Train set의 모든 사용자 pair의 Cosine similarities 계산
from sklearn.metrics.pairwise import cosine_similarity
matrix_dummy = rating_matrix.copy().fillna(0)
user_similarity = cosine_similarity(matrix_dummy, matrix_dummy)
user_similarity = pd.DataFrame(user_similarity, index=rating_matrix.index, columns=rating_matrix.index)

다음으로 알고리즘 평가를 위한 RMSE 등의 함수를 정의해주자.

def RMSE(y_true, y_pred):
    import numpy as np
    return np.sqrt(np.mean((np.array(y_true) - np.array(y_pred))**2))

def score(model):
    id_pairs = zip(x_test['user_id'], x_test['movie_id'])
    y_pred = np.array([model(user, movie) for (user, movie) in id_pairs])
    y_true = np.array(x_test['rating'])
    return RMSE(y_true, y_pred)

위에서 추천 알고리즘은 유저에 대한 평점을 "예측"하고, 실제 값과 비교하는 방식으로 성능을 측정한다고 했다.

따라서, score 함수에서는 테스트 데이터 셋의 "user_id"와 "move_id"를 추천 모델에 넣어 y_pred를 예측한 후 실제 라벨인 rating과의 차이를 RMSE로 구한다.

 

Collaborative Filtering Recommendation System을 정의하자.

def cf_simple(user_id, movie_id):
    import numpy as np
    if movie_id in rating_matrix:   # 해당 movie_id가 rating_matrix에 존재하는지 확인
        sim_scores = user_similarity[user_id]
        movie_ratings = rating_matrix[movie_id]
        none_rating_idx = movie_ratings[movie_ratings.isnull()].index
        movie_ratings = movie_ratings.dropna()
        sim_scores = sim_scores.drop(none_rating_idx)
        mean_rating = np.dot(sim_scores, movie_ratings) / sim_scores.sum()
    else:  #해당 movie_id가 없으므로 기본값 3.0을 예측치로 돌려 줌
        mean_rating = 3.0
    return mean_rating

코드에서는 여러가지 예외 사항을 처리하여 복잡해보인다. 입력값은 user_id와 movie_id이다. 

 

로직은 다음과 같다.

 

(1) 입력 받은 사용자와 다른 사용자 간의 유사도를 user_similarity 테이블을 통해 sim_scores에 할당

(2) 입력 받은 영화에 대한 모든 사용자의 rating 값을 가져와 movie_ratings에 할당

(3) 입력 받은 영화를 평가하지 않은 사용자의 index를 non_rating_idx에 할당

(4) movie_ratings에 null값을 제거 -> 해당 영화를 평가하지 않은 사용자의 rating(여기서 당연히 null) 제거

(5) 유사도 테이블에서 해당 영화를 평가하지 않은 사람들의 유사도 제거

(6) 입력 받은 영화에 대한 평가가 존재하는 사용자의 유사도와 해당 영화 ratings에 대한 가중평균 값을 구하여 mean_rating에 할당

 

마지막으로 모델 평가를 위해 앞에서 정의해준 score에 알고리즘을 입력해줌

score(cf_simple)

 

>>output :1.0179290340207656

 

참고:

연세대학교 개인화추천시스템 - 임일 교수님

Python을 이용한 개인화 추천시스템 - 임일 교수님