时间:2022-08-25 10:23:00 | 栏目:Python代码 | 点击:次
前面几篇文章中介绍的都是单向LSTM,这篇文章讲一下双向LSTM。
系列文章:
PyTorch深度学习LSTM从input输入到Linear输出
关于LSTM的输入输出在深入理解PyTorch中LSTM的输入和输出(从input输入到Linear输出)中已经有过详细叙述。
关于nn.LSTM的参数,官方文档给出的解释为:
总共有七个参数,其中只有前三个是必须的。由于大家普遍使用PyTorch的DataLoader来形成批量数据,因此batch_first也比较重要。LSTM的两个常见的应用场景为文本处理和时序预测,因此下面对每个参数我都会从这两个方面来进行具体解释。
关于LSTM的输入,官方文档给出的定义为:
可以看到,输入由两部分组成:input、(初始的隐状态h_0,初始的单元状态c_0)?
其中input:
input(seq_len, batch_size, input_size)
(h_0, c_0):
h_0(num_directions * num_layers, batch_size, hidden_size) c_0(num_directions * num_layers, batch_size, hidden_size)
h_0和c_0的shape一致。
关于LSTM的输出,官方文档给出的定义为:
可以看到,输出也由两部分组成:otput、(隐状态h_n,单元状态c_n)
其中output的shape为:
output(seq_len, batch_size, num_directions * hidden_size)
h_n和c_n的shape保持不变,参数解释见前文。
如果在初始化LSTM时令batch_first=True,那么input和output的shape将由:
input(seq_len, batch_size, input_size) output(seq_len, batch_size, num_directions * hidden_size)
变为:
input(batch_size, seq_len, input_size) output(batch_size, seq_len, num_directions * hidden_size)
即batch_size提前。
假设最后我们得到了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) 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)) # print(self.batch_size * seq_len, self.hidden_size) output = output.contiguous().view(self.batch_size * seq_len, self.num_directions * self.hidden_size) # (5 * 30, 64) pred = self.linear(output) # pred() pred = pred.view(self.batch_size, seq_len, -1) pred = pred[:, -1, :] 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.view(self.batch_size, seq_len, -1) pred = pred[:, -1, :] return pred
数据处理、训练以及预测同前面几篇文章。
这里对单步长多变量的预测进行对比,在其他条件保持一致的情况下,得到的实验结果如下所示:
方法LSTMBiLSTM(1)BiLSTM(2)MAPE7.439.299.29
可以看到,仅针对我所使用的数据而言,单向LSTM的效果更好。对于前面提到的两种方法,貌似差异不大。
源码及数据我放在了GitHub上,LSTM-Load-Forecasting