이커머스 추천 엔진 설계: 협업 필터링부터 실시간 개인화까지

이커머스

추천 엔진협업 필터링개인화머신러닝A/B 테스트

이 글은 누구를 위한 것인가

  • "이 상품을 구매한 사람은 이것도 구매했습니다" 기능을 만들려는 팀
  • 단순 인기 상품 추천에서 개인화로 전환하고 싶은 이커머스 개발자
  • 추천 시스템의 성능을 측정하고 개선하려는 데이터 팀

들어가며

아마존 매출의 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 테스트 없이 추천 품질은 알 수 없다 — 반드시 실험과 측정을 병행하라.