一、了解LSTM
1. 核心思想
首先,LSTM 是 RNN(循环神经网络)的变体。它通过引入细胞状态 C(t) 贯穿于整个网络模型,达到长久记忆的效果,进而解决了 RNN 的长期依赖问题。
2. 思维导图
每个LSTM层次都有三个重要的门结构,从前往后依次是遗忘门(forget gate layer)、输入门(input gate layer)、输出门(output gate layer)。
还有两个重要的状态,分别是细胞状态(cell state)、隐藏状态(hidden state),即图示中的 C(t) 和 h(t) 。其中细胞状态不仅记忆某个时间步的信息,而是对整个时间序列保持较为稳定的记忆,是一种长期 “记忆信息” 。对于隐藏状态来说,它更多地关注当前时间步以及上一个时间步的输出,是一种短期 “记忆信息” 。
具体内容如下面思维导图所示:
二、利用pytorch构建LSTM
1. 构造神经网络模型
1.1 LSTM层
self.lstm = nn.LSTM(
input_size=28, # 每次输入特征数量为28
hidden_size=64, # 表示每个时间步的输出会有 64 个特征
num_layers=1, # LSTM隐藏层的层数
batch_first=True # 输入数据的格式是“批次在第一位”
)
- input_size: 这告诉模型,每次输入的数据有多少个特征(比如一张28x28像素的图像,每一行就是一个时间步)。也就是图示中的 x(t) 。
- hidden_size:这是模型的“记忆”大小。即细胞状态C(t) 和隐藏状态 h(t) 的容量。
- num_layers:等于1则代表只使用一层 LSTM 网络。
- batch_first:这个参数表示输入数据的维度格式是(批次,时间步、特征数),即批次在第一维。
1.2 全连接层
self.out = nn.Linear(
in_features=64,
out_features=10 # 将LSTM层提取到的64个特征进一步转化为10个输出(0~9)
)
- in_features:全连接层的输入大小,来自LSTM的输出,每个时间步的特征数是64(即 hidden_size )
- out_features:全连接层的输出大小是10,通常表示有10个类别。
1.3 Softmax层
self.softmax = nn.Softmax(dim=1)
这一层主要是将全连接层的输出转化为概率分布。如果使用的是交叉熵代价函数(CrossEntropyLoss),可以不加这层。
2. 前向传播
-
在前面LSTM层中batch_first参数设置了输入数据的维度格式,即(批次,时间步、特征数)。所以首先要做的就是调整输入的维度格式。这里每个样本是 28 个时间步,每个时间步有 28 个特征(像是一个28x28的图像)
x = x.view(-1, 28, 28)
-
让输入数据通过LSTM层,并最终输出三个信息,分别是 output,h_n 和 c_n。output 包含了每个时间步的输出信息(理解为LSTM分析每个时间步得到的结果)。h_n 是最后一个时间步的隐藏状态,c_n 是记忆状态。我们重点关注 h_n,因为它代表了 LSTM 在处理完所有时间步后的总结。
output, (h_n, c_n) = self.lstm(x)
-
接下来从隐藏状态中拿到最后一个时间步 h_n 的输出 output_in_last_timestep。可以理解为,LSTM看完了所有时间步之后,得到了它对整个序列的理解。
output_in_last_timestep = h_n[-1, :, :]
-
最后LSTM的输出被送到全连接层,转化成10个数字,这些数字代表模型对每个类别的预测分数。并通过Softmax转化为概率。
x = self.out(output_in_last_timestep) x = self.softmax(x)
构造好的LSTM神经网络模型代码如下所示:
class LSTM(nn.Module):
def __init__(self):
super(LSTM, self).__init__()
self.lstm = nn.LSTM(
input_size=28, # 每次输入特征数量
hidden_size=64,
num_layers=1, # LSTM隐藏层的层数
batch_first=True
)
self.out = nn.Linear(
in_features=64,
out_features=10 # 将LSTM层提取到的64个特征进一步转化为10个输出(0~9)
)
self.softmax = nn.Softmax(dim=1)
def forward(self, x):
x = x.view(-1, 28, 28) # 将输入调整成一个 (批次大小, 时间步数, 特征数) 的形式
output, (h_n, c_n) = self.lstm(x)
output_in_last_timestep = h_n[-1, :, :] # 从隐藏状态中拿到最后一个时间步的输出
x = self.out(output_in_last_timestep) # LSTM的输出被送到全连接层,转化成10个数字
x = self.softmax(x)
return x
三、测试 LSTM 神经网络模型
用MNIST数据集测试代码如下:
# 训练集
train_dataset = datasets.MNIST(root='./',
train=True,
transform=transforms.ToTensor(), # 数据转换为张量格式
download=True)
# 测试集
test_dataset = datasets.MNIST(root='./',
train=False,
transform=transforms.ToTensor(),
download=True)
# 批次大小
batch_size = 100
# 装载训练集
train_loader = DataLoader(dataset=train_dataset,
batch_size=batch_size, # 每次加载多少条数据
shuffle=True) # 生成数据前打乱数据
# 装载测试集
test_loader = DataLoader(dataset=test_dataset,
batch_size=batch_size,
shuffle=True)
LR = 0.001 # 学习率
model = LSTM() # 模型
crossEntropy_loss = nn.CrossEntropyLoss() # 交叉熵代价函数
optimizer = optim.Adam(model.parameters(), LR)
def train():
model.train()
for i, data in enumerate(train_loader):
inputs, labels = data # 获得一个批次的数据和标签
out = model(inputs) # 获得模型预测输出(64张图像,10个数字的概率)
loss = crossEntropy_loss(out, labels) # 使用交叉熵损失函数时,可以直接使用整型标签,无须独热编码
optimizer.zero_grad() # 梯度清0
loss.backward() # 计算梯度
optimizer.step() # 修改权值
def test():
model.eval()
correct = 0
for i, data in enumerate(test_loader):
inputs, labels = data # 获得一个批次的数据和标签
out = model(inputs) # 获得模型预测结构(64,10)
_, predicted = torch.max(out, 1) # 获得最大值,以及最大值所在位置
correct += (predicted == labels).sum() # 判断64个值有多少是正确的
print("测试集正确率:{}\n".format(correct.item() / len(test_loader)))
# 训练20个周期
for epoch in range(20):
print("Epoch:{}".format(epoch))
train()
test()
测试结果: