zoukankan      html  css  js  c++  java
  • 【机器学习笔记】循环神经网络RNN


    1. 从一个栗子开始 - Slot Filling

    比如在一个订票系统上,我们的输入 “Arrive Taipei on November 2nd” 这样一个序列,我们设置几个槽位(Slot),希望算法能够将关键词'Taipei'放入目的地(Destination)槽位, 将November和2nd放入到达时间(Time of Arrival)槽位,将Arrive和on放入其他(Other)槽位,实现对输入序列的一个归类,以便后续提取相应信息。

    用前馈神经网络(Feedforward Neural Network)来解决这个问题的话,我们首先要对输入序列向量化,将每一个输入的单词用向量表示,可以使用 One-of-N Encoding 或者是 Word hashing 等编码方法,输出预测槽位的概率分布。

    但是这样做的话,有个问题就出现了。如果现在又有一个输入是 “Leave Taipei on November 2nd”,这里Taipei是作为一个出发地(Place of Departure),所以我们应当是把Taipei放入Departure槽位而不是Destination 槽位,可是对于前馈网络来说,对于同一个输入,输出的概率分布应该也是一样的,不可能出现既是Destination的概率最高又是Departure的概率最高。

    所以我们就希望能够让神经网络拥有“记忆”的能力,能够根据之前的信息(在这个例子中是Arrive或Leave)从而得到不同的输出。将两段序列中的Taipei分别归入Destionation槽位和Departure槽位。


    2. RNN

    • 基本概念

      在RNN中,隐层神经元的输出值都被保存到记忆单元中,下一次再计算输出时,隐层神经元会将记忆单元中的值认为是输入的一部分来考虑

      RNN中考虑了输入序列顺序,序列顺序的改变会影响输出的结果。

    • 常见变体

      • Elman Network
        将隐层的输出(即记忆单元中的值)作为下一次的输入

      (h_t = sigma_h(W_hx_t + U_hcolor{green}{h_{t-1}} + b_h))

      (y_t = sigma_h(W_yh_t + b_y))

      • Jordan Network
        将上一时间点的输出值作为输入

      (h_t = sigma_h(W_hx_t + U_hcolor{green}{y_{t-1}} + b_h))

      (y_t = sigma_h(W_yh_t + b_y))

      • Bidirectional RNN

    3. Long Short-term Memory (LSTM)

    enter image description here

    • 基本结构

      • 由Memory Cell, Input Gate, Output Gate, Forget Gate 组成
      • 特殊的神经元结构,包含4个input(三个Gate的控制信号以及输入的数据),1个output
      • 激活函数通常选用sigmoid function, sigmoid的输出介于0到1之间,表征了Gate的打开程度。
    • Traditional LSTM

    [egin{align} f_t & = sigma_g(W_fx_t + color{green}{U_fh_{t-1}} + b_f) \ i_t & = sigma_g(W_i x_t + color{green}{U_ih_{t-1}} + b_i) \ o_t & = sigma_g(W_o x_t + color{green}{U_oh_{t-1}} + b_o) \ c_t & = f_t\,{circ}\,c_{t-1} + i_t\,{circ}\,sigma_c(W_cx_tcolor{green}{+ U_ch_{t-1}} +b_c) \ h_t & = o_t \,{circ}\, sigma_h(c_t) end{align} ]

    • Peephole LSTM, 在大部分的情况下,用(color{blue}{c_{t-1}})取代(color{green}{h_{t-1}})

    [egin{align} f_t & = sigma_g(W_fx_t + color{green}{U_fcolor{blue}{c_{t-1}}} + b_f) \ i_t & = sigma_g(W_i x_t + color{green}{U_icolor{blue}{c_{t-1}}} + b_i) \ o_t & = sigma_g(W_o x_t + color{green}{U_ocolor{blue}{c_{t-1}}} + b_o) \ c_t & = f_t\,{circ}\,c_{t-1} + i_t\,{circ}\,sigma_c(W_cx_t +b_c) \ h_t & = o_t \,{circ}\, sigma_h(c_t) end{align} ]

    - $x_t$表示输入向量,$h_t$表示输出向量,$c_t$表示记忆单元的状态向量,$circ$代表Hadamard product(A.k.a. Schur product)
    - $W$表示输入权重,$U$表示循环权重,$b$表示偏置
    - $delta_g$代表sigmoid function,$delta_c$代表hyperbolic tangent, $delta_h$表示 hyperbolic tangent(peephole LSTM论文中建议选用$delta_h(x)=x$)
    - $f_t$,$i_t$和$o_t$表示门控向量值		
        - $f_t$表示遗忘门向量,表征记忆旧信息的能力
    	- $i_t$表示输入门向量,表征获取新信息的能力
    	- $o_t$表示输出门向量,表征输出信息的能力
    
    • 补充知识点
      • Short-term,表示保留对前一时间点输出的短期记忆,相比于最原始的RNN结构中的记忆单元(每次有新的输入时记忆体的状态就会被更新,因此是短期的记忆),而LSTM的记忆体则拥有相对较长的记忆时间(由Forget Gate决定),所以是Long Short-term
      • LSTM一般采用多层结构组合,Multiple-layer LSTM
      • Keras中实现了LSTM,GRU([Cho,Learning Phrase Representations using RNN Encoder-Decoder for Statistical Machine Translation,EMNLP'14] 只有两个Gate,容易训练),SimpleRNN层,可以方便的调用。

    4. RNN如何学习?

    • 损失函数的定义:

      • 每一个时间点的RNN的输出和标签值的交叉熵(cross-entropy)之和
    • 训练过程:

      • 使用被称作Backpropagation through time(BPTT)的梯度下降法
      • 训练其实是比较困难的,因为Total Loss可能会出现剧烈的抖动
        rnn training
      • 根据论文[Razvan Pascanu,On the difficulty of training Recurrent Neural Networks,ICML'13]上对RNN的分析,损失函数的表面要么非常平坦,要么非常陡峭(The error surface is either very flat or very steep),当你的参数值在较为平坦的区域做更新时,因此该区域梯度值比较小,此时的学习率一般会变得的较大,如果突然到达了陡峭的区域,梯度值陡增,再与此时较大的学习率相乘,参数就有很大幅度更新(实线表示的轨迹),因此学习过程非常不稳定。Razvan Pascanu使用了叫做“Clipping”的训练技巧:为梯度设置阈值,超过该阈值的梯度值都会被cut,这样参数更新的幅度就不会过大(虚线表示的轨迹),因此容易收敛。
    • 为什么在RNN中会有这种问题?

      • 是因为激活函数选用了sigmoid而不是ReLU么?然而并不是。事实上,在RNN中使用ReLU反而效果会不如Sigmoid,不过也是看你的参数初始化值的选取,所以也不一定,比如后面提到的Quoc V.Le的那篇文章,使用特别初始化技巧硬训ReLU的RNN得到了可比拟LSTM的效果。因此激活函数并不是这里的关键点。
      • 那究竟是什么原因呢?我们来分析梯度更新公式中的(w-etafrac{partial{L}}{partial{w}})来探寻一番。但是这样一个偏微分的关系我们应该如何来分析呢?这里我们用一个技巧:给w值一个微小的变化,观察对应的Loss的变化情况。假设当前模型是1000个只含有一个线性隐层的RNN级联结构。并假设我们当前的输入是100000……(只有第一个值是1,剩下全是0),因此最后的输出值是(w^{999})。现在假设我们(w)的值是1,那么RNN在最后时间点的输出是1,给(w)一个微小的变化+0.01,此时的输出变成了大约20000!这段区域呈现出一个陡峭的趋势。如果给(w)一个微小的变化-0.01变为0.99,测试的输出基本变成0,哪怕是(w)变到0.01时,输出依旧是0,这段区域呈现出一个平坦的趋势。因此我们可以看出由于RNN采用时间序列的结构,权重值在不同时间点被反复使用,这种累积性的变化可能对结果造成极大的影响,也可能会很长一段时间保持平稳。
    • 常用的技巧


    5. RNN的更多应用场景


    6. 其他的学习资料


    7. 本文参考资料

    Deep & Structured 未完待续

  • 相关阅读:
    JS判断鼠标移入元素的方向
    EJB开发第一个无状态会话bean、开发EJBclient
    Android摇一摇振动效果Demo
    吃饭与团队惬意
    Factorization Machines 学习笔记(三)回归和分类
    代理---视图间数据的传递:标签显示输入的内容【多个视图中】
    cocos2d-x v3.2 FlappyBird 各个类对象详细代码分析(7)
    金典 SQL笔记(4)
    用GDB调试多进程程序
    C程序设计的抽象思维-算法分析-大多数元素
  • 原文地址:https://www.cnblogs.com/surfzjy/p/6715150.html
Copyright © 2011-2022 走看看