협업 필터링
Collaborative Filtering
유사 사용자/아이템 기반 추천. Netflix, Amazon에서 활용.
Collaborative Filtering
유사 사용자/아이템 기반 추천. Netflix, Amazon에서 활용.
협업 필터링(Collaborative Filtering)은 다수 사용자의 행동 패턴을 분석해 개인화된 추천을 제공하는 알고리즘입니다. "비슷한 취향의 사용자가 좋아한 아이템을 당신도 좋아할 것이다"라는 가정에 기반합니다. Netflix의 영화 추천, Amazon의 상품 추천, Spotify의 음악 추천 등 현대 추천 시스템의 핵심 기술이며, 1990년대 초 개발된 이래 지속적으로 발전해왔습니다.
협업 필터링은 크게 두 가지 접근 방식으로 나뉩니다. 사용자 기반(User-based CF)은 현재 사용자와 유사한 사용자들을 찾아 그들이 좋아한 아이템을 추천합니다. 아이템 기반(Item-based CF)은 사용자가 이미 좋아한 아이템과 유사한 아이템을 추천합니다. Amazon은 아이템 기반을 선호하는데, 아이템 간 유사도가 사용자 간 유사도보다 안정적이고 계산이 효율적이기 때문입니다.
2024-2025년 현재 협업 필터링은 딥러닝과 결합해 크게 발전했습니다. Matrix Factorization(SVD, ALS)은 사용자-아이템 행렬을 저차원 잠재 요인으로 분해합니다. Neural Collaborative Filtering(NCF)은 신경망으로 사용자-아이템 상호작용을 모델링합니다. Two-Tower 모델은 사용자와 아이템 임베딩을 별도 네트워크로 학습해 대규모 서비스에서 효율적입니다. 최근에는 Graph Neural Network 기반 추천과 Transformer를 활용한 순차 추천도 활발히 연구됩니다.
협업 필터링의 주요 과제는 콜드 스타트(신규 사용자/아이템), 희소성(대부분 사용자가 극소수 아이템만 평가), 확장성입니다. 이를 해결하기 위해 콘텐츠 기반 필터링과 결합한 하이브리드 추천, 지식 그래프 활용, 사전학습 임베딩(LLM 기반) 등이 사용됩니다. 실무에서는 추천의 다양성(diversity), 신선도(freshness), 설명가능성(explainability)도 중요한 고려 요소입니다.
PyTorch와 Surprise 라이브러리를 사용한 협업 필터링 구현 예제입니다.
import torch
import torch.nn as nn
import numpy as np
from surprise import Dataset, Reader, SVD, KNNBasic
from surprise.model_selection import cross_validate
import pandas as pd
# 1. Surprise를 사용한 전통적 협업 필터링
def traditional_collaborative_filtering():
"""SVD와 KNN 기반 협업 필터링"""
# 샘플 데이터 생성
ratings_dict = {
'user_id': [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4],
'item_id': [1, 2, 3, 1, 2, 4, 2, 3, 4, 1, 3, 4],
'rating': [5, 3, 4, 4, 2, 5, 3, 4, 4, 5, 2, 3]
}
df = pd.DataFrame(ratings_dict)
reader = Reader(rating_scale=(1, 5))
data = Dataset.load_from_df(df[['user_id', 'item_id', 'rating']], reader)
# SVD (Matrix Factorization)
svd = SVD(n_factors=50, n_epochs=20, random_state=42)
cv_results = cross_validate(svd, data, measures=['RMSE', 'MAE'], cv=3, verbose=True)
print(f"SVD RMSE: {np.mean(cv_results['test_rmse']):.4f}")
# User-based KNN
user_knn = KNNBasic(k=20, sim_options={'name': 'cosine', 'user_based': True})
cross_validate(user_knn, data, measures=['RMSE'], cv=3, verbose=True)
# Item-based KNN
item_knn = KNNBasic(k=20, sim_options={'name': 'cosine', 'user_based': False})
cross_validate(item_knn, data, measures=['RMSE'], cv=3, verbose=True)
return svd
# 2. PyTorch로 Neural Collaborative Filtering (NCF) 구현
class NeuralCollaborativeFiltering(nn.Module):
"""NCF: GMF + MLP 결합 모델"""
def __init__(
self,
num_users: int,
num_items: int,
mf_dim: int = 32,
mlp_dims: list = [64, 32, 16]
):
super().__init__()
self.num_users = num_users
self.num_items = num_items
# GMF (Generalized Matrix Factorization) 임베딩
self.user_embedding_gmf = nn.Embedding(num_users, mf_dim)
self.item_embedding_gmf = nn.Embedding(num_items, mf_dim)
# MLP 임베딩 (더 큰 차원)
self.user_embedding_mlp = nn.Embedding(num_users, mlp_dims[0] // 2)
self.item_embedding_mlp = nn.Embedding(num_items, mlp_dims[0] // 2)
# MLP 레이어
mlp_layers = []
for i in range(len(mlp_dims) - 1):
mlp_layers.append(nn.Linear(mlp_dims[i], mlp_dims[i+1]))
mlp_layers.append(nn.ReLU())
mlp_layers.append(nn.Dropout(0.2))
self.mlp = nn.Sequential(*mlp_layers)
# 최종 예측 레이어
self.prediction = nn.Linear(mf_dim + mlp_dims[-1], 1)
self._init_weights()
def _init_weights(self):
for module in self.modules():
if isinstance(module, nn.Embedding):
nn.init.normal_(module.weight, std=0.01)
elif isinstance(module, nn.Linear):
nn.init.xavier_uniform_(module.weight)
def forward(self, user_ids: torch.Tensor, item_ids: torch.Tensor):
# GMF 경로
user_gmf = self.user_embedding_gmf(user_ids)
item_gmf = self.item_embedding_gmf(item_ids)
gmf_output = user_gmf * item_gmf # Element-wise product
# MLP 경로
user_mlp = self.user_embedding_mlp(user_ids)
item_mlp = self.item_embedding_mlp(item_ids)
mlp_input = torch.cat([user_mlp, item_mlp], dim=-1)
mlp_output = self.mlp(mlp_input)
# 결합
concat = torch.cat([gmf_output, mlp_output], dim=-1)
prediction = self.prediction(concat)
return prediction.squeeze()
# 3. Two-Tower 추천 모델 (대규모 서비스용)
class TwoTowerModel(nn.Module):
"""사용자/아이템 타워 분리 모델 - 효율적인 대규모 추천"""
def __init__(
self,
num_users: int,
num_items: int,
embedding_dim: int = 64
):
super().__init__()
# User Tower
self.user_embedding = nn.Embedding(num_users, embedding_dim)
self.user_tower = nn.Sequential(
nn.Linear(embedding_dim, 128),
nn.ReLU(),
nn.Linear(128, embedding_dim),
nn.LayerNorm(embedding_dim)
)
# Item Tower
self.item_embedding = nn.Embedding(num_items, embedding_dim)
self.item_tower = nn.Sequential(
nn.Linear(embedding_dim, 128),
nn.ReLU(),
nn.Linear(embedding_dim, embedding_dim),
nn.LayerNorm(embedding_dim)
)
def get_user_embedding(self, user_ids: torch.Tensor):
"""사용자 임베딩 - 캐싱 가능"""
x = self.user_embedding(user_ids)
return self.user_tower(x)
def get_item_embedding(self, item_ids: torch.Tensor):
"""아이템 임베딩 - 사전 계산 가능"""
x = self.item_embedding(item_ids)
return self.item_tower(x)
def forward(self, user_ids: torch.Tensor, item_ids: torch.Tensor):
user_emb = self.get_user_embedding(user_ids)
item_emb = self.get_item_embedding(item_ids)
# Dot product similarity
return torch.sum(user_emb * item_emb, dim=-1)
# 4. 학습 및 추천
def train_ncf():
"""NCF 모델 학습 예시"""
# 더미 데이터
num_users, num_items = 1000, 5000
train_users = torch.randint(0, num_users, (10000,))
train_items = torch.randint(0, num_items, (10000,))
train_ratings = torch.rand(10000) * 4 + 1 # 1-5 점수
model = NeuralCollaborativeFiltering(num_users, num_items)
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
criterion = nn.MSELoss()
# 학습
model.train()
for epoch in range(10):
optimizer.zero_grad()
predictions = model(train_users, train_items)
loss = criterion(predictions, train_ratings)
loss.backward()
optimizer.step()
if epoch % 2 == 0:
print(f"Epoch {epoch}, Loss: {loss.item():.4f}")
# 추천 생성
model.eval()
user_id = 0
all_items = torch.arange(num_items)
user_ids = torch.full((num_items,), user_id)
with torch.no_grad():
scores = model(user_ids, all_items)
top_k_items = torch.argsort(scores, descending=True)[:10]
print(f"\nUser {user_id}의 Top 10 추천: {top_k_items.tolist()}")
return model
if __name__ == "__main__":
print("=== 전통적 협업 필터링 (Surprise) ===")
traditional_collaborative_filtering()
print("\n=== Neural Collaborative Filtering ===")
train_ncf()
| 방식 | 장점 | 단점 | 적합한 상황 |
|---|---|---|---|
| User-based KNN | 직관적, 설명 가능 | 확장성 낮음 | 소규모, 사용자 유사성 중요 |
| Item-based KNN | 안정적, 캐싱 가능 | 다양성 부족 | 아이템 변동 적은 서비스 |
| Matrix Factorization | 희소성 극복, 빠름 | 암묵적 피드백 한계 | 명시적 평점 데이터 |
| NCF (딥러닝) | 비선형 관계 학습 | 학습 비용 높음 | 복잡한 상호작용 |
| Two-Tower | 대규모 효율적 | 상호작용 단순화 | 억 단위 사용자/아이템 |
협업 필터링은 이미 인기 있는 아이템을 더 많이 추천하는 경향이 있습니다. 다양성(diversity) 지표를 모니터링하고, 탐색(exploration) 전략을 포함하세요.
RMSE, nDCG 같은 오프라인 지표가 좋아도 실제 클릭률이 낮을 수 있습니다. A/B 테스트로 비즈니스 지표(CTR, 전환율, 체류시간)를 반드시 확인하세요.
협업 필터링과 콘텐츠 기반을 결합하고, 사용자 행동 변화를 반영하기 위해 정기적으로 모델을 재학습하세요. 실시간 피드백 루프도 고려하세요.