pytorch深度学习:RNN循环神经网络(二)
上一节了解到了RNN和LSTM的基础知识,这节我们使用LSTM网络对mnist手写数字数据集进行处理。重点了解LSTM网络的搭建方法以及各个参数所代表的含义。
1.问题的提出
众所周知,在处理带有时序特性的数据方面,RNN(循环神经网络)表现出了强大的能力。然而,在MNIST这样的图像分类任务中直接应用RNN可能会遇到挑战。为了解决这一问题,请考虑将MNIST中的手写字样本每一行视为不同的时序片段。具体而言,在这个案例中每张图片由28个连续的时间步组成(即Time_step=28),而每个时间步包含了28个像素信息(即Input_size=28)。接下来,请我们初始化一些超参数设置:
# 定义一些超参数
EPOCH = 1 # 训练整批数据多少次, 为了节约时间, 我们只训练一次
BATCH_SIZE = 64
TIME_STEP = 28 # rnn 时间步数 / 图片高度
INPUT_SIZE = 28 # rnn 每步输入值 / 图片每行像素
LR = 0.01 # learning rate
代码解读
该部分样本同样如前所述划分为测试样本与训练样本,在此不展开具体方法说明
2.LSTM网络的搭建
现有的PyTorch框架已经提供了现成的LSTM模块封装。为了构建模型时可直接调用nn.LSTM()函数完成搭建。我们构建了一个仅包含单个LSTM单元的一维RNN模型,并通过紧跟的一个全连接层对输入进行10类分类。
class RNN(nn.Module):
def __init__(self):
super(RNN,self).__init__()
self.rnn = nn.LSTM(
input_size=INPUT_SIZE,
hidden_size=64,
num_layers=1,
batch_first=True
)
self.fc1 = nn.Linear(64,10)
def forward(self,x):
# x shape (batch, time_step, input_size)
# r_out shape (batch, time_step, output_size)
# h_n shape (n_layers, batch, hidden_size) LSTM 有两个 hidden states, h_n 是分线, h_c 是主线
# h_c shape (n_layers, batch, hidden_size)
r_out, (h_n, h_c) = self.rnn(x, None) # None 表示 hidden state 会用全0的 state
# 选取最后一个时间点的r_out 输出
# 这里 r_out[:, -1, :] 的值也就是最后一时刻 h_n (主线)的值
out = self.fc1(r_out[:, -1, :])
return out
代码解读
来看下LSTM模块中的各个参数代表什么意义:
- input_dimension: 输入维度为28,在这个特定案例中被设定。
- hidden_units: 隐层单元数量被设定为64。
- lstm_layers: 该模型中集成多少个LSTM单元?通常情况下只有一个。
- batch_first参数: 是否设置为true?如果设置true,则输入与输出张量格式变为**[batch_size, time_step, input_size]**。
- bidirectional功能: 是否启用双向LSTM?通常情况下未启用。
在向LSTM网络输入数据之前(因为)需要将数据x(按照)其形状定义为[batch_size,time_step,input_size]的要求(执行)必要的维度重塑操作(代码如下)
b_x = x.view(-1,28,28) # -1表示自动计算,第一个28为time_step,第二个28为input_size
代码解读
经过LSTM处理后共有3个输出,分别为:
r_out, (h_n, h_c) = self.rnn(x, None) # None 表示 hidden state 会用全0的 state
代码解读
即为最终层在每个time_step上的输出结果h。
代表各层中最后一个时间步的输出结果h。
即为各层中对应最后一个时间步的记忆存储值c。
它们的格式分别为:
r_out(batch_size, time_step, hidden_size*num_directions)
h_n(num_layers*num_directions, batch_size, hidden_size)
c_n(num_layers*num_directions, batch_size, hidden_size)
代码解读
其中参数 num_directions 代表的是一个 LSTM 模型的方向性问题。如果是单方向的 LSTM 模型,则 num_directions=1;如果是双方向的 LSTM 架构,则 num_directions=2。
在后续的全连接操作中,我们选择了最后一个time_step的输出作为全连接层的输入,这是因为LSTM网络具有hidden_size=64特性,因此全连接层设定为输入层64个单元和输出层10个单元(即10分类任务),具体实现代码如下所示
# 选取最后一个时间点的r_out 输出
# 这里 r_out[:, -1, :] 的值也就是最后一时刻 h_n (主线)的值
out = self.fc1(r_out[:, -1, :])
代码解读
3.结果的展示
与之前相比,在这一阶段的训练过程没有发生明显的变化。无需额外展示这一现象的具体表现。接下来我们通过测试集进行评估。最终实验结果显示模型在测试集上的预测准确率达到**95%**左右,并完全符合我们的预期目标。为了进一步验证结果的有效性,我们选取了前十组数据进行具体分析。
第一行表示RNN网络预测的结果,第二行表示真实的结果:
[7 2 1 0 4 1 4 9 5 9] prediction number
[7 2 1 0 4 1 4 9 5 9] real number
代码解读
最终,在此处放置了完整且详细的代码,并源自于莫烦大神的教学材料。
# RNN 进行mnist手写数字集的判别 #
import torch
import torch.nn as nn
import torch.utils.data as Data
import torchvision
# 定义一些超参数
EPOCH = 1 # 训练整批数据多少次, 为了节约时间, 我们只训练一次
BATCH_SIZE = 64
TIME_STEP = 28 # rnn 时间步数 / 图片高度
INPUT_SIZE = 28 # rnn 每步输入值 / 图片每行像素
LR = 0.01 # learning rate
DOWNLOAD_MNIST = False # 如果你已经下载好了mnist数据就写上 Fasle
# Mnist 手写数字数据集
train_data = torchvision.datasets.MNIST(
root='./mnist/', # 保存或者提取位置
train=True, # this is training data
transform=torchvision.transforms.ToTensor(), # 转换 PIL.Image or numpy.ndarray 成
# torch.FloatTensor (C x H x W), 训练的时候 normalize 成 [0.0, 1.0] 区间
download=DOWNLOAD_MNIST, # 没下载就下载, 下载了就不用再下了
)
test_data = torchvision.datasets.MNIST(root='./mnist/', train=False)
# 批训练 50samples, 1 channel, 28x28 (50, 1, 28, 28)
train_loader = Data.DataLoader(dataset=train_data, batch_size=BATCH_SIZE, shuffle=True)
# 为了节约时间, 我们测试时只测试前2000个
test_x = test_data.test_data.type(torch.FloatTensor)[:2000]/255. # shape (2000, 28, 28) value in range(0,1)
test_y = test_data.test_labels.numpy()[:2000] # covert to numpy array
class RNN(nn.Module):
def __init__(self):
super(RNN,self).__init__()
self.rnn = nn.LSTM(
input_size=INPUT_SIZE,
hidden_size=64,
num_layers=1,
batch_first=True
)
self.fc1 = nn.Linear(64,10)
def forward(self,x):
# x shape (batch, time_step, input_size)
# r_out shape (batch, time_step, output_size)
# h_n shape (n_layers, batch, hidden_size) LSTM 有两个 hidden states, h_n 是分线, h_c 是主线
# h_c shape (n_layers, batch, hidden_size)
r_out, (h_n, h_c) = self.rnn(x, None) # None 表示 hidden state 会用全0的 state
# 选取最后一个时间点的r_out 输出
# 这里 r_out[:, -1, :] 的值也就是最后一时刻 h_n (主线)的值
out = self.fc1(r_out[:, -1, :])
return out
rnn = RNN()
print(rnn)
optimizer = torch.optim.Adam(rnn.parameters(),lr=LR)
loss_func = nn.CrossEntropyLoss()
for epoch in range(EPOCH):
for step,(x,y) in enumerate(train_loader):
b_x = x.view(-1,28,28)
b_y = y
output = rnn(b_x)
loss = loss_func(output,b_y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if step % 50 == 0:
test_output = rnn(test_x) # (samples, time_step, input_size)
pred_y = torch.max(test_output, 1)[1].data.numpy()
accuracy = float((pred_y == test_y).astype(int).sum()) / float(test_y.size)
print('Epoch: ', epoch, '| train loss: %.4f' % loss.data.numpy(), '| test accuracy: %.2f' % accuracy)
# 取10个值来验证预测的正确与否
test_output = rnn(test_x[:10].view(-1, 28, 28))
pred_y = torch.max(test_output, 1)[1].data.numpy().squeeze()
print(pred_y, 'prediction number')
print(test_y[:10], 'real number')
代码解读
