【35】Sequence序列网络介绍与使用(含RNN,RNNCell,LSTM,LSTMCell的调用)

文章目录

  • 1. 时间序列的表示方法
    • 1.1 word2vec编码实现
    • 1.2 GloVe编码实现
  • 2. RNN
    • 2.1 单层rnn测试
    • 2.2 多层rnn测试
  • 3. RNNCell
    • 3.1 单层RNNCell
    • 3.2 多层RNNCell
  • 4. LSTM
    • 4.1 单层lstm
    • 4.2 多层lstm
    • 4.3 单层双向lstm
    • 4.4 多层双向lstm
  • 5. LSTMCell
    • 5.1 单层LSTMCell
    • 5.2 多层LSTMCell

在上一篇笔记中,了解了可以使用各种编码的方式对一句文本进行编码为一个特征向量,处理的方法可以有词频处理,权重处理或者是哈希编码处理等等。那么有了特征向量就可以实现对当前的文本进行分类处理,就是简单的再使用其他的分类器。

而对于文本,在深度学习领域一般是用时序网络来解决这些问题,实现网络的end-to-end预测。这里的时序网络可以使用RNN、GRU、LSTM、Transformer等知名架构。对于这些实现网络,其处理的过程和之前的不同,这里是将每一个单词进行一个编码,可以使用pytorch提供的查找表,就是在每一个表中查找对于的编码向量。所以处理的输入维度变成了:[seq, batch, embedding]

下面,就介绍以[seq, batch, embedding]为输出的方式,使用各项接口来搭建RNN或者是LSTM网络,以后续实现时序预测与文本分类任务。

!nvidia-smi
Tue May 10 20:27:38 2022       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 460.32.03    Driver Version: 460.32.03    CUDA Version: 11.2     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|                               |                      |               MIG M. |
|===============================+======================+======================|
|   0  Tesla T4            Off  | 00000000:17:00.0 Off |                    0 |
| N/A   39C    P8     9W /  70W |      0MiB / 15109MiB |      0%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+
|   1  Tesla T4            Off  | 00000000:65:00.0 Off |                    0 |
| N/A   77C    P0    66W /  70W |   5472MiB / 15109MiB |     99%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+
|   2  Quadro P400         Off  | 00000000:B4:00.0 Off |                  N/A |
| 34%   36C    P8    N/A /  N/A |    103MiB /  1997MiB |      0%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
|    1   N/A  N/A     14250      C   python                           5465MiB |
|    2   N/A  N/A      3147      G   /usr/bin/X                         94MiB |
|    2   N/A  N/A      3221      G   /usr/bin/gnome-shell                5MiB |
+-----------------------------------------------------------------------------+

开始前,导入所需要的工具包

import torch
import torch.nn as nn
import numpy as np

1. 时间序列的表示方法

序列的信息其实比较常见,文本是序列信息,语音是序列信息,图像也可以看成是一行行像素的组合其同样可以看成是序列的信息。这里主要对文本信息进行描述:

单词的编码形式:[words, word_vec]
这里有两种现成的编码方法:word2vec、glove

Batch的编码形式:[word num, b, word vec ] 、 [b, word num, word vec]
这里,如果是对图像处理比较熟悉的可能会举得第二种编码形式比较容易理解,就是传入一批数据,每一篇都有N个词,每个词编码为一个长度为word vec的特征向量。
但是,其实第一种是比较符合时序模型的理解的,有N个单词,每个单词都有b个批次也是也就是b个序列的时间搓,然后每个对应的时间戳同样编码为一个长度为word vec的特征向量。所以,这样不要把b理解为是一个批量,而是要将其理解为一个序列的时间戳。因为对于序列来说,肯定是有多个时间戳的。

1.1 word2vec编码实现

一般来说,可以通过nn.Embedding来生成一个查找表,对于每个单词给出一个编码的特征向量。如果是按一下的方法来进行设置,那么其是随机生成的,没有进行初始化的。

import torch
import torch.nn as nn

word_to_ix = {'hello':0, 'world':1}
lookup_tensor = torch.tensor([word_to_ix['hello']], dtype=torch.long)

embeds = nn.Embedding(2, 5)
embeds(lookup_tensor)
tensor([[ 0.8536,  0.6441, -0.1695,  0.5107,  0.0300]],
       grad_fn=)

那么为了可以有一个好的初始化,可以使用一个现成的方式比如word2vec与GloVe,把这些数据集下载下来(大约会有几Gb的样子),然后就会把这个数据的内容填充进这个查找表中就可以直接使用了。但是这种查表的操作,是不能优化训练的。

1.2 GloVe编码实现

还可以通过pytorch nlp提供的另外一个编码方式,下载下来大概需要2.2G,直接输入单词就可以编码为一个特征向量。

from torchnlp.word_to_vector import GloVe, BPEmb
vectors = GloVe()    # 使用GloVe对单词文本进行编码
# vectors = BPEmb()  # 使用Embedding对单词文本进行编码

vectors['hello']
# 返回的是一个100位的特征向量,实现文本单词的编码

2. RNN

假设输入的维度x形式为:[seq len, batch, feature len],比如设置为[10, 3, 100]。

也就是说,这里有10个单词,总共有3句话,其中为每个单词编码为一个20位的特征向量。但是这里每一次送入的是一个feature,也就是每一个单词对于的3个句子的特征编码,这里记录为Xt:[batch,feature len],也就是每隔送入的特征shape为[3, 100]。

那么,现在根据pytorch实现的rnn的公式为:
【35】Sequence序列网络介绍与使用(含RNN,RNNCell,LSTM,LSTMCell的调用)_第1张图片

也就是说,这里初始化提供的 h t − 1 h_{t-1} ht1的维度需要与句子数保持一致,也就是说其维度需要设置为:[b, hidden len]。只有这样,才能和句子每个feature也就是Xt与Wih矩阵相乘后的结果相加,以为需要维度一致才可以相加。如下图所示:

【35】Sequence序列网络介绍与使用(含RNN,RNNCell,LSTM,LSTMCell的调用)_第2张图片

2.1 单层rnn测试

import torch.nn as nn

rnn = nn.RNN(input_size=100, hidden_size=20, num_layers=1)
print(rnn)
print(rnn._parameters.keys())
print("Wih:{} Whh:{}".format(rnn.weight_ih_l0.shape, rnn.weight_hh_l0.shape))
print("Bih:{} Bhh:{}".format(rnn.bias_ih_l0.shape, rnn.bias_hh_l0.shape))
print()

x = torch.randn(10, 3, 100)
out, h = rnn(x, torch.zeros(1, 3, 20))
print(out.shape, h.shape)
RNN(100, 20)
odict_keys(['weight_ih_l0', 'weight_hh_l0', 'bias_ih_l0', 'bias_hh_l0'])
Wih:torch.Size([20, 100]) Whh:torch.Size([20, 20])
Bih:torch.Size([20]) Bhh:torch.Size([20])

torch.Size([10, 3, 20]) torch.Size([1, 3, 20])

对于单层RNN来说, RNN这里的input_size就是每个文本/样本的特征编码,如果对于单词来说,可以使用一些编码工具word2vec将单词编码为一个特征向量,比如设置为100的特征向量;如果是对于一些序列信息,比如天气温度预测啥的,那么这个就是一个值,可以直接设置为1;

然后,这里的hidden_size相当于全连接中的一个隐藏层,可以负责升维或者是降维的操作。对于单词来说,如果编码为一个100的vec,然后隐藏层hidden_size设置为20,那么就表示对100维的特征向量进行了降维。

由于rnn需要考虑当前的隐藏信息与当前的输入,所以除了有一个矩阵 W i h W_{ih} Wih对输入 x t x_{t} xt进行降维,还需要初始化设置一个隐藏单元 h 0 h_{0} h0。这里的隐藏单元的batch维度需要与x的batch维度保持一致,否则两个矩阵之间无法相加。然后对于dim=0的维度是1,表示当前只有一层RNN,对于只需要对这1维的进行初始化即可,num_layers=k时,这里的dim=0也需要设置为k。

最后就是输出,输出的维度这里可以看见是[10, 3, 20]。由于这里的输入是[10, 3, 100],表示有10个这样的单词,但是需要知道网络是不是一次次的逐个单词喂进去的,而是一次性的全部维进去模型里面的。所以out表示的是每个隐藏单元[h0, h1,…,hi]最后一层的输出。也就是,最后返回的h是最后一个时间隐藏层的状态,所以out可以说是最后一个聚合的信息。那么,这就说明,out的最后一个隐藏单元信息与返回的隐藏单元信息h是一直所,下面来做一个验证:(out[-1] == h).all() 返回的是tensor(True),说明我们的推理是没有错的。

2.2 多层rnn测试

rnn = nn.RNN(input_size=100, hidden_size=20, num_layers=4)
print(rnn)
print(rnn._parameters.keys())
print("Wih:{} Whh:{}".format(rnn.weight_ih_l0.shape, rnn.weight_hh_l0.shape))
print("Bih:{} Bhh:{}".format(rnn.bias_ih_l0.shape, rnn.bias_hh_l0.shape))
print()

x = torch.randn(10, 3, 100)
out, h = rnn(x, torch.zeros(4, 3, 20))
print(out.shape, h.shape)
RNN(100, 20, num_layers=4)
odict_keys(['weight_ih_l0', 'weight_hh_l0', 'bias_ih_l0', 'bias_hh_l0', 'weight_ih_l1', 'weight_hh_l1', 'bias_ih_l1', 'bias_hh_l1', 'weight_ih_l2', 'weight_hh_l2', 'bias_ih_l2', 'bias_hh_l2', 'weight_ih_l3', 'weight_hh_l3', 'bias_ih_l3', 'bias_hh_l3'])
Wih:torch.Size([20, 100]) Whh:torch.Size([20, 20])
Bih:torch.Size([20]) Bhh:torch.Size([20])

torch.Size([10, 3, 20]) torch.Size([4, 3, 20])

当有单层的rnn变成多层的rnn时,也就意味着此时对于下一层RNN的输入不是原来的特征,而是经过第一层rnn处理后的特征。而此时,对于每一层的RNN,都需要一个初始化的隐藏单元hi,对于正如上诉所说的,这里dim=0的维度的设置需要与num_layers设置得相同。尽管如此,每一层的隐含单元都是与单层rnn的维度是一致的。

所以,在多层的rnn模型下,out的输出是不变的,但是h的维度会改变,因为会输出每一层的隐藏单元。如下图所示:

【35】Sequence序列网络介绍与使用(含RNN,RNNCell,LSTM,LSTMCell的调用)_第3张图片

而此时,out同样是最后输出的集合,表示为[h0,h1,…,hi]的组合叠加;而h则是每一层最后隐藏单元的集合;前者out是横向维度上的拼接,后者h是列项维度上的拼接。那么,这就以为这out的最后一个输出与h的最后一个隐藏单元其实是一样的,这样同样再做一个测试:(h[-1] == out[-1]).all(),这里返回为tensor(True);证明猜想是正确的。


3. RNNCell

3.1 单层RNNCell

cell_1 = nn.RNNCell(100, 20)
print(cell_1)
print(cell_1._parameters.keys())
print("Wih:{} Whh:{}".format(cell_1.weight_ih.shape, cell_1.weight_hh.shape))
print("Bih:{} Bhh:{}".format(cell_1.bias_ih.shape, cell_1.bias_hh.shape))
print()

x = torch.randn(10, 3, 100)
h1 = torch.zeros(3, 20)
for xt in x:
    h1 = cell_1(xt, h1)
print(h1.shape)
RNNCell(100, 20)
odict_keys(['weight_ih', 'weight_hh', 'bias_ih', 'bias_hh'])
Wih:torch.Size([20, 100]) Whh:torch.Size([20, 20])
Bih:torch.Size([20]) Bhh:torch.Size([20])

torch.Size([3, 20])

这里由于是使用了单个rnn单元,所以最后只有一个输出,就是循环处理的隐藏层单元。对于输入的文本,会分批次的输入到rnncell中,因为rnncell一次只会处理一个单词,或者是一个单词的编码。所以,需要不断的迭代输出。最后输出与隐藏单元就是同一个内容,就是h1.

3.2 多层RNNCell

# 设置多个Rnncell
cell_1 = nn.RNNCell(80, 50)
cell_2 = nn.RNNCell(50, 20)
cell_3 = nn.RNNCell(20, 10)

# 初始化隐藏层hi
x = torch.rand([10, 3, 80])
h1 = torch.zeros([3, 50])
h2 = torch.zeros([3, 20])
h3 = torch.zeros([3, 10])

# 对于每个特征进行迭代训练
for xi in x:
    h1 = cell_1(xi, h1)
    h2 = cell_2(h1, h2)
    h3 = cell_3(h2, h3)

print(h1.shape, h2.shape, h3.shape)
torch.Size([3, 50]) torch.Size([3, 20]) torch.Size([3, 10])

对于这里不同的rnncell单元的输出是不一样的,一方面可以其到降维的作用,如果想要类似rnn的效果,那么隐藏层维度设置为一样就可以了

# 设置多个Rnncell
cell_1 = nn.RNNCell(80, 80)
cell_2 = nn.RNNCell(80, 80)
cell_3 = nn.RNNCell(80, 80)

# 初始化隐藏层hi
x = torch.rand([10, 3, 80])
h1 = torch.zeros([3, 80])
h2 = torch.zeros([3, 80])
h3 = torch.zeros([3, 80])

# 对于每个特征进行迭代训练
for xi in x:
    h1 = cell_1(xi, h1)
    h2 = cell_2(h1, h2)
    h3 = cell_3(h2, h3)

print(h1.shape, h2.shape, h3.shape)
torch.Size([3, 80]) torch.Size([3, 80]) torch.Size([3, 80])

如此设置,就可以保持输入的特征维度不变


4. LSTM

对于lstm来说,在pytorch中提供的公式如下图所示:
【35】Sequence序列网络介绍与使用(含RNN,RNNCell,LSTM,LSTMCell的调用)_第4张图片

相比rnn,lstm对了3个门控的机制,可以有效的处理rnn梯度弥散与梯度爆炸的情况。而相比与rnn,lstm的使用其实是类似的,但是多了一个输入的参数,也就是多了一个控制单元c0,所以现在需要对两个控制单元隐藏状态h0与门控状态c0进行初始化即可。

4.1 单层lstm

import torch
import torch.nn as nn

lstm = nn.LSTM(input_size=100, hidden_size=20, num_layers=1)
print(lstm)
print(lstm._parameters.keys())
print("Wih:{} Whh:{}".format(lstm.weight_ih_l0.shape, lstm.weight_hh_l0.shape))
print("Bih:{} Bhh:{}".format(lstm.bias_ih_l0.shape, lstm.bias_hh_l0.shape))
print()

x = torch.rand([10, 3, 100])
h0 = torch.zeros([1, 3, 20])
c0 = torch.zeros([1, 3, 20])
out, (ht, ct) = lstm(x, (h0, c0))
print(out.shape, ht.shape, ct.shape)
LSTM(100, 20)
odict_keys(['weight_ih_l0', 'weight_hh_l0', 'bias_ih_l0', 'bias_hh_l0'])
Wih:torch.Size([80, 100]) Whh:torch.Size([80, 20])
Bih:torch.Size([80]) Bhh:torch.Size([80])

torch.Size([10, 3, 20]) torch.Size([1, 3, 20]) torch.Size([1, 3, 20])

可以看见,两个状态h0与c0最后的维度其实是没有改变的。而对于out来说,其同样是有最后的隐藏单元的拼接,(out[-1] == ht).all()的返回同样是tensor(True),所以可以证实结论。

4.2 多层lstm

lstm = nn.LSTM(input_size=100, hidden_size=20, num_layers=4)
print(lstm)
print(lstm._parameters.keys())
print("Wih:{} Whh:{}".format(lstm.weight_ih_l0.shape, lstm.weight_hh_l0.shape))
print("Bih:{} Bhh:{}".format(lstm.bias_ih_l0.shape, lstm.bias_hh_l0.shape))
print()

x = torch.rand([10, 3, 100])
h0 = torch.zeros([4, 3, 20])
c0 = torch.zeros([4, 3, 20])
out, (ht, ct) = lstm(x, (h0, c0))
print(out.shape, ht.shape, ct.shape)
LSTM(100, 20, num_layers=4)
odict_keys(['weight_ih_l0', 'weight_hh_l0', 'bias_ih_l0', 'bias_hh_l0', 'weight_ih_l1', 'weight_hh_l1', 'bias_ih_l1', 'bias_hh_l1', 'weight_ih_l2', 'weight_hh_l2', 'bias_ih_l2', 'bias_hh_l2', 'weight_ih_l3', 'weight_hh_l3', 'bias_ih_l3', 'bias_hh_l3'])
Wih:torch.Size([80, 100]) Whh:torch.Size([80, 20])
Bih:torch.Size([80]) Bhh:torch.Size([80])

torch.Size([10, 3, 20]) torch.Size([4, 3, 20]) torch.Size([4, 3, 20])

类似的,这里同样进行测试,(out[-1] == ht[-1]).all(),返回的是tensor(True)。

4.3 单层双向lstm

示意图:

【35】Sequence序列网络介绍与使用(含RNN,RNNCell,LSTM,LSTMCell的调用)_第5张图片

公式为:
【35】Sequence序列网络介绍与使用(含RNN,RNNCell,LSTM,LSTMCell的调用)_第6张图片

有时候希望预测的输出由前面的输入和后面的输入共同决定,从而提高准确度。Forward层和Backward层共同连接到输出层。

Forward层从1到t时刻正向计算,得到并保存每个时刻的隐藏层的输出向后传播;Backward层从t时刻向1反向传播,得到并保存每个时刻向后隐藏层的输出。最后每个时刻结合Farward和Backward层的相应输出的结果通过激活函数得到最终的结果。

import torch.nn as nn

lstm = nn.LSTM(input_size=100, hidden_size=20, num_layers=1, bidirectional=True)
print(lstm)
print(lstm._parameters.keys())
print("Wih:{} Whh:{}".format(lstm.weight_ih_l0.shape, lstm.weight_hh_l0.shape))
print("Bih:{} Bhh:{}".format(lstm.bias_ih_l0.shape, lstm.bias_hh_l0.shape))
print("rWih:{} rWhh:{}".format(lstm.weight_ih_l0_reverse.shape, lstm.weight_hh_l0_reverse.shape))
print("rBih:{} rBhh:{}".format(lstm.bias_ih_l0_reverse.shape, lstm.bias_hh_l0_reverse.shape))
print()

x = torch.rand([10, 3, 100])
h0 = torch.zeros([2, 3, 20])
c0 = torch.zeros([2, 3, 20])
out, (ht, ct) = lstm(x, (h0, c0))
print(out.shape, ht.shape, ct.shape)
LSTM(100, 20, bidirectional=True)
odict_keys(['weight_ih_l0', 'weight_hh_l0', 'bias_ih_l0', 'bias_hh_l0', 'weight_ih_l0_reverse', 'weight_hh_l0_reverse', 'bias_ih_l0_reverse', 'bias_hh_l0_reverse'])
Wih:torch.Size([80, 100]) Whh:torch.Size([80, 20])
Bih:torch.Size([80]) Bhh:torch.Size([80])
rWih:torch.Size([80, 100]) rWhh:torch.Size([80, 20])
rBih:torch.Size([80]) rBhh:torch.Size([80])

torch.Size([10, 3, 40]) torch.Size([2, 3, 20]) torch.Size([2, 3, 20])

这里由于是输出的前向ht与后向h’t的权重和构成的输出,所以out的最后一个内容与ht并不是完全相同的,也就说只有最后的最后的部分才是相同的。(out[-1][:, :20] == ht[0]).all(),才返回tensor(True)。 (out[-1][:, :20] == ht[-2]).all()也返回为Ture

out[-1][:, :20].shape 与 ht[0].shape 的输出均为torch.Size([3, 20])

4.4 多层双向lstm

import torch.nn as nn

lstm = nn.LSTM(input_size=100, hidden_size=20, num_layers=4, bidirectional=True)
print(lstm)
print(lstm._parameters.keys())
print("Wih:{} Whh:{}".format(lstm.weight_ih_l0.shape, lstm.weight_hh_l0.shape))
print("Bih:{} Bhh:{}".format(lstm.bias_ih_l0.shape, lstm.bias_hh_l0.shape))
print("rWih:{} rWhh:{}".format(lstm.weight_ih_l0_reverse.shape, lstm.weight_hh_l0_reverse.shape))
print("rBih:{} rBhh:{}".format(lstm.bias_ih_l0_reverse.shape, lstm.bias_hh_l0_reverse.shape))
print()

x = torch.rand([10, 3, 100])
h0 = torch.zeros([8, 3, 20])
c0 = torch.zeros([8, 3, 20])
out, (ht, ct) = lstm(x, (h0, c0))
print(out.shape, ht.shape, ct.shape)
LSTM(100, 20, num_layers=4, bidirectional=True)
odict_keys(['weight_ih_l0', 'weight_hh_l0', 'bias_ih_l0', 'bias_hh_l0', 'weight_ih_l0_reverse', 'weight_hh_l0_reverse', 'bias_ih_l0_reverse', 'bias_hh_l0_reverse', 'weight_ih_l1', 'weight_hh_l1', 'bias_ih_l1', 'bias_hh_l1', 'weight_ih_l1_reverse', 'weight_hh_l1_reverse', 'bias_ih_l1_reverse', 'bias_hh_l1_reverse', 'weight_ih_l2', 'weight_hh_l2', 'bias_ih_l2', 'bias_hh_l2', 'weight_ih_l2_reverse', 'weight_hh_l2_reverse', 'bias_ih_l2_reverse', 'bias_hh_l2_reverse', 'weight_ih_l3', 'weight_hh_l3', 'bias_ih_l3', 'bias_hh_l3', 'weight_ih_l3_reverse', 'weight_hh_l3_reverse', 'bias_ih_l3_reverse', 'bias_hh_l3_reverse'])
Wih:torch.Size([80, 100]) Whh:torch.Size([80, 20])
Bih:torch.Size([80]) Bhh:torch.Size([80])
rWih:torch.Size([80, 100]) rWhh:torch.Size([80, 20])
rBih:torch.Size([80]) rBhh:torch.Size([80])

torch.Size([10, 3, 40]) torch.Size([8, 3, 20]) torch.Size([8, 3, 20])

(out[-1][:,:20] == ht[-2]).all() 返回为True,由于是双向,所以这里第一层是正向,第二层为反向,所以这里-1表示的是反向顺序,-2表示的正向顺序。


5. LSTMCell

【35】Sequence序列网络介绍与使用(含RNN,RNNCell,LSTM,LSTMCell的调用)_第7张图片

5.1 单层LSTMCell

lstm_cell = nn.LSTMCell(input_size=100, hidden_size=20)
print(lstm_cell)
print("Wih:{}, Whh:{}".format(lstm_cell.weight_ih.shape, lstm_cell.weight_hh.shape))
print("Bih:{}, Bhh:{}".format(lstm_cell.bias_ih.shape, lstm_cell.bias_hh.shape))
print()

x = torch.rand([10, 3, 100])
h0 = torch.zeros([3, 20])
c0 = torch.zeros([3, 20])

for xt in x:
    h0, c0 = lstm_cell(xt, (h0, c0))

print(xt.shape, h0.shape, c0.shape)
LSTMCell(100, 20)
Wih:torch.Size([80, 100]), Whh:torch.Size([80, 20])
Bih:torch.Size([80]), Bhh:torch.Size([80])

torch.Size([3, 100]) torch.Size([3, 20]) torch.Size([3, 20])

lstmcell的使用与rnncell的类似,对于h0与c0不断地循环迭代,最后得到的h0就是最后的结果。此外,这里需要注意的是初始化的维度,只需要一个[batch, feacture vec]的维度就可以了。

5.2 多层LSTMCell

lstmcell_1 = nn.LSTMCell(input_size=80, hidden_size=50)
lstmcell_2 = nn.LSTMCell(input_size=50, hidden_size=20)
lstmcell_3 = nn.LSTMCell(input_size=20, hidden_size=10)

# 随机初始化输入
x = torch.rand([10, 3, 80])
# 初始化隐藏单元
h0 = torch.zeros([3, 50])
h1 = torch.zeros([3, 20])
h2 = torch.zeros([3, 10])
# 初始化控制单元
c0 = torch.zeros([3, 50])
c1 = torch.zeros([3, 20])
c2 = torch.zeros([3, 10])

for xt in x:
    h0, c0 = lstmcell_1(xt, (h0, c0))
    h1, c1 = lstmcell_2(h0, (h1, c1))
    h2, c2 = lstmcell_3(h1, (h2, c2))

print(h0.shape, h1.shape, h2.shape)
torch.Size([3, 50]) torch.Size([3, 20]) torch.Size([3, 10])

这里类似rnncell的同样设置的方法,简单的处理过程就是对于第一层的lstm,使用lstmcell_1处理好第一个单词之后,lstmcell_1的输出h0(已更新状态)作为下一层lstm层的输入,同时也作为当前层的当前时刻的一个隐藏状态。而下一层也就是lstmcell_2获取到上一层的输出h0之后,以及初始化的隐藏层单元h1,就会更新这个隐藏层单元作为当前的一个隐藏状态,作为当前层的输出。同样的,这个输出又会进一步的交给下层lstmcell_3进行处理,此时更新好的h2就会作为当前这个这个单词文本的输出。然后,循环遍历整个输入文本,逐一处理好每一个单词,这个过程中就是不断的更新每一层的隐藏单元。最后处理完之后,最后一层的LSTMCell就会更新出也称为是输出的隐藏单元就是最后的结果。

import numpy as np

lstmcell_1 = nn.LSTMCell(input_size=80, hidden_size=80)
lstmcell_2 = nn.LSTMCell(input_size=80, hidden_size=80)
lstmcell_3 = nn.LSTMCell(input_size=80, hidden_size=80)

# 随机初始化输入
x = torch.rand([10, 3, 80])
# 初始化隐藏单元
h0 = torch.zeros([3, 80])
h1 = torch.zeros([3, 80])
h2 = torch.zeros([3, 80])
# 初始化控制单元
c0 = torch.zeros([3, 80])
c1 = torch.zeros([3, 80])
c2 = torch.zeros([3, 80])

# 仿造lstm,保留每一个文本的输出特征
out = []
for xt in x:
    h0, c0 = lstmcell_1(xt, (h0, c0))
    h1, c1 = lstmcell_2(h0, (h1, c1))
    h2, c2 = lstmcell_3(h1, (h2, c2))
    out.append(h2.detach().numpy())
output = np.array(out)

print(h0.shape, h1.shape, h2.shape)
print("output.shape:", output.shape)
torch.Size([3, 80]) torch.Size([3, 80]) torch.Size([3, 80])
output.shape: (10, 3, 80)

如果想要保持维度的不变,那么可以将隐藏单元的维度设置为一样,这样最后的输出结果就会和输入的结果是一致的。


参考资料:

部分资料来源与龙曲良老师的pytorch教程与Pytorch的官方文档

你可能感兴趣的