一、了解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, # 每次输入特征数量为28hidden_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() # 梯度清0loss.backward() # 计算梯度optimizer.step() # 修改权值def test():model.eval()correct = 0for 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()
测试结果: