RNN 循环神经网络 初步学习

本文详细介绍了循环神经网络(RNN)的基本概念、模型结构、前向传播和反向传播过程,以及如何在PyTorch中实现RNN。特别关注了梯度消失问题和不同类型的激活函数,如ReLU、LeakyReLU和它们的应用。
摘要由CSDN通过智能技术生成

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

一、概述

  • RNN是一种学习序列数据的算法

    BP、CNN算法与RNN同属于神经网络,但BP与CNN的输出都是只考虑单个输入的影响,如单个物体和手写字的识别。当输入端给出一段连续的,隐含有上下文关系的输入(或者叫做与时间先后有关的输入),比如视频的下一时刻的预测,文档前后文内容的预测等,这些算法的表现就不尽如人意了。

    RNN根据"人的认知是基于过往的经验和记忆"这一观点提出,它不仅考虑前一时刻的输入,而且赋予了网络对前面的内容的一种’记忆’功能,也就是当前的输出与之前的输出相关。具体的表现形式为网络会对前面的信息进行记忆并应用于当前输出的计算中,也就是说,隐藏层之间的节点是有连接的,并且隐藏层的输入不仅包括输入层的输出还包括上一时刻隐藏层的输出。

二、RNN模型结构

普通神经网络结构

神经网络结构

RNN网络结构

RNN网络结构

可以看到在上图中存在多个时刻的网络结构,每个时刻的网络结构不仅仅有来自本层的输入,还有来自上一时刻隐藏层的输入。  

除去层级内的权重,在每一层之间也有权重,我们将其记作 W W W 。一般层级之间共享此权重,这样可以有效减少训练参数。
我们还可以将上面的三维示意图简化为更加简单的二维表示:

RNN结构二维简化

三、RNN的前向传播

前向传播计算流程

隐藏层输出 $ s_i $ 计算公式:
s i = f ( ∑ n N ( U i ⋅ x n i + b i ) ) s_i = f(\sum_{n}^N(U^i \cdot x_n^i + b_i)) si=f(nN(Uixni+bi))

在上式中,$ U $为输入层到隐藏层的权重矩阵;
$ x_n $为每一层的输入;
$ b_i 为每层的偏置;式中的函数 为每层的偏置; 式中的函数 为每层的偏置;式中的函数f$为激活函数,一般常用 tanh 或 relu

或者写成矩阵形式:
s = f ( U x + b 1 ) s = f(Ux + b_1) s=f(Ux+b1)
由于RNN的每层输出需要考虑前一时刻的输出,那么,在t时刻的隐藏层输出表示为:
s t = f ( U x t + W s t − 1 + b 1 ) s_t = f(Ux_t + Ws_{t-1} + b_1) st=f(Uxt+Wst1+b1)

  • 如此一来,不同时刻的隐藏层就将进行迭代

每层输出 o i o_i oi计算:
o i = g ( V s i + b 2 ) o_i = g(Vs_i + b_2) oi=g(Vsi+b2)

四、RNN的反向传播

在每一次的输出 o i o_i oi中都会产生一个误差值 e i e_i ei 那么总的误差可以使用下式表示:
E = ( ∑ i e i ) E = (\sum_ie_i) E=(iei)

  • 损失函数可以使用交叉熵损失函数也可以使用平方误差损失函数

将输出端的误差值反向传递,运用梯度下降法进行更新。
参数梯度分别为:
∇ U = ∂ E ∂ U = ∑ i ∂ e i ∂ U \nabla U = \frac{\partial{E}}{\partial{U}} = \sum_i{\frac{\partial e_i}{\partial U}} U=UE=iUei
∇ V = ∂ E ∂ V = ∑ i ∂ e i ∂ V \nabla V = \frac{\partial{E}}{\partial{V}} = \sum_i{\frac{\partial e_i}{\partial V}} V=VE=iVei
∇ W = ∂ E ∂ W = ∑ i ∂ e i ∂ W \nabla W = \frac{\partial{E}}{\partial{W}} = \sum_i{\frac{\partial e_i}{\partial W}} W=WE=iWei
此处以W第i个时刻的偏导数为例(将此时的误差记作 E i E_i Ei):
∂ E i ∂ W = ∂ E i ∂ o i ∂ o i ∂ s i ∂ s i ∂ W \frac{\partial{E_i}}{\partial{W}} = \frac{\partial{E_i}}{\partial{o_i}} \frac{\partial{o_i}}{\partial{s_i}} \frac{\partial{s_i}}{\partial{W}} WEi=oiEisioiWsi
由于每一时刻的 s i s_i si都与前一时刻的 s i − 1 s_{i-1} si1有关,故$ \frac{\partial{s_i}}{\partial{W}} $能够继续展开
∂ s i ∂ W = ∂ s i ∂ W + ∂ s i ∂ s i − 1 ∂ s i − 1 ∂ W + ∂ s i ∂ s i − 1 ∂ s i − 1 ∂ s i − 2 ∂ s i − 2 ∂ W + . . . \frac{\partial{s_i}}{\partial{W}} = \frac{\partial{s_i}}{\partial{W}} + \frac{\partial{s_i}}{\partial{s_{i-1}}} \frac{\partial{s_{i-1}}}{\partial{W}} + \frac{\partial{s_i}}{\partial{s_{i-1}}} \frac{\partial{s_{i-1}}}{\partial{s_{i-2}}} \frac{\partial{s_{i-2}}}{\partial{W}} +... Wsi=Wsi+si1siWsi1+si1sisi2si1Wsi2+...
因此,我们可以将$ \frac{\partial{s_i}}{\partial{W}} $写为如下形式:
∂ E i ∂ W = ∑ i = 0 n ∂ E i ∂ o i ∂ o i ∂ s i ∏ j = i + 1 n ∂ s i ∂ s i − 1 ∂ s i ∂ W \frac{\partial{E_i}}{\partial{W}} = \sum_{i=0}^n \frac{\partial{E_i}}{\partial{o_i}} \frac{\partial{o_i}}{\partial{s_i}} \prod_{j=i+1}^n \frac{\partial{s_i}}{\partial{s_{i-1}}} \frac{\partial{s_i}}{\partial{W}} WEi=i=0noiEisioij=i+1nsi1siWsi

  • ∏ j = i + 1 n ∂ s j ∂ s j − 1 \prod_{j=i+1}^n \frac{\partial{s_j}}{\partial{s_{j-1}}} j=i+1nsj1sj处,若不采用任何激活函数将变成 n − j − 1 n-j-1 nj1 W W W 连乘,将导致梯度的爆炸或消失(具体是哪种情况取决于 W W W 的大小)。但添加激活函数不能够完全避免这个问题。

五、pytroch RNN代码实现

import torch
import numpy as np
import matplotlib.pyplot as plt
from torch import nn

# 定义RNN模型(可以类别下方RNN简单测试代码理解)
class Rnn(nn.Module):
    def __init__(self, input_size):
        super(Rnn, self).__init__()
        # 定义RNN网络
        ## hidden_size是自己设置的,貌似取值都是32,64,128这样来取值
        ## num_layers是隐藏层数量,超过2层那就是深度循环神经网络了
        self.rnn = nn.RNN(
                input_size=input_size,
                hidden_size=32,
                num_layers=1,
                batch_first=True  # 输入形状为[批量大小, 数据序列长度, 特征维度]
                )
        # 定义全连接层
        self.out = nn.Linear(32, 1)

    # 定义前向传播函数
    def forward(self, x, h_0):
        r_out, h_n = self.rnn(x, h_0)
        # print("数据输出结果;隐藏层数据结果", r_out, h_n)
        # print("r_out.size(), h_n.size()", r_out.size(), h_n.size())
        outs = []
        # r_out.size=[1,10,32]即将一个长度为10的序列的每个元素都映射到隐藏层上
        for time in range(r_out.size(1)):  
            # print("映射", r_out[:, time, :])
            # 依次抽取序列中每个单词,将之通过全连接层并输出.r_out[:, 0, :].size()=[1,32] -> [1,1]
            outs.append(self.out(r_out[:, time, :])) 
            # print("outs", outs)
        # stack函数在dim=1上叠加:10*[1,1] -> [1,10,1] 同时h_n已经被更新
        return torch.stack(outs, dim=1), h_n 

TIME_STEP = 10
INPUT_SIZE = 1
LR = 0.02
model = Rnn(INPUT_SIZE)
print(model)

# 此处使用的是均方误差损失
loss_func = nn.MSELoss()  
optimizer = torch.optim.Adam(model.parameters(), lr=LR)

h_state = None  # 初始化h_state为None

for step in range(300):
    # 人工生成输入和输出,输入x.size=[1,10,1],输出y.size=[1,10,1]
    start, end = step * np.pi, (step + 1)*np.pi
    # np.linspace生成一个指定大小,指定数据区间的均匀分布序列,TIME_STEP是生成数量
    steps = np.linspace(start, end, TIME_STEP, dtype=np.float32) 
    # print("steps", steps)
    x_np = np.sin(steps)
    y_np = np.cos(steps)
    # print("x_np,y_np", x_np, y_np)
    # 从numpy.ndarray创建一个张量 np.newaxis增加新的维度
    x = torch.from_numpy(x_np[np.newaxis, :, np.newaxis])
    y = torch.from_numpy(y_np[np.newaxis, :, np.newaxis])
    # print("x,y", x,y)

    # 将x通过网络,长度为10的序列通过网络得到最终隐藏层状态h_state和长度为10的输出prediction:[1,10,1]
    prediction, h_state = model(x, h_state)
    h_state = h_state.data  
    # 这一步只取了h_state.data.因为h_state包含.data和.grad 舍弃了梯度
    # print("precision, h_state.data", prediction, h_state)
    # print("prediction.size(), h_state.size()", prediction.size(), h_state.size())
    
    # 反向传播
    loss = loss_func(prediction, y)
    optimizer.zero_grad()
    loss.backward()
    # 更新优化器参数
    optimizer.step()

# 对最后一次的结果作图查看网络的预测效果
plt.plot(steps, y_np.flatten(), 'r-')
plt.plot(steps, prediction.data.numpy().flatten(), 'b-')
plt.show()

六、补充

1、RNN的缺陷

在传递过程中会产生信息缺失,求解过程中有梯度消失的问题

2、双向循环神经网络:

单项循环神经网络只能作为与前一个数据建立连接,双向循环神经网络则可以同后一个数据建立连接。
在这里插入图片描述
记隐藏层中反向计算的权重矩阵为 $ V_i^{‘} (在图中为 (在图中为 (在图中为A_i^{’}$)
则输出的计算方法可写作:
o i = g ( V s i + V i ′ s i ′ + b 2 ) o_i = g(Vs_i + V_i^{'}s_i^{'} + b_2) oi=g(Vsi+Visi+b2)

  • 双向循环正向计算与反向计算方式相同但不共享权重(即正向矩阵记作 V i V_i Vi反向矩阵记作 V i ′ V_i^{'} Vi

3、激活函数的选择

Sigmoid函数

S i g m o i d ( x ) = 1 1 + e − x Sigmoid(x) = \frac{1}{1+e^{-x}} Sigmoid(x)=1+ex1
S i g m o i d Sigmoid Sigmoid 图像如下:

Sigmoid激活函数图像

  • S i g m o i d Sigmoid Sigmoid 的取值在 [ 0 , 1.0 ] [0, 1.0] [0,1.0] 之间。当 s i s_i si 很大或者很小的时候,该函数的导数 $f^{'}(s_i)= f(s_i)(1-f(s_i)) $ 的值都会趋向于0,造成梯度消失的情况。
tanh函数

t a n h ( x ) = e x − e − x e x + e − x tanh(x) = \frac{e^x-e^{-x}}{e^x+e^{-x}} tanh(x)=ex+exexex
tanh及其导函数图像如下:

tanh激活函数图像

  • t a n h tanh tanh 的取值在 [ − 1.0 , 1.0 ] [-1.0, 1.0] [1.0,1.0] 之间; t a n h ′ tanh^{'} tanh 的取值在 [ 0 , 1.0 ] [0, 1.0] [0,1.0] 之间。

加上激活函数后的 ∂ s j ∂ s j − 1 \frac{\partial{s_j}}{\partial{s_{j-1}}} sj1sj
∂ s j ∂ s j − 1 = t a n h ′ ( W s j − 1 + U x j ) W \frac{\partial{s_j}}{\partial{s_{j-1}}} = tanh^{'}(Ws_{j-1} + Ux_j)W sj1sj=tanh(Wsj1+Uxj)W
在添加了tanh激活函数的情况下,仍然无法完全避免梯度爆炸或消失问题。比如当 W W W 过大,即使是 t a n h ′ tanh^{'} tanh 区间最大为1,也会产生梯度爆炸的情况;或者 W W W 也落在 [ − 1.0 , 1.0 ] [-1.0, 1.0] [1.0,1.0] 之间,如果时间步长过长的话,梯度将逐渐消失(趋于0),权重将不再变化。

Relu函数和LRelu函数

R e l u ( x ) = { x ,    x > 0 0 ,    x ≤ 0 Relu(x) = \begin{cases} x,\,\,x>0 \\ 0 ,\,\, x \le 0 \end{cases} Relu(x)={x,x>00,x0
R e l u Relu Relu 图像如下:

Relu激活函数图像

  • R e l u Relu Relu 的取值在 [ 0 , ∞ ) [0, \infty) [0,) 之间。

相较于 S i g m o i d Sigmoid Sigmoid 函数和 T a n h Tanh Tanh 函数,ReLu函数是利用阈值来进行因变量的输出,计算复杂度较低;其非饱和性可以有效地解决梯度消失的问题,提供相对宽的激活边界;单侧抑制(部分为0,使得部分神经元可能输出为0),提供了网络的稀疏表达能力(相当于在本次优化中,一些神经元不参与,进而破坏了全部神经元联动优化的陷阱?,使得模型更加的鲁棒),但这同时也导致了训练过程中神经元死亡的问题即若某神经元梯度一直为负值则该神经元参数将永不更新。在实际训练中,如果学习率(Learning Rate)设置较大,会导致超过一定比例的神经元不可逆死亡,进而参数梯度无法更新,整个训练过程失败。
由于上述问题,设计了 R e L u ReLu ReLu 函数的变体即 L e a k y   R e L u ( L R e L u ) Leaky \,ReLu(LReLu) LeakyReLu(LReLu)
L R e l u ( x ) = { x ,    x > 0 a x ,    x ≤ 0 LRelu(x) = \begin{cases} x,\,\,x>0 \\ ax ,\,\, x \le 0 \end{cases} LRelu(x)={x,x>0ax,x0
L R e l u LRelu LRelu 图像如下:

LRelu激活函数图像

  • x < 0 x<0 x<0 时变为斜率为 a a a 的线性函数,一般 a a a 为一个很小的正常数, 这样既实现了单侧抑制,又保留了部分负梯度信息以致不完全丢失。
  • 将负轴部分斜率a作为网络中一个可学习的参数,进行反向传播训练,与其他含参数网络层联合优化为 P R e L U ( P a r a m e t r i c   R e L U ) PReLU(Parametric\,ReLU) PReLU(ParametricReLU) ;另一个LReLU的变种增加了“随机化”机制,具体地,在训练过程中,斜率a作为一个满足某种分布的随机采样;测试时再固定下来。 R a n d o m   R e L U ( R R e L U ) Random\,ReLU(RReLU) RandomReLU(RReLU) 在一定程度上能起到正则化的作用。(这部分没有查阅更加详细的资料)
  • 28
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值