
사람들의 취향은 실제로 서로 유사한 부분이 많다
,
두 벡터의 코사인 유사도
: 두 선이 원점에서 정확히 같은 방향으로 그려질 때
: 두 선이 원점에서 정확히 반대 방향으로 그려질 때
: 두 선이 완전히 직각일 때
mean normalization가장 직관적인 차이는 코사인 유사도는 각 벡터, 선의 크기가 중요하지 않다는 것
1: 닭 가슴살, 2: 아령, 3: 맥주, 4: 피자일 때
, ,
어떤 형식의 데이터를 사용하는지에 따라 유클리드 거리를 사용할지
코사인 유사도를 사용할지 결정
# ratings.csv 데이터 원본 일부
# user_id,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19
# 0,2.0,3.0,4.0,,2.0,3.0,,,,4.0,4.0,,,1.0,,,2.0,5.0,2.0,
# 1,,,,4.0,,5.0,,,2.0,,4.0,,1.0,,,,,5.0,,
import pandas as pd
import numpy as np
from math import sqrt
RATING_DATA_PATH = './data/ratings.csv' # 받아올 평점 데이터 경로 정의
np.set_printoptions(precision=2) # 소수점 둘째 자리까지만 출력
def distance(user_1, user_2):
"""유클리드 거리를 계산해주는 함수"""
return sqrt(np.sum((user_1 - user_2)**2))
def filter_users_without_movie(rating_data, movie_id):
"""movie_id 번째 영화를 평가하지 않은 유저들은 미리 제외해주는 함수"""
return rating_data[~np.isnan(rating_data[:,movie_id])]
def fill_nan_with_user_mean(rating_data):
"""평점 데이터의 빈값들을 각 유저 평균 값으로 체워주는 함수"""
filled_data = np.copy(rating_data) # 평점 데이터를 훼손하지 않기 위해 복사
row_mean = np.nanmean(filled_data, axis=1) # 유저 평균 평점 계산
inds = np.where(np.isnan(filled_data)) # 비어 있는 인덱스들을 구한다
filled_data[inds] = np.take(row_mean, inds[0]) #빈 인덱스를 유저 평점으로 채운다
return filled_data
def get_k_neighbors(user_id, rating_data, k):
"""user_id에 해당하는 유저의 이웃들을 찾아주는 함수"""
distance_data = np.copy(rating_data) # 평점 데이터를 훼손하지 않기 위해 복사
# 마지막에 거리 데이터를 담을 열 추가한다
distance_data = np.append(distance_data, np.zeros((distance_data.shape[0], 1)), axis=1)
# 반복문을 통해 모든 유저와의 거리 측정
for i in range(len(distance_data)):
row = distance_data[i]
if i == user_id: # 같은 유저면 거리를 무한대로 설정
row[-1] = np.inf
else: # 다른 유저면 마지막 열에 거리 데이터를 저장
row[-1] = distance(distance_data[user_id][:-1], row[:-1])
# 데이터를 거리 열을 기준으로 정렬한다
distance_data = distance_data[np.argsort(distance_data[:, -1])]
# 가장 가까운 k개의 행만 리턴한다 + 마지막(거리) 열은 제외한다
return distance_data[:k, :-1]
def predict_user_rating(rating_data, k, user_id, movie_id,):
"""예측 행렬에 따라 유저의 영화 평점 예측 값 구하기"""
# movie_id 번째 영화를 보지 않은 유저를 데이터에서 미리 제외시킨다
filtered_data = filter_users_without_movie(rating_data, movie_id)
# 빈값들이 채워진 새로운 행렬을 만든다
filled_data = fill_nan_with_user_mean(filtered_data)
# 유저 user_id와 비슷한 k개의 유저 데이터를 찾는다
neighbors = get_k_neighbors(user_id, filled_data, k)
# 이웃들의 movie_id 번째 영화 평점 평균 리턴
sum_of_neighbors = sum(neighbors[:, movie_id])
return sum_of_neighbors / k
# 실행 코드
# 평점 데이터를 불러온다
rating_data = pd.read_csv(RATING_DATA_PATH, index_col='user_id').values
# 5개의 이웃들을 써서 유저 0의 영화 3에 대한 예측 평점 구하기
predict_user_rating(rating_data, 5, 0, 3)
# 4.7999999999999998
속성을 찾거나 정할 필요가 없음
좀 더 폭넓은 상품을 추천할 수 있음
내용 기반 추천보다 성능이 더 좋게 나오는 경우가 많음
데이터가 많아야 함
유저 한 명이 열심히 평점을 줘도 다른 사람들도 열심히 평점을 줘야 함
새로운 물건이나 유저에게 추천해 주기 힘듦, 관련 데이터가 전무하기 때문
인기가 많은 소수의 상품이 추천 시스템을 장악할 수 있음
어떤 상품이 왜 추천됐는지 정확히 알기 힘듦
내용 기반 추천, 협업 필터링 등 여러 방식을 합쳐서 사용하면
모델들의 단점을 보완하면서 장점을 살릴 수 있음