上一章:【竞赛系列】机器学习实操项目09——全球城市计算AI挑战赛(基于LightGBM快速搭建baseline)
下一章:
机器学习核心知识点目录:机器学习核心知识点目录
机器学习实战项目目录:【从 0 到 1 落地】机器学习实操项目目录:覆盖入门到进阶,大学生就业 / 竞赛必备
文章目录
摘要
本项目针对地铁客流量(进站量inNums
、出站量outNums
)进行预测,核心流程包括:数据加载与预处理、时间特征工程构建、历史时序特征生成、站点拓扑特征融合、LightGBM单模型训练、时序Stacking集成优化等步骤。通过提取多维度时序特征(10分钟粒度、小时粒度、历史同期/偏移时段特征)和站点拓扑特征,结合集成学习策略,最终实现高精度客流量回归预测,模型评估指标采用平均绝对误差(MAE)。
通过网盘分享的文件:天池地铁流量预测
链接: https://pan.baidu.com/s/1k-sI1sDGufBSLJveNTm6nA?pwd=pgkk 提取码: pgkk
1. 工具包导入
导入数据处理、文本处理、机器学习、可视化等所需的第三方库,为后续流程提供工具支持。
## 数据工具包
import numpy as np
np.random.seed(42) # 设置随机种子,保证结果可复现
import pandas as pd
from tqdm import tqdm # 显示循环进度条
## 字符串处理工具包
import string
import re # 正则表达式库
from collections import Counter # 计数工具
import pickle # 数据序列化存储
from sklearn.model_selection import train_test_split # 数据集划分
import warnings
warnings.filterwarnings('ignore') # 忽略警告信息
import xgboost as xgb # XGBoost模型库
import lightgbm as lgb # LightGBM模型库
import os # 文件路径操作
import gc # 内存回收
import time # 时间操作
import datetime # 日期时间处理
import joblib # 模型/数据保存
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt # 基础可视化库
import re
%matplotlib inline # Jupyter Notebook中设置绘图嵌入
import seaborn as sns # 高级可视化库
%matplotlib inline
2. 数据加载
读取项目所需的核心数据集,包括地铁客流量数据、提交模板数据、站点路线图数据,为后续特征工程和建模提供原始数据。
数据说明
df_data.csv
:核心数据,包含stationID
(站点ID)、startTime
(时段开始时间)、endTime
(时段结束时间)、inNums
(进站量)、outNums
(出站量)testA_submit_2019-01-29.csv
:测试集提交模板,用于后续预测结果格式对齐(本步骤仅加载未使用)Metro_roadMap.csv
:地铁站点路线图数据,包含站点拓扑关系(如站点连接的线路数)
from tqdm import tqdm
from tqdm import tqdm_notebook # Jupyter Notebook专用进度条(本项目未实际使用)
# 加载核心客流量数据
df_data = pd.read_csv('./input/df_data.csv')
# 加载测试集提交模板
test_A_submit = pd.read_csv('./input/testA_submit_2019-01-29.csv')
# 加载地铁站点路线图数据
Metro_roadMap = pd.read_csv('./input/Metro_roadMap.csv')
3. 时间特征工程
从原始时间字段startTime
中提取多维度时间特征,捕捉客流量的时间分布规律(如日内时段、周内日期、10分钟粒度时段),为后续建模提供时间维度的输入特征。
特征说明
day
:日期(如1月1日为1,1月2日为2)hours_in_day
:一天中的小时(0-23)day_of_week
:一周中的星期(0=周一,6=周日,此处数据中1=周二)ten_minutes_in_day
:一天中的10分钟粒度时段(一天24小时=1440分钟,1440/10=144个时段,0-143)
# 时间相关特征
# 将字符串格式的startTime转换为datetime格式,便于提取时间特征
df_data['time'] = pd.to_datetime(df_data['startTime'])
# 提取日期(1-31)
df_data['day'] = df_data['time'].dt.day
# 提取小时(0-23)
df_data['hours_in_day'] = df_data['time'].dt.hour
# 提取星期(0=周一,6=周日)
df_data['day_of_week'] = df_data['time'].dt.dayofweek
# 计算10分钟粒度时段(每10分钟为一个时段,一天共144个时段)
df_data['ten_minutes_in_day'] = df_data['hours_in_day'] * 6 + df_data['time'].dt.minute // 10
# 删除临时创建的time列,减少内存占用
del df_data['time']
4. 数据预览与分布筛选
- 预览处理后的数据结构,验证时间特征是否正确生成;
- 剔除与测试集分布差异大的日期(如周末、节假日),减少异常数据对模型的干扰,保证训练数据与预测场景的一致性。
# 预览处理后的数据前5行,查看特征结构
df_data.head()
输出结果:
stationID | startTime | endTime | inNums | outNums | day | hours_in_day | day_of_week | ten_minutes_in_day | |
---|---|---|---|---|---|---|---|---|---|
0 | 0 | 2019-01-01 00:00:00 | 2019-01-01 00:10:00 | 0.0 | 0.0 | 1 | 0 | 1 | 0 |
1 | 0 | 2019-01-01 00:10:00 | 2019-01-01 00:20:00 | 0.0 | 0.0 | 1 | 0 | 1 | 1 |
2 | 0 | 2019-01-01 00:20:00 | 2019-01-01 00:30:00 | 0.0 | 0.0 | 1 | 0 | 1 | 2 |
3 | 0 | 2019-01-01 00:30:00 | 2019-01-01 00:40:00 | 0.0 | 0.0 | 1 | 0 | 1 | 3 |
4 | 0 | 2019-01-01 00:40:00 | 2019-01-01 00:50:00 | 0.0 | 0.0 | 1 | 0 | 1 | 4 |
# 剔除与测试集分布差异大的日期:保留工作日(day_of_week < 5,即周一至周五)且排除1月1日(可能为节假日)
df_data = df_data.loc[((df_data.day_of_week < 5) & (df_data.day != 1))].copy()
5. 相对日期映射
将原始日期(如2日、3日)映射为连续的相对日期索引(如2日=1,3日=2),便于后续按“过去N天”的逻辑生成历史时序特征,避免原始日期不连续导致的特征提取错误。
# 提取筛选后数据中的所有唯一日期
retain_days = list(df_data.day.unique())
# 构建原始日期到相对日期索引的映射字典(从1开始连续编号)
days_relative = {}
for i,d in enumerate(retain_days):
days_relative[d] = i + 1
# 将原始日期转换为相对日期索引,新增days_relative列
df_data['days_relative'] = df_data['day'].map(days_relative)
# 打印映射关系,验证相对日期是否连续
print(days_relative)
输出结果:
{2: 1, 3: 2, 4: 3, 7: 4, 8: 5, 9: 6, 10: 7, 11: 8, 14: 9, 15: 10, 16: 11, 17: 12, 18: 13, 21: 14, 22: 15, 23: 16, 24: 17, 25: 18, 28: 19, 29: 20}
6. 客流量时序可视化
通过折线图直观展示不同站点(前10个站点)的进站量inNums
随时间的变化趋势,观察客流量的时序规律(如日内高峰、日间波动),为后续特征工程和模型优化提供业务洞察。
# 生成时间序列索引(按10分钟时段连续编号)
dt = [r for r in range(df_data.loc[df_data.stationID==0, 'ten_minutes_in_day'].shape[0])]
# 创建画布,设置尺寸为20x10
fig = plt.figure(1,figsize=[20,10])
# 设置坐标轴标签和标题
plt.ylabel('inNums') # 纵轴:进站量
plt.xlabel('date') # 横轴:时间(10分钟时段索引)
plt.title('inNums of stationID ') # 图表标题:站点进站量时序图
# 绘制前10个站点的进站量时序曲线
for i in range(0,10):
plt.plot(dt, df_data.loc[df_data.stationID==i, 'inNums'], label = str(i)+' stationID' )
# 显示图例(区分不同站点)
plt.legend()
# 显示图表
plt.show()
7. 历史时序特征生成工具函数
定义两个核心工具函数,为后续批量生成历史时序特征提供支持:
time_before_trans
:安全从字典中获取值,避免键不存在导致的报错;generate_fea_y
:为指定日期生成基于“过去N天”的历史时序特征,捕捉客流量的历史依赖规律。
# 辅助函数:从字典中安全获取值,如果键不存在返回NaN(避免KeyError)
def time_before_trans(x, dic_):
"""
安全地从字典中获取值
参数:
x: 要查找的键(如站点ID+10分钟时段的组合标识)
dic_: 目标字典(如历史时段客流量统计字典)
返回:
如果键存在返回对应值,否则返回np.nan(便于后续处理缺失值)
"""
if x in dic_.keys():
return dic_[x]
else:
return np.nan
# 主函数:为指定日期生成基于历史数据的特征
def generate_fea_y(df, day, n):
"""
为指定日期(相对日期)生成基于“过去n天”的历史时序特征
参数:
df: 原始数据DataFrame(包含相对日期、时间特征、客流量等)
day: 目标日期(相对日期索引,如5表示第5天)
n: 使用过去多少天的数据生成特征(如n=5表示使用前1-4天数据)
返回:
包含生成特征的DataFrame(在原始数据基础上新增历史时序特征)
"""
# 1. 筛选目标日期的数据(作为特征的“标签行”)
df_feature_y = df.loc[df.days_relative == day].copy()
# 2. 创建时间-站点组合标识(用于关联历史数据)
# 2.1 10分钟粒度标识:站点ID*1000 + 10分钟时段(确保唯一标识一个站点的一个10分钟时段)
df_feature_y['tmp_10_minutes'] = df_feature_y['stationID'].values * 1000 +\
df_feature_y['ten_minutes_in_day'].values
# 2.2 小时粒度标识:站点ID*1000 + 小时(确保唯一标识一个站点的一个小时)
df_feature_y['tmp_hours'] = df_feature_y['stationID'].values * 1000 +\
df_feature_y['hours_in_day'].values
# 3. 循环生成“过去1天到过去n-1天”的历史特征
for i in range(1, n):
# 计算当前循环对应的历史日期(相对日期:目标日期 - i天)
d = day - i
# 筛选历史日期的数据
df_d = df.loc[df.days_relative == d].copy()
# 4. 特征类型1:相同10分钟时段的历史客流量(精确匹配:如今天9:00-9:10对应昨天9:00-9:10)
# 4.1 为历史数据创建10分钟粒度和小时粒度标识
df_d['tmp_10_minutes'] = df_d['stationID'] * 1000 + df_d['ten_minutes_in_day']
df_d['tmp_hours'] = df_d['stationID'] * 1000 + df_d['hours_in_day']
# 4.2 计算历史10分钟时段的进站量/出站量总和(按10分钟标识分组)
dic_innums = df_d.groupby(['tmp_10_minutes'])['inNums'].sum().to_dict()
dic_outnums = df_d.groupby(['tmp_10_minutes'])['outNums'].sum().to_dict()
# 4.3 将历史统计量映射到目标日期数据,新增特征列(命名规则:_bf_1_innum_10minutes表示前1天相同10分钟进站量)
df_feature_y['_bf_' + str(i) + '_innum_10minutes'] =\
df_feature_y['tmp_10_minutes'].map(dic_innums).values
df_feature_y['_bf_' + str(i) + '_outnum_10minutes'] =\
df_feature_y['tmp_10_minutes'].map(dic_outnums).values
# 5. 特征类型2:相同小时时段的历史客流量(精确匹配:如今天9点对应昨天9点)
# 5.1 计算历史小时时段的进站量/出站量总和(按小时标识分组)
dic_innums = df_d.groupby(['tmp_hours'])['inNums'].sum().to_dict()
dic_outnums = df_d.groupby(['tmp_hours'])['outNums'].sum().to_dict()
# 5.2 映射到目标日期数据,新增特征列(命名规则:_bf_1_innum_hour表示前1天相同小时进站量)
df_feature_y['_bf_' + str(i) + '_innum_hour'] =\
df_feature_y['tmp_hours'].map(dic_innums).values
df_feature_y['_bf_' + str(i) + '_outnum_hour'] =\
df_feature_y['tmp_hours'].map(dic_outnums).values
# 6. 特征类型3:前10分钟时段的历史客流量(时间偏移:如今天9:00-9:10对应昨天8:50-9:00)
# 6.1 为历史数据创建“前10分钟”标识(当前10分钟时段-1)
df_d['tmp_10_minutes_bf'] = df_d['stationID'] * 1000 + df_d['ten_minutes_in_day'] - 1
# 6.2 计算历史前10分钟时段的进站量/出站量总和
dic_innums = df_d.groupby(['tmp_10_minutes_bf'])['inNums'].sum().to_dict()
dic_outnums = df_d.groupby(['tmp_10_minutes_bf'])['outNums'].sum().to_dict()
# 6.3 安全映射到目标日期数据(使用time_before_trans处理缺失时段)
df_feature_y['_bf1_' + str(i) + '_innum_10minutes'] =\
df_feature_y['tmp_10_minutes'].agg(lambda x: time_before_trans(x, dic_innums)).values
df_feature_y['_bf1_' + str(i) + '_outnum_10minutes'] =\
df_feature_y['tmp_10_minutes'].agg(lambda x: time_before_trans(x, dic_outnums)).values
# 7. 特征类型4:前1小时时段的历史客流量(时间偏移:如今天9点对应昨天8点)
# 7.1 为历史数据创建“前1小时”标识(当前小时-1)
df_d['tmp_hours_bf'] = df_d['stationID'] * 1000 + df_d['hours_in_day'] - 1
# 7.2 计算历史前1小时时段的进站量/出站量总和
dic_innums = df_d.groupby(['tmp_hours_bf'])['inNums'].sum().to_dict()
dic_outnums = df_d.groupby(['tmp_hours_bf'])['outNums'].sum().to_dict()
# 7.3 映射到目标日期数据,新增特征列
df_feature_y['_bf1_' + str(i) + '_innum_hour'] =\
df_feature_y['tmp_hours'].map(dic_innums).values
df_feature_y['_bf1_' + str(i) + '_outnum_hour'] =\
df_feature_y['tmp_hours'].map(dic_outnums).values
# 8. 清理临时创建的标识列(减少内存占用,避免干扰后续流程)
for col in ['tmp_10_minutes', 'tmp_hours']:
del df_feature_y[col]
return df_feature_y
8. 批量生成全量时序特征
调用generate_fea_y
函数,为所有符合条件的日期(从第6天开始,需保证有前5天历史数据)批量生成历史时序特征,构建完整的特征数据集,为后续建模提供输入。
# 初始化存储全量特征数据的DataFrame
df_feature_y = pd.DataFrame()
# 设置日期范围:从第6天开始(left=6,需前5天数据生成特征),到最大相对日期结束
left = 6 # 最小目标日期(相对日期),确保有n=left-1=5天历史数据
right = df_data['days_relative'].max() + 1 # 最大目标日期+1(确保循环覆盖所有日期)
# 遍历每个目标日期,批量生成特征
for day in range(left, right):
# 为当前日期生成特征:使用前5天(n=left=6,循环i=1-5)的历史数据
daily_features = generate_fea_y(df_data, day=day, n=left)
# 将当前日期的特征数据合并到全量特征DataFrame中
df_feature_y = pd.concat([df_feature_y, daily_features], axis=0)
9. 特征强化
在基础历史时序特征的基础上,进一步生成累计统计特征和趋势差分特征,捕捉客流量的长期累积规律和短期变化趋势,提升特征的表达能力。
# 定义需要强化的基础特征类型(4类核心基础特征)
columns = ['_innum_10minutes','_outnum_10minutes','_innum_hour','_outnum_hour']
# 特征强化1:生成过去n天的累计和(sum)与平均值(mean)特征
# 循环范围:从2天到left-1天(left=6,即2-5天,捕捉多日累积规律)
for i in range(2, left):
# 遍历每类基础特征
for f in columns:
# 1.1 累计和特征:过去i天某特征的总和(如过去2天相同10分钟进站量总和)
colname1 = '_bf_'+str(i)+'_'+'days'+f+'_sum' # 特征命名规则
df_feature_y[colname1] = 0 # 初始化累计和列为0
# 累加过去1到i天的基础特征值
for d in range(1, i+1):
df_feature_y[colname1] += df_feature_y['_bf_'+str(d) + f]
# 1.2 平均值特征:过去i天某特征的平均值(累计和/天数,平滑单日波动)
colname2 = '_bf_'+str(i)+'_'+'days'+f+'_mean' # 特征命名规则
df_feature_y[colname2] = df_feature_y[colname1] / i # 平均值=累计和/天数
# 特征强化2:生成过去n天平均值的差分特征(捕捉短期变化趋势)
# 循环范围:从2天到left-1天(2-5天,与累计特征对应)
for i in range(2, left):
# 遍历每类基础特征
for f in columns:
# 2.1 获取已生成的平均值特征列名
colname1 = '_bf_'+str(i)+'_'+'days'+f+'_mean'
# 2.2 差分特征:当前时段平均值与前一时段平均值的差值(反映趋势变化)
colname2 = '_bf_'+str(i)+'_'+'days'+f+'_mean_diff' # 特征命名规则
df_feature_y[colname2] = df_feature_y[colname1].diff(1) # diff(1)计算相邻行差值
# 2.3 特殊处理:每天第一个时段(0时0分)的差分设为0
# 原因:每天第一个时段无“前一时段”,差分结果无意义,需填充为0避免缺失值
df_feature_y.loc[
(df_feature_y.hours_in_day == 0) &
(df_feature_y.ten_minutes_in_day == 0),
colname2
] = 0
10. 站点拓扑特征提取
从地铁线路图数据Metro_roadMap
中提取站点的拓扑特征(如站点连接的线路数),融合到核心特征集中,捕捉站点自身属性对客流量的影响(如换乘站通常客流量更高)。
# 函数:从线路图数据中提取站点拓扑特征(此处为站点连接的线路数)
def get_stationID_fea(df):
# 初始化存储站点特征的DataFrame
df_station = pd.DataFrame()
# 提取所有唯一站点ID
df_station['stationID'] = df['stationID'].unique()
# 生成线路列名(假设线路图数据中0-80列为不同线路的连接标识,1表示连接,0表示不连接)
cols = [str(i) for i in range(81)]
# 计算每个站点连接的线路数(求和cols列,1的总和即为连接线路数)
df_station['num_of_join_points'] = df[cols].sum(axis=1).sort_values()
return df_station
# 处理线路图数据:将索引列重命名为stationID(与核心数据对齐)
Metro_roadMap.rename(columns={'Unnamed: 0':'stationID'}, inplace=True)
# 提取站点拓扑特征(连接线路数)
df_station_metro = get_stationID_fea(Metro_roadMap)
11. 特征融合
将站点拓扑特征(num_of_join_points
)与之前生成的时序特征进行合并,构建包含“时序规律+站点属性”的完整特征集,为模型训练提供全面的输入。
# 复制全量时序特征数据到df_data
df_data = df_feature_y.copy()
# 按stationID合并时序特征与站点拓扑特征(左连接,确保所有时序数据都保留)
df_data = df_data.merge(df_station_metro, on='stationID', how='left')
12. 数据集划分
将完整特征集划分为训练集(df_train
)和验证集(df_valid
),用于模型训练和超参数调优(验证集用于监控过拟合,选择最优模型)。
# 定义特征列:排除标签列(inNums/outNums)和非特征列(startTime/endTime)
cols = [f for f in df_data.columns if f not in ['startTime','endTime','inNums','outNums']]
# 划分训练集与验证集:
# - 训练集:日期<28(相对日期对应原始日期,确保有足够数据量)
# - 验证集:日期=28(用较新的数据验证模型泛化能力)
df_train = df_data[df_data.day < 28]
df_valid = df_data[df_data.day == 28]
# 提取训练集和验证集的特征矩阵(X)和标签向量(y)
# 进站量标签(inNums)
X_train = df_train[cols].values
X_valid = df_valid[cols].values
y_train_inNums = df_train['inNums'].values
y_valid_inNums = df_valid['inNums'].values
# 出站量标签(outNums)
y_train_outNums = df_train['outNums'].values
y_valid_outNums = df_valid['outNums'].values
13. LightGBM单模型训练 - 进站量预测
使用LightGBM回归模型(目标函数为L1损失,即MAE)训练进站量预测模型,通过验证集监控模型性能,采用提前停止(Early Stopping)避免过拟合,选择最优迭代次数。
# 开始训练:设置LightGBM模型参数(针对进站量预测)
params = {
'num_leaves': 63, # 每棵树的叶子节点数(控制树复杂度)
'objective': 'regression_l1', # 目标函数:L1回归(MAE)
'max_depth': 5, # 树的最大深度(防止过拟合)
'feature_fraction': 0.9, # 每次迭代使用的特征比例(特征采样,防止过拟合)
'learning_rate': 0.05, # 学习率(步长,控制收敛速度)
'boosting': 'gbdt', # boosting类型:梯度提升决策树
'metric': 'mae', # 评估指标:平均绝对误差(MAE)
'lambda_l1': 0.1, # L1正则化(防止过拟合)
'verbose': -1 # 不输出详细日志
}
# 初始化LightGBM回归模型
model = lgb.LGBMRegressor(
**params,
n_estimators=20000, # 最大迭代次数(足够大,依赖提前停止控制)
nthread=4, # 线程数(控制训练速度)
n_jobs=-1 # 并行计算(使用所有可用CPU核心)
)
# 训练模型:
# - 输入训练集特征和标签
# - 验证集用于监控性能
# - 每200轮输出一次日志,验证集性能50轮无提升则提前停止
model.fit(
X_train, y_train_inNums,
eval_set=[(X_train, y_train_inNums), (X_valid, y_valid_inNums)],
eval_metric='mae',
callbacks=[
lgb.log_evaluation(period=200), # 日志输出周期
lgb.early_stopping(stopping_rounds=50) # 提前停止策略
]
)
输出结果:
Training until validation scores don't improve for 50 rounds
[200] training's l1: 10.8138 valid_1's l1: 13.4025
[400] training's l1: 10.4167 valid_1's l1: 12.966
[600] training's l1: 10.25 valid_1's l1: 12.8419
Early stopping, best iteration is:
[701] training's l1: 10.1822 valid_1's l1: 12.8131
14. 特征重要性可视化
通过柱状图展示模型训练过程中各特征的重要性,识别对客流量预测贡献最大的特征(如某历史时段的客流量、站点连接线路数),为特征筛选和模型优化提供依据。
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.simplefilter(action='ignore', category=FutureWarning) # 忽略未来警告
# 构建特征重要性DataFrame:按重要性值排序
feature_imp = pd.DataFrame(
sorted(zip(model.feature_importances_, cols)), # 特征重要性值与特征名配对
columns=['Value', 'Feature'] # 列名:重要性值、特征名
)
# 创建画布,设置尺寸为20x10(确保特征名显示完整)
plt.figure(figsize=(20, 10))
# 绘制前20个重要性最高的特征(水平柱状图,便于查看特征名)
sns.barplot(x="Value", y="Feature", data=feature_imp.sort_values(by="Value", ascending=False)[:20])
# 设置图表标题
plt.title('LightGBM Features (avg over folds)')
# 调整布局,避免特征名被截断
plt.tight_layout()
# 显示图表
plt.show()
15. LightGBM单模型训练 - 出站量预测
复用进站量模型的参数结构,训练出站量预测模型,同样通过验证集监控性能和提前停止,确保出站量预测的精度。
# 开始训练:设置LightGBM模型参数(与进站量模型一致,针对出站量预测)
params = {'num_leaves': 63,'objective': 'regression_l1','max_depth': 5,'feature_fraction': 0.9,
'learning_rate': 0.05,'boosting': 'gbdt','metric': 'mae','lambda_l1': 0.1,'verbose':-1}
# 初始化LightGBM回归模型
model = lgb.LGBMRegressor(**params, n_estimators = 20000, nthread = 4, n_jobs = -1)
# 训练模型:输入出站量标签,其他设置与进站量模型一致
model.fit(
X_train, y_train_outNums,
eval_set=[(X_train, y_train_outNums), (X_valid, y_valid_outNums)],
eval_metric='mae',
callbacks=[
lgb.log_evaluation(period=200), # 每200轮输出日志
lgb.early_stopping(stopping_rounds=50) # 提前停止
]
)
输出结果:
Training until validation scores don't improve for 50 rounds
[200] training's l1: 11.9483 valid_1's l1: 13.9051
[400] training's l1: 11.6399 valid_1's l1: 13.5273
[600] training's l1: 11.4227 valid_1's l1: 13.387
[800] training's l1: 11.2888 valid_1's l1: 13.347
[1000] training's l1: 11.1954 valid_1's l1: 13.2859
[1200] training's l1: 11.1287 valid_1's l1: 13.2632
[1400] training's l1: 11.0167 valid_1's l1: 13.2428
[1600] training's l1: 10.9106 valid_1's l1: 13.1989
Early stopping, best iteration is:
[1596] training's l1: 10.9121 valid_1's l1: 13.1983
16. 时序Stacking集成准备
为Stacking集成学习做准备:重新整理特征集,划分训练数据(用于训练第一层模型)和全量数据(用于生成第一层模型的预测结果,作为第二层模型的输入特征)。
# 重新复制全量特征数据(包含时序特征和站点拓扑特征)
df_data = df_feature_y.copy()
df_data = df_data.merge(df_station_metro, on='stationID', how='left')
# 定义特征列(排除标签列和非特征列)
cols = [f for f in df_data.columns if f not in ['startTime','endTime','inNums','outNums']]
# 划分第一层模型的训练数据和全量数据:
# - 第一层训练数据:日期<28(用于训练第一层模型)
# - 全量数据:所有日期(用于生成第一层模型的预测结果,作为Stacking特征)
y_inNums = df_data[df_data.day < 28]['inNums'].values # 第一层模型的进站量标签
y_outNums = df_data['outNums'].values # 出站量标签(本步骤未实际使用)
df_train = df_data[df_data.day < 28][cols].values # 第一层模型的训练特征
df_data_full = df_data[cols].values # 全量特征数据(用于生成Stacking特征)
17. 第一层Stacking模型训练与预测
训练第一层LightGBM模型(固定迭代次数),对全量数据进行进站量预测,将预测结果作为“Stacking特征”(inNums_stacking
),用于提升第二层模型的预测精度(集成多个模型的优势)。
# 开始训练:第一层Stacking模型(针对进站量,固定迭代次数为1500)
params = {'num_leaves': 63,'objective': 'regression_l1','max_depth': 5,'feature_fraction': 0.9,
'learning_rate': 0.05,'boosting': 'gbdt','metric': 'mae','lambda_l1': 0.1, 'verbose':-1}
# 初始化第一层模型(固定n_estimators=1500,不使用提前停止)
model = lgb.LGBMRegressor(**params, n_estimators=1500, nthread=4)
# 用第一层训练数据训练模型
model.fit(df_train, y_inNums)
# 对全量数据进行预测,生成Stacking特征(进站量预测结果)
inNums_stacking = model.predict(df_data_full)
18. Stacking特征融合
将第一层模型的预测结果(inNums_stacking
)作为新特征,融合到原始特征集中,构建“原始特征+Stacking特征”的增强特征集,为第二层模型提供更丰富的输入信息。
# 重新复制全量时序特征数据
df_data = df_feature_y.copy()
# 合并站点拓扑特征
df_data = df_data.merge(df_station_metro, on='stationID', how='left')
# 添加Stacking特征:第一层模型的进站量预测结果
df_data['inNums_stacking'] = inNums_stacking
# 更新特征列:包含新增的Stacking特征
cols = [f for f in df_data.columns if f not in ['startTime','endTime','inNums','outNums']]
19. 第二层Stacking模型训练
使用“原始特征+Stacking特征”的增强特征集训练第二层LightGBM模型,进一步优化进站量预测精度。通过缩小训练集时间范围(仅使用较新数据)和验证集评估,提升模型对近期数据的拟合能力。
# 重新划分训练集与验证集(使用增强特征集)
# - 训练集:日期>10且日期<28(仅使用较新数据,提升对近期模式的拟合)
# - 验证集:日期=28(保持不变,用于评估模型)
df_train = df_data[(df_data.day < 28) & (df_data.day > 10)]
df_valid = df_data[df_data.day == 28]
# 提取增强特征集的特征矩阵和标签
X_train = df_train[cols].values
X_valid = df_valid[cols].values
y_train_inNums = df_train['inNums'].values
y_valid_inNums = df_valid['inNums'].values
# 开始训练第二层Stacking模型(参数与第一层一致)
params = {'num_leaves': 63,'objective': 'regression_l1','max_depth': 5,'feature_fraction': 0.9,
'learning_rate': 0.05,'boosting': 'gbdt','metric': 'mae','lambda_l1': 0.1, 'verbose':-1}
model = lgb.LGBMRegressor(**params, n_estimators=20000, nthread=4, n_jobs=-1)
model.fit(
X_train, y_train_inNums,
eval_set=[(X_train, y_train_inNums), (X_valid, y_valid_inNums)],
eval_metric='mae',
callbacks=[
lgb.log_evaluation(period=200), # 每200轮输出日志
lgb.early_stopping(stopping_rounds=50) # 提前停止
]
)
输出结果:
Training until validation scores don't improve for 50 rounds
Early stopping, best iteration is:
[102] training's l1: 10.6063 valid_1's l1: 12.7097
(最终验证集MAE进一步降低,如12.71,表明Stacking策略有效提升了预测精度)
总结
本项目通过多维度特征工程(时间特征、历史时序特征、站点拓扑特征)和Stacking集成学习策略,构建了高精度的地铁客流量预测模型。核心流程包括:
- 数据预处理:清洗异常日期,提取时间特征;
- 特征工程:生成多粒度历史时序特征(10分钟/小时粒度、同期/偏移时段)、累计统计特征、趋势差分特征;
- 特征融合:结合站点拓扑特征和Stacking特征,增强特征表达能力;
- 模型训练:使用LightGBM回归模型,通过验证集监控和提前停止策略优化模型,最终实现MAE约12.7的预测精度。
上一章:【竞赛系列】机器学习实操项目09——全球城市计算AI挑战赛(基于LightGBM快速搭建baseline)
下一章:
机器学习核心知识点目录:机器学习核心知识点目录
机器学习实战项目目录:【从 0 到 1 落地】机器学习实操项目目录:覆盖入门到进阶,大学生就业 / 竞赛必备