手头正好在做一个电力负荷预测的复现项目,数据集翻来覆去选了好几轮,最后还是锁定了GEFCom 2014和UCI上的电力负荷数据集。GEFCom是电力负荷预测竞赛里的老牌benchmark,学术圈里大量论文用它做实验对比;UCI的ElectricityLoadForecasting数据集则更贴近日常工程:时间戳、负荷、温度这些字段清清楚楚,拿来练手Python端的特征构建和模型训练非常顺手。这篇东西,我想把从数据入手到预测模型落地这段路完整走一遍,包括为什么选这两个数据、时空特征怎么做、LightGBM和LSTM分别怎么搭、实际跑的时候踩过哪些坑,都会写到。这篇文章的核心是电力负荷时空预测模型,面向的读者是正在学时序预测、或者想快速上手一个完整项目的人,能把"数据-特征-模型-评估"这条链路跑通。
1. 数据集与项目思路:为什么从GEFCom切到UCI
1.1 GEFCom 2014到底是什么
GEFCom 2014,全称Global Energy Forecasting Competition 2014,是电力预测领域非常经典的一次国际竞赛。比赛分了负荷、风电、太阳能等多个赛道,其中负荷预测赛道提供的是按小时记录的负荷序列,同时配了对应的气象温度数据。温度是负荷预测里最重要的外部变量,空调、采暖这些负荷跟温度强相关,这个设计让GEFCom从一开始就不是单纯的"用历史推未来",而是带上了外生变量的影子。
这个数据集在学术圈用得极多,原因很简单:数据是公开的、格式相对规整、预测任务定义清晰,而且当时的冠军方案和赛后总结论文公开得比较充分,拿来当baseline非常顺手。我自己的习惯是先用GEFCom确认一个方法在这类数据上表现如何,再迁移到自己的业务数据上。它最大的价值是一个"标尺",帮你校准特征工程和模型选择的整体方向。
不过GEFCom有一个特点:它的官方任务更多关注单条或多个区域负荷序列的预测,数据组织方式偏向"时序竞赛"结构,对"空间"维度的挖掘没有刻意引导。如果你想进一步研究多个区域之间的负荷联动关系,它的数据也能做,但需要自己花时间去整理和构造特征。
1.2 UCI电力负荷数据集有什么不同
UCI机器学习库里的ElectricityLoadForecasting数据集(有时也写作ElectricityLoadForecasting2014)是电力负荷预测里另一个高频出现的实验数据。它跟GEFCom最大的区别在于:数据组织更接近实际业务表,通常包含时间戳、星期几、负荷值、温度等字段,有些版本还带额外的气象或派生特征。数据频率多数是小时级,时间跨度通常在一年以上,足够覆盖春夏秋冬的负荷形态差异。
对做工程的人来说,这种"宽表"结构反而更友好。它不像GEFCom那样需要你花时间转化数据格式,下载下来就能直接开始做特征工程。而且UCI数据集的字段噪音更少,适合作为从"看懂数据"到"跑通模型"之间的过渡材料。我建议的路线是:先用GEFCom理解负荷预测的基本问题和经典实验设定,再用UCI数据集完成一次完整的、可复现的Python建模流程,这样两边的优势都占到了。
1.3 从单变量时序到多变量时空特征
标题里有个词叫"时空预测",第一次接触的人容易把它想复杂,觉得一定要上图神经网络。其实在负荷预测场景里,"空间"可以理解成不同区域、不同台区之间的负荷相关性。比如同一个城市里,商业区和居民区的负荷形态完全不同,但它们之间会通过天气、节假日、大型活动等因素产生联动。
我的做法是分两步走:第一步,用GEFCom把单区域的负荷预测baseline做出来,理解时序模型的核心逻辑;第二步,转用UCI数据集,在特征工程里显式构造"空间"信息,比如多区域负荷的均值、差值、比例关系,或者把温度、湿度等气象特征作为区域的"外生空间变量"放进去。这样即便不用复杂的图模型,也能让预测模型感知到空间维度的同步变化。如果你后面要上更高级的方案,比如GNN,这套特征工程和baseline体系也可以直接迁移过去做对比。
2. Python环境与数据准备
2.1 环境配置与依赖库
工欲善其事,必先利其器。Python环境这块,如果刚入门,我强烈建议直接用Anaconda发行版,装完自带conda、Python解释器和一堆科学计算库,省去单独配环境的麻烦。搜索Python安装教程的话,网上方案很多,但核心就一条:别把包装乱,最好为每个项目建独立虚拟环境。
conda create -n loadforecast python=3.10 conda activate loadforecast pip install pandas numpy scikit-learn matplotlib lightgbm如果打算跑LSTM,再加一行:
pip install tensorflow装国内源的话,pip后面跟-i https://pypi.tuna.tsinghua.edu.cn/simple会快很多。我实际用下来,pandas、numpy、scikit-learn是数据处理和评估的刚需,lightgbm是梯度提升树里训练速度和精度平衡得最好的库之一,tensorflow就纯粹是为了做序列模型对比。这些库的版本不用刻意追求最新,稳定即可,我自己用的pandas 2.x、scikit-learn 1.3.x,跑下面的代码都没问题。
2.2 数据加载与字段检查
UCI下载下来的数据一般是CSV格式,读进来第一件事不是急着训练,而是把字段看清楚。
import pandas as pd df = pd.read_csv('data/uci_electricity.csv', parse_dates=['timestamp']) print(df.head()) print(df.info()) print(df.describe())parse_dates是把时间列解析成datetime类型的关键,这一步不做,后面所有时间相关的特征工程都会很痛苦。df.info()用来检查有没有缺失值、字段类型对不对,df.describe()看负荷列的最大最小值、均值标准差,第一时间发现异常数据。
列名每个版本略有差异,我见过的版本一般有这几个关键字段:时间戳、星期几、负荷值、温度。如果打开之后发现列名不完全一致,以实际为准,把代码里的列名同步改一下就行。这里有个小习惯:把原始数据备份一份,所有清洗操作都从副本开始,别直接改原始df,否则处理错了想回退都难。
2.3 缺失值处理与时间对齐
电力负荷数据出现缺失是常态,采集设备断点、传输丢包都可能导致某几个小时没有记录。处理缺失值有两条原则:一是能用插值就别直接删行,因为时间序列的连续性非常重要,删行等于告诉模型这段时间不存在,会造成时间轴断裂;二是插值方式要按缺失长度来选,短时间的缺失用前后向填充(ffill/bfill)或者线性插值足够,长时间段缺失需要更谨慎,可能需要用相似日期的负荷曲线补全。
# 短时间缺失:线性插值 df['load'] = df['load'].interpolate(method='linear') # 时间戳对齐:确保按时间升序排列 df = df.sort_values('timestamp').reset_index(drop=True) # 生成完整的时间轴,检查缺失 full_index = pd.date_range(start=df['timestamp'].min(), end=df['timestamp'].max(), freq='h') df = df.set_index('timestamp').reindex(full_index).rename_axis('timestamp').reset_index()第二步的reindex是个很实用的操作:先构造一个完整的小时索引,再和原始数据对齐,缺失的位置会自动变成NaN,接下来你可以统一处理。这个做法在做多区域数据合并时尤其重要,两个区域的时间戳可能差那么几个小时,不对齐后面构造空间特征时就会出现错位。
3. 特征工程:时空维度的体现
3.1 时间特征要分周期编码
负荷预测里时间特征是最基础也最有效的一类特征。人的作息有规律,工厂开工有周期,这些社会行为规律最终都会体现在负荷曲线上。小时、星期、月份这三个周期是最基础的,直接作为数值特征丢进模型也能用,但更好的做法是做周期编码。
原因是:小时这个特征取值是0到23,在树模型里还好,在神经网络里如果直接当数值特征,模型会认为23和0在数值上离得很远,但实际上23点和0点都是深夜,负荷形态很接近。用sin/cos循环编码可以把这个"首尾相连"的性质表达出来。
import numpy as np df['hour'] = df['timestamp'].dt.hour df['dayofweek'] = df['timestamp'].dt.dayofweek df['month'] = df['timestamp'].dt.month # 小时循环编码 df['hour_sin'] = np.sin(2 * np.pi * df['hour'] / 24) df['hour_cos'] = np.cos(2 * np.pi * df['hour'] / 24) # 星期循环编码 df['dow_sin'] = np.sin(2 * np.pi * df['dayofweek'] / 7) df['dow_cos'] = np.cos(2 * np.pi * df['dayofweek'] / 7)另外,is_weekend这种布尔特征在周末和节假日形态明显的场景里很有效。如果项目面向节假日预测,日期特征里最好把国家法定节假日、调休、特殊活动日都标记出来。春节、国庆这种长假期间负荷水平跟平时差异非常大,模型如果不告诉它"现在是假期",它很容易按平时规律预测,误差会很难看。
3.2 滞后特征与滚动统计的边界
滞后特征是时序预测里最"出效果"的手段。电力负荷往往具有明显的日周期和周期,今天上午10点的负荷,跟昨天上午10点、上周同一天上午10点的相关性极高。所以最常见的滞后特征是24小时前、48小时前、168小时前(一周前)的负荷值。
df['lag_24'] = df['load'].shift(24) df['lag_48'] = df['load'].shift(48) df['lag_168'] = df['load'].shift(168) # 滚动窗口特征 df['roll_mean_24'] = df['load'].rolling(window=24).mean().shift(1) df['roll_std_24'] = df['load'].rolling(window=24).std().shift(1)滚动均值这里我特意在后面加了一个.shift(1),这是防止数据泄漏的关键细节。比如你要预测下午3点的负荷,用"过去24小时的平均负荷"作为特征,这里的"过去24小时"在预测时点只能截止到下午2点,不能把下午3点当天的值也算进去。如果不加shift,训练时模型会看到"未来信息",验证时表现很好,一上线立刻崩。这个坑我踩过,后面会专门讲。
滞后阶数的选择可以通过自相关分析辅助判断,简单做法是画出不同滞后阶数与当前负荷的相关系数热力图。通常24、48、168这三个点会出现明显的相关性峰值。再往后阶数对模型的增量收益会递减,但特征数量会线性增长,容易造成维度膨胀。
3.3 空间维度:多区域与温度联动
说了这么多时间上的特征,终于到"空间"这一步。用UCI这类多字段数据做空间特征,我的核心思路是:把不同维度上的信息组合成"联合状态",让模型感知到多个变量的协同变化。
具体的特征构造方法可以包括以下几种:
# 假设数据里有区域A和区域B两列负荷 df['region_diff'] = df['load_a'] - df['load_b'] df['region_mean'] = (df['load_a'] + df['load_b']) / 2 # 温度变化率 df['temp_grad'] = df['temp'].diff(1) # 温度与滞后的交互特征 df['temp_lag24'] = df['temp'].shift(24) df['load_temp_inter'] = df['load'].shift(24) * (df['temp'] - df['temp'].mean())区域差值和均值是最直观的空间关系表达。比如商业区负荷和居民区负荷的差值,能反映当天是工作日还是休息日;区域均值能缓解单区域数据抖动的问题。温度的差分和滞后项则表达了气象条件的变化趋势——温度骤降往往意味着采暖负荷上升,这种变化比温度绝对值更敏感。
如果想更进一步,可以计算两个区域负荷序列的滚动相关系数,把相关性作为特征。区域之间如果高度耦合,比如都受同一个气温影响,那么它们负荷曲线的相关系数会在某个区间内波动,这种特征能帮助模型理解"当前空间状态"。这个方法不需要图神经网络也能达到类似效果,而且可解释性强。等这套特征体系搭建完,再去探索图神经网络也不迟。
3.4 特征重要性验证
特征做了一大堆,哪些真正有用?我习惯用LightGBM自带的重要度排序来做第一轮筛选。第一步训练一个快速模型,打印出feature importance前20的特征,重点看滞后特征和温度特征是不是占主导;第二步对一些可疑特征做删除实验,看指标变化。
model = lgb.LGBMRegressor(n_estimators=300, learning_rate=0.05, random_state=42) model.fit(X_train, y_train) importance = pd.DataFrame({ 'feature': X_train.columns, 'importance': model.feature_importances_ }).sort_values('importance', ascending=False) print(importance.head(20))特征筛选的核心原则是"宁少勿滥"。很多刚接触特征工程的同学喜欢堆一大堆特征,模型训练慢不说,还容易过拟合。我的经验是:先用重要性排序和相关性分析砍掉明显无效的特征,再对保留的特征做组合和衍生。电力负荷预测里,时间特征、滞后特征、温度特征基本构成了核心特征集,其他特征都是在这个基础上做增量补充。
4. 模型构建:LightGBM与LSTM实测
4.1 时序交叉验证怎么做才不泄漏
时序预测的数据集切分跟普通机器学习有本质区别。普通任务可以用train_test_split随机打乱数据,但时序数据一旦随机打乱,训练集里就出现了"未来的数据",模型等于开了天眼。正确的切分方式是严格按照时间先后顺序,前一段做训练,后一段做验证。
from sklearn.model_selection import TimeSeriesSplit tscv = TimeSeriesSplit(n_splits=5, gap=24) for train_idx, val_idx in tscv.split(X): X_train, X_val = X.iloc[train_idx], X.iloc[val_idx] y_train, y_val = y.iloc[train_idx], y.iloc[val_idx]这里gap=24的意思是训练集和验证集之间留出24个小时的间隔。为什么要有这个间隔?因为滞后特征的存在:验证集第一行的lag_24特征,它对应的值是训练集最后一天的数据,如果训练和验证紧挨着,这个特征值会恰好落在训练集内,形成一种隐式的信息重叠。隔开24小时,验证集第一行的滞后特征就不会来自训练集了,评估结果更可靠。
初次跑项目也可以简单一点,按7:3或者8:2直接切分,但一定要保证训练集的时间在前。时间序列切分这里不值得省时间,做对了,后面的评估结果才有参考意义。
4.2 LightGBM Baseline:半小时出模型
LightGBM是我在负荷预测项目里的默认baseline。它的优势是训练速度快、能处理大量特征、树模型天然可以捕捉到非线性关系和时间特征的交互效应。对于电力负荷这类表格形态的预测问题,LightGBM往往能打出非常强的效果。
import lightgbm as lgb from sklearn.metrics import mean_squared_error, mean_absolute_percentage_error features = ['hour_sin', 'hour_cos', 'dow_sin', 'dow_cos', 'lag_24', 'lag_48', 'lag_168', 'roll_mean_24', 'roll_std_24', 'temp'] X = df[features] y = df['load'] # 按时间顺序切分 split_idx = int(len(df) * 0.8) X_train, X_val = X.iloc[:split_idx], X.iloc[split_idx:] y_train, y_val = y.iloc[:split_idx], y.iloc[split_idx:] model = lgb.LGBMRegressor( n_estimators=500, learning_rate=0.05, num_leaves=31, subsample=0.8, colsample_bytree=0.8, random_state=42 ) model.fit( X_train, y_train, eval_set=[(X_val, y_val)], eval_metric='rmse', callbacks=[lgb.early_stopping(50)] ) y_pred = model.predict(X_val) rmse = np.sqrt(mean_squared_error(y_val, y_pred)) mape = mean_absolute_percentage_error(y_val, y_pred) * 100 print(f'LightGBM RMSE: {rmse:.2f}, MAPE: {mape:.2f}%')这里的参数是我跑了多次之后比较稳定的组合:n_estimators设大一些,配合early_stopping自动选择最佳迭代轮数;num_leaves=31是经验值,叶子数太大容易过拟合,太小则拟合不足;subsample和colsample_bytree都设为0.8,引入随机性增强泛化能力。实际跑下来,LightGBM在验证集上的MAPE通常能到2%-4%区间,具体取决于数据噪声程度和特征质量。
4.3 LSTM模型的简化实现
LSTM这类循环神经网络在处理序列依赖上有天然优势,它不需要像LightGBM那样手工构建大量滞后特征,而是通过记忆单元自动学习历史模式。但LSTM的缺点是训练时间长、需要的数据量和调参经验更多。
下面是一个用TensorFlow/Keras实现的简化版LSTM。注意LSTM的输入格式是三维的:样本数、时间步长、特征数。这里我用过去48小时的负荷序列作为窗口,预测下一小时的负荷。
import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.optimizers import Adam # 构造序列样本 def create_sequences(data, seq_len=48): X, y = [], [] for i in range(seq_len, len(data)): X.append(data[i-seq_len:i]) y.append(data[i]) return np.array(X), np.array(y) # 只用负荷值做单变量序列示例 load_values = df['load'].values.reshape(-1, 1) # 归一化 from sklearn.preprocessing import MinMaxScaler scaler = MinMaxScaler() load_scaled = scaler.fit_transform(load_values) X_seq, y_seq = create_sequences(load_scaled, seq_len=48) # 同样按时间切分 split_idx = int(len(X_seq) * 0.8) X_train, X_val = X_seq[:split_idx], X_seq[split_idx:] y_train, y_val = y_seq[:split_idx], y_seq[split_idx:] model = Sequential([ LSTM(64, return_sequences=True, input_shape=(48, 1)), Dropout(0.2), LSTM(32), Dense(1) ]) model.compile(optimizer=Adam(learning_rate=0.001), loss='mse') model.fit(X_train, y_train, validation_data=(X_val, y_val), epochs=10, batch_size=256, verbose=1) # 预测并反归一化 y_pred_scaled = model.predict(X_val) y_pred = scaler.inverse_transform(y_pred_scaled) y_true = scaler.inverse_transform(y_val)这个例子为了简洁只用了单变量负荷序列,但LSTM完全可以接收多维特征,比如小时、温度、滞后值都拼接到输入的最后一个维度上。我的实际经验是:多变量LSTM的效果通常优于单变量LSTM,但优化难度也更高,需要更多的调参耐心。如果你时间有限,建议先用LightGBM打底,LSTM作为对比实验和后续模型上线的候选方案。
4.4 多步预测和滚动更新策略
上面的例子都是单步预测,也就是预测未来1个小时的负荷。但实际业务里,我们更常需要预测未来24小时甚至更长时间。多步预测有三种常见范式:递归预测、直接预测、seq2seq预测。
递归预测的做法是:先预测t+1时刻的负荷,把这个预测值拼到历史序列末尾,再预测t+2,依次往后滚。它的优点是实现简单,缺点是一旦某一步预测出现偏差,误差会像滚雪球一样累积。直接预测是训练多个模型,分别预测t+1、t+2、……t+24,每个模型管一个预测步长,误差不会累积,但训练和维护成本高。seq2seq的做法则是用一个编码器读取历史序列,解码器输出未来一段时间的序列,这也是很多工业系统的选择。
对于LightGBM这类基于特征的模型,我做24小时预测时更推荐直接法的一个变体:为每个预测步长训练一个模型,但共享同一个特征集。这样每个模型专注于自己的预测步长,误差累积几乎可以忽略。如果只想快速验收,可以先用递归法跑一个简单的多步预测脚本,把思路验证通了再升级。
5. 评估指标与结果分析
5.1 指标选择:RMSE、MAPE与业务含义
评估指标的选择取决于业务诉求。RMSE(均方根误差)对大误差非常敏感,适合那些"误差大就代表事故"的场景,比如电网安全评估;MAE(平均绝对误差)的解读更直观,就是平均偏差多少千瓦;MAPE(平均绝对百分比误差)则是无量纲的,方便在不同量纲的数据之间对比模型优劣。
from sklearn.metrics import mean_absolute_error, mean_squared_error, mean_absolute_percentage_error mae = mean_absolute_error(y_val, y_pred) rmse = np.sqrt(mean_squared_error(y_val, y_pred)) mape = mean_absolute_percentage_error(y_val, y_pred)需要注意,MAPE在负荷值接近0的时候会趋近无穷大,因为分母是实际值。电力负荷数据在凌晨可能降到很低,直接用MAPE容易被个别接近0的点带偏。我的习惯是计算MAPE时,把负荷低于某个阈值(比如最大负荷的5%)的数据点过滤掉,或者改用sMAPE(对称平均绝对百分比误差)。在向业务方汇报时,MAPE仍然是最好沟通的指标,因为"预测准确率是97%"比"RMSE是200千瓦"更容易被理解。
5.2 预测曲线怎么画才直观
模型效果好坏,除了看数字指标,更直观的方式是画预测值和真实值的对比曲线。选一个连续几天的窗口,把真实负荷、预测负荷画在同一个坐标轴里,肉眼就能看出模型是否抓住了负荷的日周期、早晚高峰和整体趋势。
import matplotlib.pyplot as plt plt.figure(figsize=(12, 5)) plt.plot(y_val[:168], label='Actual', linewidth=1.5) plt.plot(y_pred[:168], label='Predicted', linewidth=1.5, linestyle='--') plt.legend() plt.xlabel('Hour') plt.ylabel('Load') plt.title('Load Prediction: Actual vs Predicted (7 days)') plt.show()画168小时(7天)的曲线是个不错的选择,既能看到日周期,也不至于因为点太多看不清细节。另外一个有用的图是误差分布直方图,横轴是预测误差,纵轴是频次。如果误差分布近似正态分布且集中在零附近,说明模型整体表现稳定;如果左偏或右偏,说明系统性地高估或低估了负荷,可能需要在特征里补充某些信息,比如温度校正或者节假日标记。
5.3 模型对比与调参心得
把LightGBM、LSTM和历史均值基线放在一起对比,能直观看出模型带来的增量。历史均值基线很简单:用过去7天同一时刻的负荷平均值作为预测。这个基线虽然笨,但能反映数据本身的周期性强度,也是判断复杂模型是否有效的底线。
下面是一次实际运行中的结果示例(具体数值会随数据和切分方式变化,但数量级可以参照):
| 模型 | RMSE | MAPE | 训练时间 | 特点 |
|---|---|---|---|---|
| 历史均值基线 | 320 | 8.5% | 0秒 | 仅用周期均值,无学习过程 |
| LightGBM | 105 | 2.8% | 约2分钟 | 特征工程强依赖,训练快 |
| LSTM(单变量) | 125 | 3.6% | 约10分钟 | 自动学习时序依赖,调参成本高 |
LightGBM在这类负荷预测任务上通常是最省事的强baseline。它的一个额外优势是特征重要性分析非常方便,能反过来指导你优化特征工程。LSTM的优势在于序列建模的灵活性,输入窗口可以直接滑动使用,但对数据量和调参要求更高,初学者很难第一次就调到最好的效果。我的建议是:先把LightGBM做到位,再尝试LSTM,两者形成互补和对比,而不是一上来就奔着深度模型去。
6. 常见问题与排查技巧实录
6.1 数据泄漏:最隐蔽的错误
数据泄漏是时序预测里最隐蔽也最致命的错误。最常见的三种泄漏场景:第一,随机切分忘了关shuffle,训练集混入未来数据;第二,滚动统计特征没有shift(1),当前时刻的信息被算进了特征;第三,使用了归一化器在全量数据上拟合,导致验证集信息混入训练过程。
第一种最好排查,切分时加shuffle=False就行;第二种需要在代码审查时格外留意,rolling().mean()后面是否跟着shift(1);第三种则发生在用MinMaxScaler或StandardScaler时。scaler.fit()必须在训练集上调用,然后用训练集上得到的参数去transform验证集。如果直接对全量数据fit_transform,验证集的均值和方差信息就泄漏到了训练阶段,评估结果会虚高。
6.2 节假日和异常天气怎么处理
节假日是电力负荷预测里不可忽视的干扰因素。在GEFCom这种国外数据集里,感恩节、圣诞节的负荷形态跟普通工作日差异很大。国内场景更是如此,春节前后两周的负荷曲线完全是另一种模式,很多工厂停工、人口流动剧烈,模型如果没见过这种模式,预测误差会非常夸张。
处理思路有两个层次:第一层是把节假日标记成特征(is_holiday、holiday_type、days_to_holiday等),让模型知道当前日期处于什么样的节假日状态;第二层是针对长假期的极端情况,单独建立调整规则或者用相似年份的同假期数据进行校准。实测下来,第一层特征工程能解决大部分问题,第二层规则适合在预测精度的最后冲刺阶段使用。
异常天气方面,温度骤降、台风暴雨这类极端事件会造成负荷短期剧烈波动,常规模型很难完美预测。可以增加气象预报特征,比如未来24小时的温差、降雨概率等,也可以采用分位数预测的框架,为调度人员提供更全面的风险区间。这个方向属于进阶玩法,baseline稳定后可以慢慢加。
6.3 训练慢、内存爆了怎么办
LightGBM在普通配置的电脑上训练负荷预测模型一般不会太慢,但如果特征数量爆炸或者数据跨度特别长,也会遇到性能问题。我的排查顺序是:先看特征数量,删掉重要度极低的特征;再看数据量,如果数据跨度好几年,可以先按年抽样或者降低训练样本密度;最后调整LightGBM参数,把subsample调低到0.5,把feature_fraction调低,训练速度会明显提升,代价是精度可能有微小下降。
LSTM的显存和训练时间问题更常见。处理方式是控制序列长度,48小时窗口是一个性价比比较高的选择;减小batch size和隐藏层维度;用tf.data做数据管道,避免每次迭代都从numpy数组切数据。另外深度学习模型强烈建议用GPU,哪怕是入门级的显卡也比CPU快好几倍。
6.4 上线部署时的特征对齐
模型上线时最容易出的问题不是模型本身,而是训练和推理时的特征不一致。训练时你生成的lag_24、roll_mean_24都是基于历史数据库的实时计算,上线后推理请求到达时,当前真实负荷可能还没有入库,滞后特征就会缺失,这个坑非常隐蔽。
解决办法是建立一个统一的特征生成函数,在训练和推理时都调用同一个函数,保证逻辑一致。同时在推理接口里做好兜底逻辑:如果某个滞后特征取不到,用最近可用值填充,并且记录一条日志,方便排查。还有一点容易被忽略:模型持久化之后,重新加载时特征顺序不能变。用model.feature_name_可以检查LightGBM模型内部的参数名,确保推理时传入的DataFrame列顺序和训练时一致。很多线上bug都是这个环节出的问题,测试的时候要多覆盖"特征顺序打乱"的用例。
最后聊点个人感受。这类电力负荷预测项目,最花时间的部分其实不是模型选型,而是数据清洗和特征工程。GEFCom和UCI两个数据集很有代表性,前者让我理解了负荷预测的经典任务设定,后者让我能专注于把Python建模流程跑通。代码本身不复杂,难的是对数据规律的理解和对细节的把控。如果你也在做类似的项目,我建议一定先把基线模型跑通,再逐步叠加特征和高级模型,这样才能判断每一步的改进是否真实有效。