尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Python回归模型预测碰撞风险:从数据清洗到XGBoost实战

Python回归模型预测碰撞风险:从数据清洗到XGBoost实战 简介机器学习回归模型是预测连续数值的核心方法在风险建模、价格预测等场景中应用广泛。其原理是通过学习历史数据中特征与目标变量间的映射关系建立函数模型。特征工程、模型选型与调优直接影响预测效果而数据清洗则决定了模型的底线。本文从数据清洗与探索性分析出发逐步构造有效特征对比线性回归、随机森林与XGBoost等模型的性能表现完整呈现基于Python的碰撞风险预测项目实战。重点解析如何将R²从0.53提升至0.86并规避数据泄漏、过拟合与环境依赖等常见坑点为回归建模项目提供可复用的工程化思路。 那门机器学习课的大作业我选了《基于Python的回归模型预测碰撞几率Risk》。题目听起来挺唬人说白了就是给一堆车辆碰撞相关的记录字段让我建立回归模型预测每次碰撞的风险值最后交源码和文档说明。当时一起做这个题的人不少但最后分数差距拉得很大有人只是把线性回归跑通就算完有人却把数据探索、特征工程、模型对比、可视化全套做齐连文档都写得像一篇小论文。这篇博文就把我当时从零到一完成这个项目的过程完整拆开讲一遍重点放在哪些环节真正决定了能不能拿高分上。如果你正在做类似的机器学习课程大作业或者想系统看看一个回归建模项目从数据到落地的完整流程这篇内容可以当作一份可以直接参考的作业模板。文章不会只贴代码我会把每个关键决策背后的原因、调试时的坑、文档里应该写什么全部交代清楚。1. 碰撞几率Risk这个作业到底在考什么1.1 先把题目翻译成建模任务基于Python建立回归模型预测碰撞几率risk这句话拆开来看其实包含了几层约束。第一这是一个回归任务不是分类任务。risk是一个连续数值不是高/低风险这种离散标签。我当时拿到的数据里risk取值大概在0到1之间表示一次碰撞发生后驾乘人员受伤严重程度的概率估计值。虽然它看起来像概率但课程要求用回归模型拟合连续值所以不能拿逻辑回归糊弄事。第二它要求用Python实现。这基本锁定了技术栈pandas做数据处理、scikit-learn和XGBoost做建模、matplotlib和seaborn做可视化。第三它要求有源码和文档说明。这意味着项目不是跑通一个模型就结束还要让别人能看懂、能运行、能复现。这一点我在后面专门展开。把这个需求翻译成建模任务就是给定一批碰撞记录的特征数据训练一个回归模型输入新的碰撞记录字段输出risk的预测值。整个流程可以拆成五步数据探索和清洗特征工程模型选型与训练评估与调优结果解释与文档呈现1.2 高分项目和及格项目的分水岭我见过不少同学的提交代码本身没什么大问题但分数就是上不去为什么因为老师看的不只是模型能不能跑而是你有没有完整地思考清楚这个建模问题。及格水平的项目通常是这样的读入CSV、删掉缺失值、把分类变量用get_dummies转一下、跑一个线性回归、输出R²然后交差。整个过程可能只有几十行代码。高分项目的差别体现在几个地方。首先是数据探索有没有画过risk的分布图、有没有分析过特征和risk之间的关系其次是特征工程有没有构造出有意义的新特征而不是直接把原始字段扔进模型然后是模型对比不能只做一个线性回归至少要有baseline、有树模型、有集成模型的横向对比最后是解释性模型训练完了能不能说清楚哪个特征最重要、模型学到了什么规律。这几点加在一起才是一个完整的机器学习项目而不只是机器学习作业。我把这个理解写进了文档的第一章老师批注就写了四个字思路清晰。1.3 为什么选Python这套组合而不是其他方案如果是纯粹为了出结果用R的caret包或者SPSS也能做甚至更快。但课程指定Python而且在工业界、科研界Python这套生态确实最完整。具体到库的选择我的原则是能用现成库解决的绝对不自己造轮子。pandas和numpy负责数据处理scikit-learn提供线性回归、数据划分、交叉验证、评估指标XGBoost负责树模型matplotlib和seaborn负责画图。这些库在Python 3.8以上的环境里都有稳定的版本支持安装方便文档也齐全。有一点要提醒环境版本一定得固定。我当时在requirements.txt里锁定了版本号因为XGBoost在不同版本里API有细微差异比如早期版本的n_estimators在部分版本里叫num_round记错的话代码直接报错。补一句我自己用的是Python 3.9 scikit-learn 1.2 XGBoost 1.7组合全程没有遇到版本兼容问题。2. 数据到手先别急着建模先搞清楚每个字段是什么意思2.1 字段含义和risk标签的边界我拿到的这份碰撞数据大约有六千多条记录字段大概是这样vehicle_speed碰撞时车辆速度数值型weather_condition天气情况分类变量有晴、雨、雪、雾等road_type道路类型分类变量比如高速公路、城市道路、乡村道路driver_age驾驶员年龄数值型alcohol_level驾驶员酒精检测值数值型collision_type碰撞类型分类变量比如追尾、侧面碰撞、正面碰撞hour_of_day碰撞发生的小时数值型seatbelt_used是否系安全带二值变量risk目标变量0-1之间的连续值拿到数据第一件事不是跑模型而是把每个字段看懂。我花了整整半天时间逐列检查数据说明文档确认每个字段的类型、取值范围、缺失比例。这一步很多人会跳过但后面所有特征工程都建立在对字段的理解之上。特别要注意risk本身是怎么来的。如果风险值是由某些字段加权计算出来的那这些字段就不能作为特征放进模型否则就构成了目标泄漏。我检查下来发现risk是由碰撞类型、车速、安全带使用情况等多个因素综合评定出来的但原始数据表里并没有提供计算risk的具体权重所以从特征列表里也无法直接反推出risk这个隐患不存在。但这个问题我在答辩时被老师专门问过能答上来会非常加分。2.2 缺失值和异常值处理决定了模型的底线数据清洗阶段我做了三件事。第一步是检查缺失值。用df.isnull().sum()扫了一遍发现alcohol_level缺失了大概12%vehicle_speed缺失了3%。我的处理方式是数值型特征用中位数填充因为中位数对异常值不敏感比均值更稳妥。分类特征本来就是字符串直接用众数填充。第二步是处理异常值。画了箱线图之后发现vehicle_speed有两个值为负的样本这显然不合理直接删除。alcohol_level有几个超过0.4的极值考虑到现实场景里这已经是非常严重的醉驾水平保留下来会影响模型稳定性我也删掉了。删除之前我先把这些样本在notebook里用单独的cell标注出来了这样文档里可以说明共删除N条异常样本删除依据是什么显得过程很严谨。第三步是数据类型转换。hour_of_day虽然是数值但本质上是一个周期性变量直接当数值用不太合理。我把它做成了两个特征is_night是否夜间和hour_sin小时的正弦变换。seatbelt_used字段原本是yes/no字符串我用map映射成1/0。weather_condition这种分类字段在建模阶段统一做独热编码。2.3 数据探索里最容易丢分的三个细节数据探索做得好不好老师一眼就能看出来因为它会体现在文档的图表里。我做了三张图每张图的结论都写进了最终报告。第一张是risk的直方图结论是分布右偏大多数样本的风险值集中在0.2到0.6之间只有少数高风险样本。这说明直接用线性回归预测时对高值段的拟合会偏弱为后面选择树模型埋下伏笔。第二张是车速和risk的散点图。从图里可以清楚看到车速超过80km/h后risk明显上升而且呈现出非线性趋势。这解释了为什么线性回归作为baseline的R²只有0.5左右因为线性模型拟合不了这种非线性关系。第三张是分类特征和risk的箱线图。我选了weather_condition和collision_type两个字段发现雨雪天气下的平均risk显著高于晴天正面碰撞比追尾的风险更高。这些结论在文档里都是卖点它证明你在思考数据而不只是调用API。3. 回归模型选型从线性回归到XGBoost的完整对比3.1 线性回归为什么值得先跑一版很多人觉得线性回归太简单不屑于用它。但作为baseline线性回归的意义在于给整个项目定一个下限如果后续模型连这个下限都突破不了说明你的特征工程或调参思路有问题。我的做法是先把原始特征做基本处理后直接训练LinearRegressionimport pandas as pd from sklearn.model_selection import train_test_split from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_squared_error, r2_score df pd.read_csv(data/collision_risk.csv) X df.drop(risk, axis1) y df[risk] # 分类变量独热编码 X pd.get_dummies(X, drop_firstTrue) X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) model LinearRegression() model.fit(X_train, y_train) pred model.predict(X_test) print(R2:, r2_score(y_test, pred)) print(RMSE:, mean_squared_error(y_test, pred, squaredFalse))这版跑出来的R²大约0.53RMSE约0.174。看这个R²基本可以得出两个结论第一特征和risk之间存在线性成分但不是全部第二一定存在非线性关系或特征交互需要用更复杂的模型来捕捉。3.2 风险值在0到1之间为什么不用逻辑回归答辩的时候几乎必被问到的问题risk取值在0到1之间看起来像概率为什么不用逻辑回归这时候要分清楚。逻辑回归解决的是二分类问题输出是属于某一类别的概率。而我们的risk是一个连续数值表示风险程度不是0/1标签。课程明确要求建立回归模型所以逻辑回归不是这个题目的合适选择。但如果risk被压缩到0-1区间且呈现明显的墙效应大量值接近0或接近1那可以考虑Beta回归或者用logit变换后做线性回归。我当时检查了risk的分布发现中间段样本占了大多数没有明显的墙效应所以直接按连续回归处理是合理的。这个分析我写进了文档的模型选择依据一节也是加分项。3.3 树模型为什么是这类项目的最优解在baseline之后我按顺序尝试了Ridge回归、随机森林回归、XGBoost回归。选树模型而不是更复杂的神经网络理由很实际数据量只有六千条深度学习容易过拟合而且解释性差树模型能自动处理非线性关系还能输出特征重要性对课程作业而言是最合适的。随机森林先上from sklearn.ensemble import RandomForestRegressor rf RandomForestRegressor( n_estimators300, max_depth10, min_samples_leaf3, random_state42 ) rf.fit(X_train, y_train) rf_pred rf.predict(X_test) print(RF R2:, r2_score(y_test, rf_pred))这版R²跑到了0.73比线性回归提升了将近0.2说明树模型确实能捕捉到非线性关系。接着上XGBoostimport xgboost as xgb xgb_model xgb.XGBRegressor( n_estimators500, learning_rate0.05, max_depth5, subsample0.8, colsample_bytree0.8, random_state42 ) xgb_model.fit( X_train, y_train, eval_set[(X_test, y_test)], verboseFalse )XGBoost这版R²直接到0.86RMSE降到0.098效果很明显。三个模型的对比我整理成了表格直接放在文档的实验部分模型R²RMSE线性回归0.530.174随机森林0.730.133XGBoost0.860.098这个表格是整份文档最有说服力的部分。老师不用看几十行代码一张表格就明白你做了哪些尝试以及为什么最终选XGBoost。4. 调参与优化我把R²从0.53提升到0.86的关键操作4.1 第一版结果很差不要慌先分析原因线性回归R²只有0.53的时候我没有急着调参而是先画了一张预测值和真实值的散点图。从图上可以很清楚地看到低风险段真实值在0.2-0.4预测偏高高风险段真实值在0.7以上预测偏低。这说明线性模型根本拟合不了风险随车速等特征快速上升的趋势。这个分析很重要因为它决定了优化的方向。如果模型欠拟合加数据是没用的需要加特征或换模型。如果是过拟合则需要简化模型或用正则化。线性回归0.53的R²属于明显欠拟合所以我直接转到树模型而不是花大量时间调线性回归的正则化参数。4.2 特征工程带来的提升比调参更大很多人调参上瘾觉得只要把n_estimators调到9999R²就能涨。实际上在数据质量和特征工程面前调参带来的提升非常有限。我做了一轮特征工程效果显著。第一个特征是速度风险分段。原始vehicle_speed是连续值但我观察到risk在速度超过80km/h后急剧上升所以构造了speed_high_risk变量当速度大于80时取1否则取0。这个特征在XGBoost的特征重要性里排名前三。第二个特征是天气和道路类型的交互项。单独看天气、单独看道路类型对risk的影响都比较平缓但雨天乡村道路组合的风险明显高于单独两个特征之和。我构造了weather_road_interaction把这两个分类变量的组合编码成一个新的分类特征。树模型对交互项的捕捉能力比较强加了之后R²提升明显。第三个特征是驾龄估计。driver_age是年龄但不能直接代表驾驶经验。我构造了driver_experience driver_age - 18并做了下限截断避免出现负数。这个特征补充了年龄之外的信息量。做完这三个特征后XGBoost的R²提升到了0.82。之后我再做调参才涨到0.86。说实话特征工程贡献了大部分提升调参只是锦上添花。4.3 网格搜索和交叉验证的正确用法调参我用了RandomizedSearchCV没有用全量网格搜索。原因是参数组合空间太大全量搜索太慢随机搜索在效率和效果之间更平衡。from sklearn.model_selection import RandomizedSearchCV param_grid { n_estimators: [300, 500, 800], learning_rate: [0.01, 0.05, 0.1], max_depth: [3, 5, 7], subsample: [0.7, 0.8, 1.0], colsample_bytree: [0.7, 0.8, 1.0] } search RandomizedSearchCV( xgb.XGBRegressor(random_state42), param_distributionsparam_grid, n_iter30, cv5, scoringr2, n_jobs-1, random_state42 ) search.fit(X_train, y_train) print(search.best_params_)这里有个细节scoringr2意味着交叉验证的目标是最大化R²。如果更关心误差大小可以改成neg_mean_squared_error。我当时两个指标都看了一下最终以R²为第一优化目标。交叉验证还有一个容易忽略的坑一定要在训练集内部做CV千万不能用测试集来帮你选参数。否则测试集的评估结果就失去意义了因为你已经在用测试集的信息做决策了。我在代码里先train_test_split后面所有的CV都在X_train内部进行X_test只用来做最终评估。4.4 早停机制防止树模型过拟合的高效手段XGBoost自带早停机制训练时如果连续多少轮验证集指标没有提升就自动停止。这个功能简直是为课程作业量身定做的因为没人想盯着训练过程等几百轮跑完。xgb_model xgb.XGBRegressor( n_estimators1000, learning_rate0.05, max_depth5, early_stopping_rounds50, eval_metricrmse, random_state42 ) xgb_model.fit( X_train, y_train, eval_set[(X_test, y_test)], verboseFalse )设置early_stopping_rounds50后模型会在验证集RMSE连续50轮不再下降时提前停止避免在n_estimators1000的设置下盲目训练到最后一轮。最终模型实际的树的数量大概在300多棵比1000少了很多过拟合风险更低训练速度也更快。5. 模型解释和可视化这是拉开分数差距的核心材料5.1 特征重要性分析让老师知道你读懂模型XGBoost训练完成后feature_importances_属性可以直接给出每个特征的贡献度。我把它画成了一张水平条形图并只展示top10特征。importance pd.Series( xgb_model.feature_importances_, indexX_train.columns ).sort_values(ascendingFalse) importance.head(10).plot.barh()从结果来看vehicle_speed排第一alcohol_level排第二speed_high_risk排第三。这三个特征从现实角度看完全合理速度快、酒驾、超速碰撞风险必然高。我在文档里专门用了一节写特征重要性结果符合领域常识说明模型学习到的规律具有可解释性这句话看起来很平淡但老师很吃这一套因为它证明了你不是在调参调出了一个黑盒而是真的理解业务逻辑。5.2 预测值和真实值对比以及残差分析除了特征重要性我还画了两张图。第一张是预测值vs真实值的散点图。理想情况下所有点应该落在yx这条对角线上。我这张图里点虽然大体分布在对角线附近但高值段明显有一些偏离说明模型对极端高风险的预测偏保守。这个问题在文档里如实写了反而显得分析客观。第二张是残差图。残差 真实值 - 预测值以预测值为横轴画散点图residual y_test - pred plt.scatter(pred, residual, alpha0.5) plt.axhline(0, colorred, linestyle--)残差图的理想状态是随机分布在0附近没有明显模式。我这张图里大部分残差集中在0附近但在预测值0.7以上的区域残差系统性偏正。这说明高风险段被低估了。如果后续要改进可以往这个方向想办法比如单独训练一个高风险样本的模型或者对目标变量做变换。5.3 文档里必须写的三个核心结论可视化不是画完就完了关键是要把结论写出来。我最终在文档的结果与分析章节只写了三个结论第一车辆速度是预测碰撞风险最重要的特征且影响呈非线性高速状态下风险快速上升。第二酒精水平显著影响risk模型对高酒精样本的预测基本准确。第三天气和道路类型的交互作用不可忽略雨雪天气下的乡村道路风险显著提高模型捕捉到了这一交互效应。这三个结论全部来自数据分析和模型结果每一条都有图表支撑。这种写法比直接贴一堆测试指标更有说服力也更容易拿高分因为老师能看出你对数据和模型的思考是完整的。6. 踩坑记录这些坑不避开分数一定被扣6.1 数据泄漏最隐蔽也最致命的错误数据泄漏是机器学习大作业里最容易犯、又最不容易发现的错误。常见形式有两种。第一种是把目标变量的成分当作特征。比如risk如果是根据碰撞类型、车速、安全带加权算出来的你把碰撞类型、车速、安全带当作特征模型当然会预测得很准但这是作弊。第二种是在数据预处理阶段用了全量数据的信息造成未来信息泄漏。比如先用全量数据做标准化、做填充均值、做类别编码然后再切分训练集和测试集。这样测试集的信息提前进入了训练过程评估结果会虚高。我当时的做法是先把数据train_test_split拆开然后分别在训练集上fit标准化器、填充器再transform训练集和测试集。虽然最后XGBoost不需要标准化但整条流水线必须保证先用训练集学参数再用这个参数处理测试集。这一点在文档里写明白老师会认为你理解训练测试分离的本质。6.2 独热编码处理不当导致维度爆炸原始数据里有两个高基数分类特征weather_condition有6个类别road_type有5个类别collision_type有4个类别。直接用pd.get_dummies问题不大但如果换成有几百个类别的大数据集独热编码会产生几千列稀疏特征线性模型在这种高维稀疏数据上很容易过拟合。我在这次作业里没有遇到维度爆炸问题但还是在代码里加了一步对低频类别做了合并。具体做法是统计每个类别的出现次数出现次数少于50的类别统一替换为other类别然后再做独热编码。这个操作对最终模型效果提升有限但对代码规范性和工程意识展示是有加分的。6.3 环境配置和依赖版本问题这个问题在课程作业里太常见了。很多同学在自己电脑上跑得好好的代码交上去老师一运行就报错版本不兼容。我当时在项目根目录放了一个requirements.txtpandas1.5.3 numpy1.24.3 scikit-learn1.2.2 xgboost1.7.6 matplotlib3.7.1 seaborn0.12.2 jupyter1.0.0同时在README里写清楚了推荐环境Python 3.9及以上Windows/macOS/Linux均可。这样评阅人不管用什么系统只要按步骤创建虚拟环境安装依赖就能跑起来。千万不要只用一句话需要安装pandas带过版本不锁定等于没写。6.4 代码提交中的低级错误我见过最可惜的情况是模型训练好了但代码里用了绝对路径C:/Users/xxx/Desktop/...老师换台电脑跑直接FileNotFoundError。我的习惯是所有数据路径都用相对路径项目根目录统一用Path(__file__).parent.parent定位。比如在src/train_model.py里数据路径写成from pathlib import Path import pandas as pd PROJECT_ROOT Path(__file__).parent.parent DATA_PATH PROJECT_ROOT / data / collision_risk.csv df pd.read_csv(DATA_PATH)这样不管项目文件夹放在哪个目录只要保持内部结构不变换台电脑直接能跑。这个细节看似不起眼但在评阅环节能省掉很多沟通成本。7. 源码结构和文档说明怎么写才配得上高分项目7.1 目录结构设计让老师一眼看懂源码组织得好不好直接影响印象分。我最后提交的目录结构是这样的collision-risk-project/ ├── data/ │ └── collision_risk.csv ├── notebooks/ │ ├── 01_EDA.ipynb │ └── 02_modeling.ipynb ├── src/ │ ├── __init__.py │ ├── data_preprocess.py │ ├── train_model.py │ └── predict.py ├── models/ │ └── xgboost_model.pkl ├── report/ │ ├── figures/ │ └── 实验报告.md ├── requirements.txt └── README.mddata目录放原始数据notebooks目录放探索性分析和建模过程的Jupyter Notebooksrc目录放模块化的Python脚本models目录放训练好的模型文件report目录放图表和实验报告。整个结构从上到下非常清晰。关键在于src里不放一次性代码放的必须是可复用的模块。比如data_preprocess.py里定义了load_data()、clean_data()、feature_engineering()三个函数train_model.py里调用这些函数。这样老师看代码时会觉得你有工程意识而不是把所有逻辑塞在一个大号notebook里。7.2 README和实验报告的核心内容README是所有代码的入口我写完后的目录变成了# 基于Python的回归模型预测碰撞几率Risk ## 项目简介 基于车辆碰撞记录数据构建回归模型预测碰撞风险值risk。 ## 环境依赖 - Python 3.9 - 依赖库见requirements.txt ## 快速开始 1. pip install -r requirements.txt 2. python src/train_model.py 3. python src/predict.py ## 结果摘要 - XGBoost回归模型 - R²: 0.86 - RMSE: 0.098实验报告是一份Markdown文档包含六章问题描述、数据说明、方法设计、实验结果、结果分析、结论与改进方向。每个章节我控制在500到800字总篇幅大概四千字左右。配上6张图表整个报告的观感就非常完整了。写作技巧我总结了一个经验实验报告不是代码注释的堆砌而是讲了一个完整的故事。故事从我拿到了一批碰撞数据目标是预测risk开始一路讲到数据有什么问题我如何处理、为什么选XGBoost、模型学到了什么规律、还有哪些不足。老师批改的时候看的就是这个思维链条而不是孤立的代码片段。7.3 答辩演示的三条实用建议如果这个项目需要答辩我再分享三条经验。第一条不要在演示时从头到尾念代码没有人喜欢听代码讲解。要做的是准备三张关键的图risk分布图、模型对比表格、特征重要性条形图。讲的时候围绕我发现了什么、我怎么处理、模型效果如何、为什么这个结果可信来展开。第二条预判评委可能会问的问题。我在答辩前自己列了五个问题为什么用XGBoostrisk是0到1为什么不用逻辑回归怎么证明没有数据泄漏特征重要性结果和领域常识是否一致模型有没有改进空间这五个问题我全部在报告里找到了对应的答案答辩时基本没有冷场。第三条准备好模型的实际预测演示。我当时用predict.py写了一个函数输入一条碰撞记录的字段直接输出risk预测值。演示时老师随机编了一条车速90km/h、雨天、乡村道路、未系安全带、酒精值0.15的记录模型的预测值确实比平均水平高出一截。这个现场演示比任何言辞都有说服力直接打消了模型会不会只是个摆设的疑虑。最后再分享一点自己的体会。做这个项目最大的收获不是学会了XGBoost怎么调参而是理解了一个完整的机器学习项目应该长什么样。从数据理解到特征工程从模型选型到结果解释每一步都有它的逻辑每一步都会踩坑而这些坑恰恰是学费最值钱的部分。如果你现在正在做类似的作业别急着抄代码先把你手里那份数据每一列的含义搞清楚再想清楚我要回答什么问题。这两件事想透了后面的一切都是水到渠成。本文还有配套的精品资源点击获取
返回列表