데이터 분석/머신러닝

[ML] 15. 아이템 기반 최근접 이웃 협업 필터링

eunnys 2023. 11. 28. 14:53

▶ 데이터셋 다운로드
- 사용자-영화 평점 행렬 데이터 필요
- Grouplens 사이트에서 만든 MoviesLens 데이터셋 사용 (축소 버전 사용)
https://grouplens.org/datasets/movielens/latest

 

MovieLens Latest Datasets

These datasets will change over time, and are not appropriate for reporting research results. We will keep the download links stable for automated downloads. We will not archive or make available p…

grouplens.org

 

# 데이터 로딩
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)  # (9742, 3)
print(ratings.shape) # (100836, 4)
movies.head()

 

ratings.head()

 

# 전체 사용자는 610명
ratings['userId'].unique().size

 

 

## 데이터 전처리 ##
- 평점 데이터가 행 데이터로 되어있어서 사용자-아이템(영화) 평점 행렬로 변환 (피벗테이블 사용)

 

# 불필요한 컬럼 제거
ratings = ratings.drop('timestamp', axis=1)
ratings_matrix = ratings.pivot_table(index='userId', columns='movieId', values='rating')
print(ratings_matrix.shape) # (610, 9724)
ratings_matrix.head()

 

 

## 데이터 병합 ##
- 영화 아이디(movieId)를 영화 제목으로 변환하기 위해 ratings 데이터와 movies 데이터 병합
- 사용자-아이템 평점 행렬로 변환
- NaN 값은 0으로 변경

 

rating_movies = pd.merge(ratings, movies, on='movieId')
print(rating_movies.shape) # (100836, 5)
rating_movies.head(2)

 

ratings_matrix = rating_movies.pivot_table(index='userId', columns='title', values='rating')
print(ratings_matrix.shape) # (610, 9719)
ratings_matrix.head(2)

 

# 결측치를 0으로 채움
ratings_matrix = ratings_matrix.fillna(0)
ratings_matrix.head(2)

 

 

## 영화와 영화간 평점 유사도 계산 ##
cosine_similarity()를 이용하여 영화와 영화간 유사도를 산출하기 위해서는 ratings_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) # (9719, 9719)
item_sim_df.head(3)

 

 

## 개인화된 영화 추천을 위한 예측 평점 계산 ##
- 매개변수 설명
    - ratings_arr : 사용자-영화 평점 행렬, shape(610, 9719)
    - item_sim_arr : 영화간 평점 유사도 행렬, shape(9719, 9719)

 

 

def predict_rating_topsim(ratings_arr, item_sim_arr, n=20):
    # 예측 평점 행렬 : R_hat
    pred = np.zeros_like(ratings_arr) # 0으로 채워진 똑같은 모양의 행렬

    # 영화의 수 만큼 반복 (9718)
    for col in range(ratings_arr.shape[1]): 
        # 아이템 유사도 행렬에서 col번째 영화와 유사도가 큰 n개 영화의 인덱스를 반환
        top_n_items = np.argsort(item_sim_arr[:, col])[::-1][:n] # 내림차순 정렬
        # 사용자 수 만큼 반복 (610)
        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]))

    return pred

 

 

## 함수 내용 테스트 ## 

 

ratings_arr = ratings_matrix.values
item_sim_arr = item_sim_df.values

# 0번째 영화와 평점 유사도가 높은 상위 20개 영화의 인덱스 값
top_n_items = np.argsort(item_sim_arr[:, 0])[::-1][:20]
print(top_n_items)
print()

# 실제 평점 유사도 : S_i,n
print(item_sim_arr[0,:][top_n_items])
print()

# 실제 평점 : R_u,n
print(ratings_arr[0,:][top_n_items])
print()

# 0번째 사용자의 0번째 영화에 대한 예측 평점
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번째 유저의 9719개 영화에 대한 최대와 최소 예측 평점 값
user_1 = ratings_pred_matrix.loc[1]
print(np.max(user_1), np.min(user_1))
4.595776354211019 0.0

 

# 1번째 유저의 9719개 영화에 대한 예측 평점 중 0점이 아닌 영화의 개수 (1133개)
user_1[user_1 != 0].size

 

 

## 평점을 주지 않은 영화 목록 반환 함수 ##
- 영화 추천은 개인이 아직 관람하지 않은 영화를 추천하는 방식

 

def get_unseen_movies(ratings_matrix, userId):
    # 9719개의 영화에 대한 평점 정보가 넘어옴 (시리즈 형태 : 인덱스는 영화의 제목, 값은 평점)
    user_rating = ratings_matrix.loc[userId] 
    # 관람하지 않은 영화의 목록만 넘파이의 다차원 배열로 반환
    unseen_list = user_rating[user_rating == 0].index.values
    return unseen_list

 

 

## 특정 사용자의 관람하지 않은 영화에 대한 예측 평점 기반 추천 ##

 

unseen_list = get_unseen_movies(ratings_matrix, 9)
# 9번 유저가 관람하지 않은 영화의 목록과 예측 평점
ratings_pred_matrix.loc[9, unseen_list] 
# ratings_pred_matrix.loc[9, unseen_list].size # 9673
title
'71 (2014)                                                0.0
'Hellboy': The Seeds of Creation (2004)                   0.0
'Round Midnight (1986)                                    0.0
'Salem's Lot (2004)                                       0.0
'Til There Was You (1997)                                 0.0
                                                         ... 
anohana: The Flower We Saw That Day - The Movie (2013)    0.0
eXistenZ (1999)                                           0.0
xXx: State of the Union (2005)                            0.0
¡Three Amigos! (1986)                                     0.0
À nous la liberté (Freedom for Us) (1931)                 0.0
Name: 9, Length: 9673, dtype: float64

 

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]

# 9번째 유저의 관람하지 않은 영화 중 예측 평점이 높은 상위 10개 영화 목록과 예측 평점
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
Click to add a cell.