-
Notifications
You must be signed in to change notification settings - Fork 5
policy aipw v1 #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
policy aipw v1 #23
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
안녕하세요 성은님. 코드 리뷰 남깁니다. 올려주신 내용 잘 확인했습니다!
시뮬레이션 데이터 생성하고 최적 정책을 시각화하는 부분을 깔끔하게 구현해주셨네요. 한눈에 이해하기 쉬운 그래프로 표현된 것 같습니다.
현재 코드에서 특별히 변경할 부분은 없습니다. 말씀하신대로 이 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
코드 작성하시느라 정말 고생하셨습니다!
덕분에 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
초반에 연구 주제와 데이터셋에 대한 설명이 함께 들어가면 전체 흐름을 이해하는 데 더 도움이 될 것 같습니다!
| "# ==============================================================================\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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이번에 CausalForest를 직접 구현해주셔서 구조를 이해하는 데 큰 도움이 됐습니다! 다만 실무적으로는 econml의 CausalForestDML 같은 기존 라이브러리를 활용하면 코드가 훨씬 간결하고 유지보수에도 용이할 것 같아요.
연구 및 코드 리뷰 요약
|
No description provided.