Python医疗花费预测实战:手写算法+多模型融合

发布时间:2026/10/12 14:50:11
Python医疗花费预测实战:手写算法+多模型融合
简介本资源是一份面向机器学习初学者与课程设计实践者的医疗花费预测完整项目包聚焦Python建模全流程训练涵盖从数据探索、多模型手写实现到高级融合策略的系统性实践。资源共5个文件含2个核心Python源码main.py等、1份Word格式设计报告详述算法原理与实验分析、1份Markdown说明文档及1份LICENSE协议压缩包仅1.29MB轻量易用。已有684人学习下载适合高校数据科学课程作业、AI实践课设或自学进阶者参考。读者可获得全手写不调包的随机森林与线性回归实现、scikit-learn主流回归模型GBDT/SVR/Lasso/决策树对比实验、三种调参方式GridSearchCV/RandomizedSearchCV/手动实操、以及直接平均/加权平均/Stacking三类模型融合方案并配套K折交叉验证与留一法评估细节代码结构清晰、注释充分具备强复现性与教学参考价值。1. 医疗花费预测不是“调个包就完事”这份全手写多模型融合的Python实战资源专治数据少、特征杂、结果飘的临床预测焦虑你手上有一份医保结算单2000条记录字段杂乱年龄、性别、住院天数、诊断编码、手术标志、用药频次…领导说“下周要个预测模型预估下次住院费用”但没给预算买GPU也没配数据工程师。这时候网上搜“Python医疗花费预测”90%的结果是直接from sklearn.ensemble import RandomForestRegressor然后fit()—— 可现实里测试集才1070行特征间存在强共线性还有大量重复样本连describe()都能看出某些费用字段明显右偏。这份基于Python的医疗花费预测.zip不是玩具Demo它用全手写不调包的随机森林和线性回归打底再叠加scikit-learn的GBDT、SVR、Lasso、决策树最后用直接平均、加权平均、stacking三层融合收口所有代码跑在纯CPU环境训练耗时控制在3分钟内MAE稳定压在186元以内原始数据单位为元。它适合两类人一是课程设计卡在“模型太简单被老师打回”的本科生二是医院信息科刚接手DRG费用分析、急需可解释、可复现、能讲清每一步逻辑的落地脚本的工程师。别被标题里的“预测”骗了——它真正价值在于把“为什么这个参数要这么设”“为什么这里必须用留一法而不是K折”“为什么融合权重不能随便拍脑袋”全摊开写进.docx设计报告里。2. 从零构建核心算法不调包实现线性回归与随机森林看清梯度下降与树分裂的本质2.1 手写线性回归用解析解梯度下降双验证拒绝黑匣子main.py中的LinearRegressionManual类不是教科书伪代码而是严格对标真实医疗数据特性写的。医疗费用常呈右偏分布所以它默认对目标变量charges做对数变换np.log1p(y)再拟合——这步在 scikit-learn 的LinearRegression里得自己套壳而这里直接嵌进fit()方法里def fit(self, X, y, lr0.01, n_iter1000, use_logTrue): if use_log: y np.log1p(y) # 防止log(0)且压缩长尾 self.theta np.zeros(X.shape[1]) for i in range(n_iter): y_pred X.dot(self.theta) error y_pred - y gradient (2 / len(y)) * X.T.dot(error) self.theta - lr * gradient self.is_fitted True注意lr0.01是血泪经验——医疗数据量小训练集约1000行学习率太大直接发散n_iter1000是实测收敛阈值少于800次误差波动超5%多于1200次无收益。use_logTrue必须开启否则对数万元级费用预测MAE会飙升到400元。解析解验证模块更关键它用np.linalg.inv(X.T X) X.T y计算闭式解并与梯度下降结果比对。当两者theta向量差值的L2范数 1e-5时才认为梯度下降收敛可靠。这步在课程设计答辩时能直接堵住“你确定没写错”的灵魂拷问。2.2 手写随机森林控制树深度特征采样防过拟合于小样本医疗数据特征少仅12列、样本少总数据2000标准RF极易过拟合。这里的RandomForestManual强制限制单棵树最大深度max_depth8实测超过10层OOB误差开始上扬特征采样比例max_features0.7非默认的sqrt(n_features)因医疗特征间相关性高需更强扰动最小叶子节点样本数min_samples_leaf5避免单个罕见诊断码撑起整棵子树构建流程精简但致命每棵树用有放回抽样bootstrap生成训练子集X_boot, y_boot对每个节点随机选0.7个特征在其中找最优分割点_find_best_split()分割标准用MSE减少量而非信息增益回归任务本质树生长到max_depth或min_samples_leaf触发即停不剪枝小数据下预剪枝比后剪枝更稳def _build_tree(self, X, y, depth0): if depth self.max_depth or len(y) self.min_samples_leaf: return {value: np.mean(y), is_leaf: True} # 随机选特征索引 feat_idxs np.random.choice(X.shape[1], int(X.shape[1] * self.max_features), replaceFalse) best_feat, best_thresh self._find_best_split(X, y, feat_idxs) # ... 递归构建左右子树提示feat_idxs的replaceFalse是硬性要求——医疗特征如“是否手术”“是否ICU”是二元强信号若允许重复采样某棵树可能连续分裂同一特征丧失多样性。2.3 为什么坚持手写——课程设计与工程落地的双重刚需课程设计角度老师检查源码时看到sklearn.linear_model.LinearRegression().fit()会质疑“这和抄作业有什么区别”而看到theta - lr * gradient和手动矩阵求逆立刻确认你理解了最小二乘本质。设计报告.docx里专门用一节对比了手写版与sklearn版在相同数据上的系数差异0.3%证明实现正确性。工程落地角度医院服务器常禁用pip install或只允许白名单库。手写模块依赖仅numpypandas而scikit-learn在老旧Linux环境常因cython版本冲突编译失败。这份资源里手写部分就是你的“后悔药”——当pip install sklearn报错时LinearRegressionManual立刻顶上。3. 多模型调参实战GridSearchCV、RandomizedSearchCV与手动调参的取舍边界3.1 GridSearchCV适合小范围、高确定性的超参组合对DecisionTreeRegressor这种参数少max_depth,min_samples_split,criterion的模型用网格搜索最稳妥。资源中配置如下param_grid_dt { max_depth: [5, 7, 10], min_samples_split: [10, 20, 30], criterion: [mse, friedman_mse] } grid_dt GridSearchCV( DecisionTreeRegressor(random_state42), param_grid_dt, cv5, # K折交叉验证 scoringneg_mean_absolute_error, n_jobs-1 ) grid_dt.fit(X_train, y_train)参数说明cv5是平衡效率与稳定性之选——医疗数据小5折比10折更稳scoringneg_mean_absolute_error因MAE比RMSE更鲁棒费用预测中异常值多n_jobs-1在多核CPU上加速但医院旧服务器若只有2核需显式设为n_jobs1否则进程卡死。3.2 RandomizedSearchCV应对高维参数空间的降维打击GradientBoostingRegressor有n_estimators,learning_rate,max_depth,subsample,max_features等7个参数全网格搜索爆炸。这里用随机搜索限定100次采样param_dist_gbr { n_estimators: randint(50, 300), learning_rate: uniform(0.01, 0.2), max_depth: randint(3, 10), subsample: uniform(0.8, 0.2), # 0.8~1.0 max_features: uniform(0.5, 0.5) # 0.5~1.0 } random_gbr RandomizedSearchCV( GradientBoostingRegressor(random_state42), param_distributionsparam_dist_gbr, n_iter100, cvLeaveOneOut(), # 关键小数据用留一法 scoringneg_mean_absolute_error, random_state42, n_jobs1 # 避免旧服务器多进程崩溃 )为什么用LeaveOneOut()测试集仅1070行5折CV每折仅214个样本方差极大留一法LOO用n-1个样本训练1个验证虽慢但方差最低。实测LOO选出的GBDT测试MAE比5折低12.3元。3.3 手动调参当自动搜索失效时的终极防线当SVR遇到医疗数据的高斯核陷阱gamma太小欠拟合太大过拟合自动搜索常卡在局部最优。资源中提供手动调试模板# 先固定C100, kernelrbf扫gamma gammas [0.001, 0.01, 0.1, 1, 10] results [] for g in gammas: svr SVR(C100, gammag, kernelrbf) scores cross_val_score(svr, X_train, y_train, cvLeaveOneOut(), scoringneg_mean_absolute_error) results.append(-scores.mean()) # 绘图找拐点 → 发现gamma0.1时MAE最低189.2元 # 再固定gamma0.1扫C[10,50,100,200] → C100最优玄学经验医疗费用预测中SVR的gamma最优值常落在0.01~0.1区间因费用分布平滑度中等而C需较大≥100因允许少量高价异常值存在避免模型被拉偏。4. 模型融合与评估直接平均、加权平均、stacking的实测效果与避坑指南4.1 直接平均法基线模型快但粗糙将5个模型RF、GBDT、DT、SVR、Lasso的预测结果简单取均值preds np.column_stack([ rf_pred, gbr_pred, dt_pred, svr_pred, lasso_pred ]) ensemble_pred np.mean(preds, axis1)效果MAE192.5元比最佳单模型GBDT的186.3元略差但稳定性极强——各模型预测方差被平滑极端错误如某模型对老年患者费用预测偏低30%被稀释。适合首次部署作为兜底方案。4.2 加权平均法用验证集误差倒数定权重简单有效权重计算公式weight_i 1 / MAE_i再归一化maes [186.3, 189.7, 195.2, 198.1, 191.4] # 各模型在验证集MAE weights np.array([1/m for m in maes]) weights weights / weights.sum() # 归一化 ensemble_pred np.average(preds, weightsweights, axis1)效果MAE降至185.1元提升1.2元。关键洞察GBDT权重最高0.32SVR最低0.18印证了GBDT对医疗费用非线性关系的捕捉能力更强。4.3 Stacking堆叠法用元模型学习“何时该信谁”第一层5个基模型输出预测值 → 构成新特征矩阵1070×5第二层用LinearRegression拟合该矩阵到真实标签# 第一层预测用K折避免泄露 from sklearn.model_selection import KFold kf KFold(n_splits5, shuffleTrue, random_state42) stack_X np.zeros((len(X_test), 5)) for i, model in enumerate([rf, gbr, dt, svr, lasso]): stack_X[:, i] cross_val_predict(model, X_train, y_train, cvkf, n_jobs1) # 第二层训练 meta_model LinearRegression() meta_model.fit(stack_X, y_train) final_pred meta_model.predict(stack_X_test) # stack_X_test同理生成效果MAE183.7元为最优。但训练耗时翻倍需5×525次K折且对小数据敏感——若某基模型在某一折表现极差其预测值会污染整个stack_X矩阵。4.4 避坑模型融合的四个血泪现场现象Stacking融合后测试MAE反而比单模型高195元原因cross_val_predict未正确使用导致训练集预测值泄露用全部X_train拟合后直接predict而非K折生成解决严格按KFold分折每折用其余4折训练基模型预测当前折样本拼接成stack_X。现象加权平均权重全趋近于0.2毫无区分度原因各模型MAE过于接近如186.3/186.5/186.7倒数计算后差异小于1e-4解决改用1/(MAE_i 1e-3)加微小扰动或切换为1/(MAE_i^2)增强区分度。现象直接平均预测值出现负数医疗费用不可能为负原因Lasso回归在特征缩放不足时截距项偏移解决所有模型输入前强制StandardScaler().fit_transform(X_train)并在预测后对负值设为0np.clip(ensemble_pred, 0, None)。现象Stacking元模型过拟合验证集MAE低但测试集飙升原因元模型复杂度太高如用GBDT代替LinearRegression解决元模型必须简单——医疗小数据下线性回归或Ridge回归足够复杂模型只会记住噪声。5. 数据预处理与评估细节为什么isnull().sum()之后还要查重复值5.1 重复值检测医疗数据的隐形陷阱设计报告.docx强调“isnull().sum()显示无缺失值但duplicated().sum()发现127行完全重复”。这不是脏数据而是同一患者多次住院的合理记录——但若不做处理模型会误以为“相同特征必然对应相同费用”导致过拟合。资源中处理策略明确# 识别重复行所有列完全相同 dups df.duplicated() print(f重复行数: {dups.sum()}) # 输出127 # 方案A保留首次出现默认 df_clean df.drop_duplicates() # 方案B对重复行的费用取均值更合理因同一患者不同次住院费用可能不同 df_grouped df.groupby(list(df.columns[:-1]))[charges].mean().reset_index()为什么选方案B医疗场景中“年龄45、性别男、诊断I10、无手术”可能对应三次住院费用分别为2800、3100、2950元。取均值3016元比随机删掉两次更能保留统计规律。设计报告里用箱线图对比了两种方案下费用分布的偏度变化证实方案B使分布更对称。5.2 特征工程从原始字段到可建模特征的三步转化原始数据含sex,bmi,children,smoker,region等字段但直接喂模型会失效region东南西北是名义变量需pd.get_dummies()转为4列独热编码smokeryes/no转为0/1但必须与bmi交互吸烟者BMI每增加1费用增幅比非吸烟者高2.3倍报告中有t检验p0.001children0~5看似数值实为有序分类资源中将其分段0→0,1-2→1,3→2# 关键交互特征 df[smoker_bmi] df[smoker] * df[bmi] # 有序分段 df[children_cat] pd.cut(df[children], bins[-1,0,2,10], labels[0,1,2], include_lowestTrue)5.3 评估不止看MAE临床可接受的误差带宽设计报告定义了临床实用评估标准误差区间占比要求临床意义500元≥70%1000元≥95%2000元≤2%预警高风险患者实测结果手写RF|500元占比68.2% →不达标GBDT72.1% →达标Stacking融合75.3% →最优提示单纯优化MAE可能牺牲临床实用性。比如某模型MAE183元但|500元仅65%不如MAE187元但占比73%的模型——后者每天能多帮3个患者精准分流。6. 部署前必做的三件事模型可解释性、预测稳定性、生产环境兼容性验证6.1 SHAP值解读告诉医生“为什么预测这个费用”医疗场景拒绝黑盒。资源中为GBDT模型集成SHAP需pip install shapimport shap explainer shap.TreeExplainer(gbr_best) shap_values explainer.shap_values(X_test.iloc[0:100]) # 取前100样本 shap.summary_plot(shap_values, X_test.iloc[0:100], feature_namesX_test.columns, plot_typebar)关键发现见设计报告图3smoker_bmi交互特征SHAP均值达1240元是最强正向驱动因子region_northeastSHAP均值-890元表明该地区医保控费更严age在60岁后SHAP值陡增印证老龄化成本上升临床价值当医生看到“患者费用预测值高主要因吸烟且BMI32”可立即干预——这比单纯说“模型预测32000元”有用十倍。6.2 预测稳定性测试对抗微小扰动的鲁棒性医疗数据录入常有小误差如BMI录成28.3 vs 28.4。资源中提供稳定性验证脚本# 对测试集每行生成100个扰动样本±0.1% stable_results [] for i in range(len(X_test)): base_pred final_model.predict(X_test.iloc[i:i1])[0] perturbed_preds [] for _ in range(100): X_pert X_test.iloc[i:i1].copy() X_pert np.random.normal(0, 0.001, X_pert.shape) # ±0.1% perturbed_preds.append(final_model.predict(X_pert)[0]) stable_results.append({ base: base_pred, std: np.std(perturbed_preds), range: max(perturbed_preds) - min(perturbed_preds) }) # 统计95%样本的std 150元 → 稳定结论所有模型中Stacking融合的std中位数仅89元显著优于单模型GBDT为132元证明融合有效抑制了单模型的抖动。6.3 生产环境兼容性从开发机到医院服务器的平滑迁移医院服务器常见问题及解决方案问题现象资源中对策scikit-learn版本冲突ImportError: cannot import name check_array提供requirements.txt锁定scikit-learn1.0.2经测试兼容CentOS 6.5内存不足4GBRandomizedSearchCV进程被OOM killer终止所有CV操作强制n_jobs1并添加内存监控psutil.virtual_memory().percent 80才启动无图形界面matplotlib报错预测脚本默认不绘图plotTrue需手动开启且用Agg后端从那以后我每次交付医疗预测脚本都强制走一遍这三步用SHAP画出前3个最重要特征的影响图打印出来给医生看对线上100条真实记录做稳定性扰动测试确保std200元在目标服务器上用python -m pip install -r requirements.txt --no-cache-dir重装而非pip install -U。这三步做完模型才真正从代码变成临床工具。希望帮到你。本文还有配套的精品资源点击获取