Conversation
There was a problem hiding this comment.
안녕하세요 성은님. 코드 리뷰 남깁니다. 올려주신 내용 잘 확인했습니다!
시뮬레이션 데이터 생성하고 최적 정책을 시각화하는 부분을 깔끔하게 구현해주셨네요. 한눈에 이해하기 쉬운 그래프로 표현된 것 같습니다.
현재 코드에서 특별히 변경할 부분은 없습니다. 말씀하신대로 이 PR은 AIPW 점수 계산을 위한 좋은 준비 단계라고 생각합니다. 이어서 AIPW 점수를 계산하는 로직을 이 PR에 추가로 커밋해서 올려주시면 좋을 것 같습니다. 감사합니다.
|
안녕하세요, 성은님.
필수적인 수정 사항은 아니니, 시간 되실 때 가볍게 검토해보시면 좋을 것 같습니다. |
|
일단 aipw score 계산까지 구현했는데, 주신 코멘트는 다음번에 좀 더 반영해보겠습니다! |
|
안녕하세요 성은님. 코드 리뷰 남깁니다. 올려주신 내용 잘 확인했습니다! 미팅 이후 참고자료의 결과와 동일하게 나왔는지 검토하였습니다.
AIPW 정책 비교 (Difference Estimate)
이렇게 결과 차이가 나는 이유로, R 코드는 grf라는 인과추론 전용 패키지의 causal_forest 함수를 사용했는데요, 이 함수는 CATE 추정에 추가 알고리즘(Honesty, Debiasing)이 사용된 걸로 알고 있습니다. 다만, 작성해주신 Python 코드는 scikit-learn의 범용 RandomForestRegressor를 조합하여 CausalForest 클래스를 사용해서 그런 것 같습니다. 따라서, CausalForest 클래스에 대해서도 추가 설명하고 결과가 원본링크와 왜 차이 나는지에 대해서 노트로 명시하면 될 듯합니다. 다음주에 노트 적어주신 거 확인 후에 close 하도록 하겠습니다. |
There was a problem hiding this comment.
코드 작성하시느라 정말 고생하셨습니다!
덕분에 policy learning의 구현 흐름을 코드 차원에서 이해할 수 있었습니다.
추가로 몇 가지 코멘트를 드립니다.
-
Markdown cell을 활용해 목차를 구분하면 코드의 가독성과 논리 흐름이 더욱 명확해질 것 같습니다.
-
이번 분석은 이전의 가상 데이터셋이 아니라, 토픽 발표 때 사용하셨던 실제 데이터를 활용하신 게 맞을까요? 그렇다면 실제 데이터 기반 분석으로 별도 페이지로 분리하여 정리하는 것도 좋을 것 같습니다!
| "\"\"\"\n", | ||
| "================================================================================\n", | ||
| "R vs Python 구현 차이\n", | ||
| "================================================================================\n", | ||
| "\n", | ||
| "1. AIPW 정책 가치 (Value Estimate)\n", | ||
| "- R 코드 (V2): 0.3459015997 (약 34.6%) -> R의 추정치가 단순 평균에 더 가깝게 나옴\n", | ||
| "- Python 코드 (V2): 0.3376925438 (약 33.8%)\n", | ||
| "\n", | ||
| "2. AIPW 정책 비교 (Difference Estimate)\n", | ||
| "- R 코드 (V2): 0.0806035592 (약 8.1%p)\n", | ||
| "- Python 코드 (V2): 0.0721973740 (약 7.2%p)\n", | ||
| "\n", | ||
| "차이가 발생하는 이유:\n", | ||
| "----------------------------\n", | ||
| "1. 알고리즘 차이\n", | ||
| " - R(grf): Honest splitting, debiasing, CATE 전용 트리 알고리즘 사용\n", | ||
| " - Python(scikit-learn): 일반 RandomForest 기반, T-learner 사용, debiasing 없음\n", | ||
| "\n", | ||
| "2. 패키지 한계\n", | ||
| " - grf (R): 인과추론 전용 패키지\n", | ||
| " - scikit-learn / econml (Python): 일반 ML 기반, 구현 방식 상이\n", | ||
| "\n", | ||
| "================================================================================\n", | ||
| "\"\"\"" |
There was a problem hiding this comment.
초반에 연구 주제와 데이터셋에 대한 설명이 함께 들어가면 전체 흐름을 이해하는 데 더 도움이 될 것 같습니다!
| "# ==============================================================================\n", | ||
| "# STEP 3: CAUSAL FOREST WITH AIPW\n", | ||
| "# ==============================================================================\n", | ||
| "\n", | ||
| "print(\"=\" * 70)\n", | ||
| "print(\"CAUSAL FOREST WITH AIPW\")\n", | ||
| "print(\"=\" * 70)\n", | ||
| "\n", | ||
| "# Create model matrix (design matrix with intercept)\n", | ||
| "X_design = pd.get_dummies(data[covariates], drop_first=False)\n", | ||
| "# Add intercept\n", | ||
| "X_design.insert(0, 'intercept', 1)\n", | ||
| "X_design = X_design.values\n", | ||
| "\n", | ||
| "Y = data[outcome].values\n", | ||
| "W = data[treatment].values\n", | ||
| "\n", | ||
| "# Try to use sklearn if available\n", | ||
| "try:\n", | ||
| " from sklearn.ensemble import RandomForestRegressor, RandomForestClassifier\n", | ||
| " use_sklearn = True\n", | ||
| " print(\"Using scikit-learn for Random Forest\")\n", | ||
| "except ImportError:\n", | ||
| " use_sklearn = False\n", | ||
| " print(\"scikit-learn not found. Using simplified implementation.\")\n", | ||
| " print(\"For exact replication of R results, install scikit-learn: pip install scikit-learn\")\n", | ||
| "\n", | ||
| "# Causal Forest Implementation\n", | ||
| "if use_sklearn:\n", | ||
| " class CausalForest:\n", | ||
| " \"\"\"Causal Forest implementation matching grf package behavior\"\"\"\n", | ||
| " \n", | ||
| " def __init__(self, n_estimators=2000, max_features=None, min_samples_leaf=5, \n", | ||
| " W_hat=None, honest=True):\n", | ||
| " self.n_estimators = n_estimators\n", | ||
| " self.max_features = max_features if max_features else 'sqrt'\n", | ||
| " self.min_samples_leaf = min_samples_leaf\n", | ||
| " self.W_hat_fixed = W_hat\n", | ||
| " self.honest = honest\n", | ||
| " \n", | ||
| " def fit(self, X, Y, W):\n", | ||
| " n = len(Y)\n", | ||
| " \n", | ||
| " # If W.hat is provided (randomized setting), use it\n", | ||
| " if self.W_hat_fixed is not None:\n", | ||
| " self.W_hat = np.full(n, self.W_hat_fixed)\n", | ||
| " else:\n", | ||
| " # Estimate propensity score\n", | ||
| " ps_model = RandomForestClassifier(\n", | ||
| " n_estimators=500,\n", | ||
| " max_features=self.max_features,\n", | ||
| " min_samples_leaf=self.min_samples_leaf,\n", | ||
| " random_state=42,\n", | ||
| " n_jobs=-1\n", | ||
| " )\n", | ||
| " ps_model.fit(X, W)\n", | ||
| " self.W_hat = ps_model.predict_proba(X)[:, 1]\n", | ||
| " self.W_hat = np.clip(self.W_hat, 0.01, 0.99)\n", | ||
| " \n", | ||
| " # Estimate outcome model\n", | ||
| " outcome_model = RandomForestRegressor(\n", | ||
| " n_estimators=500,\n", | ||
| " max_features=self.max_features,\n", | ||
| " min_samples_leaf=self.min_samples_leaf,\n", | ||
| " random_state=42,\n", | ||
| " n_jobs=-1\n", | ||
| " )\n", | ||
| " outcome_model.fit(X, Y)\n", | ||
| " self.Y_hat = outcome_model.predict(X)\n", | ||
| " \n", | ||
| " # T-learner for treatment effects\n", | ||
| " model_1 = RandomForestRegressor(\n", | ||
| " n_estimators=1000,\n", | ||
| " max_features=self.max_features,\n", | ||
| " min_samples_leaf=self.min_samples_leaf,\n", | ||
| " random_state=42,\n", | ||
| " n_jobs=-1\n", | ||
| " )\n", | ||
| " model_0 = RandomForestRegressor(\n", | ||
| " n_estimators=1000,\n", | ||
| " max_features=self.max_features,\n", | ||
| " min_samples_leaf=self.min_samples_leaf,\n", | ||
| " random_state=42,\n", | ||
| " n_jobs=-1\n", | ||
| " )\n", | ||
| " \n", | ||
| " # Fit separate models for treated and control\n", | ||
| " if np.sum(W == 1) > 0:\n", | ||
| " model_1.fit(X[W == 1], Y[W == 1])\n", | ||
| " self.mu_1 = model_1.predict(X)\n", | ||
| " else:\n", | ||
| " self.mu_1 = np.zeros(n)\n", | ||
| " \n", | ||
| " if np.sum(W == 0) > 0:\n", | ||
| " model_0.fit(X[W == 0], Y[W == 0])\n", | ||
| " self.mu_0 = model_0.predict(X)\n", | ||
| " else:\n", | ||
| " self.mu_0 = np.zeros(n)\n", | ||
| " \n", | ||
| " # Treatment effect\n", | ||
| " self.tau_hat = self.mu_1 - self.mu_0\n", | ||
| " \n", | ||
| " return self\n", | ||
| " \n", | ||
| " def predict(self):\n", | ||
| " return {'predictions': self.tau_hat}\n", | ||
| "else:\n", | ||
| " # Simplified implementation without sklearn\n", | ||
| " class CausalForest:\n", | ||
| " def __init__(self, n_estimators=100, W_hat=None, **kwargs):\n", | ||
| " self.n_estimators = min(n_estimators, 100)\n", | ||
| " self.W_hat_fixed = W_hat\n", | ||
| " \n", | ||
| " def fit(self, X, Y, W):\n", | ||
| " n = len(Y)\n", | ||
| " \n", | ||
| " if self.W_hat_fixed is not None:\n", | ||
| " self.W_hat = np.full(n, self.W_hat_fixed)\n", | ||
| " else:\n", | ||
| " self.W_hat = np.full(n, np.mean(W))\n", | ||
| " \n", | ||
| " self.Y_hat = np.full(n, np.mean(Y))\n", | ||
| " \n", | ||
| " if np.sum(W == 1) > 0:\n", | ||
| " self.mu_1 = np.full(n, np.mean(Y[W == 1]))\n", | ||
| " else:\n", | ||
| " self.mu_1 = np.full(n, np.mean(Y))\n", | ||
| " \n", | ||
| " if np.sum(W == 0) > 0:\n", | ||
| " self.mu_0 = np.full(n, np.mean(Y[W == 0]))\n", | ||
| " else:\n", | ||
| " self.mu_0 = np.full(n, np.mean(Y))\n", | ||
| " \n", | ||
| " self.tau_hat = self.mu_1 - self.mu_0\n", | ||
| " \n", | ||
| " return self\n", | ||
| " \n", | ||
| " def predict(self):\n", | ||
| " return {'predictions': self.tau_hat}\n", | ||
| "\n", | ||
| "# Estimate a causal forest\n", | ||
| "print(\"\\nFitting causal forest (randomized setting with W.hat=0.5)...\")\n", | ||
| "forest = CausalForest(n_estimators=2000 if use_sklearn else 100, W_hat=0.5)\n", | ||
| "forest.fit(X_design, Y, W)\n", | ||
| "\n", | ||
| "# Get predictions\n", | ||
| "tau_hat = forest.predict()['predictions']\n", | ||
| "\n", | ||
| "# Estimate outcome models for treated and control\n", | ||
| "mu_hat_1 = forest.Y_hat + (1 - forest.W_hat) * tau_hat # E[Y|X,W=1]\n", | ||
| "mu_hat_0 = forest.Y_hat - forest.W_hat * tau_hat # E[Y|X,W=0]\n", | ||
| "\n", | ||
| "# Compute AIPW scores\n", | ||
| "gamma_hat_1 = mu_hat_1 + W / forest.W_hat * (Y - mu_hat_1)\n", | ||
| "gamma_hat_0 = mu_hat_0 + (1 - W) / (1 - forest.W_hat) * (Y - mu_hat_0)\n", | ||
| "\n", | ||
| "print(\"Causal forest fitted successfully.\")\n", | ||
| "print()" |
There was a problem hiding this comment.
이번에 CausalForest를 직접 구현해주셔서 구조를 이해하는 데 큰 도움이 됐습니다! 다만 실무적으로는 econml의 CausalForestDML 같은 기존 라이브러리를 활용하면 코드가 훨씬 간결하고 유지보수에도 용이할 것 같아요.
연구 및 코드 리뷰 요약
|
No description provided.