当前位置:   article > 正文

循环神经网络RNN_rnn unfold

rnn unfold

目录

循环神经网络基本原理

RNN的一般结构

RNN训练算法 - BPTT

RNN的代码实现

实践中经典的RNN

RNN的梯度爆炸和消失问题

LSTM

Gated Recurrent Unit (GRU)

Peephole LSTM

Bi-directional RNN(双向RNN)

Continuous time RNN(CTRNN)


在前面的task学习了全连接神经网络和卷积神经网络,以及它们的训练和使用。他们都只能单独的取处理一个个的输入,前一个输入和后一个输入是完全没有关系的。但是,某些任务需要能够更好的处理序列的信息,即前面的输入和后面的输入是有关系的。比如,当我们在理解一句话意思时,孤立的理解这句话的每个词是不够的,我们需要处理这些词连接起来的整个序列;当我们处理视频的时候,我们也不能只单独的去分析每一帧,而要分析这些帧连接起来的整个序列。这时,就需要用到深度学习领域中另一类非常重要神经网络:循环神经网络(Recurrent Neural Network)

循环神经网络基本原理

循环神经网络是一种人工神经网络,它的节点间的连接形成一个遵循时间序列的有向图,它的核心思想是,样本间存在顺序关系,每个样本和它之前的样本存在关联。通过神经网络在时序上的展开,我们能够找到样本之间的序列相关性。

  • 文本:字母和词汇的序列
  • 语音:音节的序列
  • 视频:图像帧的序列
  • 时态数据:气象观测数据,股票交易数据、房价数据等

RNN的一般结构

6.10

其中各个符号的表示:x_t,s_t,o_t分别表示的是tt时刻的输入、记忆和输出,U,V,W是RNN的连接权重,b_s,b_o是RNN的偏置,\sigma,\varphi是激活函数,\sigma通常选tanh或sigmoid,\varphi通常选用softmax。

我们现在这样来理解,如果把上面有W的那个带箭头的圈去掉,它就变成了最普通的全连接神经网络。x是一个向量,它表示输入层的值(这里面没有画出来表示神经元节点的圆圈);s是一个向量,它表示隐藏层的值(这里隐藏层面画了一个节点,这一层其实是多个节点,节点数与向量s的维度相同);

U是输入层到隐藏层的权重矩阵,o也是一个向量,它表示输出层的值;V是隐藏层到输出层的权重矩阵。

那么,现在我们来看看W是什么。循环神经网络的隐藏层的值s不仅仅取决于当前这次的输入x,还取决于上一次隐藏层的值s。权重矩阵 W就是隐藏层上一次的值作为这一次的输入的权重。

图右unfold是RNN按照时间线的展开,这个网络在t时刻接收到输入x_t 之后,隐藏层的值是 s_t ,输出值是 o_t 。关键一点是, s_t 的值不仅仅取决于x_t ,还取决于s_{t-1} 。我们可以用下面的公式来表示循环神经网络的计算方法:

st=σ(Uxt+Wst1+bs)ot=φ(Vst+bo)

其中 softmax 函数,用于分类问题的概率计算。本质上是将一个K维的任意实数向量压缩 (映射)成另一个K维的实数向量,其中向量中的每个元素取值都介于(0,1)之间。

\sigma(\vec{z})_{i}=\frac{e^{z_{i}}}{\sum_{j=1}^{K} e^{z_{j}}}

RNN训练算法 - BPTT

我们先来回顾一下BP算法,就是定义损失函数 Loss 来表示输出 \hat{y}和真实标签 y的误差,通过链式法则自顶向下求得 Loss 对网络权重的偏导。沿梯度的反方向更新权重的值, 直到 Loss 收敛。而这里的 BPTT 算法就是加上了时序演化,后面的两个字母 TT 就是 Through Time。

6.17

我们先定义输出函数:

\begin{array}{l}s_{t}=\tanh \left(U x_{t}+W s_{t-1}\right) \\ \hat{y}_{t}=\operatorname{softmax}\left(V s_{t}\right)\end{array}

再定义损失函数:

\begin{aligned} E_{t}\left(y_{t}, \hat{y}_{t}\right) =-y_{t} \log \hat{y}_{t} \\ E(y, \hat{y}) =\sum_{t} E_{t}\left(y_{t}, \hat{y}_{t}\right) \\ =-\sum_{t} y_{t} \log \hat{y}_{t}\end{aligned}

6.18

以下为复合函数的链式求导过程

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

  1. 前向计算每个神经元的输出值;
  2. 反向计算每个神经元的误差项值,它是误差函数E对神经元j的加权输入的偏导数;
  3. 计算每个权重的梯度。

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

RNN的代码实现

完整代码参考GitHub:learn_dl/rnn.py at master · hanbt/learn_dl · GitHub

  1. import numpy as np
  2. from cnn import element_wise_op
  3. from activators import ReluActivator, IdentityActivator
  4. '''我们用RecurrentLayer类来实现一个循环层。下面的代码是初始化一个循环层,可以在构造函数中设置卷积层的超参数。我们注意到,循环层有两个权重数组,U和W
  5. '''
  6. class RecurrentLayer(object):
  7. def __init__(self, input_width, state_width,
  8. activator, learning_rate):
  9. self.input_width = input_width
  10. self.state_width = state_width
  11. self.activator = activator
  12. self.learning_rate = learning_rate
  13. self.times = 0 # 当前时刻初始化为t0
  14. self.state_list = [] # 保存各个时刻的state
  15. self.state_list.append(np.zeros(
  16. (state_width, 1))) # 初始化s0
  17. self.U = np.random.uniform(-1e-4, 1e-4,
  18. (state_width, input_width)) # 初始化U
  19. self.W = np.random.uniform(-1e-4, 1e-4,
  20. (state_width, state_width)) # 初始化W
  21. '''在forward方法中,实现循环层的前向计算,这部分比较简单。'''
  22. def forward(self, input_array):
  23. '''
  24. 根据『式2』进行前向计算
  25. '''
  26. self.times += 1
  27. state = (np.dot(self.U, input_array) +
  28. np.dot(self.W, self.state_list[-1]))
  29. element_wise_op(state, self.activator.forward)
  30. self.state_list.append(state)
  31. '''在backword方法中,实现BPTT算法。'''
  32. def backward(self, sensitivity_array,
  33. activator):
  34. '''
  35. 实现BPTT算法
  36. '''
  37. self.calc_delta(sensitivity_array, activator)
  38. self.calc_gradient()
  39. '''在update方法中,实现梯度下降算法'''
  40. def update(self):
  41. '''
  42. 按照梯度下降,更新权重
  43. '''
  44. self.W -= self.learning_rate * self.gradient
  45. def calc_delta(self, sensitivity_array, activator):
  46. self.delta_list = [] # 用来保存各个时刻的误差项
  47. for i in range(self.times):
  48. self.delta_list.append(np.zeros(
  49. (self.state_width, 1)))
  50. self.delta_list.append(sensitivity_array)
  51. # 迭代计算每个时刻的误差项
  52. for k in range(self.times - 1, 0, -1):
  53. self.calc_delta_k(k, activator)
  54. def calc_delta_k(self, k, activator):
  55. '''
  56. 根据k+1时刻的delta计算k时刻的delta
  57. '''
  58. state = self.state_list[k+1].copy()
  59. element_wise_op(self.state_list[k+1],
  60. activator.backward)
  61. self.delta_list[k] = np.dot(
  62. np.dot(self.delta_list[k+1].T, self.W),
  63. np.diag(state[:,0])).T
  64. def calc_gradient(self):
  65. self.gradient_list = [] # 保存各个时刻的权重梯度
  66. for t in range(self.times + 1):
  67. self.gradient_list.append(np.zeros(
  68. (self.state_width, self.state_width)))
  69. for t in range(self.times, 0, -1):
  70. self.calc_gradient_t(t)
  71. # 实际的梯度是各个时刻梯度之和
  72. self.gradient = reduce(
  73. lambda a, b: a + b, self.gradient_list,
  74. self.gradient_list[0]) # [0]被初始化为0且没有被修改过
  75. def calc_gradient_t(self, t):
  76. '''
  77. 计算每个时刻t权重的梯度
  78. '''
  79. gradient = np.dot(self.delta_list[t],
  80. self.state_list[t-1].T)
  81. self.gradient_list[t] = gradient
  82. '''循环层是一个带状态的层,每次forword都会改变循环层的内部状态,这给梯度检查带来了麻烦。因此,我们需要一个reset_state方法,来重置循环层的内部状态'''
  83. def reset_state(self):
  84. self.times = 0 # 当前时刻初始化为t0
  85. self.state_list = [] # 保存各个时刻的state
  86. self.state_list.append(np.zeros(
  87. (self.state_width, 1))) # 初始化s0
  88. def data_set():
  89. x = [np.array([[1], [2], [3]]),
  90. np.array([[2], [3], [4]])]
  91. d = np.array([[1], [2]])
  92. return x, d
  93. '''最后,是梯度检查的代码'''
  94. def gradient_check():
  95. '''
  96. 梯度检查
  97. '''
  98. # 设计一个误差函数,取所有节点输出项之和
  99. error_function = lambda o: o.sum()
  100. rl = RecurrentLayer(3, 2, IdentityActivator(), 1e-3)
  101. # 计算forward值
  102. x, d = data_set()
  103. rl.forward(x[0])
  104. rl.forward(x[1])
  105. # 求取sensitivity map
  106. sensitivity_array = np.ones(rl.state_list[-1].shape,
  107. dtype=np.float64)
  108. # 计算梯度
  109. rl.backward(sensitivity_array, IdentityActivator())
  110. # 检查梯度
  111. epsilon = 10e-4
  112. for i in range(rl.W.shape[0]):
  113. for j in range(rl.W.shape[1]):
  114. rl.W[i,j] += epsilon
  115. rl.reset_state()
  116. rl.forward(x[0])
  117. rl.forward(x[1])
  118. err1 = error_function(rl.state_list[-1])
  119. rl.W[i,j] -= 2*epsilon
  120. rl.reset_state()
  121. rl.forward(x[0])
  122. rl.forward(x[1])
  123. err2 = error_function(rl.state_list[-1])
  124. expect_grad = (err1 - err2) / (2 * epsilon)
  125. rl.W[i,j] += epsilon
  126. print 'weights(%d,%d): expected - actural %f - %f' % (
  127. i, j, expect_grad, rl.gradient[i,j])
  128. def test():
  129. l = RecurrentLayer(3, 2, ReluActivator(), 1e-3)
  130. x, d = data_set()
  131. l.forward(x[0])
  132. l.forward(x[1])
  133. l.backward(d, ReluActivator())
  134. return l

实践中经典的RNN

RNN的梯度爆炸和消失问题

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

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

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

  1. 合理的初始化权重值。初始化权重,使每个神经元尽可能不要取极大或极小值,以躲开梯度消失的区域
  2. 使用relu代替sigmoid和tanh作为激活函数
  3. 使用其他结构的RNNs,比如长短时记忆网络(LTSM)和Gated Recurrent Unit(GRU),这是最流行的做法

LSTM

LSTM,即长短时记忆网络,于1997年被Sepp Hochreiter 和Jürgen Schmidhuber提出来,LSTM是一种用于深度学习领域的人工循环神经网络(RNN)结构。一个LSTM单元由输入门、输出门和遗忘门组成,三个门控制信息进出单元。

6.20

  • LSTM依靠贯穿隐藏层的细胞状态实现隐藏单元之间的信息传递,其中只有少量的线性操作
  • LSTM引入了“门”机制对细胞状态信息进行添加或删除,由此实现长程记忆
  • “门”机制由一个Sigmoid激活函数层和一个向量点乘操作组成,Sigmoid层的输出控制了信息传递的比例

Gated Recurrent Unit (GRU)

Gated Recurrent Unit (GRU),是在2014年提出的,可认为是LSTM 的变种,它的细胞状态与隐状态合并,在计算当前时刻新信息的方法和LSTM有 所不同;GRU只包含重置门和更新门;在音乐建模与语音信号建模领域与LSTM具有相似的性能,但是参数更少,只有两个门控。

6.19

Peephole LSTM

让门层也接受细胞状态的输入,同时考虑隐层信息的输入。

6.25

Bi-directional RNN(双向RNN)

Bi-directional RNN(双向RNN)假设当前t的输出不仅仅和之前的序列有关,并且还与之后的序列有关,例如:完形填空,它由两个RNNs上下叠加在一起组成,输出由这两个RNNs的隐藏层的状态决定。

6.26

6.27

Continuous time RNN(CTRNN)

CTRNN利用常微分方程系统对输入脉冲序列神经元的影响 进行建模。CTRNN被应用到进化机器人中,用于解决视觉、协作和最 小认知行为等问题。

6.28

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/579411
推荐阅读
相关标签
  

闽ICP备14008679号