머신러닝

[파이썬 머신러닝 완벽 가이드] - 9장

주니빙 2025. 1. 2. 12:37

9장  추천 시스템

 

01  추천 시스템의 개요와 배경

추천 시스템을 통해 사용자의 취향을 이해하고 맞춤 상품과 콘텐츠를 제공해 조금이라도 오래 자기 사이트에 고객을 머무르게 한다.

추천엔진 : 사용자가 무엇을 원하는지 빠르게 찾아내어 사용자의 온라인 쇼핑 이용 즐거움을 배가시킨다.

 

추천 시스템을 구성하는 데이터

  • 사용자가 어떤 상품을 구매했는가?
  • 사용자가 어떤 상품을 둘러보거나 장바구니에 넣었는가?
  • 사용자가 평가한 영화 평점은? 제품 평가는?
  • 사용자가 스스로 작성한 자신의 취향은?
  • 사용자가 무엇을 클릭했는가?

 

추천 시스템의 유형 - 콘텐츠 기반 필터링 방식 / 협업 필터링 방식

 

02  콘텐츠 기반 필터링 추천 시스템

콘텐츠 기반 필터링 방식 : 사용자가 특정한 아이템을 매우 선호하는 경우 그 아이템과 비슷한 콘텐츠를 가진 다른 아이템을 추천하는 방식

 

03  최근접 이웃 협업 필터링

협업 필터링 방식 : 친구들에게 물어보는 것과 유사한 방식으로, 사용자가 아이템에 매긴 평점 정보나 상품 구매 이력과 같은 사용자 행동 양식만을 기반으로 추천을 수행하는 것

 

협업 필터링의 주요 목표 : 사용자-아이템 평점 매트릭스와 같은 축적된 사용자 행동 데이터를 기반으로 사용자가 아직 평가하지 않은 아이템을 예측 평가 하는 것

 

협업 펼터링 알고리즘에 사용되는 사용자-아이템 평점 행렬에서 행은 개별 사용자, 열은 개별 아이템으로 구성

사용자-아이템 평점 행렬 : 많은 아이템을 열로 가지는 다차원 행렬이며, 사용자가 아이템에 대한 평점을 매기는 경우가 많지 않기 때문에 희소 행렬의 특징을 가진다.

 

최근접 이웃 협업 필터링

  • 사용자 기반 : 당신과 비슷한 고객들이 다음 상품도 구매했습니다
  • 아이템 기반 : 이 상품을 선택한 다른 고객들은 다음 상품도 구매했습니다

 

04  잠재 요인 협업 필터링

잠재 요인 협업 필터링 : 사용자-아이템 평점 매트릭스 속에 숨어 있는 잠재 요인을 추출해 추천 예측을 할 수 있게 하는 기법

 

 

행렬 분해 : 다차원의 매트릭스를 저차원 매트릭스로 분해히는 기법으로서 대표적으로 SVD, NMF 등이 있다.

 

확률적 경사 하강법을 이용한 행렬 분해 방법 : P 와 Q 행렬로 계산된 예측 R 행렬 값이 실제 R 행렬 값과 가장 최소의 오류를 가질 수 있도록 반복적인 비용 함수 최적화를 통해 P와 Q를 유추해내는 것

 

확률적 경사 하강법(이하 SGD)을 이용한 행렬 분해의 전반적인 절차

  1. P와 Q를임의의값을가진행렬로설정
  2. P와 Q.T 값을 곱해 예측 R 행렬을 계산하고 예측 R 행렬과 실제 R 행렬에 해당하는 오류 값을 계산
  3. 이 오류 값을 최소화할 수 있도록 P와 Q 행렬을 적절한 값으로 각각 업데이트
  4. 만족할 만한 오류 값을 가질 때까지 2. 3번 작업을 반복하면서 P와 Q 값을 업데이트해 근사화

 

import numpy as np

# 원본 행렬 R 생성, 분해 행렬 P와 Q 초기화, 잠재요인 차원 K는 3 설정. 
R = np.array([[4, np.NaN, np.NaN, 2, np.NaN ],
              [np.NaN, 5, np.NaN, 3, 1 ],
              [np.NaN, np.NaN, 3, 4, 4 ],
              [5, 2, 1, 2, np.NaN ]])
num_users, num_items = R.shape
K=3

# P와 Q 매트릭스의 크기를 지정하고 정규분포를 가진 random한 값으로 입력합니다. 
np.random.seed(1)
P = np.random.normal(scale=1./K, size=(num_users, K))
Q = np.random.normal(scale=1./K, size=(num_items, K))
from sklearn.metrics import mean_squared_error

def get_rmse(R, P, Q, non_zeros):
    error = 0
    # 두개의 분해된 행렬 P와 Q.T의 내적으로 예측 R 행렬 생성
    full_pred_matrix = np.dot(P, Q.T)
    
    # 실제 R 행렬에서 널이 아닌 값의 위치 인덱스 추출하여 실제 R 행렬과 예측 행렬의 RMSE 추출
    x_non_zero_ind = [non_zero[0] for non_zero in non_zeros]
    y_non_zero_ind = [non_zero[1] for non_zero in non_zeros]
    R_non_zeros = R[x_non_zero_ind, y_non_zero_ind]
    full_pred_matrix_non_zeros = full_pred_matrix[x_non_zero_ind, y_non_zero_ind]
      
    mse = mean_squared_error(R_non_zeros, full_pred_matrix_non_zeros)
    rmse = np.sqrt(mse)
    
    return rmse
# R > 0 인 행 위치, 열 위치, 값을 non_zeros 리스트에 저장. 
non_zeros = [ (i, j, R[i,j]) for i in range(num_users) for j in range(num_items) if R[i,j] > 0 ]

steps=1000
learning_rate=0.01
r_lambda=0.01

# SGD 기법으로 P와 Q 매트릭스를 계속 업데이트. 
for step in range(steps):
    for i, j, r in non_zeros:
        # 실제 값과 예측 값의 차이인 오류 값 구함
        eij = r - np.dot(P[i, :], Q[j, :].T)
        # Regularization을 반영한 SGD 업데이트 공식 적용
        P[i,:] = P[i,:] + learning_rate*(eij * Q[j, :] - r_lambda*P[i,:])
        Q[j,:] = Q[j,:] + learning_rate*(eij * P[i, :] - r_lambda*Q[j,:])

    rmse = get_rmse(R, P, Q, non_zeros)
    if (step % 50) == 0 :
        print("### iteration step : ", step," rmse : ", rmse)

pred_matrix = np.dot(P, Q.T)
print('예측 행렬:\\n', np.round(pred_matrix, 3))

 

 

05  콘텐츠 기반 필터링 실습 - TMDB 5000 영화 데이터 세트

데이터 출처

https://www.kaggle.com/datasets/tmdb/tmdb-movie-metadata

 

TMDB 5000 Movie Dataset

Metadata on ~5,000 movies from TMDb

www.kaggle.com

콘텐츠 기반 필터링 실습.ipynb
0.02MB

 

 

06  아이템 기반 최근접 이웃 협업 필터링 실습

데이터 출처

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

최근접 이웃 협업 필터링 실습.ipynb
0.06MB

 

 

07  행렬 분해를 이용한 잠재 요인 협업 필터링 실습

SGD 기반의 행렬 분해를 구현

import numpy as np
from sklearn.metrics import mean_squared_error

def get_rmse(R, P, Q, non_zeros):
    error = 0
    # 두개의 분해된 행렬 P와 Q.T의 내적 곱으로 예측 R 행렬 생성
    full_pred_matrix = np.dot(P, Q.T)
    
    # 실제 R 행렬에서 널이 아닌 값의 위치 인덱스 추출하여 실제 R 행렬과 예측 행렬의 RMSE 추출
    x_non_zero_ind = [non_zero[0] for non_zero in non_zeros]
    y_non_zero_ind = [non_zero[1] for non_zero in non_zeros]
    R_non_zeros = R[x_non_zero_ind, y_non_zero_ind]
    
    full_pred_matrix_non_zeros = full_pred_matrix[x_non_zero_ind, y_non_zero_ind]
      
    mse = mean_squared_error(R_non_zeros, full_pred_matrix_non_zeros)
    rmse = np.sqrt(mse)
    
    return rmse
def matrix_factorization(R, K, steps=200, learning_rate=0.01, r_lambda = 0.01):
    num_users, num_items = R.shape
    # P와 Q 매트릭스의 크기를 지정하고 정규분포를 가진 랜덤한 값으로 입력합니다. 
    np.random.seed(1)
    P = np.random.normal(scale=1./K, size=(num_users, K))
    Q = np.random.normal(scale=1./K, size=(num_items, K))
       
    # R > 0 인 행 위치, 열 위치, 값을 non_zeros 리스트 객체에 저장. 
    non_zeros = [ (i, j, R[i,j]) for i in range(num_users) for j in range(num_items) if R[i,j] > 0 ]
   
    # SGD기법으로 P와 Q 매트릭스를 계속 업데이트. 
    for step in range(steps):
        for i, j, r in non_zeros:
            # 실제 값과 예측 값의 차이인 오류 값 구함
            eij = r - np.dot(P[i, :], Q[j, :].T)
            # Regularization을 반영한 SGD 업데이트 공식 적용
            P[i,:] = P[i,:] + learning_rate*(eij * Q[j, :] - r_lambda*P[i,:])
            Q[j,:] = Q[j,:] + learning_rate*(eij * P[i, :] - r_lambda*Q[j,:])
       
        rmse = get_rmse(R, P, Q, non_zeros)
        if (step % 10) == 0 :
            print("### iteration step : ", step," rmse : ", rmse)
            
    return P, Q

이후 다시 만들어진 사용자-아이템 평점 행렬을 matrix_factorization( ) 함수를 이용해 행렬 분해 진행하기…

 

08  파이썬 추천 시스템 패키지 - Surprise

Surprise : 파이씬 기반에서 사이킷런과 유사한 API와 프레임워크를 제공, conda나 pip를 통해 설치한다

$ pip install scikit-suprise

 

Suprise 패키지의 주요 장점

  • 다양한 추천 알고리즘
  • Surprise의 핵심 API는 사이킷런의 핵심 API와 유사한 API명으로 작성 됨