feat: 添加GPU检测与自动回退逻辑并移除冗余文档

- 在notebook中引入GPU检测逻辑,根据CUDA可用性自动选择XGBoost计算后端
- 更新XGBoost配置,使用动态变量替代硬编码的GPU参数
- 删除过时的需求分析文档,保持项目结构整洁
- 确保代码在不同硬件环境下均可正常运行
This commit is contained in:
2026-04-30 15:04:29 +08:00
parent ceddbdd559
commit 6ac02ba4fe
3 changed files with 4 additions and 954 deletions
@@ -44,9 +44,7 @@
"id": "a12f069a", "id": "a12f069a",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": "import xgboost as xgb\nimport optuna\noptuna.logging.set_verbosity(optuna.logging.WARNING)\n\n# GPU Fallback: 自动检测CUDA可用性,无GPU时自动切换到CPU\ntry:\n import subprocess\n result = subprocess.run(['nvidia-smi'], capture_output=True, text=True)\n USE_GPU = result.returncode == 0\nexcept:\n USE_GPU = False\n\nXGB_TREE_METHOD = 'gpu_hist' if USE_GPU else 'hist'\nXGB_DEVICE = 'cuda' if USE_GPU else 'cpu'\nprint(f'XGBoost compute method: {\"GPU (CUDA)\" if USE_GPU else \"CPU\"}')\n\nRANDOM_STATE = 42\nnp.random.seed(RANDOM_STATE)\nplt.rcParams['figure.figsize'] = (10, 6)\nplt.rcParams['font.size'] = 12\nsns.set_style('whitegrid')\nprint('All libraries imported successfully!')"
"import warnings\nwarnings.filterwarnings('ignore')\nimport os\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom sklearn.metrics import accuracy_score, f1_score, classification_report, confusion_matrix, ConfusionMatrixDisplay\nfrom sklearn.model_selection import cross_val_score\nfrom sklearn.preprocessing import StandardScaler, LabelEncoder\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.ensemble import RandomForestClassifier, VotingClassifier\nfrom sklearn.pipeline import Pipeline\nfrom sklearn.compose import ColumnTransformer\nfrom sklearn.preprocessing import OneHotEncoder\nfrom sklearn.impute import SimpleImputer\nfrom sklearn.cluster import KMeans\nfrom sklearn.mixture import GaussianMixture\nfrom sklearn.metrics import silhouette_score\nfrom sklearn.decomposition import PCA\nimport xgboost as xgb\nimport optuna\noptuna.logging.set_verbosity(optuna.logging.WARNING)\n\nRANDOM_STATE = 42\nnp.random.seed(RANDOM_STATE)\nplt.rcParams['figure.figsize'] = (10, 6)\nplt.rcParams['font.size'] = 12\nsns.set_style('whitegrid')\nprint('All libraries imported successfully!')"
]
}, },
{ {
"cell_type": "code", "cell_type": "code",
@@ -227,9 +225,7 @@
"id": "30cd02ce", "id": "30cd02ce",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": "print('Training Random Forest...')\nstart = time.time()\nrf_pipeline = Pipeline(steps=[\n ('preprocessor', preprocessor),\n ('classifier', RandomForestClassifier(n_estimators=200, class_weight='balanced', random_state=RANDOM_STATE, n_jobs=-1))\n])\nrf_pipeline.fit(X_train, y_train_enc)\nrf_time = time.time() - start\n\nrf_results = evaluate_model(rf_pipeline, X_train, y_train_enc, X_val, y_val_enc, le_target, 'RandomForest')\nrf_results['train_time'] = rf_time\n\nprint('Training XGBoost...')\nstart = time.time()\nxgb_pipeline = Pipeline(steps=[\n ('preprocessor', preprocessor),\n ('classifier', xgb.XGBClassifier(n_estimators=200, learning_rate=0.1, max_depth=6,\n objective='multi:softmax', num_class=3,\n tree_method=XGB_TREE_METHOD, device=XGB_DEVICE,\n random_state=RANDOM_STATE, verbosity=0))\n])\nxgb_pipeline.fit(X_train, y_train_enc)\nxgb_time = time.time() - start\n\nxgb_results = evaluate_model(xgb_pipeline, X_train, y_train_enc, X_val, y_val_enc, le_target, 'XGBoost')\nxgb_results['train_time'] = xgb_time\n\nprint(f'RF time: {rf_time:.2f}s | XGB time: {xgb_time:.2f}s')"
"import time\n\nprint('Training Random Forest...')\nstart = time.time()\nrf_pipeline = Pipeline(steps=[\n ('preprocessor', preprocessor),\n ('classifier', RandomForestClassifier(n_estimators=200, class_weight='balanced', random_state=RANDOM_STATE, n_jobs=-1))\n])\nrf_pipeline.fit(X_train, y_train_enc)\nrf_time = time.time() - start\n\nrf_results = evaluate_model(rf_pipeline, X_train, y_train_enc, X_val, y_val_enc, le_target, 'RandomForest')\nrf_results['train_time'] = rf_time\n\nprint('Training XGBoost...')\nstart = time.time()\nxgb_pipeline = Pipeline(steps=[\n ('preprocessor', preprocessor),\n ('classifier', xgb.XGBClassifier(n_estimators=200, learning_rate=0.1, max_depth=6,\n objective='multi:softmax', num_class=3,\n tree_method='gpu_hist', device='cuda', random_state=RANDOM_STATE, verbosity=0))\n])\nxgb_pipeline.fit(X_train, y_train_enc)\nxgb_time = time.time() - start\n\nxgb_results = evaluate_model(xgb_pipeline, X_train, y_train_enc, X_val, y_val_enc, le_target, 'XGBoost')\nxgb_results['train_time'] = xgb_time\n\nprint(f'RF time: {rf_time:.2f}s | XGB time: {xgb_time:.2f}s')"
]
}, },
{ {
"cell_type": "code", "cell_type": "code",
@@ -293,9 +289,7 @@
"id": "e6361576", "id": "e6361576",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": "def objective(trial):\n params = {\n 'n_estimators': trial.suggest_int('n_estimators', 100, 500),\n 'max_depth': trial.suggest_int('max_depth', 3, 10),\n 'learning_rate': trial.suggest_float('learning_rate', 0.01, 0.3, log=True),\n 'min_child_weight': trial.suggest_int('min_child_weight', 1, 10),\n 'subsample': trial.suggest_float('subsample', 0.5, 1.0),\n 'colsample_bytree': trial.suggest_float('colsample_bytree', 0.5, 1.0),\n 'gamma': trial.suggest_float('gamma', 0, 5),\n 'reg_alpha': trial.suggest_float('reg_alpha', 1e-4, 10.0, log=True),\n 'reg_lambda': trial.suggest_float('reg_lambda', 1e-4, 10.0, log=True),\n 'objective': 'multi:softmax',\n 'num_class': 3,\n 'random_state': RANDOM_STATE,\n 'tree_method': XGB_TREE_METHOD,\n 'device': XGB_DEVICE,\n 'verbosity': 0\n }\n pipeline = Pipeline(steps=[\n ('preprocessor', preprocessor),\n ('classifier', xgb.XGBClassifier(**params))\n ])\n pipeline.fit(X_train, y_train_enc)\n y_pred = pipeline.predict(X_val)\n score = f1_score(y_val_enc, y_pred, average='macro')\n return score\n\nprint('Starting Optuna hyperparameter optimisation (30 trials)...')\nstudy = optuna.create_study(direction='maximize', sampler=optuna.samplers.TPESampler(seed=RANDOM_STATE))\nstudy.optimize(objective, n_trials=30, show_progress_bar=False)\n\nprint(f'Best trial: {study.best_trial.number} | Best macro-F1: {study.best_value:.4f}')"
"def objective(trial):\n params = {\n 'n_estimators': trial.suggest_int('n_estimators', 100, 500),\n 'max_depth': trial.suggest_int('max_depth', 3, 10),\n 'learning_rate': trial.suggest_float('learning_rate', 0.01, 0.3, log=True),\n 'min_child_weight': trial.suggest_int('min_child_weight', 1, 10),\n 'subsample': trial.suggest_float('subsample', 0.5, 1.0),\n 'colsample_bytree': trial.suggest_float('colsample_bytree', 0.5, 1.0),\n 'gamma': trial.suggest_float('gamma', 0, 5),\n 'reg_alpha': trial.suggest_float('reg_alpha', 1e-4, 10.0, log=True),\n 'reg_lambda': trial.suggest_float('reg_lambda', 1e-4, 10.0, log=True),\n 'objective': 'multi:softmax',\n 'num_class': 3,\n 'random_state': RANDOM_STATE,\n 'tree_method': 'gpu_hist', 'device': 'cuda',\n 'verbosity': 0\n }\n pipeline = Pipeline(steps=[\n ('preprocessor', preprocessor),\n ('classifier', xgb.XGBClassifier(**params))\n ])\n pipeline.fit(X_train, y_train_enc)\n y_pred = pipeline.predict(X_val)\n score = f1_score(y_val_enc, y_pred, average='macro')\n return score\n\nprint('Starting Optuna hyperparameter optimisation (30 trials)...')\nstudy = optuna.create_study(direction='maximize', sampler=optuna.samplers.TPESampler(seed=RANDOM_STATE))\nstudy.optimize(objective, n_trials=30, show_progress_bar=False)\n\nprint(f'Best trial: {study.best_trial.number} | Best macro-F1: {study.best_value:.4f}')"
]
}, },
{ {
"cell_type": "code", "cell_type": "code",
@@ -313,9 +307,7 @@
"id": "640263ea", "id": "640263ea",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": "best_xgb_params = {\n **study.best_params,\n 'objective': 'multi:softmax',\n 'num_class': 3,\n 'random_state': RANDOM_STATE,\n 'tree_method': XGB_TREE_METHOD,\n 'device': XGB_DEVICE,\n 'verbosity': 0\n}\n\nprint('Training tuned XGBoost...')\nimport time\nstart = time.time()\ntuned_xgb_pipeline = Pipeline(steps=[\n ('preprocessor', preprocessor),\n ('classifier', xgb.XGBClassifier(**best_xgb_params))\n])\ntuned_xgb_pipeline.fit(X_train, y_train_enc)\ntuned_time = time.time() - start\n\ntuned_results = evaluate_model(tuned_xgb_pipeline, X_train, y_train_enc, X_val, y_val_enc, le_target, 'XGBoost_Tuned')\ntuned_results['train_time'] = tuned_time\n\nprint('\\n=== TUNED XGBOOST RESULTS ===')\nfor k, v in tuned_results.items():\n if k != 'model':\n print(f'{k}: {v:.4f}')\n\nprint(f'\\nTuning improvement (macro-F1): +{tuned_results[\"val_f1_macro\"] - xgb_results[\"val_f1_macro\"]:.4f}')"
"best_xgb_params = {\n **study.best_params,\n 'objective': 'multi:softmax',\n 'num_class': 3,\n 'random_state': RANDOM_STATE,\n 'tree_method': 'gpu_hist', 'device': 'cuda',\n 'verbosity': 0\n}\n\nprint('Training tuned XGBoost...')\nimport time\nstart = time.time()\ntuned_xgb_pipeline = Pipeline(steps=[\n ('preprocessor', preprocessor),\n ('classifier', xgb.XGBClassifier(**best_xgb_params))\n])\ntuned_xgb_pipeline.fit(X_train, y_train_enc)\ntuned_time = time.time() - start\n\ntuned_results = evaluate_model(tuned_xgb_pipeline, X_train, y_train_enc, X_val, y_val_enc, le_target, 'XGBoost_Tuned')\ntuned_results['train_time'] = tuned_time\n\nprint('\\n=== TUNED XGBOOST RESULTS ===')\nfor k, v in tuned_results.items():\n if k != 'model':\n print(f'{k}: {v:.4f}')\n\nprint(f'\\nTuning improvement (macro-F1): +{tuned_results[\"val_f1_macro\"] - xgb_results[\"val_f1_macro\"]:.4f}')"
]
}, },
{ {
"cell_type": "code", "cell_type": "code",
@@ -1,942 +0,0 @@
# 机器学习个人课程作业需求分析与实现方案
## 1. 文档目的
本文档基于以下材料整理:
- `外教课/原文要求.txt`
- `外教课/课程作业实现方案分析.md`
- `外教课/课程作业整合及任务拆解与时间规划清单.md`
- `资料/DTS304TC_Assessment1_(word)_2026(1).pdf`
- `资料/dataset_final(1).zip` 中的数据文件结构
目标是输出一份面向实际执行的详细需求分析和实现方案,用于指导 `Jupyter Notebook + Theory and Reflection PDF + hidden-test CSV + 补充代码` 的完整完成过程。
---
## 2. 原始任务核心要求
### 2.1 作业目标
需要围绕一个虚构但接近真实场景的健康保险数据集,建立并改进一个多分类模型,用于预测申请人的保费风险等级:
- `Low`
- `Standard`
- `High`
该任务不是单纯追求 leaderboard 排名,而是要求展示完整、规范、可复现的机器学习工作流,包括:
- 数据清理与预处理
- 数据泄露特征识别与删除
- 基线模型建立
- `Random Forest` 与一种 `Boosting` 模型的公平对比
- 至少一个主模型的高级超参数优化
- 按学号末位完成一个必做改进类别,并额外完成至少一个可选改进类别
- `K-Means``GMM` 的无监督探索
- 基于验证集证据选择最终模型
- 生成规定格式的 hidden-test 预测结果 CSV
### 2.2 提交物
必须提交以下文件:
- 一个 `Jupyter Notebook`,格式为 `.ipynb`
- 一个 `Coursework Answer Sheet / Theory and Reflection PDF`
- 一个 hidden-test 预测结果 `CSV`
- 如使用 notebook 外部的辅助脚本,也必须一并提交
### 2.3 成绩结构
根据 PDF 原文,整份作业分值结构如下:
- `Question 1: Notebook-Based Coding Exercise``60`
- `Theory and Reflection PDF``30`
- `Coding Quality / Answer Sheet Quality / Submission Guidelines``10`
这意味着不能只重模型分数,文档质量、实验组织、结果解释和提交格式同样直接影响成绩。
---
## 3. 从原始 PDF 提炼出的硬性约束
以下内容属于正式要求中的关键硬约束,必须优先满足。
### 3.1 评价指标约束
- 主指标是 `macro-F1`
- `accuracy` 只是辅助指标
- 所有重要模型对比、调参结果、改进结果都应同时报告:
- `macro-F1`
- `accuracy`
原因是类别不平衡明显,不能只用准确率判断模型优劣。
### 3.2 数据泄露约束
PDF 明确指出:
- 数据中存在 `1` 个泄露特征
- 必须先识别并移除,再进入后续分析
- 如果没有删除,后续多个部分都会被视为无效或严重失真
这说明“找出泄露特征”不是建议项,而是作业关键检查点。
### 3.3 模型比较约束
Notebook 中必须完成:
- 一个 baseline pipeline
- 一个 `Random Forest`
- 一个 `Boosting` 模型
并且需要满足“受控比较”原则:
- 使用同一套预处理流程
- 使用同一份训练/验证划分
- 使用同一评价指标
否则比较结论不成立。
### 3.4 高级调参约束
至少一个主模型需要使用真正的高级调参方法,例如:
- `Optuna/TPE`
- 贝叶斯优化
- `Hyperopt`
- `Ray Tune`
PDF 明确说明:
- 单独使用 `RandomizedSearchCV` 通常不足以进入高分档
因此建议主方案使用 `Optuna`
### 3.5 个性化改进约束
必须完成:
- 根据学号末位对应的 `1` 个必做类别
- 额外至少 `1` 个自选类别
推荐但非强制:
- 再额外完成第 `2` 个可选类别,以增强区分度
### 3.6 无监督探索约束
必须完成紧凑版的:
- `K-Means`
- `Gaussian Mixture Model (GMM)`
重点不是聚类分数超过监督学习模型,而是体现:
- 对无监督方法机制的理解
- 对结果的谨慎解释
-`hard assignment``soft assignment` 区别的认识
### 3.7 hidden-test 导出约束
最终 CSV 必须:
- 文件名为 `test_result_[your_student_id].csv`
- 第一列必须是 `applicant_id`
- 第二列必须是 `customer_key`
- 第三列必须是 `premium_risk`
- 预测标签只能使用:
- `Standard`
- `High`
- `Low`
PDF 明确说明:
- 命名或格式错误会在该部分自动扣 `4`
- 不允许在 hidden test 上调参
- 不允许声称 hidden test 性能
### 3.8 PDF 写作约束
`Theory and Reflection PDF` 必须:
- 不超过 `4`
- 不超过 `1200`
- 不得简单重复 notebook 内容
- 每一个理论问题都必须引用 notebook 中至少一个表、图或指标
超过页数或字数限制会固定扣 `5` 分。
### 3.9 AI 使用约束
PDF 原文给出了比普通课程更严格的 AI 使用边界:
- 不允许直接用 ChatGPT 生成作业答案
- AI 只能作为有限支持工具:
- 代码理解
- 调试
- 语法润色
- AI 不能替代:
- 方法设计
- 消融逻辑
- 定性分析
- 反思写作
因此最终提交物必须明显体现“你自己做了实验和分析”。
---
## 4. 数据集层面的已知信息
基于对 `dataset_final(1).zip` 的结构和文件头信息检查,目前已确认:
### 4.1 数据文件
压缩包内包含:
- `dataset_final/train.csv`
- `dataset_final/val.csv`
- `dataset_final/test_features.csv`
### 4.2 数据规模
- `train.csv``74375 x 33`
- `val.csv``13125 x 33`
- `test_features.csv``12500 x 32`
说明:
- 训练集和验证集包含标签列 `premium_risk`
- hidden-test 文件不包含标签
### 4.3 字段结构
训练集列名包括:
- 标识类字段:`applicant_id`, `customer_key`, `applicant_ref_code`
- 时间/类别字段:`application_month`, `employment_sector`, `prior_debt_products`, `debt_portfolio_quality`, `account_tenure`, `minimum_payment_only`, `spending_profile`
- 数值特征:收入、负债、额度变化、查询次数、逾期情况、投资金额、余额等
- 明显可疑字段:`bureau_risk_index`
- 噪声字段:`noise_feature_1``noise_feature_5`
- 标签:`premium_risk`
### 4.4 类别分布
训练集标签分布:
- `Standard`: `39686`
- `High`: `21586`
- `Low`: `13103`
结论:
- 数据明显不平衡
- 使用 `macro-F1` 作为主指标完全合理
- 在个性化改进中,`Category C` 类的重采样、类权重、阈值逻辑会很自然
### 4.5 缺失值概览
当前观察到缺失值较多的字段包括:
- `net_monthly_income_gbp`
- `avg_payment_delay_days`
- `monthly_investment_gbp`
- `prior_debt_products`
- `account_tenure`
- `late_payment_count`
- `credit_limit_change_pct`
- `credit_inquiry_count`
- `end_month_balance_gbp`
这说明预处理部分不能仅做“简单删行”,更适合使用 pipeline 化的缺失值处理方案。
---
## 5. 对任务本质的理解
这份作业本质上考查的不是“谁把模型调得最高”,而是以下四个能力:
- 是否能建立规范的机器学习实验流程
- 是否能识别不合理特征并避免数据泄露
- 是否能做公平比较、合理调参和证据驱动分析
- 是否能把理论概念和自己的实验结果一一对应起来
因此,高分解法必须同时满足:
- 模型结果合理
- 实验过程规范
- 分析论证充分
- notebook 和 PDF 严格互相对应
---
## 6. Notebook 需求拆解
下面按原始评分结构,对 notebook 部分做可执行拆解。
### 6.1 A 部分:清洗、预处理与基线建模
必须完成的内容:
- 读取 `train.csv``val.csv``test_features.csv`
- 明确 `X_train / y_train / X_val / y_val / X_test`
- 识别并删除泄露特征
- 处理脏值、缺失值、类别变量
- 建立一个基线 pipeline
- 报告 baseline 的:
- `accuracy`
- `macro-F1`
- confusion matrix
- 确保 train、val、test 使用完全一致的预处理规则
建议实现:
- 使用 `ColumnTransformer + Pipeline`
- 数值特征:
- `SimpleImputer(strategy='median')`
- 类别特征:
- `SimpleImputer(strategy='most_frequent')`
- `OneHotEncoder(handle_unknown='ignore')`
- baseline 模型:
- `LogisticRegression`
-`HistGradientBoosting` 前的简单基线
更稳妥的推荐是:
-`LogisticRegression(class_weight='balanced')` 作为 baseline
原因:
- 简单
- 可解释
- 适合作为后续树模型的比较起点
### 6.2 泄露特征识别策略
由于 PDF 强调必须自行识别,不建议在正式 notebook 中直接“拍脑袋认定”某列是泄露。
建议采用下面的证据链:
1. 先从业务语义初筛高风险列
候选优先检查:
- `bureau_risk_index`
- 各类明显后验统计或接近标签定义的字段
2. 做单变量或极简模型筛查
例如:
- 单列训练一个简单模型
- 比较每列单独带来的验证集 `macro-F1`
3. 检查“异常高”的预测能力
若某列单独就能异常接近目标标签,则高度怀疑为 leakage
4. 删除该特征后重新建立基线
在 notebook 中明确说明:
- 删除前风险
- 删除后原因
- 为什么后续分析必须基于删除后的数据
注意:
- 从字段命名看,`bureau_risk_index` 是最值得优先怀疑的候选
- 但正式提交中最好写成“通过字段语义 + 验证结果发现其构成泄露或近似泄露”
### 6.3 B 部分:随机森林与 boosting 的受控比较
必须完成:
- 保持同一预处理
- 比较:
- `RandomForestClassifier`
- 一个 boosting 模型
推荐 boosting 模型优先级:
1. `XGBoost`
2. `LightGBM`
3. `HistGradientBoostingClassifier`
推荐理由:
- PDF 明确点名推荐 `XGBoost`
- 后续调参空间更丰富
- 更容易做高质量的超参数优化
本节输出建议至少包含:
- 模型对比表:
- accuracy
- macro-F1
- 训练时间
- 每个模型的 confusion matrix
- 分类报告或按类别 F1
- 简短解释 bagging 与 boosting 在本数据集上的差异
重点:
- 不是证明谁永远更强
- 而是说明在当前数据集上,哪种偏差-方差特性更适合
### 6.4 C 部分:高级超参数优化
必须完成:
- 至少选择一个主模型
- 使用高级优化方法进行调参
- 目标函数对准验证集 `macro-F1`
推荐主模型:
- `XGBoost`
推荐优化器:
- `Optuna``TPESampler`
推荐搜索空间示例:
- `n_estimators`
- `max_depth`
- `learning_rate`
- `min_child_weight`
- `subsample`
- `colsample_bytree`
- `gamma`
- `reg_alpha`
- `reg_lambda`
建议输出:
- 最优参数表
- 前若干 trial 的结果摘要
- 调参前后性能对比表
- 关键超参数的重要性解释
注意:
- 需要简要说明为什么搜索空间这样设
- 需要说明“原本预期哪些参数最关键”
- 需要说明“调参结果是否符合预期”
这部分内容将直接为 PDF 中第 2 题服务。
### 6.5 D 部分:个性化改进
这是 notebook 中占比最高、最容易拉开差距的部分。
#### 学号末位对应关系
- `0-1` -> `Category A`:数据质量与缺失机制
- `2-3` -> `Category B`:特征表示与特征工程
- `4-5` -> `Category C`:类别不平衡与目标设计
- `6-7` -> `Category D`:鲁棒性、校准或集成
- `8-9` -> `Category E`:公平性、诊断或可解释性
#### 每类推荐落地方式
`Category A` 可选实现:
- `IterativeImputer`
- 更细粒度缺失指示器
- 异常值裁剪或 winsorization
- 脏值统一清洗
`Category B` 可选实现:
- 特征交叉
- 类别合并
- 目标无监督编码之外的安全表示
- log 变换、比例特征、债务收入比等衍生变量
`Category C` 可选实现:
- `class_weight`
- `SMOTE` 或其他重采样
- focal-like 思想的替代实现
- 基于验证集的阈值调整
`Category D` 可选实现:
- 概率校准
- soft voting
- stacking
- bootstrap 稳定性测试
`Category E` 可选实现:
- `SHAP`
- permutation importance
- 分组公平性检查
- 错误样本分析
- 高风险误判模式总结
#### 强烈建议的写法
无论学号末位落在哪类,都建议再加一个容易出效果的 optional 类别:
- 若主模型是树模型,优先补:
- `Category E` 可解释性
-`Category D` 集成/校准
这是因为:
- PDF 明确欢迎“具体洞见”
- 可解释性和误差分析非常利于写 reflection
- 这些内容能让 PDF 更有证据,不容易空泛
#### 个性化改进部分必须有的证据
- 紧凑版 ablation table
- 改进前后 accuracy / macro-F1 对比
- 必要时增加 class-wise F1
- 简短解释:
- 做了什么
- 为什么这样做
- 结果是否提升
- 若未提升,为什么仍然有价值
### 6.6 E 部分:K-Means 与 GMM 探索
这一部分应控制篇幅,不宜做成主线。
建议流程:
1. 从清洗后的特征中取适合聚类的数值空间
2. 必要时先做缩放
3. 可以考虑降维到:
- PCA 2D 用于可视化
4.`k=2~8` 分别跑:
- `KMeans`
- `GaussianMixture`
建议输出:
- `K-Means`
- inertia / SSE 曲线
- cluster size
- silhouette score(可选加强)
- `GMM`
- BIC / AIC 或 log-likelihood 趋势
- component size
- posterior probability / responsibility 统计
最后做一个对比表或图,回答:
- 为什么 `K-Means` 是 hard assignment
- 为什么 `GMM` 是 soft assignment
- 当前数据是否存在模糊边界
- `GMM` 是否额外揭示了不确定性或重叠结构
### 6.7 F 部分:最终模型与 hidden-test 导出
必须满足的原则:
- 只能依据验证集结果选最终模型
- 不允许根据 hidden-test 结果回头调参
建议流程:
1. 锁定最终 pipeline
2.`train + val` 合并后重新训练
3.`test_features.csv` 预测
4. 生成严格符合格式的 CSV
推荐导出逻辑:
-`test_features.csv` 保留:
- `applicant_id`
- `customer_key`
- 新增一列:
- `premium_risk`
最终列顺序必须是:
1. `applicant_id`
2. `customer_key`
3. `premium_risk`
---
## 7. 推荐的整体实现路线
下面给出一条适合拿分且可操作的实现主线。
### 7.1 技术栈建议
- Python
- `pandas`
- `numpy`
- `scikit-learn`
- `xgboost`
- `optuna`
- `matplotlib`
- `seaborn`
- `shap`(如选择可解释性方向)
- `imbalanced-learn`(如选择重采样方向)
### 7.2 推荐项目结构
```text
coursework_ml/
├─ notebook/
│ └─ insurance_premium_risk.ipynb
├─ src/
│ ├─ data_utils.py
│ ├─ features.py
│ ├─ metrics.py
│ ├─ tuning.py
│ └─ export.py
├─ outputs/
│ ├─ figures/
│ ├─ tables/
│ └─ predictions/
├─ report/
│ └─ theory_and_reflection.pdf
└─ README.md
```
如果想降低复杂度,也可以只保留:
- 一个主 notebook
- 一到两个辅助 `.py` 脚本
关键是要保证复现性,而不是强行复杂化。
### 7.3 Notebook 章节建议
建议 notebook 按下面顺序组织:
1. Introduction and Setup
2. Data Loading
3. Data Cleaning and Leakage Check
4. Baseline Pipeline
5. Controlled Comparison: Random Forest vs Boosting
6. Advanced Hyperparameter Optimisation
7. Personalised Improvement Work
8. K-Means and GMM Exploration
9. Final Model Selection
10. Hidden-Test Export
11. Conclusion
优点:
- 与评分结构高度对齐
- PDF 写作时可以直接反向索引表格和图片
---
## 8. 推荐的模型方案
### 8.1 baseline
推荐:
- `LogisticRegression`
作用:
- 给出最低可比较基线
- 验证预处理链是否稳定
- 提供线性模型与树模型的风格对照
### 8.2 bagging 方案
推荐:
- `RandomForestClassifier`
关注点:
- 对类别变量经 one-hot 后的适应性
- 是否更稳但不够激进
- 是否在少数类上表现一般
### 8.3 boosting 方案
推荐首选:
- `XGBoost`
备选:
- `LightGBM`
若环境依赖受限可退而求其次:
- `HistGradientBoostingClassifier`
### 8.4 最终模型的推荐候选
最可能的优胜路线通常是:
- 删除泄露特征后
- 用稳定的预处理 pipeline
-`XGBoost` 作为主模型
- 叠加一个与你学号类别匹配的必做改进
- 再叠加一个可解释性或校准类的 optional 改进
一个较稳的最终候选组合是:
- 主模型:调参后的 `XGBoost`
- 必做改进:按学号末位选择
- 可选改进:`SHAP + error analysis``probability calibration`
---
## 9. PDF 的写作映射方案
为了避免 PDF 和 notebook 脱节,建议从 notebook 设计开始就准备好下面这些证据位。
### 9.1 Bagging vs Boosting
PDF 要回答:
- bagging 和 boosting 的定义与性质
- 两个模型的验证结果
- 辅助分析
- 与本数据集的结合解释
Notebook 需要提前准备:
- `RF vs XGB` 对比表
- confusion matrix
- class-wise F1
- 调参前后稳定性或训练/验证表现
### 9.2 Hyperparameter Optimisation
PDF 要回答:
- 优化器为什么合理
- 搜索空间为什么合理
- 哪些参数最重要
- 调参结果是否符合预期
Notebook 需要提前准备:
- Optuna study 结果表
- 最优参数
- 调参前后指标变化
- 若可能,参数重要性图
### 9.3 K-Means vs GMM
PDF 要回答:
- hard vs soft assignment
- 两者假设差异
- 当前数据上的观察结论
Notebook 需要提前准备:
- 一张聚类比较图
- 一张指标对比表
- 一段关于重叠和不确定性的解释
### 9.4 Personalised Reflection
PDF 要回答:
- 必做类别做了什么
- 可选类别做了什么
- 遇到的问题
- 做出的努力
- 学到了什么
Notebook 需要提前准备:
- ablation table
- before/after 结果对比
- 若有失败实验,也保留最关键的一两个作为证据
### 9.5 AI-use Declaration
PDF 中建议采用诚实、克制、可核验的表述,例如:
- 使用 AI 协助理解报错、检查代码逻辑、润色语言
- 所有方法设计、实验执行、结果核验和结论撰写均由本人完成
- 所有表格、图和结论均以 notebook 实验结果为依据
注意:
- 不能写成“AI 帮我完成了模型设计”
- 不能出现与 notebook 证据对不上的泛化陈述
---
## 10. 风险点分析
### 10.1 最大风险:没有先删除泄露特征
后果:
- 分数看似很高
- 但整个分析会被视为失真
- 影响 baseline、比较、调参和最终模型选择
### 10.2 常见风险:比较不公平
表现:
- baseline 和后续模型使用了不同预处理
- 一个模型用 train,一个模型用 train+val
- 某些模型调参后和默认模型直接横比
后果:
- 结论缺乏可信度
### 10.3 常见风险:只报 accuracy
由于类别不平衡:
- 只看 accuracy 会掩盖少数类问题
- 很容易丢掉对 `Low``High` 类的分析深度
### 10.4 常见风险:个性化改进做成“试很多但没有逻辑”
PDF 原文明确更看重:
- meaningful diagnostic
- concrete insight
而不是:
- 一大堆零散实验截图
### 10.5 常见风险:PDF 空泛
如果 PDF 只是教材知识复述,而没有引用 notebook 的具体图表或指标,会被明显扣分。
### 10.6 常见风险:CSV 格式错误
尤其要避免:
- 文件名错误
- 列顺序错误
- 标签拼写错误
- 导出时丢掉 `applicant_id``customer_key`
---
## 11. 建议的执行顺序
建议按以下顺序推进,效率最高:
1. 先读取并检查 train / val / test 的列结构
2. 找出并删除泄露特征
3. 建立统一预处理 pipeline
4. 跑 baseline
5.`Random Forest vs XGBoost` 初始比较
6. 选一个主模型做 `Optuna`
7. 根据学号末位完成必做改进
8. 增加一个 optional 改进
9.`K-Means + GMM` 紧凑探索
10. 选最终模型并重训
11. 导出 `test_result_[student_id].csv`
12. 最后再写 PDF
原因:
- PDF 的每个回答都依赖 notebook 证据
- 先写 PDF 容易出现空泛和证据脱节
---
## 12. 建议的时间分配
如果按一份完整高质量作业来做,建议分配如下:
- 数据清洗与泄露识别:`15%`
- baseline 与模型比较:`20%`
- 高级调参:`20%`
- 个性化改进:`25%`
- K-Means / GMM`10%`
- 最终导出与提交检查:`5%`
- PDF 写作:`5%`
如果时间紧,最不能压缩的部分是:
- 泄露识别
- 受控比较
- 个性化改进
- PDF 与 notebook 的证据对应
---
## 13. 最推荐的落地方案
在当前要求下,一套相对稳妥、兼顾分数与实现成本的方案如下:
### 13.1 Notebook 主线
- 删除泄露特征
- 建立 `ColumnTransformer + Pipeline`
- baseline 使用 `LogisticRegression`
- 受控比较使用:
- `RandomForestClassifier`
- `XGBoost`
-`Optuna``XGBoost`
- 个性化改进做:
- 学号对应的必做类别
- 再补一个 `Category E``Category D`
- 无监督探索做:
- `KMeans`
- `GaussianMixture`
- 最终模型大概率采用:
- 调参后的 `XGBoost` 或其增强版本
### 13.2 PDF 主线
围绕五个 compulsory prompt 分别写,每一题都强制绑定 notebook 中的至少一个:
-
-
- 指标结果
写法上坚持:
- 先简短理论
- 再接本次实验数据
- 再给出数据集相关解释
### 13.3 提交前检查清单
- notebook 能从头运行到尾
- 图表和表格在 notebook 中可见
- PDF 中提到的数值和 notebook 完全一致
- hidden-test CSV 命名正确
- hidden-test CSV 列顺序正确
- 标签名称拼写正确
- 所有附加脚本一并提交
---
## 14. 当前仍待确认的信息
以下信息目前仍需你确认,才可以把“最终实现方案”从通用版收束为定制版:
- 你的 `学号末位`
- 你是否打算使用 `XGBoost`
- 你是否希望 optional 改进优先走:
- 可解释性方向
- 集成/校准方向
- 类别不平衡方向
其中最关键的是:
- `学号末位`
因为它直接决定 `Category A/B/C/D/E` 中哪一类是你的必做项。
---
## 15. 结论
这份机器学习作业的最优策略不是“盲目追求最高分模型”,而是:
- 先保证实验规范
- 再确保比较公平
- 再通过高级调参和个性化改进拉开差距
- 最后让 PDF 和 notebook 形成严格的证据闭环
如果后续要真正动手实现,建议直接按照本文档第 `11` 节的执行顺序推进,并优先先确认学号末位,再定制个性化改进方案。