이 글은 누구를 위한 것인가
- "이 상품을 구매한 사람은 이것도 구매했습니다" 기능을 만들려는 팀
- 단순 인기 상품 추천에서 개인화로 전환하고 싶은 이커머스 개발자
- 추천 시스템의 성능을 측정하고 개선하려는 데이터 팀
들어가며
아마존 매출의 35%가 추천 시스템에서 나온다. 단순한 "인기 상품" 목록에서 벗어나 사용자 행동 기반 개인화로 전환하면 CTR과 전환율이 크게 달라진다.
이 글은 bluefoxdev.kr의 이커머스 개인화 전략 을 참고하여 작성했습니다.
1. 추천 유형 선택
[추천 알고리즘 비교]
협업 필터링 (Collaborative Filtering):
User-based: 비슷한 취향 사용자의 구매 → 추천
Item-based: 같이 구매된 상품 → 추천
장점: 도메인 지식 불필요
단점: Cold Start (신규 사용자/상품 문제)
콘텐츠 기반 (Content-Based):
상품 속성 유사도 기반 추천
장점: Cold Start 없음, 설명 가능
단점: 새로운 카테고리 추천 어려움
하이브리드:
두 가지 혼합 → 실무에서 주로 사용
Matrix Factorization (ALS, SVD):
User-Item 행렬을 저차원으로 분해
암묵적 피드백(클릭, 구매)에 효과적
2. ALS 기반 추천 구현
import numpy as np
from scipy.sparse import csr_matrix
from implicit import als
def build_user_item_matrix(interactions_df):
"""
interactions_df: user_id, item_id, weight (구매=3, 장바구니=2, 클릭=1)
"""
user_ids = interactions_df['user_id'].astype('category')
item_ids = interactions_df['item_id'].astype('category')
matrix = csr_matrix((
interactions_df['weight'].values,
(user_ids.cat.codes, item_ids.cat.codes)
))
return matrix, user_ids.cat.categories, item_ids.cat.categories
def train_als_model(user_item_matrix):
model = als.AlternatingLeastSquares(
factors=64, # 잠재 요인 수
regularization=0.1,
iterations=20,
calculate_training_loss=True,
)
model.fit(user_item_matrix)
return model
def get_recommendations(model, user_id, user_ids, item_ids, n=10):
"""사용자별 추천 상품 top-N"""
user_idx = list(user_ids).index(user_id)
recommendations, scores = model.recommend(
user_idx,
user_item_matrix[user_idx],
N=n,
filter_already_liked_items=True, # 이미 구매한 상품 제외
)
return [
{"item_id": item_ids[i], "score": float(s)}
for i, s in zip(recommendations, scores)
]
def get_similar_items(model, item_id, item_ids, n=6):
"""유사 상품 추천 (상품 상세 페이지)"""
item_idx = list(item_ids).index(item_id)
similar, scores = model.similar_items(item_idx, N=n+1)
return [
{"item_id": item_ids[i], "score": float(s)}
for i, s in zip(similar[1:], scores[1:]) # 자기 자신 제외
]
3. 실시간 이벤트 기반 업데이트
from kafka import KafkaConsumer
import json
import redis
r = redis.Redis()
def update_realtime_recommendations(user_id: str, item_id: str, event: str):
"""
클릭/장바구니/구매 이벤트로 실시간 추천 업데이트
"""
weights = {"click": 1, "cart": 2, "purchase": 5}
weight = weights.get(event, 1)
# 사용자-아이템 점수 업데이트 (Redis Sorted Set)
r.zincrby(f"user:interests:{user_id}", weight, item_id)
r.expire(f"user:interests:{user_id}", 86400 * 30) # 30일 유지
# 실시간 추천 캐시 무효화
r.delete(f"reco:user:{user_id}")
def get_session_recommendations(user_id: str, session_items: list[str]) -> list[str]:
"""세션 내 최근 본 상품 기반 즉석 추천"""
if not session_items:
return get_popular_items()
# 최근 본 상품들의 유사 상품 합산
scores = {}
for item_id in session_items[-3:]: # 최근 3개만
similar = get_similar_items_from_cache(item_id)
for s in similar:
scores[s['item_id']] = scores.get(s['item_id'], 0) + s['score']
# 이미 본 상품 제외
for item_id in session_items:
scores.pop(item_id, None)
return sorted(scores.keys(), key=lambda x: -scores[x])[:10]
# Kafka Consumer
consumer = KafkaConsumer(
'user-events',
bootstrap_servers=['localhost:9092'],
value_deserializer=lambda m: json.loads(m.decode('utf-8'))
)
for message in consumer:
event = message.value
update_realtime_recommendations(
event['user_id'], event['item_id'], event['event_type']
)
4. 추천 품질 측정
[추천 시스템 지표]
오프라인 지표:
Precision@K: 추천 K개 중 실제 구매 비율
Recall@K: 실제 구매 중 추천에 포함된 비율
NDCG@K: 순위 가중 정확도
온라인 지표 (A/B 테스트):
CTR (Click-Through Rate): 추천 클릭 비율
CVR (Conversion Rate): 추천 → 구매 비율
Revenue per User: 추천 통한 수익/사용자
목표:
추천 CTR: 5% 이상 (인기 상품 대비 2배)
추천 CVR: 일반 탐색 대비 1.5배 이상
마무리
추천 시스템은 처음부터 복잡할 필요가 없다. Item-based 협업 필터링으로 시작해서 데이터가 쌓이면 ALS로, 실시간 이벤트를 추가하면서 점진적으로 발전시켜라. A/B 테스트 없이 추천 품질은 알 수 없다 — 반드시 실험과 측정을 병행하라.