본문 바로가기

데이터/머신러닝

실전 1.3 협업 필터링

해당 글은 코드잇 강의를 참고하여 작성되었습니다.

- 협업 필터링이란?

  • 수많은 유저 데이터들이 협업해서 상품 추천
  • 비슷한 유저를 통해 추천

 

- 데이터 표현하기

  • 유저의 평점을 벡터로 표현
  • 유저 a가 영화 i에 준 평점 = ri(a)
  • 거​리를 계산할 때 비어있는 데이터는 0이나 평균 또는 mean normalization을 사용해 채워준다.

 

- 비슷한 유저 정의하기 : 유클리드 거리

  • 거리가 멀수록 유사도가 낮고 가까울수록 유사도가 높다. 

 

- 비슷한 유저 정의하기 : 코사인 유사도

  • 두 선사이의 각도를 통해 유사도를 구한다.
  • 코사인의 값은 데이터가 비슷할수록 크고 다를수록 작다.
  • 유클리드 거리와 코사인 유사도의 또 다른 차이는 유클리드 거리는 클수록 두 데이터가 다르고, 작을수록 두 데이터가 비슷하다는 의미였고, 코사인 유사도는 클수록 두 데이터가 비슷하고, 작을수록 두 데이터가 다르다는 의미이다. 따라서 유저 A와 가장 '비슷한' 유저들을 찾을 때 유클리드 거리는 거리가 가장 작은 유저들을 찾아야 되고요. 코사인 유사도는 유사도가 가장 큰 유저들을 찾아내야 된다.

 

- 상품 추천하기

  • ri(x) : 유저 x의 영화 i에 대한 평점
  • : 상품 i를 평가한 유저 중 유저 x와 가장 비슷한 k명의 집합
  • 위 식을 사용해 평점 데이터의 빈 원소들을 채운 후 예상 평점이 가장 높은 영화 몇 개를 유저에게 추천한다.
#유저 평점 예측하기

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=0)  # 유저 평균 평점 계산
    
    inds = np.where(np.isnan(filled_data))  # 비어 있는 인덱스들을 구한다
    filled_data[inds] = np.take(row_mean, inds[1])  #빈 인덱스를 유저 평점으로 채운다
    
    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)
    
    return np.mean(neighbors[:, movie_id])
    
    
# 실행 코드   
# 평점 데이터를 불러온다
rating_data = pd.read_csv(RATING_DATA_PATH, index_col='user_id').values
# 5개의 이웃들을 써서 유저 0의 영화 3에 대한 예측 평점 구하기
predict_user_rating(rating_data, 5, 0, 3)

 

- 상품 기반 협업 필터링

  • 작동하는 방식은 거의 유사하지만 상품이 유저들보다 단순하기 때문에 성능은 더 좋다.

 

- 장점

  • 속성을 찾거나 정할 필요가 없다.
  • 좀 더 폭넓은 상품을 추천할 수 있다.
  • 내용 기반 추천보다 성능이 더 좋게 나오는 경우가 많다.

 

- 단점

  • 데이터가 많아야 한다.
  • 인기가 많은 소수의 상품이 추천 시스템을 장악할 수 있다.
  • 어떤 상품이 왜 추천되었는지 정확히 알기 어렵다.
  • 따라서 선형회귀/ 다항회귀/ 유저 기반 협업 필터링/ 상품 기반 협업 필터링 등 다양한 방식을 합쳐서 사용한다.

'데이터 > 머신러닝' 카테고리의 다른 글

Chapter 02-1 훈련 세트와 테스트 세트  (0) 2022.02.20
실전 1.4 행렬 인수분해  (0) 2021.07.27
실전 1.2 내용 기반 추천  (0) 2021.07.26
4.3 에다 부스트  (0) 2021.07.26
4.2 랜덤 포레스트  (0) 2021.07.25