news 2026/10/18 15:32:58

城市共享单车租借量预测:Kaggle回归项目完整闭环

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
城市共享单车租借量预测:Kaggle回归项目完整闭环

简介:这是一份面向计算机、数据科学及相关专业学习者与从业者的Kaggle入门实战资源,围绕城市自行车共享系统使用状况展开数据分析与需求预测。项目基于赛题数据完成探索性分析与建模,借助Python脚本和Jupyter Notebook实现共享单车租赁量预测,适合作为课程设计、毕业设计或算法入门练习。压缩包共8个文件,其中数据文件3个、Python脚本2个、Notebook笔记2个、说明文档1份,整体仅823KB,结构紧凑,便于快速下载与本地复现。资源目前已有520人学习浏览,内容覆盖数据读取、特征探索、神经网络建模及结果可视化等环节,并配有项目说明文档,可帮助读者理解从数据清洗到预测输出的完整流程。对于希望快速上手机器学习项目、积累Kaggle竞赛经验或完成相似课题的同学,这份资源提供了可直接运行的代码与清晰的思路说明。

1. 城市自行车共享系统分析预测:入门 Kaggle 的第一道完整闭环

很多人拿到类似“python源码+项目说明”的压缩包后,第一反应是直接跑一遍脚本,看到打印出来的分数就开始惆怅:分数到底是什么意思?换几个参数还能不能涨?这个标题指向的项目,正好是 Kaggle 入门里少见的“数据量适中、业务含义清晰、评价指标明确”的回归任务:预测城市公共自行车系统按小时计的租借数量 count。它不需要图像、不需要 GPU、不需要分布式,只需要你熟悉 pandas 和 sklearn 就能走通从 csv 到提交文件的完整流程。这篇文章不逐行念源码包里的代码,而是讲清楚一个做数据挖掘的人拿到这个题后会怎么做——特征怎么构造、验证集怎么切、哪些参数值得调、哪些细节最容易让新手翻车。

2. 读懂字段与业务含义:预测目标到底是什么

2.1 先看字段表:一条租车记录里藏了哪些信息

解压资源包后,一般会看到数据文件 train.csv、test.csv 以及项目说明文档。train.csv 每一行是一条小时级的租借记录,字段表如下:

字段取值范围含义
datetime2011-01-01 00:00 起逐小时租借时刻
season1~4春、夏、秋、冬
holiday0/1是否节假日
workingday0/1是否工作日
weather1~4天气等级,1 最好,4 最差
temp归一化到 0~1实际温度
atemp归一化到 0~1体感温度
humidity0~100相对湿度
windspeed归一化到 0~1风速
casual非负整数临时注册用户租借量
registered非负整数正式注册用户租借量
count非负整数总租借量 = casual + registered

test.csv 里没有 casual、registered 和 count,这三列中 count 才是你的预测目标,casual 和 registered 在测试集里根本不存在,建议直接丢弃而不是拿来做特征。很多人第一次犯的错就是想把 casual 和 registered 也当特征送进模型,结果训练集和测试集特征不一致,直接报错。

2.2 画一下 count 的分布:为什么评价指标偏偏用 RMSLE

这个项目的官方评价指标是 RMSLE,即 Root Mean Squared Logarithmic Error。为什么要用对数?先画一下 count 的分布就明白了。

import pandas as pd import numpy as np import matplotlib.pyplot as plt df = pd.read_csv('train.csv') df['datetime'] = pd.to_datetime(df['datetime']) fig, axes = plt.subplots(1, 2, figsize=(12, 4)) axes[0].hist(df['count'], bins=50, edgecolor='white') axes[0].set_title('count 原始分布') axes[1].hist(np.log1p(df['count']), bins=50, edgecolor='white') axes[1].set_title('log1p(count) 分布') plt.show()

原始 count 严重右偏,大量低租借量样本集中在 0~100,少数极端值冲到 900 以上。对这种分布直接算 RMSE,模型会拼命拟合那几条大值尾巴,而忽略大多数普通时段。取 log1p 之后分布近似正态,模型会更均衡。log1p 就是 log(1 + x),加 1 是为了避免 log(0) 报错,因为租借量可以取 0,而 log(0) 是负无穷。

所以整个建模主线应该是:对训练集的目标 count 做 log1p 变换,模型预测的是对数空间的值,提交前再用 expm1 还原成真实租借量。RMSLE 天然惩罚“低估”比“高估”更狠,因此宁可预测略高一点,也不要预测为 0,这一点对后面处理极端天气时段很关键。

2.3 时间序列数据不能随机切分:按最后 21 天做验证集

train.csv 的时间跨度是逐小时连续的,日期本身有强趋势和周期性。如果用 sklearn 默认的 train_test_split 随机切分,模型会“偷看”未来数据,验证集分数很好看,一旦提交到测试集就现原形。常见做法是先把 datetime 排序,再按时间顺序切出最后 21 天作为验证集。

df = df.sort_values('datetime').reset_index(drop=True) split_point = df['datetime'].max() - pd.Timedelta(days=21) train_part = df[df['datetime'] <= split_point] valid_part = df[df['datetime'] > split_point] print(train_part.shape, valid_part.shape)

注意 reset_index(drop=True) 这一步不能省,否则后面索引错位会让你 debug 到怀疑人生。21 天是一个自然周期数,包含三周的工作日与周末,覆盖了 weekday 和 hour 的全部组合,验证结果比较可信。

3. 特征工程与基线模型:从 datetime 里挖出有效特征

3.1 拆解 datetime:hour、weekday、month、year 一个都不能少

原始数据只有一个 datetime 字符串,但自行车租借量对“几点钟、星期几、几月份”极其敏感,这些信息不会自己变成列,必须手动拆。这里用 pandas 的 dt 访问器一步到位:

def parse_time(df): df = df.copy() dt = pd.to_datetime(df['datetime']) df['hour'] = dt.dt.hour df['weekday'] = dt.dt.weekday df['month'] = dt.dt.month df['year'] = dt.dt.year return df train_part = parse_time(train_part) valid_part = parse_time(valid_part)

hour 的取值范围是 0~23,代表一天中的小时,这是最重要的周期性特征;weekday 的 0 是周一、6 是周日;month 代表季节内的月份趋势;year 代表整体增长趋势,因为城市共享单车系统在按年扩张,租借量一年比一年高。把这四个字段加进特征列表,再用随机森林跑一轮基线,基本能拿到一个不错的初分。

不要试图把 datetime 本身当数值特征送进模型,它的时间戳数值巨大且无单调语义,对树模型只会造成无意义的分裂。我习惯的做法是拆出这四个字段后,把原始的 datetime 列保留在 DataFrame 里用于最后对齐,但绝不选入特征集。

3.2 业务特征:早晚高峰标记与极端天气合并

只靠拆时间还不够,因为租借量背后是通勤行为。工作日的早高峰 7~9 点和晚高峰 17~19 点会形成两个明显尖峰,而周末的高峰出现在午后。直接构造一个 rush_hour 标记能把树模型的分裂效率提一大截:

def make_business_features(df): df = df.copy() df['rush_hour'] = ( (df['workingday'] == 1) & df['hour'].isin([7, 8, 9, 17, 18, 19]) ).astype(int) df['weekend_afternoon'] = ( (df['workingday'] == 0) & df['hour'].between(12, 16) ).astype(int) df['weather'] = df['weather'].replace(4, 3) return df

weather 字段的含义是天气等级,4 代表暴雨、暴雪等极端天气。但这个等级在训练集里样本极少,模型学不到规律,反而会被它带偏。常见做法是把 4 合并到 3,统一当成“差天气”处理。temp 和 atemp 高度相关,可以二选一,也可以保留让树模型自己决定,随机森林对冗余特征并不敏感。

还有一个容易被忽略的点:humidity 和 windspeed 有时会有极端值,比如 windspeed 为 0 不代表无风,而是该小时风速低于某个阈值被归零,不需要做缺失填充。把这些业务特征加上后,特征列表大概长这样:

feature_cols = [ 'season', 'holiday', 'workingday', 'weather', 'temp', 'atemp', 'humidity', 'windspeed', 'hour', 'weekday', 'month', 'year', 'rush_hour', 'weekend_afternoon' ]

3.3 基线模型:随机森林和自定义 RMSLE 评估函数

特征准备好了,先别上花哨模型,用随机森林跑一个基线。随机森林对特征尺度不敏感,不用归一化,能直接处理数值和类别特征,是这类表格型任务最好的起步模型。目标值记得做 log1p:

from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_squared_error def rmsle(y_true, y_pred): y_pred = np.clip(y_pred, 0, None) return np.sqrt( mean_squared_error(np.log1p(y_true), np.log1p(y_pred)) ) model = RandomForestRegressor( n_estimators=200, max_depth=None, min_samples_leaf=1, random_state=42, n_jobs=-1, ) model.fit(train_part[feature_cols], np.log1p(train_part['count'])) pred_log = model.predict(valid_part[feature_cols]) pred = np.expm1(pred_log) print('RMSLE:', rmsle(valid_part['count'], pred))

这里有几个参数需要说明。n_estimators=200 是起步值,之后可以加大到 500 甚至 1000 看分数是否继续下降;max_depth=None 表示让树完全生长,随机森林本身靠样本和特征随机性控制过拟合,不需要像 GBDT 那样限制深度;min_samples_leaf=1 表示叶子节点不设最小样本数,在入门阶段先用默认值,过拟合风险交给后验验证集来判断;random_state=42 保证每次运行结果一致;n_jobs=-1 让所有 CPU 核心并行训练。rmsle 函数里先用 clip 把负预测值切成 0,再取 log1p,这一步防止预测出负租借量导致 log 报错。

4. 交叉验证与调参:三个必调参数和一套有效流程

4.1 时间序列交叉验证:为什么 KFold 在这里不适用

很多人用随机森林调参时习惯直接套 GridSearchCV 加 KFold,但在这种逐小时连续的数据上,KFold 随机打乱会造成数据泄露,验证分数会虚高。正确做法是用 TimeSeriesSplit,它按时间顺序切出递增的训练窗口:

from sklearn.model_selection import TimeSeriesSplit tscv = TimeSeriesSplit(n_splits=5) scores = [] for train_idx, valid_idx in tscv.split(df): X_tr = df.iloc[train_idx][feature_cols] y_tr = np.log1p(df.iloc[train_idx]['count']) X_va = df.iloc[valid_idx][feature_cols] y_va = np.log1p(df.iloc[valid_idx]['count']) model = RandomForestRegressor( n_estimators=200, max_depth=None, min_samples_leaf=1, random_state=42, n_jobs=-1, ) model.fit(X_tr, y_tr) pred = np.expm1(model.predict(X_va)) scores.append(rmsle(np.expm1(y_va), pred)) print('各折 RMSLE:', np.round(scores, 4)) print('平均 RMSLE:', round(np.mean(scores), 4))

TimeSeriesSplit 的 n_splits=5 会生成 5 组训练集和验证集,训练集一次比一次大,验证集始终在训练集之后,严格保持时间顺序。这比手动切一个验证集更稳,因为你可以在 5 段不同时期上验证模型稳定性,避免某一时期的天气异常造成的偶然偏差。

4.2 三个必调参数:影响最直接的超参数

随机森林在这个项目上真正值得调的超参数就是下面三个,其他参数在入门阶段动了也白动。

参数建议范围作用与调整方向
n_estimators100~1000树的数量。越多越稳,但收益递减,调到分数不再变化即可
max_depth10~30树的最大深度。限制深度可以抑制过拟合,数据量大时效果明显
min_samples_leaf2~10叶子节点最少样本数。调大能让模型更平滑,对抗噪声

调整顺序我一般这么来:先固定 n_estimators=200,扫一遍 max_depth 观察验证分数;然后固定最优 depth,扫 min_samples_leaf;最后加大 n_estimators 到分数不再明显下降。不要一上来就 GridSearchCV 把所有参数一起搜,网格搜索的组合爆炸会让你等上几个小时还未必有结果。

4.3 手动调参循环:一份可以照着跑的模板

以下是我常用的一组手动调参代码,重点看循环结构和输出方式,你可以直接套用:

depth_list = [10, 15, 20, 25, None] for depth in depth_list: scores = [] for train_idx, valid_idx in tscv.split(df): X_tr = df.iloc[train_idx][feature_cols] y_tr = np.log1p(df.iloc[train_idx]['count']) X_va = df.iloc[valid_idx][feature_cols] y_va = np.log1p(df.iloc[valid_idx]['count']) model = RandomForestRegressor( n_estimators=200, max_depth=depth, min_samples_leaf=5, random_state=42, n_jobs=-1, ) model.fit(X_tr, y_tr) pred = np.expm1(model.predict(X_va)) scores.append(rmsle(np.expm1(y_va), pred)) print(f'depth={depth}: 平均 RMSLE={np.mean(scores):.4f}')

跑完这组循环,你会看到 max_depth 从 10 加到 20 时分数明显下降,之后趋于平稳甚至反弹,那个拐点就是当前数据量下的最优深度。min_samples_leaf 的扫描同理,把上面代码里的 depth_list 换成 [1, 2, 5, 10] 即可。

纯手动循环看起来土,但这正是这个项目最值得做的部分:你能直观看到每个参数改变后分数怎么变,建立起“参数-性能”的直觉。等这套流程跑熟了,再去用 Optuna 或者 GridSearchCV 自动化也不迟。

4.4 模型融合:把随机森林和梯度提升树加权平均

单模型调到一定程度后分数会卡住,常见做法是引入第二个模型梯度提升树 GBDT,和随机森林做加权平均。GBDT 对异常值和噪声更敏感,但往往能捕捉到随机森林忽略的局部模式。

from sklearn.ensemble import GradientBoostingRegressor model_gbdt = GradientBoostingRegressor( n_estimators=300, max_depth=6, learning_rate=0.05, min_samples_leaf=5, random_state=42, ) model_rf = RandomForestRegressor( n_estimators=500, max_depth=20, min_samples_leaf=5, random_state=42, n_jobs=-1, ) model_rf.fit(X_tr, y_tr) model_gbdt.fit(X_tr, y_tr) pred_rf = np.expm1(model_rf.predict(X_va)) pred_gbdt = np.expm1(model_gbdt.predict(X_va)) pred_blend = 0.6 * pred_rf + 0.4 * pred_gbdt

权重 0.6/0.4 是根据两个模型在验证集上的单模型分数估算的,随机森林一般略优,所以权重大一些。融合后 RMSLE 通常能再降 0.01~0.02,别小看这点提升,竞赛排名往往就差在这。GBDT 的训练速度比随机森林慢很多,调参时要有点耐心,learning_rate 调小后 n_estimators 必须加大才能保证效果。

5. 常见问题与避坑:5 个让分数翻车的细节

5.1 随机划分验证集导致分数虚高,提交后大跌

现象:用 train_test_split 随机切分,验证集 RMSLE 只有 0.6 左右,开心地提交,线上分数直接涨到 1.0 以上。

原因:这是时间序列数据,随机切分让模型在训练时看到了验证集时间段的信息,尤其是节假日和天气模式,模型等于开卷考试。

解决:统一用 TimeSeriesSplit 或按时间手动切分,验证分数才有参考意义。拿这个项目练手时,我吃过一次亏,之后凡是带时间列的数据,我默认先按时间排序再切分,再也不信任随机划分的结果。

5.2 log 变换与 expm1 还原不对称导致预测值出现负数

现象:验证集上计算 RMSLE 时报错,提示 log 遇到负值,检查发现预测值里有负数。

原因:模型在 log1p 空间训练,但预测时忘了还原,或者在还原前没有做 clip。极端天气下,模型可能预测出很小的值,expm1 还原后接近 0,但偶尔会有负值。

解决:在自定义 RMSLE 里先 np.clip(y_pred, 0, None),提交前同样对所有预测值做 clip。这个细节不处理,轻则报错,重则提交文件里出现负数租借量,直接得零分。

5.3 season 和 month 信息高度重叠,特征重要性严重重复

现象:模型输出特征重要性排行榜,season 排在前面,但把它删掉后分数几乎不变。

原因:season 只有 1~4 四个取值,本质上就是从 month 粗粒度分出来的,两个字段携带的信息大部分重叠。树模型会把特征在不同深度重复使用,造成冗余。

解决:保留 month 删除 season,或者保留 season 做 one-hot 删除 month,二选一。我试过同时保留,RMSLE 没有改善,还多了一列无意义的特征,不如直接删掉 season 让模型把精力放在 hour 和 weekday 上。

5.4 weather=4 的样本极少,模型把极端天气当噪声

现象:weather 等级为 4 的时段预测严重偏高,明明下暴雨,模型还预测出两三百的租借量。

原因:整个训练集里 weather=4 的样本寥寥无几,树模型在分裂时几乎不会选中这个条件,等于完全忽视了极端天气。

解决:先把 weather=4 合并到 3,保证每个类别都有足够样本。同时可以额外构造一个 bad_weather 二元特征,把 3 和 4 标为 1,让模型至少知道这种天气对租借量有明显的抑制作用。

5.5 周末和工作日的行为规律完全不同,一个模型吃不下两种模式

现象:验证集上工作日时段的预测误差大,周末时段的预测也有系统性偏差,模型整体分数卡在 0.7 上不去。

原因:工作日租借量主要来自通勤,早晚高峰极陡;周末是休闲骑行,高峰午后平缓。把这两类模式塞进同一个模型,树模型需要多学一倍的分裂规则才能勉强兼顾。

解决:构造 hour 与 workingday 的交互特征 hour_workingday,把工作日的 24 小时和周末的 24 小时编码成 48 个取值,让树模型直接按“周几+几点”分裂。更进一步的做法是分别建模,两个模型各管一类,但会对特征工程和调参工作量翻倍,入门阶段先做交互特征就够用了。

6. 进阶技巧与验证:用预测结果反推业务洞察

模型调完不是终点,拿验证集上的预测结果做一次业务向的分析,才能体现这个项目的完整价值。按 weekday 和 hour 分组,对比真实值与预测值的平均租借量,可以直观看出模型在哪类时段上不够准,也能得出一些有意思的结论:

result = valid_part[['datetime', 'count']].copy() result['pred'] = pred result['weekday'] = result['datetime'].dt.weekday result['hour'] = result['datetime'].dt.hour summary = result.groupby(['weekday', 'hour'])[['count', 'pred']].mean() summary['error'] = summary['pred'] - summary['count'] worst_hours = summary['error'].abs().sort_values(ascending=False).head(5) print(worst_hours)

从分组表里我很容易发现,模型在早晚高峰的预测普遍偏低,因为高峰时段受天气影响波动大;反而是平峰时段预测很稳。这个结论反过来指导我调参:与其再死磕全局参数,不如去优化高峰时段特征。我还养成了一个习惯,每次调完参都做一次这类分组验证,而不是只看一个总分,这样模型出了问题能立刻定位到具体时间段。另一个提交前的细节是索引对齐,test.csv 的 datetime 顺序不能动,预测结果按行号写回去就行,别做任何重排。这个项目跑完,你对 pandas 操作、特征工程、交叉验证、模型融合这套流程就有了整套手感,再去做其他表格类竞赛,骨架都是现成的。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/18 15:27:25

CD276/B7-H3功能抗体应用指南:从实验设计到体内模型

前阵子我这边一个做肿瘤免疫的同学在群里问了个很实在的问题&#xff1a;拿到一株CD276&#xff08;B7-H3&#xff09;的功能抗体&#xff0c;除了按说明书跑流式&#xff0c;还能往哪些方向做&#xff1f;这问题看起来基础&#xff0c;但真要认真回答&#xff0c;牵扯的东西不…

作者头像 李华
网站建设 2026/10/18 15:24:46

老照片上色全攻略:三种实用方法一次掌握

黑白老照片上色这事&#xff0c;我做了不少年&#xff0c;从最早的画笔一点一点涂&#xff0c;到后来用图层混合模式偷懒&#xff0c;再到现在AI一键生成打底稿&#xff0c;整套流程算是踩过不少坑也攒了不少心得。这个标题其实点得很准——上色本身不难&#xff0c;难的是“简…

作者头像 李华
网站建设 2026/10/18 15:24:20

地下水控制技术规范解读:降水帷幕回灌与基坑安全实践

简介&#xff1a;这是浙江省中小建筑企业协会批准发布的团体标准《工程建设地下水控制技术规范》&#xff0c;标准编号为T/ZZXJX 155-2022&#xff0c;由浙江光大建设集团有限公司、浙江中立建设有限公司等单位共同主编&#xff0c;适用于浙江省内工程项目建设过程中地下水控制…

作者头像 李华
网站建设 2026/10/18 15:23:54

专业化健康管理实训室建设指南:从设备选型到课程融入

专业化健康管理实训室&#xff0c;助力康养人才精准培育这几年我陆续参与了几个康养类职业院校的实训室建设项目&#xff0c;期间走了一圈下来最大的感受就是&#xff1a;健康管理实训室和传统的护理实训室完全是两码事。很多学校在建设初期把重点放在了“设备多不多、房间够不…

作者头像 李华
网站建设 2026/10/18 15:23:16

控制计划编制与现场审核:从PFMEA到落地执行的方法

简介&#xff1a;这是一份围绕控制计划&#xff08;Control Plan&#xff09;编制的PPT课件&#xff0c;面向制造业质量工程师、汽车行业质量管理人员及参与APQP项目的人员&#xff0c;系统讲解CP的概念、定义、目的与实施时机。内容覆盖ISO/TS 16949对APQP输出方法的要求&…

作者头像 李华