Spaces:
Runtime error
Runtime error
Antigravity Deploy Agent
Deploy Suicide Risk Detection web application to Hugging Face Spaces
0be18fb | import os | |
| import joblib | |
| import numpy as np | |
| import pandas as pd | |
| from sklearn.compose import ColumnTransformer | |
| from sklearn.impute import SimpleImputer | |
| from sklearn.mixture import GaussianMixture | |
| from sklearn.model_selection import train_test_split | |
| from sklearn.neighbors import KernelDensity | |
| from sklearn.pipeline import Pipeline | |
| from sklearn.preprocessing import OneHotEncoder | |
| def train_profile_brain( | |
| processed_dir: str = "data/processed", | |
| artifacts_dir: str = "outputs/artifacts", | |
| out_csv_name: str = "bd_profile_with_risk.csv", | |
| seed: int = 42, | |
| gmm_components: int = 3, | |
| kde_bandwidth: float = 2.0, | |
| w_gmm: float = 0.6, | |
| w_kde: float = 0.4, | |
| ): | |
| """ | |
| Branch 2 — Profile Brain (Density Estimation) | |
| - Loads structured profile dataset (no labels required) | |
| - Preprocesses numeric + categorical features (impute + one-hot) | |
| - Fits GMM + KDE on training split (unsupervised) | |
| - Calibrates log-likelihood to risk score using calibration split percentiles | |
| - Produces final profile risk = w_gmm*risk_gmm + w_kde*risk_kde | |
| - Writes an output CSV with risk columns and saves artifacts | |
| """ | |
| os.makedirs(artifacts_dir, exist_ok=True) | |
| bd_path = os.path.join(processed_dir, "bd_suicide_clean_structured.csv") | |
| bd = pd.read_csv(bd_path) | |
| # Keep a raw copy (unsupervised) | |
| X_raw = bd.copy() | |
| # Split numeric vs categorical | |
| num_cols = [c for c in X_raw.columns if pd.api.types.is_numeric_dtype(X_raw[c])] | |
| cat_cols = [c for c in X_raw.columns if c not in num_cols] | |
| print("Numeric:", num_cols) | |
| print("Categorical:", cat_cols) | |
| # ---------------- Preprocessor ---------------- | |
| # IMPORTANT: make OneHotEncoder output dense to satisfy GMM/KDE requirements | |
| # sklearn >= 1.2 uses sparse_output; older versions use sparse | |
| try: | |
| oh = OneHotEncoder(handle_unknown="ignore", sparse_output=False) | |
| except TypeError: | |
| oh = OneHotEncoder(handle_unknown="ignore", sparse=False) | |
| pre = ColumnTransformer( | |
| transformers=[ | |
| ("num", Pipeline([("imp", SimpleImputer(strategy="median"))]), num_cols), | |
| ( | |
| "cat", | |
| Pipeline( | |
| [ | |
| ("imp", SimpleImputer(strategy="most_frequent")), | |
| ("oh", oh), | |
| ] | |
| ), | |
| cat_cols, | |
| ), | |
| ], | |
| remainder="drop", | |
| ) | |
| # Train / calibration split (unsupervised) | |
| X_train_raw, X_cal_raw = train_test_split(X_raw, test_size=0.3, random_state=seed) | |
| X_train = pre.fit_transform(X_train_raw) | |
| X_cal = pre.transform(X_cal_raw) | |
| # Ensure numeric dtype (helps stability / speed) | |
| X_train = np.asarray(X_train, dtype=np.float32) | |
| X_cal = np.asarray(X_cal, dtype=np.float32) | |
| print("X_train:", X_train.shape, "X_cal:", X_cal.shape) | |
| # ---------------- GMM ---------------- | |
| gmm = GaussianMixture( | |
| n_components=gmm_components, | |
| covariance_type="diag", | |
| reg_covar=1e-3, | |
| random_state=seed, | |
| ) | |
| gmm.fit(X_train) | |
| # Calibration: map log-likelihood to risk via percentile scaling | |
| cal_ll = gmm.score_samples(X_cal) | |
| low, high = np.percentile(cal_ll, [5, 95]) | |
| def ll_to_risk(ll: np.ndarray) -> np.ndarray: | |
| # Higher LL => more "normal" => lower risk | |
| norm = np.clip((ll - low) / (high - low + 1e-9), 0.0, 1.0) | |
| return 1.0 - norm | |
| X_all = pre.transform(X_raw) | |
| X_all = np.asarray(X_all, dtype=np.float32) | |
| all_ll = gmm.score_samples(X_all) | |
| bd["risk_gmm"] = ll_to_risk(all_ll) | |
| # ---------------- KDE ---------------- | |
| kde = KernelDensity(kernel="gaussian", bandwidth=kde_bandwidth) | |
| kde.fit(X_train) | |
| cal_ll_kde = kde.score_samples(X_cal) | |
| low_kde, high_kde = np.percentile(cal_ll_kde, [5, 95]) | |
| def ll_to_risk_kde(ll: np.ndarray) -> np.ndarray: | |
| norm = np.clip((ll - low_kde) / (high_kde - low_kde + 1e-9), 0.0, 1.0) | |
| return 1.0 - norm | |
| all_ll_kde = kde.score_samples(X_all) | |
| bd["risk_kde"] = ll_to_risk_kde(all_ll_kde) | |
| # ---------------- Final Profile Risk ---------------- | |
| bd["risk_profile"] = w_gmm * bd["risk_gmm"] + w_kde * bd["risk_kde"] | |
| # Risk levels by percentiles (dataset-relative) | |
| p70, p90 = np.percentile(bd["risk_profile"], [70, 90]) | |
| def risk_to_level(r: float) -> str: | |
| if r >= p90: | |
| return "High" | |
| if r >= p70: | |
| return "Medium" | |
| return "Low" | |
| bd["risk_level_profile"] = bd["risk_profile"].apply(risk_to_level) | |
| # ---------------- Save CSV ---------------- | |
| out_csv_path = os.path.join(processed_dir, out_csv_name) | |
| bd.to_csv(out_csv_path, index=False) | |
| print("✅ Saved:", out_csv_path) | |
| # ---------------- Save Artifacts ---------------- | |
| joblib.dump(pre, os.path.join(artifacts_dir, "profile_preprocessor.joblib")) | |
| joblib.dump(gmm, os.path.join(artifacts_dir, "gmm.joblib")) | |
| joblib.dump(kde, os.path.join(artifacts_dir, "kde.joblib")) | |
| meta = { | |
| "gmm_components": int(gmm_components), | |
| "kde_bandwidth": float(kde_bandwidth), | |
| "w_gmm": float(w_gmm), | |
| "w_kde": float(w_kde), | |
| "p70": float(p70), | |
| "p90": float(p90), | |
| "calibration_ll_percentiles_gmm": [float(low), float(high)], | |
| "calibration_ll_percentiles_kde": [float(low_kde), float(high_kde)], | |
| "num_features_after_preprocess": int(X_train.shape[1]), | |
| "train_rows": int(X_train.shape[0]), | |
| "calibration_rows": int(X_cal.shape[0]), | |
| } | |
| joblib.dump(meta, os.path.join(artifacts_dir, "profile_risk_meta.joblib")) | |
| return {"out_csv": out_csv_path, "meta": meta} | |