AI_Challenge2026 / utils /dm_risk.py
SeoYun
Claude Opus 5.5
feat: DM ์ž…๋ ฅ์„ SHAP ์ƒ์œ„ 11๊ฐœ๋กœ ์ถ•์†Œ
f570757
Raw History Blame Contribute Delete
3.83 kB
"""DM ๊ฐœ์ธ๋ณ„ ์œ„ํ—˜์š”์ธ - local SHAP.
LLM_Report/utils/dm_risk.py์™€ ๊ณ„์‚ฐ ๋กœ์ง์€ ๊ฐ™๋‹ค(5-fold ์ฒดํฌํฌ์ธํŠธ ๊ฐ๊ฐ์—
๋Œ€ํ•ด KernelExplainer๋กœ local SHAP์„ ๊ตฌํ•˜๊ณ  ํ‰๊ท  - ์žฌํ•™์Šต ์—†์Œ). ๋‹ค๋ฅธ ์ ์€
๋ฐฐ๊ฒฝํ‘œ๋ณธ์„ ์›๋ณธ ๋ฐ์ดํ„ฐ์—์„œ ์‹ค์‹œ๊ฐ„์œผ๋กœ kmeans ์š”์•ฝํ•˜์ง€ ์•Š๊ณ 
WEB/scripts/precompute_artifacts.py๊ฐ€ ๋ฏธ๋ฆฌ ๋งŒ๋“ค์–ด๋‘” dm_shap_background.csv
(kmeans ํ•ฉ์„ฑ ๋Œ€ํ‘œ์  100๊ฐœ, ์‹ค์ œ ๊ฐœ์ธ ๋ ˆ์ฝ”๋“œ ์•„๋‹˜)๋ฅผ ๊ทธ๋Œ€๋กœ ์“ด๋‹ค๋Š” ๊ฒƒ -
์›๋ณธ ๋ฐ์ดํ„ฐ๋ฅผ Space์— ์•ˆ ์˜ฌ๋ฆฌ๊ธฐ ์œ„ํ•จ์ด์ž, ๋งค ์š”์ฒญ๋งˆ๋‹ค kmeans๋ฅผ ๋‹ค์‹œ ์•ˆ
๋Œ๋ ค๋„ ๋˜๋ฏ€๋กœ ๋” ๋น ๋ฅด๋‹ค(๋ฌด๋ฃŒ CPU์—์„œ ํŠนํžˆ ์ค‘์š”).
์ฒดํฌํฌ์ธํŠธ๋Š” PRS_DM์„ ๋„ฃ์–ด ํ•™์Šตํ•œ ๋ชจ๋ธ(ML/checkpoints/dm_prs)์ด๋‹ค. prs_value
(z ์ ์ˆ˜)๋ฅผ ์ฃผ๋ฉด PRS๋ฅผ mask=1๋กœ ๋„ฃ๊ณ  PRS_DM๋„ ํ•˜๋‚˜์˜ SHAP feature๋กœ ์„ค๋ช…ํ•œ๋‹ค.
None์ด๋ฉด PRS๋ฅผ [MASK]๋กœ ๋‘๊ณ  EHR feature๋งŒ ์„ค๋ช…ํ•œ๋‹ค(ํ•™์Šต ๋•Œ --prs-dropout์œผ๋กœ
[MASK]๋„ ํ•™์Šต๋ผ ์žˆ์–ด EHR๋งŒ์œผ๋กœ ์˜ˆ์ธก ๊ฐ€๋Šฅ).
"""
from pathlib import Path
import numpy as np
import pandas as pd
import shap
import torch
from models.mlp import MultimodalModel
from utils.shap_utils import build_predict_fn
def compute_dm_local_shap(
patient_row: dict,
features: list,
background_df: pd.DataFrame,
checkpoint_dir: str = "artifacts/checkpoints",
n_folds: int = 5,
prs_dim: int = 1,
device: str = "cpu",
top_k: int = 8,
prs_value: float = None,
prs_feature: str = "PRS_DM",
exclude_features: tuple = (),
) -> tuple:
"""๋ฐ˜ํ™˜: (importance_df(top_k, feature/patient_value/local_shap/direction),
ensemble_predicted_prob). 5๊ฐœ fold ์ฒดํฌํฌ์ธํŠธ ๊ฐ๊ฐ์œผ๋กœ ์ด ํ•œ ์‚ฌ๋žŒ์˜ local
SHAP์„ ๊ตฌํ•˜๊ณ  feature๋ณ„ ํ‰๊ท ์„ ์ตœ์ข…๊ฐ’์œผ๋กœ ์“ด๋‹ค(์˜ˆ์ธก ํ™•๋ฅ ๋„ 5๊ฐœ ๋ชจ๋ธ ํ‰๊ท ).
exclude_features: ์˜ˆ์ธก์—๋Š” ๊ทธ๋Œ€๋กœ ์“ฐ๋˜ ์„ค๋ช… ํ‘œ์—์„œ๋งŒ ๋บ„ feature - ์‚ฌ์šฉ์ž๊ฐ€
์ž…๋ ฅํ•˜์ง€ ์•Š์•„ ์ธ๊ตฌ ์ค‘๊ฐ„๊ฐ’์œผ๋กœ ์ฑ„์šด ํ•ญ๋ชฉ์€ ์ค‘๊ฐ„๊ฐ’์ด SHAP ๋ฐฐ๊ฒฝ ํ‰๊ท ๊ณผ ๋‹ฌ๋ผ
(์˜ˆ: ALT ์ค‘๊ฐ„๊ฐ’ 19 vs ํ‰๊ท  22.7) ์ž…๋ ฅํ•˜์ง€๋„ ์•Š์€ ๊ฐ’์— ๊ธฐ์—ฌ๋„๊ฐ€ ์ƒ๊ธฐ๋ฏ€๋กœ ๋บ€๋‹ค."""
prs_in_input = prs_value is not None
ehr_features = list(features)
patient_row = dict(patient_row)
if prs_in_input:
features = ehr_features + [prs_feature]
patient_row[prs_feature] = float(prs_value)
patient_vec = np.array([[patient_row[f] for f in features]], dtype=np.float32)
background = background_df[features].to_numpy(dtype=np.float32)
shap_values_per_fold, probs = [], []
for fold_id in range(n_folds):
checkpoint_path = Path(checkpoint_dir) / f"dm_fold{fold_id}.pt"
model = MultimodalModel(ehr_dim=len(ehr_features), prs_dim=prs_dim).to(device)
model.load_state_dict(torch.load(checkpoint_path, map_location=device))
model.eval()
predict_fn = build_predict_fn(
model, prs_dim=max(prs_dim, 1), device=device, apply_sigmoid=True, prs_in_input=prs_in_input
)
explainer = shap.KernelExplainer(predict_fn, background)
sv = np.asarray(explainer.shap_values(patient_vec)).reshape(1, len(features))
shap_values_per_fold.append(sv[0])
probs.append(float(predict_fn(patient_vec)[0]))
mean_shap = np.mean(shap_values_per_fold, axis=0)
ensemble_prob = float(np.mean(probs))
df = pd.DataFrame(
{"feature": features, "patient_value": [patient_row[f] for f in features], "local_shap": mean_shap}
)
df = df[~df["feature"].isin(exclude_features)]
df = df.assign(_abs=df["local_shap"].abs()).sort_values("_abs", ascending=False).drop(columns="_abs")
df = df.head(top_k).reset_index(drop=True)
df["direction"] = df["local_shap"].apply(lambda x: "๋‹น๋‡จ๋ณ‘ ์œ„ํ—˜ ์ฆ๊ฐ€" if x > 0 else "๋‹น๋‡จ๋ณ‘ ์œ„ํ—˜ ๊ฐ์†Œ")
return df, ensemble_prob