RNN模板类定义及其对象调用
import torch from torch import nn from torch.nn import functional as F seq_len = 85 batch_size = 32 embedding_dim = 256 hidden_size = 512 # RNN每个单元将单词维度映射到新的维度,因此模板定义需要指定其映射关系 rnn=nn.RNN(input_size=256,hidden_size=hidden_size,num_layers=1,bidirectional=False) x = torch.randn([seq_len,batch_size,embedding_dim]) # 每个单链RNN都有一个初始化参数 h0= torch.zeros([1,batch_size,hidden_size]) # ht是每一个时间步t的输出,hn是最后一个时间步的输出 output,hn = rnn(x,h0)
参数说明
RNN模板类有四个参数: input_size: 特征个数,in_features,单词的embedding_dim hidden_size:特征个数,即要将原特征变换到什么维度,out_features,RNN隐藏层向量维数 num_layers: RNN链的层数 bidirectional:每层的RNN链是单向一条RNN链,还是正反两条RNN链 RNN对象调用的两个参数: x:数据,[L,B,C] [序列长度,批次大小,特征个数] [序列,批次,特征] h0: 每条RNN链开始端,隐藏层的初始化参数,通常是0矩阵(但个人认为一个包含当前序列语境的上下文向量更合适,此想法尚未验证)
参数记忆技巧
模板类定义 参数网络终极目的就是特征变换,所有必有 一个输入+一个输出 这里对应的是input_size,hidden_size RNN还多出来两个,一个是层数,是一个方向 模板对象参数 输入数据是必然的,特征是数据的特征, RNN还多了一个参数,每条RNN单链的初始化参数矩阵, 它要与每个单链的首个单词进行信息融合
循环神经网络主要哪三个
RNN LSTM GRU
循环神经网络的循环
一个单词一单词的处理 每个单元处理一个单词,其输入是 当前单词 + 上一个单元的输出 所以RNN处理是的序列,并且无法并发, 因为它只能顺序地处理一个单词后, 才能处理下一个单词, 前后有依赖关系
循环神经网络的网络
指全连接 ℎ𝑡=tanh(x𝑡@𝑊𝑖+𝑏𝑖+(ℎ𝑡−1)@𝑊ℎ+𝑏ℎ) xw+b 就是全连接, 对当前的单词及上一个单元的输出各做一个全连接, 然后相加融合,再加一个激活函数 这里有个面试题,RNN的本质是什么? 是全连接 不要怎么追究这一问一答的细节,因为它不是数学公式,面试官问时就这么答就行了
循环神经网络的问题
依赖问题:序列过长时信息丢失严重, 经实践实验,序列长度超过20个时,精度明显下降 LSTM比RNN好上一丢丢...
循环神经网络优化
双向比单向效果好一些 output,hn 信息量不一样,根据业务场景合理使用,而不是千篇一律只用一个 层数为2就差不多了,更的层数比如5效果未必好
RNN参数 一指模板类的参数,二指数据转换对象的参数 模板类参数:输入,输出,是否双向,层数 数据转换对象参数:数据x,隐藏层hidden 参数与输入输出的shape import torch from torch import nn from torch.nn import functional as F seq_len = 85 batch_size = 32 embedding_dim = 256 hidden_size = 512 # RNN每个单元将单词维度映射到新的维度,因此模板定义需要指定其映射关系 rnn=nn.RNN(input_size=256,hidden_size=hidden_size,num_layers=1,bidirectional=False) x = torch.randn([seq_len,batch_size,embedding_dim]) # 每一个单元处理,都有一个隐藏层 # 输出层output包含所有的隐藏层,[seq_len,batch_size,hidden_size] h0= torch.zeros([1,batch_size,hidden_size]) # ht是每一个时间步t的输出,hn是最后一个时间步的输出 output,hn = rnn(x,h0) hn.shape torch.Size([1, 32, 512]) output.shape torch.Size([87, 32, 512]) 隐藏层为什么要有批次这个维度? RNN某一时刻就处理一个单词,而 隐藏层就是一个单词的输出 单词的embedding_dim转为hidden_size 从设计的目的来看,隐藏层不需要批次, 也跟多少个单词,即序列的维度没有关系 因为每个单词的向量维度提前固定了 但深度学习的计算都是以批次计算的 文本矩阵化后序列长度与批次都会固定下来 从第1个位置开始,一次处理一个批次的单词, 然后处理第2个位置上的单词,又是一个批次的数据, ...... 这里设定只有一个单向RNN链, ℎ𝑡=tanh(𝑥𝑡𝑊𝑖+𝑏𝑖+(ℎ𝑡−1)𝑊ℎ+𝑏ℎ) xt是t时刻,即序列循环中第t个单词的批次数据, xt.shape=[1,batch_size,embedding_dim] xtWi全连接后的shape为[1,batch_size,hidden_size] 隐藏层转换后的结果是要与批次数据xtWi相加, 也就是进行批次计算, 于是就设定ht的shape为[1,batch_size,hidden_size] 从第一个初始化隐藏层h0至最后一个隐藏层ht都是如此 至于序列这个维度,从头到尾,跟隐藏的shape没关系, 即一句话有多少个单词,跟每个单词按多少维输出没关系, 对于单向RNN链,RNN每次计算的只有一个单词, 任一时刻都只有一个单词在再与隐藏层进行计算 对于多个RNN链, 同一时刻与隐藏层计算的单词个数是单向RNN的链数, 即一个链就是一个序列的for循环, 每个for循环同一时刻只有一个单词在与一个隐藏层计算 多组参数对应关系 import torch from torch import nn from torch.nn import functional as F seq_len = 85 batch_size = 32 embedding_dim = 256 hidden_size = 512 bidirectional=True num_layers = 1 single_rnn_nums = num_layers # RNN每个单元将单词维度映射到新的维度,因此模板定义需要指定其映射关系 rnn=nn.RNN(input_size=256,hidden_size=hidden_size,num_layers=num_layers,bidirectional=bidirectional) x = torch.randn([seq_len,batch_size,embedding_dim]) # 每一个单元处理,都有一个隐藏层 # 输出层output包含所有的隐藏层,[seq_len,batch_size,hidden_size] if bidirectional: single_rnn_nums = num_layers*2 h0 = torch.zeros([single_rnn_nums, batch_size, hidden_size]) # ht是每一个时间步t的输出,hn是最后一个时间步的输出 output,hn = rnn(x,h0) hn.shape torch.Size([2, 32, 512]) output.shape torch.Size([85, 32, 1024]) seq_len = 85 batch_size = 32 embedding_dim = 256 hidden_size = 512 bidirectional=True num_layers = 2 single_rnn_nums = num_layers hn.shape torch.Size([4, 32, 512]) output.shape torch.Size([85, 32, 1024]) 隐藏层的第1维,就是dim=0这个维度, |
双层,双向可以增加模型的表达能力 import torch from torch import nn from torch.nn import functional as F seq_len = 85 batch_size = 32 embedding_dim = 512 hidden_size = 512 # RNN每个单元将单词维度映射到新的维度,因此模板定义需要指定其映射关系 rnn=nn.RNN(input_size=512,hidden_size=hidden_size,num_layers=2,bidirectional=False) x = torch.randn([seq_len,batch_size,embedding_dim]) out,hn = rnn(x) out.shape # torch.Size([85, 32, 512]) |
|
|
|
全连接RNNCell
一个RNNCell 循环单元,就是将一个单词从一个维度变换到另外一个维度 一个单元处理一个序列位置上的单词 相同的单元在循环,但每次的输入却是一个序列上不同位置的单词 做这个变换的,就是xw+b,即全连接,所以有人认为RNN的本质就是全连接
序列依赖与单向RNN
从第1个单词到最后一个单词形成的这条链,这里给它起一个名称,叫一个单向RNN 每个单向RNN链条都需要一个初始化的隐藏层, 同一时刻,这个隐藏层计算的对象是单个的RNNCell, 它与输入的单词向量按位相加,注意哈,是按位相加 就是一个向量与另外一个向量按位相加, 再经过激活函数变换一下,就是一个RNNCell的输出了,也是下一个单元的隐藏层 因为矩阵运算要shape一致, 初始化向量要与单词向量相加,而单词向量比如这里的256, 先做一个全连接,变换成512,这里的初始化向量也是512, 然后它们二者就可以相加了 512+512,按位相加上shape不变,向量维度还是512 然后就开始的下一个单元,一直512下去,直到最后一个单元,还是512 输入的时间并不是一次输入一个单词,神经网络是批次计算, 因当前的单词依赖于上一个单词的输出, 所以RNN有序列依赖,可以处理序列问题,同时无法并行
隐藏向量的维度与个数
中间单词的shape是[seq_len,batch_size,embedding_dim], 上来先来个全连接,变成 [seq_len,batch_size,hidden_size] 初始化向量为了迎合这个矩阵运算的规则,初始化的shape为[1,batch_size,hidden_size] 序列这个维度为1,因为同一时刻与隐藏层向量计算的只有一个单词 所以,每条单向RNN链都需要一个初始化向量, 就是一个隐藏层向量,就可以知道 隐藏层dim=0维的值, 就是 RNN的层数*方向数,方向数在单向时为1,双向时为2
output输出层
每个单词有一个输出,所以序列维度数不变 批次维度数也不变 最后的输出向量的维度,如果是单向,就是hidden_size 如果是双向,前半部分是正向,后半部分是反向,二者连接起来了,维数为hidden_size*2 在做优化时,比如某些transform算法将output的最后一维的两个隐藏层向量拆分出来, 相加,不是拼接,而是相加, 得到一个无论是双向还是单向其输出都是hidden_size的结果
隐藏层输出
# ht是每一个时间步t的输出,hn是最后一个时间步的输出 output,hn = model(x,h0) hn是每条单向RNN链最后一个RNNCell的输出, 融合有整个RNN链上所有单词的信息 它的shape与隐藏层初始化向量h0的shape一致
一个单向RNN链的实现
import torch from torch import nn seq_len = 87 batch_size = 32 embedding_dim = 256 hidden_size = 512 class SingleRNNDefine(nn.Module): def __init__(self,input_size=embedding_dim,hidden_size=hidden_size): super().__init__() # [batch_size,embedding_dim]@[embedding_dim,hidden_size] = [batch_size,hidden_size] self.cell_linear_x = nn.Linear(in_features=input_size, out_features=hidden_size) self.cell_linear_h = nn.Linear(in_features=hidden_size, out_features=hidden_size) def forward(self,x,h0): seq_len, batch_size, embedding = x.shape output = [] ht = h0[0] # [1,batch_size,embedding] for t in range(seq_len): # print(f"x[{t}].shape={x[t].shape}") # x[86].shape=torch.Size([32, 256]) # [batch_size,embedding] --> [batch_size,hidden_size] # 对于每一个时间步来说,不需要管seq_len的维度,因为一步一个单词 each_word = self.cell_linear_x(x[t]) # print(f"t={t},each_word.shape={each_word.shape}") # print(f"t={t},ht.shape={ht.shape}") ht = self.cell_linear_h(ht) ht = torch.tanh(each_word + ht) # print(ht.shape) # torch.Size([32, 512]) output.append(ht.tolist()) hn = torch.unsqueeze(input=ht,dim=0) # print(hn.shape) # torch.Size([1, 32, 512]) output = torch.Tensor(output) return output,hn if __name__=="__main__": rnn = SingleRNNDefine(input_size=embedding_dim, hidden_size=hidden_size) x = torch.randn([seq_len, batch_size, embedding_dim]) # 每一个单元处理,都有一个隐藏层 h0= torch.zeros([1, batch_size, hidden_size]) # ht是每一个时间步所有单向RNN链t时刻的输出,hn是所有单向RNN链最后一个时间步的输出 # 输出层output包含所有的隐藏层,[seq_len,batch_size,hidden_size] output,hn = rnn(x,h0) print(output.shape) # torch.Size([87, 32, 512]) print(hn.shape) # torch.Size([1, 32, 512])