在很多的时间序列预测任务中,利用卷积神经网络(CNN)和长短期记忆网络(LSTM)的混合模型是目前常见的深度学习解决方案之一。
CNN和LSTM各自有不同的特长,CNN擅长局部模式的捕捉,LSTM擅长捕捉序列的长依赖关系。通过混合这两种网络,可以非常好地学习时间序列数据中的复杂模式。
核心原理 CNN 部分 :CNN 的优势在于能够从输入数据中提取局部特征。对于时间序列预测问题,时间序列可以看作一维数据序列。CNN 可以通过一维卷积操作提取时间序列的局部时间依赖模式,如趋势、周期性波动等局部特征。
LSTM 部分 :LSTM 是一种递归神经网络(RNN)的变体,专门设计用来解决长期依赖问题。通过记忆门控机制(如输入门、遗忘门、输出门),LSTM 能够很好地捕捉序列中长期的时间依赖关系,并对未来的值进行预测。
混合模型首先使用 CNN 提取局部特征,然后将这些特征输入到 LSTM 中,进一步捕捉时间序列的长时间依赖模式,从而提高预测精度。
模型架构 输入层 :输入的时间序列数据可以是一个
的矩阵, 其中 表示时间序列的长度, 表示特征的维度。
卷积层(CNN) :通过一维卷积层(1D Convolutional Layer)来提取局部特征。假设卷积核的大小为 ,则卷积操作可以表示为:
其中, 表示卷积操作,
是卷积核, 是偏置, 是激活函数,通常选择 ReLU 或者 Sigmoid。卷积层会提取局部时间窗口内的模式,如趋势或短期波动。
池化层(可选) :通过池化层(Pooling Layer)减少特征的空间尺寸。常用的池化方式包括最大池化(Max Pooling)和平均池化(Average Pooling),以便于减少模型的计算量和防止过拟合。
LSTM 层 :将经过 CNN 处理后的特征序列输入到 LSTM 层,捕捉长期依赖。LSTM 的输入是卷积层的输出特征序列。LSTM 的计算公式如下:
其中, 是遗忘门, 是输入门, 是输出门, 是记忆单元的状态,
是当前的隐藏状态, 和 分别是权重矩阵和偏置项。LSTM 层能够有效捕捉输入序列中长期的依赖关系。
全连接层 :LSTM 输出的隐藏状态 被传递给全连接层,生成最终的预测值。全连接层的输出公式为:其中, 和 分别是全连接层的权重和偏置项, 是预测的结果。
损失函数 :常见的时间序列预测任务的损失函数为均方误差(MSE):其中,
是样本数, 是实际值, 是预测值。
公式解释 卷积操作 :在时间序列中,1D 卷积可以理解为将卷积核应用到连续的时间点上,提取时间窗口内的特征。公式中的 表示当前时间点
及其前后 个时间点的数据。通过这种方式,CNN 能够在不丧失时间顺序的情况下提取局部模式。
LSTM 结构 :LSTM 是通过门控机制来控制信息的流动。遗忘门 控制哪些历史信息需要保留,输入门 控制哪些新信息需要加入,输出门 决定了当前时刻的隐藏状态输出。记忆单元 记录着每个时刻的重要信息,从而保留了时间序列的长期依赖性。
优势
局部和全局模式捕捉 :CNN 擅长在短期窗口内捕捉局部时间模式(例如季节性波动),而 LSTM 擅长捕捉长期依赖。结合二者可以更好地应对复杂的时间序列预测任务。
降维与特征提取 :CNN 提取特征的同时减少数据的维度,减少了输入 LSTM 的信息量,避免了 LSTM 因输入序列过长导致的效率问题。
总的来说,CNN 和 LSTM 的混合模型结合了卷积网络和递归网络的优势,可以在时间序列预测任务中更好地捕捉短期与长期的特征,从而提升预测精度。
一个完整案例 CNN-LSTM 混合模型在时间序列预测中的应用。
时间序列预测是机器学习中的一大热门课题,特别是在金融、气象、能源消耗预测等领域。CNN(卷积神经网络)和 LSTM(长短期记忆网络)分别在特征提取和捕获时间依赖性方面各有独特优势,因此将这两者结合起来构建混合模型,能够有效提升预测性能。
本案例将介绍如何基于 PyTorch 实现一个 CNN-LSTM 混合模型用于时间序列预测,并展示相关的可视化图形,帮助大家直观理解模型表现。
CNN-LSTM 的混合模型结合了 CNN 提取局部特征的能力和 LSTM 学习时序依赖的能力。该模型首先通过 CNN 层对输入的时间序列进行卷积,提取高层次特征,然后将这些特征输入到 LSTM 中,用于捕获时间上的长期依赖,最后通过全连接层输出预测结果。
模型的结构如下图所示:
输入数据 -> CNN层(卷积+池化) -> LSTM层 -> 全连接层 -> 输出预测
这种结构能够有效提取数据的局部特征(CNN)和时间依赖关系(LSTM),在时间序列预测任务中具有优势。
数据准备 我们使用虚拟数据集来模拟时间序列预测任务。假设数据是某种周期性波动和随机噪声叠加的时间序列。
import numpy as npimport matplotlib.pyplot as pltimport torchimport torch.nn as nnimport torch.optim as optimfrom sklearn.preprocessing import MinMaxScalerfrom torch.utils.data import DataLoader, TensorDataset# 生成虚拟时间序列数据 np.random.seed(42 ) time = np.arange(0 , 1000 , 0.1 ) data = np.sin(0.05 * time) + np.sin(0.01 * time) + 0.5 * np.random.randn(len(time))# 标准化数据 scaler = MinMaxScaler(feature_range=(-1 , 1 )) data = scaler.fit_transform(data.reshape(-1 , 1 )).reshape(-1 )# 准备时间序列数据集 (输入序列, 预测值) def create_sequences (data, seq_length) : xs = [] ys = [] for i in range(len(data)-seq_length-1 ): x = data[i:(i+seq_length)] y = data[i+seq_length] xs.append(x) ys.append(y) return np.array(xs), np.array(ys) seq_length = 50 # 时间序列长度 X, y = create_sequences(data, seq_length)# 转换为 PyTorch 张量
X = torch.from_numpy(X).float() y = torch.from_numpy(y).float()# 划分训练集和测试集 train_size = int(len(X) * 0.8 ) train_X, test_X = X[:train_size], X[train_size:] train_y, test_y = y[:train_size], y[train_size:] train_dataset = TensorDataset(train_X, train_y) test_dataset = TensorDataset(test_X, test_y) train_loader = DataLoader(train_dataset, batch_size=64 , shuffle=True ) test_loader = DataLoader(test_dataset, batch_size=64 , shuffle=False )
构建 CNN-LSTM 模型 接下来,定义 CNN-LSTM 模型。我们将使用 1D 卷积层提取时间序列的局部特征,接着将这些特征输入到 LSTM 层进行时序预测。
class CNN_LSTM_Model (nn.Module) : def __init__ (self, input_size, hidden_size, num_layers) : super(CNN_LSTM_Model, self).__init__() # 1D卷积层 self.cnn = nn.Sequential( nn.Conv1d(in_channels=1 , out_channels=32 , kernel_size=3 , padding=1 ), nn.ReLU(), nn.MaxPool1d(kernel_size=2 ) ) # LSTM层 self.lstm = nn.LSTM(input_size=32 , hidden_size=hidden_size, num_layers=num_layers, batch_first=True ) # 全连接层 self.fc = nn.Linear(hidden_size, 1 ) def forward (self, x) : x = x.unsqueeze(1 ) # 增加一个通道维度, 因为1D卷积需要 (batch, channel, seq_len) x = self.cnn(x) x = x.permute(0 , 2 , 1 ) # 调整维度 (batch, seq_len, features) lstm_out, _ = self.lstm(x) out = self.fc(lstm_out[:, -1 , :]) # 取最后时间步的输出 return out
模型训练 我们使用均方误差(MSE)作为损失函数,Adam 作为优化器。训练过程将会迭代多个 epoch,并在训练和测试集上记录损失,以便后续可视化分析。
# 超参数 input_size = 32 hidden_size = 64 num_layers = 2 num_epochs = 100 learning_rate = 0.001 # 模型实例化 model = CNN_LSTM_Model(input_size, hidden_size, num_layers) criterion = nn.MSELoss() optimizer = optim.Adam(model.parameters(), lr=learning_rate)# 训练模型 train_losses = [] test_losses = []for epoch in range(num_epochs): model.train() train_loss = 0 for batch_X, batch_y in train_loader: optimizer.zero_grad() outputs = model(batch_X) loss = criterion(outputs.squeeze(), batch_y) loss.backward() optimizer.step() train_loss += loss.item() train_losses.append(train_loss / len(train_loader)) # 测试模型 model.eval() test_loss = 0 with torch.no_grad(): for batch_X, batch_y in test_loader: outputs = model(batch_X) loss = criterion(outputs.squeeze(), batch_y) test_loss += loss.item() test_losses.append(test_loss / len(test_loader))
if (epoch+1 ) % 10 == 0 : print(f'Epoch [{epoch+1 } /{num_epochs} ], Train Loss: {train_loss/len(train_loader):.4 f} , Test Loss: {test_loss/len(test_loader):.4 f} ' )
预测和可视化 在模型训练完成后,我们在测试集上进行预测,并绘制预测结果和训练过程中的损失变化图。
# 绘制损失变化曲线 plt.figure(figsize=(10 , 6 )) plt.plot(train_losses, label='Train Loss' ) plt.plot(test_losses, label='Test Loss' ) plt.xlabel('Epochs' ) plt.ylabel('Loss' ) plt.title('Loss Curve' ) plt.legend() plt.grid(True ) plt.show()# 测试集上的预测曲线 model.eval() predictions = []with torch.no_grad(): for batch_X, _ in test_loader: outputs = model(batch_X) predictions.append(outputs.detach().numpy()) predictions = np.concatenate(predictions).squeeze() predictions = scaler.inverse_transform(predictions.reshape(-1 , 1 )).reshape(-1 ) true_values = scaler.inverse_transform(test_y.numpy().reshape(-1 , 1 )).reshape(-1 )# 绘制预测值与真实值对比 plt.figure(figsize=(10 , 6 )) plt.plot(true_values, label='True Values' , color='b' ) plt.plot(predictions, label='Predictions' , color='r' ) plt.xlabel('Time Steps' ) plt.ylabel('Value' ) plt.title('Predictions vs True Values' ) plt.legend() plt.grid(True ) plt.show()
损失曲线 第一张图展示了训练过程中模型的损失变化情况,可以观察到训练损失和测试损失逐渐下降的趋势。通过这张图,可以直观地看到模型的收敛情况以及是否出现过拟合。
预测曲线 第二张图展示了模型在测试集上的预测结果与真实值的对比。通过这张图,我们能够直观评估模型在未见过的数据上的表现,观察预测是否准确拟合了数据的趋势。
残差分布 绘制残差(预测值与真实值的差异)的分布图,可以评估模型在预测时的误差特征,是否存在系统性偏差。
# 计算残差 residuals = true_values - predictions# 绘制残差分布图 plt.figure(figsize=(10 , 6 )) plt.hist(residuals, bins=50 , color='purple' , alpha=0.7 ) plt.title('Residuals Distribution' ) plt.xlabel('Residual' ) plt.ylabel('Frequency' ) plt.grid(True ) plt.show()
残差序列图
残差序列图可以帮助我们查看误差随时间的变化是否有规律。
# 绘制残差随时间变化图 plt.figure(figsize=(10 , 6 )) plt.plot(residuals, label='Residuals' , color='orange' ) plt.xlabel('Time Steps' ) plt.ylabel('Residual' ) plt.title('Residuals over Time' ) plt.grid(True ) plt.show()
模型优化与调参 网络架构优化 卷积核尺寸与数量: 可以尝试不同大小的卷积核(例如 5 或 7),或者增加卷积层数量,提取更深层的局部特征。LSTM层数与隐藏单元数: 增加或减少 LSTM 的层数和每层的隐藏单元数,寻找最佳的时序依赖模式捕获能力。激活函数: 除了 ReLU,还可以尝试 LeakyReLU 或者 ELU,观察是否能加速收敛或提高模型效果。正则化与防止过拟合 Dropout: 可以在 LSTM 或 CNN 层之间添加 dropout 层,以防止模型过拟合。早停机制: 在训练时引入 early stopping,根据测试集的损失提前终止训练,避免过度训练导致的过拟合。超参数调优