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

本文介绍了如何处理RNN输入数据,包括文本数据转化为dataset和批处理,以及如何验证RNN内部循环过程。通过实例展示了RNN模型的建立和前向传播,手动计算与模型输出的对比,证明了RNN的工作原理。此外,讨论了torch的pack/pad序列优化技术,避免了在处理不同长度序列时填充0的无效计算,提高了资源利用率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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 = t a n h ( W x t + b i h + U h ( t − 1 ) + b h h ) h t : t 时刻的隐藏层状态 X t : t 时刻的输入 h t − 1 : 前一时刻的隐藏层状态 b : 偏置 \begin{array}{l} h_t = tanh(Wx_t + b_{ih} + Uh_{(t-1)} + b_{hh})\\ ht: t时刻的隐藏层状态\\ X_t:t时刻的输入\\ h_{t-1}:前一时刻的隐藏层状态\\ b:偏置 \end{array} ht=tanh(Wxt+bih+Uh(t1)+bhh)ht:t时刻的隐藏层状态Xt:t时刻的输入ht1:前一时刻的隐藏层状态b:偏置

# rnn.Rnn.all_weights[k] 循环网络参数  k: 代表第k个循环层,这里是单层循环 k=0
#[[weight_ih_l,weight_hh_l,bias_ih_l,bias_hh_l]] 
W = rnn.Rnn.all_weights[0][0].data 
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(W)))

# 第三时刻(第三步) 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原理图

在这里插入图片描述

3、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]))

'''

### 端到端深度学习模型分类 #### 定义特点 端到端深度学习模型是一种直接从输入数据映射至所需输出的框架,整个过程中不需要显式的特征工程或复杂的预处理阶段。这类模型可以从原始数据中自动捕捉并利用复杂模式完成特定任务[^1]。 #### 应用场景举例 对于语音识别而言,传统方法涉及多个独立组件的设计优化;而采用端到端的方法,则可将这些环节统一在一个连续的过程中执行,从而简化了系统的构建流程,并提高了性能表现[^3]。 #### 主要类别概述 根据不同的应用场景和技术需求,当前较为常见的几种端到端深度学习模型包括但不限于: - **图像识别**:此类模型可以直接接收未经加工过的像素级信息作为输入源,在经过多层卷积运算之后给出目标物体标签预测结果。例如用于MNIST手写数字辨识的任务中所使用的全连接前馈神经网络就属于这一类[^4]。 - **自然语言处理(NLP)**:针对文本序列分析问题提出的解决方案,比如机器翻译、情感倾向判断等。BERT (Bidirectional Encoder Representations from Transformers) 是一种典型的双向编码器表征转换器架构,它能有效地理解上下文语境下的词语含义变化规律。 - **时间序列预测**:适用于金融股票走势预报、天气状况推测等领域的时间依赖型数据分析工作。LSTM(Long Short-Term Memory, 长短期记忆单元) 和 GRU(Gated Recurrent Unit, 门控循环单元) 这两种特殊的RNN(Recurrent Neural Network, 循环神经网络)变体非常适合解决此类长期依赖关系建模难题。 ```python import torch.nn as nn class SimpleCNN(nn.Module): def __init__(self): super(SimpleCNN, self).__init__() self.conv_layer = nn.Conv2d(in_channels=1, out_channels=16, kernel_size=(5, 5)) self.fc_layer = nn.Linear(in_features=16 * 10 * 10, out_features=10) def forward(self, x): batch_size = x.size(0) x = self.conv_layer(x).relu() x = x.view(batch_size, -1) output = self.fc_layer(x) return output ``` 此代码片段展示了一个简单的卷积神经网络(CNN),可用于图像分类任务。该网络接受单通道灰度图作为输入,并通过一系列线性和非线性变换最终得到十个可能的结果之一,对应于MNIST数据集中代表的手写数字字符。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值