资讯动态

Kaggle自行车共享预测入门:从RMSLE到特征工程的避坑指南

发布时间:2026/10/18 22:34:02 来源:尧图企业网站定制
简介面向数据分析初学者与毕业设计场景的 Kaggle 入门实战包围绕城市自行车共享系统使用状况展开探索性分析与需求预测。项目基于真实赛题数据提供完整 Python 源码与说明文档适合作为课程设计、初期项目演示或算法练习素材内容覆盖数据预处理、特征工程、神经网络建模等核心环节对希望接触机器学习完整流程的读者具有明确借鉴价值。压缩包共 8 个文件以 CSV 数据集、Python 脚本、Jupyter Notebook 和 Markdown 说明为主其中 Notebook 便于分步复现分析过程Python 脚本可直接运行训练与预测说明文档则梳理了项目背景与实现思路整体大小仅 823KB轻量易用。目前已有 520 人学习下载。通过该资源可一次性获得赛题数据、建模代码、运行演示及项目笔记既能快速跑通基线方案也能在此基础上调整参数、扩展特征用于后续优化实验或写入毕业设计文档。1. 城市自行车共享系统使用状况分析及预测这个Kaggle入门项目把特征工程的坑一次教全很多人入门Kaggle时都盯着泰坦尼克号或房价预测但真正让基本功一次性长齐的往往是城市自行车共享系统使用状况分析及预测这个项目。它的数据集中字段不复杂、单机就能跑难点在于预测目标自带强周期性和两种完全不同的用户群体差异评估指标也和常见回归不太一样。直接建模的人很容易做出一个“本地分数不错、提交却翻车”的模型。这篇文章从评估指标、特征工程到多模型对比把每一步能落地的代码、参数含义和边界条件写清楚新手能照做已经跑过一遍的人也能回来补几个坑。2. 预测目标与RMSLE指标先想清楚要预测什么、按什么打分2.1 预测目标与字段组织方式这个任务的目标是根据日期时间、天气、季节等特征预测某个小时内城市自行车共享系统的租赁总数量。训练数据按小时粒度组织datetime字段从年到小时都有其余字段有季节、节假日、工作日、天气、温度和风速等。有个细节容易被忽略数据集里casual和registered分别代表临时用户和注册用户的租赁量count是两者之和。测试集里没有casual和registered只有datetime和环境特征。也就是说最终提交的预测对象是count。至于怎么用这个拆分结构后面单独讲。2.2 评估指标RMSLE为什么不用RMSE也不直接用RMSE这个比赛的评估指标是RMSLE根均方对数误差。它和RMSE的核心差别在于RMSE对大数值的误差更敏感而自行车租赁量分布严重右偏凌晨时接近0晚高峰能到几百。如果按RMSE优化模型会把几乎所有精力花在减少峰值时段的偏差上对租赁量较低的时段几乎放弃。RMSLE在计算时先取对数再算误差相当于把“预测值翻倍”和“真实值翻倍”视为同等严重。对数变换后凌晨时段的几个单位误差和高峰时段的几十个单位误差在权重上更容易被模型同时兼顾。先实现这个指标后面的所有验证都用同一个函数import numpy as np def rmsle(y_true, y_pred): # 预测值可能为负先截断到0避免log(1x)出现NaN y_pred np.clip(y_pred, 0, None) # log1p log(1x)同时在0附近保持数值稳定 return float(np.sqrt(np.mean((np.log1p(y_pred) - np.log1p(y_true)) ** 2)))逻辑说明预测值在进入对数前必须截断为非负数否则log1p会算出NaN真实值本身非负不需要额外处理。项目中所有模型验证、早停、提交前检查都用这个函数不要在本地验证里换一套指标否则提交时容易对不上。2.3 字段表与第一次数据检查拿到数据后先做两层检查字段完整性、训练集和测试集的时间范围。下面这个表列一下核心字段的实际情况字段类型含义与处理建议datetime字符串解析为pandas datetime拆出hour、weekday、month、yearseason数值编码1春2夏3秋4冬注意顺序不代表大小holiday0/1是否节假日workingday0/1是否工作日与hour的交互很关键weather1-41晴2雾3小雨/雪4极端天气temp / atemp连续温度与体感温度相关性极高humidity连续湿度百分比windspeed连续风速casual / registered整数训练集才有测试集没有不能拿来当特征count整数目标变量等于casual registered第一次数据处理时先解析datetime并验证count与casual、registered的关系。常见做法是直接算一遍加法校验import pandas as pd train pd.read_csv(train.csv) test pd.read_csv(test.csv) # 校验count是否等于casual与registered之和 diff (train[casual] train[registered] - train[count]).abs().sum() print(count校验差值合计:, diff) # 解析时间并拆出常用时间特征 for df in (train, test): df[datetime] pd.to_datetime(df[datetime]) df[hour] df[datetime].dt.hour df[weekday] df[datetime].dt.weekday df[month] df[datetime].dt.month df[year] df[datetime].dt.year print(train.shape, test.shape) print(train[count].describe())逻辑说明这一步解决两件事。第一确认数据没有被破坏count确实由两部分相加得到第二把时间戳拆成hour、weekday等结构化特征后续直接使用。hour直接暴露在特征里后模型能直接感知上下班高峰这是整个项目最核心的信号之一。3. 数据处理与特征工程从datetime拆分到让模型识别工作日高峰3.1 时间特征的周期化处理hour和weekday都是周期性变量。如果直接把hour作为数值特征喂给线性模型模型会认为23点距离0点和距离18点一样远这与真实情况不符。常见做法是把时间映射到三角函数空间构造周期特征import numpy as np for df in (train, test): # 构造小时周期特征 df[hour_cos] np.cos(2 * np.pi * df[hour] / 24) df[hour_sin] np.sin(2 * np.pi * df[hour] / 24) # 构造星期周期特征 df[weekday_cos] np.cos(2 * np.pi * df[weekday] / 7) df[weekday_sin] np.sin(2 * np.pi * df[weekday] / 7)逻辑说明cos和sin把周期变量映射到单位圆上让线性模型能感知“23点与0点很接近”这一事实。对随机森林这种基于分裂的模型来说sin/cos不一定比原始hour更有用但保留它们不会损害树模型的性能还能让线性模型受益。周期特征项目里通常两种方案都试一下而不是默认加上。3.2 工作日与小时的交互这个项目最重要的业务特征观察租赁数据你会发现一个明显规律工作日的租赁曲线呈双峰形态早高峰8点左右、晚高峰17到18点周末和节假日则是中午到下午的单峰形态。如果只把hour和workingday分别作为独立特征树模型需要靠大量分裂才能学到这个交互而显式构造交互特征可以显著降低模型的学习难度# 用hour与workingday构造交互特征 for df in (train, test): df[hr_workingday] df[hour] * df[workingday] df[hr_holiday] df[hour] * df[holiday]逻辑说明hr_workingday让模型在同一个特征里可以直接区分工作日的8点和周末的8点。hr_holiday则是补充维度holiday为1的样本与周末行为模式类似但工作日标志为0单独乘出来能让模型更容易记住假日的特殊形态。交互特征加完之后建议检查一下训练集和测试集的特征分布防止某个交互值在测试集里完全没出现过。3.3 温度与体感温度的处理temp和atemp的相关性通常在0.99左右两个特征同时保留对线性模型会造成明显的多重共线性对树模型的影响稍小但也属于冗余特征。常见做法是保留temp删除atemp或者保留atemp删除temp二者都试一下选择本地RMSLE更低的方案。注意删除特征不要只凭相关系数要跑一遍验证集对比。# 观察temp与atemp的相关性 print(temp与atemp相关系数:, train[temp].corr(train[atemp])) # 项目中常见的做法删除atemp保留temp train train.drop(columns[atemp]) test test.drop(columns[atemp])逻辑说明相关特征保留两个不会让树模型崩溃但会增加线性模型的不稳定性。在排除冗余特征时核心判断标准是本地RMSLE是否变化如果删除后指标几乎不变说明这个特征是纯冗余。3.4 稀疏天气类别的合并weather字段共4个取值其中天气4对应暴雨暴雪等极端情况。实际数据里这个类别的样本极少远不足其他类别的零头。样本太少会导致模型在训练时几乎分不到有效分裂甚至某些实现会报类别缺失错误。常见做法是将weather为4的样本合并到weather为3语义上也比较顺因为3本身就是小雨小雪同属降水天气。# 合并稀疏天气类别 for df in (train, test): df.loc[df[weather] 4, weather] 3 # 确认合并后的类别分布 print(训练集weather分布:\n, train[weather].value_counts()) print(测试集weather分布:\n, test[weather].value_counts())逻辑说明合并前先确认测试集里同样没有weather为4的样本然后再做替换。这套操作在处理类别极不均衡的分类特征时算是常规动作替换顺序和替换目标取决于业务含义这里把4合并到3是因为两者都代表降水天气逻辑自洽。3.5 特征列整理与缺失值兜底即使这个数据集相对干净也要养成检查缺失值的习惯。windspeed在部分样本中可能为0humidity偶有异常但一般不会缺。实际表格数据中测试集的缺失处理需要单独准备不能直接拿训练集的均值去填而不确认列名是否对齐feature_cols [ hour, hour_cos, hour_sin, weekday, weekday_cos, weekday_sin, month, year, season, weather, holiday, workingday, temp, humidity, windspeed, hr_workingday, hr_holiday ] X_train train[feature_cols].fillna(train[feature_cols].median()) X_test test[feature_cols].fillna(train[feature_cols].median()) print(特征列数量:, len(feature_cols)) print(训练特征形状:, X_train.shape) print(测试特征形状:, X_test.shape)逻辑说明用训练集的median填充测试集而不是用测试集自身的统计量填充是为了避免训练和推理时的数据分布漂移。这一步做完后特征工程基本收敛可以开始建基线模型。4. 从基线到集成线性回归、随机森林与XGBoost的调参路径4.1 先建一个log1p线性基线特征工程做完之后第一个模型建议用线性回归。它训练快、结果可解释主要目的是验证特征工程的正确性而不是追求最优分数。由于评估指标是RMSLE常见做法是先把目标变量做log1p变换让模型在log空间训练这样优化目标和评估指标保持同构from sklearn.linear_model import LinearRegression # 目标做log1p变换 y_train_log np.log1p(train[count]) # 训练线性回归基线 lr LinearRegression() lr.fit(X_train, y_train_log) # 预测并反变换 preds_log lr.predict(X_test) preds np.expm1(preds_log) # 预测值可能为负提交前clip preds np.clip(preds, 0, None) submission pd.DataFrame({ datetime: test[datetime], count: preds }) submission.to_csv(baseline_lr.csv, indexFalse)逻辑说明这里的核心逻辑是“训练时用log1p、预测后expm1”。模型学的是log(1count)反变换后得到的才是count预测值。因为RMSLE本身在log空间计算这样处理能让优化目标和评估指标一致这是项目里的通用做法。4.2 随机森林参数含义与调优方向线性基线跑完后下一个值得试的模型是随机森林。它对特征缩放不敏感、天然处理交互在这个项目上通常能明显超过线性回归。核心参数有几个需要理解清楚from sklearn.ensemble import RandomForestRegressor rf RandomForestRegressor( n_estimators300, max_depth15, min_samples_leaf3, max_features0.7, random_state42, n_jobs-1 ) rf.fit(X_train, y_train_log) importances pd.Series(rf.feature_importances_, indexfeature_cols).sort_values(ascendingFalse) print(importances.head(10))参数说明n_estimators是树的数量通常300到500边际收益就不大了更多树只拖慢推理速度max_depth控制在15左右防止过深拟合噪声min_samples_leaf设成3让叶子节点至少有3个样本避免产出极端预测值max_features设为0.7即每棵树随机选取70%的特征参与分裂降低树间相关性。特征重要性打印出来之后重点看hour、hr_workingday、temp是否排在前列如果hour和hr_workingday没进前五说明特征构造或数据解析出了问题。随机森林的调参顺序一般是先定max_depth再调min_samples_leaf最后看n_estimators不要上来就调max_features。每次只动一个参数用时间序列验证来对比不要依赖随机K折结果。4.3 XGBoost面对树的复杂度与学习率XGBoost在这个数据集上的表现通常和随机森林接近或略好训练速度和调参空间更大。它的关键参数组合和随机森林不太一样from xgboost import XGBRegressor xgb XGBRegressor( n_estimators600, learning_rate0.05, max_depth5, subsample0.8, colsample_bytree0.8, reg_lambda1.0, random_state42 ) # 先拟合后续替换为带早停的版本 xgb.fit(X_train, y_train_log, verboseFalse) preds_log xgb.predict(X_test) preds np.clip(np.expm1(preds_log), 0, None)参数说明learning_rate取0.05是项目里的常见折中调低到0.01会需要更多树且训练时间变长max_depth在这个表格型项目里通常3到6就够太大的深度容易在小数据上迅速过拟合subsample和colsample_bytree分别控制行和列的采样比例0.8是常用起点reg_lambda是L2正则默认1.0如果本地验证集上训练误差和验证误差差距过大优先把这个值调大而不是减小learning_rate。XGBoost的n_estimators应该配合早停使用而不是拍一个固定值。早停需要在独立验证集上进行具体做法见下一章的时间序列验证。4.4 模型对比与Stacking的收益边界到这里通常会面对一个选择随机森林和XGBoost二选一还是叠加融合。先看一个定性对比模型行为特点在这个项目上的典型表现线性回归基线参考能检验特征工程是否正确通常本地得分最差但可以作为融合的补充成员随机森林抗噪性强max_depth控制不好时容易过拟合小时级波动通常明显超过线性回归XGBoost调参后一般略优于随机森林训练时间可控当learning_rate和max_depth配合当前特征集时表现稳定加权融合把三者预测值按权重加权平均比单一模型有小幅提升但这个项目上不会出现质变常见的做法是线性回归、随机森林、XGBoost三个模型的预测值做加权平均比如0.2、0.4、0.4的权重。这个项目的特征量级小Stacking提升空间有限三层堆叠反而不如简单加权稳定。权重可以先试等权重再按各自本地RMSLE的反比加权不需要过度优化。5. 交了学费才明白的4个坑负预测、随机K折、数据泄漏与稀疏类别5.1 预测出现负值导致RMSLE变成NaN现象第一次跑完线性模型计算本地RMSLE时输出NaN或者提交后线上分数高得离谱。原因线性回归没有天然的下界约束当某些特征组合对应的租赁量很低时log空间的预测值反变换后变成负数而log1p函数对负数输入直接返回NaN。解决所有模型预测后统一执行np.clip(preds, 0, None)不止是为计算指标提交文件里也不能出现负数。注意这个clip要在expm1之后做不要在log空间做。preds np.clip(np.expm1(preds_log), 0, None)5.2 随机K折交叉验证让本地分数虚高现象本地交叉验证RMSLE相当漂亮提交后分数差出一大截。原因这个数据是按小时记录的时间序列相邻小时、相邻几天的样本之间高度相关。随机K折会把同一时间段的数据拆进训练集和验证集模型相当于见过“几乎相同”的样本验证集失去参考价值。解决必须用按时间顺序的验证方式按天分组。常见做法是把数据按日期排序后取最后20%作为验证集或者按天做GroupKFold并保证验证集严格在训练集之后from sklearn.model_selection import TimeSeriesSplit # 按小时原始顺序做时间序列切分每一步训练集都在验证集之前 tscv TimeSeriesSplit(n_splits4) for fold, (train_idx, val_idx) in enumerate(tscv.split(X_train)): X_tr, X_val X_train.iloc[train_idx], X_train.iloc[val_idx] y_tr, y_val y_train_log.iloc[train_idx], y_train_log.iloc[val_idx] # 这个fold里训练模型并计算RMSLE rf_fold RandomForestRegressor(n_estimators200, max_depth10, random_state42) rf_fold.fit(X_tr, y_tr) val_pred np.clip(np.expm1(rf_fold.predict(X_val)), 0, None) print(ffold {fold}, RMSLE:, rmsle(np.expm1(y_val), val_pred))逻辑说明TimeSeriesSplit保证训练集永远是验证集之前的时间段不会让模型看到未来的数据。这个项目的样本虽然按小时排列但日期跨度较长直接用TimeSeriesSplit即可不需要自己按日期额外分组。提交前所有模型的最终评估都要用同一套时间序列划分不然不同模型之间的对比没有意义。5.3 把casual和registered当作特征本地分数好到不真实现象在特征里加上了casual和registered后本地RMSLE骤降到极低水平提交后分数崩掉。原因测试集根本没有这两列它属于数据泄漏。casual和registered是同期行为的结果变量不是可观测的先验特征。训练时加入它们后模型本质上是在“作弊”上线时无从构造相同特征。解决要么直接预测count作为单一目标要么分别对casual和registered建模让模型各自学习不同用户群体的规律最后将两个预测相加。两条路都可以走关键是用时间序列验证对比两种方案不要凭感觉选。单独建模的方式在这个项目里往往更稳因为casual和registered的分布差异确实很大。5.4 极端天气类别在测试集里可能完全不存在现象训练时weather4的样本极少模型对这个类别的分裂基本没有学到东西某个实现甚至在推理时报错。原因天气4样本量占整个数据集的极小比例与类别3又同属降水场景模型很难学到有效区分逻辑。解决把weather为4的样本合并到3逻辑上顺、数据上稳。处理完后再看测试集如果测试集里本来就没有4合并动作主要是为了训练集的稳定性。合并后本地RMSLE一般不会变差反而可能因为类别内样本更均衡而带来小幅提升。6. 提交前再花10分钟时间序列验证与预测分布检查如果你准备拿这个项目作为自己的Kaggle入门实践最后这十分钟值得养成习惯。第一个习惯每次提交前复查RMSLE。把本地预测文件加载回来重新计算一次rmsle确认没有NaN、没有负数、没有log空间的低级错误。第二个习惯画预测分布图。预测值分布应该和训练集count分布大致同形态右偏、峰值在0附近、长尾延伸到几百。如果预测分布出现尖峰堆积或者全是同一个值优先怀疑特征处理中对齐出了问题。第三个习惯检查时间序列验证的划分是否覆盖了训练集末尾。很多人的最终模型是用全部训练数据拟合的但验证结论只来自早期fold导致实测末尾时段的泛化能力没被检验过。我一般会把最后一个fold单独拿出来跑一遍全流程确认末尾时间段的表现和前三折差距不大再决定用哪组参数提交。第四个习惯预测值加权融合后回看一次分布偏移情况。如果融合后预测值整体比训练集均值高出一截警惕模型对季节或年份趋势过拟合。这个项目的价值在于它是少数能在一台笔记本上完整跑通“指标设计、特征工程、时间序列验证、多模型对比”全流程的入门任务。很多人后来回头看最怀念的反而不是XGBoost调参而是第一次做hr_workingday交互特征时的豁然开朗。希望帮你少踩几个坑也把这个项目当作你正式进入Kaggle的第一步。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑