
행렬 1은 유저의 취향을 저장하고 있는 행렬 행은 유저들의 순서, 열은 속성에 대한 선호도, 원소는 행에 해당하는 유저가 열에 해당하는 속성을 얼마나 좋아하는지 나타냄
행렬 2는 영화의 속성을 저장하고 있는 행렬 열들에는 영화 순서 행은 영화들의 속성, 원소는 열에 해당하는 영화가 얼마만큼 행에 해당하는 속성인지 나타냄
각 원소는 유저의 취향과 영화의 속성의 곱이다
평점 몇 개가 비어 있을 때, 나머지 평점들에 대해 완벽하게 인수분해 할 수 있다면
빈 값들은 쉽게 구할 수 있다
하지만 빈값들이 있으면 행렬 인수분해를 하기 어렵다
곱했을 때 최대한 평점 행렬과 비슷하게 나오는 두 행렬을 구하는 것이 핵심
feature learning 이라고 함import numpy as np
def cost(prediction, R):
"""행렬 인수분해 알고리즘의 손실을 계산해주는 함수"""
cost_array = (prediction - R) ** 2
return np.nansum(cost_array)
# 실행 코드
# 예측 값 행렬
prediction = np.array([
[4, 4, 1, 1, 2, 2],
[4, 4, 3, 1, 5, 5],
[2, 2, 1, 1, 3, 4],
[1, 3, 1, 4, 2, 2],
[1, 2, 4, 1, 2, 5],
])
# 실제 값 행렬
R = np.array([
[3, 4, 1, np.nan, 1, 2],
[4, 4, 3, np.nan, 5, 3],
[2, 3, np.nan, 1, 3, 4],
[1, 3, 2, 4, 2, 2],
[1, 2, np.nan, 2, 2, 4],
])
cost(prediction, R)
# 10.0
선형 회귀의 손실 함수는 아래로 볼록한 함수
하지만 행렬 인수분해의 손실 함수는 아래로 볼록하지 않음
선형 회귀와는 달리 변수가 뿐만 아니라 가 있고 이 둘이 곱해졌기 때문
2차 이상의 다항 함수처럼 여러 극소점이 존재
경사 하강법을 해도 손실을 가장 작게 만드는 와 값을 찾았다고 할 수 없음
그래도 임의로 설정한 값들보다 경사 하강법을 사용해서 구한 값들이 항상 성능이
더 좋고 많은 경우 최소점이 아니라 극소점을 찾아도 성능이 충분히 좋게 나옴
이러한 문제점을 극복하기 위해서는 임의로 초기화를 여러번해서
경사 하강법을 많이 해본 후 가장 성능이 좋게 나온 모델을 사용하는 방식 상요
training set에 너무 딱 맞은 나머지test set에서 성능이 안 좋게 나오는 것을 말함import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# 체점을 위해 임의성을 사용하는 numpy 도구들의 결과가 일정하게 나오도록 해준다
np.random.seed(5)
RATING_DATA_PATH = './data/ratings.csv' # 데이터 파일 경로 정의
# numpy 출력 옵션 설정
np.set_printoptions(precision=2)
np.set_printoptions(suppress=True)
def predict(Theta, X):
"""유저 취향과 상품 속성을 곱해서 예측 값을 계산하는 함수"""
return Theta @ X
def cost(prediction, R):
"""행렬 인수분해 알고리즘의 손실을 계산해주는 함수"""
return np.nansum((prediction - R)**2)
def initialize(R, num_features):
"""임의로 유저 취향과 상품 속성 행렬들을 만들어주는 함수"""
num_users, num_items = R.shape
Theta = np.random.rand(num_users, num_features)
X = np.random.rand(num_features, num_items)
return Theta, X
def gradient_descent(R, Theta, X, iteration, alpha, lambda_):
"""행렬 인수분해 경사 하강 함수"""
num_user, num_items = R.shape
num_features = len(X)
costs = []
for _ in range(iteration):
prediction = predict(Theta, X)
error = prediction - R
costs.append(cost(prediction, R))
for i in range(num_user):
for j in range(num_items):
if not np.isnan(R[i][j]):
for k in range(num_features):
Theta[i][k] -= alpha * (np.nansum(error[i, :] * X[k, :]) + lambda_ * Theta[i][k])
X[k][j] -= alpha * (np.nansum(error[:, j] * Theta[:, k]) + lambda_ * X[k][j])
return Theta, X, costs
#----------------------실행(채점) 코드----------------------
# 평점 데이터를 가지고 온다
ratings_df = pd.read_csv(RATING_DATA_PATH, index_col='user_id')
# 평점 데이터에 mean normalization을 적용한다
for row in ratings_df.values:
row -= np.nanmean(row)
R = ratings_df.values
Theta, X = initialize(R, 5) # 행렬들 초기화
Theta, X, costs = gradient_descent(R, Theta, X, 200, 0.001, 0.01) # 경사 하강
# 손실이 줄어드는 걸 시각화 하는 코드 (디버깅에 도움이 됨)
# plt.plot(costs)
Theta, X
# (array([[-0.35, 1.56, 0.31, -0.21, -0.26],
# [ 0.92, 0.21, 0.36, 0.56, 0.99],
# [ 0.48, 0.55, -0.19, 0.06, 1.71],
# [-0.64, 1.03, 0.35, -0.32, 0.13],
# [-0.39, -0.68, 0.44, 0.05, 1.05],
# [ 0.07, -0.64, 0.92, 1.23, -0.58],
# [ 0.33, 0.93, -1.21, 2.09, 0.27],
# [ 0.79, -0.48, 1.12, 0.05, 0.46],
# [ 1.06, -0.68, -0.28, 0.18, -1.12],
# [ 0.39, 0.63, 0.14, 0.98, 0.1 ],
# [ 1.47, 0.62, -0.91, -0.29, -0.35],
# [-1.56, 0.77, 0.83, 1.1 , 0.13],
# [-0.89, 0.47, 0.47, -0.25, 0.81],
# [ 0.86, -0.13, -1.01, 0.2 , 0.76],
# [-0.53, -1.14, -0.47, 0.08, -0.72],
# [-0.27, -0.07, 0.41, 0.49, 1.5 ],
# [ 0.17, -0.01, 0.07, -1.66, 0.27],
# [ 1.32, 0.88, 0.83, 0.72, -1.09],
# [-0.17, -1.68, 1.86, -0.16, -0.26],
# [-0.88, -0.53, -1.33, 0.14, 0.19]]),
# array([[ 0.12, 0.48, -2.18, -0.67, -1.05, 0.41, 0.03, -0.37, -0.86,
# 0.44, -0.71, 1.26, -0.55, 0.17, 0.74, 0.94, -0.07, 1.98,
# 1.12, 0.68],
# [-0.61, -0.4 , -0.12, 0.11, -0.22, 0.1 , 0.71, -0.36, 0.97,
# 0.95, 0.62, -0.72, 0.26, -1.56, 0.18, -0.28, -0.29, 1.7 ,
# 0.02, -0.87],
# [ 0.12, 1.59, 0.25, 1.02, -1. , 0.88, -0.27, 0.39, 0.33,
# 0.48, -1.17, -0.05, -1.69, 0.65, -0.12, -1.09, -0.89, -0.35,
# 0.65, 0.47],
# [ 0.33, -0.84, -0.73, -0.55, 0.11, 1.18, -1. , 0.15, 0.29,
# -0.21, 0.76, 0.46, -0.59, -0.5 , -0.92, -0.21, 0.86, 0.45,
# 1.77, -0.02],
# [-0.75, -0.25, -0.72, 1.1 , 0.94, 0.54, 0.55, -1.34, -1.28,
# 1.08, 0.79, 0.63, -0.68, 0.21, 1.02, -0.46, -0.06, -0.81,
# 0.93, -0.72]]))