ARTICLE DETAIL

资讯详情

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

共享单车租赁预测:随机森林与SVR的对比实战

共享单车租赁预测:随机森林与SVR的对比实战 简介这是一份Python数据分析与可视化实战项目聚焦共享单车租赁数量预测采用随机森林与支持向量机模型适合正在学习数据分析、机器学习及时间序列预测的开发者。压缩包共8个文件约37.41MB含IPython Notebook代码、CSV数据集、可视化结果PNG、HTML导出报告及配套说明覆盖从数据预处理、特征工程到模型训练与评估的完整流程。目前已有454人学习下载代码量约300行结构紧凑便于快速上手。借助Notebook与可视化图表读者可直观看到随机森林的特征重要性排序与预测效果并理解SVM在非线性关系建模中的应用同时掌握交叉验证和超参数调优等关键实操要点。1. 共享单车预测租赁数量先把回归问题的边界画清楚拿到共享单车租赁数据最容易踩的第一个坑不是模型而是问题类型。目标是“某小时租出多少辆单车”这是一个连续值预测属于回归任务如果误判成分类后续所有评估和调参都会偏。随机森林和支持向量机准确说是支持向量回归 SVR是这个场景里经常被拿来对比的两个模型随机森林无需特征缩放即可训练而 SVR 对数据尺度非常敏感必须先标准化。本文按一个约 300 行代码的 Python 数据分析与可视化项目来组织覆盖数据读入、特征构建、可视化、模型训练、调参与预测结果展示。读完可以直接照着写自己的版本也能看到两个模型在共享单车预测上的实际差异。2. 数据读入与特征构建共享单车租赁数据不能直接丢给模型这一章先处理数据。共享单车公开数据UCI Bike Sharing Dataset的 hour 版本一行代表某天某个小时的租赁记录样本量通常在 17000 行出头。字段看着不多但类型混着数值、类别和日期直接全列喂进 sklearn 会埋下隐患。先认字段再做特征最后画两张图确认直觉。2.1 字段怎么看bike sharing dataset 的列名和类型先列出 hour.csv 主要字段方便对照后面的代码。预测目标是cnt它是casual与registered之和代表该小时租赁总数。字段类型含义建模建议dtedayobject日期只用于拆出年、月、星期seasonint季节 1-4类别型yrint年份 0-1数值代表训练数据的时间范围mnthint月份 1-12类别型可保留数值hrint小时 0-23最重要的周期特征holidayint是否假日0/1 二值weekdayint星期几 0-6类别型workingdayint是否工作日0/1 二值weathersitint天气 1-4类别型4 类样本极少tempfloat归一化温度数值保留atempfloat体感温度数值保留humfloat归一化湿度数值保留windspeedfloat归一化风速数值保留cntint租赁总数预测目标读入后第一件事不是画图而是确认形状和缺失值。17999 行的元数据实际读进来后缺失值通常为 0但不能假设。用read_csv读入再看dtypes和空值数量确认后再决定特征怎么建。import pandas as pd df pd.read_csv(hour.csv) print(df.shape) print(df.dtypes) print(df.isnull().sum().max())这段代码输出三组信息行数列数、每列类型、最大缺失数量。如果最大缺失数量为 0后面可以跳过缺失值处理。dtypes用来判断哪些列是类别取值season、weathersit这类列虽然存储为 int但它们的数值之间没有等差关系后续特征构建时要有区分。2.2 特征构建把 datetime 和 hr 拆成可建模的 hour、month、weekdaydteday和hr是一对组合键前者表示日期后者表示小时。建模时不能把dteday原样交给 sklearn它是个字符串需要先从日期里拆出年份、月份和星期再把hr作为小时特征最后从特征列表里去掉dteday和hr之外的原始列。df[dteday] pd.to_datetime(df[dteday]) df[year] df[dteday].dt.year df[month] df[dteday].dt.month df[weekday] df[dteday].dt.weekday df[hour] df[hr] feature_cols [ season, yr, month, hour, holiday, weekday, workingday, weathersit, temp, atemp, hum, windspeed, ] X df[feature_cols] y df[cnt]这段代码的关键在于month和weekday由dteday生成hour由hr复制出来。这样特征列名统一为month、weekday、hour和原始数据解耦。注意dteday只包含日期时间在hr列里所以不能从dt.hour取小时。feature_cols里没有casual和registered它们与cnt是加和关系放进特征等于把答案告诉模型。year列也没有进入特征因为这份数据只覆盖一年多的范围年份没有太多泛化价值如果数据跨多年可以保留yr列它已经表达了年份差异。2.3 可视化检查租赁量与小时、天气的关系在训练之前先做一次可视化能提前发现两个模型都将遇到的数据形态。共享单车租赁量有明显的早晚高峰但这种规律不会直接显示在字段里必须画出来看。用groupby算每小时平均租赁量再用 matplotlib 画折线图这一步是整个 Python 数据分析与可视化项目里成本最低、回报最高的一环。import matplotlib.pyplot as plt hour_mean df.groupby(hour)[cnt].mean() plt.figure(figsize(8, 4)) plt.plot(hour_mean.index, hour_mean.values, markero) plt.xlabel(hour) plt.ylabel(average count) plt.title(Average rental by hour) plt.tight_layout() plt.savefig(hour_avg.png, dpi120)这张图通常会出现双峰曲线早晨 7 点到 9 点一次高峰傍晚 17 点到 19 点一次更高的高峰凌晨 3 点到 5 点几乎无人用车。看到这个形态后hour必须作为核心特征保留。接着看天气影响用 seaborn 的箱线图按weathersit分组import seaborn as sns plt.figure(figsize(6, 4)) sns.boxplot(datadf, xweathersit, ycnt) plt.savefig(weather_box.png, dpi120)箱线图会显示weathersit4时样本很少中位数和箱体形状可能异常。这时候不要急着删行而是确认测试集里是否出现该类别如果出现SVR 可能给出一个极端预测。两张图保存为 png 后可以进入模型部分。3. 随机森林回归和支持向量机两种模型的实现路径这一章先讲模型选型再给出可直接复现的代码。共享单车租赁预测是一个回归任务所以支持向量机在代码里表现为SVR而不是SVC。随机森林侧则用RandomForestRegressor。两个模型的 API 风格不同数据预处理要求也不同。3.1 为什么回归用 SVR 而不是 SVMSVM 通过寻找最大间隔超平面做分类输出的是类别SVR 的思路是设定一个间隔带允许预测值与真实值之间的误差落在带内超过带才计算损失。共享单车cnt是连续数值且范围从个位数到几百分类模型无法表达这种数量差异因此要用 SVR。随机森林回归则是用多棵决策树对同一个样本做预测再取平均。它能直接输出连续值不需要像 SVR 那样预设间隔带宽度。两者适合放在同一套评估里对比随机森林擅长捕捉小时、天气这类离散特征的组合SVR 对连续特征温度、湿度的非线性关系更敏感。实际项目中两个模型都要跑而不是根据“哪个先进”来决定。3.2 特征缩放SVR 绕不开的标准化步骤随机森林不关心特征量纲temp在 0 到 1hum也在 0 到 1直接训练没问题。SVR 使用核函数计算样本间距离数值范围不一致会让距离被量纲大的特征主导因此必须先标准化。流程固定为先切分训练集测试集再用训练集拟合StandardScaler最后 transform 两边。from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)代码里fit_transform只出现在训练集测试集只用transform。这是最常见的错误点把X_test也拿去fit_transform会引入测试集分布信息导致验证分数虚高。实际部署时线上数据要用训练好的scaler做同样的变换不能用别的统计量。3.3 最小可运行代码随机森林回归随机森林不需要缩放直接用X_train训练。先给一个最小可运行版本包含训练、预测、三个评估指标。from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score rf RandomForestRegressor( n_estimators200, max_depthNone, min_samples_leaf5, random_state42, n_jobs-1, ) rf.fit(X_train, y_train) y_pred_rf rf.predict(X_test) print(RF RMSE:, round(mean_squared_error(y_test, y_pred_rf, squaredFalse), 2)) print(RF MAE:, round(mean_absolute_error(y_test, y_pred_rf), 2)) print(RF R2:, round(r2_score(y_test, y_pred_rf), 4))max_depthNone让树自由生长min_samples_leaf5防止叶子节点过细n_jobs-1使用所有 CPU 核心。RMSE 对异常点敏感MAE 更能反映平均偏差共享单车数据中午夜与雨天计数差异很大RMSE 会高于 MAE这是长尾分布的正常现象。这里的random_state42保证结果可复现调参后重新运行会得到一致的数字。3.4 SVR 训练与参数表SVR 的默认参数可以直接出结果但效果通常不如随机森林要调整C、epsilon和gamma。先跑基线from sklearn.svm import SVR svr SVR(kernelrbf, C100.0, epsilon0.1, gammascale) svr.fit(X_train_scaled, y_train) y_pred_svr svr.predict(X_test_scaled) print(SVR RMSE:, round(mean_squared_error(y_test, y_pred_svr, squaredFalse), 2)) print(SVR MAE:, round(mean_absolute_error(y_test, y_pred_svr), 2)) print(SVR R2:, round(r2_score(y_test, y_pred_svr), 4))gammascale会根据特征数量自动计算适合当前 12 个特征的数据。参数作用见下表参数值作用调参方向kernelrbf高斯核处理非线性关系linear 更快但拟合不足C100惩罚系数越大越不容忍误差cnt数量级在几百C 放大到 10~100epsilon0.1间隔带宽度带内不计损失误差太大时增大到 1gammascale核函数影响半径过拟合时手动调小注意表里的 C 和 epsilon 是针对共享单车 count 原始数量级设定的。如果对目标取对数C 和 epsilon 需要重新搜索不能直接沿用。第一次跑出来的结果往往是 SVR 的 MAE 比随机森林差一些原因是cnt分布右偏SVR 对峰值不够敏感。解决思路是目标变换放在第 5 章讲先用当前版本进入调参和可视化。4. 参数调优与预测可视化脚本能跑通不代表项目能交付训练出基线只能算第二步共享单车租赁预测项目还要回答三个问题参数是否接近最优、哪些特征真正起作用、预测结果与真实值的偏差是否有规律。这一章把调参、特征重要性和预测可视化串起来。4.1 用 GridSearchCV 搜随机森林和 SVR 的关键参数手工调参容易顾此失彼网格搜索能一次性比较多组参数。先写随机森林的粗搜索from sklearn.model_selection import GridSearchCV param_grid_rf { n_estimators: [100, 200], max_depth: [None, 15], min_samples_leaf: [2, 5], } grid_rf GridSearchCV( RandomForestRegressor(random_state42, n_jobs-1), param_gridparam_grid_rf, cv3, scoringneg_root_mean_squared_error, n_jobs-1, ) grid_rf.fit(X_train, y_train) print(best RF params:, grid_rf.best_params_)scoring用neg_root_mean_squared_error因为 GridSearchCV 默认分数越高越好所以把 RMSE 取负。上面只有 8 组组合3 折交叉验证下随机森林要跑几分钟。SVR 不要直接复制这个参数空间因为 RBF 核 SVR 在近 14000 条训练样本上非常慢建议用RandomizedSearchCV或用 2 折。from sklearn.model_selection import RandomizedSearchCV import scipy.stats as st param_dist_svr { C: st.expon(scale100), epsilon: st.uniform(0.01, 1), gamma: st.uniform(0.01, 1), } search_svr RandomizedSearchCV( SVR(kernelrbf), param_distributionsparam_dist_svr, n_iter8, cv2, scoringneg_mean_absolute_error, n_jobs-1, random_state42, ) search_svr.fit(X_train_scaled, y_train) print(best SVR params:, search_svr.best_params_)随机搜索用分布定义参数范围n_iter8只抽 8 组配合 2 折交叉验证总训练次数降到一个可接受的范围。SVR 在调参时先固定一个较小的n_iter看结果趋势再放大直接跑 50 组会非常痛苦。4.2 特征重要性随机森林能告诉你什么特征在起作用随机森林自带feature_importances_可以排序并画条形图。这个功能只能用于树模型SVR 没有等价的可解释性输出。importance pd.Series( rf.feature_importances_, indexfeature_cols ).sort_values(ascendingFalse) plt.figure(figsize(8, 4)) importance.head(10).plot.barh() plt.gca().invert_yaxis() plt.title(Random Forest Feature Importance) plt.tight_layout() plt.savefig(feature_importance.png, dpi120)在共享单车数据上结果通常是hour排第一后面跟着temp、hum、weathersit。这符合直觉用户是否骑车首先取决于时间段其次取决于天气是否适合骑行。holiday和workingday的重要性一般低于上述特征因为节假日样本量占比小。如果误把casual、registered加进特征它们的权重会远超其他列模型 R2 接近 1但实际根本无法获取未来同时段的这两项数据属于典型的特征泄漏。4.3 预测结果可视化真实值与预测值对比和残差检查预测可视化用散点图最直白横轴是真实cnt纵轴是预测值理想情况点全落在 yx 对角线上。测试集约 3400 个点点大小要调小否则重叠后看不出密度分布。plt.figure(figsize(7, 5)) plt.scatter(y_test, y_pred_rf, s5, alpha0.5, labelRF) plt.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], r--) plt.xlabel(actual count) plt.ylabel(predicted count) plt.legend() plt.tight_layout() plt.savefig(pred_vs_actual.png, dpi120)真实值低时点集中在左下方预测较准真实值高时点明显偏到对角线下方说明模型低估了高峰时段租赁量。这是右偏目标造成的系统性问题。进一步检查残差residual y_test - y_pred_rf print(residual mean:, round(residual.mean(), 2)) print(residual std:, round(residual.std(), 2))残差均值接近 0说明没有整体高估或低估。残差标准差可以直接理解为预测误差的波动范围。如果残差均值为负代表预测普遍偏高反之偏低。这一步做完就可以进入最后一章的三个优化技巧。5. 让 300 行代码少踩坑三个实用技巧最后讲三个在共享单车租赁预测项目里值得写进代码的细节。它们不会让代码膨胀但会让结果更可靠。5.1 对 count 取对数再做回归预测完再还原cnt分布严重右偏凌晨接近 0早晚高峰几百SVR 对这类分布拟合困难。取对数能把大数值压缩让模型更关注相对误差。代码里用log1p和expm1成对处理避免计数为 0 时出现负无穷y_train_log np.log1p(y_train) svr.fit(X_train_scaled, y_train_log) y_pred_log svr.predict(X_test_scaled) y_pred_exp np.expm1(y_pred_log)log1p(y)等价于log(y 1)预测结果再用expm1还原。是否有效需要对比随机森林不取对数可能效果也不错因为树模型能靠切分直接逼近峰值区间SVR 取对数后 MAE 通常会下降。注意取对数后SVR 的 C 和 epsilon 必须重新调上一章参数表里的值不再适用。5.2 不要用原始 datetime 直接建模时间特征要拆到小时级别。dteday是日期字符串hr才是小时把dteday直接传给 sklearn 会报类型错误就算转成时间戳整数随机森林也会花很多切分去拟合时间轴却难以泛化到新月份。正确做法是保留hour、month、weekday再用holiday、workingday补充节假日信息。如果想进一步表达小时的周期性可以加np.sin(2 * np.pi * hour / 24)和np.cos(2 * np.pi * hour / 24)但一定要把新列同步到feature_cols否则后面画特征重要性时索引会错位。5.3 固定 random_state并把模型和 scaler 一起保存调参时每次运行结果必须可复现。RandomForestRegressor、SVR、train_test_split都固定random_state否则前后两次 RMSE 不同你会误以为模型被优化。项目收尾时用 joblib 保存模型SVR 场景要连同 scaler 一起保存import joblib joblib.dump(rf, bike_rf_model.joblib) joblib.dump(scaler, bike_scaler.joblib) loaded_rf joblib.load(bike_rf_model.joblib)训练好的模型、scaler、预测脚本三个文件放在一起部署时先对输入特征做scaler.transform再交给 SVR。随机森林不需要缩放但如果最终选用 SVR不保存 scaler 就等于把标准化环节丢在了训练阶段。保存完成后可以随机抽几条测试数据验证loaded_rf.predict结果与训练时一致再结束项目。本文还有配套的精品资源点击获取
返回列表