PromleeBlog
sitemap
aboutMe

posting thumbnail
ETA 거리 의도 피처 엔지니어링 전략 - XGBoost 추천시스템 3편
ETA Feature Engineering Strategy - XGBoost Recommendation System Part 3

📅

들어가기 전에 🔗

안녕하세요.
1편에서는 추천 문제를 랭킹으로 정의하고 라벨을 설계했습니다.
2편에서는 Station Place Edge 데이터셋을 만들고 정제 파이프라인을 구성했습니다.
이번 3편에서는 ETA 이동시간을 공정성 피처로 바꾸는 방법을 다룹니다.

실습 코드와 데이터셋은 GitHub 저장소↗에서 확인하실 수 있습니다.
시리즈 흐름
시리즈 흐름

이번 편 로드맵 🔗


피처란 무엇인가 🔗

머신러닝에서 피처는 모델이 판단에 사용하는 입력 정보입니다.
학습 데이터 한 줄은 보통 방과 후보 장소 조합으로 구성됩니다.
각 피처는 그 조합의 성질을 숫자나 범주로 표현합니다.
예를 들어 eta_mean은 평균 이동시간이고 eta_range는 참여자 간 이동시간 격차입니다.
purpose_match는 모임 목적과 장소 카테고리 일치 여부를 나타냅니다.

라벨과 피처의 차이 🔗

이번 실습에서는 행동 로그를 기반으로 만든 target_score를 라벨로 사용합니다.
그리고 ETA 통계량과 의도 정보를 피처로 사용합니다.

왜 ETA를 공정성 피처로 쓰는가 🔗

약속 장소 추천에서는 평균 이동시간만 보면 불공정이 가려질 수 있습니다.
예를 들어 세 명의 ETA가 15분 15분 45분이면 평균은 25분이라 좋아 보입니다.
하지만 한 명은 명확히 불리한 상태입니다.
그래서 ETA 분포 자체를 피처로 넣어야 체감 공정성을 모델이 학습할 수 있습니다.

이동비용 피처군 🔗

의도 피처군 🔗

공정성 피처가 필요한 이유
공정성 피처가 필요한 이유

학습 코드 핵심 로직 🔗

먼저 파이썬 학습 코드의 핵심 수식을 확인합니다.

후보 점수 생성 로직 🔗

ml/synthetic_generator_part3.py
eta_range = float(np.max(etas) - np.min(etas))
eta_mean = float(np.mean(etas))
bonus = _purpose_category_bonus(purpose, str(place.get("category", "")))
 
score = 1.2 * eta_range + 0.3 * eta_mean - 8.0 * bonus

라벨 합성 로직 🔗

ml/label_builder.py
click_score = weights.click if int(action.get("is_clicked", 0)) == 1 else 0.0
vote_score = weights.vote if int(action.get("is_voted", 0)) == 1 else 0.0
final_score = weights.final if int(action.get("is_final_selected", 0)) == 1 else 0.0
 
rating = float(action.get("rating", 0) or 0)
rating = max(0.0, min(5.0, rating))
feedback_score = (rating / 5.0) * weights.rating_max_bonus
 
return click_score + vote_score + final_score + feedback_score

ETA 피처 생성 로직 🔗

ml/feature_builder_part3.py
etas_np = np.array(etas, dtype=float)
 
eta_mean = float(np.mean(etas_np))
eta_std = float(np.std(etas_np))
eta_max = float(np.max(etas_np))
eta_min = float(np.min(etas_np))
eta_range = float(eta_max - eta_min)
eta_p90 = float(np.percentile(etas_np, 90))

NDCG 계산 로직 🔗

ml/evaluate_part3.py
def ndcg_at_k(y_true, y_score, group_ids, k):
    ndcgs = []
    for gid in np.unique(group_ids):
        m = group_ids == gid
        t = y_true[m]
        s = y_score[m]
 
        order = np.argsort(-s)
        ideal = np.argsort(-t)
 
        dcg = _dcg(t[order], k=k)
        idcg = _dcg(t[ideal], k=k)
        ndcgs.append(0.0 if idcg == 0.0 else dcg / idcg)
 
    return float(np.mean(ndcgs)) if ndcgs else 0.0
아래 스크립트 섹션에서는 이 코드를 어떤 순서로 실행하는지 연결해 설명합니다.

실습 순서 정리 🔗

실습은 환경 준비 -> 데이터 생성 -> 피처 생성 -> 평가 순서로 진행합니다.

운영체제별 실행 파일 🔗

Mac & Linux는 _mac_linux.sh로 끝나는 스크립트, Windows는 _windows.ps1로 끝나는 스크립트를 사용합니다.

실행 전 체크포인트 🔗


스크립트 내부 설명 🔗

1) 환경 준비 스크립트 🔗

scripts/setup_mac_linux.sh
#!/usr/bin/env bash
set -euo pipefail
 
python3 -m venv .venv-ml
source .venv-ml/bin/activate
 
python -m pip install --upgrade pip
pip install -r requirements-ml.txt
 
echo "Environment ready."

2) 데이터 생성과 라벨링 스크립트 🔗

scripts/generate_part3.sh
#!/usr/bin/env bash
set -euo pipefail
 
source .venv-ml/bin/activate
 
python ml/synthetic_generator_part3.py   --stations ml/out/stations.csv   --places ml/out/places.csv   --n-rooms 80   --seed 42   --out-rooms ml/rooms_part3.jsonl   --out-actions ml/synthetic_actions_part3.jsonl
 
python ml/label_builder.py   --input ml/synthetic_actions_part3.jsonl   --output ml/synthetic_actions_part3_labeled.jsonl

3) 피처 생성 스크립트 🔗

scripts/build_features_part3.sh
#!/usr/bin/env bash
set -euo pipefail
 
source .venv-ml/bin/activate
 
python ml/feature_builder_part3.py   --stations ml/out/stations.csv   --places ml/out/places.csv   --edges ml/out/edges.csv   --rooms ml/rooms_part3.jsonl   --labeled-actions ml/synthetic_actions_part3_labeled.jsonl   --out-dir ml/out_part3

4) 평가 스크립트 🔗

scripts/evaluate_part3.sh
#!/usr/bin/env bash
set -euo pipefail
 
source .venv-ml/bin/activate
 
python ml/evaluate_part3.py   --data ml/out_part3/train_ltr.csv   --feature-report ml/out_part3/feature_report.json   --out ml/out_part3/eval_report.json   --k 4

출력 예시와 해석 🔗

ml/out_part3/eval_report.json
{
  "models": {
    "no_eta": {
      "ndcg_at_k": 0.553259
    },
    "eta_mean_only": {
      "ndcg_at_k": 0.541776
    },
    "full_fairness": {
      "ndcg_at_k": 0.669059
    }
  }
}

해석 포인트 🔗


결론 🔗

이번 3편에서는 ETA를 평균값 하나가 아닌 분포로 다루어 공정성을 피처에 반영했습니다.
또한 목적 시간대 그룹크기 같은 의도 피처를 결합해 추천 품질을 안정적으로 끌어올리는 방법을 확인했습니다.
다음 4편에서는 이 피처 테이블을 기반으로 XGBoost 학습 파이프라인과 실패 시 fallback 설계를 다루겠습니다.

참고 🔗