赞
踩
最近在看李沐老师动手学深度学习的序列模型,结合网上的一些资料做了一些汇总,解决了对于模型的理解和使用,在这里记录一下。
参考:简单易懂深入PyTorch中RNN、LSTM和GRU使用和理解-CSDN博客
torch.nn.RNN
是 PyTorch 中用于构建一个简单的 Elman 循环神经网络(RNN)的类。它可以应用于输入序列,使用 tanh
或 ReLU
作为激活函数。下面将详细描述这个类的功能、参数和注意事项。
num_layers=2
表示两个 RNN 层堆叠在一起。'tanh'
或 'relu'
。False
,则层不使用偏置权重 b_ih
和 b_hh
。True
,则输入和输出张量的格式为 (batch, seq, feature)
;否则为 (seq, batch, feature)
。True
,则变为双向 RNN。input
, h_0
input
: 形状为 (seq_len, batch, input_size)
或 (batch, seq_len, input_size)
(如果 batch_first=True
)的张量,包含输入序列的特征。h_0
: 形状为 (num_layers * num_directions, batch, hidden_size)
的张量,包含初始隐藏状态。如果未提供,默认为零。output
, h_n
output
: 包含最后一层 RNN 的输出特征(h_t
)的张量。h_n
: 形状为 (num_layers * num_directions, batch, hidden_size)
的张量,包含批次中每个元素的最终隐藏状态。import torch
import torch.nn as nn
# 创建 RNN 实例
rnn = nn.RNN(input_size=10, hidden_size=20, num_layers=2, nonlinearity='tanh')
# 对于hidden_size的理解:每一个时间步对应的神经元内部的参数个数
# 输入数据
input = torch.randn(5, 3, 10) # (seq_len, batch, input_size)
h0 = torch.randn(2, 3, 20) # (num_layers, batch, hidden_size)
# 前向传播
output, hn = rnn(input, h0)
这段代码展示了如何创建一个简单的 RNN 网络并进行前向传播。在这个例子中,input
是一个随机生成的输入张量,其维度是序列长度、批大小和输入大小。h0
是初始隐藏状态。输出 output
包含了每个时间步的隐藏状态,而 hn
是最终的隐藏状态。
torch.nn.LSTM
是 PyTorch 中用于构建长短期记忆(LSTM)网络的类。LSTM 是一种特殊类型的循环神经网络(RNN),特别适合处理和预测时间序列数据中的长期依赖关系。
num_layers=2
表示两个 LSTM 层堆叠在一起。False
,则层不使用偏置权重 b_ih
和 b_hh
。True
,则输入和输出张量的格式为 (batch, seq, feature)
;否则为 (seq, batch, feature)
。True
,则变为双向 LSTM。input
, (h_0, c_0)
input
: 形状为 (seq_len, batch, input_size)
或 (batch, seq_len, input_size)
(如果 batch_first=True
)的张量,包含输入序列的特征。h_0
: 形状为 (num_layers * num_directions, batch, hidden_size)
的张量,包含初始隐藏状态。c_0
: 形状为 (num_layers * num_directions, batch, hidden_size)
的张量,包含初始细胞状态。output
, (h_n, c_n)
output
: 包含最后一层 LSTM 的输出特征(h_t
)的张量。h_n
: 形状为 (num_layers * num_directions, batch, hidden_size)
的张量,包含序列中每个元素的最终隐藏状态。c_n
: 形状为 (num_layers * num_directions, batch, hidden_size)
的张量,包含序列中每个元素的最终细胞状态。import torch
import torch.nn as nn
# 创建 LSTM 实例
lstm = nn.LSTM(input_size=10, hidden_size=20, num_layers=2)
# 输入数据
input = torch.randn(5, 3, 10) # (seq_len, batch, input_size)
h0 = torch.randn(2, 3, 20) # (num_layers, batch, hidden_size)
c0 = torch.randn(2, 3, 20) # (num_layers, batch, hidden_size)
# 前向传播
output, (hn, cn) = lstm(input, (h0, c0))
这段代码展示了如何创建一个 LSTM 网络并进行前向传播。在这个例子中,input
是一个随机生成的输入张量,其维度是序列长度、批大小和输入大小。h0
和 c0
分别是初始隐藏状态和细胞状态。输出 output
包含了每个时间步的隐藏状态,而 (hn, cn)
是最终的隐藏状态和细胞状态。
torch.nn.GRU
是 PyTorch 中用于构建门控循环单元(GRU)网络的类。GRU 是一种循环神经网络,类似于 LSTM,但结构更为简单,用于处理序列数据。
num_layers=2
表示两个 GRU 层堆叠在一起。False
,则层不使用偏置权重 b_ih
和 b_hh
。True
,则输入和输出张量的格式为 (batch, seq, feature)
;否则为 (seq, batch, feature)
。True
,则变为双向 GRU。input
, h_0
input
: 形状为 (seq_len, batch, input_size)
或 (batch, seq_len, input_size)
(如果 batch_first=True
)的张量,包含输入序列的特征。h_0
: 形状为 (num_layers * num_directions, batch, hidden_size)
的张量,包含初始隐藏状态。output
, h_n
output
: 包含最后一层 GRU 的输出特征(h_t
)的张量。h_n
: 形状为 (num_layers * num_directions, batch, hidden_size)
的张量,包含序列中每个元素的最终隐藏状态。import torch
import torch.nn as nn
# 创建 GRU 实例
gru = nn.GRU(input_size=10, hidden_size=20, num_layers=2)
# 输入数据
input = torch.randn(5, 3, 10) # (seq_len, batch, input_size)
h0 = torch.randn(2, 3, 20) # (num_layers, batch, hidden_size)
# 前向传播
output, hn = gru(input, h0)
这段代码展示了如何创建一个 GRU 网络并进行前向传播。在这个例子中,input
是一个随机生成的输入张量,其维度是序列长度、批大小和输入大小。h0
是初始隐藏状态。输出 output
包含了每个时间步的隐藏状态,而 hn
是最终的隐藏状态。
参考:(94 封私信 / 80 条消息) pytorch对于双向LSTM,对于output,(h_n,c_n)=nn.LSTM(x)怎么取输出? - 知乎 (zhihu.com)
假设最后我们得到了output(batch_size, seq_len, 2 * hidden_size),我们需要将其输入到线性层,有以下两种方法可以参考:
(1)直接输入
和单向一样,我们可以将output直接输入到Linear。在单向LSTM中:
self.linear = nn.Linear(self.hidden_size, self.output_size)
而在双向LSTM中:
self.linear = nn.Linear(2 * self.hidden_size, self.output_size)
模型:
class BiLSTM(nn.Module): def __init__(self, input_size, hidden_size, num_layers, output_size, batch_size): super().__init__() self.input_size = input_size self.hidden_size = hidden_size self.num_layers = num_layers self.output_size = output_size self.num_directions = 2 self.batch_size = batch_size self.lstm = nn.LSTM(self.input_size, self.hidden_size, self.num_layers, batch_first=True, bidirectional=True) self.linear = nn.Linear(self.num_directions * self.hidden_size, self.output_size) def forward(self, input_seq): h_0 = torch.randn(self.num_directions * self.num_layers, self.batch_size, self.hidden_size).to(device) c_0 = torch.randn(self.num_directions * self.num_layers, self.batch_size, self.hidden_size).to(device) # print(input_seq.size()) seq_len = input_seq.shape[1] # input(batch_size, seq_len, input_size) # output(batch_size, seq_len, num_directions * hidden_size) output, _ = self.lstm(input_seq, (h_0, c_0)) pred = self.linear(output) # pred() pred = pred[:, -1, :] # 注意,这是batch_first=True的情况 return pred
(2)处理后再输入
在LSTM中,经过线性层后的output的shape为(batch_size, seq_len, output_size)。假设我们用前24个小时(1 to 24)预测后2个小时的负荷(25 to 26),那么seq_len=24, output_size=2。根据LSTM的原理,最终的输出中包含了所有位置的预测值,也就是((2 3), (3 4), (4 5)…(25 26))。很显然我们只需要最后一个预测值,即output[:, -1, :]。
而在双向LSTM中,一开始output(batch_size, seq_len, 2 * hidden_size),这里面包含了所有位置的两个方向的输出。简单来说,output[0]为序列从左往右第一个隐藏层状态输出和序列从右往左最后一个隐藏层状态输出的拼接;output[-1]为序列从左往右最后一个隐藏层状态输出和序列从右往左第一个隐藏层状态输出的拼接。
如果我们想要同时利用前向和后向的输出,我们可以将它们从中间切割,然后求平均。比如output的shape为(30, 24, 2 * 64),我们将其变成(30, 24, 2, 64),然后在dim=2上求平均,得到一个shape为(30, 24, 64)的输出,此时就与单向LSTM的输出一致了。
具体处理方法:
output = output.contiguous().view(self.batch_size, seq_len, self.num_directions, self.hidden_size)
output = torch.mean(output, dim=2)
模型代码:
class BiLSTM(nn.Module): def __init__(self, input_size, hidden_size, num_layers, output_size, batch_size): super().__init__() self.input_size = input_size self.hidden_size = hidden_size self.num_layers = num_layers self.output_size = output_size self.num_directions = 2 self.batch_size = batch_size self.lstm = nn.LSTM(self.input_size, self.hidden_size, self.num_layers, batch_first=True, bidirectional=True) self.linear = nn.Linear(self.hidden_size, self.output_size) def forward(self, input_seq): h_0 = torch.randn(self.num_directions * self.num_layers, self.batch_size, self.hidden_size).to(device) c_0 = torch.randn(self.num_directions * self.num_layers, self.batch_size, self.hidden_size).to(device) # print(input_seq.size()) seq_len = input_seq.shape[1] # input(batch_size, seq_len, input_size) input_seq = input_seq.view(self.batch_size, seq_len, self.input_size) # output(batch_size, seq_len, num_directions * hidden_size) output, _ = self.lstm(input_seq, (h_0, c_0)) output = output.contiguous().view(self.batch_size, seq_len, self.num_directions, self.hidden_size) output = torch.mean(output, dim=2) pred = self.linear(output) # print('pred=', pred.shape) pred = pred[:, -1, :] return pred
首先,pytorch中LSTM的输出一般用到的是输出层和隐藏层这两个,另一个细胞状态,我没咋用过,就不讲了。
一般两种用法,要么将输出层全连接然后得出结果,要么用隐藏层全连接,然后得出结果,有学长说用隐藏层效果会好一点。两种用法应该都可以。如果网络只有一层的时候,用输出层和隐藏层是没有任何区别的,当网络层数大于1时,才会有区别。
举个例子就懂了:
# coding: utf-8 import torch import torch.nn as nn import torchvision.transforms as transforms import torchvision.datasets as datasets import matplotlib.pyplot as plt input_dim = 28 # 输入维度 hidden_dim = 100 # 隐层的维度 layer_dim = 1 # 1层LSTM output_dim = 10 # 输出维度 BATCH_SIZE = 32 # 每批读取的 EPOCHS = 10 # 训练10轮 trainsets = datasets.MNIST(root="./data", train=True, download=True, transform=transforms.ToTensor()) testsets = datasets.MNIST(root="./data", train=False, transform=transforms.ToTensor()) train_loader = torch.utils.data.DataLoader(dataset=trainsets, batch_size=BATCH_SIZE, shuffle=True) test_loader = torch.utils.data.DataLoader(dataset=testsets, batch_size=BATCH_SIZE, shuffle=True) class LSTM_Model(nn.Module): def __init__(self, input_dim, hidden_dim, layer_dim, output_dim): super(LSTM_Model, self).__init__() self.hidden_dim = hidden_dim self.layer_dim = layer_dim self.lstm = nn.LSTM(input_dim, hidden_dim, layer_dim, batch_first=True) # 全连接层 self.fc = nn.Linear(hidden_dim, output_dim) def forward(self, x): # layer_dim, batch_size, hidden_dim h0 = torch.zeros(self.layer_dim, x.size(0), self.hidden_dim).requires_grad_().to(device) # 初始化cell, state c0 = torch.zeros(self.layer_dim, x.size(0), self.hidden_dim).requires_grad_().to(device) # 分离隐藏状态,避免梯度爆炸 lstm_out, (h_n, cn) = self.lstm(x, (h0.detach(), c0.detach())) print("-" * 10) print("lstm_out.shape", lstm_out.shape) print("lstm_out[:, -1].shape", lstm_out[:, -1].shape) print("-" * 10) print("h_n.shape", h_n.shape) print("-" * 10) feature_map = torch.cat([h_n[i, :, :] for i in range(h_n.shape[0])], dim=-1) print("feature_map.shape", feature_map.shape) print("-" * 10) print("lstm_out[:, -1]", lstm_out[:, -1]) print("-" * 10) print("feature_map", feature_map) print("-" * 10) out = self.fc(feature_map) print("out", out.shape) return out model = LSTM_Model(input_dim, hidden_dim, layer_dim, output_dim) device = torch.device("cuda:0" if torch.cuda.is_available() else 'cpu') # 损失函数 criterion = nn.CrossEntropyLoss() # 优化器 learning_rate = 0.01 optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate) # 模型训练 sequence_dim = 28 # 序列长度 loss_list = [] accuracy_list = [] iteration_list = [] # 迭代次数 iter = 0 for epoch in range(EPOCHS): for i, (images, labels) in enumerate(train_loader): model.train() images = images.view(-1, sequence_dim, input_dim).requires_grad_().to(device) labels = labels.to(device) optimizer.zero_grad() # 前向传播 outputs = model(images) # 计算损失 loss = criterion(outputs, labels) # 反向传播 loss.backward() # 更新参数 optimizer.step() # 计数器加1 iter += 1 # 模型验证 if iter % 500 == 0: model.eval() # 声明 # 计算验证的accuracy correct = 0.0 total = 0.0 # 迭代测试集,获取数据、预测 for images, labels in test_loader: images = images.view(-1, sequence_dim, input_dim).to(device) # 模型预测 outputs = model(images) # 获取预测概率最大值的下标 predict = torch.max(outputs.data, 1)[1] # 统计测试集的大小 total += labels.size(0) # 统计判断预测正确的数量 if torch.cuda.is_available(): correct += (predict.gpu() == labels.gpu()).sum() else: correct += (predict == labels).sum() # 计算accuracy accuracy = correct / total * 100 loss_list.append(loss.data) accuracy_list.append(accuracy) iteration_list.append(iter) # 打印信息 print("loos:{}, Loss:{}, Accuracy:{}".format(iter, loss.item(), accuracy)) plt.plot(iteration_list, loss_list) plt.xlabel("Number of Iteration") plt.ylabel("Loss") plt.title("LSTM") plt.show() plt.plot(iteration_list, accuracy_list, color='r') plt.xlabel("Number of iteration") plt.ylabel("Accuracy") plt.title("LSTM") plt.show()
分析:
input_dim = 28 # 输入步长(seq_len)
hidden_dim = 100 # 隐层的维度(hidden_size)
layer_dim = 1 # 1层LSTM(num_layers)
output_dim = 10 # 输出维度
BATCH_SIZE = 32 # 每批读取的(batch_size)
lstm_out:【batch_size, seq_len, hidden_size * num_directions】
lstm_hn:【num_directions * num_layers, batch_size, hidden_size】
此时我们看到,输出层的最后一步的shape和拼接后的隐藏层的shape是一样的,我们可以打印出具体的数值。
可以看到,这两层的值是一样的,因此当网络为1层的时候输出层和隐藏层的结果没有任何区别。
最终输出是【batch_size, num_classes】,应该没什么疑问,因为是十分类任务。
但是当网络超过一层时,可以把上面的代码将层数直接改为2,不出意外应该会报错。
分析结果可以发现,隐藏层的结果比输出层的结果多了一层,同时观察打印出来的结果,可以发现,隐藏层不仅有输出层的信息,还有前一层隐藏层的信息,即隐藏层最终的信息是大于输出层的,因此,说隐藏层效果会好一点也很容易解释,因为包含的信息更多了。
通过上述实例可以发现,用隐藏层代替输出层进入全连接层理论上应该会有更好的效果,且模型batch_first=True or False只对输出层有影响,而对隐藏层的输出是没有影响的,因此,以后都用隐藏层来全连接比好,也不容易搞错。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。