콘텐츠로 이동
Data Prep
상세

Double Machine Learning (DML)

1. 개요

Double Machine Learning (DML)은 고차원 데이터에서 인과 효과(causal effect)를 추정하기 위한 프레임워큼. Chernozhukov et al. (2018)이 제안했으며, 머신러닝의 유연성을 활용하면서도 정규화 편향(regularization bias)을 제거하여 유효한 통계적 추론을 가능하게 함.

핵심 기여

구분 설명
문제 해결 ML 기반 인과 추정에서 발생하는 정규화 편향 제거
방법론 Neyman orthogonality + Cross-fitting
적용 분야 처리 효과 추정, 정책 평가, A/B 테스트 분석

2. 문제 정의

2.1 Partially Linear Model (PLM)

가장 기본적인 DML 설정인 부분 선형 모델을 고려함:

Y = theta_0 * D + g_0(X) + epsilon,    E[epsilon|D,X] = 0
D = m_0(X) + V,                        E[V|X] = 0

여기서: - Y: 결과 변수 (outcome) - D: 처리 변수 (treatment) - X: 공변량/교란변수 (covariates/confounders) - theta_0: 추정하고자 하는 인과 효과 파라미터 - g_0(X): 알 수 없는 nuisance function (Y에 대한 X의 효과) - m_0(X): 알 수 없는 nuisance function (D에 대한 X의 효과)

2.2 추정 목표

목표는 고차원 nuisance parameter eta_0 = (g_0, m_0)가 존재하는 상황에서 저차원 파라미터 theta_0에 대한 유효한 추론을 수행하는 것.


3. 기존 방법의 한계

3.1 단순 ML 접근법의 문제

직접적인 ML 적용은 정규화 편향을 유발함:

theta_hat = (1/n * sum(D_i^2))^(-1) * (1/n * sum(D_i * (Y_i - g_hat(X_i))))

이 추정량의 오차 분해:

sqrt(n) * (theta_hat - theta_0) = a + b

a = (1/n * sum(D_i^2))^(-1) * (1/n * sum(D_i * epsilon_i))  -- 정상 (점근 정규)
b = (1/n * sum(D_i^2))^(-1) * (1/n * sum(D_i * (g_0(X_i) - g_hat(X_i))))  -- 발산

b 항(정규화 편향)으로 인해 sqrt(n) 수렴 속도를 달성할 수 없음.

3.2 편향 발생 원인

원인 설명
정규화 편향 ML 모델의 정규화로 인한 느린 수렴
과적합 편향 동일 데이터로 nuisance 추정과 theta 추정 시 발생

4. DML의 핵심 아이디어

4.1 Neyman Orthogonality (직교화)

X의 효과를 D에서 제거하여 직교화된 회귀변수를 구성함:

V = D - m_0(X)    (직교화된 처리 변수)

직교 추정량:

theta_check = (1/n * sum(V_hat_i * D_i))^(-1) * (1/n * sum(V_hat_i * (Y_i - g_hat(X_i))))

오차 분해:

sqrt(n) * (theta_check - theta_0) = a* + b* + c*
  • a*: 점근 정규 분포
  • b*: (m_hat - m_0) * (g_hat - g_0)의 곱 형태로, 두 오차의 곱이므로 빠르게 소멸
  • c*: sample splitting으로 제거 가능

4.2 Cross-fitting

과적합 편향을 제거하기 위해 K-fold cross-fitting을 적용함:

1. 데이터를 K개 fold로 분할
2. 각 fold k에 대해:
   - 나머지 K-1개 fold로 g_hat, m_hat 학습
   - fold k에서 theta 추정에 사용
3. 모든 fold의 결과를 결합

5. 알고리즘

5.1 DML1 알고리즘

Input: 데이터 {(Y_i, D_i, X_i)}_{i=1}^n, ML 모델 M_g, M_m, fold 수 K

1. 데이터를 K개 fold {I_1, ..., I_K}로 분할

2. FOR k = 1 to K:
   - I_k^c = {1,...,n} \ I_k  (학습용)
   - g_hat_k = M_g.fit(X[I_k^c], Y[I_k^c] - theta_init * D[I_k^c])
   - m_hat_k = M_m.fit(X[I_k^c], D[I_k^c])

3. FOR k = 1 to K:
   - V_hat_k = D[I_k] - m_hat_k(X[I_k])
   - residual_k = Y[I_k] - g_hat_k(X[I_k])

4. theta_hat = (sum_k sum_{i in I_k} V_hat_i * D_i)^(-1) * 
               (sum_k sum_{i in I_k} V_hat_i * residual_i)

5. SE = sqrt(Var(psi) / n)  where psi is the influence function

Output: theta_hat, SE, confidence interval

5.2 Score Functions

IV-type Score:

psi(W; theta, eta) = (Y - l(X) - theta * (D - m(X))) * (D - m(X))

Partialling-out Score:

psi(W; theta, eta) = (Y - l(X) - theta * (D - m(X))) * (D - m(X))
where l(X) = E[Y|X]


6. Python 구현

6.1 DoubleML 라이브러리 사용

import numpy as np
from doubleml import DoubleMLData, DoubleMLPLR
from sklearn.ensemble import RandomForestRegressor
from lightgbm import LGBMRegressor

# 데이터 준비
# X: 공변량, Y: 결과, D: 처리변수
dml_data = DoubleMLData.from_arrays(x=X, y=Y, d=D)

# ML 모델 설정
ml_l = LGBMRegressor(n_estimators=300, learning_rate=0.1, verbose=-1)
ml_m = LGBMRegressor(n_estimators=200, learning_rate=0.1, verbose=-1)

# DML 모델 구성
dml_plr = DoubleMLPLR(
    obj_dml_data=dml_data,
    ml_l=ml_l,           # E[Y|X] 추정
    ml_m=ml_m,           # E[D|X] 추정 (propensity)
    n_folds=5,           # cross-fitting folds
    score='partialling out'
)

# 학습 및 추론
dml_plr.fit()

print(f"Causal Effect (theta): {dml_plr.coef[0]:.4f}")
print(f"Standard Error: {dml_plr.se[0]:.4f}")
print(f"95% CI: [{dml_plr.confint().iloc[0, 0]:.4f}, {dml_plr.confint().iloc[0, 1]:.4f}]")
print(f"p-value: {dml_plr.pval[0]:.4f}")

6.2 EconML 라이브러리 사용

from econml.dml import LinearDML, CausalForestDML
from sklearn.linear_model import LassoCV
from sklearn.ensemble import GradientBoostingRegressor

# Average Treatment Effect (ATE) 추정
linear_dml = LinearDML(
    model_y=GradientBoostingRegressor(n_estimators=100),
    model_t=GradientBoostingRegressor(n_estimators=100),
    cv=5,
    random_state=42
)

linear_dml.fit(Y, T=D, X=None, W=X)  # X=None for ATE, W=confounders

# ATE와 신뢰구간
ate = linear_dml.ate(X=None)
ate_ci = linear_dml.ate_interval(X=None, alpha=0.05)
print(f"ATE: {ate:.4f}, 95% CI: [{ate_ci[0]:.4f}, {ate_ci[1]:.4f}]")

6.3 직접 구현 (교육용)

import numpy as np
from sklearn.model_selection import KFold
from sklearn.ensemble import RandomForestRegressor

def double_ml(Y, D, X, n_folds=5, model_class=RandomForestRegressor):
    """
    Double Machine Learning for Partially Linear Model

    Parameters
    ----------
    Y : array-like, shape (n,)
        Outcome variable
    D : array-like, shape (n,)
        Treatment variable
    X : array-like, shape (n, p)
        Covariates/confounders
    n_folds : int
        Number of folds for cross-fitting
    model_class : class
        ML model class for nuisance estimation

    Returns
    -------
    theta : float
        Estimated causal effect
    se : float
        Standard error
    """
    n = len(Y)
    kf = KFold(n_splits=n_folds, shuffle=True, random_state=42)

    # 잔차 저장
    Y_residual = np.zeros(n)
    D_residual = np.zeros(n)

    # Cross-fitting
    for train_idx, test_idx in kf.split(X):
        # Nuisance 모델 학습
        model_y = model_class(n_estimators=100, random_state=42)
        model_d = model_class(n_estimators=100, random_state=42)

        model_y.fit(X[train_idx], Y[train_idx])
        model_d.fit(X[train_idx], D[train_idx])

        # 잔차 계산 (테스트 폴드)
        Y_residual[test_idx] = Y[test_idx] - model_y.predict(X[test_idx])
        D_residual[test_idx] = D[test_idx] - model_d.predict(X[test_idx])

    # theta 추정 (partialling-out)
    theta = np.sum(D_residual * Y_residual) / np.sum(D_residual * D_residual)

    # 표준오차 계산
    psi = (Y_residual - theta * D_residual) * D_residual
    var_theta = np.mean(psi**2) / (np.mean(D_residual**2)**2)
    se = np.sqrt(var_theta / n)

    return theta, se

# 사용 예시
theta, se = double_ml(Y, D, X, n_folds=5)
print(f"theta = {theta:.4f} (SE = {se:.4f})")
print(f"95% CI: [{theta - 1.96*se:.4f}, {theta + 1.96*se:.4f}]")

7. 확장: Heterogeneous Treatment Effects

7.1 CATE 추정

Conditional Average Treatment Effect (CATE)를 추정하려면 CausalForestDML 사용:

from econml.dml import CausalForestDML

# CATE 추정
cf_dml = CausalForestDML(
    model_y=GradientBoostingRegressor(n_estimators=100),
    model_t=GradientBoostingRegressor(n_estimators=100),
    n_estimators=1000,
    cv=5,
    random_state=42
)

cf_dml.fit(Y, T=D, X=X_effect, W=X_confounders)

# 개인별 처리 효과
cate = cf_dml.effect(X_effect)
cate_ci = cf_dml.effect_interval(X_effect, alpha=0.05)

8. 실무 적용 가이드

8.1 모델 선택 기준

상황 권장 ML 모델
고차원, 희소 Lasso, Elastic Net
비선형 관계 Random Forest, Gradient Boosting
대용량 데이터 LightGBM, XGBoost
복잡한 상호작용 Neural Networks

8.2 Hyperparameter 튜닝

from sklearn.model_selection import GridSearchCV

# Nuisance 모델 튜닝
param_grid = {
    'n_estimators': [100, 200, 300],
    'max_depth': [3, 5, 7],
    'learning_rate': [0.01, 0.1]
}

model = GridSearchCV(
    LGBMRegressor(verbose=-1),
    param_grid,
    cv=5,
    scoring='neg_mean_squared_error'
)

# 튜닝된 모델로 DML 적용

8.3 진단 및 검증

# Nuisance 모델 성능 확인
print("Nuisance Model Performance:")
print(f"  Y model R2: {dml_plr.nuisance_targets['ml_l'].r2_score:.4f}")
print(f"  D model R2: {dml_plr.nuisance_targets['ml_m'].r2_score:.4f}")

# Sensitivity Analysis (DoWhy 활용)
from dowhy import CausalModel

model = CausalModel(
    data=df,
    treatment='D',
    outcome='Y',
    common_causes=X_columns
)

# Refutation tests
refute_random = model.refute_estimate(
    identified_estimand, estimate,
    method_name="random_common_cause"
)

9. 한계 및 주의사항

한계 설명
관측되지 않은 교란변수 DML도 unconfoundedness 가정 필요
Nuisance 모델 성능 잘못된 nuisance 추정은 편향 유발
처리 변수의 변동 약한 도구변수 문제와 유사
작은 표본 점근적 결과이므로 충분한 표본 필요

10. 참고문헌

  1. Chernozhukov, V., Chetverikov, D., Demirer, M., Duflo, E., Hansen, C., Newey, W., & Robins, J. (2018). Double/debiased machine learning for treatment and structural parameters. The Econometrics Journal, 21(1), C1-C68. https://doi.org/10.1111/ectj.12097

  2. Robinson, P. M. (1988). Root-N-consistent semiparametric regression. Econometrica, 56(4), 931-954.

  3. DoubleML Documentation: https://docs.doubleml.org/

  4. EconML Documentation: https://econml.azurewebsites.net/

  5. PyWhy (DoWhy + EconML): https://www.pywhy.org/


Last Updated: 2026-02-03