Session 01 · Part 1 · 09:30 – 12:00
Wine Quality
EBM + DiCE 실습
와인 품질의 원인을 찾고, 레시피를 만들어라
Part 1 — Wine Quality EBM + DiCE
11개 화학 성분이 와인 품질을 어떻게 결정하는지 EBM으로 해석하고, DiCE로 개선 레시피를 만듭니다.
Part 1-A · EDA
학습 목표
- 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 상관계수 | 방향 |
|---|---|---|
| alcohol | 0.476 | ↑ 높을수록 품질 ↑ |
| volatile acidity | -0.391 | ↓ 낮을수록 품질 ↑ |
| sulphates | 0.251 | ↑ 높을수록 품질 ↑ |
| citric acid | 0.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 실습
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 acidity | 0.6 이상에서 품질 급락 | promo_sku_count — 과도한 행사 시 효과 감소 |
| sulphates | 적정 구간에서 양의 기여, 이후 포화 | temperature — 기온 최적 구간 매출 효과 |
⚠️ Shape function ≠ 인과 그래프: EBM에서 alcohol이 중요하게 나와도 "알코올 함량을 높이면 품질이 오른다"고 단정할 수 없습니다. Shape function은 모델이 학습한 상관 영향 곡선입니다. 인과 주장은 DoWhy에서 검증해야 합니다.
Part 1-C · DiCE 실습
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 function | interpretML 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에 시작됩니다.