哈喽,我是小白~
今天和大家来聊聊时间序列预测中随机森林和GRU的混合模型~
先从具体过程分解开始~
步骤一:时间序列数据准备(窗口划分)
假设我们有一个每日销售额的时间序列:
数据: y = [y, y, y, ..., y]
用 滑动窗口法 构造输入:
步骤二:随机森林提取特征
用滑动窗口得到的每一个 当作输入,训练随机森林模型:
或者,我们也可以使用随机森林中间的“叶子索引”或“树路径编码”作为 非线性高维表示 (像特征变换):
步骤三:构造 GRU 输入
将原始窗口 和随机森林输出或编码 拼接:
然后我们可以把一系列 组成 GRU 的时间输入序列:
步骤四:GRU 时间建模
GRU 单元状态更新如下(重复细节解释):
更新门 (决定保留多少历史信息):
重置门 (决定遗忘多少旧状态):
候选隐藏状态 :
更新隐藏状态 :
输出预测值:
优化目标(损失函数)
最常见是 MSE(均方误差):
也可以根据应用使用 MAE、Huber Loss、加权误差等。
混合模型的变种
1. 并联结构(Ensemble)
其中 是一个加权系数(可调超参数,也可以学习得到)。
2. 二阶段结构(预训练 + Fine-tune)
总的来说,如果你时间序列数据中存在 复杂的非线性结构 + 时序依赖 ,而且希望提升模型的 泛化能力 和 抗噪性 ;
那么这个“ 随机森林 + GRU 的混合模型 ”确实是一个非常值得尝试的结构。
完整案例
之类,将传统机器学习方法(随机森林)与深度学习模型(GRU)结合进行时间序列预测。
首先生成带趋势、季节性和噪声的虚拟时间序列数据,并通过滑动窗口构造特征。
然后使用随机森林模型进行初步预测,并将其输出作为额外特征加入 GRU 模型输入中。
通过 PyTorch 构建并训练 GRU 网络,以进一步提升预测精度。最终可视化原始序列、预测效果和训练损失,并计算混合模型在测试集上的均方误差(MSE)评价指标。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
import torch
import torch.nn as nn
from torch.utils.data import DataLoader, TensorDataset
# 1. 生成模拟时间序列数据(带趋势、季节性与噪声)
np.random.seed(42)
T = 500
time = np.arange(T)
trend = time * 0.02
seasonal = 2 * np.sin(2 * np.pi * time / 50)
noise = 0.5 * np.random.randn(T)
series = 10 + trend + seasonal + noise
# 2. 构造滑动窗口特征
def create_windows(data, window_size):
X, y = [], []
for i in range(len(data) - window_size):
X.append(data[i:i+window_size])
y.append(data[i+window_size])
return np.array(X), np.array(y)
window_size = 20
X, y = create_windows(series, window_size)
# 3. 划分训练和测试集(避免泄露,按时间顺序切分)
split_idx = int(0.8 * len(X))
X_train, X_test = X[:split_idx], X[split_idx:]
y_train, y_test = y[:split_idx], y[split_idx:]
# 4. 训练随机森林并提取预测值
rf = RandomForestRegressor(n_estimators=100, random_state=42)
rf.fit(X_train, y_train)
rf_train_pred = rf.predict(X_train)
rf_test_pred = rf.predict(X_test)
# 5. 将 RF 输出拼接入 GRU 输入特征
X_train_gru = np.hstack([X_train, rf_train_pred.reshape(-1,1)])
X_test_gru = np.hstack([X_test, rf_test_pred.reshape(-1,1)])
# 6. PyTorch GRU 模型定义
class GRUModel(nn.Module):
def __init__(self, input_size, hidden_size=32, num_layers=1):
super(GRUModel, self).__init__()
self.gru = nn.GRU(input_size, hidden_size, num_layers, batch_first=True)
self.fc = nn.Linear(hidden_size, 1)
def forward(self, x):
out, _ = self.gru(x)
out = self.fc(out[:, -1, :])
return out
# 准备 DataLoader
def to_tensor_dataset(X, y):
return TensorDataset(torch.tensor(X, dtype=torch.float32).unsqueeze(1),
torch.tensor(y, dtype=torch.float32).unsqueeze(-1))
train_dataset = to_tensor_dataset(X_train_gru, y_train)
test_dataset = to_tensor_dataset(X_test_gru, y_test)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=False)
test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)
# 7. 训练 GRU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = GRUModel(input_size=window_size+1).to(device)
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
train_losses = []
for epoch in range(50):
model.train()
epoch_loss = 0
for X_batch, y_batch in train_loader:
X_batch, y_batch = X_batch.to(device), y_batch.to(device)
optimizer.zero_grad()
outputs = model(X_batch)
loss = criterion(outputs, y_batch)
loss.backward()
optimizer.step()
epoch_loss += loss.item() * X_batch.size(0)
train_losses.append(epoch_loss / len(train_loader.dataset))
# 8. 测试并预测
model.eval()
with torch.no_grad():
test_preds = []
test_truth = []
for X_batch, y_batch in test_loader:
X_batch = X_batch.to(device)
preds = model(X_batch).cpu().numpy().flatten()
test_preds.extend(preds)
test_truth.extend(y_batch.numpy().flatten())
# 9. 画图展示
# 图1:原始序列与训练/测试分割
plt.figure(figsize=(10,4))
plt.plot(time, series, color='magenta', label='原始序列')
plt.axvline(x=split_idx+window_size, color='cyan', linestyle='--', label='训练/测试分界')
plt.title('图1:原始时间序列与训练测试分割')
plt.legend()
plt.show()
# 图2:随机森林预测 vs 真实值(测试集)
plt.figure(figsize=(10,4))
plt.plot(range(len(y_test)), y_test, color='orange', label='真实值 y_test')
plt.plot(range(len(rf_test_pred)), rf_test_pred, color='blue', linestyle='--', label='RF 预测')
plt.title('图2:随机森林在测试集上的表现')
plt.legend()
plt.show()
# 图3:GRU 训练损失曲线
plt.figure(figsize=(10,4))
plt.plot(range(1, len(train_losses)+1), train_losses, color='red', marker='o')
plt.title('图3:GRU 训练损失曲线')
plt.xlabel('Epoch')
plt.ylabel('MSE Loss')
plt.show()
# 图4:混合模型最终预测 vs 真实值
plt.figure(figsize=(10,4))
plt.plot(range(len(test_truth)), test_truth, color='green', label='真实值')
plt.plot(range(len(test_preds)), test_preds, color='purple', linestyle='--', label='混合模型预测')
plt.title('图4:混合模型在测试集上的预测表现')
plt.legend()
plt.show()
# 10. 输出评价指标
mse = mean_squared_error(test_truth, test_preds)
print(f"混合模型测试集 MSE: {mse:.4f}")
此外,代码计算了测试集上的 MSE 指标,量化了模型性能。全流程严格按时间先后顺序切分数据,确保无“信息泄露”。