◈ 협업필터링과 KNN을 함께 사용
• 특정 사용자에 대해서 예측값 가중치를 계산할 때, 가장 가까운 K명의 neighbor의 데이터만 사용하는 방식
✔ 가정
• 취향이 비슷한 사람들은 모든 아이템에 대한 선호가 일관되게 유사하다
• 추천 대상인 사용자와 유사도가 높은 사용자일수록, 취향을 정확하게 반영할 수 있다.
✔ 이웃의 크기 결정 방식
① KNN :
특정 사용자와 최대한 유사한 K 명의 이웃을 사용
② Thresholding :
유사도의 기준을 정해두고(ex. 0.7 이상) 그에 따라서 이웃을 정하는 방식.
KNN보다 정확도는 높지만, Coverage가 낮아진다(추천할 수 있는 사용자의 %가 줄어든다)
→ Thresholding의 경우 해당 조건을 만족하는 이웃이 존재하지 않을 수 있으므로, 주로 KNN 방식을 사용한다
✔ 최적의 K 값?
• 너무 크면 정확도가 떨어지고, 너무 작으면 과적합 가능성 생김
• optimal neighbor size는 k 를 여러번 바꿔가면서 확인해보면 됨
◈ 실습
• 이전 기본 CF 게시글과 나머지 부분은 모두 동일하고, cf_simple() 함수와 score() 함수만 변경함
# 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=0):
id_pairs = zip(x_test['user_id'], x_test['movie_id'])
y_pred = np.array([model(user, movie, neighbor_size) for (user, movie) in id_pairs])
y_true = np.array(x_test['rating'])
return RMSE(y_true, y_pred)
# CF + KNN
def cf_knn(user_id, movie_id, neighbor_size=20):
if movie_id in rating_matrix:
# 현재 사용자와 다른 사용자 간의 similarity 가져오기 (총 943개)
sim_scores = user_similarity[user_id]
# 현재 영화에 대한 모든 사용자의 rating값 가져오기
movie_ratings = rating_matrix[movie_id]
# 현재 영화를 평가하지 않은 사용자의 index 가져오기
none_rating_idx = movie_ratings[movie_ratings.isnull()].index
# 현재 영화를 평가하지 않은 사용자의 rating (null) 제거
movie_ratings = movie_ratings.drop(none_rating_idx)
# 현재 영화를 평가하지 않은 사용자의 similarity값 제거
sim_scores = sim_scores.drop(none_rating_idx)
### 여기서부터 KNN 추가한 내용
if neighbor_size == 0: # Neighbor size가 지정되지 않은 경우
# 현재 영화를 평가한 모든 사용자의 가중평균값 구하기
mean_rating = np.dot(sim_scores, movie_ratings) / sim_scores.sum()
else: # Neighbor size가 지정된 경우
# 지정된 neighbor size 값과 해당 영화를 평가한 총사용자 수 중 작은 것으로 결정
neighbor_size = min(neighbor_size, len(sim_scores))
# array로 바꾸기 (argsort를 사용하기 위함)
sim_scores = np.array(sim_scores)
movie_ratings = np.array(movie_ratings)
# 유사도를 순서대로 정렬
user_idx = np.argsort(sim_scores)
# 유사도를 neighbor size만큼 받기
sim_scores = sim_scores[user_idx][-neighbor_size:]
# 영화 rating을 neighbor size만큼 받기
movie_ratings = movie_ratings[user_idx][-neighbor_size:]
# 최종 예측값 계산
mean_rating = np.dot(sim_scores, movie_ratings) / sim_scores.sum()
else:
mean_rating = 3.0
return mean_rating
score(cf_knn, 30)
• 최종 결과가 1.0095 정도로, 단순 CF 보다는 성능이 개선됨을 확인
• neighbor 사이즈가 입력된 경우에도, 해당 영화를 평가한 유저의 수가 K 보다 적을 수 있으므로 한번 더 확인 해야함
<최적의 K값 확인>
#최적의 K값 확인
for k in (10,20,30,40,50):
print('k = ', k,'RMSE = ', score(cf_knn, k))
• 모든 경우에 통용할 수 있는 K 값을 단정지어서 말할 수 없으므로, 여러번 시도하면서 찾는게 최선이다.
이 경우에는 k=30 ~ 40인 구간에 대해서 다시 세부적으로 탐색해볼 필요가 있다.
<실제로 추천 하기>
recommender 함수 내에서 cf_knn() 부분에 파라미터가 3개 들어가는 것 제외하고 동일함
# 전체 데이터 로딩
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)
rating_matrix = ratings.pivot(values='rating', index='user_id', columns='movie_id')
# 영화 제목 가져오기
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')
# Cosine similarity 계산
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)
# 추천하기
def recommender(user, n_items=10, neighbor_size=20):
# 현재 사용자의 모든 아이템에 대한 예상 평점 계산
predictions = []
rated_index = rating_matrix.loc[user][rating_matrix.loc[user] > 0].index # 이미 평가한 영화 확인
items = rating_matrix.loc[user].drop(rated_index)
# 여기서 파라미터 값이 3개 들어감
# 예상평점 계산
for item in items.index:
predictions.append(cf_knn(user, item, neighbor_size))
recommendations = pd.Series(data=predictions, index=items.index, dtype=float)
recommendations = recommendations.sort_values(ascending=False)[:n_items] # 예상평점이 가장 높은 영화 선택
recommended_items = movies.loc[recommendations.index]['title']
return recommended_items
# 영화 추천 함수 부르기
recommender(2, 10, 20)
◈ 참고자료 출처
• "Python을 이용한 개인화 추천시스템", 임일, 청람
'추천시스템' 카테고리의 다른 글
컨텐츠 기반 추천(Content Based Filtering) (0) | 2022.01.10 |
---|---|
협업필터링 - 사용자의 평가 경향 고려 (0) | 2021.12.27 |
협업필터링 - 아이템 기반(IBCF) (0) | 2021.11.04 |
협업필터링 기본 (1) | 2021.10.11 |
추천시스템 관련 기본 개념 (0) | 2021.10.08 |
댓글