cos大壮 2025-01-24 17:00 浙江
CNN和LSTM各自有不同的特长,CNN擅长局部模式的捕捉,LSTM擅长捕捉序列的长依赖关系。
在很多的时间序列预测任务中,利用卷积神经网络(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 np
import matplotlib.pyplot as plt
import torch
import torch.nn as nn
import torch.optim as optim
from sklearn.preprocessing import MinMaxScaler
from 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):.4f}, Test Loss: {test_loss/len(test_loader):.4f}')
预测和可视化
在模型训练完成后,我们在测试集上进行预测,并绘制预测结果和训练过程中的损失变化图。
# 绘制损失变化曲线
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,根据测试集的损失提前终止训练,避免过度训练导致的过拟合。
超参数调优
学习率: 使用学习率衰减策略,在训练过程中逐渐减小学习率,以便在收敛时保持稳定。
批量大小: 调整 batch_size 可以影响模型的收敛速度和性能,尝试不同大小的 batch 来找到最优组合。
往期精彩回顾
适合初学者入门人工智能的路线及资料下载
机器学习及深度学习笔记等资料打印
《统计学习方法》的代码复现专辑
交流群
请备注:”昵称-学校/公司-研究方向“,例如:”张小明-浙大-CV“加群。
(也可以加入机器学习交流qq群772479961)