(十五)从零开始学人工智能-深度学习基础2
一、循环神经网络基础
在前面的文章中,介绍了全连接神经网络(DNN)和卷积神经网络(CNN),以及它们的训练和使用。回忆一下,它们都是能单独的处理一个个的输入,前一个输入和后一个输入是完全没有关系的。然后,现实当中是,某些任务需要能够更好的处理序列性质的信息,即前面的输入和后面的输入是有关系的。例如,当我们理解一句话意思时,孤立的理解这句话的每个词都是不够的,我们需要处理这些词连接起来的整个序列;当我们处理视频时,也不能只单独的去分析每一帧,而要分析这些帧连接起来的整个序列。这时,使用我们前文介绍的DNN和CNN是不够的,而需要用到深度学习领域中另一类非常重要的神经网络:循环神经网络(Recurrent Neural Network, RNN)。RNN种类繁多,过程繁琐,本部分首先对其结构进行剥茧抽丝,以理解RNNs及其训练算法;进一步地,介绍两中常见的RNN类型:长短时记忆网络(Long Short-Term Memory Network,LSTM),门控循环单元(Gated Recurrent Unit, GRU)。
1.1 从语言模型开始
为什么要从语言模型开始呢?因为,RNN是在自然语言处理领域中最先被用起来的,例如,RNN可以构建语言模型。那什么是语言模型呢?
我们可以让电脑做这样一个练习:写出一个句子前面的一些词,然后,让电脑帮我们写出接下来的一个词。比如下面这句话:
我昨天上学迟到了,老师批评了____。
在这个句子中,接下来的词最有可能的是“我”,而不是“小明”,更不会是“吃饭”。
在这个例子中,语言模型是这样的一个东西:给定一个一句话前面的部分,预测接下来最有可能的一个词是什么。
语言模型是对一种语言的特征进行建模,它有很多用处。比如在语音转文本(STT)的应用中,声学模型输出的结果,往往是若干个可能的候选词,这时候就需要语言模型来从这些候选词中选择一个最有可能的。当然,它同样也可以用在图像到文本的识别中国(OCR)。
在使用RNN之前,语言模型主要采用N-Gram算法。N是一个自然数,比如2或者3.它的含义是:假设一个词出现的概率只与前面N个词相关。我们以2-Gram为例,对这句话给出部分进行分词:
我|昨天|上学|迟到|了|,|老师|批评|了|____。
如果用2-Gram进行建模,那么电脑在预测的时候,只会看到前面的“了”,然后,电脑会在语料库中,搜索“了”后面最有可能的一个词。不管最后电脑选择的是不是“我”,显然这个模型是不靠谱的,因为“了”前面说了那么一大推实际上是没有用到的。如果使用3-Gram模型呢,会搜索“批评了”后面最有可能的词,感觉比2-Gram靠谱了不少,但还是远远不够的。因为这句话最关键的信息“我”,远在9个词之前。
看到这儿,大家可能会想,可以继续提升N的值呀,比如4-Gram、5-Gram、\(\dots\)。实际上,大家再深入想一下就会发现,这个想法是没有实用性的,因为当我们想处理任意长度的句子时,N设为多少都是不合适的;另外,模型的大小和N的关系是指数级的,4-Gram模型就会占用海量的存储空间。
所以,就该轮到RNN出场了,RNN理论上可以往前看(或往后看)任意多个词。
1.2 什么是循环神经网络?
循环神经网络种类繁多,我们先从最简单的基础循环神经网络开始吧~
1.2.1 基本循环神经网络
下图是一个简单的循环神经网络,它由输入层、一个隐藏层和一个输出层组成:
循环神经网络的实在是太难画出来了,网上所有大神们都不得不用了这种抽象的手法。不过,仔细看的话,如果把上面有\(W\)的那个带箭头的圈去掉,它就变成了最普通的全连接网络了。\(x\)是一个向量,它表示输入层的值(这里没有画出来表示输入层神经元节点的圆圈),\(s\)是一个向量,它表示隐藏层的值(这里隐藏层画了一个节点,你也可以想象这一层其实是多个节点,节点数目与向量\(s\)的维度相同);\(U\)是输入层到隐藏层的权重矩阵;\(O\)也是一个向量,它表示输出层的值;\(V\)是隐藏层到输出层的权重矩阵。那么,现在我们来看看\(W\)是什么,循环神经网络的隐藏层的值\(s\)不仅仅取决于当前这次输入\(x\),还取决于上一次隐藏层的值\(s\)。权重矩阵\(W\)就是隐藏层上一次的值作为这一次的输入的权重。
如果我们把上面的图展开,循环神经网络的也可以画成下面的样子:
现在看上去就比较清晰了,这个网络在\(t\)时刻接收到输入\(x_t\)之后,隐藏层的值是\(s_t\),输出值是\(o_t\)。关键一点是,\(s_t\)的值不仅仅取决于\(x_t\),还取决于\(s_{t-1}\)。我们可以用下面的公式来表示循环神经网络的计算方法:
$$o_t=g(Vs_t) \tag{1}$$
$$s_t = f(Ux_t+Ws_{t-1}) \tag{2}$$
式1是输出层的计算公式,输出层是全连接层,也就是它的节点都是和隐藏层的每个节点相连。\(V\)是一个输出层的权重矩阵,\(g\)是激活函数。式2是隐藏层的计算公式,它是循环层。\(U\)是输入\(x\)的权重矩阵,\(W\)是上一次输出值\(s_{t-1}\)作为这一次输入的权重矩阵,\(f\)是激活函数。
从上面的公式我们可以看出,循环层和全连接层的区别就是循环层多了一个权重矩阵\(W\)。
如果反复把式2带入到式1,我们可以得到:
$$\begin{aligned}o_t &=g(Vs_t) \ &=Vf(Ux_1+Ws_{t-1})\ &=Vf(Ux_t+Wf(Ux_{t-1}+Ws_{t-2}))\&=Vf(Ux_t+Wf(Ux_{t-1}+Wf(Ux_{t-2}+Ws_{t-3})))\ &=Vf(Ux_t+Wf(Ux_{t-1}+Wf(Ux_{t-2}+Wf(Ux_{t-3}+\dots)))) \end{aligned}$$
从上面可以看出,循环神经网络的输出值\(o_t\),是受前面历次输入值\(X_t\)、\(X_{t-1}\)、\(X_{t-2}\)、\(\dots\)影响的,这也是为什么循环神经网络可以往前看任意多个输入值的原因。
1.2.2 双向循环神经网络
对于语言模型来说,很多时候光看前面的词是不够的,比如下面这句话:
我的手机坏了,我打算____一部新手机。
可以想象的是,如果我们只看到横线前面的词,手机坏了,那么我是打算修一修?换一部新的手机?还是哭哭?这些都是无法确定的。但是如果我们还看到横线后面的词是“一部新手机”,那么,横线上的词填“买”的概率就大得多了。
在上一节中的基本循环神经网络是无法对此进行建模的,因此,我们需要双向循环神经网络,如下图所示:
当遇到这种从未来穿越回来的场景时,难免处于懵逼的状态。不过我们还是可以用屡试不爽的老办法:先分析一个特殊场景,然后再总结一般的规律。我们先考虑上图中,\(y_2\)的计算。
从上图可以看出,双向循环神经网络的隐藏层要保存两个值,一个\(A\)参与正向计算,另一个值\(A\’\)参与计算。最终的输出值\(y_2\)取决于\(A_2\)和\(A_2\’\)。其计算方法为:
\(y_2=g(VA_2+V\’A_2\’)\)
其中\(A_2\)和\(A_2\’\)的计算为:
\(A_2=f(WA_1+UX_2)\)
\(A_2\’=f(W\’A_3\’+U\’X_2)\)
至此,我们已经可以看出一般的规律:正向计算时,隐藏层的值\(S_t\)与\(S_{t-1}\)有关;反向计算时,隐藏层的值\(S_t\’\)与\(S_{t+1}\’\)有关;最终的输出取决于正向和反向计算的加和。现在,我们仿照式1和式2,写出双向循环神经网络的计算方法:
\(O_t=g(VS_t+V\’S_t\’)\)
\(S_t=f(UX_t+WS_{t-1})\)
\(S_t\’=f(U\’X_t+W\’S_{t+1}\’)\)
从上面的三个公式可以看到,正向计算和反向计算不共享权重,也就是说\(U\)和\(U\’\),\(W\)和\(W\’\),\(V\)和\(V\’\)都是不同的权重矩阵。
1.2.3 深度循环神经网络
前面我们介绍的循环神经网络只有一个隐藏层,当然了,也可以堆叠两个以上的隐藏层,这样就得到了深度循环神经网络。如下图所示:
我们把第\(i\)个隐藏层的值表示为\(S_t^{(i)}\)、\(S_t\’^{(i)}\),则深度循环神经网络的计算方式可以表示为:
\(O_t=g(V^{(i)}S_t^{(i)}+V\’^{(i)}S_t\’^{(i)})\)
\(S_t^{(i)}=f(U^{(i)}S_t^{i-1}+W^{(i)}S_{t-1})\)
\(S_t\’^{(i)}=f(U\’^{(i)}S_t\’^{(i-1)}+W\’^{(i)}S_{t+1}\’)\)
\(\cdots\)
\(S_t^{(1)}=f(U^{(1)}X_t+W^{(1)}S_{t-1})\)
\(S_t\’^{(1)}=f(U\’^{(1)}X_t+W\’^{(1)}S_{t+1}\’)\)
1.3 循环神经网络的训练
1.3.1 循环神经网络的训练算法:BPTT
\(BPTT\)算法是针对循环层的训练算法,它的基本原理和\(BP\)算法是一样的,也包含同样的三个步骤:
前向计算每个神经元的输出值;
反向计算每个神经元的误差项\(\delta_j\)值,它是误差函数\(E\)对神经元\(j\)的加权输入\(net_j\)的偏导数;
计算每个权重的梯度。
循环层如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a8kYlBqb-1583227061275)(./img2/5.png)]
最后再用随机梯度下降算法更新权重。
前向计算
使用前面的式2对循环层进行前向计算:
\(S_t=f(UX_t+WS_{t-1})\)
注意:上面的\(S_t\)、\(X_t\)、\(S_{t-1}\)都是向量,用黑体字母表示(在这里都用大写替代表示了