Skip to content

Lzino/Recommendation_system

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Recommendation system

유저 행동 로그 1,600만 건을 분석하여 개인화 추천 시스템을 설계한 과제입니다.


목차

  1. 과제 개요
  2. 분석 결과 요약
  3. 전체 구조
  4. 핵심 설계 결정 및 이유
  5. 트러블슈팅
  6. 환경 설정 및 실행
  7. 배운 점 / 느낀 점

1. 과제 개요

Task #1 — 탐색적 데이터 분석 (EDA)

유저 행동 로그와 상품 정보를 분석하여 구매 전환의 인사이트를 파악합니다.

Task #2 — 추천 시스템 설계

EDA 인사이트를 바탕으로 실행 가능한 개인화 추천 시스템을 설계합니다.

데이터셋

테이블 규모 주요 컬럼
products 33,236건 category, price, fulfillment_type, is_advertisement
attributes 288,152건 key (10종), value
reviews 21,745건 rating, count, contents (요약 텍스트)
embeddings 33,098건 vector (64차원)
users 581,278건 age, gender
logs 16,030,051건 event_type, event_time, user_id, product_id

데이터 주요 특성 및 이해 과정

항목 내용
로그 기간 단일 날짜(1일치) — 장기 선호 변화·계절성 반영 불가
결측치: users.age / gender 약 20% 결측 — 선택적 입력 또는 검증용 의도적 제거로 판단, 행동 기반 분석에는 포함
결측치: reviews 전체 상품 중 약 65%는 리뷰 없음 — TF-IDF 클러스터를 근사값으로 활용
embeddings 커버리지 33,098 / 33,236 상품 (약 0.4% 미생성) — 해당 상품은 인기도 fallback 처리
세션 식별자 부재 order_id / session_id 없음 — 30분 윈도우로 세션 근사 (상세: 트러블슈팅)

2. 분석 결과 요약

이벤트 퍼널

impression  ──────────────────────────── 약 9.4M
    │
    ▼  CTR ≈ 높음
  click     ──────────────
    │
    ▼  CVR ≈ 1~2%
purchase    ──

클릭 이후 전환율이 병목. 추천 시스템의 우선 목표는 CVR 개선.

11개 가설 검증 요약

# 가설 검증 결과 추천 시스템 시사점
1 광고 CTR↑, CVR 중립 채택 광고 여부는 랭킹 피처로 주의 사용
2 빠른 배송(express) → CVR↑ 채택 배송 방식을 랭킹 피처로 활용
3 성별·연령별 패턴 상이 채택 인구통계 기반 세그먼트 개인화 필요
4 카테고리별 CVR 차이 채택 카테고리 인기도를 후보 생성에 반영
5 할인율 30-50% 구간 최적 채택 할인율을 랭킹 피처에 포함
6 고가 상품일수록 리뷰 의존↑ 채택 가격대별 리뷰 가중치 차등 적용
7 속성별 전환율 차이 존재 채택 color·fit·material을 피처로 활용
8 헤비유저 ≠ 일반유저 채택 세그먼트별 추천 전략 차별화
9 장바구니 후 빠른 구매 결정 채택 리타게팅 타이밍 최적화
10 리뷰 클러스터별 CVR 차이 채택 리뷰 텍스트를 상품 표현에 반영
11 세트 구매 패턴 존재 채택 Cross-selling 추천에 활용

3. 전체 구조

분석 파이프라인

dataset/
├── logs.parquet           (16M행 — DuckDB로 집계)
├── products.parquet.txt
├── users.parquet.txt
├── reviews.parquet.txt
├── attributes.parquet.txt
└── embeddings.parquet.txt
        │
        ▼
┌────────────────────────────────────────┐
│         reco_eda_model.ipynb           │
│                                        │
│  0. 환경 설정 & 데이터 로딩                 │
│  1. 기본 탐색 (결측치·분포·퍼널)             │
│  2. 전처리 — 로그 집계 (DuckDB PIVOT)      │
│  3. 가설 검증 (11개)                      │
│  4. EDA 인사이트 요약                     │
│  ─────────────────────────────         │
│  5. 추천 시스템 문제 정의                   │
│  6. 피처 엔지니어링                        │
│  7. 유저 세그먼테이션 (K-Means)            │
│  8. 추천 모델 설계                        │
│     8.1 Retrieval — 임베딩 유사도         │
│     8.2 Retrieval — 협업 필터링           │
│     8.3 Ranking — LightGBM 설계         │
│  9. 평가 방법론                           │ 
│  10. 시스템 아키텍처                       │
│  11. 결론 및 한계점                       │
│  12. 모델 저장                           │
└────────────────────────────────────────┘
        │
        ▼
models/
├── config.json          (하이퍼파라미터·피처 메타)
├── user_scaler.pkl      (StandardScaler)
├── user_kmeans.pkl      (유저 세그먼테이션 K=4)
├── review_tfidf.pkl     (TF-IDF Vectorizer)
├── review_kmeans.pkl    (리뷰 클러스터링 K=5)
└── model.pth            (임베딩 행렬 + 클러스터 파라미터)

추천 시스템 아키텍처

[유저 요청]
     │
     ▼
┌──────────────────────┐
│   Stage 1: Retrieval  │  33K 상품 → 수백 후보
│                       │
│  ① 임베딩 유사도         │  product embedding (64d)
│     코사인 유사도        │  → 유사 상품 Top-K
│                       │
│  ② 협업 필터링          │  user vector = Σ(상품 임베딩
│     가중 평균 임베딩      │    × 행동 가중치)
│     purchase=5        │  → 코사인 유사도로 후보 생성
│     cart=3            │
│     wishlist=2        │
│     click=1           │
│                       │
│  ③ 인기도 Fallback     │  신규 유저 / cold start
└──────────┬───────────┘
           │
           ▼
┌──────────────────────┐
│   Stage 2: Ranking   │  후보 → 최종 순위 (Top-N)
│   LightGBM           │
│                      │
│  유저 피처             │  age, gender, segment,
│                      │  click_to_purchase_rate
│  상품 피처             │  category, price, discount_rate,
│                      │  fulfillment_type, rating
│  상호작용 피처          │  embedding cosine similarity,
│                      │  과거 클릭·위시리스트 여부
└──────────┬───────────┘
           │
           ▼
┌──────────────────────┐
│   Re-ranking          │  비즈니스 규칙 적용
│                       │  (다양성 보장, 광고 제한 등)
└──────────────────────┘

유저 세그먼테이션 (K=4)

행동 기반 K-Means 클러스터링으로 유저를 4개 세그먼트로 분류합니다.

피처 설명
total_clicks 총 클릭 수
total_purchases 총 구매 수
total_wishlist 총 위시리스트 추가 수
total_cart 총 장바구니 추가 수
avg_price 평균 관심 상품 가격
avg_discount_rate 평균 할인율
click_to_purchase_rate 클릭 대비 구매 전환율

4. 핵심 설계 결정 및 이유

DuckDB로 1,600만 건 로그 처리

pandas로 16M행을 메모리에 올리면 수십 GB가 필요합니다. DuckDB를 사용하면 parquet 파일을 직접 SQL로 집계하여 결과만 pandas DataFrame으로 가져옵니다. PIVOT 구문 하나로 (user_id, product_id) × event_type 교차 집계를 처리했습니다.

# 집계 결과만 메모리로 가져옴 (전체 16M행 로드 불필요)
log_pivot = con.execute(f"""
    PIVOT (
        SELECT user_id, product_id, event_type, COUNT(*) as cnt
        FROM '{LOGS_PATH}'
        GROUP BY user_id, product_id, event_type
    )
    ON event_type USING SUM(cnt)
""").df()

Two-Stage Retrieval + Ranking 선택

단순히 "구매 이력이 많은 상품"을 추천하는 popularity-based 방식 대신 Two-Stage 구조를 선택한 이유는:

  1. 확장성: 33K 상품 × 581K 유저를 실시간 full-pair 스코어링하면 초당 처리 불가
  2. 품질: Retrieval로 후보를 줄인 뒤 Ranking에서 정밀한 개인화 적용
  3. 유연성: 각 Stage를 독립적으로 개선 가능

행동 가중치 설계 (purchase=5, cart=3, wishlist=2, click=1)

단순 클릭보다 구매가 훨씬 강한 구매 의사 신호입니다. 임베딩 기반 협업 필터링에서 유저 벡터를 상품 임베딩의 가중 평균으로 구성할 때, 이 가중치가 유저 선호를 얼마나 잘 표현하는지 직결됩니다. EDA에서 확인된 이벤트 퍼널의 전환 강도를 근거로 설정했습니다.

리뷰 TF-IDF + KMeans 클러스터링 (k=5)

리뷰 텍스트를 개별 상품의 속성으로 사용하면 데이터 희소성 문제가 있습니다. 리뷰가 없는 상품도 많기 때문입니다. TF-IDF로 텍스트를 벡터화한 뒤 K-Means로 클러스터를 만들면, 리뷰가 없는 상품도 상품 속성(카테고리, 가격 등)으로 클러스터를 근사할 수 있습니다.

결측 유저(인구통계 약 20%)를 제거하지 않고 유지한 이유

age, gender 결측은 회원가입 시 선택 입력이거나, 품질 검증 목적으로 의도적으로 제거된 것으로 판단했습니다. 이 유저들의 행동 로그는 유효하므로:

  • 인구통계 기반 분석: 결측 제외
  • 행동 기반 분석 (CVR, 클러스터링): 결측 포함 (unknown 그룹으로 분류)

5. 트러블슈팅

세션 식별: order_id / session_id 부재

문제: "동시 구매" 패턴(가설 11)을 분석하려면 세션 단위 묶음이 필요한데 데이터에 order_id, session_id가 없었습니다.

해결: 동일 유저의 구매 이벤트를 시간 기준 30분 윈도우로 묶어 세션을 근사했습니다. DuckDB에서 LAG 윈도우 함수로 이전 구매 시각과의 차이를 계산한 뒤, 30분 초과이거나 첫 구매이면 새 세션으로 분류했습니다.

WITH lagged AS (
    SELECT *,
        LAG(purchase_time) OVER (PARTITION BY user_id ORDER BY purchase_time) as prev_time
    FROM purchases
),
sessioned AS (
    SELECT *,
        SUM(CASE WHEN EXTRACT(EPOCH FROM (purchase_time - prev_time)) > 1800
            OR prev_time IS NULL THEN 1 ELSE 0 END)
        OVER (PARTITION BY user_id ORDER BY purchase_time) as session_id
    FROM lagged
)

DuckDB 중첩 윈도우 함수 오류

문제: DuckDB에서 SUM(...) OVER 안에 LAG(...) OVER를 중첩하면 파싱 오류가 발생했습니다.

해결: CTE(Common Table Expression)를 단계별로 분리하여 LAG 계산 → SUM 계산을 순차적으로 처리했습니다. (위 코드 참고)


6. 환경 설정 및 실행

Python 3.14+, uv 기반 프로젝트입니다.

# 의존성 설치
uv sync

# Jupyter 노트북 실행
uv run jupyter lab reco_eda_model.ipynb

주요 의존성

재현 가능성을 위해 uv.lock에 정확한 버전이 고정되어 있습니다. uv sync 실행 시 동일한 환경이 재구성됩니다.

패키지 버전 용도
duckdb ≥ 1.4.4 1,600만 건 로그 고속 집계
pandas ≥ 3.0.0 데이터 처리
numpy ≥ 2.4.2 수치 연산
scikit-learn ≥ 1.8.0 K-Means, TF-IDF, 코사인 유사도
matplotlib ≥ 3.10.8 시각화
seaborn ≥ 0.13.2 통계 시각화
pyarrow ≥ 23.0.1 parquet 파일 I/O
Python ≥ 3.14 런타임

저장 모델 아티팩트

models/
├── config.json        하이퍼파라미터, 피처 구성, 메타 정보
├── user_scaler.pkl    유저 피처 StandardScaler
├── user_kmeans.pkl    유저 세그먼테이션 KMeans (k=4)
├── review_tfidf.pkl   리뷰 TF-IDF Vectorizer (max_features=3000)
├── review_kmeans.pkl  리뷰 클러스터링 KMeans (k=5)
└── model.pth          임베딩 행렬 + 클러스터 파라미터

7. 결론 및 제안

비즈니스 제안

EDA에서 도출된 인사이트를 추천 시스템에 반영했을 때 기대할 수 있는 효과와, 우선순위별 개선 방향을 제안합니다.

우선순위 제안 근거
1순위 클릭→구매 전환율 개선을 핵심 KPI로 설정 CTR은 높지만 CVR이 1~2%로 병목 구간 확인
2순위 express 배송 상품을 랭킹 가중치에 반영 배송 방식별 CVR 차이 유의미 (가설 2)
3순위 연령·성별 세그먼트별 추천 전략 분리 인구통계별 행동 패턴 상이 (가설 3)
4순위 장바구니 추가 후 30분 이내 리타게팅 강화 구매 결정까지 시간이 짧음 (가설 9)
5순위 Cross-selling 추천 모듈 별도 구성 세트 구매 패턴 존재 (가설 11)

한계점 및 개선 방향

한계점 영향 개선 방안
1일치 로그만 사용 가능 계절성·장기 선호 변화 반영 불가 다기간 로그 축적 후 재학습
session_id 부재 정확한 세션 단위 분석 제한 30분 윈도우 근사 사용, 실서비스에서 세션 ID 수집 필요
LightGBM 학습 데이터 미구비 Ranking 모델은 설계만 수행, 실제 학습 불가 레이블(purchase 여부) 포함된 충분한 로그 필요
Cold start 대응 제한 신규 유저·신규 상품 추천 정확도 낮음 인구통계 기반 초기 추천 + 온보딩 선호 수집 강화
온라인 평가 미수행 A/B 테스트 없이 오프라인 지표만 검증 실서비스 배포 전 단계적 A/B 테스트 필수

평가 지표 제안

단계 지표 설명
오프라인 Precision@K, Recall@K, NDCG@K 추천 목록의 관련성 평가
오프라인 Coverage 전체 상품 중 추천에 노출되는 비율
온라인 (A/B) CTR, CVR 실제 클릭·구매 전환율 변화
온라인 (A/B) Revenue per User 유저당 매출 변화
다양성 Intra-list Diversity 추천 목록 내 카테고리 다양성

8. 회고

DuckDB의 실용성

pandas로 메모리에 올리기 버거운 대용량 로그를 SQL로 집계하는 패턴이 얼마나 강력한지 체감했습니다. PIVOT, LAG, 윈도우 함수를 활용해 복잡한 집계를 단 몇 줄로 처리할 수 있었고, 결과만 pandas로 가져오는 방식이 실무에서도 매우 유용하겠다고 느꼈습니다.

가설 주도 EDA의 효과

"데이터를 탐색한다"는 접근보다 "이 가설이 맞는지 검증한다"는 접근이 분석의 방향성을 훨씬 명확하게 만들어줬습니다. 11개 가설을 먼저 세우고 데이터로 검증하는 과정에서, 인사이트가 Task #2 추천 시스템 설계에 자연스럽게 연결되는 흐름이 만들어졌습니다.

불완전한 데이터에서의 설계 원칙

1일치 로그만으로는 장기 선호도 변화나 계절성을 반영할 수 없고, session_id 부재로 정확한 세션 분석이 제한됩니다. 완벽한 데이터가 갖춰지지 않은 상황에서도 합리적인 근사(30분 윈도우 세션)와 명확한 한계 명시가 중요하다는 것을 배웠습니다.


About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published