RNN API调用示例
**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可以提取序列特征,那什么是序列特征?
一句话就是一个序列,它有多个单词组成,每个单词都是一个向量,但序列特征只有一个
- 针对所有的单词向量
- 是整句话的特征
对于交易数据,一个账户的交易在时间上展开之后,按固定的时间窗口滑动,就得到一个个序列
- 每个序列有一个特征
- 如果一个序列异常,可能是序列中某个或某些交易异常,但这些序列取自相同的账户,是按账户排序的
- 因此,序列的异常,就代表着账户的异常
比如,图结构中针对图的预测,比如,分子结构,如果一个分子部分存在异常,那么也可以说这个分子异常
mytab('ulrnn_1', 'tbrnn_1');
循环神经网络参数
概述
双层,双向
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])
mytab('ulrnn_param', 'tbrnn_param');
RNN内部结构
全连接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自编码实现
一个单向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])
参考文章