[Recommender System] Matrix Factorization
2025.06.01 - [Data & Research] - [Recommender System] Table of Contents
저번 포스팅에서 언급했던 Collaborative filtering의 model-based의 방법론의 대표격이라고 할 수 있는 Matrix Factorization에 대해서 알아볼까요? 2009년 넷플릭스 컨테스트에서 우승자들이 채택한 방식으로 이제는 클래식이 된 방법론입니다. 사용자-아이템 상호작용 행렬(예: 평점 행렬)을 저차원의 사용자 잠재 요인 행렬과 아이템 잠재 요인 행렬의 곱으로 분해하여, 사용자가 아직 평가하지 않은 아이템에 대한 선호도를 예측하는 것을 목표로 합니다.
1. Matrix Factorization이란?
아래 그림에서 보시는 것처럼 우리가 관측할 수 있는 것은 User X Item 행렬입니다. 이 방법론의 핵심 컨셉은 User X Item의 형태의 평점이 중간의 latent factor 과 연관이 있다고 보는 것입니다. 나중에 딥러닝 등을 이용해서 비선형적 latent factor도 뽑아낼 수도 있겠지만 일단 이 latent factor가 선형적으로 연관되어 있다고 가정하면 User-Item matrix는 아래 처럼 User latent factor matrix P와 item latent factor matrix Q와의 곱으로 표현할 수 있다는 것이죠.
성분을 활용하여 표현하면 $$ \hat{R}_{mn} = \sum_{k=1}^{k} P_{mk} Q^{T}_{kn} $$ 과 같은 꼴입니다.
latent factor의 개수(dimension)은 몇 개로 정하라고 딱 정해진 것은 없습니다. 마치 neural network에서 중간의 hidden layer의 개수나 node의 개수에 정답이 없듯이 MF에서도 latent factor의 개수는 일종의 hyper parameter입니다.
- 사용자-아이템 평점 행렬 (R): ( 명의 사용자, 개의 아이템)
- 사용자 잠재 요인 행렬 (P): ( 명의 사용자, 개의 잠재 요인)
- 아이템 잠재 요인 행렬 (Q): ( 개의 아이템, 개의 잠재 요인)
이렇게 행렬 분해를 할 수 있는 기법으로 Singular Value Decomposition(SVD)이 있지만 보통 User-Item matrix는 결측치가 많아서 이 기법을 적용하기가 어렵습니다. 문제를 실제 평점 matrix R과 latent factor를 도입해서 분리해서 만들어 낸 \( \hat{R}_{ij} \) 두 대상의 괴리를 좁히는 방향으로 잡아봅시다. 손실함수(Loss function)을 도입해서
\[ \min_{q^*, p^*} \sum_{(u,i) \in K} (r_{ui} - q_i^\top p_u)^2 + \lambda (\|q_i\|^2 + \|p_u\|^2) \]
마치 선형회귀에서의 오차와 비슷한 오차에 overfitting방지를 위한 P, Q matrix의 probenious norm을 패널티 항으로 넣어주었습니다. (L2-regularization)
자, 그럼 어떤 방법으로 이 손실함수를 최소화하는 parameter들을 찾을 수 있을까요? 크게 2가지 방법이 있습니다.
2. Matrix Factorization 손실함수 최소화 parameter 탐색 방법론
(1) Stochastic Gradient Descent (SGD)
SGD는 손실 함수의 기울기(gradient)를 계산하여 파라미터(사용자 잠재 요인 및 아이템 잠재 요인)를 업데이트하는 반복적인 최적화 알고리즘입니다. 딥러닝에서도 자주 보셨을거라 생각합니다.
각 학습 단계에서 임의의 관측된 평점 \(R_{i,j}\)에 대해 예측 오차 \(e_{ij}=R_{ij}-p^{T}_{i}q_{j}\) 를 계산하고, 다음과 같이 latent factor 벡터를 업데이트 합니다.
\[ p_i \leftarrow p_i + \gamma (e_{ij}q_j - \lambda p_i) \] \[ q_j \leftarrow q_j + \gamma (e_{ij}p_i - \lambda q_j) \]
여기서 \(\gamma\)는 learning_rate입니다. SGD는 아래와 같은 특성이 있습니다.
- 각 업데이트 단계에서 하나의 데이터 포인트만 사용하므로 계산 비용이 저렴합니다.
- 대규모 데이터셋에 효과적입니다.
- 최적점으로 수렴하는 과정이 불안정할 수 있으며, 적절한 학습률 설정이 중요합니다.
(2) Alternating Least Squares (ALS)
말그대로 P와 Q를 번갈아가면서 최적화 하는 방법론입니다. MCMC에서도 parameter를 고정해놓고 하나를 업데이트하는 방식으로 sampling을 진행했었죠? 여기서도 어떻게보면 그와 같은 방식으로 parameter 업데이트를 진행합니다.
MF의 예시를 하나 살펴볼까요?
from surprise import SVD
from surprise import Reader
from surprise import Dataset
from surprise.model_selection import train_test_split
from surprise import accuracy
# Reader 객체 생성: 평점 범위, 구분자, 첫 번째 줄 건너뛰기 설정
reader = Reader(rating_scale=(0.5, 5.0), sep=',', skip_lines=1)
# Dataset 객체 생성: 파일 경로와 Reader 객체 전달
file_path = 'ratings.csv' # 다운로드 및 압축 해제한 ratings.csv 파일의 경로로 수정하세요.
data = Dataset.load_from_file(file_path, reader=reader)
# 학습 데이터와 테스트 데이터 분리 (data는 이제 Dataset 객체)
trainset, testset = train_test_split(data, test_size=0.25, random_state=42)
# SVD 모델 초기화 (Matrix Factorization의 한 종류)
model = SVD(n_factors=50, random_state=42) # n_factors는 잠재 요인 수
# 모델 학습
model.fit(trainset)
# 테스트 데이터에 대한 예측
predictions = model.predict(uid='1', iid='302') # r_est=None 제거
print(f"사용자 1이 아이템 302에 예상하는 평점: {predictions.est:.2f}")
# 전체 테스트 데이터에 대한 예측 및 성능 평가
test_predictions = model.test(testset)
accuracy.rmse(test_predictions)
accuracy.mae(test_predictions)
# 특정 사용자에 대한 상위 N개 추천 아이템 얻기 (직접 구현 필요)
def get_top_n_recommendations(predictions, n=10):
top_n = {}
for uid, iid, true_r, est, _ in predictions:
if uid not in top_n:
top_n[uid] = []
top_n[uid].append((iid, est))
for uid, user_ratings in top_n.items():
user_ratings.sort(key=lambda x: x[1], reverse=True)
top_n[uid] = user_ratings[:n]
return top_n
top_n = get_top_n_recommendations(test_predictions)
# 사용자 1에 대한 상위 5개 추천 아이템 출력
if '1' in top_n:
print("\n사용자 1에 대한 상위 10개 추천 아이템:")
for iid, est in top_n['1'][:10]:
print(f"아이템 ID: {iid}, 예상 평점: {est:.2f}")
3. 손실함수의 확장
기본적인 Matrix Factorization에서 확장된 버전의 연구들도 존재합니다.
(1) User 및 Item Bias 교정
많은 추천 시나리오에서 사용자나 아이템 자체의 고유한 편향이 평점에 영향을 미칠 수 있을텐데요
사용자 편향 (User Bias, \(b_{u}\)): 어떤 사용자는 전반적으로 평점을 후하게 주는 경향이 있는 반면, 어떤 사용자는 짜게 주는 경향이 있습니다.
아이템 편향 (Item Bias, \(b_{i}\)): 어떤 아이템은 전반적으로 높은 평가를 받는 경향이 있는 반면, 어떤 아이템은 낮은 평가를 받는 경향이 있습니다.
이러한 편향을 고려하지 않으면 모델이 정확한 예측을 수행하기 어려울 수 있습니다.
그래서 이러한 bias term (마치 회귀분석에서의 상수항)을 넣어 개선할 수 있습니다.
$$
\hat{R}_{ui} = \mu + b_u + b_i + p_u q_i^T
$$
이런 세팅에서 SGD를 적용한다면
\[e_{ui} = R_{ui} - \hat{R}_{ui}\]
$$ p_u \leftarrow p_u + \gamma (e_{ui} q_i - \lambda p_u) $$ $$ q_i \leftarrow q_i + \gamma (e_{ui} p_u - \lambda q_i) $$ $$ b_u \leftarrow b_u + \gamma (e_{ui} - \lambda b_u) $$ $$ b_i \leftarrow b_i + \gamma (e_{ui} - \lambda b_i) $$
와 같이 쓸 수 있을 겁니다.
(2) Implicit Feedback, User attribute 반영
명시적으로 별점이나 평가를 남기는 feedback이 explicit feedback이라면, 간접적으로 feedback을 남기는 것을 Implicit feedback이라고 합니다. 많은 경우에 클릭, 구매, 재생 등 사용자 행동 로그 등으로부터 이런 implicit feedback을 끌어내야 하는데요. 이런경우에도 Matrix Factorization을 적용하려면 역시 약간의 변형이 필요할 겁니다.
- Collaborative Filtering for Implicit Feedback Datasets (Hu, Koren, Volinsky, 2008)
이 논문에서는 사용자의 암시적 행동(예: 아이템 시청 여부, 구매 여부)을 이진 신호로 간주하고, 행동의 빈도를 신뢰도로 활용했습니다. 예측 $ \hat{R}_{ui} $ 는 명시적인 평점 예측이 아닌 선호도 점수로 해석될 수 있고 아래와 같이 쓸 수 있습니다.
$$
\hat{r}_{ui} = x_u^T y_i
$$
이 모델은 손실 함수를 정의할 때 암시적 행동의 빈도에 따라 가중치를 부여하여 학습합니다. 높은 빈도의 상호작용은 더 높은 신뢰도를 가지는 것으로 간주됩니다.
- Incorporating User and Item Features into Collaborative Filtering (Koren, 2008)
이 연구에서는 사용자 및 아이템의 속성을 MF 모델에 직접적으로 통합하는 방법을 제시합니다. 예측 평점 $ \hat{R}_{ui} $ 는 사용자 및 아이템의 잠재 요인뿐만 아니라, 각 속성에 대한 가중치를 고려하여 계산됩니다.
$$
\hat{R}_{ui} = \mu + b_u + b_i + p_u^T q_i + \sum_{a \in A_u} w_a^u x_{ua} + \sum_{b \in B_i} w_b^i y_{ib}
$$
(3) 시간에 따라 변화하는 사용자의 선호도와 아이템의 인기 반영
- Collaborative Filtering with Temporal Dynamics (Koren, 2009)
이 논문은 사용자 편향, 아이템 편향, 사용자 잠재 요인, 아이템 잠재 요인 모두 시간에 따라 변화할 수 있다는 아이디어를 제시합니다. 예측 평점 $ \hat{R}_{ui}(t) $)는 시간 t에서의 사용자 u와 아이템 i의 상호작용을 모델링합니다.
$$
\hat{R}_{ui}(t) = \mu + b_u(t) + b_i(t) + p_u(t)^T q_i(t)
$$
이 밖에도 Confidence interval의 변화라든가 Deep learning구조와 연관되어 확장된 연구들이 있지만 계속하자면 한도 끝도 없기에... 여기서 줄이도록 하겠습니다.