Session 01 · Part 1 · 09:30 – 12:00

Wine Quality
EBM + DiCE 실습

와인 품질의 원인을 찾고, 레시피를 만들어라

🍷 150분 · 전체 2/5 UCI Wine Quality · 1,599 샘플

Part 1 — Wine Quality EBM + DiCE

11개 화학 성분이 와인 품질을 어떻게 결정하는지 EBM으로 해석하고, DiCE로 개선 레시피를 만듭니다.

Part 1-A · EDA
09:30 – 10:15 · 45분 · Wine 데이터 탐색 & 피처 이해

학습 목표

  • UCI Wine Quality 데이터의 구조와 피처 의미를 파악한다
  • 품질 점수(0–10)를 low/mid/high 3구간으로 변환하고 클래스 불균형을 확인한다
  • train / validation / test 세트로 분리하고 로컬에 저장한다
"어떤 화학 성분이 와인 품질과 관련이 있는가? EBM 학습 전, 자신만의 가설을 먼저 기록해두자."
데이터셋정보
출처UCI Wine Quality — Red Wine
샘플 수1,599개
피처 수11개 (화학 성분)
타겟quality score (3–8) → low/mid/high
문제 유형분류 (3-class)
1 데이터 로드 & 기본 탐색 01_eda.ipynb

📋 복사해서 붙여넣기

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# ── 데이터 로드 (UCI 직접 또는 S3)
URL = "https://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-red.csv"
df = pd.read_csv(URL, sep=";")
print(f"shape: {df.shape}")
print(df.describe().round(2))
print("결측치:", df.isnull().sum().sum())

# ── 품질 분포 + 상관관계 히트맵
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
df["quality"].value_counts().sort_index().plot(
    kind="bar", ax=axes[0], color="#8B2252", edgecolor="white")
axes[0].set_title("Quality Score Distribution")
sns.heatmap(df.corr(), ax=axes[1], cmap="RdBu_r",
    center=0, annot=False, vmin=-1, vmax=1)
axes[1].set_title("Feature Correlation Heatmap")
plt.tight_layout(); plt.savefig("output/eda_overview.png", dpi=150)
plt.show()
ℹ️ 예상 출력 — quality score 분포: quality 5가 681개(최다), quality 8이 18개(최소). Class imbalance! "high" 클래스가 1%대라는 점을 EBM 학습 시 고려해야 합니다.
피처quality 상관계수방향
alcohol0.476↑ 높을수록 품질 ↑
volatile acidity-0.391↓ 낮을수록 품질 ↑
sulphates0.251↑ 높을수록 품질 ↑
citric acid0.226↑ 높을수록 품질 ↑
total sulfur dioxide-0.185↓ 낮을수록 품질 ↑
2 품질 구간화 & Train/Val/Test 분리 01_eda.ipynb

📋 복사해서 붙여넣기

from sklearn.model_selection import train_test_split

# ── low: ≤5 / mid: 6–7 / high: ≥8
def to_label(q):
    if q <= 5:    return "low"
    elif q <= 7:  return "mid"
    else:         return "high"

df["quality_label"] = df["quality"].apply(to_label)
print(df["quality_label"].value_counts())

# ── 피처 / 타겟 분리
FEATURES = [c for c in df.columns if c not in ["quality", "quality_label"]]
X = df[FEATURES]
y = df["quality_label"]

# ── 80/10/10 분리 (stratify로 클래스 비율 유지)
X_train, X_tmp, y_train, y_tmp = train_test_split(
    X, y, test_size=0.2, stratify=y, random_state=42)
X_val, X_test, y_val, y_test = train_test_split(
    X_tmp, y_tmp, test_size=0.5, stratify=y_tmp, random_state=42)

print(f"Train: {len(X_train)} | Val: {len(X_val)} | Test: {len(X_test)}")

Part 1-B · EBM 실습
10:15 – 11:15 · 60분 · shape function으로 와인 품질의 원인을 읽는다

EBM이란?

EBM(Explainable Boosting Machine)은 각 피처의 영향을 shape function이라는 곡선으로 표현합니다. 선형 회귀가 "alcohol +1 → 품질 +0.3 (직선)"인 반면, EBM은 "alcohol 8–9구간: 품질 영향 거의 없음, 10–11구간: 품질 크게 향상, 12+: 추가 효과 포화"처럼 구간별로 다른 패턴을 포착합니다.

3 EBM 학습 02_ebm.ipynb

📋 복사해서 붙여넣기

from interpret.glassbox import ExplainableBoostingClassifier
import pickle

# ── EBM 학습 (3-class 분류)
ebm = ExplainableBoostingClassifier(
    max_bins=256,
    interactions=3,
    outer_bags=8,
    random_state=42
)
ebm.fit(X_train, y_train)

# ── 검증 성능
from sklearn.metrics import accuracy_score, classification_report
y_pred = ebm.predict(X_val)
print(f"Val Accuracy: {accuracy_score(y_val, y_pred):.3f}")
print(classification_report(y_val, y_pred))

# ── 모델 저장
with open("output/ebm_wine.pkl", "wb") as f:
    pickle.dump(ebm, f)
print("✓ 모델 저장 완료")
4 Shape Function 시각화 & 해석 02_ebm.ipynb

📋 복사해서 붙여넣기

from interpret import show

# ── Global 해석 (feature importance + shape functions)
ebm_global = ebm.explain_global(name="Wine EBM")
show(ebm_global)

# ── 특정 피처 shape function 확인
# alcohol, volatile_acidity, sulphates 순서대로 확인하세요
ℹ️ Shape function 읽는 법: x축은 피처 값, y축은 그 값이 예측에 얼마나 플러스/마이너스 기여하는지를 나타냅니다. alcohol shape function에서 ~11% 이후 기여도가 빠르게 올라가는 임계점을 찾아보세요.
피처EBM에서의 패턴편의점 연결
alcohol도수 높을수록 품질 ↑, ~10.5% 이후 급증foot_traffic — 임계점 이후 매출 급증 구조
volatile acidity0.6 이상에서 품질 급락promo_sku_count — 과도한 행사 시 효과 감소
sulphates적정 구간에서 양의 기여, 이후 포화temperature — 기온 최적 구간 매출 효과
⚠️ Shape function ≠ 인과 그래프: EBM에서 alcohol이 중요하게 나와도 "알코올 함량을 높이면 품질이 오른다"고 단정할 수 없습니다. Shape function은 모델이 학습한 상관 영향 곡선입니다. 인과 주장은 DoWhy에서 검증해야 합니다.

Part 1-C · DiCE 실습
11:15 – 12:00 · 45분 · mid-quality 와인을 high-quality로 만드는 레시피 도출

DiCE란?

DiCE는 "이 와인이 high-quality가 되려면 어떤 성분이 얼마나 바뀌어야 하는가?"를 묻습니다. 반사실(counterfactual) 시나리오를 제시하되, 포도 품종처럼 바꿀 수 없는 요소(non-actionable)는 고정하고, 제조 단계에서 조절 가능한 요소(actionable)만 변경합니다.

5 DiCE 설정 & Counterfactual 생성 03_dice.ipynb

📋 복사해서 붙여넣기

import dice_ml
from dice_ml import Dice

# ── DiCE 데이터 객체 생성
d = dice_ml.Data(
    dataframe=pd.concat([X_train, y_train], axis=1),
    continuous_features=FEATURES,
    outcome_name="quality_label"
)

# ── DiCE 모델 연결 (EBM 사용)
m = dice_ml.Model(model=ebm, backend="sklearn")
exp = Dice(d, m, method="random")

# ── 분석 대상 샘플 선택 (mid-quality 1개)
mid_samples = X_val[y_val == "mid"].head(1)
print(mid_samples)
6 Actionable 피처 설정 & CF 생성 03_dice.ipynb

📋 복사해서 붙여넣기

# ── Actionable 피처: 제조 단계에서 조절 가능한 성분
ACTIONABLE = [
    "alcohol",           # 발효 조절로 변경 가능
    "sulphates",         # 첨가량 조절 가능
    "total sulfur dioxide",  # 이산화황 첨가량 조절
    "free sulfur dioxide",
    "citric acid",       # 첨가 조절 가능
]
# Non-actionable: density, fixed acidity (포도 품종 특성)

cf = exp.generate_counterfactuals(
    mid_samples,
    total_CFs=3,
    desired_class="high",
    features_to_vary=ACTIONABLE
)
cf.visualize_as_dataframe(show_only_changes=True)
ℹ️ CF 읽는 법: 변경된 값만 표시됩니다. alcohol 8.8 → 11.4 라면 "알코올 함량을 11.4%로 높이면 이 와인이 high-quality로 분류될 가능성이 높다"는 의미입니다. 단, 이것은 모델의 예측이지 인과 검증이 아닙니다.

Part 1 산출물 정리

산출물파일내용
EDA 시각화output/eda_overview.png품질 분포 + 상관관계 히트맵
EBM 모델output/ebm_wine.pkl학습된 EBM 분류기
Shape functioninterpretML show() 브라우저 출력alcohol, volatile acidity, sulphates Top 3 shape function
DiCE CF브라우저 출력 / 스크린샷"medium → high 전환 레시피" 상위 3 시나리오
Part 1 핵심 인사이트: alcohol과 volatile acidity가 와인 품질의 핵심 드라이버입니다. DiCE 시나리오에서 alcohol ↑ + volatile acidity ↓ + sulphates ↑ 조합이 공통적으로 등장합니다. 이 조합을 편의점 "프로모션 + 발주 패턴" 최적화에 그대로 응용할 수 있습니다.

Part 1 완료 후 Session 02 (Housing S3 + EBM + DoWhy)로 이동합니다. 점심 식사 후 13:00에 시작됩니다.