当前位置: 首页 > news >正文

循环神经网络(Recurrent Neural Network,RNN)

RNN(循环神经网络)

引言

RNN(Recurrent Neural Network,循环神经网络),用于处理序列数据(Sequence),传统的神经网络模型是从输入层到隐藏层,最后再到输出层。层与层之间使用全连接。但是当遇到对句子进行补全(给了几个单词,预测下一个单词,比如已经有了i love,rnn可能会给出you),由于前后单词是有相关性的,传统神经网络对这种问题就无法处理。RNN之所以叫作循环神经网络主要是因为RNN当前的输出与前面的输出有关,网络能够对之前的信息进行记忆,并与下一次的输入一同进行处理,给出输出。
RNN一般有多种结构,包括:One to One、One to Many、Many to One、Many to Many几种
在这里插入图片描述

One to One 是最原始的神经网络( y = f ( W x + b ) y=f(Wx+b) y=f(Wx+b)),One to Many的应用例如:从图像生成文字,Many to One 的应用例如:文本情感分类,Many to Many的应用例如:机器翻译,以及帧级别的视频分类(注意Many to Many中sequence的时序性,所以有两种不同的例子)。

  1. One to One
    One to One 就是最简单最基本的单层网络。
    在这里插入图片描述

    读取前一个隐藏态 h t − 1 h_{t-1} ht1 和当前的输入 x t x_t xt,生成下一个隐藏态。Vanilla的循环函数有两个权重,两个权重分别与前一个时间步状态h和此时间步的输入x进行相乘求和,然后使用tanh函数,将结果缩放至[-1,1]范围。Vanilla简单,但效果不怎么好。

  2. One to Many
    在这里插入图片描述

    在这里插入图片描述

    One to Many接收固定长的输入项,输出不定长的输出项,有以上两种结构,第一种结构是输入一次,此后每个阶段产生一次输出,即各阶段都依赖于上一阶段的信息;第二种结构是每个阶段都将初始的输入作为输入,结合前面产生的信息,得到每个阶段的输出。One to Many的计算图如下:
    在这里插入图片描述

  3. Many to One
    处理一个序列,但是输出为一个单独的值,通常用于处理序列分类问题,比如判断一个文章的类别,或者某个句子的情感分类。
    在这里插入图片描述

    在最后一个隐藏层上进行输出变换,即可得到一个单值。
    在这里插入图片描述

  4. Many to Many
    Many to Many 是最经典的RNN结构,主要也分两种,一种是输入序列长度等于输出序列长度,一种是输入序列长度和输出序列长度不等。
    在这里插入图片描述

RNN还有一个重要变种:Sequence to Sequence,即使用Many to One + One to Many。
Many to One 将输入序列编码成一个单独的向量(即Encoder),One to Many从这个单独的向量中产生输出(即Decoder)。编码阶段是会收到一个不定长序列,可能是一个英语句子,整个句子会被编码器网络最终的隐层状态编码,把不定长输入编码成了一个单独的向量。解码网络是输入是前面编码好的向量,生成的是一个不定长输出序列,可能是用另一种语言表述的同义句子。
在这里插入图片描述

Encoder-Decoder结构存在一些缺点:

  • 最大的局限性:编码和解码之间的唯一联系是固定长度的语义向量c
  • 编码要把整个序列的信息压缩进一个固定长度的语义向量c
  • 语义向量c无法完全表达整个序列的信息
  • 先输入的内容携带的信息,会被后输入的信息稀释掉,或者被覆盖掉
  • 输入序列越长,这样的现象越严重,这样使得在Decoder解码时一开始就没有获得足够的输入序列信息,解码效果会打折扣

原理

RNN之所以叫做循环神经网络,是因为对于序列中的每个元素,都会执行相同的任务,输出依赖于先前的计算。从另一个角度考虑可以认为RNN具有记忆,可以捕获到目前位置见过的信息。理论上RNN是可以利用任意长度序列的信息的,但是实际上RNN会被限制在固定的几个时间步里。下面是一个经典的RNN示意图。
在这里插入图片描述

RNN的计算步骤如下:

  1. x t x_t xt表示第t步的输入,例如 x 1 x_1 x1是一个与句子中第二个词有关的独热编码( x 0 x_0 x0是第一个词)。
  2. s t s_t st是第t步的隐藏层,也就是神经网络的记忆单元。 s t s_t st的计算基于先前隐藏层,当前的 s t s_t st计算公式为: s t = f ( U x t + W s t − 1 ) s_t = f(Ux_t+Ws_{t-1}) st=f(Uxt+Wst1) f f f函数常常是tanh或者ReLU激活函数。 s − 1 s_{-1} s1常常被设置为全0的向量,用于计算第一个隐藏层。
  3. o t o_t ot是第t步的输出。例如,我们想要预测句中下一个单词,我们需要得到一个概率,利用这个概率来预测是词表中的哪个单词。 o t = s o f t m a x ( V s t ) o_t = softmax(Vs_t) ot=softmax(Vst)
    下面的图对于RNN结构更加直观,要注意的是隐藏层是每个不同时间的更新状态,并非是一层一层传递下去。
    在这里插入图片描述
    在这里插入图片描述

根据上图,RNN的前向传播可以写为:
t i = W h x x i + W h h h i − 1 + b h h i = e ( t i ) s i = W y h h i + b y y ^ i = g ( s i ) t_i = W_{hx}x_i + W_{hh}h_{i-1}+b_h\\ h_i = e(t_i)\\ s_i = W_{yh}h_i+b_y\\ \hat y_i = g(s_i) ti=Whxxi+Whhhi1+bhhi=e(ti)si=Wyhhi+byy^i=g(si)
代价函数可以是 ∑ i ∣ ∣ y ^ i − y i ∣ ∣ 2 / 2 \sum_i||\hat y_i-y_i||^2/2 i∣∣y^iyi2/2
或者交叉熵 − ∑ i ∑ j y i j l o g ( y ^ i j ) + ( 1 − y i j ) l o g ( 1 − y ^ i j ) -\sum_i\sum_jy_{ij}log(\hat y_{ij})+(1-y_{ij})log(1-\hat y_{ij}) ijyijlog(y^ij)+(1yij)log(1y^ij)
需要注意的是

  • s t s_t st能够获取先前的所有信息, o t o_t ot仅仅依赖于当前t时刻的记忆进行计算,为了降低网络复杂度, s t s_t st只包含前面若干步,不会包含之前所有的隐藏层状态。
  • 在RNN中,每输入一步,每一层都共享参数U,V,W,这也就反映了RNN每一步都在做相同的事情,只是输入不同,这就大大降低了网络中的参数。而传统的神经网络中,每一层的W是不共享的。、
  • 图中每一步都会有输出,但是每一步都要有输出并不是必须的。比如,我们需要预测一条语句所表达的情绪,我们仅仅需要关系最后一个单词输入后的输出,而不需要知道每个单词输入后的输出。同理,每步都需要输入也不是必须的。RNNs的关键之处在于隐藏层,隐藏层能够捕捉序列的信息。

反向传播
深度学习中,经常处理的是独立同分布的数据,比如文本生成、图像分类,每个样本之间是相互独立的,但是现实中会有许多包含时序结构的数据,比如视频中的帧,这类数据传统的反向传播可能没有办法很好地捕捉其中存在的相关性,因此RNN中使用BPTT(Back Propagation Through Time)算法来进行反向传播,BPTT引入了时间维度,考虑了Sequence数据中的时序关系。隐藏层状态 s t s_t st的更新规则包含了当前时刻的输入 x t x_t xt以及上一个时刻的隐藏层状态 h t − 1 h_{t-1} ht1,从而更好地捕获Sequence中的时间相关性。

在BPTT中我们需要计算对于参数U、V和W的偏导数,从而使得更新参数以最小化损失。

通过对整个序列的损失函数求导,我们可以找到在参数空间中使得损失函数逐步减小的方向,然后通过反向传播来更新参数。

由于RNN处理的是时序数据,因此需要基于时间进行反向传播,这也是BPTT名称的由来。尽管BPTT是在时序数据上进行反向传播,但本质上它仍然是反向传播算法,因此求解每个时间步的梯度是该算法的核心操作。

以一个长度为3的时间序列为例,前相传播的计算为:
h t = f ( U X t + W h t − 1 ) h_t = f(UX_t+Wh_{t-1}) ht=f(UXt+Wht1)
通过线性变换 U X t + W h t − 1 UX_t+Wh_{t-1} UXt+Wht1,加上激活函数 f f f的作用,得到了新的隐藏状态 h t h_t ht。这个结构使得RNN能够记忆之前的信息并将其应用于当前的预测任务中。
输出层为:
y ^ t = f ( V h t ) \hat y_t = f(Vh_t) y^t=f(Vht)
那么长度为3的时间序列就可以表示为
{ h 1 = f ( U x 1 + W h 0 ) y ^ 1 = f ( V h 1 ) \begin {cases} h_1 = f(Ux_1 +Wh_0)\\ \hat y_1 = f(Vh_1) \end {cases} {h1=f(Ux1+Wh0)y^1=f(Vh1)

{ h 2 = f ( U x 2 + W h 1 ) y ^ 2 = f ( V h 2 ) \begin {cases} h_2 = f(Ux_2 +Wh_1)\\ \hat y_2 = f(Vh_2) \end {cases} {h2=f(Ux2+Wh1)y^2=f(Vh2)

{ h 3 = f ( U x 3 + W h 2 ) y ^ 3 = f ( V h 3 ) \begin {cases} h_3 = f(Ux_3 +Wh_2)\\ \hat y_3 = f(Vh_3) \end {cases} {h3=f(Ux3+Wh2)y^3=f(Vh3)

针对最后一个时刻(t=3)求出U、V、W的梯度有:
在这里插入图片描述

可以看出,在对U求偏导时,只需要当前数据即可,但是对W和U求偏导,需要不断往前回溯。

那么就可以通过最后时刻的偏导,计算其他时刻对U、V、W的偏导。
那么对于t时刻,就有对于V的偏导:
∂ L t ∂ V = ∂ L t y ^ t ∗ ∂ y t ∂ V \frac{\partial L_t}{\partial V} = \frac{\partial L_t}{\hat y_t} * \frac{\partial y_t}{\partial V} VLt=y^tLtVyt
对于W的偏导:
∂ L t ∂ W = ∑ k = 1 t ∂ L t ∂ y ^ t ∗ ∂ y ^ t ∂ h t ∗ ( ∏ i = k + 1 t ∂ h i ∂ h i − 1 ) ∗ ∂ h k W \frac{\partial L_t}{\partial W} = \sum_{k=1}^t \frac{\partial L_t}{\partial \hat y_t} * \frac{\partial \hat y_t}{\partial h_t} * (\prod_{i=k+1}^t \frac{\partial h_i}{\partial h_{i-1}}) * \frac{\partial h_k}{W} WLt=k=1ty^tLthty^t(i=k+1thi1hi)Whk
对于U的偏导:
∂ L t ∂ U = ∑ k = 1 t ∂ L t ∂ y ^ t ∗ ∂ y ^ t ∂ h t ∗ ( ∏ i = k + 1 t ∂ h i ∂ h i − 1 ) ∗ ∂ h k U \frac{\partial L_t}{\partial U} = \sum_{k=1}^t \frac{\partial L_t}{\partial \hat y_t} * \frac{\partial \hat y_t}{\partial h_t} * (\prod_{i=k+1}^t \frac{\partial h_i}{\partial h_{i-1}}) * \frac{\partial h_k}{U} ULt=k=1ty^tLthty^t(i=k+1thi1hi)Uhk
由于和的导数等于导数的和,因此最后就有

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

BPTT存在的梯度消失和梯度爆炸问题
由于和W、U有关的求梯度中,存在连乘,那么就会出现指数级别的增长,当 ∂ h i ∂ h i − 1 > 1 \frac{\partial h_i}{\partial h_{i-1}} > 1 hi1hi>1时,会导致梯度爆炸。
而小于1时,则会出现梯度消失。

代码

%matplotlib inline
import  torch
import datetime
import  numpy as np
import  torch.nn as nn
import  torch.optim as optim
from    matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from pylab import mpl
mpl.rcParams['font.sans-serif'] = ['FangSong']
mpl.rcParams['axes.unicode_minus'] = False
###########################设置全局变量###################################num_time_steps = 16    # 训练时时间窗的步长
input_size = 3          # 输入数据维度
hidden_size = 16        # 隐含层维度
output_size = 3         # 输出维度
num_layers = 1
lr=0.01
####################定义RNN类##############################################class Net(nn.Module):def __init__(self, input_size, hidden_size, num_layers):super(Net, self).__init__()self.rnn = nn.RNN(input_size=input_size,hidden_size=hidden_size,num_layers=num_layers,batch_first=True,)for p in self.rnn.parameters():nn.init.normal_(p, mean=0.0, std=0.001)self.linear = nn.Linear(hidden_size, output_size)def forward(self, x, hidden_prev):out, hidden_prev = self.rnn(x, hidden_prev)# [b, seq, h]out = out.view(-1, hidden_size)out = self.linear(out)#[seq,h] => [seq,3]out = out.unsqueeze(dim=0)  # => [1,seq,3]return out, hidden_prev####################初始化训练集#################################
def getdata():x1 = np.linspace(1,10,30).reshape(30,1)y1 = (np.zeros_like(x1)+2)+np.random.rand(30,1)*0.1z1 = (np.zeros_like(x1)+2).reshape(30,1)tr1 =  np.concatenate((x1,y1,z1),axis=1)# mm = MinMaxScaler()# data = mm.fit_transform(tr1)   #数据归一化return tr1#####################开始训练模型#################################
def tarin_RNN(data):model = Net(input_size, hidden_size, num_layers)print('model:\n',model)criterion = nn.MSELoss()optimizer = optim.Adam(model.parameters(), lr)#初始化hhidden_prev = torch.zeros(1, 1, hidden_size)l = []# 训练3000次for iter in range(3000):# loss = 0start = np.random.randint(10, size=1)[0]end = start + 15x = torch.tensor(data[start:end]).float().view(1, num_time_steps - 1, 3)# 在data里面随机选择15个点作为输入,预测第16y = torch.tensor(data[start + 5:end + 5]).float().view(1, num_time_steps - 1, 3)output, hidden_prev = model(x, hidden_prev)hidden_prev = hidden_prev.detach()loss = criterion(output, y)model.zero_grad()loss.backward()optimizer.step()if iter % 100 == 0:print("Iteration: {} loss {}".format(iter, loss.item()))l.append(loss.item())##############################绘制损失函数#################################plt.plot(l,'r')plt.xlabel('训练次数')plt.ylabel('loss')plt.title('RNN损失函数下降曲线')return hidden_prev,model
#############################预测#########################################def RNN_pre(model,data,hidden_prev):data_test = data[19:29]data_test = torch.tensor(np.expand_dims(data_test, axis=0),dtype=torch.float32)pred1,h1 = model(data_test,hidden_prev )print('pred1.shape:',pred1.shape)pred2,h2 = model(pred1,hidden_prev )print('pred2.shape:',pred2.shape)pred1 = pred1.detach().numpy().reshape(10,3)pred2 = pred2.detach().numpy().reshape(10,3)predictions = np.concatenate((pred1,pred2),axis=0)# predictions= mm.inverse_transform(predictions)print('predictions.shape:',predictions.shape)#############################预测可视化########################################fig = plt.figure(figsize=(9, 6))ax = Axes3D(fig)fig.add_axes(ax)ax.scatter3D(data[:, 0],data[:, 1],data[:,2],c='red')ax.scatter3D(predictions[:,0],predictions[:,1],predictions[:,2],c='y')ax.set_xlabel('X')ax.set_xlim(0, 8.5)ax.set_ylabel('Y')ax.set_ylim(0, 10)ax.set_zlabel('Z')ax.set_zlim(0, 4)plt.title("RNN航迹预测")plt.show()def main():data = getdata()start = datetime.datetime.now()hidden_pre, model = tarin_RNN(data)end = datetime.datetime.now()print('The training time: %s' % str(end - start))plt.show()RNN_pre(model, data, hidden_pre)
if __name__ == '__main__':main()

model:
Net(
(rnn): RNN(3, 16, batch_first=True)
(linear): Linear(in_features=16, out_features=3, bias=True)
)
Iteration: 0 loss 14.835593223571777
Iteration: 100 loss 0.869890570640564
Iteration: 200 loss 0.6771376132965088
Iteration: 300 loss 0.7746877074241638
Iteration: 400 loss 0.587168276309967
Iteration: 500 loss 0.048645373433828354
Iteration: 600 loss 0.004850820172578096
Iteration: 700 loss 0.0020328934770077467
Iteration: 800 loss 0.0012394418008625507
Iteration: 900 loss 0.0037996249739080667
Iteration: 1000 loss 0.000738648057449609
Iteration: 1100 loss 0.00034336611861363053
Iteration: 1200 loss 0.0006057258578948677
Iteration: 1300 loss 0.00029988010646775365
Iteration: 1400 loss 0.00026152681675739586
Iteration: 1500 loss 0.0009218865889124572
Iteration: 1600 loss 0.0004799812741111964
Iteration: 1700 loss 0.0005697591695934534
Iteration: 1800 loss 0.00027546813362278044
Iteration: 1900 loss 0.00027389932074584067
Iteration: 2000 loss 0.00035360114998184144
Iteration: 2100 loss 0.000485988799482584
Iteration: 2200 loss 0.0004234382649883628
Iteration: 2300 loss 0.00032358983298763633
Iteration: 2400 loss 0.00027860625414177775
Iteration: 2500 loss 0.0003559405740816146
Iteration: 2600 loss 0.0003529365349095315
Iteration: 2700 loss 0.0008158125565387309
Iteration: 2800 loss 0.00040216089109890163
Iteration: 2900 loss 0.000560364976990968
The training time: 0:00:05.810719

在这里插入图片描述
在这里插入图片描述

参考

循环神经网络(RNN, Recurrent Neural Networks)介绍

Recurrent Neural Networks Tutorial, Part 1 – Introduction to RNNs

cs231n

RNN详解(Recurrent Neural Network)

BPTT算法详解:深入探究循环神经网络(RNN)中的梯度计算【原理理解】

代码


http://www.mrgr.cn/news/60766.html

相关文章:

  • MySQL练习题-求连续、累计、环比和同比问题
  • SwiftUI(五)- ForEach循环创建视图尺寸类安全区域
  • 软考:中间件
  • 开源实时数仓的构建
  • 微信小程序生成海报 / 两张图片合并生成一张
  • nginx------正向代理,反向代理生产,以及能否不使用代理详解
  • 4个硬盘数据修复攻略:让你的数据失而复得。
  • 同一个Service内部调用开启事务
  • Python多语双峰分布
  • 练习LabVIEW第二十四题
  • Unity Job System详解(3)——NativeArray源码分析
  • 100种算法【Python版】第21篇——Wilson算法
  • Java Lock CountDownLatch 总结
  • 李宇皓现身第十届“文荣奖”,allblack造型帅气绅士引关注
  • 加强版 第一节图像二值化定义
  • 四、常量指针其他
  • 信创认证(信创人才考评证书)的含金量?到底有多少?
  • 【Flask】三、Flask 常见项目架构
  • IPV6扩展头部
  • SQL进阶技巧:Hive如何进行更新和删除操作?
  • 自修室预约系统|基于java和小程序的自修室预约系统设计与实现(源码+数据库+文档)
  • 代码随想录第46天|
  • 前端:遇到的面试题
  • Oracle 第10章:触发器
  • Spring MVC介绍
  • Spring Boot 3项目创建与示例(Web+JPA)