◈ 참고자료 출처
• "Python을 이용한 개인화 추천시스템", 임일, 청람
◈ 개념
(1) UBCF(User-Based Collaborative Filtering)
• 사용자간 유사도 사용 → 사용자별로 개인화된 추천 리스트
• 사용자를 기반으로 하기 때문에 데이터 변화에 민감함.
• 계산량이 많음 (아이템 보다 사용자 수가 많기 때문에)
(2) IBCF(Item-Based Collaborative Filtering)
• 아이템간 유사도 사용 → 사용자가 선택한 아이템이 같으면 동일한 추천리스트
• Robustness가 높음 = Coverage가 높음 → 사용자가 아이템 하나만 평가하면 추천이 가능함
• 계산량이 적고 데이터 변화에 덜 민감함
• 상대적으로 대규모 상업용 시스템에 더 많이 사용됨
• 예상 평점이 return 되는 것이 아니기 때문에, test set에서 해당 사용자를 얼마나 잘 맞췄는지 precision, recall 등으로 평가한다.
◈ 실습 : UBCF와 transpose 형태
• 아이템간의 유사도를 이용해서 각 아이템을 평가한 유저들의 rating을 가중평균하면, 각 유저에 대한 아이템의 예상 평점 도출 가능
✅ 데이터 불러오기
import numpy as np
import pandas as pd
#데이터 불러오기
i_cols = ['movie_id', 'title', 'release date', 'video release date', 'IMDB URL',
'unknown', 'Action', 'Adventure', 'Animation', 'Children\'s', 'Comedy',
'Crime', 'Documentary', 'Drama', 'Fantasy', 'Film-Noir', 'Horror',
'Musical', 'Mystery', 'Romance', 'Sci-Fi', 'Thriller', 'War', 'Western']
#영화 정보 데이터
movies = pd.read_csv('u.item', sep='|', names=i_cols, encoding='latin-1')
movies = movies[['movie_id', 'title']]
movies = movies.set_index('movie_id')
#사용자들의 평점 데이터
r_cols = ['user_id', 'movie_id', 'rating', 'timestamp']
ratings = pd.read_csv('u.data', names=r_cols, sep='\t',encoding='latin-1')
ratings = ratings.drop('timestamp', axis=1)
데이터를 불러오고 column 이름을 별도로 설정한다.
✅ pivot 테이블 생성
from sklearn.model_selection import train_test_split
# Rating 데이터를 test, train로 분리
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)
#full matrix 생성
#UBCF와 다른 부분 : movie_id를 인덱스로 매트릭스 생성
rating_matrix_t = x_train.pivot(values='rating', index='movie_id', columns='user_id')
train/test 데이터를 나누고, 아이템 기반으로 유사도를 구해야 하기 때문에 영화 이름을 인덱스로 full_matrix를 생성함 (IBCF와 transpose 형태)
✅ Item matrix 생성
# RMSE 계산
def RMSE(y_true, y_pred):
return np.sqrt(np.mean((np.array(y_true) - np.array(y_pred))**2))
def score(model, neighbor_size=20):
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)
# 아이템 pair의 Cosine similarities 계산
from sklearn.metrics.pairwise import cosine_similarity
matrix_dummy = rating_matrix_t.copy().fillna(0)
item_similarity = cosine_similarity(matrix_dummy, matrix_dummy)
item_similarity = pd.DataFrame(item_similarity, index=rating_matrix_t.index, columns=rating_matrix_t.index)
IBCF에서는 아이템을 기준으로 유사도 행렬을 생성함 (1631 * 1631) 형태
✅ IBCF 함수
def ibcf(user_id, movie_id):
import numpy as np
if user_id in rating_matrix_t: # 사용자가 train set에 있는지 확인
if movie_id in item_similarity: # 현재 영화가 train set에 있는지 확인
# 현재 영화와 다른 영화의 similarity 값 가져오기 : column
sim_scores = item_similarity[movie_id]
# 현 사용자의 모든 rating 값 가져오기
user_rating = rating_matrix_t[user_id] : column
# 사용자가 평가하지 않은 영화 index 가져오기
non_rating_idx = user_rating[user_rating.isnull()].index
# 사용자가 평가하지 않은 영화 제거
user_rating = user_rating.dropna()
# 사용자가 평가하지 않은 영화의 similarity 값 제거
sim_scores = sim_scores.drop(non_rating_idx)
# 현 영화에 대한 사용자의 예상 rating 계산, 가중치는 현 영화와 사용자가 평가한 영화의 유사도
mean_rating = np.dot(sim_scores, user_rating) / sim_scores.sum()
else:
mean_rating = 3.0
else:
mean_rating = 3.0
return mean_rating
#성과 평가
score(ibcf)
유저 기반 추천과 행과 열이 반대인 형태로 진행함.
✅ IBCF + KNN
#KNN 을 활용한 iBCF
def ibcf_knn(user_id, movie_id, neighbor_size=20):
import numpy as np
if user_id in rating_matrix_t: # 사용자가 train set에 있는지 확인
if movie_id in item_similarity: # 현재 영화가 train set에 있는지 확인
# 현재 영화와 다른 영화의 similarity 값 가져오기
sim_scores = item_similarity[movie_id]
# 현 사용자의 모든 rating 값 가져오기
user_rating = rating_matrix_t[user_id]
# 사용자가 평가하지 않은 영화 index 가져오기
non_rating_idx = user_rating[user_rating.isnull()].index
# 사용자가 평가하지 않은 영화 제거
user_rating = user_rating.dropna()
# 사용자가 평가하지 않은 영화의 similarity 값 제거
sim_scores = sim_scores.drop(non_rating_idx)
#KNN
# Neighbor size가 지정되지 않은 경우
if neighbor_size == 0:
# 현재 영화를 평가한 모든 사용자의 가중평균값 구하기
mean_rating = np.dot(sim_scores, user_rating) / sim_scores.sum()
# Neighbor size가 지정된 경우
else:
# 지정된 neighbor size 값과 해당 영화를 평가한 총사용자 수 중 작은 것으로 결정
neighbor_size = min(neighbor_size, len(sim_scores))
# array로 바꾸기 (argsort를 사용하기 위함)
sim_scores = np.array(sim_scores)
user_rating = np.array(user_rating)
# 유사도를 순서대로 정렬
user_idx = np.argsort(sim_scores)
# 유사도를 neighbor size만큼 받기
sim_scores = sim_scores[user_idx][-neighbor_size:]
# 영화 rating을 neighbor size만큼 받기
user_rating = user_rating[user_idx][-neighbor_size:]
# 최종 예측값 계산
mean_rating = np.dot(sim_scores, user_rating) / sim_scores.sum()
else:
mean_rating = 3.0
else:
mean_rating = 3.0
return mean_rating
# 정확도 계산
score(ibcf_knn)
중간에 KNN을 사용하여 가까운 이웃들만 추천한다. min() 함수 사용하는 이유는 설정한 이웃 크기보다 평가한 영화 수가 적을 수도 있기 때문.
◈ 개선된 IBCF
• 실제 IBCF가 이루어지는 방식은 아래 그림과 같다
위는 한 사용자에 대한 추천 과정을 보여주는 것. 앞선 사례에서는 Train / Test로 평점을 사용해서 RMSE로 평가했다.
하지만 위 그림에서는 Train Set으로 학습하고 가장 선호도가 높게 나타난 Item 4, Item 1과 유사한 항목을 Test Set에서 계산해서 추천한다. (Item 9, Item 7)
따라서 이 방식으로 하면 RMSE 계산이 불가능하기 때문에 Precision, Recall 등을 사용해야 함
< 고려해야 할 사항들 >
① Reference
• 한 사용자가 좋게 평가한 아이템의 수를 몇개로 사용할 것인가?
ex) Item 1 과 Item 4를 둘 다 사용할 것인가? 하나만 사용할 것인가?
• 정확도를 평가할 때 올바르게 추천했다고 판단한 기준을 어떻게 정할 것인가? (정확도에 직접적인 영향 줌)
ex) Test Set에서 Item 7은 평점이 낮다. 이 아이템이 최종적으로 추천되었을 경우 올바르게 추천되었다고 볼 수 있을 것인가?
② 추천할 아이템의 수
• 추천하는 아이템 수가 많으면 → Recall 이 높아짐
• 추천하는 아이템 수가 적으면 → Precision이 높아짐
: F1 score로 판단하는게 적절
'추천시스템' 카테고리의 다른 글
컨텐츠 기반 추천(Content Based Filtering) (0) | 2022.01.10 |
---|---|
협업필터링 - 사용자의 평가 경향 고려 (0) | 2021.12.27 |
협업필터링 + KNN (0) | 2021.10.11 |
협업필터링 기본 (1) | 2021.10.11 |
추천시스템 관련 기본 개념 (0) | 2021.10.08 |
댓글