Aromajoin Project

Matrix factorization : 행렬분해

qkrwnsmir 2024. 10. 13. 17:17

1. Matrix Factorization 개념

 

Matrix factorization(행렬 분해)은 주로 추천 시스템에서 많이 사용되는 기법으로, 유저-아이템 상호작용 데이터(예: 좋아요, 평점 등)를 행렬 형태로 표현한 후, 이를 두 개의 저차원 행렬로 분해해 숨겨진 패턴을 발견하는 방법이다.

 

주로 협업 필터링 Collaborative Filtering 방식에 자주 사용된다.  이번 프로젝트에서 쓰일 알고리즘방식이기 때문에 Matrix factorization이 협업필터링에 어떻게 쓰이고, 프로젝트에 어떤식으로 녹여낼지 공부해본다.

 

(협업필터링은 이전글 참고)

 

사용자 잠재 요인 행렬과 아이템 잠재 요인 행렬 두가지가 있다. 전자는 사용자를, 후자는 아이템을 벡터로 표현한 행렬들이다.

 

여기서 잠재요인이란 사용자가 좋아하거나 싫어하는 특징을 수치화한것으로, 명시적 특징이 아닐 수 있다.

ex) 장르, 감독, 배우 가 아닌 숨어있는 패턴을 학습해남.

 

상호작용 행렬 R을 두 개의 잠재 행렬 PQ로 분해한다.

R은 상호자-아이템, P는 사용자 Q는 아이템

 


 

3. 목적 함수 (Objective Function)

 

Matrix Factorization은 보통 다음과 같은 목적 함수를 최소화하여 학습한다.

ai 공부를 하다보면 목적함수가 나오게되는데 일단 수식은 깊게 파보진 않고, 어떤 의미를 가지는지를 먼저 본다.

위의 식은 사용자의 실제 행동과 예측된 상호작용 값 사이의 오차를 최소화하고, 과적합을 막기위해 정규화 항을 추가해 제어한다.

 

람다로 시작하는 항이 정규화 항이다.

 

딥러닝에 주로 사용되는 경사하강법으로 분리하는데, 행렬분해 학습할때 사용된다.

그러나 matrix factorization은 AI와는 구별되는 행렬계산알고리즘이다.

 

딥러닝까지 가기에는 차원이 적기때문이다. 따라서 저차원 잠재요인으로 분해해 예측하는 기법으로 사용된다.

즉 딥러닝은 다층신경망으로 비선형관계를 학습하고, MF는 저차원의 선형적 기법이다.

 

User latent factors에서 열은 자기 마음대로 결정하면된다. 유저와 아이템간의 어떤 설명할 수 없는 핵심 요소들이 있는데, 그 요소들을 몇가지로 나눌지 정하는것이다.

저 예시에서는 3으로 나눈건데, n*3, 3*m으로 나누는것이다.

 

처음에 회색영역은 비어있는공간인데, 사용자가 모든 아이템에 대해 평가(좋아요, 평점 등)를 남기지 않았을 수 있다. 그래서 상호작용 행렬에는 빈칸이 많이 존재하게 된다. 비어있을때는 행렬분해를 할 수 없기때문에 목적함수의 최적화를 통해 빈칸에 가장 적절한 값을을 추측해 분해하는것이다. 2개로 할수도있고 10개로 할 수도 있다. 해보고 좋은거 선택하는것이다. 즉 n이 잠재요인이 되는것이다.

 

 

잠재 요인 수가 많을수록: 더 많은 정보를 학습할 수 있지만, **과적합(overfitting)**의 위험이 있다.

잠재 요인 수가 적을수록: 학습이 간단해지지만, 정보를 충분히 반영하지 못할 위험이 있다.

 

다시 수식으로 돌아와서 (끝인줄 알았지? ㅎ ㅎ)

Rui는 사용자 u가 아이템 i에 대해 실제로 남긴 상호작용 값이다.

Pu*Qu는 사용자의 잠재요인 P와 아이템 잠재요인 Q를 통해 예측한 상호작용 값이다.

 

이 둘을 뺀다는것은 오차를 계한하는것이고, 제곱하는 이유는 부호 상관없이 오차의 크기를 측정하고, 제곱을 해서 오차가 클수록 패널티를 더 주기 위함이다. 딥러닝, ml공부해본사람은 익숙할 하다.

 

참고로 절댓값을 안쓰는 이유는 미분불가능해 Gradient Descent적용이 불가능하기 떄문

 

이후 시그마로 행렬을 다 커버한 뒤 전체의 최소값을 구해주는것이 목적함수의 목적이다.

 

 

모르는 빈칸이랑 예측값의 오차를 어떻게 구한다는거지??

 

공부하며 내가 든 의문점이다.

 

Matrix Factorization에서는 사용자-아이템 간의 상호작용 데이터를 행렬로 나타내지만, 이 행렬은 보통 희소 행렬(sparse matrix)이다. 즉, 모든 사용자와 아이템 간의 상호작용이 기록되어 있는 것이 아니라, 일부만 기록되어 있다. 예를 들어, 사용자가 모든 향기나 동영상에 대해 평가를 남기지 않았기 때문에 빈칸(미관측 데이터)이 많이 존재한다.

 

목적 함수는 관측된 값예측 값의 오차를 계산합니다. 중요한 점은 오차는 관측된 값이 있는 곳에서만 계산된다.

이후엔 결정된 p q의 값으로 R' 메트릭스가 생성되고, 점차 오차를 줄여나간다.

P와Q의 내적으로 빈칸에 대한 예측값을 채워나간다.

그러나 아로마 플레이어와같이 초기데이터가 너무 희박한 상황에서는 아예 초기값으로 특정조차 할 수 없는 상황이 생길 수 있어 이런 상황에는 몇가지 해결방식을 선택할 수 있다.

 

 

1. 기본값으로 채우기 (Default Value Filling)

 

가장 단순한 방법은 빈칸을 기본값으로 채우는 것이다. 예를 들어, 사용자들이 평가하지 않은 아이템들에 대해 중립적인 값을 임시로 채워넣고 학습을 진행할 수 있다.

 

장점: 계산이 간단하며, 모델을 빠르게 학습할 수 있다.

단점: 데이터의 실제 선호도가 반영되지 않기 때문에 정확도가 떨어질 수 있다.

 

예시:

 

사용자가 어떤 아이템을 평가하지 않았다면, 그 아이템에 대한 **중간 값(예: 3점)**을 할당해 학습을 시작할 수 있다.

 

2. 사용자 또는 아이템에 대한 프로파일 정보 활용

 

데이터가 희소할 때는 사용자나 아이템에 대한 추가적인 메타데이터(프로파일 정보)를 활용하여 빈칸을 채울 수 있다. 예를 들어:

 

사용자 정보: 나이, 성별, 거주 지역, 과거 행동 패턴(예: 구매 기록).

아이템 정보: 카테고리, 장르, 제조사, 가격 등.

 

이 정보를 기반으로 콘텐츠 기반 필터링(Content-Based Filtering) 기법을 사용해 초기 모델을 개선할 수 있다.

 

예시:

 

새로운 사용자가 아이템을 평가하지 않았더라도, 해당 사용자가 같은 카테고리의 다른 아이템에 대해 긍정적인 평가를 남겼다면, 이를 참고하여 해당 아이템에 대한 예측을 진행할 수 있다.

 

3. Implicit Feedback(암묵적 피드백) 활용

 

희소한 데이터의 문제를 해결하기 위해, **암묵적인 피드백(Implicit Feedback)**을 활용할 수 있다. 이는 사용자가 명시적으로 평점이나 좋아요/싫어요와 같은 피드백을 남기지 않더라도, 다른 행동 데이터를 활용해 선호도를 추정하는 방식이다.

 

암묵적 피드백의 예시:

 

시청 시간: 사용자가 동영상을 끝까지 시청했다면, 그 동영상을 좋아할 가능성이 높다고 판단.

클릭 기록: 사용자가 여러 번 클릭한 아이템을 좋아할 가능성이 높다고 추정.

검색 기록: 사용자가 특정 키워드로 아이템을 자주 검색한 경우, 그 아이템에 대한 관심을 암묵적으로 드러낸 것으로 간주.

 

암묵적인 피드백은 명시적인 피드백보다 데이터 양이 많을 가능성이 크기 때문에, 데이터를 보완하는 데 큰 도움이 될 수 있다.

 

혹은 알고리즘 외적으로

 

1. 사용자 등록 시 설문: 새로운 사용자가 가입할 때 몇 가지 질문을 통해 초기 선호도를 수집한다. 예를 들어, 선호하는 영화 장르나 좋아하는 향기 유형에 대한 질문을 통해 초기 데이터 수집한다.

2. 초기 추천 기반 제공: 다른 사용자들의 일반적인 행동 데이터를 기반으로 새로운 사용자에게 초기 추천을 제공한다. 이를 통해 새로운 사용자가 더 많은 상호작용을 하도록 유도하고, 점점 더 개인화된 추천을 제공할 수 있다.

 

를 거쳐 초기데이터를 확보한 후 진행할 수도 있다.

 

 


 

백그라운드는 여기까지 알아보도록하고, 이제 프로젝트에 이 기법을 어떻게 녹여낼지 알아보자.

아로마 플레이어는 일반 영상 플랫폼과는 다르게 자체 카트리지의 향에 대한 좋아요 싫어요 데이터가 있다.

이 점을 활용해 향기의 좋아요 싫어요, 영상의 좋아요 싫어요 데이터 그리고 사용자가 동영상에서 향기를 얼마나 자주 사용했는지와 같은 행동데이터를 고려할 수 있을 것이다.

import numpy as np
from sklearn.decomposition import NMF
from sklearn.metrics import mean_squared_error

# 1. 사용자-향기 상호작용 데이터 (좋아요: 1, 싫어요: -1, 평가하지 않음: 0)
# ERD의 Aroma_Reactions Table에 기반
user_aroma_reactions = np.array([
    [1, 0, 0, -1, 1],  # user_serial: U1
    [0, 1, -1, 0, 0],  # user_serial: U2
    [1, 1, 0, -1, 0],  # user_serial: U3
    [0, 0, 1, 0, -1],  # user_serial: U4
    [-1, 1, 0, 0, 1]   # user_serial: U5
])

# 2. 사용자-동영상 상호작용 데이터 (좋아요: 1, 싫어요: -1, 평가하지 않음: 0)
# ERD에서 일반 동영상 좋아요/싫어요 데이터를 가정한 Video Reactions Table
user_video_reactions = np.array([
    [1, 0, 0, -1, 1],  # user_serial: U1
    [0, 1, 1, 0, 0],   # user_serial: U2
    [1, 1, 0, 1, 0],   # user_serial: U3
    [0, 0, 1, 0, -1],  # user_serial: U4
    [-1, 1, 0, 0, 1]   # user_serial: U5
])

# 3. 암묵적 피드백 (향기 사용 횟수) - 동영상에서 향기 사용 빈도
# ERD의 Video_Fragrance_Timeline 및 암묵적 사용 정보를 가정
aroma_usage_data = np.array([
    [30, 0, 0, 20, 50],  # user_serial: U1 사용 데이터
    [0, 40, 10, 0, 0],   # user_serial: U2
    [60, 70, 0, 0, 0],   # user_serial: U3
    [0, 0, 30, 0, 10],   # user_serial: U4
    [10, 50, 0, 0, 90]   # user_serial: U5
])

# 4. 사용자-향기 상호작용 데이터를 암묵적 피드백으로 채움
user_aroma_reactions_filled = user_aroma_reactions.copy()
user_aroma_reactions_filled[user_aroma_reactions == 0] = aroma_usage_data[user_aroma_reactions == 0] * 0.05

print("암묵적 피드백으로 채워진 사용자-향기 상호작용 행렬:")
print(user_aroma_reactions_filled)

# 5. 사용자-동영상 상호작용도 암묵적 피드백이 있었다면 반영 가능 (여기서는 데이터만 사용)
user_video_reactions_filled = user_video_reactions.copy()
# (동영상 관련 피드백 예시는 생략, 유사한 방식으로 채울 수 있음)

# 6. 사용자-향기 및 사용자-동영상 데이터를 통합하여 하나의 행렬로 결합
# 사용자가 향기 및 동영상에 남긴 상호작용 데이터를 통합
# 최종적으로 향기 + 동영상 상호작용 행렬을 사용해 추천
combined_interactions = np.hstack([user_aroma_reactions_filled, user_video_reactions_filled])

print("통합된 사용자-향기 및 사용자-동영상 상호작용 행렬:")
print(combined_interactions)

# 7. 비음수 행렬 분해 (Matrix Factorization)
model = NMF(n_components=3, init='random', random_state=42, max_iter=500)

# 사용자 잠재 요인 행렬 P, 아이템 잠재 요인 행렬 Q 학습
P = model.fit_transform(combined_interactions)
Q = model.components_

# 예측된 사용자-향기 및 동영상 선호도 행렬
predicted_preferences = np.dot(P, Q)

# 예측된 선호도 출력
print("예측된 사용자-향기 및 동영상 선호도 행렬:")
print(predicted_preferences)

# 성능 평가: 실제 값과 예측된 값 비교 (향기와 동영상 구분하지 않고 통합)
mask = (combined_interactions != 0)  # 관측된 값만 비교
mse = mean_squared_error(combined_interactions[mask], predicted_preferences[mask])
print(f"관측된 데이터에 대한 MSE: {mse:.4f}")

 

이 코드에서 #7을 살펴보면

model = NMF(n_components=3, init='random', random_state=42, max_iter=500)

n_component = 3이 잠재요인 NMF로 나타난다. 클수록 과적합이 발생할 수 있다. (실험적으로 결정)

 

또한 여러가지 요인들이 (향기 좋아요, 영상 좋아요, 암묵적) 함께 작용한다.

이는

# 사용자-향기 및 사용자-동영상 데이터를 통합하여 하나의 행렬로 결합
combined_interactions = np.hstack([user_aroma_reactions_filled, user_video_reactions])

수평결합으로 처리할 수 있다.

 

암묵적 피드백은 좋아요 두개를 수평결합 하기 전 

# 사용자-향기 상호작용 데이터를 암묵적 피드백으로 채움
user_aroma_reactions_filled = user_aroma_reactions.copy()
user_aroma_reactions_filled[user_aroma_reactions == 0] = aroma_usage_data[user_aroma_reactions == 0] * 0.05

0인 부분에 향기 사용횟수를 반영시킨다.

 

이부분도 회의를 거쳐 어떤식으로 알고리즘을 적용시킬지 고민해봐야한다.