
简介这份资源是面向机器学习初学者与数据竞赛练习者的中风预测建模项目基于Kaggle公开的中风数据集通过性别、年龄、疾病史与吸烟状况等特征判断患者是否存在中风风险适合用来练习分类建模、数据可视化与模型部署的完整流程。压缩包共11个文件约4.36MB包含3个Jupyter Notebook用于数据探索与建模分析另有csv原始数据、pkl训练模型、py部署脚本、Procfile与sh启动脚本等覆盖从数据处理到Heroku部署的环节并附有说明文档与示意图。已有743人学习下载。项目以追求高F1分数与AUC为目标读者可参考其中的特征筛选、类别不平衡处理与模型评估思路并借助部署文件了解如何将预测模型封装为可访问的应用适合作为课程设计或简历项目的实践参考。1. 卒中预测建模一份能直接跑通的 Jupyter Notebook 资源拿到一份标题叫stroke-prediction-model的资源第一反应往往是“又一个 Titanic 式的练手项目”。但真正拆开看它解决的是一个很实际的问题在结构化临床表格数据上把“是否发生卒中”这个二分类任务从数据清洗一路做到模型评估中间不跳过任何一个容易翻车的环节。这类资源对两类人最有价值——一类是想把机器学习流程走完整、但不想在环境配置和数据泄漏上反复卡壳的新手另一类是手里有类似表格数据、想找一个可改可调的基线模板的从业者。整份资源以 Jupyter Notebook 为载体意味着每一步的中间结果都可见、可改、可复现而不是丢给你一个黑匣子脚本让你猜它干了什么。下面按“它是什么、怎么跑、坑在哪”的顺序拆。2. 数据管道与特征工程从原始表格到可训练矩阵2.1 先看清数据长什么样再决定怎么处理卒中预测这类表格数据典型结构是“一行一个样本、一列一个字段”字段大致分三类数值型年龄、血糖、BMI、类别型性别、是否吸烟、是否高血压、以及可能存在的缺失。打开 Notebook 的第一件事不是急着fit而是把每列的 dtype、缺失率、取值分布打印出来。常见做法是先跑一段概览代码把数值列和类别列分开后面才能对症下药。import pandas as pd import numpy as np df pd.read_csv(stroke_data.csv) # 数值列与类别列分开后续处理路径不同 num_cols df.select_dtypes(include[np.number]).columns.tolist() cat_cols df.select_dtypes(include[object]).columns.tolist() # 缺失率概览超过阈值的列要单独决策 missing df.isnull().mean().sort_values(ascendingFalse) print(missing[missing 0]) # 目标列分布判断是否类别不平衡 print(df[stroke].value_counts(normalizeTrue))这段代码的逻辑很直白select_dtypes按类型切分字段避免把类别列误当数值参与标准化缺失率排序让你一眼看到哪些列需要插补、哪些列缺失太多干脆丢弃目标列分布则决定后面要不要做重采样或调类别权重。参数上唯一需要留意的是缺失率阈值我一般把 40% 作为分界线超过就考虑整列删除低于则插补。注意stroke这个目标列名要按实际数据替换别照抄。2.2 缺失值插补与类别编码的取舍数值列常用中位数插补类别列常用众数插补这是最稳的默认方案。但这里有个容易被忽略的点插补必须放在训练集上拟合、再应用到验证集否则验证集的信息会泄漏进训练过程。Notebook 里如果图省事在全量数据上做fillna后面的评估分数会虚高这是血泪经验里最常见的一类翻车。from sklearn.model_selection import train_test_split from sklearn.impute import SimpleImputer from sklearn.preprocessing import StandardScaler, OneHotEncoder from sklearn.compose import ColumnTransformer from sklearn.pipeline import Pipeline X df.drop(columns[stroke]) y df[stroke] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, stratifyy, random_state42 ) # 数值管道中位数插补 标准化 num_pipe Pipeline([ (imputer, SimpleImputer(strategymedian)), (scaler, StandardScaler()), ]) # 类别管道众数插补 独热编码 cat_pipe Pipeline([ (imputer, SimpleImputer(strategymost_frequent)), (encoder, OneHotEncoder(handle_unknownignore)), ]) preprocessor ColumnTransformer([ (num, num_pipe, num_cols), (cat, cat_pipe, cat_cols), ])这里用Pipeline和ColumnTransformer把预处理封装起来核心目的是让插补和编码只在fit时接触训练数据transform时复用训练集的统计量。stratifyy保证训练集和测试集的类别比例一致对不平衡数据尤其重要handle_unknownignore防止测试集出现训练时没见过的类别导致报错。参数上random_state42只是固定切分换成别的整数也行但要保证全流程一致否则复现时对不上号。2.3 不平衡处理别一上来就上 SMOTE卒中数据通常正样本占比很低直接训练会让模型倾向于全预测为负类准确率看着高、召回率惨不忍睹。常见做法有三条路调class_weightbalanced、用 SMOTE 过采样、或者调分类阈值。我的建议是先试class_weight因为它不改变数据分布、不引入合成样本最不容易出问题SMOTE 要放在训练集内部做绝不能对测试集过采样否则评估就失真了。from imblearn.pipeline import Pipeline as ImbPipeline from imblearn.over_sampling import SMOTE # SMOTE 只在训练折内生效测试集保持原始分布 model_pipe ImbPipeline([ (prep, preprocessor), (smote, SMOTE(random_state42)), (clf, LogisticRegression(class_weightbalanced, max_iter1000)), ])注意这里用的是imblearn的Pipeline而不是 sklearn 的因为只有前者能保证重采样步骤在交叉验证的每一折内部执行。如果混用SMOTE 会在切分前就把合成样本混进验证集分数会好看得离谱实际部署时原形毕露。max_iter1000是给逻辑回归留足收敛空间数据量大时默认值经常报收敛警告。3. 模型训练与评估把基线跑稳再谈调参3.1 选逻辑回归还是树模型先看你要什么这份资源里如果同时给了逻辑回归和随机森林/梯度提升的对比别急着认定树模型一定赢。逻辑回归的可解释性强系数方向能直接对应风险因素适合需要向非技术方解释的场景树模型在特征交互复杂时表现更好但调参成本高、容易过拟合。我的习惯是先跑逻辑回归当基线拿到一个可解释的分数再用树模型看能提升多少提升不明显就保留简单模型。from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import cross_val_score models { logreg: LogisticRegression(class_weightbalanced, max_iter1000), rf: RandomForestClassifier(class_weightbalanced, n_estimators300, random_state42), } for name, clf in models.items(): pipe ImbPipeline([ (prep, preprocessor), (smote, SMOTE(random_state42)), (clf, clf), ]) scores cross_val_score(pipe, X_train, y_train, cv5, scoringroc_auc) print(name, scores.mean().round(4), scores.std().round(4))cross_val_score用 5 折交叉验证scoringroc_auc而不是准确率是因为不平衡数据下 AUC 更能反映排序能力。打印均值和标准差标准差大说明模型对数据切分敏感稳定性存疑。参数上n_estimators300是随机森林的树数量太少欠拟合、太多训练慢300 是个折中值cv5在样本量不大时够用样本上千可以加到 10。3.2 评估指标别只看准确率不平衡二分类的评估准确率几乎没用。要看的至少包括召回率漏诊多少、精确率误报多少、F1 和 AUC。医疗场景下漏诊代价高召回率往往优先但也不能无限牺牲精确率否则误报太多同样不可用。Notebook 里应该把混淆矩阵和分类报告都打出来而不是只报一个数字。from sklearn.metrics import classification_report, confusion_matrix, roc_auc_score best_pipe ImbPipeline([ (prep, preprocessor), (smote, SMOTE(random_state42)), (clf, LogisticRegression(class_weightbalanced, max_iter1000)), ]) best_pipe.fit(X_train, y_train) y_pred best_pipe.predict(X_test) y_prob best_pipe.predict_proba(X_test)[:, 1] print(confusion_matrix(y_test, y_pred)) print(classification_report(y_test, y_pred, digits3)) print(AUC:, round(roc_auc_score(y_test, y_prob), 4))predict_proba取第二列是因为正类在索引 1这个细节写错会导致 AUC 算反。classification_report里重点看正类的 recall 和 precisiondigits3只是显示精度。如果召回率明显偏低下一步不是换模型而是调分类阈值——把默认 0.5 往下调用召回换精确具体阈值看业务能接受多少误报。3.3 阈值调整一个被低估的杠杆很多人训完模型直接predict就完事其实阈值是个免费的性能调节旋钮。把预测概率从高到低排序逐个阈值算召回和精确画一条曲线选一个业务上可接受的点。这一步不需要重新训练成本极低但经常能把召回率拉上来好几个点。import numpy as np from sklearn.metrics import precision_recall_curve precisions, recalls, thresholds precision_recall_curve(y_test, y_prob) # 找召回率不低于 0.8 的前提下精确率最高的阈值 mask recalls[:-1] 0.8 best_idx np.argmax(precisions[:-1][mask]) best_thr thresholds[mask][best_idx] print(threshold:, round(best_thr, 3), precision:, round(precisions[:-1][mask][best_idx], 3))precision_recall_curve返回的 thresholds 比 precisions/recalls 少一个元素所以索引要错位处理这是 sklearn 的一个固定行为写错会报索引越界。recalls[:-1] 0.8是业务约束你可以按实际需求改成 0.7 或 0.9。选出来的阈值要记下来部署时用y_prob best_thr代替predict。4. 避坑与排查这几处翻车我见过太多次4.1 数据泄漏分数虚高的头号元凶现象是交叉验证 AUC 高得离谱0.95 以上但换个数据集就崩。原因通常是在切分前做了插补、标准化或过采样测试集信息渗进了训练。解决方法是把所有预处理塞进Pipeline确保fit只碰训练折。检查手段很简单故意把测试集标签打乱如果分数还是很高基本可以确定有泄漏。4.2 类别编码顺序错乱现象是训练时正常、预测时报“未见过的类别”。原因是OneHotEncoder没设handle_unknownignore或者训练和预测用了两次独立的fit。解决方法是编码器只fit一次保存下来供预测复用用joblib把整个 pipeline 序列化别只存模型权重。4.3 随机种子不统一现象是同一份代码跑两次结果差很多复现不了。原因是train_test_split、模型初始化、SMOTE 各自用了不同的random_state或干脆没设。解决方法是在 Notebook 开头统一定义一个种子变量所有涉及随机的地方都引用它包括交叉验证的cv切分。4.4 把 ID 列当特征喂进去现象是模型在训练集上表现完美测试集一塌糊涂。原因是数据里混了样本编号、就诊流水号这类唯一标识列模型直接记住了 ID 和标签的对应关系。解决方法是在drop阶段显式排除 ID 类字段并在特征列表里做一次人工核对别全靠select_dtypes自动判断。4.5 忽略特征量纲导致系数不可比现象是逻辑回归系数大小悬殊误以为某个特征特别重要。原因是数值列没标准化年龄和血糖的量纲差了几个数量级。解决方法是在数值管道里加StandardScaler标准化后的系数才能横向比较重要性。注意标准化同样要放在 Pipeline 里不能提前对全量数据做。5. 从 Notebook 到可复用脚本固化流程的几个技巧Notebook 适合探索但不适合反复执行。把稳定下来的流程抽成函数或脚本是这份资源能不能真正用起来的分水岭。我一般会做三件事把预处理和模型封装成一个build_pipeline()函数把评估逻辑封装成evaluate(pipe, X, y)再用joblib把训练好的 pipeline 落盘。这样下次换数据只需改读取路径其余全复用。import joblib def build_pipeline(num_cols, cat_cols, clf): num_pipe Pipeline([ (imputer, SimpleImputer(strategymedian)), (scaler, StandardScaler()), ]) cat_pipe Pipeline([ (imputer, SimpleImputer(strategymost_frequent)), (encoder, OneHotEncoder(handle_unknownignore)), ]) prep ColumnTransformer([ (num, num_pipe, num_cols), (cat, cat_pipe, cat_cols), ]) return ImbPipeline([ (prep, prep), (smote, SMOTE(random_state42)), (clf, clf), ]) pipe build_pipeline(num_cols, cat_cols, LogisticRegression(class_weightbalanced, max_iter1000)) pipe.fit(X_train, y_train) joblib.dump(pipe, stroke_pipeline.joblib)落盘的是整个 pipeline 而不是单独的模型这样预测时插补、编码、重采样的参数全都带着不会出现“训练时标准化了、预测时忘了”的低级错误。加载时joblib.load回来直接predict_proba即可。参数上唯一要盯的是handle_unknownignore必须保留否则线上遇到新类别直接抛异常。验证方法我习惯用一个小脚本做冒烟测试加载 pipeline喂几条手工构造的样本确认输出概率在 0 到 1 之间、且对明显高风险样本给出较高概率。这一步花不了几分钟但能挡住大部分“模型存了但用不了”的问题。另外特征重要性可以用逻辑回归的系数或树模型的feature_importances_打出来和业务常识对一遍——如果模型认为“是否吸烟”不重要那多半是编码或列名对错了别急着信分数。从那以后我每次交付这类表格建模资源都强制走一遍“切分前不碰数据、pipeline 整体落盘、阈值单独调”这三步少一步后面就得返工。希望帮到你。本文还有配套的精品资源点击获取