본문 바로가기

AI 개발

추천시스템 개발하기 : 행렬분해 - 모델 기반 협업필터링 기법

반응형

Matrix Factorization - Model-based Collaborative Filtering

모델 기반 (Model-based) CF

사용자-아이템 상호작용 데이터 속에 숨어있는 잠재적인 패턴(Latent Pattern)을 학습하는 모델을 구축하는 방식입니다.

 

유튜브 링크 : https://youtu.be/B5k0Iv-0mLY

행렬 분해 (Matrix Factorization, MF):
  • 가장 대표적이고 성공적인 모델 기반 CF 기법 (Two-Tower 이전 시대의 왕!)
  • 핵심 아이디어: 거대한 사용자-아이템 평점 행렬 (매우 희소함)을 두 개의 작은 저차원 '잠재 요인(Latent Factor)' 행렬 (사용자-잠재요인 행렬, 아이템-잠재요인 행렬)의 곱으로 근사(분해)할 수 있다고 가정합니다.
  • 동작 방식:
    1. 잠재 요인 학습: 주어진 평점 데이터를 가장 잘 설명하는 사용자 잠재 요인 벡터와 아이템 잠재 요인 벡터를 학습합니다. (주로 SGD, ALS 등의 최적화 알고리즘 사용) 이 잠재 요인은 명시적이진 않지만 '장르 선호도', '배우 선호도' 같은 숨겨진 특징을 나타낼 수 있습니다.
    2. 점수 예측: 특정 사용자의 잠재 요인 벡터와 특정 아이템의 잠재 요인 벡터를 내적(Dot Product)하여 해당 사용자가 해당 아이템에 대해 매길 평점(또는 선호도)을 예측합니다.
    3. 추천: 예측 점수가 높은 아이템들을 추천합니다.
  • 장점: 데이터 부족(Sparsity) 문제를 잘 처리함, 숨겨진 패턴을 발견하여 정확도가 높은 경향, 확장성이 좋음 (잠재 요인 벡터만 저장하면 됨).
  • 단점: 추천 이유 설명이 이웃 기반보다 어려움, 학습 과정이 필요함.

행렬 분해 (Matrix Factorization, MF) 자세히 알아보기 🚀

Matrix Factorization (MF)은 협업 필터링(CF)을 구현하는 모델 기반(Model-based) 기법 중 가장 대표적이고 성공적인 방법 중 하나입니다. 특히 딥러닝 기반 추천 시스템이 대중화되기 전까지 추천 시스템 분야에서 왕좌를 차지했을 정도로 강력하고 널리 사용되었죠. Two-Tower 모델 같은 최신 딥러닝 모델들도 MF의 핵심 아이디어인 '잠재 요인(Latent Factor)' 개념을 계승하고 발전시킨 경우가 많습니다. MF를 이해하면 현대 추천 시스템의 근간을 이해하는 데 큰 도움이 됩니다.

1. 문제 정의: 왜 MF가 필요할까?

협업 필터링의 기본 데이터는 사용자-아이템 상호작용 행렬 (User-Item Interaction Matrix) 입니다. 보통 행(Row)은 사용자를, 열(Column)은 아이템을 나타내고, 행렬의 각 값은 사용자가 아이템에 대해 매긴 평점, 클릭 여부, 구매 횟수 등을 나타냅니다.

[표 예시: 사용자-아이템 상호작용 행렬]

여기서 가장 큰 문제는 이 행렬이 매우 희소(Sparse)하다는 것입니다. 대부분의 사용자는 전체 아이템 중 극히 일부하고만 상호작용하기 때문에, 행렬의 대부분 값은 비어있거나('?') 알 수 없는 상태입니다.

MF의 목표는 이 희소한 행렬의 빈칸을 채워서, 사용자가 아직 상호작용하지 않은 아이템에 대해 얼마나 선호할지를 예측하는 것입니다.


2. 핵심 아이디어: 잠재 요인 (Latent Factors)을 찾아라!

MF는 사용자-아이템 상호작용 행렬 R (크기 N x M) 이 두 개의 저차원(Low-rank) 행렬 P와 Q의 곱으로 근사될 수 있다고 가정합니다.

  • P (User-Factor Matrix): 사용자를 나타내는 행렬 (크기 N x K). 각 행은 특정 사용자를 나타내는 잠재 요인 벡터 (User Latent Factor Vector) 입니다. 이 벡터는 사용자의 숨겨진(Latent) 취향이나 특성을 나타냅니다. (예: 특정 장르 선호도, 가격 민감도 등 - 명시적이진 않음)
  • Q (Item-Factor Matrix): 아이템을 나타내는 행렬 (크기 M x K). 각 행은 특정 아이템을 나타내는 잠재 요인 벡터 (Item Latent Factor Vector) 입니다. 이 벡터는 아이템의 숨겨진 특성이나 속성을 나타냅니다. (예: 특정 장르의 포함 정도, 배우/감독의 특성 등 - 명시적이진 않음)
  • K: 잠재 요인의 차원 수 (하이퍼파라미터). 보통 원래 행렬의 차원 N, M보다 훨씬 작습니다 (K << N, K << M). 이 K가 작기 때문에 '저차원'이라고 부릅니다.

 

수식으로 표현하면:

R ≈ P × Qᵀ

특정 사용자 u가 특정 아이템 i에 대해 예측하는 평점(r̂_ui)은 어떻게 계산될까요?

ui = p_u ⋅ q_i = Σ{k=1}^{K} (p_uk * q_ik)

직관적인 의미: 사용자의 잠재 요인 벡터와 아이템의 잠재 요인 벡터를 내적한다는 것은, "사용자의 취향(p_u)과 아이템의 특성(q_i)이 얼마나 잘 맞는지를 계산"하는 것과 같습니다. 두 벡터가 비슷한 방향을 가리키고 값이 클수록 내적 값이 커지고, 이는 높은 예측 평점으로 이어집니다.

3. 어떻게 P와 Q를 찾을까? - 학습 과정

이제 가장 중요한 질문은 "어떻게 최적의 P와 Q 행렬을 찾을 것인가?" 입니다. 즉, 원래 행렬 R의 알려진 값들을 가장 잘 설명하는 P와 Q를 찾아야 합니다.

  • 목표 함수 (Objective Function) 정의: 예측값(p_u ⋅ q_i)과 실제값(r_ui)의 차이(오차)를 최소화하는 P와 Q를 찾습니다. 가장 일반적인 손실 함수(Loss Function)는 제곱 오차합(Sum of Squared Errors) 입니다.
  • 과적합 방지 (Regularization): 위 손실 함수만 사용하면 모델이 학습 데이터에만 너무 최적화되어 새로운 데이터에 대한 예측 성능이 떨어지는 과적합(Overfitting)이 발생할 수 있습니다. 이를 방지하기 위해 정규화 항을 추가합니다. 가장 많이 사용하는 것은 L2 정규화입니다.
  • 최적화 알고리즘: 이 Regularized Loss를 최소화하는 P와 Q를 찾기 위해 주로 다음 두 가지 알고리즘을 사용합니다.
    • 확률적 경사 하강법 (Stochastic Gradient Descent, SGD): 알려진 평점 데이터 (u, i, r_ui) 하나를 무작위로 선택합니다. 해당 데이터에 대한 예측 오차 (r_ui - p_u ⋅ q_i)를 계산합니다. 오차를 줄이는 방향으로 p_u 와 q_i 벡터를 조금씩 업데이트합니다. (정규화 항도 고려) 모든 알려진 평점 데이터에 대해 이 과정을 여러 번 반복합니다(Epochs). 구현이 비교적 간단하고 대규모 데이터에도 잘 작동합니다.
    • 교대 최소 제곱법 (Alternating Least Squares, ALS): 아이템 잠재 요인 행렬 Q를 고정시킨 상태에서, 손실 함수를 최소화하는 사용자 잠재 요인 행렬 P를 계산합니다. (이는 수학적으로 한 번에 풀 수 있는 형태가 됩니다) 사용자 잠재 요인 행렬 P를 고정시킨 상태에서, 손실 함수를 최소화하는 아이템 잠재 요인 행렬 Q를 계산합니다. P와 Q가 수렴할 때까지 1, 2 단계를 번갈아(Alternating) 반복합니다. 병렬 처리에 용이하고, 암시적 피드백(Implicit Feedback) 데이터에 효과적이라고 알려져 있습니다. (Spark MLlib 등에 구현되어 있음)

4. 추천 생성

학습이 완료되어 최적의 P와 Q 행렬을 얻었다면, 사용자 u에게 아이템을 추천하는 방법은 간단합니다.

  1. 사용자 u가 아직 상호작용하지 않은 모든 아이템 i에 대해 예측 평점 r̂_ui = p_u ⋅ q_i 를 계산합니다.
  2. 계산된 예측 평점들을 내림차순으로 정렬합니다.
  3. 상위 N개의 아이템을 사용자 u에게 추천합니다.

5. 장점

  • 희소성 문제 해결: 알려지지 않은 평점을 예측하여 희소한 데이터를 효과적으로 다룹니다.
  • 확장성: 이웃 기반 모델보다 일반적으로 더 적은 메모리를 사용하고 예측 속도가 빠릅니다 (사용자/아이템 벡터만 저장).
  • 잠재 패턴 발견: 데이터에 숨겨진 사용자 취향과 아이템 특성을 발견하여 예측 정확도를 높일 수 있습니다.

6. 단점

  • 콜드 스타트 문제: 새로운 사용자나 아이템에 대해서는 잠재 요인 벡터를 학습할 데이터가 없으므로 추천이 어렵습니다. (다른 기법과 혼합 필요)
  • 해석의 어려움: 학습된 잠재 요인이 정확히 무엇을 의미하는지 직관적으로 해석하기 어려울 수 있습니다.
  • 상호작용 데이터 중심: 주로 사용자-아이템 상호작용 데이터에 의존하며, 사용자/아이템의 부가적인 정보(메타데이터)를 직접적으로 활용하기는 상대적으로 어렵습니다 (확장된 모델에서는 가능).

코드 예시

from surprise import Dataset
from surprise import Reader
from surprise import SVD
from surprise import accuracy
from surprise.model_selection import train_test_split

# 1. 데이터 로드 및 Reader 정의
# Surprise는 자체 데이터 형식을 사용하므로, Reader를 통해 데이터를 읽어들여야 합니다.

# (예시) ratings.csv 파일이 있다고 가정: user_id, item_id, rating 형식
# 예시 데이터:
# 1,101,4.0
# 1,102,3.5
# 2,101,2.0
# 2,103,5.0

# 파일 경로 지정
file_path = 'ratings.csv'

# Reader 정의: 평점 범위 지정 (여기서는 1점부터 5점)
reader = Reader(rating_scale=(1, 5))

# Dataset 로드: 파일 경로와 reader를 사용하여 데이터셋 생성
data = Dataset.load_from_file(file_path, reader=reader)

# 2. 데이터 분할 (Train/Test)
# 모델 성능 평가를 위해 데이터를 학습용과 테스트용으로 나눕니다.

trainset, testset = train_test_split(data, test_size=0.25, random_state=42) # 테스트 25%, random_state로 결과 고정

# 3. 모델 선택 및 학습
# Surprise는 다양한 MF 알고리즘을 제공합니다. 여기서는 가장 기본적인 SVD를 사용합니다.
# SVD는 MF의 한 종류이며, 행렬 분해를 통해 잠재 요인을 학습합니다.

# SVD 모델 선택 (n_factors: 잠재 요인 수)
model = SVD(n_factors=50, random_state=42) # 잠재 요인 50개, random_state로 결과 고정

# 학습 데이터로 모델 학습
model.fit(trainset)

# 4. 테스트 데이터로 예측 및 평가
# 학습된 모델을 사용하여 테스트 데이터에 대한 평점을 예측하고, 성능을 평가합니다.

# 테스트 데이터에 대한 예측
predictions = model.test(testset)

# RMSE (Root Mean Squared Error) 계산 및 출력
rmse = accuracy.rmse(predictions)
print(f'RMSE: {rmse}')

# 5. 특정 사용자에 대한 추천 생성 (예시)
# 학습된 모델을 사용하여 특정 사용자가 아직 평가하지 않은 아이템에 대한 평점을 예측하고, 추천을 생성합니다.

user_id = 1  # 추천을 생성할 사용자 ID
items_to_predict = trainset.build_anti_testset() # 학습 데이터에 없는 (사용자가 평가하지 않은) 아이템 목록 생성
user_items = []
for uid, iid, _ in items_to_predict:
    if uid == user_id:
        user_items.append((uid, iid, _))

# 특정 사용자에 대한 아이템 평점 예측
user_predictions = model.test(user_items)

# 예측 평점이 높은 순으로 아이템 정렬
from operator import itemgetter
user_predictions.sort(key=itemgetter('est'), reverse=True)

# 상위 N개 아이템 추천 (예: 상위 10개)
top_n = 10
top_recommendations = user_predictions[:top_n]

print(f'\nTop {top_n} recommendations for user {user_id}:')
for prediction in top_recommendations:
    print(f'Item {prediction.iid}: Predicted rating = {prediction.est:.2f}') #iid는 아이템ID est는 예상 평점

 

코드 설명: 


1.  데이터 로드: ratings.csv 파일에서 사용자, 아이템, 평점 데이터를 읽어옵니다.  Surprise의 `Reader`를 사용하여 평점 범위를 지정하고, `Dataset.load_from_file`을 사용하여 데이터셋을 생성합니다.

 

2.  데이터 분할: 데이터를 학습용과 테스트용으로 8:2.5 비율로 나눕니다.

 

3.  모델 선택 및 학습:`SVD` 모델을 선택하고 (잠재 요인 수 `n_factors` 설정), 학습 데이터로 모델을 학습시킵니다.

 

4.  예측 및 평가: 테스트 데이터에 대한 평점을 예측하고, RMSE (Root Mean Squared Error)를 사용하여 모델 성능을 평가합니다.

 

5.  추천 생성: 특정 사용자가 아직 평가하지 않은 아이템에 대한 평점을 예측하고, 예측 평점이 높은 순으로 아이템을 정렬하여 상위 N개 아이템을 추천합니다.

참고:

*   이 코드는 기본적인 예시이며, 실제 서비스에서는 데이터 전처리, 하이퍼파라미터 튜닝, 다양한 모델 비교 등을 통해 성능을 더욱 향상시킬 수 있습니다.

 

*   Surprise 라이브러리에 대한 자세한 내용은 공식 문서를 참조하세요: [http://surpriselib.com/](http://surpriselib.com/)

 

*   ratings.csv 파일을 직접 생성하거나, Surprise에서 제공하는 내장 데이터셋 (예: MovieLens)을 사용할 수도 있습니다.

 

*   필요에 따라 다른 MF 알고리즘 (예: SVD++, NMF)을 선택할 수 있습니다.

이 코드를 통해 MF의 기본 원리를 이해하고, 실제 추천 시스템 개발에 적용해 보시기 바랍니다. 추가적인 질문이나 도움이 필요하시면 언제든지 문의해주세요!

 

 

정리
P와 Q의 컬럼은 DB 컬럼이 아니라, 모델이 학습을 통해 발견하는 '잠재 요인(Latent Factor)'입니다. 각 컬럼의 의미는 미리 정하는 것이 아니라, 학습 과정에서 형성됩니다. (그래서 해석하기 어려울 수도 있습니다.) 개발자는 잠재 요인의 개수(K)를 정하고, 모델은 랜덤 초기값에서 시작하여 최적의 잠재 요인 값을 찾아갑니다.

현업에서 많이 사용되는 Matrix Factorization 라이브러리:

  • Apache Spark MLlib (ALS): 대규모 데이터 + 분산 처리 필수
  • Surprise: 빠른 프로토타이핑, 연구, 중소규모
  • Implicit: 암시적 피드백 데이터 중심
  • LightFM: 사용자/아이템 정보 함께 활용 (하이브리드)
  • TensorFlow / PyTorch / Keras (직접 구현): 최고의 유연성, 최신 기술, 딥러닝 확장

결론:

Matrix Factorization은 사용자-아이템 상호작용 데이터의 희소성을 극복하고 숨겨진 패턴을 찾아 추천 정확도를 높이는 강력한 협업 필터링 기법입니다. 비록 최신 딥러닝 모델들이 등장했지만, MF의 핵심 아이디어는 여전히 유효하며 많은 추천 시스템의 기반이 되고 있습니다. MF를 이해하는 것은 추천 시스템 개발자에게 매우 중요한 기본 소양이라고 할 수 있습니다!

반응형