前言
这里的目标是 循环神经网络(RNN)。
总结一下目前的学习情况:
不使用任何的深度学习框架,仅使用 Numpy、Math 等数学计算工具
学习内容 | 状态 |
---|---|
类似 Pytorch 的 DataLoader | 已完成 |
深度神经网络(DNN)- 交通量预测 与 鸢尾花分类 | 已完成 |
卷积神经网络(CNN)- MNIST分类 | 已完成 |
Sigmoid、Tanh、Relu、Leaky-Relu | 已完成 |
L1Loss、L2Loss、SmoothL1Loss | 已完成 |
多分类网络,Softmax + 交叉熵损失 | 已完成 |
One-Hot 编码 | 已完成 |
循环神经网络(RNN) | 已完成 |
长短期记忆网络(LSTM) | 未完成 |
K均值聚类(K-means) | — |
主成分分析(PCA) | — |
Hog 特征提取 | — |
SVM 分类器 | — |
Region with CNN Feature(R-CNN) | — |
You Only Look Once(YOLO) | — |
目前为止已经完成了 DNN、CNN、RNN 三种基础神经网络的数学推理和算法实现,至于后面的 LSTM、R-CNN 等,都基本是在这三种基础神经网络上,通过不断改善方法、组合多种机制实现的,例如:LSTM 中的门结构、细胞状态,R-CNN 中的候选框、Hog特征提取等。后面的就基本学不到头了,只能有针对性地对这些经典神经网络的中特色部分进行学习。
概述
循环神经网络(RNN) 是一类以 序列数据 为输入,在序列的演进方向进行递归且所以 循环单元 按链式连接的递归神经网络。1
之前完成的 深度神经网络(DNN)、卷积神经网络(CNN),网络的输入节点或输出节点的数量是固定的,因此无法处理 序列较长且长度不一 的数据,例如:一段语音、一段文字等。对这些问题就是 循环神经网络 所擅长的。23
网络结构
最主流的 RNN 模型可以参考下图:
整个 RNN 模型可以看作是多个循环结构的组合,这里可以从 单个模型 和 组合模型 两个角度来解析模型。
对于 单个模型 而言,它需要包含以下内容( 为模型的序列索引号):
- 代表在序列索引号为 的模型的输入。
- 是某个固定长度的数据序列
- 与 之间不需要存在某种连续的关系,例如:时间、序列等
- 代表在序列索引号为 的模型的净输入。
- 由 和 共同决定
- 代表在序列索引号为 的模型的激活后输出(隐藏状态)。
- 代表在序列索引号为 的模型的输出。
- 和 间的关系相当于全连接层
- 代表在序列索引号为 的模型的损失。
- 代表在序列索引号为 的模型的真实输出。
- 、、 分别是计算 时 和 的权重,以及计算 时 的权重。
- 它们在 RNN 的同一层模型中是共享的,体现了RNN的模型的“循环反馈”的思想
对于 组合模型 而言:
- 作为单个模型的输入是固定长度的,组合模型的输入是不定长的,将不定长输入分割为多个 长度的输入,然后依次输入单个模型中,组合模型的头尾是相连接的。
- 隐藏状态 并不像上图,只有1层,可能有多层,但循环只发生在每层的隐藏状态之间。
前向传播
对于任意一个序列索引号 ,隐藏状态 由 和 得到:
和 分别构成两个全连接,并将结果进行叠加。
其中 b 是线性关系的偏置,同时由于 RNN 是首尾相连进行循环,当 时, 是指最后一个模型的隐藏状态
序列索引号为 的模型的激活后输出 的表达式:
其中 是模型的激活函数,一般为 Tanh
序列索引号为 的模型的输出 的表达式:
最总序列索引号为 的模型的预测输出为:
一般矩阵乘积 = 哈达马积 = 克罗内克积 =
反向传播
对于 RNN,我们在所有的输出节点都有损失,所有损失的表达式为:
是序列索引号为 模型的输出损失(梯度) 输出损失的计算需要按数据输入的顺序进行
对于 、 梯度计算的表达式:
由于输出层 只是一层全连接层,只是由于输入和隐藏状态的实现,所有输出层的输出损失需要进行累加,其表达式也较为简单,比较复杂的是隐藏层梯度的计算。
由于 是由 和 决定的,所以隐藏状态的梯度 需要从 往 方向进行推导(前向传播的倒序):
这里在代码实现时有一个坑,生成对角矩阵:np.diag(aray) 4 array是一个1维数组时,结果形成一个以一维数组为对角线元素的矩阵 array是一个n维矩阵时,结果输出矩阵的对角线元素 由于 是一个 的矩阵,所以无法生成对应的对角矩阵,需要先通过 reshape 将 转换成
这里还有一个问题就是,隐藏状态的序列索引号为 的输出损失(即最后时刻的输出损失),它后面已经没有其他序列索引了,所以只需要按当前时刻计算:
关于为什么会使用到对角矩阵,由于 和 都是 的向量,向量对向量求导会形成 雅可比矩阵:
$$ \frac{\partial h_{t+1}}{\partial z_{t+1}} = \begin{pmatrix} & \frac{\partial h^{t+1}_1}{\partial z^{t+1}_1} & \frac{\partial h^{t+1}_1}{\partial z^{t+1}_2} & \cdots & \frac{\partial h^{t+1}_1}{\partial z^{t+1}_N} \\\\ & \frac{\partial h^{t+1}_2}{\partial z^{t+1}_1} & \frac{\partial h^{t+1}_2}{\partial z^{t+1}_2} & \cdots & \frac{\partial h^{t+1}_2}{\partial z^{t+1}_N} \\\\ & \vdots & \vdots & \ddots & \vdots \\\\ & \frac{\partial h^{t+1}_N}{\partial z^{t+1}_1} & \frac{\partial h^{t+1}_N}{\partial z^{t+1}_2} & \cdots & \frac{\partial h^{t+1}_N}{\partial z^{t+1}_N} \end{pmatrix} $$
但因为激活函数 ,所以上述的雅可比矩阵仅有主对角线是有效的:
$$ \frac{\partial h_{t+1}}{\partial z_{t+1}} = \begin{pmatrix} & \frac{\partial h^{t+1}_1}{\partial z^{t+1}_1} & 0 & \cdots & 0 \\ & 0 & \frac{\partial h^{t+1}_2}{\partial z^{t+1}_2} & \cdots & 0 \\ & \vdots & \vdots & \ddots & \vdots \\ & 0 & 0 & \cdots & \frac{\partial h^{t+1}_N}{\partial z^{t+1}_N} \end{pmatrix} = diag(\sigma{'}(h_{t+1})) $$
在获取隐藏状态 的梯度 后,按照激活函数的求导方法,求净输入的梯度(导数) :
有了净输入的梯度 后,就能得到 、、 梯度计算的表达式:
训练结果
没有做一个复杂的网络,借用了一下之前深度神经网络-DNN里的数据,构建了一个以 人数、机动车数、公路面积 为输入,公路客运量 为输出的网络。
网络包含有3个模型,模型的输入分别是 人数、机动车数、公路面积,将第3个模型的输出作为网络的输出,即 公路客运量 的预测值。
如果需要更加符合网络设计目的,那么理想情况是将 某一年的人数、机动车数、公路面积 作为单个模型的输入,然后将连续数年作为不同模型的输入,每一层的输出就是该年的预测值。
RNN 的问题
计算慢
RNN 本质上还是 DNN 的改进,由于无法处理 序列数据输入、不定长输入 等问题,所以将一个庞大的 DNN 网络拆分未多个小的 DNN 网络,然后通过创建 隐藏状态 实现网络间的关联。
为了保证单个网络的表达能力,所以单个网络的规模不会过小,那么组合后的网络规模相比全连接会更大,计算会更慢,但也只是个可大可小的问题。
梯度消失
既然 RNN 中使用了 Tanh 指数函数,不可避免的RNN就需要面对 梯度消失 的问题。
RNN 计算网络损失的过程如下,是通过累加进行计算的:
举个例子:在计算 时刻的梯度时,如果 时刻的权重过小,那么在反向传播时,网络本质上已经忽略了 时刻以及该时刻之前的状态,这些被忽略的时刻,没有对 时刻的梯度计算产生贡献。