{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":99552,"databundleVersionId":13851420,"sourceType":"competition"},{"sourceId":13246846,"sourceType":"datasetVersion","datasetId":8059640},{"sourceId":13354474,"sourceType":"datasetVersion","datasetId":8214516},{"sourceId":13375266,"sourceType":"datasetVersion","datasetId":8325535}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport os\nimport glob\nfrom sklearn.metrics import roc_auc_score\nfrom typing import List, Optional\nfrom scipy.optimize import minimize\nimport pickle\n\n\nimport pandas as pd\nimport numpy as np\nfrom sklearn.model_selection import StratifiedKFold\nfrom sklearn.metrics import roc_auc_score, accuracy_score\nimport lightgbm as lgb\nimport joblib\nfrom lightgbm import early_stopping\nimport xgboost as xgb\n\nfrom sklearn.neural_network import MLPClassifier\nfrom sklearn.metrics import roc_auc_score\nfrom sklearn.preprocessing import StandardScaler\n\nfrom sklearn.svm import SVC\nfrom sklearn.metrics import roc_auc_score\nfrom sklearn.preprocessing import StandardScaler\nfrom sklearn.inspection import permutation_importance\nfrom catboost import CatBoostClassifier, Pool\nfrom sklearn.preprocessing import LabelEncoder\nfrom xgboost.callback import EarlyStopping\n\nxgb.set_config(verbosity=0)","metadata":{"_uuid":"cd3e6d88-ae36-460f-97ff-e76d0cb7c1e9","_cell_guid":"36a7bb3b-c58b-4aca-9405-c277e348187a","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-10-14T00:04:50.455475Z","iopub.execute_input":"2025-10-14T00:04:50.45591Z","iopub.status.idle":"2025-10-14T00:04:50.465613Z","shell.execute_reply.started":"2025-10-14T00:04:50.45588Z","shell.execute_reply":"2025-10-14T00:04:50.464211Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for name in ['lgb', 'xgb', 'cat']:\n    if not os.path.exists(name):\n        os.mkdir(name)","metadata":{"_uuid":"52361c4d-6c99-4d4b-bb39-c30cf1f76ae4","_cell_guid":"ed691935-910e-4453-8a63-79ce947f71a3","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-10-14T00:04:50.468385Z","iopub.execute_input":"2025-10-14T00:04:50.46888Z","iopub.status.idle":"2025-10-14T00:04:50.492661Z","shell.execute_reply.started":"2025-10-14T00:04:50.468852Z","shell.execute_reply":"2025-10-14T00:04:50.491573Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def weighted_multilabel_auc(\n    y_true: np.ndarray,\n    y_scores: np.ndarray,\n    class_weights: Optional[List[float]] = None,\n) -> float:\n    \"\"\"Compute weighted AUC for multilabel classification.\n\n    Parameters:\n    -----------\n    y_true : np.ndarray of shape (n_samples, n_classes)\n        True binary labels (0 or 1) for each class\n    y_scores : np.ndarray of shape (n_samples, n_classes)\n        Target scores (probability estimates or decision values)\n    class_weights : array-like of shape (n_classes,), optional\n        Weights for each class. If None, uniform weights are used.\n        Weights will be normalized to sum to 1.\n\n    Returns:\n    --------\n    weighted_auc : float\n        The weighted average AUC\n\n    Raises:\n    -------\n    ValueError\n        If any class does not have both positive and negative samples\n    \"\"\"\n    y_true = np.asarray(y_true)\n    y_scores = np.asarray(y_scores)\n    n_classes = y_true.shape[1]\n\n    individual_aucs = roc_auc_score(y_true, y_scores, average=None)\n\n    if class_weights is None:  # Uniform weights\n        weights_array = np.ones(n_classes)\n    else:\n        weights_array = np.asarray(class_weights)\n\n    if len(weights_array) != n_classes:\n        raise ValueError(\n            f'Number of weights ({len(weights_array)}) must match '\n            f'number of classes ({n_classes})'\n        )\n\n    # Check for non-negative weights\n    if np.any(weights_array < 0):\n        raise ValueError('All class weights must be non-negative')\n\n    # Check that at least one weight is positive\n    if np.sum(weights_array) == 0:\n        raise ValueError('At least one class weight must be positive')\n\n    # Normalize weights to sum to 1\n    weights_array = weights_array / np.sum(weights_array)\n\n    # Compute weighted average\n    return np.sum(individual_aucs * weights_array)","metadata":{"_uuid":"d2f0253a-eed1-41eb-a6a3-8c94b43a45bc","_cell_guid":"39eb8c8b-3ec4-4b84-87f0-390cfa1c3537","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-10-14T00:04:50.493719Z","iopub.execute_input":"2025-10-14T00:04:50.494108Z","iopub.status.idle":"2025-10-14T00:04:50.516696Z","shell.execute_reply.started":"2025-10-14T00:04:50.494079Z","shell.execute_reply":"2025-10-14T00:04:50.515546Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport os\nimport glob\nfrom sklearn.metrics import roc_auc_score\nfrom typing import List, Optional\nfrom scipy.optimize import minimize\nimport pickle\n\n\nimport pandas as pd\nimport numpy as np\nfrom sklearn.model_selection import StratifiedKFold\nfrom sklearn.metrics import roc_auc_score, accuracy_score\nimport lightgbm as lgb\nimport joblib\nfrom lightgbm import early_stopping\nimport xgboost as xgb\n\nfrom sklearn.neural_network import MLPClassifier\nfrom sklearn.metrics import roc_auc_score\nfrom sklearn.preprocessing import StandardScaler\n\nfrom sklearn.svm import SVC\nfrom sklearn.metrics import roc_auc_score\nfrom sklearn.preprocessing import StandardScaler\nfrom sklearn.inspection import permutation_importance\nfrom catboost import CatBoostClassifier, Pool\nfrom sklearn.preprocessing import LabelEncoder\nfrom xgboost.callback import EarlyStopping\n\nxgb.set_config(verbosity=0)\nfor name in ['lgb', 'xgb', 'cat']:\n    if not os.path.exists(name):\n        os.mkdir(name)\ndef weighted_multilabel_auc(\n    y_true: np.ndarray,\n    y_scores: np.ndarray,\n    class_weights: Optional[List[float]] = None,\n) -> float:\n    \"\"\"Compute weighted AUC for multilabel classification.\n\n    Parameters:\n    -----------\n    y_true : np.ndarray of shape (n_samples, n_classes)\n        True binary labels (0 or 1) for each class\n    y_scores : np.ndarray of shape (n_samples, n_classes)\n        Target scores (probability estimates or decision values)\n    class_weights : array-like of shape (n_classes,), optional\n        Weights for each class. If None, uniform weights are used.\n        Weights will be normalized to sum to 1.\n\n    Returns:\n    --------\n    weighted_auc : float\n        The weighted average AUC\n\n    Raises:\n    -------\n    ValueError\n        If any class does not have both positive and negative samples\n    \"\"\"\n    y_true = np.asarray(y_true)\n    y_scores = np.asarray(y_scores)\n    n_classes = y_true.shape[1]\n\n    # Get AUC for each class\n    individual_aucs = roc_auc_score(y_true, y_scores, average=None)\n\n    # Handle weights\n    if class_weights is None:  # Uniform weights\n        weights_array = np.ones(n_classes)\n    else:\n        weights_array = np.asarray(class_weights)\n\n    # Check weight dimensions\n    if len(weights_array) != n_classes:\n        raise ValueError(\n            f'Number of weights ({len(weights_array)}) must match '\n            f'number of classes ({n_classes})'\n        )\n\n    # Check for non-negative weights\n    if np.any(weights_array < 0):\n        raise ValueError('All class weights must be non-negative')\n\n    # Check that at least one weight is positive\n    if np.sum(weights_array) == 0:\n        raise ValueError('At least one class weight must be positive')\n\n    # Normalize weights to sum to 1\n    weights_array = weights_array / np.sum(weights_array)\n\n    # Compute weighted average\n    return np.sum(individual_aucs * weights_array)\n\n\nLABEL_COLS = [\n    'Left Infraclinoid Internal Carotid Artery',\n    'Right Infraclinoid Internal Carotid Artery',\n    'Left Supraclinoid Internal Carotid Artery',\n    'Right Supraclinoid Internal Carotid Artery',\n    'Left Middle Cerebral Artery',\n    'Right Middle Cerebral Artery',\n    'Anterior Communicating Artery',\n    'Left Anterior Cerebral Artery',\n    'Right Anterior Cerebral Artery',\n    'Left Posterior Communicating Artery',\n    'Right Posterior Communicating Artery',\n    'Basilar Tip',\n    'Other Posterior Circulation'\n]\n\n# YOLO label mappings\nYOLO_LABELS_TO_IDX = {\n    'Anterior Communicating Artery': 0,\n    'Basilar Tip': 1,\n    'Left Anterior Cerebral Artery': 2,\n    'Left Infraclinoid Internal Carotid Artery': 3,\n    'Left Middle Cerebral Artery': 4,\n    'Left Posterior Communicating Artery': 5,\n    'Left Supraclinoid Internal Carotid Artery': 6,\n    'Other Posterior Circulation': 7,\n    'Right Anterior Cerebral Artery': 8,\n    'Right Infraclinoid Internal Carotid Artery': 9,\n    'Right Middle Cerebral Artery': 10,\n    'Right Posterior Communicating Artery': 11,\n    'Right Supraclinoid Internal Carotid Artery': 12\n}\n\nYOLO_INV_MAP = {v: k for k, v in YOLO_LABELS_TO_IDX.items()}\n\nYOLO_LABELS = sorted(list(YOLO_LABELS_TO_IDX.keys()))\n\nloc_cols = [f\"loc_prob_{i}\" for i in range(13)]\n\nfeature_cols = ['aneurysm_prob'] + loc_cols + LABEL_COLS +  ['Aneurysm Present'] + ['PatientAge', 'PatientSex']\nyolo2flayer_loc_cols = [f'loc_prob_{LABEL_COLS.index(YOLO_INV_MAP[int(loc_col[-1])])}' for loc_col in loc_cols]\n\nN = 14","metadata":{"_uuid":"c82dcc5d-9fe9-4f5d-8ef7-7c0f40fb6f10","_cell_guid":"06fbf1da-dbf0-42ed-9d4f-629f7a3126af","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-10-14T00:04:50.51799Z","iopub.execute_input":"2025-10-14T00:04:50.518349Z","iopub.status.idle":"2025-10-14T00:04:50.546705Z","shell.execute_reply.started":"2025-10-14T00:04:50.518323Z","shell.execute_reply":"2025-10-14T00:04:50.545755Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"list(enumerate(zip(YOLO_LABELS, yolo2flayer_loc_cols)))","metadata":{"_uuid":"9b0198fb-d914-45ca-aad6-e106f571812b","_cell_guid":"7a475348-4ab7-472f-b29f-d5229c27d2b5","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-10-14T00:04:50.641901Z","iopub.execute_input":"2025-10-14T00:04:50.642586Z","iopub.status.idle":"2025-10-14T00:04:50.653234Z","shell.execute_reply.started":"2025-10-14T00:04:50.642546Z","shell.execute_reply":"2025-10-14T00:04:50.651692Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_main = pd.read_csv('/kaggle/input/rsna-intracranial-aneurysm-detection/train.csv')","metadata":{"_uuid":"7b62ac5d-625c-4645-9ddc-2a06921c8cf6","_cell_guid":"e1c9b266-8fcb-4a14-b126-91a7abe4f8ee","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-10-14T00:04:50.655436Z","iopub.execute_input":"2025-10-14T00:04:50.655749Z","iopub.status.idle":"2025-10-14T00:04:50.690684Z","shell.execute_reply.started":"2025-10-14T00:04:50.655715Z","shell.execute_reply":"2025-10-14T00:04:50.689468Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Load data\ndf_flayer = pd.read_csv('/kaggle/input/iad-model/oof_df_cv7722_seg_aux.csv')\ndf_meta = df_main.copy()[['SeriesInstanceUID', 'PatientAge', 'PatientSex']]\n\n# Convert age to numeric\ndf_meta['PatientAge'] = df_meta['PatientAge'].astype('float32')\n\n# Encode PatientSex (e.g. M → 1, F → 0)\nle = LabelEncoder()\ndf_meta['PatientSex'] = le.fit_transform(df_meta['PatientSex'].astype(str))\n\n# Merge with your other dataframe if needed\ndf_merged = df_flayer.merge(df_meta, on='SeriesInstanceUID', how='left')\n\nprint(df_meta['PatientSex'].value_counts())\nprint(df_meta.head())\n\n# Save the LabelEncoder\nwith open('label_encoder_sex.pkl', 'wb') as f:\n    pickle.dump(le, f)","metadata":{"_uuid":"11d46274-fd07-458f-bdd9-ed5c7042fca1","_cell_guid":"1459bc30-98f2-4de0-9146-645a59b2ed02","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-10-14T00:04:50.691934Z","iopub.execute_input":"2025-10-14T00:04:50.692299Z","iopub.status.idle":"2025-10-14T00:04:50.731136Z","shell.execute_reply.started":"2025-10-14T00:04:50.69227Z","shell.execute_reply":"2025-10-14T00:04:50.730128Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"all_folds_yolo11m = {\n    \"fold0\": {\n        \"y11_yolo11m\": \"/kaggle/input/rsna-yolo-models/yolo-11m-2.5D_fold0\",\n    },\n    # TODO: CHANGE TO 2.5D WHEN FINISHED\n    \"fold1\": {\n       \"y11_yolo11m\": \"/kaggle/input/rsna-yolo-models/yolo-11m-2.5D_fold1\",\n    },\n    \"fold2\": {\n        \"y11_yolo11m\": \"/kaggle/input/rsna-yolo-models/yolo-11m-2.5D_fold22\",\n    },\n    \"fold3\": {\n        \"y11_yolo11m\": \"/kaggle/input/rsna-yolo-models/yolo-11m-2.5D_fold3\",\n    },\n    \"fold4\": {\n        \"y11_yolo11m\": \"/kaggle/input/rsna-yolo-models/yolo-11m-2.5D_fold4\",\n    },\n}\n\n\nall_folds_yolo_eff2s = {\n    \"fold0\": {\n        \"y11_effnetv_25d\": \"/kaggle/input/rsna-yolo-models/cv_effnetv2_s_drop_path_25d_fold0\",\n    },\n    \"fold1\": {\n        \"y11_effnetv_25d\": \"/kaggle/input/rsna-yolo-models/cv_effnetv2_s_drop_path_25d_fold1\",\n    },\n    \"fold2\": {\n        \"y11_effnetv_25d\": \"/kaggle/input/rsna-yolo-models/cv_effnetv2_s_drop_path_25d_fold2\",\n    },\n    \"fold3\": {\n        \"y11_effnetv_25d\": \"/kaggle/input/rsna-yolo-models/cv_effnetv2_s_drop_path_25d_fold3\",\n    },\n    \"fold4\": {\n        \"y11_effnetv_25d\": \"/kaggle/input/rsna-yolo-models/cv_effnetv2_s_drop_path_25d_fold4\",\n    },\n}\n\n\ndef get_yolo_oof_preds(all_folds):\n    results = {}\n    for fold, models in all_folds.items():\n        fold_results = {}\n        for model_name, base in models.items():\n            matches = glob.glob(f\"{base}/**/per_series_predictions.csv\", recursive=True)\n            fold_results[model_name] = matches[0] if matches else None\n        results[fold] = fold_results\n    \n    dfs = []\n    for fold, models in results.items():\n        for model_name, csv_path in models.items():\n            if csv_path:\n                df = pd.read_csv(csv_path)\n                df[\"fold_id\"] = fold\n                df[\"model\"] = model_name\n                dfs.append(df)\n    \n    if dfs:\n        all_df = pd.concat(dfs, ignore_index=True)\n        all_df.to_csv(\"all_folds_series_predictions.csv\", index=False)\n        print(f\"✅ Combined {len(dfs)} CSV files into all_folds_series_predictions.csv\")\n    else:\n        print(\"⚠️ No CSV files found.\")\n    return all_df","metadata":{"_uuid":"c348bdaa-e569-4e2e-8b22-9d1e37508a95","_cell_guid":"91774ad7-e163-4b0f-bb5e-07c85a3c95f7","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-10-14T00:04:50.732156Z","iopub.execute_input":"2025-10-14T00:04:50.73247Z","iopub.status.idle":"2025-10-14T00:04:50.742627Z","shell.execute_reply.started":"2025-10-14T00:04:50.732442Z","shell.execute_reply":"2025-10-14T00:04:50.741386Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"all_df_yolo11m = get_yolo_oof_preds(all_folds_yolo11m)\nall_df_yolo11m.rename(columns={loc_col: f'yolo11m_{loc_col}' for loc_col in loc_cols}, inplace=True)\nall_df_yolo11m.rename(columns={'aneurysm_prob': 'yolo11m_aneurysm_prob'}, inplace=True)\n\nall_df_yolo_eff2s = get_yolo_oof_preds(all_folds_yolo_eff2s)\nall_df_yolo_eff2s.rename(columns={loc_col: f'yolo_eff2s_{loc_col}' for loc_col in loc_cols}, inplace=True)\nall_df_yolo_eff2s.rename(columns={'aneurysm_prob': 'yolo_eff2s_aneurysm_prob'}, inplace=True)\n\nlabel_to_yolo_idx = {label: YOLO_LABELS_TO_IDX[label] for label in LABEL_COLS}\n\n\nyolo11m_loc_cols = [f'yolo11m_{loc_col}' for loc_col in loc_cols]\nyolo_eff2s_loc_cols = [f'yolo_eff2s_{loc_col}' for loc_col in loc_cols]\n\nyolo11m_loc_col_map = [f\"yolo11m_loc_prob_{label_to_yolo_idx[label]}\" for label in LABEL_COLS]\nyolo_eff2s_loc_col_map = [f\"yolo_eff2s_loc_prob_{label_to_yolo_idx[label]}\" for label in LABEL_COLS]\n\nfeature_cols = ['yolo11m_aneurysm_prob'] + yolo11m_loc_cols + ['yolo_eff2s_aneurysm_prob'] + yolo_eff2s_loc_cols + LABEL_COLS +  ['Aneurysm Present'] + ['PatientAge', 'PatientSex']\n\nprint(feature_cols)","metadata":{"_uuid":"124294c5-d461-4ca3-b09b-b3a433a1641d","_cell_guid":"a90b0f15-13e5-45ee-bc59-6f5e4e3681b1","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-10-14T00:04:50.746024Z","iopub.execute_input":"2025-10-14T00:04:50.74632Z","iopub.status.idle":"2025-10-14T00:04:51.48517Z","shell.execute_reply.started":"2025-10-14T00:04:50.746299Z","shell.execute_reply":"2025-10-14T00:04:51.483908Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"merged = pd.merge(all_df_yolo11m, all_df_yolo_eff2s, on=\"SeriesInstanceUID\", how=\"inner\")\nmerged = pd.merge(merged, df_flayer, on=\"SeriesInstanceUID\", how=\"inner\")\nmerged = pd.merge(merged, df_meta, on=\"SeriesInstanceUID\", how=\"inner\")\nmerged['fold_id'] = all_df_yolo11m['fold_id'].copy()\ndf_main = df_main.set_index('SeriesInstanceUID').loc[merged['SeriesInstanceUID']].reset_index()","metadata":{"_uuid":"d51c09b1-2624-48a1-9eef-33635d3b200f","_cell_guid":"0991eedf-4c72-4630-ba54-011acef0525e","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-10-14T00:04:51.486486Z","iopub.execute_input":"2025-10-14T00:04:51.486832Z","iopub.status.idle":"2025-10-14T00:04:51.523039Z","shell.execute_reply.started":"2025-10-14T00:04:51.486807Z","shell.execute_reply":"2025-10-14T00:04:51.521773Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_meta_multilabel_lgb(df):\n    \"\"\"\n    Multi-label LightGBM training: trains one binary classifier per label.\n    Computes OOF predictions and per-label AUROC.\n    \"\"\"\n    features = feature_cols\n    labels = LABEL_COLS + [\"Aneurysm Present\"]\n    X = df[features]\n    folds = df[\"fold_id\"]\n\n    oof_preds = pd.DataFrame(index=df.index, columns=labels, dtype=float)\n    feature_importances = pd.DataFrame()\n    feature_importances[\"feature\"] = features\n\n    unique_folds = folds.unique()\n    best_iterations = []\n\n    for label in labels:\n        print(f\"\\n=== Training for label: {label} ===\")\n        oof_preds_label = np.zeros(len(df))\n        for fold in unique_folds:\n            print(f\"\\n--- Fold {fold} ---\")\n            train_idx = df[df[\"fold_id\"] != fold].index\n            valid_idx = df[df[\"fold_id\"] == fold].index\n\n            X_train, X_valid = X.loc[train_idx], X.loc[valid_idx]\n            y_train, y_valid = df_main.loc[train_idx, label], df_main.loc[valid_idx, label]\n\n            model = lgb.LGBMClassifier(\n                objective=\"binary\",\n                metric=\"auc\",\n                max_depth=2,\n                learning_rate=0.02,\n                reg_lambda=0.0047,\n                n_estimators=2000,\n                random_state=0,\n                verbose=-1\n            )\n\n            model.fit(\n                X_train, y_train,\n                eval_set=[(X_valid, y_valid)],\n                eval_metric=\"auc\",\n                callbacks=[early_stopping(stopping_rounds=300, verbose=True)]\n            )\n\n            oof_preds_label[valid_idx] = model.predict_proba(X_valid)[:, 1]\n            feature_importances[f\"{label}_fold_{fold}\"] = model.feature_importances_\n            joblib.dump(model, f\"lgb/meta_classifier_{label}_fold_{fold}.pkl\")\n            best_iterations.append(model.best_iteration_)\n\n        oof_preds[label] = oof_preds_label\n        label_auroc = roc_auc_score(df_main[label[:]], oof_preds_label)\n        print(f\"=== OOF AUROC for {label}: {label_auroc:.4f} ===\")\n    cls_score = weighted_multilabel_auc(df_main[\"Aneurysm Present\"].values[:, None], oof_preds[\"Aneurysm Present\"].values[:, None])\n    loc_score = weighted_multilabel_auc(df_main[LABEL_COLS].values, oof_preds[LABEL_COLS].values)\n    score = 0.5 * cls_score + 0.5 * loc_score\n    print(f'kaggle score: {score}')\n\n    # Average feature importance across all folds/labels\n    fi_cols = [col for col in feature_importances.columns if col != \"feature\"]\n    feature_importances[\"average\"] = feature_importances[fi_cols].mean(axis=1)\n    feature_importances = feature_importances.sort_values(by=\"average\", ascending=False)\n\n    #print(\"\\nFeature Importances:\")\n    #print(feature_importances)\n\n    return oof_preds, feature_importances","metadata":{"_uuid":"f7de1484-e651-457f-b24f-ff166f33db02","_cell_guid":"1fc7a158-9e9c-47b4-bd49-c9a17165bf87","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-10-14T00:04:51.524724Z","iopub.execute_input":"2025-10-14T00:04:51.525121Z","iopub.status.idle":"2025-10-14T00:04:51.537079Z","shell.execute_reply.started":"2025-10-14T00:04:51.525089Z","shell.execute_reply":"2025-10-14T00:04:51.536199Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"oof_preds_lgb, feature_importances_lgb =  train_meta_multilabel_lgb(merged)","metadata":{"_uuid":"6ee018df-81fc-4c6a-9775-cf7a2afa3940","_cell_guid":"d8199459-a198-4af9-9e48-2ebff67cf68a","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-10-14T00:04:51.53791Z","iopub.execute_input":"2025-10-14T00:04:51.538226Z","iopub.status.idle":"2025-10-14T00:05:22.535545Z","shell.execute_reply.started":"2025-10-14T00:04:51.538203Z","shell.execute_reply":"2025-10-14T00:05:22.534735Z"},"_kg_hide-output":true,"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_meta_multilabel_xgb(df):\n    \"\"\"\n    Multi-label XGBoost training: trains one binary classifier per label.\n    Computes OOF predictions and per-label AUROC.\n    \"\"\"\n    features = feature_cols\n    labels = LABEL_COLS + [\"Aneurysm Present\"]\n    X = df[features]\n    folds = df[\"fold_id\"]\n\n    oof_preds = pd.DataFrame(index=df.index, columns=labels, dtype=float)\n    feature_importances = pd.DataFrame()\n    feature_importances[\"feature\"] = features\n\n    unique_folds = folds.unique()\n    best_iterations = []\n\n    for label in labels:\n        print(f\"\\n=== Training for label: {label} ===\")\n        oof_preds_label = np.zeros(len(df))\n\n        for fold in unique_folds:\n            print(f\"\\n--- Fold {fold} ---\")\n            train_idx = df[df[\"fold_id\"] != fold].index\n            valid_idx = df[df[\"fold_id\"] == fold].index\n\n            X_train, X_valid = X.loc[train_idx], X.loc[valid_idx]\n            y_train, y_valid = df_main.loc[train_idx, label], df_main.loc[valid_idx, label]\n\n            model = xgb.XGBClassifier(\n                objective=\"binary:logistic\",\n                eval_metric=\"auc\",\n                max_depth=2,\n                learning_rate=0.02,\n                reg_lambda=0.0047,\n                n_estimators=2000,\n                random_state=0,\n                #tree_method=\"hist\",       # faster for large datasets\n                n_jobs=-1,\n                verbosity=0\n            )\n\n            model.fit(\n                X_train, y_train,\n                eval_set=[(X_valid, y_valid)],\n                callbacks=[EarlyStopping(rounds=300, save_best=True)],\n                verbose = False\n            )\n\n            # predict using the best iteration\n            oof_preds_label[valid_idx] = model.predict_proba(X_valid, iteration_range=(0, model.best_iteration + 1))[:, 1]\n\n            # Save feature importance for each fold\n            feature_importances[f\"{label}_fold_{fold}\"] = model.feature_importances_\n\n            # Save model\n            joblib.dump(model, f\"xgb/meta_classifier_{label}_fold_{fold}.pkl\")\n            best_iterations.append(model.best_iteration)\n\n        oof_preds[label] = oof_preds_label\n        label_auroc = roc_auc_score(df_main[label], oof_preds_label)\n        print(f\"=== OOF AUROC for {label}: {label_auroc:.4f} ===\")\n\n    # Compute Kaggle-like score\n    cls_score = weighted_multilabel_auc(\n        df_main[\"Aneurysm Present\"].values[:, None],\n        oof_preds[\"Aneurysm Present\"].values[:, None]\n    )\n    loc_score = weighted_multilabel_auc(\n        df_main[LABEL_COLS].values,\n        oof_preds[LABEL_COLS].values\n    )\n    score = 0.5 * cls_score + 0.5 * loc_score\n    print(f'kaggle score: {score:.6f}')\n\n    # Average feature importance across all folds/labels\n    fi_cols = [col for col in feature_importances.columns if col != \"feature\"]\n    feature_importances[\"average\"] = feature_importances[fi_cols].mean(axis=1)\n    feature_importances = feature_importances.sort_values(by=\"average\", ascending=False)\n\n    return oof_preds, feature_importances","metadata":{"_uuid":"51a6f210-594a-4f15-aece-44b157da40dd","_cell_guid":"86cfffb0-bd14-44dd-9ac9-aa1bb2d36137","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-10-14T00:05:22.536617Z","iopub.execute_input":"2025-10-14T00:05:22.536913Z","iopub.status.idle":"2025-10-14T00:05:22.549495Z","shell.execute_reply.started":"2025-10-14T00:05:22.536881Z","shell.execute_reply":"2025-10-14T00:05:22.548522Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"oof_preds_xgb, feature_importances_xgb =  train_meta_multilabel_xgb(merged)","metadata":{"_uuid":"0c07eea0-e30c-4c33-9b81-ec9efa58a9ac","_cell_guid":"ebb370bf-d761-4da1-a9cf-b1be12c271b2","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-10-14T00:05:22.550919Z","iopub.execute_input":"2025-10-14T00:05:22.551373Z","iopub.status.idle":"2025-10-14T00:07:01.973124Z","shell.execute_reply.started":"2025-10-14T00:05:22.551337Z","shell.execute_reply":"2025-10-14T00:07:01.971412Z"},"_kg_hide-output":true,"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_meta_multilabel_catboost(df):\n    \"\"\"\n    Multi-label CatBoost training: trains one binary classifier per label.\n    Computes OOF predictions and per-label AUROC.\n    \"\"\"\n    features = feature_cols\n    labels = LABEL_COLS + [\"Aneurysm Present\"]\n    X = df[features]\n    folds = df[\"fold_id\"]\n\n    oof_preds = pd.DataFrame(index=df.index, columns=labels, dtype=float)\n    feature_importances = pd.DataFrame()\n    feature_importances[\"feature\"] = features\n\n    unique_folds = folds.unique()\n    best_iterations = []\n\n    # Shared CatBoost parameters\n    params = {\n        'loss_function': 'Logloss',\n        'eval_metric': 'AUC',\n        'depth': 6,\n        'learning_rate': 0.02,\n        'l2_leaf_reg': 4.7,\n        'iterations': 2000,\n        'random_seed': 0,\n        'bootstrap_type': 'Bayesian',\n        'bagging_temperature': 1.0,\n        'od_type': 'Iter',\n        'od_wait': 300,\n        'verbose': False\n    }\n\n    for label in labels:\n        print(f\"\\n=== Training for label: {label} ===\")\n        oof_preds_label = np.zeros(len(df))\n\n        for fold in unique_folds:\n            print(f\"\\n--- Fold {fold} ---\")\n            train_idx = df[df[\"fold_id\"] != fold].index\n            valid_idx = df[df[\"fold_id\"] == fold].index\n\n            X_train, X_valid = X.loc[train_idx], X.loc[valid_idx]\n            y_train, y_valid = df_main.loc[train_idx, label], df_main.loc[valid_idx, label]\n\n            train_pool = Pool(X_train, y_train)\n            valid_pool = Pool(X_valid, y_valid)\n\n            model = CatBoostClassifier(**params)\n\n            model.fit(\n                train_pool,\n                eval_set=valid_pool,\n                use_best_model=True\n            )\n\n            oof_preds_label[valid_idx] = model.predict_proba(X_valid)[:, 1]\n            feature_importances[f\"{label}_fold_{fold}\"] = model.get_feature_importance(train_pool)\n\n            joblib.dump(model, f\"cat/meta_classifier_{label}_fold_{fold}.pkl\")\n            best_iterations.append(model.get_best_iteration())\n\n        oof_preds[label] = oof_preds_label\n        label_auroc = roc_auc_score(df_main[label], oof_preds_label)\n        print(f\"=== OOF AUROC for {label}: {label_auroc:.4f} ===\")\n\n    # Combine aneurysm/global score\n    cls_score = weighted_multilabel_auc(\n        df_main[\"Aneurysm Present\"].values[:, None],\n        oof_preds[\"Aneurysm Present\"].values[:, None]\n    )\n    loc_score = weighted_multilabel_auc(\n        df_main[LABEL_COLS].values,\n        oof_preds[LABEL_COLS].values\n    )\n    score = 0.5 * cls_score + 0.5 * loc_score\n    print(f'kaggle score: {score:.6f}')\n\n    # Average feature importance across all folds and labels\n    fi_cols = [col for col in feature_importances.columns if col != \"feature\"]\n    feature_importances[\"average\"] = feature_importances[fi_cols].mean(axis=1)\n    feature_importances = feature_importances.sort_values(by=\"average\", ascending=False)\n\n    return oof_preds, feature_importances","metadata":{"_uuid":"5d8646ec-765d-4767-a82a-4140151f01a6","_cell_guid":"066ca8a1-2180-4d5a-bf9f-c22bce4d9612","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-10-14T00:07:01.975943Z","iopub.execute_input":"2025-10-14T00:07:01.976595Z","iopub.status.idle":"2025-10-14T00:07:01.999351Z","shell.execute_reply.started":"2025-10-14T00:07:01.976556Z","shell.execute_reply":"2025-10-14T00:07:01.997382Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"oof_preds_cat, feature_importances_cat =  train_meta_multilabel_catboost(merged)","metadata":{"_uuid":"029d08fb-000d-4d82-a40c-7b6b77a3945f","_cell_guid":"b06ce952-3c11-493a-8bb0-02b1e8f4689f","trusted":true,"collapsed":false,"_kg_hide-output":true,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-10-14T00:07:02.003646Z","iopub.execute_input":"2025-10-14T00:07:02.004104Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def compute_score(p_cls, p_loc):\n    return 0.5 * (p_cls + p_loc)\n\ngt_loc = df_main[LABEL_COLS]\npred_loc = oof_preds_lgb[LABEL_COLS] + oof_preds_xgb[LABEL_COLS] + oof_preds_cat[LABEL_COLS] + merged[LABEL_COLS] + merged[yolo11m_loc_col_map].values + merged[yolo_eff2s_loc_col_map].values\nscore_loc = weighted_multilabel_auc(gt_loc.values, pred_loc.values)\n\ngt_cls = df_main['Aneurysm Present']\npred_cls = oof_preds_lgb['Aneurysm Present'] + oof_preds_xgb['Aneurysm Present'] + oof_preds_cat['Aneurysm Present'] + merged['Aneurysm Present'] + merged['yolo11m_aneurysm_prob']  + merged['yolo_eff2s_aneurysm_prob']\nscore_cls = weighted_multilabel_auc(gt_cls.values[:, None], pred_cls.values[:, None])\ncompute_score(score_cls, score_loc)","metadata":{"_uuid":"f86fd506-63ba-45a8-b5c2-26a31b7e923c","_cell_guid":"4151354b-31a0-4b02-9421-df2b408da93f","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Prepare prediction arrays\npreds_loc_list = [\n    oof_preds_lgb[LABEL_COLS].values,\n    oof_preds_xgb[LABEL_COLS].values,\n    oof_preds_cat[LABEL_COLS].values,\n    merged[LABEL_COLS].values,\n    merged[yolo11m_loc_col_map].values,\n    merged[yolo_eff2s_loc_col_map].values\n]\n\npreds_cls_list = [\n    oof_preds_lgb['Aneurysm Present'].values,\n    oof_preds_xgb['Aneurysm Present'].values,\n    oof_preds_cat['Aneurysm Present'].values,\n    merged['Aneurysm Present'].values,\n    merged['yolo11m_aneurysm_prob'].values,\n    merged['yolo_eff2s_aneurysm_prob'].values\n]\n\ngt_loc_values = df_main[LABEL_COLS].values\ngt_cls_values = df_main['Aneurysm Present'].values[:, None]\n\n\n# Objective function to **minimize** (negative score)\ndef objective(weights):\n    # Normalize weights to sum 1 (optional)\n    weights = np.array(weights)\n    weights /= weights.sum()\n\n    # Weighted ensemble\n    pred_loc_weighted = sum(w * p for w, p in zip(weights, preds_loc_list))\n    pred_cls_weighted = sum(w * p for w, p in zip(weights, preds_cls_list))\n\n    # Compute individual scores\n    score_loc = weighted_multilabel_auc(gt_loc_values, pred_loc_weighted)\n    score_cls = weighted_multilabel_auc(gt_cls_values, pred_cls_weighted)\n\n    # Compute combined score\n    combined_score = compute_score(score_cls, score_loc)\n\n    # Return negative because we minimize\n    return -combined_score\n\n# Initial weights\ninit_weights = [1/len(preds_loc_list)] * len(preds_loc_list)\n\n# Run Nelder-Mead optimization\nresult = minimize(objective, init_weights, method='Nelder-Mead', options={'maxiter': 1000, 'disp': True})\n\n# Optimized weights\noptimized_weights = result.x / result.x.sum()\nprint(\"Optimized weights:\", optimized_weights)\nprint(\"Maximized score:\", -result.fun)","metadata":{"_uuid":"615ae856-bf35-4b53-995a-9e3ad35a2c41","_cell_guid":"a68bea6e-3dce-4275-af2d-e94a02fa4fd3","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def compute_auc_table():\n    results = []\n\n    # LGB\n    cls_auc_lgb = weighted_multilabel_auc(\n        df_main[\"Aneurysm Present\"].values[:, None],\n        oof_preds_lgb[\"Aneurysm Present\"].values[:, None]\n    )\n    loc_auc_lgb = weighted_multilabel_auc(\n        df_main[LABEL_COLS].values,\n        oof_preds_lgb[LABEL_COLS].values\n    )\n    results.append([\"LightGBM\", loc_auc_lgb, cls_auc_lgb, 0.5 * (loc_auc_lgb + cls_auc_lgb)])\n\n    # XGB\n    cls_auc_xgb = weighted_multilabel_auc(\n        df_main[\"Aneurysm Present\"].values[:, None],\n        oof_preds_xgb[\"Aneurysm Present\"].values[:, None]\n    )\n    loc_auc_xgb = weighted_multilabel_auc(\n        df_main[LABEL_COLS].values,\n        oof_preds_xgb[LABEL_COLS].values\n    )\n    results.append([\"XGBoost\", loc_auc_xgb, cls_auc_xgb, 0.5 * (loc_auc_xgb + cls_auc_xgb)])\n\n    # CatBoost\n    cls_auc_cat = weighted_multilabel_auc(\n        df_main[\"Aneurysm Present\"].values[:, None],\n        oof_preds_cat[\"Aneurysm Present\"].values[:, None]\n    )\n    loc_auc_cat = weighted_multilabel_auc(\n        df_main[LABEL_COLS].values,\n        oof_preds_cat[LABEL_COLS].values\n    )\n    results.append([\"CatBoost\", loc_auc_cat, cls_auc_cat, 0.5 * (loc_auc_cat + cls_auc_cat)])\n\n    # YOLO11m\n    cls_auc_y11m = weighted_multilabel_auc(\n        df_main[\"Aneurysm Present\"].values[:, None],\n        merged[\"yolo11m_aneurysm_prob\"].values[:, None]\n    )\n    loc_auc_y11m = weighted_multilabel_auc(\n        df_main[LABEL_COLS].values,\n        merged[yolo11m_loc_col_map].values\n    )\n    results.append([\"YOLO11m\", loc_auc_y11m, cls_auc_y11m, 0.5 * (loc_auc_y11m + cls_auc_y11m)])\n\n    # YOLO EfficientNetV2-S\n    cls_auc_eff2s = weighted_multilabel_auc(\n        df_main[\"Aneurysm Present\"].values[:, None],\n        merged[\"yolo_eff2s_aneurysm_prob\"].values[:, None]\n    )\n    loc_auc_eff2s = weighted_multilabel_auc(\n        df_main[LABEL_COLS].values,\n        merged[yolo_eff2s_loc_col_map].values\n    )\n    results.append([\"YOLO EffNetV2-S\", loc_auc_eff2s, cls_auc_eff2s, 0.5 * (loc_auc_eff2s + cls_auc_eff2s)])\n\n    # F-layer baseline (if available)\n    cls_auc_flayer = weighted_multilabel_auc(\n        df_main[\"Aneurysm Present\"].values[:, None],\n        merged[\"Aneurysm Present\"].values[:, None]\n    )\n    loc_auc_flayer = weighted_multilabel_auc(\n        df_main[LABEL_COLS].values,\n        merged[LABEL_COLS].values\n    )\n    results.append([\"F-layer Baseline\", loc_auc_flayer, cls_auc_flayer, 0.5 * (loc_auc_flayer + cls_auc_flayer)])\n\n    # Combine results\n    table = pd.DataFrame(results, columns=[\"Model\", \"Loc AUC\", \"Cls AUC\", \"Kaggle Score\"])\n    print(\"\\n=== AUC Summary Table ===\")\n    print(table.to_markdown(index=False))\n    return table\n\n# Call the summary function\nauc_table = compute_auc_table()","metadata":{"_uuid":"079d32cd-e25d-4597-a5a6-b8d55623f6a2","_cell_guid":"7d788cc8-7000-4ac9-82d9-f3a0f8fab5a9","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null}]}