RNN输入数据加工问题与循环原理流程分析(torch)

RNN输入数据加工问题与循环流程分析(torch)

  1. RNN 输入序列数据加工处理

    1.1 原始数据

    # 假设训练样本text,为4行文本,每个词的词向量为torch.size(1),单元素0维量,tensor.item()获取其中元素
    '''
    text = [我,
            我 爱 你,
            爱 ,
            你
           ]
      --》[[1],[1,2,3],[2],[3]]
    '''
    

    1.2 形成dataset

    import torch
    from torch.nn.utils.rnn import pack_padded_sequence,pad_packed_sequence
    import torch.nn as nn
    from torch.utils.data import Dataset,DataLoader
    
    # 文本长度不一致情况,按最大长度补充0
    text = [[1], [1, 2, 3], [2], [3]]
    feature_size = 1  # 句子序列,每一个词代表一个feature,
    
    # 提取序列数据集
    class Datasets:
    
        def __init__(self, data, feature_size):
            self.data = data
            self.feature_size = feature_size
            self.max_seq_len = max([len(i) for i in text])  # 所有样本中最长的序列,这里指最长文本
            self.data_len = len(data)
    
        def __getitem__(self, index):
            seq_zero = torch.zeros(size=(self.max_seq_len, self.feature_size))  # 构建等长全零序列
            seq_data = torch.tensor(self.data[index]).reshape(-1, self.feature_size)
            seq_zero[0:seq_data.shape[0], 0:seq_data.shape[1]] = seq_data  # 将全零对应位置填充序列值
            return seq_zero
    
        def __len__(self):
            return self.data_len
       
    
    data = Datasets(text, feature_size=feature_size)
    print(data[0])
    print(data[1])
    ''' 如结果所示,已将序列按序列最大长度,进行等长处理
    tensor([[1.],
            [0.],
            [0.]])
            
    tensor([[1.],
            [2.],
            [3.]])       
    '''
    

    1.3 形成批数据

    # 形成序列批数据
    batch_size = 2  # 每批数据训练样本的个数
    dataloader = DataLoader(dataset=data, batch_size=batch_size)
    
    for i in dataloader:
        print(i)
        break
        
    
    '''
    tensor([[[1.],
             [0.],
             [0.]],
    
            [[1.],
             [2.],
             [3.]]])
    '''
    
    
  2. 验证RNN内部数据循环过程(RNN原理)

    2.1 建立RNN模型

    # 构建RNN网络,只进行一次 RNN 循环,未加输出层
    class RNN(nn.Module):
    
        def __init__(self):
            super().__init__()
            self.Rnn = nn.RNN(input_size=1  # 输出特征尺寸,这里只每个词的词向量size
                              , hidden_size=2  # 隐藏层输出尺寸,每个x经过循环层后的输出结果
                              , num_layers=1  # 循环层的个数,如果>=2,后一个RNN层将接收上一个RNN的输出结果
                              , nonlinearity='tanh'  # 非线性激活(tanh/relu)
                              , bias=False  # 是否添加偏置
                              , batch_first=True  # 输入输出的顺序batch开头, (batch,seq, feature_size)
                              , dropout=0  # 在每个RNN引入dropout(最后一个RNN除外)
                              , bidirectional=False  # 是否设置双向RNN
                              )
    
        def forward(self, x):
            x = self.Rnn(x)
            return x
    

    2.2 rnn 向前传播结果

    # 实例化RNN 网络
    rnn = RNN()
    
    for i in dataloader:
        out, hn = rnn(i)
        print(out)  # 中间每一步ht(包含前面步的结果) 形状为(batch,seq_len,hidden_size)
        print(hn)  # 最后一步ht|t=seq_len 形状为(batch,hidden_size)
        break
    '''    
      tensor([[[ 0.2950, -0.1102],
             [-0.1728, -0.0011],
             [ 0.0981, -0.0234]],
    
            [[ 0.2950, -0.1102],
             [ 0.4083, -0.2188],
             [ 0.5828, -0.3432]]], grad_fn=<TransposeBackward1>)
             
             
    tensor([[[ 0.0981, -0.0234],
             [ 0.5828, -0.3432]]], grad_fn=<StackBackward>)
     '''
    
    

    2.3 手动进行rnn 各节点的计算

    # rnn 核心流程 h_t = tanh(W_{ih} x_t + b_{ih} + W_{hh} h_{(t-1)} + b_{hh})
    W = rnn.Rnn.all_weights[0][0].data  # [weight_ih_l[k],weight_hh_l[k],bias_ih_l[k],bias_hh_l[k]] k: 代表第k个rnn_layer
    U = rnn.Rnn.all_weights[0][1].data
    
    
    # 针对样本1 序列为[1,0,0]
    h0 = torch.zeros(1, 2)
    test1 = torch.tensor([[1], [0], [0]], dtype=torch.float)
    
    # 第一时刻(第一步) h1 = W*x1+U*h0
    h1 = torch.tanh(torch.mm(test1[0].unsqueeze(dim=0), torch.t(W)) + torch.mm(h0, torch.t(U)))
    
    # 第二时刻(第二步) h2 = W*x2+U*h1
    h2 = torch.tanh(torch.mm(test1[1].unsqueeze(dim=0), torch.t(W)) + torch.mm(h1, torch.t(U)))
    
    # 第三时刻(第三步) h3 = W*x3+U*h2
    h3 = torch.tanh(torch.mm(test1[2].unsqueeze(dim=0), torch.t(W)) + torch.mm(h2, torch.t(U)))
    
    out1 = torch.stack((h1, h2, h3), dim=0)
    print(out1)
    
    '''
    tensor([[[ 0.2950, -0.1102]],
    
            [[-0.1728, -0.0011]],
    
            [[ 0.0981, -0.0234]]])
    '''
    

    2.4 结果验证

    # 针对样本1 [1,0,0],rnn模型向前传播的每个节点的结果,和手动计算ht=xt*W+U*ht-1的结果完全一样,从而侧面验证RNN原理图
    

  1. torch内部pack/pad 优化短序列中填充部分(计算0浪费资源)的计算

    '''
    如上图所示,填充的0特征也一直参与计算,但实际中0只是为了填充矩阵形状,计算无意义,浪费资源
    为优化此类问题,torch提供了pack/pad函数
    '''
    
    a = torch.tensor([[[1.],
                       [0.],
                       [0.]],
    
                      [[1.],
                       [2.],
                       [3.]]])
    
    print(rnn(a)[0])
    # rnn模型计算结果,序列中填充的0也参与了结果运算,ht=U*h(t-1)+W*0 x特征不起作用,且一直累加h(t-1)*U
    '''
    tensor([[[ 0.1739, -0.0812],
             [-0.1102,  0.1351],
             [ 0.1213, -0.1079]],
    
            [[ 0.1739, -0.0812],
             [ 0.2361, -0.0268],
             [ 0.4040, -0.0814]]], grad_fn=<TransposeBackward1>)
    '''
    
    # 使用pack_padded_sequence重新打包张量 a
    sort = sorted([(torch.sum(a[i] > 0).item(), i) for i in range(a.shape[0])], key=lambda x: x[0], reverse=True)
    sort_index = [i[1] for i in sort]
    sort_len = [i[0] for i in sort]
    
    pack_a = pack_padded_sequence(input=a[sort_index]  # 经过填充的seq(按序列原本长度有从大到小排序)
                                  , lengths=sort_len  # 每一个序列填充前的长度
                                  , batch_first=True  # 输入输出的顺序batch开头, (batch,seq, feature_size)
                                  , enforce_sorted=True  # 序列是否按填充前长度排序,不按顺序排,会有些问题
                                  )
    print(pack_a)
    
    '''
    PackedSequence(data=tensor([[1.],
            [1.],
            [2.],
            [3.]]), batch_sizes=tensor([2, 1, 1]), sorted_indices=None, unsorted_indices=None)
            
    解读:原本一批数据batch_size=2,样本1[1,0,0]和样本2[1,2,3]
         两两组合着一起运算(像多线程一样)[1,1]两个样本同时计算第一个节点数据
         [0,2],[0,3]计算剩下节点数据,因为batch_size=2,所有torch相当于两个样本一起计算的
         
         经过pack压缩后,样本1 [1],样本2[1,2,3], batch_size=tensor([2, 1, 1])
         这样计算第一个节点时按batch_size=2 [1,1]一起计算
         计算剩下节点时,batch_size=1,[2],[3]单独计算剩下节点
    '''
    
    
    # rnn运行结果,可以看到,第一个样本只计算量x1=1,第二个样本计算了x1=1,x2=2,x3=3三个ht
    print(rnn(pack_a))
    '''
    PackedSequence(data=tensor([[-0.4410,  0.5417],
    
            [-0.4410,  0.5417],
            [-0.7087,  0.9123],
            [-0.8708,  0.9822]], grad_fn=<CatBackward>), batch_sizes=tensor([2, 1, 1]), sorted_indices=None, unsorted_indices=None)
    '''
    
    
    # 将压缩后的结果,填充,就和不压缩前模型运算的形状一样了
    print(pad_packed_sequence(sequence=rnn(pack_a)[0]
                              , batch_first=True
                              , padding_value=0.0
                              , total_length=None))
    '''
    (tensor([[[-0.4410,  0.5417],
             [-0.7087,  0.9123],
             [-0.8708,  0.9822]],
    
            [[-0.4410,  0.5417],
             [ 0.0000,  0.0000],
             [ 0.0000,  0.0000]]], grad_fn=<TransposeBackward0>), tensor([3, 1]))
    
    '''
    
    
posted @ 2022-03-10 17:06  旁人怎会懂  阅读(73)  评论(0编辑  收藏  举报