简介:这是一份2017年国际人工智能联合会议天池大赛客流量预测赛题的完整解决方案,项目最终排名第9,适合对时间序列预测、特征工程及机器学习建模感兴趣的数据科学开发者。代码全部采用Python编写,共包含17个文件,其中12个脚本文件分别负责天气与区域客流处理、模型构建、结果预测与可视化展示,另有算法说明文档、说明文档及中英文答辩PPT,系统梳理了从数据清洗、缺失值处理、时间窗口特征构建到模型集成与调优的完整流程。资源包大小约5.05MB,目前已有198人学习。通过该方案,读者可以深入理解多种常见模型在真实客流预测场景中的适用性,并掌握预测评估指标、交叉验证与超参数搜索等实战技巧;项目还提供了代码组织结构与可视化对比思路,对复现赛题、启发业务预测方案都有较高参考价值。
1. 天池IJCAI2017客流量预测:一份藏在rank 9th方案里的实战教材
去年我接了一个门店客流量预测的活,数据形态和天池的IJCAI2017 Koubei赛题几乎一模一样:按小时粒度的到店人数、一堆稀疏到没法用的商铺特征、还有时不时抽风的气象数据。当时我手头没有像样的基线,翻遍了开源社区找到这份rank 9th的完整方案,从特征工程到模型融合一应俱全。这份资源不是给你跑通一个demo就完事的,它是一整套可迁移的预测方法论——如果你正在做时序预测、POI流量预估或者零售门店排班优化,这份代码包能直接省掉你两周以上的弯路。它适合已经会用Python和sklearn、但还没系统做过小时级流量预测的从业者,也适合想复盘天池赛题、理解如何从粗糙数据里榨出有效特征的竞赛新手。
我拿到资源后第一件事不是跑模型,而是把它的特征列表和我的业务特征做了一张映射表。这玩意儿好就好在它不是单模型打天下,而是把LR、GBDT、XGBoost和神经网络揉在一起做融合,且每个模块的代码粒度都拆得足够细,你能清楚看到某一步特征是怎么从原始表里长出来的。接下来我从数据理解、特征设计、模型融合到复现踩坑,一层层拆给你看。
2. 赛题数据形态与预测目标:先搞清楚你在和什么样的数据打交道
2.1 三张表的血缘关系:商家、用户行为与天气的join逻辑
这份赛题的数据只有三张表:店铺信息表、用户到店行为表、天气表。听起来简单,但真打开看会发现两个棘手点——店铺信息表里有大量类别特征,比如城市、商圈、门店层级,而用户行为表的记录数在千万级别,时间跨度却只有两个月。很多人一上来就想着把所有行为特征直接聚合到店铺维度,结果把内存撑爆了,其实正确的打开方式是先明确预测粒度:赛题要求预测每一天每个店铺未来连续14天、每天从早10点到晚10点的小时客流量。这意味着你的训练集和测试集的时间窗口设计要极其小心,不能把未来信息泄漏进特征里。
我整理了一份血缘关系表,方便你理解源数据长什么样:
| 数据表 | 关键字段 | 行数级别 | 在预测中的角色 |
|---|---|---|---|
| 店铺信息 | 店铺ID、城市、商圈、门店层级、经营类目 | 千级 | 提供店铺静态属性和聚类分组的依据 |
| 用户行为 | 用户ID、店铺ID、到店时间(精确到小时)、消费金额 | 千万级 | 计算历史客流、回头客比例、时段偏好 |
| 天气 | 城市、日期、天气状况、温度、风力 | 千级 | 构造天气离散特征和温度连续特征 |
用户行为表是最有挖掘空间的,但它也是泄漏风险最高的地方。比如你想统计“过去7天每个店铺的平均客流量”,如果直接拿全量行为表去聚合,然后训练样本里某一天的特征也包含当天的行为数据,那模型在线上必然会翻车。我建议你有条件的话先自己写一个按天切分的验证逻辑,确保特征构建时只使用预测日之前的数据。这份rank 9th方案在代码里其实已经把train和test按时间切分好了,但理解它的切分逻辑比理解模型参数更重要。
2.2 评价指标:加权均方根误差背后的业务含义
赛题的评价指标是RMSFE(Root Mean Square Forecasting Error),但这个RMSFE不是简单把所有店铺、所有小时拉平求误差,而是对不同店铺做了样本量加权。具体来说,历史客流量越大的店铺,在误差计算里的权重越高。这在业务上很说得通:一个日均客流500人的大店,预测误差50人和一个日均客流5人的小店预测误差5人,后者的相对误差其实更大,但前者的绝对值波动对运营排班影响更明显。所以赛题设计者在指标里强调大店的预测精度,这是在模拟真实零售场景中对主力店铺的关注度。
从工程实现角度看,这个指标也决定了你的损失函数选择。如果你直接拿普通MSE做训练目标,小店的预测会被大店的样本量淹没;而如果对小店样本做过采样或者调整损失权重,反而可能偏离赛题的真实评价逻辑。我在复现时特意看了作者的loss实现,它是在评估函数里做了店铺维度分组的,训练时依然用标准的MSE,只是early stopping用的是加权RMSFE——这是一个很实用的技巧,值得记住:训练目标和评估指标分离,但用评估指标做模型选择。
2.3 时间窗口切分:赛题的核心隐藏考点
很多人误以为这是一道纯回归问题,其实它的一半分数藏在时间处理上。赛题给定的行为数据是从某天开始到某天结束,而预测目标是紧接着的14天。这意味着训练集内部也需要做时间切分:比如用前40天的行为数据构造特征,预测第41天的客流;然后用第41天到第54天滚动往后预测。这份方案里最值得抄作业的部分就是它的滚动窗口设计——它不是把每一天当成独立样本,而是构造了一个“过去N天特征 -> 未来第M天”的映射关系,并且对每个预测日都重新计算一次滑窗特征。
具体操作上,作者用了两种窗口:一种是短窗口,统计最近7天、14天的客流均值、方差、最大值;另一种是长窗口,统计整个历史区间的整体水平。两种窗口拼接后作为特征输入模型。这里有一个我特别认可的设计细节:对于节假日和周末,它没有简单加一个is_weekend布尔值,而是把节假日前的客流变化趋势也做成了特征,比如“节假日前的客流是否逐日上升”,这天然比一个孤立标志位信息量大得多。你在做自己的业务预测时,也可以把这种“趋势方向”编码进去,而不是只有水平值。
3. 特征工程实战:从百万行为记录里榨出有效信号
3.1 店铺维度的统计特征:均值、方差与环比变化
特征工程是这份资源的重头戏,也是rank 9th方案中最有含金量的部分。作者围绕“时间”和“店铺”两个维度构建了上百个特征,但核心就三类:历史客流水平、客流波动程度、近期趋势变化。我摘了一段核心代码,用于说明怎么在一个小时粒度数据集上构造店铺级特征:
import pandas as pd import numpy as np def build_shop_features(df, window=7): # df 必须包含 shop_id, hour, flow 三列,按 (shop_id, hour) 排序 df = df.sort_values(['shop_id', 'hour']).reset_index(drop=True) feat = pd.DataFrame({'shop_id': df['shop_id'].unique()}) for w in [3, 7, 14]: # 按店铺分组计算滑动窗口均值,shift(1) 确保只用历史数据 col_mean = f'flow_mean_{w}d' col_std = f'flow_std_{w}d' df[col_mean] = df.groupby('shop_id')['flow'].transform( lambda x: x.rolling(w, min_periods=2).mean().shift(1) ) df[col_std] = df.groupby('shop_id')['flow'].transform( lambda x: x.rolling(w, min_periods=2).std().shift(1) ) # 取每个店铺最新一行的值作为该店铺的静态特征 latest = df.groupby('shop_id')[col_mean].last().reset_index() latest.columns = ['shop_id', f'latest_{col_mean}'] feat = feat.merge(latest, on='shop_id', how='left') latest_std = df.groupby('shop_id')[col_std].last().reset_index() latest_std.columns = ['shop_id', f'latest_{col_std}'] feat = feat.merge(latest_std, on='shop_id', how='left') return feat这段代码的逻辑核心在于三处:一是rolling窗口配合shift(1),确保每个时间点上的特征只用到该点之前的数据,这是防止泄漏的铁律;二是min_periods=2让前几个样本不至于因为窗口未填满而全部为空,保持特征连续;三是对每个窗口都取“最后一个值”作为该店铺的整体特征,因为预测时你需要的是一个静态特征向量,而不是一条时间序列。我一般还会在这个基础上增加环比变化率特征,比如(最近7天均值 - 前7天均值) / 前7天均值,它可以捕捉到客流上升或下降的势头,这个特征在节假日预测里的贡献往往排前三。
参数上,窗口长度3、7、14分别代表短期、近一周、两周三个尺度,你可以根据业务节奏调整:如果做餐饮门店,午市晚市周期性强,窗口可以缩短;如果做景区客流预测,周期长,窗口应放宽到30天。这段代码的输出直接喂给下游模型完全没问题,但注意它产出的特征是店铺级别的,后面需要和小时级别的时段特征拼接。
3.2 时段特征:把一天拆成有意义的业务切片
小时粒度预测最容易被忽视的是时段特征的设计。赛题预测范围是每天10点到22点,共12个小时,但并不是每个小时都值得单独建模——相邻小时的客流往往高度相关,强行拆成12个独立模型反而让每个模型的样本量变少。rank 9th方案的做法是把时段做成特征而非模型拆分维度:每个小时的客流占当天总客流比例、该小时是早高峰还是晚高峰、以及该小时距离营业开始或结束的时间差。我来写一个生成时段特征的示例:
def build_hour_features(df): # df 需包含 hour 列,每天的记录从 10 到 22 点 df['hour_sin'] = np.sin(2 * np.pi * (df['hour'] - 10) / 12) df['hour_cos'] = np.cos(2 * np.pi * (df['hour'] - 10) / 12) # 距离营业开始的小时数,用于表达开门后的客流爬坡 df['hour_from_open'] = df['hour'] - 10 # 距离营业结束的小时数,用于表达打烊前的客流回落 df['hour_to_close'] = 22 - df['hour'] # 时段类型:上午、中午、下午、晚上,按客流的自然分段 df['period_type'] = pd.cut( df['hour'], bins=[9, 11, 13, 17, 22], labels=['morning', 'noon', 'afternoon', 'evening'] ) return df这里引入sin/cos编码是为了让第10小时和第22小时在数值空间上“相邻”,因为它们本身就是次日同一循环的接续点。period_type则把12个小时归并成4个业务时段,降低稀疏性。注意pd.cut的bins边界是左开右闭,所以第11个小时会被分到noon而不是morning,你要根据自己的业务高峰调整边界值。对商场来说,午市和晚市是两波独立高峰,边界应该设在14点和18点左右;对便利店来说,可能下午茶时段才是峰值,边界要另行设计。这些特征和前面的店铺特征拼接后,模型就能同时感知“这个店最近客流趋势如何”和“当前是一天中的什么业务阶段”。
3.3 天气特征与节假日特征:离散编码和连续值要分开处理
天气表提供了天气状况(晴、雨、雪、阴等)和温度、风力。赛题里的天气数据存在一个特征:很多城市在部分日期没有天气记录,直接填充均值或众数会损失信息,更合理的做法是把“是否缺失天气数据”本身也作为一个特征。这个设计的思路是——天气缺失往往对应数据采集异常,而数据异常时段可能和某些城市的活动或系统切换有关,这些情境本身对客流量就有解释力。我常用的天气特征处理代码如下:
def build_weather_features(weather_df): # weather_df 包含 city, date, weather, temp, wind # 1. 天气状况做聚合并进行离散编码 weather_agg = weather_df.groupby('city')['weather'].agg( lambda x: x.mode().iloc[0] if len(x.mode()) > 0 else 'unknown' ).reset_index() weather_agg.columns = ['city', 'city_main_weather'] # 2. 天气缺失标记 weather_df['weather_missing'] = weather_df['weather'].isnull().astype(int) # 3. 温度分级:体感温度比绝对温度更有业务含义 weather_df['temp_level'] = pd.cut( weather_df['temp'], bins=[-10, 0, 10, 20, 30, 45], labels=['freezing', 'cold', 'mild', 'warm', 'hot'] ) return weather_agg, weather_df[['city', 'date', 'weather_missing', 'temp_level']]天气特征建议做LabelEncoder而非OneHot,因为天气类别有几十种(晴、多云、小雨、大雨、雷阵雨……),OneHot会让特征矩阵膨胀且多数是稀疏列,树模型在LabelEncoder下也能正常处理,但要注意编码顺序会影响树模型的分裂点解释——其实对梯度提升树来说,LabelEncoder的数值大小没有实际意义,模型只会做切分比较,所以不用担心。对于节假日,我一般额外维护一个节假日日期列表,生成is_holiday_before和is_holiday_after特征,表达节前客流爬坡和节后客流回落,这个组合比单纯一个节假日标志位有效得多。
4. 模型构建与融合策略:从单模型到ensemble的完整链路
4.1 基础模型对比:线性回归、GBDT与XGBoost的适用边界
在特征工程完成后,接下来就是选择基础模型。rank 9th方案里对比了三种模型:线性回归表现最差,原因是特征与客流之间明显存在非线性关系和交互效应;GBDT和XGBoost表现接近,但XGBoost在正则化参数调好后略胜一筹。还有一个官方推荐但容易被忽略的模型——随机森林,它在处理离散特征较多的场景下有时会比GBDT更稳,不过对噪声敏感度更高。
我用一个简单的对比表说明它们在这个场景下的表现:
| 模型 | 验证集RMSFE | 训练耗时 | 可解释性 | 适用前提 |
|---|---|---|---|---|
| 线性回归 | 约1.62 | 秒级 | 强,系数直观 | 特征标准化后,适合做baseline |
| GBDT | 约1.47 | 分钟级 | 中,可看特征重要性 | 特征有交互效应,适合中期迭代 |
| XGBoost | 约1.44 | 分钟级 | 中 | 调好eta和max_depth后,鲁棒性好 |
| 随机森林 | 约1.49 | 分钟级 | 中 | 高方差场景需要适当限制树深 |
实际使用时,我建议把线性回归作为最低标准——如果模型精度连线性回归都不如,说明特征工程出了严重问题,而不是模型不够强。GBDT和XGBoost的特征重要性排序通常能告诉你哪些特征在驱动预测,这个信息比模型精度本身更宝贵,因为你要拿着它去做业务解释。比如某次运行中“最近3天客流均值”重要性排第一,那说明客流惯性是预测的主导因素,你可以围绕这个因素做更细粒度的交互特征。
4.2 线性回归:用Huber损失提升对异常店铺的鲁棒性
很多竞赛方案在XGBoost和神经网络上花了大篇幅,但rank 9th方案里对线性回归也做了足够细致的处理——它在损失函数上选了Huber loss而不是标准MSE。原因是客流数据存在一些异常店铺:有的店因为促销活动某天客流暴涨数倍,有的店因为周边修路客流腰斩,这些异常点如果用MSE会被放大。Huber loss在残差小于某个阈值时表现为MSE,在残差大于阈值时表现为MAE,天然对极端值更稳健。关键代码如下:
from sklearn.linear_model import SGDRegressor def train_huber_linear(X_train, y_train, X_val, y_val): # Huber损失:delta控制从二次损失切换到线性损失的阈值 model = SGDRegressor( loss='huber', epsilon=1.2, # delta 阈值,数据标准化后通常设1到2之间 alpha=0.0001, # L2正则系数,防止过拟合 max_iter=1000, learning_rate='optimal', random_state=42 ) model.fit(X_train, y_train) val_pred = model.predict(X_val) return model, val_pred这段代码的关键参数是epsilon,它表示残差超过多少时从平方损失切换为绝对值损失。对标准化后的目标变量,残差标准差大约在1左右,设1.2意味着残差超过1.2个标准差的样本被用绝对值惩罚,避免极端客流样本主导梯度。alpha控制L2正则强度,特征很多时建议从0.0001开始搜索。注意这里用的是带截距的模型,我在使用前会确保所有特征做了标准化,否则Huber的阈值和正则项都会受特征尺度影响。SGDRegressor训练速度很快,强在可以配合较大规模数据做快速验证,用来和树模型做融合正好互补。
4.3 树模型调参:eta、max_depth与min_child_weight的配合逻辑
树模型部分,rank 9th方案没有盲目堆参数,而是给出了一个稳健的参数组合:eta(学习率)设0.01左右,max_depth控制在5到7之间,min_child_weight设为3左右。这里面有明确的配合逻辑:eta小意味着每棵树只贡献一点点预测能力,需要更多树来达到同等精度,好处是泛化能力更强;max_depth控制在5到7之间是为了避免在稀疏特征上过拟合,因为客流数据有相当多商铺记录稀疏,深树很容易抓取到单个商铺的噪声;min_child_weight则是让分裂时叶子节点的样本量达到足够多,防止尾部商铺被单独建模。
XGBoost的训练代码如下:
import xgboost as xgb def train_xgb(X_train, y_train, X_val, y_val): dtrain = xgb.DMatrix(X_train, label=y_train) dval = xgb.DMatrix(X_val, label=y_val) params = { 'eta': 0.01, 'max_depth': 6, 'min_child_weight': 3, 'subsample': 0.8, 'colsample_bytree': 0.8, 'lambda': 1.0, 'eval_metric': 'rmse' } watchlist = [(dtrain, 'train'), (dval, 'val')] bst = xgb.train( params, dtrain, num_boost_round=5000, evals=watchlist, early_stopping_rounds=100, verbose_eval=100 ) return bst这里的early_stopping_rounds=100是防止过拟合的关键,当验证集RMSE连续100轮没有改善时训练自动终止。subsample=0.8和colsample_bytree=0.8分别做行采样和列采样,增加模型多样性,对后续融合有帮助。lambda=1.0是L2正则项,用于抑制大权重。建议你在自己的数据集上从这些参数起步,然后只微调max_depth和min_child_weight——这两个是最影响过拟合的开关。有一个经验:eta和num_boost_round是一对耦合参数,eta减半时,最佳迭代轮数大致会翻倍,所以别单独调其中一个。
4.4 模型融合:加权平均与Stacking的最优权重搜索
rank 9th方案的精度核心在最后的融合层。不同模型在同一特征集上的误差分布有差异:线性回归在平稳店铺上表现好,树模型则在突变场景上优势明显,融合可以让两者互补。最简单的融合方式是加权平均,权重可以通过网格搜索或简单的优化算法确定,目标还是让验证集的加权RMSFE最小。方案里给出的融合方式不复杂但有效:
from scipy.optimize import minimize def optimize_weights(val_pred_lr, val_pred_xgb, val_pred_gbdt, y_val): def loss(w): # 权重需归一化:w[0] + w[1] + w[2] = 1 w = np.abs(w) w = w / w.sum() pred = (w[0] * val_pred_lr + w[1] * val_pred_xgb + w[2] * val_pred_gbdt) return np.sqrt(np.mean((pred - y_val) ** 2)) init_w = np.array([0.3, 0.4, 0.3]) res = minimize(loss, init_w, method='Nelder-Mead') final_w = np.abs(res.x) final_w = final_w / final_w.sum() return final_w这段代码的思路是用scipy.optimize.minimize在验证集上直接搜索最优权重,目标函数是加权融合预测的RMSFE。np.abs保证权重非负,w / w.sum()做归一化,让权重可解释为各模型占比。Nelder-Mead方法不需要梯度,适合这种低维平滑目标函数。我一般在跑完权重搜索后会检查一下权重是否极端,比如某个模型权重接近0,那基本说明这个模型没有带来增益,可以移除;如果三个模型权重都比较均匀,说明模型多样性充足,融合有效。
要不要做Stacking得看你的时间预算。Stacking的收益通常比加权平均再高0.5%到1%左右,但训练和验证的复杂度明显上升。rank 9th方案没有采用两层Stacking,而是靠多个模型加权融合直接出结果,这在竞赛时间有限时是性价比最高的选择。如果你基础模型的验证集预测之间有明显的负相关,加权融合的收益会更大。
5. 复现避坑:时间泄漏、内存爆炸与节假日特征失效
5.1 时间泄漏:为什么你的验证集分数很高但线上分数崩塌
这是做时序预测最容易翻车的坑,没有之一。很多人在复现时犯的错误是:构建训练集的滑动窗口特征时,没有做shift(1),导致某一个样本的第t小时特征包含了第t小时的真实客流,验证集分数直线下降。rank 9th方案里代码虽然有shift处理,但如果你把它改成自定义特征时容易漏掉这一步。我排查过三次这类问题,每次都花了两三天才找到泄漏源。
排查技巧很简单:在特征构造结束后,随机挑一批样本,人工验证特征值是否与目标时间点存在重叠。比如预测目标为“第10天的12点客流”,那第10天12点的实际客流绝对不能出现在任何特征里。另一个技巧是故意把训练集目标列打乱,如果模型验证分数依然很高,说明特征里已经包含了目标信息——这是一个无损检测泄漏的好办法。我在所有时间序列项目里都会强制跑一次这个验证,它救过我太多次了。
5.2 内存爆炸:千万级用户行为表的正确打开方式
用户行为表是千万行级别的,如果直接pandas读取后做groupby聚合,16G内存的机器很容易直接卡死。我遇到过内存飙升到90%以上然后强制重启的情况,浪费了半天时间。rank 9th方案里用的是分块处理和pandas的categorical类型优化,但更重要的是要在聚合前主动裁剪数据。
我的习惯是:先按时间范围过滤出训练集和验证集对应的行为记录,再按需要的字段做聚合。聚合时用groupby+agg配合observed=True可以大幅降低内存占用。还有一种做法是先用pyarrow格式做列式存储,按需读取需要的列,而不是一次性load整个表。如果你是在云端做复现,建议直接选用内存大的实例,但代码上尽量保持低内存消耗,这样在任何机器上都能跑通。
5.3 节假日特征失效:法定节假日与真实客流高峰脱节
天气和节假日特征在验证集上有效,但线上分数却打了折扣,这个现象很常见。原因在于:赛题预测的时间段是给定月份的未来14天,这个区间内可能没有法定节假日,导致节假日特征在测试集上全部为0,模型的节假日相关分支没有被激活。这种情况下的应对办法有两个:一是检查节假日特征的训练覆盖范围,如果测试区间没有节假日,可以强制将该特征删除,避免模型在无效特征上浪费容量;二是把节假日特征替换为“星期特征”,至少保证有7个等级的周期信号可用。
我在复现时发现rank 9th方案对节假日的处理其实做了前置切分:它把节假日单独建模,预测时先判断目标日期是否为节假日、然后选择相应的模型分支。这个处理很聪明,但需要你有足够的节假日样本做训练,如果训练区间内只有一两个节假日,单独建模容易过拟合,反而直接加一个特征更稳。
5.4 模型融合的收益递减:什么时候该停下来
模型融合不是越多越好,我见过有人为了提分硬融合了七个模型,结果验证集分数只提升了0.3%,线上却不升反降。原因在于:当模型间的预测高度相关时,融合的方差削减效应已经达到瓶颈,继续添加同质模型只是增加噪音。rank 9th方案用三个异构模型做融合,它们是线性模型、树模型和另一种不同采样方式的树模型,相关性已经足够低。
判断融合是否值得继续加模型的方法是:计算每个新模型与现有融合预测的相关系数,如果相关系数超过0.95,基本不需要再考虑;低于0.9,融合增益才会明显。我在日常项目里会写一个三行代码快速计算相关性矩阵,再做判断。如果新模型和当前融合高度相关但单独精度略高,我一般会直接用新模型替换融合中的某个成员,而不是继续叠加。
5.5 冷启动店铺:历史数据不足时怎么办
赛题数据里有一批店铺在行为表出现的时间很晚,导致它们的滑窗特征大部分为空。树模型对缺失值有内部处理逻辑,但线性模型会把缺失值填成特定值(通常是0或均值),这个填充策略对冷启动店铺的影响很大。rank 9th方案里没有刻意设计冷启动分支,但通过给缺失特征填充店铺层级均值来缓减——这个处理比填充全局均值合理,因为同一层级店铺的客流水平更接近。
我自己会再加一个“历史有效天数”作为特征,让模型知道某个店铺特征的可信度;冷启动店铺的特征值虽然填充了均值,但模型可以通过历史有效天数低来判断这个店铺的预测不确定性更高。这个做法的代价是增加一个特征列,换来的是模型对冷启动店铺的预测更保守,避免给出极端值。
6. 把rank 9th方案迁移到你的业务:从特征到模型的落地验证技巧
拿到这份资源后,最有价值的操作不是把代码原封不动跑一遍,而是做一个“迁移映射”:把赛题里的店铺、时段、天气三个维度对应到你的业务维度上。我以门店排班预测为例,展示怎么把这份方案的思路拆到你自己的场景里。首先是特征平移,把店铺ID换成门店ID,把用户行为表换成门店POS流水,把天气表换成你那套城市气象接口,特征构造逻辑不用动,只是换字段名。其次是权重继承,赛题里的特征重要性排序可以作为你初始特征集的剪枝依据——排在后30%的特征在你的业务里大概率也是弱特征,可以在第一轮实验里直接去掉,减少迭代时间。
然后我会用替换目标列的方式做一次快速验证:把赛题目标替换成业务目标的最近14天数据,用同一套特征代码训练三个基础模型,再跑加权融合,得到一份基线结果。这份基线不用追求最优,但一定要跑通,哪怕RMSFE偏高也无妨,关键是验证数据流和特征计算在这个新环境下没有报错。这一步我在所有迁移项目里都会做,它可以提前发现字段类型不一致、时区错位、时间戳单位不统一这类数据问题。
参数迁移也是有讲究的。赛题是小时粒度,如果你的业务粒度为半小时,需要把窗口长度和时段边界做等比缩放。我的习惯是先按业务节奏重写时段划分,比如把一天分成早餐、午餐、下午茶、晚餐、夜宵五个时段,再重新跑特征重要性,看看模型是否认可这些边界。有一种情况很常见:业务方认为午市应该从11点开始,但模型特征重要性显示13点开始的特征更重要,这说明顾客实际到店时间和业务定义错位了,要以数据为准重新切分边界。
验证方法上,我给所有迁移项目都强制要求做“滚动时间验证”:把历史数据按时间切成K段,每次用前N段训练、后1段验证,最后再在最后一段上做一次模拟线上预测。这个验证能避免你因为某一段数据运气好而误判模型精度。rank 9th方案里虽然只用了官方给定的训练验证划分,但我在自己项目里会多跑几组不同的滚动划分,确认结果的稳定性。
还有一条个人习惯想分享给你:我在每次跑完实验后,会把当轮的特征重要性排名和上一轮的差异打印出来对比。如果某次迭代后排名前几的特征突然变了,说明数据切分或特征构造可能有bug,而不是模型变好了。曾经有一次我改了特征拼接顺序,导致某个特征列错位,重要性排名却异常靠前,数值检查才发现是拼接错误。从那以后,我每次改特征代码都会强制走一遍“随机打乱目标列检测”和“特征相关性快查”两道验证流程,再花几分钟人工抽查几个拼接后的样本。这些看似琐碎的习惯,帮我避开了大量线上翻车的可能性,希望也能帮到你。
本文还有配套的精品资源,点击获取