Pytorch之循环神经网络

希望通过本文,能够让大家,不需要再去参阅其他资料,就可以完全弄清楚torch.nn.LSTM(),不明白的地方可以随时留言~本文不定时更新。

循环神经网络层的理解

torch.nn.LSTM()

pack_padded_sequence和Pad_packed_sequence

在设计RNNs类的神经网络的时候,经常会使用pack_padded_sequence和Pad_packed_sequence,这是因为pytorch通过pack_padded_sequence和Pad_packed_sequence这两个效果互逆的方法给予了RNNs处理变长序列的能力。这样可以有效地减小对运算效率与结果的影响。其实就是通过一个pack(压缩)和UNpack(解压)的过程让网络的前向计算跳过所对应的词向量的计算过程。

例子说明

以下内容来自于这里
我们举一个简单的例子来说明一下。


import torch
from torch import nn

# 输入的句子的id序列


txt_id_seq = torch.tensor([[3,2,1,0],    
                           [2,1,0,0],
                           [5,0,0,0]])

embed_layer = nn.Embedding(num_embeddings=6,  # 可能会嵌入的数量,也就是词表的长度
                           embedding_dim=3,   # 单个id值的嵌入的维度,也就是词向量的维度
                           padding_idx=0)     # <PAD>的id值,默认为0

embed_seq = embed_layer(txt_id_seq)   # 将id映射成词向量

print("id序列嵌入后的形状:", embed_seq.shape)
print("嵌入矩阵的形状:", embed_layer.weight.data.shape)

gru = nn.GRU(input_size=3,       # 输入的单个time step的维度,也就是词向量的维度
             hidden_size=5,      # 隐层的维度
             num_layers=1,		 # 每个门控循环单元的层数
             batch_first=True)   # 导入的数据形状为[B, T, *]

out, h_n = gru(embed_seq, None)     # 将词向量的维度,从embedding_dim映射到hidden_dim
print("RNN的out的形状:", out.shape)
print("RNN最后一个cell的输出值的形状:", h_n.shape)

结果为:

id序列嵌入后的形状: torch.Size([3, 4, 3])
嵌入矩阵的形状: torch.Size([6, 3])
RNN的out的形状: torch.Size([3, 4, 5])
RNN最后一个cell的输出值的形状: torch.Size([1, 3, 5])

上述过程就是一般NLP任务feature extraction的步骤,我们一般会取循环神经网络out的最后一个(也就是最后一个输出的o_t)作为整句话的语义特征。在这里插入图片描述
我们看看输入的每个id序列最后的语义特征是什么。

for i in range(3):
	print(out[i])

结果为:

tensor([[-0.3654,  0.0183, -0.4721, -0.5012, -0.0049],
        [-0.3866, -0.2832,  0.0739, -0.0595,  0.2906],
        [-0.1104,  0.1980,  0.0995,  0.5426,  0.1445],
        [-0.1349, -0.1310,  0.0822,  0.3626,  0.0617]],
       grad_fn=<SelectBackward>)
tensor([[-0.2425, -0.3400,  0.3298,  0.1249,  0.1939],
        [-0.0271,  0.1630,  0.2649,  0.6118,  0.0186],
        [-0.1007, -0.1612,  0.1775,  0.3766, -0.0449],
        [-0.1405, -0.2970,  0.1338,  0.2727, -0.0831]],
       grad_fn=<SelectBackward>)
tensor([[-0.0678, -0.1343, -0.1453, -0.1782, -0.3926],
        [-0.1260, -0.2608, -0.0760,  0.0901, -0.2029],
        [-0.1359, -0.3325, -0.0125,  0.1766, -0.1350],
        [-0.1429, -0.3722,  0.0308,  0.2016, -0.1150]],
       grad_fn=<SelectBackward>)

我们原来的id序列是[[3,2,1,0],[2,1,0,0],[5,0,0,0]],其中是0,是没有意义的填充符,但是这三个id序列在经过嵌入,GRU后得到了上面out中的结果,第一个id序列[3,2,1,0]被映射到了

[[-0.3654, 0.0183, -0.4721, -0.5012, -0.0049],
[-0.3866, -0.2832, 0.0739, -0.0595, 0.2906],
[-0.1104, 0.1980, 0.0995, 0.5426, 0.1445],
[-0.1349, -0.1310, 0.0822, 0.3626, 0.0617]]

其中的0被映射到了矩阵的最后一行的 [-0.1349, -0.1310, 0.0822, 0.3626, 0.0617]。代表的0应该是没有意义的,但是最终映射得到的结果是一串非稀疏的向量,而且没有意义的0还需要经过如上的一串运算,看起来并不是最好的结果。

但是通过pack_padded_sequence和pad_packed_sequence操作就可以解决上述问题。如下:

embed_seq_pack = nn.utils.rnn.pack_padded_sequence(input=embed_seq,       # 需要编码的batch_size个话
												   lengths=torch.tensor([3, 2, 1]), # 每句话的长度
												   batch_first=True)  # 输入的形状为[B, T, *]

out_pack, h_n_pack = gru(embed_seq_pack, None)    # gru支持编码后的序列输入

out, _  = nn.utils.rnn.pad_packed_sequence(sequence=out_pack,  # 需要解码的序列
										   batch_first=True,   # 解压的形状为[B, T, *]
                                           padding_value=0)    # 解压后的<PAD>的id值

print(out.shape)
print("输入的第三个id序列:", txt_id_seq[2])
print("对应的特征映射:", out[2])

结果为:

torch.Size([3, 4, 5])
输入的第三个id序列: tensor([5, 0, 0, 0])
对应的特征映射: tensor([[-0.2815,  0.4022, -0.2261,  0.1051, -0.2380],
        [ 0.0000,  0.0000,  0.0000,  0.0000,  0.0000],
        [ 0.0000,  0.0000,  0.0000,  0.0000,  0.0000],
        [ 0.0000,  0.0000,  0.0000,  0.0000,  0.0000]],
       grad_fn=<SelectBackward>)

从上述程序中可以看到pack_padded_sequence和pad_packed_sequence的使用方法:pack_padded_sequence接受batch_size个通过嵌入矩阵的词向量拼接成的矩阵,和一个长度为batch_size的一维tensor,这个tensor代表该batch中每个句子的长度。通过参数batch_size来指定数据的组织方式是[B, T, *]还是[T, B, *]。得到一个PackedSequence类(压缩序列类),这个类通过指定句长的参数去掉了所有的词向量,我们可以打印上述三个id序列的PackedSequence类和原本的嵌入矩阵看看:

print("三个id序列的嵌入矩阵为:", embed_seq)
print("三个句子嵌入矩阵的压缩序列类:", embed_seq_pack)

结果为:

三个id序列的嵌入矩阵为: tensor([[[-0.0358,  1.0701,  1.5646],
         [-0.0347, -0.3378,  0.2072],
         [ 2.0477, -0.0644,  0.2723],
         [ 0.0000,  0.0000,  0.0000]],

        [[-0.0347, -0.3378,  0.2072],
         [ 2.0477, -0.0644,  0.2723],
         [ 0.0000,  0.0000,  0.0000],
         [ 0.0000,  0.0000,  0.0000]],

        [[-0.8102,  1.3946,  0.1938],
         [ 0.0000,  0.0000,  0.0000],
         [ 0.0000,  0.0000,  0.0000],
         [ 0.0000,  0.0000,  0.0000]]], grad_fn=<EmbeddingBackward>)
三个句子嵌入矩阵的压缩序列类: PackedSequence(data=tensor([[-0.0358,  1.0701,  1.5646],
        [-0.0347, -0.3378,  0.2072],
        [-0.8102,  1.3946,  0.1938],
        [-0.0347, -0.3378,  0.2072],
        [ 2.0477, -0.0644,  0.2723],
        [ 2.0477, -0.0644,  0.2723]], grad_fn=<PackPaddedSequenceBackward>), batch_sizes=tensor([3, 2, 1]), sorted_indices=None, unsorted_indices=None)

因为我们指定了batch_first=True,所以所有的全0行向量都被去掉了。这么处理后的序列参与gru的运算时,原本的0向量就不参与运算,最后直接映射到全0序列。

需要注意的是,pack_padded_sequence中代表句长的向量,默认是需要强制降序,如果你给的不是降序的向量,会报错,此时你可以手动通过排序来解决,也可以通过设置pack_padded_sequence中的参数enforce_sorted=False来解决(因为enforce_sorted=False时,程序内部会无条件排序)。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱科研的徐博士

请各位看官赏赐,小仙女笔芯笔芯

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值