zoukankan      html  css  js  c++  java
  • 循环神经网络(RNN)原理

    我们之前所学的全连接神经网络(DNN)和卷积神经网络(CNN),他们的前一个输入和后一个输入是没有关系的。但是当我们处理序列信息的时候,某些前面的输入和后面的输入是有关系的,比如:当我们在理解一句话意思时,孤立的理解这句话的每个词是不够的,我们需要处理这些词连接起来的整个序列;这个时候我们就需要使用到循环神经网络(Recurrent Neural Network)

    RNN在自然语言处理领域最先被使用起来,RNN可以为语言模型进行建模:

    我没有完成上级布置给我的任务,所以  被开除了

    让电脑来填写下划线的词最有可能的是『我』,而不太可能是『小明』,甚至是『吃饭』。

    语言模型就是这样的东西:给定一个一句话前面的部分,预测接下来最有可能的一个词是什么。

    基本循环神经网络

    基本循环神经网络结构:一个输入层、一个隐藏层和一个输出层。

      $x$是输入层的值。$s$表示隐藏层的值,$U$是输入层到隐藏层的权重矩阵,$O$是输出层的值。$V$是隐藏层到输出层的权重矩阵。循环神经网络的隐藏层的值s不仅仅取决于当前这次的输入x,还取决于上一次隐藏层的值s权重矩阵$W$就是隐藏层上一次的值作为这一次的输入的权重。

      这个网络在t时刻接收到输入$X_t$之后,隐藏层的值是$S_t$,输出值是$O_t$。关键一点是,$S_t$的值不仅仅取决于$X_t$,还取决于$S_{t-1}$

    $$egin{align}
    mathrm{o}_t&=g(Vmathrm{s}_t)qquadquad(式1)\
    mathrm{s}_t&=f(Umathrm{x}_t+Wmathrm{s}_{t-1})qquad(式2)\
    end{align}$$

      式1是输出层的计算公式,输出层是一个全连接层,也就是它的每个节点都和隐藏层的每个节点相连。V是输出层的权重矩阵,g是激活函数

      式2是隐藏层的计算公式,它是循环层。U是输入x的权重矩阵,W是上一次的值$S_{t-1}$作为这一次的输入的权重矩阵,f是激活函数

      隐含层有两个输入,第一是$U$与$X_t$向量的乘积,第二是上一隐含层输出的状态$S(t-1)$和$W$的乘积。等于前一次计算输出的$S(t-1)$需要缓存一下,在本次输入$X_t$一起计算,共同输出最后的$O$。

    如果反复把式2带入式1,我们将得到:

    $$egin{align} mathrm{o}_t&=g(Vmathrm{s}_t)\ &=Vf(Umathrm{x}_t+Wmathrm{s}_{t-1})\ &=Vf(Umathrm{x}_t+Wf(Umathrm{x}_{t-1}+Wmathrm{s}_{t-2}))\ &=Vf(Umathrm{x}_t+Wf(Umathrm{x}_{t-1}+Wf(Umathrm{x}_{t-2}+Wmathrm{s}_{t-3})))\ &=Vf(Umathrm{x}_t+Wf(Umathrm{x}_{t-1}+Wf(Umathrm{x}_{t-2}+Wf(Umathrm{x}_{t-3}+...)))) end{align}$$

    从上面可以看出,循环神经网络的输出值$O_t$,是受前面历次输入值$X_t、X_{t-1}、X_{t-2}、X_{t-3}、...$影响的,这就是为什么循环神经网络可以往前看任意多个输入值的原因。这样其实不好,因为如果太前面的值和后面的值已经没有关系了,循环神经网络还考虑前面的值的话,就会影响后面值的判断。

    双向循环神经网络

    对于语言模型来说,很多时候光看前面的词是不够的,比如下面这句话:

    我的手机坏了,我打算  一部新的手机。

    我们这个时候就需要双向循环神经网络。

    从上图可以看出,双向卷积神经网络的隐藏层要保存两个值,一个A参与正向计算,另一个值A'参与反向计算。最终的输出值$y_2$取决于$A_2$和${A}'_2$。其计算方法为:

    $$mathrm{y}_2=g(VA_2+V'A_2')$$

    $A_2$和${A}'_2$则分别计算:

    $$egin{align}
    A_2&=f(WA_1+Umathrm{x}_2)\
    A_2'&=f(W'A_3'+U'mathrm{x}_2)\
    end{align}$$

    现在,我们已经可以看出一般的规律:正向计算时,隐藏层的值$S_t$与$S_{t-1}$有关;反向计算时,隐藏层的值${S}'_t$与${S}'_{t+1}$有关;最终的输出取决于正向和反向计算的加和。现在,我们仿照式1式2,写出双向循环神经网络的计算方法:

    $$egin{align}
    mathrm{o}_t&=g(Vmathrm{s}_t+V'mathrm{s}_t')\
    mathrm{s}_t&=f(Umathrm{x}_t+Wmathrm{s}_{t-1})\
    mathrm{s}_t'&=f(U'mathrm{x}_t+W'mathrm{s}_{t+1}')\
    end{align}$$

    从上面三个公式我们可以看到,正向计算和反向计算不共享权重,也就是说U和U'、W和W'、V和V'都是不同的权重矩阵。

    深度循环神经网络

    循环神经网络的训练算法:BPTT

    BPTT算法是针对循环层的训练算法,它的基本原理和BP算法是一样的,也包含同样的三个步骤:

    1、前向计算每个神经元的输出值;

    2、反向计算每个神经元的误差项$delta_j$值,它是误差函数E对神经元j的加权输入$net_j$的偏导数;

    3、计算每个权重的梯度。

    4、最后用随机梯度下降算法更新权重。

    循环层如下图所示:

    前向计算

    使用前面的式2对循环层进行前向计算:

    $$mathrm{s}_t=f(Umathrm{x}_t+Wmathrm{s}_{t-1})$$

    我们假设输入向量x的维度是m,输出向量s的维度是n,则矩阵U的维度是$n$ x $m$,矩阵W的维度是$n$ x $n$。下面是上式展开矩阵的样子,看起来会直观一些:

    $$egin{align}egin{bmatrix}
    s_1^t\s_2^t\.\.\s_n^t\
    end{bmatrix}=f(egin{bmatrix}
    u_{11} u_{12} ... u_{1m}\
    u_{21} u_{22} ... u_{2m}\
    .\.\u_{n1} u_{n2} ... u_{nm}\
    end{bmatrix}egin{bmatrix}
    x_1\x_2\.\.\x_m\
    end{bmatrix}+egin{bmatrix}
    w_{11} w_{12} ... w_{1n}\
    w_{21} w_{22} ... w_{2n}\
    .\.\w_{n1} w_{n2} ... w_{nn}\
    end{bmatrix}egin{bmatrix}
    s_1^{t-1}\s_2^{t-1}\.\.\s_n^{t-1}\
    end{bmatrix})end{align}$$

    误差项的计算

    BPTT算法将第I层t时刻的误差项$delta_t^l$值沿两个方向传播,一个方向是其传递到上一层网络,得到$delta_t^{l-1}$,这部分只和权重矩阵U有关;另一个方向是将其沿时间线传递到初始$t_1$时刻,得到$delta_1^l$,这部分只和权重矩阵W有关。

    由于推导过程十分复杂,本博客不打算推导。如有兴趣可以查看这篇文章零基础入门深度学习(5)-循环神经网络

    权重梯度的计算

    由于推导过程十分复杂,本博客不打算推导。如有兴趣可以查看这篇文章零基础入门深度学习(5)-循环神经网络

    RNN的梯度爆炸和消失问题

    实践中前面介绍的几种RNNs并不能很好的处理较长的序列,RNN在训练中很容易发生梯度爆炸梯度消失,这导致梯度不能在较长序列中一直传递下去,从而使RNN无法捕捉到长距离的影响。

    通常来说,梯度爆炸更容易处理一些。因为梯度爆炸的时候,我们的程序会收到NaN错误。我们也可以设置一个梯度阈值,当梯度超过这个阈值的时候可以直接截取。

    梯度消失更难检测,而且也更难处理一些。总的来说,我们有三种方法应对梯度消失问题:

    1、合理的初始化权重值。初始化权重,使每个神经元尽可能不要取极大或极小值,以躲开梯度消失的区域。

    2、使用relu代替sigmoid和tanh作为激活函数。原理请参考上一篇文章零基础入门深度学习(4) - 卷积神经网络的激活函数一节。

    3、使用其他结构的RNNs,比如长短时记忆网络(LTSM)和Gated Recurrent Unit(GRU),这是最流行的做法。我们将在以后的文章中介绍这两种网络。

    RNN的应用举例——基于RNN的语言模型

    我们来介绍一下RNN语言模型,我们首先把词依次输入到循环神经网络中,每输入一个词,循环神经网络就输出截止到目前为止下一个最可能的词。例如,当我们依次输入:

    我 昨天 上学 迟到 了

    神经网络的输出如下图所示:

      其中,s和e是两个特殊的词,分别表示一个序列的开始和结束。

    向量化

    为了让语言模型能够被神经网络处理,我们必须把词表达为向量形式,这样神经网络才能处理。所以我们需要把“词”进行向量化

    1、建立一个包含所有词的词典,每个词在词典里面有一个唯一的编码。

    2、任意一个词都可以用一个N维的one-hot向量来表示(其中N是词典中词的总个数)。假设一个词在词典中的编号是i,v是表示这个词的向量,$V_j$是向量的第j个元素,则:

    $$v_j=egin{equation}egin{cases}1qquad j=i\0qquad j e iend{cases}end{equation}$$

    上面这个公式的含义,可以用下面的图来直观的表示:

    但是处理这种向量会导致我们的神经网络有很多参数,带来庞大的计算量。因此往往需要使用一些降维方法。

    语言模型要求输出的是下一个最可能的词,我们可以让循环神经网络计算词典中每个词是下一个词的概率,概率最大的词就是下一个最后的词。输出向量是一个N维向量,向量中的每个元素对应着词典中相应的词是下一个词的概率。

    Softmax层

    语言模型时对下一个词出现的概率进行建模,让神经网络输出概率的方法就是用Softmax层作为神经网络的输出层。Softmax作为激活函数:1、每一项为取值为0-1之间的正数;2、所有项的总和是1。

    $$g(z_i)=frac{e^{z_i}}{sum_{k}e^{z_k}}$$

    语言模型的训练

    首先我们把语料“我 昨天 上学 迟到 了”转换成语言模型的训练数据集。我们获取输入-标签对:

    输入

    标签

    S

    昨天

    昨天

    上学

    上学

    迟到

    迟到

    e

    对输入x和标签y进行向量化得到one-hot向量。向量中只有一个元素的值是1,其他元素都是0。

    最后我们使用交叉熵误差函数作为优化目标,对模型进行优化。

    交叉熵误差

    当激活函数是softmax时,对应的误差函数E通常选择交叉熵误差函数,其定义如下:

    $$L(y,o)=-frac{1}{N}sum_{nin{N}}{y_nlogo_n}$$

    在上式子中,N是训练样本的个数,向量$y_n$是样本的标记,向量$O_n$是网络的输出。标记$y_n$是一个one-hot向量,例如:$y_1=[1,0,0,0]$,如果网络的输出$O=[0.03,0.09,0.24,0.64]$,那么交叉熵是(假设只有一个训练样本,即N=1):

    $$egin{align}
    L&=-frac{1}{N}sum_{nin{N}}{y_nlogo_n}\
    &=-y_1logo_1\
    &=-(1*log0.03+0*log0.09+0*log0.24+0*log0.64)\
    &=3.51
    end{align}$$

    RNN的实现

    由于用TensorFlow和Keras实现RNN模型篇幅过长,我分成了两篇文章去写:

    TensorFlow中实现RNN,彻底弄懂time_step

    Keras实现RNN模型

    RNN项目

    参考文献

    零基础入门深度学习(5)--循环神经网络

  • 相关阅读:
    线程池进程池
    设计原则与设计模式
    腾讯阿里第三方接入
    计划任务
    系统服务
    Python Faker模块
    Python openpyxl模块
    Python-docx模块
    进程管理
    磁盘管理
  • 原文地址:https://www.cnblogs.com/LXP-Never/p/10391308.html
Copyright © 2011-2022 走看看