확률적 경사하강법을 이용한 행렬 분해
확률적 경사 하강법은 회귀에서 사용한 경사 하강법의 한 종류이다.
확률적 경사 하강법을 이용한 행렬 분해 방법을 요약하자면 ,P와 Q 행렬로 계산된 예측 R행렬 값이 실제 R 행렬 값과 가장 최소의 오류를 가질 수 있도록 반복적인 비용 함수 최적화를 통해 P와 Q를 유추해내는 것 이다.
확률적 경사 하강법을 이용한 행렬 분해의 전반적인 절차는 다음과 같다.
- P와 Q를 임의의 값을 가진 행렬로 설정
- P와 Q.T 값을 곱해 예측 R 행렬을 계산하고 예측 R행렬과 실제 R 행렬에 해당하는 오류 값을 계산.
- 이 오류 값을 최소화 할 수 있도록 P와 Q 행렬을 적절한 값으로 각각 업데이트
- 만족할만한 오류 값을 가질 때 까지 2,3번 작업을 반복하면서 P와 Q 값을 업데이트해 근사화
경사 하강법을 이용한 회귀는 비용 함수를 최소화 하는 방향성을 가지고 회귀계수의 업데이트 값 (w1_update, w0_update)를 구하고 이 업데이트 값을 회귀 계수에 반복적으로 적용하는 것이 핵심 로직 이었다.
평점 행렬을 경사 하강법을 이용해 행렬 분해하는 것도 이와 유사하다.
L2 규제를 반영해 실제 R 행렬 값과 예측 R 행렬 값의 차이를 최소화하는 방향성을 가지고 P행렬과 Q행렬에 업데이트 값을 반복적으로 수행하면서 최적화된 예측 R 행렬을 구하는 방식이 SGD 기반의 행렬 분해이다.
SGD를 이용한 행렬 분해 실습
분해하려는 원본 행렬 R을 P와 Q로 분해한 뒤에 다시 P와 Q.RT의 내적으로 예측 행렬을 만들어 보자.
먼저 원본 행렬 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 매트릭스의 크기를 지정하고 정규분포를 가진 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))
다음 으로 실제 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를 반복해서 업데이트할 횟수를 의미하며, learning_rate는 SGD의 학습률, r_lambda는 L2 Regulatriztion 계수 이다.
setps=1000번 동안 반복하면서 공식을 통해 업데이트 한다.
그리고 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 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)
### iteration step : 0 rmse : 3.2388050277987723
### iteration step : 50 rmse : 0.4876723101369648
### iteration step : 100 rmse : 0.1564340384819247
### iteration step : 150 rmse : 0.07455141311978046
### iteration step : 200 rmse : 0.04325226798579314
### iteration step : 250 rmse : 0.029248328780878973
### iteration step : 300 rmse : 0.022621116143829466
### iteration step : 350 rmse : 0.019493636196525135
### iteration step : 400 rmse : 0.018022719092132704
### iteration step : 450 rmse : 0.01731968595344266
### iteration step : 500 rmse : 0.016973657887570753
### iteration step : 550 rmse : 0.016796804595895633
### iteration step : 600 rmse : 0.01670132290188466
### iteration step : 650 rmse : 0.01664473691247669
### iteration step : 700 rmse : 0.016605910068210026
### iteration step : 750 rmse : 0.016574200475705
### iteration step : 800 rmse : 0.01654431582921597
### iteration step : 850 rmse : 0.01651375177473524
### iteration step : 900 rmse : 0.01648146573819501
### iteration step : 950 rmse : 0.016447171683479155
이제 분해된 P와 Q함수를 P*Q.T로 예측 행렬을 만들어서 출력해 보자.
pred_matrix = np.dot(P, Q.T)
print('예측 행렬:\n', np.round(pred_matrix, 3))
예측 행렬:
[[3.991 0.897 1.306 2.002 1.663]
[6.696 4.978 0.979 2.981 1.003]
[6.677 0.391 2.987 3.977 3.986]
[4.968 2.005 1.006 2.017 1.14 ]]
원본 행렬과 비교해 널이 아닌 값은 큰 차이가 나지 않으며, 널인 값은 새로운 예측값으로 채워졌다.
본문 내용은 파이썬 머신러닝 완벽 가이드 (권철민 저)을 요약정리한 내용입니다.
'머신러닝' 카테고리의 다른 글
컨텐츠 기반 필터링 실습 (0) | 2020.08.25 |
---|---|
추천시스템 (0) | 2020.08.24 |
DBSCAN (0) | 2020.08.24 |
GNM(Gaussian Mixture Model) (0) | 2020.08.24 |
평균이동(Mean Shift) (0) | 2020.08.24 |