추천 시스템이란?
이전 내용
협업 필터링
◆ 협업 필터링:
사용자가 아이템에 매긴 평점 정보나 상품 구매 이력과 같은 사용자 행동 양식(User Behavior)만을 기반으로 추천을 수행하는 방식이며, 주요 목표는 사용자-아이템 평점 매트릭스와 같은 축적된 사용자 행동 데이터를 기반으로 사용자가 아직 평가하지 않은 아이템을 예측 평가(Predicted Rating) 하는 것이다.
협업 필터링 기반의 추천 시스템은 최근접 이웃 방식과 잠재 요인 방식으로 나뉘며, 두 방식 모두 사용자-아이템 평점 행렬 데이터에만 의지해 추천을 수행한다.
협업 필터링 알고리즘에 사용되는 사용자-아이템 평점 행렬에서 행(Row)은 개별 사용자, 열(Column)은 개별 아이템으로 구성되며, 사용자 아이디 행, 아이템 아이디 열 위치에 해당하는 값이 평점을 나타내는 형태가 돼야 한다.
만약 데이터가 레코드 레벨 형태인 사용자-아이템 평점 데이터라면 판다스의 pivot_table()과 같은 함수를 이용해 사용자-아이템 평점 행렬 형태로 변경해야 한다.
일반적으로 이러한 사용자-아이템 평점 행렬은 많은 아이템을 열로 가지는 다차원 행렬이며, 사용자가 아이템에 대한 평점을 매기는 경우가 많지 않기 때문에 희소 행렬(Sparse Matrix) 특성을 가지고 있다.
최근접 이웃 협업 필터링
최근접 이웃 협업 필터링은 메모리(Memory) 협업 필터링이라고도 하며, 사용자 기반과 아이템 기반으로 나눌 수 있다.
- 사용자 기반: 사용자와 비슷한 고객들이 구매한 상품 리스트 추천. 특정 사용자와 유사한 다른 사용자를 Top-N으로 선정해 이 Top-N 사용자가 좋아하는 아이템을 추천하는 방식으로, 특정 사용자와 타 사용자 간의 유사도(Similarity)를 측정한 뒤 가장 유사도가 높은 Top-N 사용자를 추출해 그들이 선호하는 아이템을 추천하는 것.
- 아이템 기반: 해당 아이템을 선택한 다른 고객들이 구매한 상품 리스트 추천. 아이템이 가지는 속성과는 상관없이 사용자들이 그 아이템을 좋아하는지/싫어하는지의 평가 척도가 유사한 아이템을 추천하는 기준이 되는 알고리즘.
일반적으로 사용자 기반보다는 아이템 기반 협업 필터링이 정확도가 더 높은데, 그 이유는 비슷한 상품을 구입(선호)한다고 해서 사람들의 취향이 비슷하다고 판단하기는 어려운 경우가 많기 대문이다. 따라서, 최근접 이웃 협업 필터링은 대부분 아이템 기반의 알고리즘을 적용한다.
잠재 요인 협업 필터링
잠재 요인 협업 필터링은 사용자-아이템 평점 매트릭스 속에 숨어 있는 잠재 요인을 추출해 추천 예측을 할 수 있게 하는 기법이다. 대규모 다차원 행렬을 SVD와 같은 차원 감소 기법으로 분해하는 과정에서 잠재 요인을 추출하는데, 이러한 기법을 행렬 분해(Matrix Factorization)라고 한다.
잠재 요인 협업 필터링은 사용자-아이템 평점 행렬 데이터만을 이용해 말 그대로 '잠재 요인'을 끄집어내는 것을 의미하며, 잠재 요인을 기반으로 다차원 희소 행렬인 사용자-아이템 행렬 데이터를 저차원 밀집 행렬의 사용자-잠재 요인 행렬과 아이템-잠재 요인 행렬의 전치 행렬(= 잠재 요인 - 아이템 행렬)로 분해할 수 있으며, 이렇게 분해된 두 행렬의 내적을 통해 새로운 예측 사용자-아이템 평점 행렬 데이터를 만들어서 사용자가 아직 평점을 부여하지 않는 아이템에 대한 예측 평점을 생성하는 것이 잠재 요인 협력 필터링 알고리즘의 골자이다.
◆ 행렬 분해
다차원의 매트릭스를 저차원 매트릭스로 분해하는 기법. 대표적으로 SVD, NMF 등이 있다.
'분해'는 인수분해를 말하며, 행렬 분해는 분해의 대상이 행렬이 된다.
M개의 사용자 행과 N개의 아이템 열을 가진 평점 행렬 R은 M X N 차원으로 구성되며, 행렬 분해를 통해서 사용자-K 차원 잠재 요인 행렬 P와 K 잠재 요인-아이템 행렬 Q.T로 분해될 수 있다. 즉, R = P*Q.T 이다.
- M: 총 사용자 수
- N: 총 아이템 수
- K: 잠재 요인의 차원 수
- R: M * N 차원의 사용자-아이템 평점 행렬
- P: 사용자와 잠재 요인과의 값을 가지는 M * K 차원의 사용자-잠재 요인 행렬
- Q: 아이템과 잠재 요인과의 값을 가지는 N * K 차원의 아이템-잠재 요인 행렬
- Q.T: Q 매트릭스의 행과 열 값을 교환한 전치 행렬
행렬 분해는 주로 SVD 방식을 이용하는데, SVD는 널(NaN) 값이 없는 행렬에만 적용할 수 있어, 널 값이 있는 경우에는 SVD 방식으로 분해가 어려워 확률적 경사 하강법이나 ALS 방식을 이용해 SVD를 수행한다.
◆ 확률적 경사 하강법을 이용한 행렬 분해
[머신러닝] 경사 하강법(GD, gradient descent)
확률적 경사 하강법을 이용한 행렬 분해 방법은 P와 Q 행렬로 계산된 예측 R 행렬 값이 실제 R 행렬 값과 가장 최소의 오류를 가질 수 있도록 반복적인 비용 함수 최적화를 통해 P와 Q를 유추해내는 것이다.
[전반적인 절차]
- P와 Q를 임의의 값을 가진 행렬로 설정
- P와 Q.T 값을 곱해 예측 R 행렬로 계산하고 예측 R 행렬과 실제 R 행렬에 해당하는 오류 값 계산
- 이 오류 값을 최소화할 수 있도록 P와 Q 행렬을 적절한 값으로 각각 업데이트
- 만족할 만한 오류 값을 가질 때까지 2, 3번 작업을 반복하면서 P와 Q 값을 업데이트해 근사화
평점 행렬을 경사 하강법을 이용해 행렬 분해하는 방법은 L2 규제를 반영해 실제 R 행렬 값과 예측 R 행렬 값의 차이를 최소화하는 방향성을 가지고 P행렬과 Q행렬에 업데이트 값을 반복적으로 수행하면서 최적화된 예측 R행렬을 구하는 방식이 SGD 기반의 행렬 분해이다.
SGD를 이용한 행렬 분해 수행 예제
분해하려는 원본 행렬 R을 P와 Q로 분해한 뒤에 다시 P와 Q.T의 내적으로 예측 행렬을 만드는 예제.
원본 행렬 R을 미정인 널 값(np.NaN)을 포함해 생성하고 분해 행렬 P와 Q는 정규 분포를 가진 랜덤 값으로 초기화한다.
(잠재 요인 차원은 3)
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 행렬의 크기를 지정하고 정규 분포를 가진 임의의 값으로 입력
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 행렬과 예측 행렬의 오차를 구하는 get_rmse() 함수 생성]
get_rmse() 함수는 실제 R 행렬의 널이 아닌 행렬 값의 위치 인덱스를 추출해 이 인덱스에 있는 실제 R 행렬값과 분해된 P, Q를 이용해 다시 조합된 예측 행렬 값의 RMSE 값을 반환한다.
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
[SGD 기반으로 행렬 분해 수행]
R에서 널 값을 제외한 데이터의 행렬 인덱스 추출
- steps는 SGD를 반복해서 업데이트할 횟수 (여기서는 1000번)
- learning_rate: SGD의 학습률
- r_lambda: L2 규제 계수
get_rmse() 함수를 통해 50회 반복할 때마다 오류 값 출력
# R > 0인 행 위치, 열 위치, 값을 non_zeros 리스트에 저장
non_zeros = [(i, j, R[i, j]) for i in range(num_users) for j in range(num_items) if not np.isnan(R[i, j])]
steps = 1000
learning_rate = 0.01
r_lambda = 0.01 # Regularization 계수
for step in range(steps):
for i, j, r in non_zeros:
eij = r - np.dot(P[i, :], Q[j, :].T)
P[i, :] += learning_rate * (eij * Q[j, :] - r_lambda * P[i, :])
Q[j, :] += learning_rate * (eij * P[i, :] - r_lambda * Q[j, :])
if (step % 50) == 0:
rmse = get_rmse(R, P, Q, non_zeros)
print('### iteration step: ', step, 'rmse ', rmse)
[분해된 P와 Q 함수를 P*Q.T로 예측 행렬을 만들어 출력]
pred_matrix = np.dot(P, Q.T)
print('예측 행렬:\n', np.round(pred_matrix, 3))
원본 행렬과 비교해 널이 아닌 값은 큰 차이가 나지 않으며, 널인 값은 새로운 예측 값으로 채워졌다.
다음 내용
[출처]
파이썬 머신러닝 완벽 가이드
'[파이썬 Projects] > <파이썬 머신러닝>' 카테고리의 다른 글
[머신러닝] 추천 시스템: 잠재 요인 협업 필터링 실습 (3) | 2024.11.03 |
---|---|
[머신러닝] 추천 시스템: 아이템 기반 최근접 이웃 협업 필터링 실습 (1) | 2024.11.03 |
[머신러닝] 추천 시스템: 콘텐츠 기반 필터링 (1) | 2024.11.01 |
[머신러닝] 추천시스템 (0) | 2024.11.01 |
[머신러닝] 텍스트 분석 실습 - 캐글 Mercari (1) | 2024.11.01 |