电力负荷预测这类时间序列问题,最让人头疼的就是如何实现连续多步的高精度预测。传统单步预测就像蒙着眼睛走路,每次只能试探性地迈出一步。而多步滚动预测则像打开了手电筒,能够照亮前方多步的路况。这里我用的GRU模型配合滑动窗口机制,就像给预测装上了"记忆眼镜"。
具体实现时,滑动窗口机制是关键。假设我们设置观测窗口(train_window)为32小时,预测长度(pre_len)为4小时。模型会先用前32小时的数据预测接下来4小时的值,然后窗口滑动1小时,用33小时的数据预测下一个4小时,如此循环。这种滚动预测方式更接近实际业务场景,因为现实中我们往往需要连续预测未来多个时间点的数值。
我曾在某能源公司的实际项目中验证过,相比单步预测,多步滚动预测的误差累计会更大,但通过调整窗口大小和预测步长的比例,能够找到最佳平衡点。一般来说,观测窗口至少要是预测长度的4-5倍,这样模型才有足够的历史信息来捕捉规律。
电力负荷数据通常存在明显的周期性和趋势性。我常用的ETTh1数据集包含电力系统的多项指标,这里我们以'OT'特征列为例:
python复制# 数据读取与预处理
true_data = pd.read_csv('ETTh1.csv')
target = 'OT' # 目标特征列
train_size = 0.85 # 训练集比例
pre_len = 4 # 预测未来4小时
train_window = 32 # 观测窗口为32小时
# 数据标准化
scaler_train = MinMaxScaler(feature_range=(0, 1))
train_data_normalized = scaler_train.fit_transform(train_data.reshape(-1, 1))
数据标准化是容易被忽视但极其重要的一步。电力负荷值可能跨度很大,不进行归一化会导致模型难以收敛。我习惯用MinMaxScaler将数据压缩到0-1之间,预测后再反归一化得到真实值。
这是整个项目最核心的部分,需要特别注意时间序列的连续性:
python复制def create_inout_sequences(input_data, tw, pre_len):
inout_seq = []
L = len(input_data)
for i in range(L - tw - pre_len):
train_seq = input_data[i:i + tw]
train_label = input_data[i + tw:i + tw + pre_len]
inout_seq.append((train_seq, train_label))
return inout_seq
这个函数会生成一系列"历史窗口-预测目标"的数据对。比如总共有100小时数据,窗口32小时,预测4小时,最终会生成64组训练样本(100-32-4)。在实际项目中,我发现适当重叠的滑动窗口能增加数据多样性,提升模型泛化能力。
相比LSTM,GRU的参数更少,训练速度更快,这在电力负荷预测这类对实时性要求较高的场景中很有优势:
python复制class GRU(nn.Module):
def __init__(self, input_dim=1, hidden_dim=32, num_layers=1, output_dim=1):
super(GRU, self).__init__()
self.gru = nn.GRU(input_dim, hidden_dim, num_layers=num_layers, batch_first=True)
self.fc = nn.Linear(hidden_dim, output_dim)
self.dropout = nn.Dropout(0.1)
def forward(self, x):
h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_dim).to(x.device)
out, _ = self.gru(x, h0)
out = self.dropout(out[:, -self.pre_len:, :])
return self.fc(out)
这里有几个关键点:
batch_first=True让输入维度为(batch, seq_len, features),更符合直觉训练时我发现几个实用技巧:
python复制optimizer = torch.optim.Adam(model.parameters(), lr=0.005)
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, 'min')
loss_function = nn.MSELoss()
for epoch in range(100):
model.train()
for seq, labels in train_loader:
optimizer.zero_grad()
y_pred = model(seq)
loss = loss_function(y_pred, labels)
loss.backward()
optimizer.step()
scheduler.step(loss)
测试阶段的滚动预测需要特别注意数据衔接问题。我采用的方法是:
python复制model.eval()
test_inputs = train_data_normalized[-train_window:].tolist()
for i in range(len(test_data_normalized)):
seq = torch.FloatTensor(test_inputs[-train_window:])
with torch.no_grad():
pred = model(seq.unsqueeze(0))
test_inputs.append(pred[0,-1,0].item())
这种方法虽然会积累误差,但最接近实际应用场景。在电力负荷预测中,我通常会让预测长度(pre_len)小于实际需要预测的步数,然后多次滚动预测。
误差分析我主要看三个指标:
python复制def evaluate(y_true, y_pred):
mae = np.mean(np.abs(y_true - y_pred))
rmse = np.sqrt(np.mean((y_true - y_pred)**2))
mape = np.mean(np.abs((y_true - y_pred)/y_true))*100
return mae, rmse, mape
可视化时我习惯用对比曲线图,同时标注关键误差点:
python复制plt.figure(figsize=(12,6))
plt.plot(test_data, label='Actual Load')
plt.plot(predictions, label='Predicted Load', linestyle='--')
plt.fill_between(range(len(test_data)),
predictions - mae,
predictions + mae,
alpha=0.2, color='orange')
plt.title('Load Prediction with Error Range')
plt.legend()
经过多个项目实践,我总结出一些超参数设置经验:
问题1:预测结果滞后
这是时间序列预测的老大难问题。我的解决办法是:
问题2:极端值预测不准
电力负荷在节假日经常出现异常值:
问题3:长期预测误差累积
以下是整合了所有优化点的完整代码,我在多个工业项目中都使用过这个框架:
python复制import torch
import torch.nn as nn
import numpy as np
from sklearn.preprocessing import MinMaxScaler
class GRUModel(nn.Module):
def __init__(self, input_dim=1, hidden_dim=32, output_dim=1, n_layers=2, dropout=0.2):
super().__init__()
self.gru = nn.GRU(input_dim, hidden_dim, n_layers,
batch_first=True, dropout=dropout)
self.fc = nn.Linear(hidden_dim, output_dim)
def forward(self, x):
h0 = torch.zeros(self.gru.num_layers, x.size(0),
self.gru.hidden_size).to(x.device)
out, _ = self.gru(x, h0)
out = self.fc(out[:, -pre_len:, :])
return out
# 数据准备
def prepare_data(data, train_window=32, pre_len=4):
scaler = MinMaxScaler()
data_norm = scaler.fit_transform(data.reshape(-1,1))
sequences = []
for i in range(len(data_norm)-train_window-pre_len):
seq = data_norm[i:i+train_window]
label = data_norm[i+train_window:i+train_window+pre_len]
sequences.append((seq, label))
train_size = int(0.85*len(sequences))
train_data = sequences[:train_size]
test_data = sequences[train_size:]
return train_data, test_data, scaler
# 训练函数
def train_model(model, train_data, epochs=100, lr=0.005):
optimizer = torch.optim.Adam(model.parameters(), lr=lr)
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, 'min')
loss_fn = nn.HuberLoss()
for epoch in range(epochs):
model.train()
total_loss = 0
for seq, labels in train_data:
optimizer.zero_grad()
pred = model(torch.FloatTensor(seq).unsqueeze(0))
loss = loss_fn(pred, torch.FloatTensor(labels).unsqueeze(0))
loss.backward()
optimizer.step()
total_loss += loss.item()
avg_loss = total_loss/len(train_data)
scheduler.step(avg_loss)
if epoch % 10 == 0:
print(f'Epoch {epoch} | Loss: {avg_loss:.4f}')
return model
这个版本加入了Huber损失、学习率调度和更灵活的模型结构,在实际项目中表现更稳定。使用时只需要准备电力负荷数据,设置合适的窗口大小和预测长度即可。