title: 吴恩达深度学习笔记(第五课时)
categories: 人工智能
tags:
吴恩达深度学习笔记
在本课程中你将学会序列模型,它是深度学习中最令人激动的内容之一。循环神经网络(RNN)之类的模型在语音识别、自然语言处理和其他领域中引起变革。在本节课中,你将学会如何自行创建这些模型。我们先看一些例子,这些例子都有效使用了序列模型。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JCY1qOWJ-1575538721565)(https://www.github.com/OneJane/blog/raw/master/小书匠/ae2970d80a119cd341ef31c684bfac49.png)]
在进行语音识别时,给定了一个输入音频片段 X X X,并要求输出对应的文字记录 Y Y Y。这个例子里输入和输出数据都是序列模型,因为 X X X是一个按时播放的音频片段,输出 Y Y Y是一系列单词。所以之后将要学到的一些序列模型,如循环神经网络等等在语音识别方面是非常有用的。
音乐生成问题是使用序列数据的另一个例子,在这个例子中,只有输出数据 Y Y Y是序列,而输入数据可以是空集,也可以是个单一的整数,这个数可能指代你想要生成的音乐风格,也可能是你想要生成的那首曲子的头几个音符。输入的 X X X可以是空的,或者就是个数字,然后输出序列 Y Y Y。
在处理情感分类时,输入数据 X X X是序列,你会得到类似这样的输入:“There is nothing to like in this movie.”,你认为这句评论对应几星?
系列模型在DNA序列分析中也十分有用,你的DNA可以用A、C、G、T四个字母来表示。所以给定一段DNA序列,你能够标记出哪部分是匹配某种蛋白质的吗?
在机器翻译过程中,你会得到这样的输入句:“Voulez-vou chante avecmoi?”(法语:要和我一起唱么?),然后要求你输出另一种语言的翻译结果。
在进行视频行为识别时,你可能会得到一系列视频帧,然后要求你识别其中的行为。
在进行命名实体识别时,可能会给定一个句子要你识别出句中的人名。
所以这些问题都可以被称作使用标签数据 ( X , Y ) (X,Y) (X,Y)作为训练集的监督学习。但从这一系列例子中你可以看出序列问题有很多不同类型。有些问题里,输入数据 X X X和输出数据 Y Y Y都是序列,但就算在那种情况下, X X X和 Y Y Y有时也会不一样长。或者像上图编号1所示和上图编号2的 X X X和 Y Y Y有相同的数据长度。在另一些问题里,只有 X X X或者只有 Y Y Y是序列。
所以在本节我们学到适用于不同情况的序列模型。
下节中我们会定义一些定义序列问题要用到的符号。
本节先从定义符号开始一步步构建序列模型。
比如说你想要建立一个序列模型,它的输入语句是这样的:“Harry Potter and Herminoe Granger invented a new spell.”,(这些人名都是出自于J.K.Rowling笔下的系列小说Harry Potter)。假如你想要建立一个能够自动识别句中人名位置的序列模型,那么这就是一个命名实体识别问题,这常用于搜索引擎,比如说索引过去24小时内所有新闻报道提及的人名,用这种方式就能够恰当地进行索引。命名实体识别系统可以用来查找不同类型的文本中的人名、公司名、时间、地点、国家名和货币名等等。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tgivVo7E-1575538721565)(https://www.github.com/OneJane/blog/raw/master/小书匠/cccbc03192af67a089b53d7940659505.png)]
现在给定这样的输入数据 x x x,假如你想要一个序列模型输出 y y y,使得输入的每个单词都对应一个输出值,同时这个 y y y能够表明输入的单词是否是人名的一部分。技术上来说这也许不是最好的输出形式,还有更加复杂的输出形式,它不仅能够表明输入词是否是人名的一部分,它还能够告诉你这个人名在这个句子里从哪里开始到哪里结束。比如Harry Potter(上图编号1所示)、Hermione Granger(上图标号2所示)。
更简单的那种输出形式:
这个输入数据是9个单词组成的序列,所以最终我们会有9个特征集和来表示这9个单词,并按序列中的位置进行索引, x < 1 > x^{<1>} x<1>、 x < 2 > x^{<2>} x<2>、 x < 3 > x^{<3>} x<3>等等一直到 x < 9 > x^{<9>} x<9>来索引不同的位置,我将用 x < t > x^{
输出数据也是一样,我们还是用 y < 1 > y^{<1>} y<1>、 y < 2 > y^{<2>} y<2>、 y < 3 > y^{<3>} y<3>等等一直到 y < 9 > y^{<9>} y<9>来表示输出数据。同时我们用 T x T_{x} Tx来表示输入序列的长度,这个例子中输入是9个单词,所以 T x = 9 T_{x}= 9 Tx=9。我们用 T y T_{y} Ty来表示输出序列的长度。在这个例子里 T x = T y T_{x} =T_{y} Tx=Ty,上个视频里你知道 T x T_{x} Tx和 T y T_{y} Ty可以有不同的值。
你应该记得我们之前用的符号,我们用 x ( i ) x^{(i)} x(i)来表示第 i i i个训练样本,所以为了指代第 t t t个元素,或者说是训练样本i的序列中第 t t t个元素用 x ( i ) < t > x^{\left(i \right)
所以在这个例子中, T x ( i ) = 9 T_{x}^{(i)}=9 Tx(i)=9,但如果另一个样本是由15个单词组成的句子,那么对于这个训练样本, T x ( i ) = 15 T_{x}^{(i)}=15 Tx(i)=15。
既然我们这个例子是NLP,也就是自然语言处理,这是我们初次涉足自然语言处理,一件我们需要事先决定的事是怎样表示一个序列里单独的单词,你会怎样表示像Harry这样的单词, x < 1 > x^{<1>} x<1>实际应该是什么?
接下来我们讨论一下怎样表示一个句子里单个的词。想要表示一个句子里的单词,第一件事是做一张词表,有时也称为词典,意思是列一列你的表示方法中用到的单词。这个词表(下图所示)中的第一个词是a,也就是说词典中的第一个单词是a,第二个单词是Aaron,然后更下面一些是单词and,再后面你会找到Harry,然后找到Potter,这样一直到最后,词典里最后一个单词可能是Zulu。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y5YAar8e-1575538721566)(https://www.github.com/OneJane/blog/raw/master/小书匠/a45c8066f935c6f29d00a95e36cb6662.png)]
因此a是第一个单词,Aaron是第二个单词,在这个词典里,and出现在367这个位置上,Harry是在4075这个位置,Potter在6830,词典里的最后一个单词Zulu可能是第10,000个单词。所以在这个例子中我用了10,000个单词大小的词典,这对现代自然语言处理应用来说太小了。对于商业应用来说,或者对于一般规模的商业应用来说30,000到50,000词大小的词典比较常见,但是100,000词的也不是没有,而且有些大型互联网公司会用百万词,甚至更大的词典。许多商业应用用的词典可能是30,000词,也可能是50,000词。不过我将用10,000词大小的词典做说明,因为这是一个很好用的整数。
如果你选定了10,000词的词典,构建这个词典的一个方法是遍历你的训练集,并且找到前10,000个常用词,你也可以去浏览一些网络词典,它能告诉你英语里最常用的10,000个单词,接下来你可以用one-hot表示法来表示词典里的每个单词。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GC0zo0VZ-1575538721567)(https://www.github.com/OneJane/blog/raw/master/小书匠/8deca8a84f06466155d2d8d53d26e05d.png)]
举个例子,在这里 x < 1 > x^{<1>} x<1>表示Harry这个单词,它就是一个第4075行是1,其余值都是0的向量(上图编号1所示),因为那是Harry在这个词典里的位置。
同样 x < 2 > x^{<2>} x<2>是个第6830行是1,其余位置都是0的向量(上图编号2所示)。
and在词典里排第367,所以 x < 3 > x^{<3>} x<3>就是第367行是1,其余值都是0的向量(上图编号3所示)。如果你的词典大小是10,000的话,那么这里的每个向量都是10,000维的。
因为a是字典第一个单词, x < 7 > x^{<7>} x<7>对应a,那么这个向量的第一个位置为1,其余位置都是0的向量(上图编号4所示)。
所以这种表示方法中, x < t > x^{
那么还剩下最后一件事,我们将在之后的视频讨论,如果你遇到了一个不在你词表中的单词,答案就是创建一个新的标记,也就是一个叫做Unknow Word的伪造单词,用<UNK>作为标记,来表示不在词表中的单词,我们之后会讨论更多有关这个的内容。
总结一下本节课的内容,我们描述了一套符号用来表述你的训练集里的序列数据 x x x和 y y y,在下节课我们开始讲述循环神经网络中如何构建 X X X到 Y Y Y的映射。
上节视频中,你了解了我们用来定义序列学习问题的符号。现在我们讨论一下怎样才能建立一个模型,建立一个神经网络来学习 X X X到 Y Y Y的映射。
可以尝试的方法之一是使用标准神经网络,在我们之前的例子中,我们有9个输入单词。想象一下,把这9个输入单词,可能是9个one-hot向量,然后将它们输入到一个标准神经网络中,经过一些隐藏层,最终会输出9个值为0或1的项,它表明每个输入单词是否是人名的一部分。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qzO553Lo-1575538721567)(https://www.github.com/OneJane/blog/raw/master/小书匠/1653ec3b8eb718ca817d3423ae3ca643.png)]
但结果表明这个方法并不好,主要有两个问题,
一、是输入和输出数据在不同例子中可以有不同的长度,不是所有的例子都有着同样输入长度 T x T_{x} Tx或是同样输出长度的 T y T_{y} Ty。即使每个句子都有最大长度,也许你能够填充(pad)或零填充(zero pad)使每个输入语句都达到最大长度,但仍然看起来不是一个好的表达方式。
二、一个像这样单纯的神经网络结构,它并不共享从文本的不同位置上学到的特征。具体来说,如果神经网络已经学习到了在位置1出现的Harry可能是人名的一部分,那么如果Harry出现在其他位置,比如 x < t > x^{
之前我们提到过这些(上图编号1所示的 x < 1 > x^{<1>} x<1>…… x < t > x^{
那么什么是循环神经网络呢?我们先建立一个(下图编号1所示)。如果你以从左到右的顺序读这个句子,第一个单词就是,假如说是 x < 1 > x^{<1>} x<1>,我们要做的就是将第一个词输入一个神经网络层,我打算这样画,第一个神经网络的隐藏层,我们可以让神经网络尝试预测输出,判断这是否是人名的一部分。循环神经网络做的是,当它读到句中的第二个单词时,假设是 x < 2 > x^{<2>} x<2>,它不是仅用 x < 2 > x^{<2>} x<2>就预测出 y ^ < 2 > {\hat{y}}^{<2>} y^<2>,他也会输入一些来自时间步1的信息。具体而言,时间步1的激活值就会传递到时间步2。然后,在下一个时间步,循环神经网络输入了单词 x < 3 > x^{<3>} x<3>,然后它尝试预测输出了预测结果 y ^ < 3 > {\hat{y}}^{<3>} y^<3>,等等,一直到最后一个时间步,输入了 x < T x > x^{
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SKNQaZ2u-1575538721568)(https://www.github.com/OneJane/blog/raw/master/小书匠/cb041c33b65e17600842ebf87174c4f2.png)]
要开始整个流程,在零时刻需要构造一个激活值 a < 0 > a^{<0>} a<0>,这通常是零向量。有些研究人员会随机用其他方法初始化 a < 0 > a^{<0>} a<0>,不过使用零向量作为零时刻的伪激活值是最常见的选择,因此我们把它输入神经网络。
在一些研究论文中或是一些书中你会看到这类神经网络,用这样的图形来表示(上图编号2所示),在每一个时间步中,你输入 x < t > x^{
循环神经网络是从左向右扫描数据,同时每个时间步的参数也是共享的,所以下页幻灯片中我们会详细讲述它的一套参数,我们用 W ax W_{\text{ax}} Wax来表示管理着从 x < 1 > x^{<1>} x<1>到隐藏层的连接的一系列参数,每个时间步使用的都是相同的参数 W ax W_{\text{ax}} Wax。而激活值也就是水平联系是由参数 W a a W_{aa} Waa决定的,同时每一个时间步都使用相同的参数 W a a W_{aa} Waa,同样的输出结果由 W ya W_{\text{ya}} Wya决定。下图详细讲述这些参数是如何起作用。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kDzKzx3I-1575538721568)(https://www.github.com/OneJane/blog/raw/master/小书匠/140529e4d7531babb5ba21778cd88bc3.png)]
在这个循环神经网络中,它的意思是在预测 y ^ < 3 > {\hat{y}}^{< 3 >} y^<3>时,不仅要使用 x < 3 > x^{<3>} x<3>的信息,还要使用来自 x < 1 > x^{<1>} x<1>和 x < 2 > x^{<2>} x<2>的信息,因为来自 x < 1 > x^{<1>} x<1>的信息可以通过这样的路径(上图编号1所示的路径)来帮助预测 y ^ < 3 > {\hat{y}}^{<3>} y^<3>。这个循环神经网络的一个缺点就是它只使用了这个序列中之前的信息来做出预测,尤其当预测 y ^ < 3 > {\hat{y}}^{<3>} y^<3>时,它没有用到 x < 4 > x^{<4>} x<4>, x < 5 > x^{<5>} x<5>, x < 6 > x^{<6>} x<6>等等的信息。所以这就有一个问题,因为如果给定了这个句子,“Teddy Roosevelt was a great President.”,为了判断Teddy是否是人名的一部分,仅仅知道句中前两个词是完全不够的,还需要知道句中后部分的信息,这也是十分有用的,因为句子也可能是这样的,“Teddy bears are on sale!”。因此如果只给定前三个单词,是不可能确切地知道Teddy是否是人名的一部分,第一个例子是人名,第二个例子就不是,所以你不可能只看前三个单词就能分辨出其中的区别。
所以这样特定的神经网络结构的一个限制是它在某一时刻的预测仅使用了从序列之前的输入信息并没有使用序列中后部分的信息,我们会在之后的双向循环神经网络(BRNN)的视频中处理这个问题。但对于现在,这个更简单的单向神经网络结构就够我们来解释关键概念了,之后只要在此基础上作出修改就能同时使用序列中前面和后面的信息来预测 y ^ < 3 > {\hat{y}}^{<3>} y^<3>,不过我们会在之后的视频讲述这些内容,接下来我们具体地写出这个神经网络计算了些什么。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PvYphNYd-1575538721569)(https://www.github.com/OneJane/blog/raw/master/小书匠/19cbb2d356a2a6e0f35aa2a946b23a2a.png)]
这里是一张清理后的神经网络示意图,和我之前提及的一样,一般开始先输入 a < 0 > a^{<0>} a<0>,它是一个零向量。接着就是前向传播过程,先计算激活值 a < 1 > a^{<1>} a<1>,然后再计算 y < 1 > y^{<1>} y<1>。
a < 1 > = g 1 ( W a a a < 0 > + W a x x < 1 > + b a ) a^{<1>} = g_{1}(W_{{aa}}a^{< 0 >} + W_{{ax}}x^{< 1 >} + b_{a}) a<1>=g1(Waaa<0>+Waxx<1>+ba)
y ^ < 1 > = g 2 ( W y a a < 1 > + b y ) \hat y^{< 1 >} = g_{2}(W_{{ya}}a^{< 1 >} + b_{y}) y^<1>=g2(Wyaa<1>+by)
我将用这样的符号约定来表示这些矩阵下标,举个例子 W ax W_{\text{ax}} Wax,第二个下标意味着 W ax W_{\text{ax}} Wax要乘以某个 x x x类型的量,然后第一个下标 a a a表示它是用来计算某个 a a a类型的变量。同样的,可以看出这里的 W ya W_{\text{ya}} Wya乘上了某个 a a a类型的量,用来计算出某个 y ^ \hat {y} y^类型的量。
循环神经网络用的激活函数经常是tanh,不过有时候也会用ReLU,但是tanh是更通常的选择,我们有其他方法来避免梯度消失问题,我们将在之后进行讲述。选用哪个激活函数是取决于你的输出 y y y,如果它是一个二分问题,那么我猜你会用sigmoid函数作为激活函数,如果是 k k k类别分类问题的话,那么可以选用softmax作为激活函数。不过这里激活函数的类型取决于你有什么样类型的输出 y y y,对于命名实体识别来说 y y y只可能是0或者1,那我猜这里第二个激活函数 g g g可以是sigmoid激活函数。
更一般的情况下,在 t t t时刻,
a < t > = g 1 ( W a a a < t − 1 > + W a x x < t > + b a ) a^{< t >} = g_{1}(W_{aa}a^{< t - 1 >} + W_{ax}x^{< t >} + b_{a}) a<t>=g1(Waaa<t−1>+Waxx<t>+ba)
y ^ < t > = g 2 ( W y a a < t > + b y ) \hat y^{< t >} = g_{2}(W_{{ya}}a^{< t >} + b_{y}) y^<t>=g2(Wyaa<t>+by)
所以这些等式定义了神经网络的前向传播,你可以从零向量 a < 0 > a^{<0>} a<0>开始,然后用 a < 0 > a^{<0>} a<0>和 x < 1 > x^{<1>} x<1>来计算出 a < 1 > a^{<1>} a<1>和 y ^ < 1 > \hat y^{<1>} y^<1>,然后用 x < 2 > x^{<2>} x<2>和 a < 1 > a^{<1>} a<1>一起算出 a < 2 > a^{<2>} a<2>和 y ^ < 2 > \hat y^{<2>} y^<2>等等,像图中这样,从左到右完成前向传播。
现在为了帮我们建立更复杂的神经网络,我实际要将这个符号简化一下,我在下一张幻灯片里复制了这两个等式(上图编号1所示的两个等式)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YMYH4URN-1575538721570)(https://www.github.com/OneJane/blog/raw/master/小书匠/27afdd27f45ad8ddf78677af2a3eeaf8.png)]
接下来为了简化这些符号,我要将这部分( W aa a < t − 1 > + W ax x < t > W_{\text{aa}}a^{
用这个符号( [ a < t − 1 > , x < t > ] \left\lbrack a^{< t - 1 >},x^{< t >}\right\rbrack [a<t−1>,x<t>])的意思是将这两个向量堆在一起,我会用这个符号表示,即 [ a < t − 1 > x < t > ] \begin{bmatrix}a^{< t-1 >} \\ x^{< t >} \\\end{bmatrix} [a<t−1>x<t>](上图编号4所示),最终这就是个10,100维的向量。你可以自己检查一下,用这个矩阵乘以这个向量,刚好能够得到原来的量,因为此时,矩阵 [ W a a ⋮ W a x ] [ {{W}_{aa}}\vdots {{W}_{ax}}] [Waa⋮Wax]乘以 [ a < t − 1 > x < t > ] \begin{bmatrix} a^{< t - 1 >} \\ x^{< t >} \\ \end{bmatrix} [a<t−1>x<t>],刚好等于 W a a a < t − 1 > + W a x x < t > W_{{aa}}a^{
同样对于这个例子( y ^ < t > = g ( W y a a < t > + b y ) \hat y^{
RNN前向传播示意图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5JDzmNAJ-1575538721571)(https://www.github.com/OneJane/blog/raw/master/小书匠/rnn-f.png)]
好就这么多,你现在知道了基本的循环神经网络,下节课我们会一起来讨论反向传播,以及你如何能够用RNN进行学习。
之前我们已经学过了循环神经网络的基础结构,在本节视频中我们将来了解反向传播是怎样在循环神经网络中运行的。和之前一样,当你在编程框架中实现循环神经网络时,编程框架通常会自动处理反向传播。但我认为,在循环神经网络中,对反向传播的运行有一个粗略的认识还是非常有用的,让我们来一探究竟。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OhdoJKrh-1575538721571)(https://www.github.com/OneJane/blog/raw/master/小书匠/998c7af4f90cd0de0c88f138b61f0168.png)]
在之前你已经见过对于前向传播(上图蓝色箭头所指方向)怎样在神经网络中从左到右地计算这些激活项,直到输出所有地预测结果。而对于反向传播,我想你已经猜到了,反向传播地计算方向(上图红色箭头所指方向)与前向传播基本上是相反的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p0tdDnBH-1575538721572)(https://www.github.com/OneJane/blog/raw/master/小书匠/ad9dd74b6ce9bcea14baa289df530d6b.png)]
我们来分析一下前向传播的计算,现在你有一个输入序列, x < 1 > x^{<1>} x<1>, x < 2 > x^{<2>} x<2>, x < 3 > x^{<3>} x<3>一直到 x < T x > x^{< T_{x} >} x<Tx>,然后用 x < 1 > x^{<1>} x<1>还有 a < 0 > a^{<0>} a<0>计算出时间步1的激活项,再用 x < 2 > x^{<2>} x<2>和 a < 1 > a^{<1>} a<1>计算出 a < 2 > a^{<2>} a<2>,然后计算 a < 3 > a^{<3>} a<3>等等,一直到 a < T x > a^{< T_{x} >} a<Tx>。
为了真正计算出 a < 1 > a^{<1>} a<1>,你还需要一些参数, W a W_{a} Wa和 b a b_{a} ba,用它们来计算出 a < 1 > a^{<1>} a<1>。这些参数在之后的每一个时间步都会被用到,于是继续用这些参数计算 a < 2 > a^{<2>} a<2>, a < 3 > a^{<3>} a<3>等等,所有的这些激活项都要取决于参数 W a W_{a} Wa和 b a b_{a} ba。有了 a < 1 > a^{<1>} a<1>,神经网络就可以计算第一个预测值 y ^ < 1 > \hat y^{<1>} y^<1>,接着到下一个时间步,继续计算出 y ^ < 2 > \hat y^{<2>} y^<2>, y ^ < 3 > \hat y^{<3>} y^<3>,等等,一直到 y ^ < T y > \hat y^{
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xhZ3Etdy-1575538721572)(https://www.github.com/OneJane/blog/raw/master/小书匠/71a0ed918704f6d35091d8b6d60793e4.png)]
然后为了计算反向传播,你还需要一个损失函数。我们先定义一个元素损失函数(上图编号1所示)
L < t > ( y ^ < t > , y < t > ) = − y < t > log y ^ < t > − ( 1 − y < t > ) l o g ( 1 − y ^ < t > ) L^{
它对应的是序列中一个具体的词,如果它是某个人的名字,那么 y < t > y^{
现在我们来定义整个序列的损失函数,将 L L L定义为(上图编号2所示)
L ( y ^ , y ) = ∑ t = 1 T x L < t > ( y ^ < t > , y < t > ) L(\hat y,y) = \ \sum_{t = 1}^{T_{x}}{L^{< t >}(\hat y^{< t >},y^{< t >})} L(y^,y)= ∑t=1TxL<t>(y^<t>,y<t>)
在这个计算图中,通过 y ^ < 1 > \hat y^{<1>} y^<1>可以计算对应的损失函数,于是计算出第一个时间步的损失函数(上图编号3所示),然后计算出第二个时间步的损失函数,然后是第三个时间步,一直到最后一个时间步,最后为了计算出总体损失函数,我们要把它们都加起来,通过下面的等式(上图编号2所示的等式)计算出最后的 L L L(上图编号4所示),也就是把每个单独时间步的损失函数都加起来。
这就是完整的计算图,在之前的例子中,你已经见过反向传播,所以你应该能够想得到反向传播算法需要在相反的方向上进行计算和传递信息,最终你做的就是把前向传播的箭头都反过来,在这之后你就可以计算出所有合适的量,然后你就可以通过导数相关的参数,用梯度下降法来更新参数。
在这个反向传播的过程中,最重要的信息传递或者说最重要的递归运算就是这个从右到左的运算,这也就是为什么这个算法有一个很别致的名字,叫做**“通过(穿越)时间反向传播**(backpropagation through time)”。取这个名字的原因是对于前向传播,你需要从左到右进行计算,在这个过程中,时刻 t t t不断增加。而对于反向传播,你需要从右到左进行计算,就像时间倒流。“通过时间反向传播”,就像穿越时光,这种说法听起来就像是你需要一台时光机来实现这个算法一样。
RNN反向传播示意图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W8eXzutK-1575538721573)(https://www.github.com/OneJane/blog/raw/master/小书匠/rnn_cell_backprop.png)]
希望你大致了解了前向和反向传播是如何在RNN中工作的,到目前为止,你只见到了RNN中一个主要的例子,其中输入序列的长度和输出序列的长度是一样的。在下节课将展示更多的RNN架构,这将让你能够处理一些更广泛的应用。
现在你已经了解了一种RNN结构,它的输入量 T x T_{x} Tx等于输出数量 T y T_{y} Ty。事实上,对于其他一些应用, T x T_{x} Tx和 T y T_{y} Ty并不一定相等。在这个视频里,你会看到更多的RNN的结构。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W7lH3BTs-1575538721574)(https://www.github.com/OneJane/blog/raw/master/小书匠/329b0748f7282efc206ea8de6a709833.png)]
你应该还记得这周第一个视频中的那个幻灯片,那里有很多例子输入 x x x和输出 y y y,有各种类型,并不是所有的情况都满足 T x = T y T_{x}=T_{y} Tx=Ty。
比如音乐生成这个例子, T x T_{x} Tx可以是长度为1甚至为空集。再比如电影情感分类,输出 y y y可以是1到5的整数,而输入是一个序列。在命名实体识别中,这个例子中输入长度和输出长度是一样的。
还有一些情况,输入长度和输出长度不同,他们都是序列但长度不同,比如机器翻译,一个法语句子和一个英语句子不同数量的单词却能表达同一个意思。
所以我们应该修改基本的RNN结构来处理这些问题,这个视频的内容参考了Andrej Karpathy的博客,一篇叫做《循环神经网络的非理性效果》(“The Unreasonable Effectiveness of Recurrent Neural Networks”)的文章,我们看一些例子。
你已经见过 T x = T y T_{x} = T_{y} Tx=Ty的例子了(下图编号1所示),也就是我们输入序列 x < 1 > x^{<1>} x<1>, x < 2 > x^{<2>} x<2>,一直到 x < T x > x^{< T_{x}>} x<Tx>,我们的循环神经网络这样工作,输入 x < 1 > x^{<1>} x<1>来计算 y ^ < 1 > \hat y^{<1>} y^<1>, y ^ < 2 > \hat y^{<2>} y^<2>等等一直到 y ^ < T y > \hat y^{
现在我们看另外一个例子,假如说,你想处理情感分类问题(下图编号2所示),这里 x x x可能是一段文本,比如一个电影的评论,“These is nothing to like in this movie.”(“这部电影没什么还看的。”),所以 x x x就是一个序列,而 y y y可能是从1到5的一个数字,或者是0或1,这代表正面评价和负面评价,而数字1到5代表电影是1星,2星,3星,4星还是5星。所以在这个例子中,我们可以简化神经网络的结构,输入 x < 1 > x^{<1 >} x<1>, x < 2 > x^{< 2 >} x<2>,一次输入一个单词,如果输入文本是“These is nothing to like in this movie”,那么单词的对应如下图编号2所示。我们不再在每个时间上都有输出了,而是让这个RNN网络读入整个句子,然后在最后一个时间上得到输出,这样输入的就是整个句子,所以这个神经网络叫做“多对一”(many-to-one)结构,因为它有很多输入,很多的单词,然后输出一个数字。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aRD9Nia7-1575538721574)(https://www.github.com/OneJane/blog/raw/master/小书匠/14e1df0a7a8cdd1584b2e92e87e23aa7.png)]
为了完整性,还要补充一个“一对一”(one-to-one)的结构(上图编号3所示),这个可能没有那么重要,这就是一个小型的标准的神经网络,输入 x x x然后得到输出 y y y,我们这个系列课程的前两个课程已经讨论过这种类型的神经网络了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-clbu4jDP-1575538721575)(https://www.github.com/OneJane/blog/raw/master/小书匠/db580f1dfd6095d672fc62cce74ce5e2.png)]
除了“多对一”的结构,也可以有“一对多”(one-to-many)的结构。对于一个“一对多”神经网络结构的例子就是音乐生成(上图编号1所示),事实上,你会在这个课后编程练习中去实现这样的模型,你的目标是使用一个神经网络输出一些音符。对应于一段音乐,输入 x x x可以是一个整数,表示你想要的音乐类型或者是你想要的音乐的第一个音符,并且如果你什么都不想输入, x x x可以是空的输入,可设为0向量。
这样这个神经网络的结构,首先是你的输入 x x x,然后得到RNN的输出,第一个值,然后就没有输入了,再得到第二个输出,接着输出第三个值等等,一直到合成这个音乐作品的最后一个音符,这里也可以写上输入 a < 0 > a^{<0>} a<0>(上图编号3所示)。有一个后面才会讲到的技术细节,当你生成序列时通常会把第一个合成的输出也喂给下一层(上图编号4所示),所以实际的网络结构最终就像这个样子。
我们已经讨论了“多对多”、“多对一”、“一对一”和“一对多”的结构,对于“多对多”的结构还有一个有趣的例子值得详细说一下,就是输入和输出长度不同的情况。你刚才看过的多对多的例子,它的输入长度和输出长度是完全一样的。而对于像机器翻译这样的应用,输入句子的单词的数量,比如说一个法语的句子,和输出句子的单词数量,比如翻译成英语,这两个句子的长度可能不同,所以还需要一个新的网络结构,一个不同的神经网络(上图编号2所示)。首先读入这个句子,读入这个输入,比如你要将法语翻译成英语,读完之后,这个网络就会输出翻译结果。有了这种结构 T x T_{x} Tx和 T y T_{y} Ty就可以是不同的长度了。同样,你也可以画上这个 a < 0 > a^{<0>} a<0>。这个网络的结构有两个不同的部分,这(上图编号5所示)是一个编码器,获取输入,比如法语句子,这(上图编号6所示)是解码器,它会读取整个句子,然后输出翻译成其他语言的结果。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HaDSs5id-1575538721575)(https://www.github.com/OneJane/blog/raw/master/小书匠/1daa38085604dd04e91ebc5e609d1179.png)]
这就是一个“多对多”结构的例子,到这周结束的时候,你就能对这些各种各样结构的基本构件有一个很好的理解。严格来说,还有一种结构,我们会在第四周涉及到,就是“注意力”(attention based)结构,但是根据我们现在画的这些图不好理解这个模型。
总结一下这些各种各样的RNN结构,这(上图编号1所示)是“一对一”的结构,当去掉 a < 0 > a^{<0>} a<0>时它就是一种标准类型的神经网络。还有一种“一对多”的结构(上图编号2所示),比如音乐生成或者序列生成。还有“多对一”,这(上图编号3所示)是情感分类的例子,首先读取输入,一个电影评论的文本,然后判断他们是否喜欢电影还是不喜欢。还有“多对多”的结构(上图编号4所示),命名实体识别就是“多对多”的例子,其中 T x = T y T_{x}=T_{y} Tx=Ty。最后还有一种“多对多”结构的其他版本(上图编号5所示),对于像机器翻译这样的应用, T x T_{x} Tx和 T y T_{y} Ty就可以不同了。
现在,你已经了解了大部分基本的模块,这些就是差不多所有的神经网络了,除了序列生成,有些细节的问题我们会在下节课讲解。
我希望你从本视频中了解到用这些RNN的基本模块,把它们组合在一起就可以构建各种各样的模型。但是正如我前面提到的,序列生成还有一些不一样的地方,在这周的练习里,你也会实现它,你需要构建一个语言模型,结果好的话会得到一些有趣的序列或者有意思的文本。下节课深入探讨序列生成。
在自然语言处理中,构建语言模型是最基础的也是最重要的工作之一,并且能用RNN很好地实现。在本视频中,你将学习用RNN构建一个语言模型,在本周结束的时候,还会有一个很有趣的编程练习,你能在练习中构建一个语言模型,并用它来生成莎士比亚文风的文本或其他类型文本。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zxh8JsuR-1575538721576)(https://www.github.com/OneJane/blog/raw/master/小书匠/fa17a85c0e7d9b14633013f2223c877b.png)]
所以什么是语言模型呢?比如你在做一个语音识别系统,你听到一个句子,“the apple and pear(pair) salad was delicious.”,所以我究竟说了什么?我说的是 “the apple and pair salad”,还是“the apple and pear salad”?(pear和pair是近音词)。你可能觉得我说的应该更像第二种,事实上,这就是一个好的语音识别系统要帮助输出的东西,即使这两句话听起来是如此相似。而让语音识别系统去选择第二个句子的方法就是使用一个语言模型,他能计算出这两句话各自的可能性。
举个例子,一个语音识别模型可能算出第一句话的概率是 P ( The apple and pair salad ) = 3.2 × 1 0 − 13 P( \text{The apple and pair salad}) = 3.2 \times 10^{-13} P(The apple and pair salad)=3.2×10−13,而第二句话的概率是 P ( The apple and pear salad ) = 5.7 × 1 0 − 10 P\left(\text{The apple and pear salad} \right) = 5.7 \times 10^{-10} P(The apple and pear salad)=5.7×10−10,比较这两个概率值,显然我说的话更像是第二种,因为第二句话的概率比第一句高出1000倍以上,这就是为什么语音识别系统能够在这两句话中作出选择。
所以语言模型所做的就是,它会告诉你某个特定的句子它出现的概率是多少,根据我所说的这个概率,假设你随机拿起一张报纸,打开任意邮件,或者任意网页或者听某人说下一句话,并且这个人是你的朋友,这个你即将从世界上的某个地方得到的句子会是某个特定句子的概率是多少,例如“the apple and pear salad”。它是两种系统的基本组成部分,一个刚才所说的语音识别系统,还有机器翻译系统,它要能正确输出最接近的句子。而语言模型做的最基本工作就是输入一个句子,准确地说是一个文本序列, y < 1 > y^{<1>} y<1>, y < 2 > y^{<2>} y<2>一直到 y < T y > y^{
那么如何建立一个语言模型呢?为了使用RNN建立出这样的模型,你首先需要一个训练集,包含一个很大的英文文本语料库(corpus)或者其它的语言,你想用于构建模型的语言的语料库。语料库是自然语言处理的一个专有名词,意思就是很长的或者说数量众多的英文句子组成的文本。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X1U1d72w-1575538721577)(https://markdown.xiaoshujiang.com/img/spinner.gif “[[[1575536829342]]]” )]
假如说,你在训练集中得到这么一句话,“Cats average 15 hours of sleep a day.”(猫一天睡15小时),你要做的第一件事就是将这个句子标记化,意思就是像之前视频中一样,建立一个字典,然后将每个单词都转换成对应的one-hot向量,也就是字典中的索引。可能还有一件事就是你要定义句子的结尾,一般的做法就是增加一个额外的标记,叫做EOS(上图编号1所示),它表示句子的结尾,这样能够帮助你搞清楚一个句子什么时候结束,我们之后会详细讨论这个。EOS标记可以被附加到训练集中每一个句子的结尾,如果你想要你的模型能够准确识别句子结尾的话。在本周的练习中我们不需要使用这个EOS标记,不过在某些应用中你可能会用到它,不过稍后就能见到它的用处。于是在本例中我们,如果你加了EOS标记,这句话就会有9个输入,有 y < 1 > y^{<1>} y<1>, y < 2 > y^{<2>} y<2>一直到 y < 9 > y^{<9>} y<9>。在标记化的过程中,你可以自行决定要不要把标点符号看成标记,在本例中,我们忽略了标点符号,所以我们只把day看成标志,不包括后面的句号,如果你想把句号或者其他符号也当作标志,那么你可以将句号也加入你的字典中。
现在还有一个问题如果你的训练集中有一些词并不在你的字典里,比如说你的字典有10,000个词,10,000个最常用的英语单词。现在这个句,“The Egyptian Mau is a bread of cat.”其中有一个词Mau,它可能并不是预先的那10,000个最常用的单词,在这种情况下,你可以把Mau替换成一个叫做UNK的代表未知词的标志,我们只针对UNK建立概率模型,而不是针对这个具体的词Mau。
完成标识化的过程后,这意味着输入的句子都映射到了各个标志上,或者说字典中的各个词上。下一步我们要构建一个RNN来构建这些序列的概率模型。在下一张幻灯片中会看到的一件事就是最后你会将 x < t > x^{
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tZT1eHfD-1575538721577)(https://www.github.com/OneJane/blog/raw/master/小书匠/986226c39270a1e14643e8658fe6c374.png)]
现在我们来建立RNN模型,我们继续使用“Cats average 15 hours of sleep a day.”这个句子来作为我们的运行样例,我将会画出一个RNN结构。在第0个时间步,你要计算激活项 a < 1 > a^{<1>} a<1>,它是以 x < 1 > x^{<1 >} x<1>作为输入的函数,而 x < 1 > x^{<1>} x<1>会被设为全为0的集合,也就是0向量。在之前的 a < 0 > a^{<0>} a<0>按照惯例也设为0向量,于是 a < 1 > a^{<1>} a<1>要做的就是它会通过softmax进行一些预测来计算出第一个词可能会是什么,其结果就是 y ^ < 1 > \hat y^{<1>} y^<1>(上图编号1所示),这一步其实就是通过一个softmax层来预测字典中的任意单词会是第一个词的概率,比如说第一个词是 a a a的概率有多少,第一个词是Aaron的概率有多少,第一个词是cats的概率又有多少,就这样一直到Zulu是第一个词的概率是多少,还有第一个词是UNK(未知词)的概率有多少,还有第一个词是句子结尾标志的概率有多少,表示不必阅读。所以 y ^ < 1 > \hat y^{<1>} y^<1>的输出是softmax的计算结果,它只是预测第一个词的概率,而不去管结果是什么。在我们的例子中,最终会得到单词Cats。所以softmax层输出10,000种结果,因为你的字典中有10,000个词,或者会有10,002个结果,因为你可能加上了未知词,还有句子结尾这两个额外的标志。
然后RNN进入下个时间步,在下一时间步中,仍然使用激活项 a < 1 > a^{<1>} a<1>,在这步要做的是计算出第二个词会是什么。现在我们依然传给它正确的第一个词,我们会告诉它第一个词就是Cats,也就是 y ^ < 1 > \hat y^{<1>} y^<1>,告诉它第一个词就是Cats,这就是为什么 y < 1 > = x < 2 > y^{<1>} = x^{<2>} y<1>=x<2>(上图编号2所示)。然后在第二个时间步中,输出结果同样经过softmax层进行预测,RNN的职责就是预测这些词的概率(上图编号3所示),而不会去管结果是什么,可能是b或者arron,可能是Cats或者Zulu或者UNK(未知词)或者EOS或者其他词,它只会考虑之前得到的词。所以在这种情况下,我猜正确答案会是average,因为句子确实就是Cats average开头的。
然后再进行RNN的下个时间步,现在要计算 a < 3 > a^{<3>} a<3>。为了预测第三个词,也就是15,我们现在给它之前两个词,告诉它Cats average是句子的前两个词,所以这是下一个输入, x < 3 > = y < 2 > x^{<3>} = y^{<2>} x<3>=y<2>,输入average以后,现在要计算出序列中下一个词是什么,或者说计算出字典中每一个词的概率(上图编号4所示),通过之前得到的Cats和average,在这种情况下,正确结果会是15,以此类推。
一直到最后,没猜错的话,你会停在第9个时间步,然后把 x < 9 > x^{<9>} x<9>也就是 y < 8 > y^{<8>} y<8>传给它(上图编号5所示),也就是单词day,这里是 a < 9 > a^{<9>} a<9>,它会输出 y < 9 > y^{<9>} y<9>,最后的得到结果会是EOS标志,在这一步中,通过前面这些得到的单词,不管它们是什么,我们希望能预测出EOS句子结尾标志的概率会很高(上图编号6所示)。
所以RNN中的每一步都会考虑前面得到的单词,比如给它前3个单词(上图编号7所示),让它给出下个词的分布,这就是RNN如何学习从左往右地每次预测一个词。
接下来为了训练这个网络,我们要定义代价函数。于是,在某个时间步 t t t,如果真正的词是 y < t > y^{
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-scNBHKer-1575538721578)(https://www.github.com/OneJane/blog/raw/master/小书匠/794b8461aeb485ee61c368c90738523e.png)]
如果你用很大的训练集来训练这个RNN,你就可以通过开头一系列单词像是Cars average 15或者Cars average 15 hours of来预测之后单词的概率。现在有一个新句子,它是 y < 1 > y^{<1>} y<1>, y < 2 > y^{<2>} y<2>, y < 3 > y^{<3>} y<3>,为了简单起见,它只包含3个词(如上图所示),现在要计算出整个句子中各个单词的概率,方法就是第一个softmax层会告诉你 y < 1 > y^{<1>} y<1>的概率(上图编号1所示),这也是第一个输出,然后第二个softmax层会告诉你在考虑 y < 1 > y^{<1>} y<1>的情况下 y < 2 > y^{<2>} y<2>的概率(上图编号2所示),然后第三个softmax层告诉你在考虑 y < 1 > y^{<1>} y<1>和 y < 2 > y^{<2>} y<2>的情况下 y < 3 > y^{<3>} y<3>的概率(上图编号3所示),把这三个概率相乘,最后得到这个含3个词的整个句子的概率。
这就是用RNN训练一个语言模型的基础结构,可能我说的这些东西听起来有些抽象,不过别担心,你可以在编程练习中亲自实现这些东西。下一节课用语言模型做的一件最有趣的事就是从模型中进行采样。
在你训练一个序列模型之后,要想了解到这个模型学到了什么,一种非正式的方法就是进行一次新序列采样,来看看到底应该怎么做。
记住一个序列模型模拟了任意特定单词序列的概率,我们要做的就是对这些概率分布进行采样来生成一个新的单词序列。下图编号1所示的网络已经被上方所展示的结构训练训练过了,而为了进行采样(下图编号2所示的网络),你要做一些截然不同的事情。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FhWR8W14-1575538721579)(https://www.github.com/OneJane/blog/raw/master/小书匠/8b901fc8fcab9e16b1fe26b92f4ec546.png)]
第一步要做的就是对你想要模型生成的第一个词进行采样,于是你输入 x < 1 > = 0 x^{<1>} =0 x<1>=0, a < 0 > = 0 a^{<0>} =0 a<0>=0,现在你的第一个时间步得到的是所有可能的输出是经过softmax层后得到的概率,然后根据这个softmax的分布进行随机采样。Softmax分布给你的信息就是第一个词a的概率是多少,第一个词是aaron的概率是多少,第一个词是zulu的概率是多少,还有第一个词是UNK(未知标识)的概率是多少,这个标识可能代表句子的结尾,然后对这个向量使用例如numpy命令,np.random.choice
(上图编号3所示),来根据向量中这些概率的分布进行采样,这样就能对第一个词进行采样了。
然后继续下一个时间步,记住第二个时间步需要 y ^ < 1 > \hat y^{<1>} y^<1>作为输入,而现在要做的是把刚刚采样得到的 y ^ < 1 > \hat y^{<1>} y^<1>放到 a < 2 > a^{<2>} a<2>(上图编号4所示),作为下一个时间步的输入,所以不管你在第一个时间步得到的是什么词,都要把它传递到下一个位置作为输入,然后softmax层就会预测 y ^ < 2 > \hat y^{<2>} y^<2>是什么。举个例子,假如说对第一个词进行抽样后,得到的是The,The作为第一个词的情况很常见,然后把The当成 x < 2 > x^{<2>} x<2>,现在 x < 2 > x^{<2>} x<2>就是 y ^ < 1 > \hat y^{<1>} y^<1>,现在你要计算出在第一词是The的情况下,第二个词应该是什么(上图编号5所示),然后得到的结果就是 y ^ < 2 > \hat y^{<2>} y^<2>,然后再次用这个采样函数来对 y ^ < 2 > \hat y^{<2>} y^<2>进行采样。
然后再到下一个时间步,无论你得到什么样的用one-hot码表示的选择结果,都把它传递到下一个时间步,然后对第三个词进行采样。不管得到什么都把它传递下去,一直这样直到最后一个时间步。
那么你要怎样知道一个句子结束了呢?方法之一就是,如果代表句子结尾的标识在你的字典中,你可以一直进行采样直到得到EOS标识(上图编号6所示),这代表着已经抵达结尾,可以停止采样了。另一种情况是,如果你的字典中没有这个词,你可以决定从20个或100个或其他个单词进行采样,然后一直将采样进行下去直到达到所设定的时间步。不过这种过程有时候会产生一些未知标识(上图编号7所示),如果你要确保你的算法不会输出这种标识,你能做的一件事就是拒绝采样过程中产生任何未知的标识,一旦出现就继续在剩下的词中进行重采样,直到得到一个不是未知标识的词。如果你不介意有未知标识产生的话,你也可以完全不管它们。
这就是你如何从你的RNN语言模型中生成一个随机选择的句子。直到现在我们所建立的是基于词汇的RNN模型,意思就是字典中的词都是英语单词(下图编号1所示)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q4SvVaqz-1575538721579)(https://www.github.com/OneJane/blog/raw/master/小书匠/1d31771da8ced333968541fbbf67e6f1.png)]
根据你实际的应用,你还可以构建一个基于字符的RNN结构,在这种情况下,你的字典仅包含从a到z的字母,可能还会有空格符,如果你需要的话,还可以有数字0到9,如果你想区分字母大小写,你可以再加上大写的字母,你还可以实际地看一看训练集中可能会出现的字符,然后用这些字符组成你的字典(上图编号2所示)。
如果你建立一个基于字符的语言模型,比起基于词汇的语言模型,你的序列 y ^ < 1 > \hat y^{<1>} y^<1>, y ^ < 2 > \hat y^{<2>} y^<2>, y ^ < 3 > \hat y^{<3>} y^<3>在你的训练数据中将会是单独的字符,而不是单独的词汇。所以对于前面的例子来说,那个句子(上图编号3所示),“Cats average 15 hours of sleep a day.”,在该例中C就是 y ^ < 1 > \hat y^{<1>} y^<1>,a就是 y ^ < 2 > \hat y^{<2>} y^<2>,t就是 y ^ < 3 > \hat y^{<3>} y^<3>,空格符就是 y ^ < 4 > \hat y^{<4>} y^<4>等等。
使用基于字符的语言模型有有点也有缺点,优点就是你不必担心会出现未知的标识,例如基于字符的语言模型会将Mau这样的序列也视为可能性非零的序列。而对于基于词汇的语言模型,如果Mau不在字典中,你只能把它当作未知标识UNK。不过基于字符的语言模型一个主要缺点就是你最后会得到太多太长的序列,大多数英语句子只有10到20个的单词,但却可能包含很多很多字符。所以基于字符的语言模型在捕捉句子中的依赖关系也就是句子较前部分如何影响较后部分不如基于词汇的语言模型那样可以捕捉长范围的关系,并且基于字符的语言模型训练起来计算成本比较高昂。所以我见到的自然语言处理的趋势就是,绝大多数都是使用基于词汇的语言模型,但随着计算机性能越来越高,会有更多的应用。在一些特殊情况下,会开始使用基于字符的模型。但是这确实需要更昂贵的计算力来训练,所以现在并没有得到广泛地使用,除了一些比较专门需要处理大量未知的文本或者未知词汇的应用,还有一些要面对很多专有词汇的应用。
在现有的方法下,现在你可以构建一个RNN结构,看一看英文文本的语料库,然后建立一个基于词汇的或者基于字符的语言模型,然后从训练的语言模型中进行采样。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XWubTN29-1575538721580)(https://www.github.com/OneJane/blog/raw/master/小书匠/4c7e0878e85865c8fed558375e14b938.png)]
这里有一些样本,它们是从一个语言模型中采样得到的,准确来说是基于字符的语言模型,你可以在编程练习中自己实现这样的模型。如果模型是用新闻文章训练的,它就会生成左边这样的文本,这有点像一篇不太合乎语法的新闻文本,不过听起来,这句“Concussion epidemic”,to be examined,确实有点像新闻报道。用莎士比亚的文章训练后生成了右边这篇东西,听起来很像是莎士比亚写的东西:
“The mortal moon hath her eclipse in love.
And subject of this thou art another this fold.
When besser be my love to me see sabl’s.
For whose are ruse of mine eyes heaves.”
这些就是基础的RNN结构和如何去建立一个语言模型并使用它,对于训练出的语言模型进行采样。在之后的视频中,我想探讨在训练RNN时一些更加深入的挑战以及如何适应这些挑战,特别是梯度消失问题来建立更加强大的RNN模型。下节课,我们将谈到梯度消失并且会开始谈到GRU,也就是门控循环单元和LSTM长期记忆网络模型。
你已经了解了RNN时如何工作的了,并且知道如何应用到具体问题上,比如命名实体识别,比如语言模型,你也看到了怎么把反向传播用于RNN。其实,基本的RNN算法还有一个很大的问题,就是梯度消失的问题。这节课我们会讨论,在下几节课我们会讨论一些方法用来解决这个问题。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y8bEbVMK-1575538721580)(https://www.github.com/OneJane/blog/raw/master/小书匠/8fb1c4afe30b7a0ede26522b355068ba.png)]
你已经知道了RNN的样子,现在我们举个语言模型的例子,假如看到这个句子(上图编号1所示),“The cat, which already ate ……, was full.”,前后应该保持一致,因为cat是单数,所以应该用was。“The cats, which ate ……, were full.”(上图编号2所示),cats是复数,所以用were。这个例子中的句子有长期的依赖,最前面的单词对句子后面的单词有影响。但是我们目前见到的基本的RNN模型(上图编号3所示的网络模型),不擅长捕获这种长期依赖效应,解释一下为什么。
你应该还记得之前讨论的训练很深的网络,我们讨论了梯度消失的问题。比如说一个很深很深的网络(上图编号4所示),100层,甚至更深,对这个网络从左到右做前向传播然后再反向传播。我们知道如果这是个很深的神经网络,从输出 y ^ \hat y y^得到的梯度很难传播回去,很难影响靠前层的权重,很难影响前面层(编号5所示的层)的计算。
对于有同样问题的RNN,首先从左到右前向传播,然后反向传播。但是反向传播会很困难,因为同样的梯度消失的问题,后面层的输出误差(上图编号6所示)很难影响前面层(上图编号7所示的层)的计算。这就意味着,实际上很难让一个神经网络能够意识到它要记住看到的是单数名词还是复数名词,然后在序列后面生成依赖单复数形式的was或者were。而且在英语里面,这中间的内容(上图编号8所示)可以任意长,对吧?所以你需要长时间记住单词是单数还是复数,这样后面的句子才能用到这些信息。也正是这个原因,所以基本的RNN模型会有很多局部影响,意味着这个输出 y ^ < 3 > \hat y^{<3>} y^<3>(上图编号9所示)主要受 y ^ < 3 > \hat y^{<3>} y^<3>附近的值(上图编号10所示)的影响,上图编号11所示的一个数值主要与附近的输入(上图编号12所示)有关,上图编号6所示的输出,基本上很难受到序列靠前的输入(上图编号10所示)的影响,这是因为不管输出是什么,不管是对的,还是错的,这个区域都很难反向传播到序列的前面部分,也因此网络很难调整序列前面的计算。这是基本的RNN算法的一个缺点,我们会在下几节视频里处理这个问题。如果不管的话,RNN会不擅长处理长期依赖的问题。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PCzcKDNl-1575538721581)(https://www.github.com/OneJane/blog/raw/master/小书匠/ac5d647140997ba713c376fb097ea0e2.png)]
尽管我们一直在讨论梯度消失问题,但是,你应该记得我们在讲很深的神经网络时,我们也提到了梯度爆炸,我们在反向传播的时候,随着层数的增多,梯度不仅可能指数型的下降,也可能指数型的上升。事实上梯度消失在训练RNN时是首要的问题,尽管梯度爆炸也是会出现,但是梯度爆炸很明显,因为指数级大的梯度会让你的参数变得极其大,以至于你的网络参数崩溃。所以梯度爆炸很容易发现,因为参数会大到崩溃,你会看到很多NaN,或者不是数字的情况,这意味着你的网络计算出现了数值溢出。如果你发现了梯度爆炸的问题,一个解决方法就是用梯度修剪。梯度修剪的意思就是观察你的梯度向量,如果它大于某个阈值,缩放梯度向量,保证它不会太大,这就是通过一些最大值来修剪的方法。所以如果你遇到了梯度爆炸,如果导数值很大,或者出现了NaN,就用梯度修剪,这是相对比较鲁棒的,这是梯度爆炸的解决方法。然而梯度消失更难解决,这也是我们下几节视频的主题。
总结一下,在前面的课程,我们了解了训练很深的神经网络时,随着层数的增加,导数有可能指数型的下降或者指数型的增加,我们可能会遇到梯度消失或者梯度爆炸的问题。加入一个RNN处理1,000个时间序列的数据集或者10,000个时间序列的数据集,这就是一个1,000层或者10,000层的神经网络,这样的网络就会遇到上述类型的问题。梯度爆炸基本上用梯度修剪就可以应对,但梯度消失比较棘手。我们下节会介绍GRU,门控循环单元网络,这个网络可以有效地解决梯度消失的问题,并且能够使你的神经网络捕获更长的长期依赖,我们去下个视频一探究竟吧。
你已经了解了基础的RNN模型的运行机制,在本节视频中你将会学习门控循环单元,它改变了RNN的隐藏层,使其可以更好地捕捉深层连接,并改善了梯度消失问题,让我们看一看。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T3peSdGY-1575538721581)(https://www.github.com/OneJane/blog/raw/master/小书匠/db720dae1606767241df59d8fd6079ee.png)]
你已经见过了这个公式, a < t > = g ( W a [ a < t − 1 > , x < t > ] + b a ) a^{< t >} = g(W_{a}\left\lbrack a^{< t - 1 >},x^{< t >}\right\rbrack +b_{a}) a<t>=g(Wa[a<t−1>,x<t>]+ba),在RNN的时间 t t t处,计算激活值。我把这个画个图,把RNN的单元画个图,画一个方框,输入 a < t − 1 > a^{
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hYaWYITY-1575538721582)(https://www.github.com/OneJane/blog/raw/master/小书匠/1521560729.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8HlcdFtD-1575538721582)(https://www.github.com/OneJane/blog/raw/master/小书匠/13cee4a2d0ad3897924c22d729c5b02d.png)]
许多GRU的想法都来分别自于Yu Young Chang, Kagawa,Gaza Hera, Chang Hung Chu和
Jose Banjo的两篇论文。我再引用上个视频中你已经见过的这个句子,“The cat, which already ate……, was full.”,你需要记得猫是单数的,为了确保你已经理解了为什么这里是was而不是were,“The cat was full.”或者是“The cats were full”。当我们从左到右读这个句子,GRU单元将会有个新的变量称为 c c c,代表细胞(cell),即记忆细胞(下图编号1所示)。记忆细胞的作用是提供了记忆的能力,比如说一只猫是单数还是复数,所以当它看到之后的句子的时候,它仍能够判断句子的主语是单数还是复数。于是在时间 t t t处,有记忆细胞 c < t > c^{
所以这些等式表示了GRU单元的计算,在每个时间步,我们将用一个候选值重写记忆细胞,即 c ~ < t > {\tilde{c}}^{
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xRJ1JUUR-1575538721583)(https://www.github.com/OneJane/blog/raw/master/小书匠/5e55fb9f6de649031e7f9a4b249f4fea.png)]
重点来了,在GRU中真正重要的思想是我们有一个门,我先把这个门叫做 Γ u \Gamma_{u} Γu(上图编号4所示),这是个下标为 u u u的大写希腊字母 Γ \Gamma Γ, u u u代表更新门,这是一个0到1之间的值。为了让你直观思考GRU的工作机制,先思考 Γ u \Gamma_{u} Γu,这个一直在0到1之间的门值,实际上这个值是把这个式子带入sigmoid函数得到的, Γ u = σ ( W u [ c < t − 1 > , x < t > ] + b u ) \Gamma_{u}= \sigma(W_{u}\left\lbrack c^{
然后GRU的关键部分就是上图编号3所示的等式,我们刚才写出来的用 c ~ \tilde{c} c~更新 c c c的等式。然后门决定是否要真的更新它。于是我们这么看待它,记忆细胞 c < t > c^{
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RtVwEdTa-1575538721583)(https://www.github.com/OneJane/blog/raw/master/小书匠/32f3b94e4e4523bc413d9687ec29e801.png)]
所以我们接下来要给GRU用的式子就是 c < t > = Γ u ∗ c ~ < t > + ( 1 − Γ u ) ∗ c < t − 1 > c^{
让我再画个图来(下图所示)解释一下GRU单元,顺便说一下,当你在看网络上的博客或者教科书或者教程之类的,这些图对于解释GRU和我们稍后会讲的LSTM是相当流行的,我个人感觉式子在图片中比较容易理解,那么即使看不懂图片也没关系,我就画画,万一能帮得上忙就最好了。
GRU单元输入 c < t − 1 > c^{
再用一个不同的参数集,通过sigmoid激活函数算出 Γ u \Gamma_{u} Γu, Γ u = σ ( W u [ c < t − 1 > , x < t > ] + b u ) \Gamma_{u}= \sigma(W_{u}\left\lbrack c^{
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QDVncdn2-1575538721584)(https://www.github.com/OneJane/blog/raw/master/小书匠/cfa628f62f1c57ee6213793a438957a3.png)]
这就是GRU单元或者说是一个简化过的GRU单元,它的优点就是通过门决定,当你从左(上图编号10所示)到右扫描一个句子的时候,这个时机是要更新某个记忆细胞,还是不更新,不更新(上图编号11所示,中间 Γ u = 0 \Gamma_{u}=0 Γu=0一直为0,表示一直不更新)直到你到你真的需要使用记忆细胞的时候(上图编号12所示),这可能在句子之前就决定了。因为sigmoid的值,现在因为门很容易取到0值,只要这个值是一个很大的负数,再由于数值上的四舍五入,上面这些门大体上就是0,或者说非常非常非常接近0。所以在这样的情况下,这个更新式子(上图编号13所示的等式)就会变成 c < t > = c < t − 1 > c^{
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XdtStW2O-1575538721584)(https://www.github.com/OneJane/blog/raw/master/小书匠/c1df3f793dcb1ec681db6757b4974cee.png)]
现在我想说下一些实现的细节,在这个我写下的式子中 c < t > c^{
当然在实际应用中 Γ u \Gamma_{u} Γu不会真的等于0或者1,有时候它是0到1的一个中间值(上图编号5所示),但是这对于直观思考是很方便的,就把它当成确切的0,完全确切的0或者就是确切的1。元素对应的乘积做的就是告诉GRU单元哪个记忆细胞的向量维度在每个时间步要做更新,所以你可以选择保存一些比特不变,而去更新其他的比特。比如说你可能需要一个比特来记忆猫是单数还是复数,其他比特来理解你正在谈论食物,因为你在谈论吃饭或者食物,然后你稍后可能就会谈论“The cat was full.”,你可以每个时间点只改变一些比特。
你现在已经理解GRU最重要的思想了,幻灯片中展示的实际上只是简化过的GRU单元,现在来描述一下完整的GRU单元。
对于完整的GRU单元我要做的一个改变就是在我们计算的第一个式子中给记忆细胞的新候选值加上一个新的项,我要添加一个门 Γ r \Gamma_{r} Γr(下图编号1所示),你可以认为 r r r代表相关性(relevance)。这个 Γ r \Gamma_{r} Γr门告诉你计算出的下一个 c < t > c^{
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ru4Pvwtz-1575538721585)(https://www.github.com/OneJane/blog/raw/master/小书匠/daf9e8fa30888b6a1b407ca6ea303984.png)]
正如你所见,有很多方法可以来设计这些类型的神经网络,然后我们为什么有 Γ r \Gamma_{r} Γr?为什么不用上一张幻灯片里的简单的版本?这是因为多年来研究者们试验过很多很多不同可能的方法来设计这些单元,去尝试让神经网络有更深层的连接,去尝试产生更大范围的影响,还有解决梯度消失的问题,GRU就是其中一个研究者们最常使用的版本,也被发现在很多不同的问题上也是非常健壮和实用的。你可以尝试发明新版本的单元,只要你愿意。但是GRU是一个标准版本,也就是最常使用的。你可以想象到研究者们也尝试了很多其他版本,类似这样的但不完全是,比如我这里写的这个。然后另一个常用的版本被称为LSTM,表示长短时记忆网络,这个我们会在下节视频中讲到,但是GRU和LSTM是在神经网络结构中最常用的两个具体实例。
还有在符号上的一点,我尝试去定义固定的符号让这些概念容易理解,如果你看学术文章的话,你有的时候会看到有些人使用另一种符号 x ~ \tilde{x} x~, u u u, r r r和 h h h表示这些量。但我试着在GRU和LSTM之间用一种更固定的符号,比如使用更固定的符号 Γ \Gamma Γ来表示门,所以希望这能让这些概念更好理解。
所以这就是GRU,即门控循环单元,这是RNN的其中之一。这个结构可以更好捕捉非常长范围的依赖,让RNN更加有效。然后我简单提一下其他常用的神经网络,比较经典的是这个叫做LSTM,即长短时记忆网络,我们在下节视频中讲解。
(Chung J, Gulcehre C, Cho K H, et al. Empirical Evaluation of Gated Recurrent Neural Networks on Sequence Modeling[J]. Eprint Arxiv, 2014.
Cho K, Merrienboer B V, Bahdanau D, et al. On the Properties of Neural Machine Translation: Encoder-Decoder Approaches[J]. Computer Science, 2014.)
在上一个视频中你已经学了GRU(门控循环单元)。它能够让你可以在序列中学习非常深的连接。其他类型的单元也可以让你做到这个,比如LSTM即长短时记忆网络,甚至比GRU更加有效,让我们看看。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mq4Vxg3V-1575538721585)(https://www.github.com/OneJane/blog/raw/master/小书匠/523650730db3f0d5c05a7192da02f878.png)]
这里是上个视频中的式子,对于GRU我们有 a < t > = c < t > a^{< t >} = c^{
还有两个门:
更新门 Γ u \Gamma_{u} Γu(the update gate)
相关门 Γ r \Gamma_{r} Γr(the relevance gate)
c ~ < t > {\tilde{c}}^{
LSTM是一个比GRU更加强大和通用的版本,这多亏了 Sepp Hochreiter和 Jurgen Schmidhuber,感谢那篇开创性的论文,它在序列模型上有着巨大影响。我感觉这篇论文是挺难读懂的,虽然我认为这篇论文在深度学习社群有着重大的影响,它深入讨论了梯度消失的理论,我感觉大部分的人学到LSTM的细节是在其他的地方,而不是这篇论文。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qQjYXyEE-1575538721586)(https://www.github.com/OneJane/blog/raw/master/小书匠/9456d50c55cf0408a3fb2b6e903d85d6.png)]
这就是LSTM主要的式子(上图编号2所示),我们继续回到记忆细胞c上面来,使用 c ~ < t > = t a n h ( W c [ a < t − 1 > , x < t > ] + b c {\tilde{c}}^{
我们像以前那样有一个更新门 Γ u \Gamma_{u} Γu和表示更新的参数 W u W_{u} Wu, Γ u = σ ( W u [ a < t − 1 > , x < t > ] + b u ) \Gamma_{u}= \sigma(W_{u}\left\lbrack a^{
然后这里(上图编号7所示)用遗忘门(the forget gate),我们叫它 Γ f \Gamma_{f} Γf,所以这个 Γ f = σ ( W f [ a < t − 1 > , x < t > ] + b f ) \Gamma_{f} =\sigma(W_{f}\left\lbrack a^{
然后我们有一个新的输出门, Γ o = σ ( W o [ a < t − 1 > , x < t > ] + > b o ) \Gamma_{o} =\sigma(W_{o}\left\lbrack a^{
于是记忆细胞的更新值 c < t > = Γ u ∗ c ~ < t > + Γ f ∗ c < t − 1 > c^{
所以这给了记忆细胞选择权去维持旧的值 c < t − 1 > c^{
然后这个表示更新门( Γ u = σ ( W u [ a < t − 1 > , x < t > ] + b u ) \Gamma_{u}= \sigma(W_{u}\left\lbrack a^{
遗忘门( Γ f = σ ( W f [ a < t − 1 > , x < t > ] + b f ) \Gamma_{f} =\sigma(W_{f}\left\lbrack a^{
最后 a < t > = c < t > a^{
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rFc5bFAI-1575538721587)(https://www.github.com/OneJane/blog/raw/master/小书匠/94e871edbd87337937ce374e71d56e42.png)]
再提一下,这些式子就是控制LSTM行为的主要的式子了(上图编号1所示)。像之前一样用图片稍微解释一下,先让我把图画在这里(上图编号2所示)。如果图片过于复杂,别担心,我个人感觉式子比图片好理解,但是我画图只是因为它比较直观。这个右上角的图的灵感来自于Chris Ola的一篇博客,标题是《理解LSTM网络》(Understanding LSTM Network),这里的这张图跟他博客上的图是很相似的,但关键的不同可能是这里的这张图用了 a < t − 1 > a^{
这里其中一个元素很有意思,如你在这一堆图(上图编号8所示的一系列图片)中看到的,这是其中一个,再把他们连起来,就是把它们按时间次序连起来,这里(上图编号9所示)输入 x < 1 > x^{<1>} x<1>,然后 x < 2 > x^{<2>} x<2>, x < 3 > x^{<3>} x<3>,然后你可以把这些单元依次连起来,这里输出了上一个时间的 a a a, a a a会作为下一个时间步的输入, c c c同理。在下面这一块,我把图简化了一下(相对上图编号2所示的图有所简化)。然后这有个有意思的事情,你会注意到上面这里有条线(上图编号10所示的线),这条线显示了只要你正确地设置了遗忘门和更新门,LSTM是相当容易把 c < 0 > c^{<0>} c<0>的值(上图编号11所示)一直往下传递到右边,比如 c < 3 > = c < 0 > c^{<3>} = c^{<0>} c<3>=c<0>(上图编号12所示)。这就是为什么LSTM和GRU非常擅长于长时间记忆某个值,对于存在记忆细胞中的某个值,即使经过很长很长的时间步。
这就是LSTM,你可能会想到这里和一般使用的版本会有些不同,最常用的版本可能是门值不仅取决于 a < t − 1 > a^{
如你所见LSTM主要的区别在于一个技术上的细节,比如这(上图编号13所示)有一个100维的向量,你有一个100维的隐藏的记忆细胞单元,然后比如第50个 c < t − 1 > c^{
LSTM前向传播图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xe5I7JCm-1575538721588)(https://www.github.com/OneJane/blog/raw/master/小书匠/LSTM.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u8LcuFhg-1575538721589)(https://www.github.com/OneJane/blog/raw/master/小书匠/LSTM_rnn.png)]
LSTM反向传播计算:
门求偏导:
KaTeX parse error: \tag works only in display equations
KaTeX parse error: \tag works only in display equations
KaTeX parse error: \tag works only in display equations
KaTeX parse error: \tag works only in display equations
参数求偏导 :
$ dW_f = d\Gamma_f^{\langle t \rangle} * \begin{pmatrix} a_{prev} \ x_t\end{pmatrix}^T \tag{5} $
$ dW_u = d\Gamma_u^{\langle t \rangle} * \begin{pmatrix} a_{prev} \ x_t\end{pmatrix}^T \tag{6} $
$ dW_c = d\tilde c^{\langle t \rangle} * \begin{pmatrix} a_{prev} \ x_t\end{pmatrix}^T \tag{7} $
$ dW_o = d\Gamma_o^{\langle t \rangle} * \begin{pmatrix} a_{prev} \ x_t\end{pmatrix}^T \tag{8}$
为了计算 d b f , d b u , d b c , d b o db_f, db_u, db_c, db_o dbf,dbu,dbc,dbo 需要各自对 d Γ f ⟨ t ⟩ , d Γ u ⟨ t ⟩ , d c ~ ⟨ t ⟩ , d Γ o ⟨ t ⟩ d\Gamma_f^{\langle t \rangle}, d\Gamma_u^{\langle t \rangle}, d\tilde c^{\langle t \rangle}, d\Gamma_o^{\langle t \rangle} dΓf⟨t⟩,dΓu⟨t⟩,dc~⟨t⟩,dΓo⟨t⟩ 求和。
最后,计算隐藏状态、记忆状态和输入的偏导数:
$ da_{prev} = W_fT*d\Gamma_f{\langle t \rangle} + W_u^T * d\Gamma_u^{\langle t \rangle}+ W_c^T * d\tilde c^{\langle t \rangle} + W_o^T * d\Gamma_o^{\langle t \rangle} \tag{9}$
$ dc_{prev} = dc_{next}\Gamma_f^{\langle t \rangle} + \Gamma_o^{\langle t \rangle} * (1- \tanh(c_{next})2)*\Gamma_f{\langle t \rangle}*da_{next} \tag{10}$
$ dx^{\langle t \rangle} = W_fT*d\Gamma_f{\langle t \rangle} + W_u^T * d\Gamma_u^{\langle t \rangle}+ W_c^T * d\tilde c_t + W_o^T * d\Gamma_o^{\langle t \rangle}\tag{11} $
这就是LSTM,我们什么时候应该用GRU?什么时候用LSTM?这里没有统一的准则。而且即使我先讲解了GRU,在深度学习的历史上,LSTM也是更早出现的,而GRU是最近才发明出来的,它可能源于Pavia在更加复杂的LSTM模型中做出的简化。研究者们在很多不同问题上尝试了这两种模型,看看在不同的问题不同的算法中哪个模型更好,所以这不是个学术和高深的算法,我才想要把这两个模型展示给你。
GRU的优点是这是个更加简单的模型,所以更容易创建一个更大的网络,而且它只有两个门,在计算性上也运行得更快,然后它可以扩大模型的规模。
但是LSTM更加强大和灵活,因为它有三个门而不是两个。如果你想选一个使用,我认为LSTM在历史进程上是个更优先的选择,所以如果你必须选一个,我感觉今天大部分的人还是会把LSTM作为默认的选择来尝试。虽然我认为最近几年GRU获得了很多支持,而且我感觉越来越多的团队也正在使用GRU,因为它更加简单,而且还效果还不错,它更容易适应规模更加大的问题。
所以这就是LSTM,无论是GRU还是LSTM,你都可以用它们来构建捕获更加深层连接的神经网络。
(Hochreiter S, Schmidhuber J. Long Short-Term Memory[J]. Neural Computation, 1997, 9(8):1735-1780.)
现在,你已经了解了大部分RNN模型的关键的构件,还有两个方法可以让你构建更好的模型,其中之一就是双向RNN模型,这个模型可以让你在序列的某点处不仅可以获取之前的信息,还可以获取未来的信息,我们会在这个视频里讲解。第二个就是深层的RNN,我们会在下个视频里见到,现在先从双向RNN开始吧。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GkbXmljI-1575538721589)(https://www.github.com/OneJane/blog/raw/master/小书匠/63be3fd3701604f94b45a08f1d8b460d.png)]
为了了解双向RNN的动机,我们先看一下之前在命名实体识别中已经见过多次的神经网络。这个网络有一个问题,在判断第三个词Teddy(上图编号1所示)是不是人名的一部分时,光看句子前面部分是不够的,为了判断 y ^ < 3 > \hat y^{<3>} y^<3>(上图编号2所示)是0还是1,除了前3个单词,你还需要更多的信息,因为根据前3个单词无法判断他们说的是Teddy熊,还是前美国总统Teddy Roosevelt,所以这是一个非双向的或者说只有前向的RNN。我刚才所说的总是成立的,不管这些单元(上图编号3所示)是标准的RNN块,还是GRU单元或者是LSTM单元,只要这些构件都是只有前向的。
那么一个双向的RNN是如何解决这个问题的?下面解释双向RNN的工作原理。为了简单,我们用四个输入或者说一个只有4个单词的句子,这样输入只有4个, x < 1 > x^{<1>} x<1>到 x < 4 > x^{<4>} x<4>。从这里开始的这个网络会有一个前向的循环单元叫做 a → < 1 > {\overrightarrow{a}}^{<1>} a<1>, a → < 2 > {\overrightarrow{a}}^{<2>} a<2>, a → < 3 > {\overrightarrow{a}}^{<3>} a<3>还有 a → < 4 > {\overrightarrow{a}}^{<4>} a<4>,我在这上面加个向右的箭头来表示前向的循环单元,并且他们这样连接(下图编号1所示)。这四个循环单元都有一个当前输入 x x x输入进去,得到预测的 y ^ < 1 > \hat y^{<1>} y^<1>, y ^ < 2 > \hat y^{<2>} y^<2>, y ^ < 3 > \hat y^{<3>} y^<3>和 y ^ < 4 > \hat y^{<4>} y^<4>。
到目前为止,我还没做什么,仅仅是把前面幻灯片里的RNN画在了这里,只是在这些地方画上了箭头。我之所以在这些地方画上了箭头是因为我们想要增加一个反向循环层,这里有个 a ← < 1 > {\overleftarrow{a}}^{<1>} a<1>,左箭头代表反向连接, a ← < 2 > {\overleftarrow{a}}^{<2>} a<2>反向连接, a ← < 3 > {\overleftarrow{a}}^{<3>} a<3>反向连接, a ← < 4 > {\overleftarrow{a}}^{<4>} a<4>反向连接,所以这里的左箭头代表反向连接。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bm7PUPLU-1575538721590)(https://www.github.com/OneJane/blog/raw/master/小书匠/48c787912f7f8daee638dd311583d6cf.png)]
同样,我们把网络这样向上连接,这个 a a a反向连接就依次反向向前连接(上图编号2所示)。这样,这个网络就构成了一个无环图。给定一个输入序列 x < 1 > x^{<1>} x<1>到 x < 4 > x^{<4>} x<4>,这个序列首先计算前向的 a → < 1 > {\overrightarrow{a}}^{<1>} a<1>,然后计算前向的 a → < 2 > {\overrightarrow{a}}^{<2>} a<2>,接着 a → < 3 > {\overrightarrow{a}}^{<3>} a<3>, a → < 4 > {\overrightarrow{a}}^{<4>} a<4>。而反向序列从计算 a ← < 4 > {\overleftarrow{a}}^{<4>} a<4>开始,反向进行,计算反向的 a ← < 3 > {\overleftarrow{a}}^{<3>} a<3>。你计算的是网络激活值,这不是反向而是前向的传播,而图中这个前向传播一部分计算是从左到右,一部分计算是从右到左。计算完了反向的 a ← < 3 > {\overleftarrow{a}}^{<3>} a<3>,可以用这些激活值计算反向的 a ← < 2 > {\overleftarrow{a}}^{<2>} a<2>,然后是反向的 a ← < 1 > {\overleftarrow{a}}^{<1>} a<1>,把所有这些激活值都计算完了就可以计算预测结果了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oGLh4JWj-1575538721591)(https://www.github.com/OneJane/blog/raw/master/小书匠/053831ff43d039bd5e734df96d8794cb.png)]
举个例子,为了预测结果,你的网络会有如 y ^ < t > \hat y^{
这就是双向循环神经网络,并且这些基本单元不仅仅是标准RNN单元,也可以是GRU单元或者LSTM单元。事实上,很多的NLP问题,对于大量有自然语言处理问题的文本,有LSTM单元的双向RNN模型是用的最多的。所以如果有NLP问题,并且文本句子都是完整的,首先需要标定这些句子,一个有LSTM单元的双向RNN模型,有前向和反向过程是一个不错的首选。
以上就是双向RNN的内容,这个改进的方法不仅能用于基本的RNN结构,也能用于GRU和LSTM。通过这些改变,你就可以用一个用RNN或GRU或LSTM构建的模型,并且能够预测任意位置,即使在句子的中间,因为模型能够考虑整个句子的信息。这个双向RNN网络模型的缺点就是你需要完整的数据的序列,你才能预测任意位置。比如说你要构建一个语音识别系统,那么双向RNN模型需要你考虑整个语音表达,但是如果直接用这个去实现的话,你需要等待这个人说完,然后获取整个语音表达才能处理这段语音,并进一步做语音识别。对于实际的语音识别的应用通常会有更加复杂的模块,而不是仅仅用我们见过的标准的双向RNN模型。但是对于很多自然语言处理的应用,如果你总是可以获取整个句子,这个标准的双向RNN算法实际上很高效。
好的,这就是双向RNN,下一个视频,也是这周的最后一个,我们会讨论如何用这些概念,标准的RNN,LSTM单元,GRU单元,还有双向的版本,构建更深的网络。
目前你学到的不同RNN的版本,每一个都可以独当一面。但是要学习非常复杂的函数,通常我们会把RNN的多个层堆叠在一起构建更深的模型。这节视频里我们会学到如何构建这些更深的RNN。
一个标准的神经网络,首先是输入 x x x,然后堆叠上隐含层,所以这里应该有激活值,比如说第一层是 a [ 1 ] a^{\left\lbrack 1 \right\rbrack} a[1],接着堆叠上下一层,激活值 a [ 2 ] a^{\left\lbrack 2 \right\rbrack} a[2],可以再加一层 a [ 3 ] a^{\left\lbrack 3 \right\rbrack} a[3],然后得到预测值 y ^ \hat{y} y^。深层的RNN网络跟这个有点像,用手画的这个网络(下图编号1所示),然后把它按时间展开就是了,我们看看。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bzNPVI2u-1575538721591)(https://www.github.com/OneJane/blog/raw/master/小书匠/8378c2bfe73e1ac9f85d6aa79b71b5eb.png)]
这是我们一直见到的标准的RNN(上图编号3所示方框内的RNN),只是我把这里的符号稍微改了一下,不再用原来的 a < 0 > a^{<0 >} a<0>表示0时刻的激活值了,而是用 a [ 1 ] < 0 > a^{\lbrack 1\rbrack <0>} a[1]<0>来表示第一层(上图编号4所示),所以我们现在用 a [ l ] < t > a^{\lbrack l\rbrack
我们看个具体的例子,看看这个值( a [ 2 ] < 3 > a^{\lbrack 2\rbrack <3>} a[2]<3>,上图编号5所示)是怎么算的。激活值 a [ 2 ] < 3 > a^{\lbrack 2\rbrack <3>} a[2]<3>有两个输入,一个是从下面过来的输入(上图编号6所示),还有一个是从左边过来的输入(上图编号7所示), a [ 2 ] < 3 > = g ( W a [ 2 ] [ a [ 2 ] < 2 > , a [ 1 ] < 3 > ] + b a [ 2 ] ) a^{\lbrack 2\rbrack < 3 >} = g(W_{a}^{\left\lbrack 2 \right\rbrack}\left\lbrack a^{\left\lbrack 2 \right\rbrack < 2 >},a^{\left\lbrack 1 \right\rbrack < 3 >} \right\rbrack + b_{a}^{\left\lbrack 2 \right\rbrack}) a[2]<3>=g(Wa[2][a[2]<2>,a[1]<3>]+ba[2]),这就是这个激活值的计算方法。参数 W a [ 2 ] W_{a}^{\left\lbrack 2 \right\rbrack} Wa[2]和 b a [ 2 ] b_{a}^{\left\lbrack 2 \right\rbrack} ba[2]在这一层的计算里都一样,相对应地第一层也有自己的参数 W a [ 1 ] W_{a}^{\left\lbrack 1 \right\rbrack} Wa[1]和 b a [ 1 ] b_{a}^{\left\lbrack 1 \right\rbrack} ba[1]。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2mR8m825-1575538721592)(https://www.github.com/OneJane/blog/raw/master/小书匠/455863a3c8c2dfaa0e5474bfa2c6824d.png)]
对于像左边这样标准的神经网络,你可能见过很深的网络,甚至于100层深,而对于RNN来说,有三层就已经不少了。由于时间的维度,RNN网络会变得相当大,即使只有很少的几层,很少会看到这种网络堆叠到100层。但有一种会容易见到,就是在每一个上面堆叠循环层,把这里的输出去掉(上图编号1所示),然后换成一些深的层,这些层并不水平连接,只是一个深层的网络,然后用来预测 y < 1 > y^{<1>} y<1>。同样这里(上图编号2所示)也加上一个深层网络,然后预测 y < 2 > y^{<2>} y<2>。这种类型的网络结构用的会稍微多一点,这种结构有三个循环单元,在时间上连接,接着一个网络在后面接一个网络,当然 y < 3 > y^{<3>} y<3>和 y < 4 > y^{<4>} y<4>也一样,这是一个深层网络,但没有水平方向上的连接,所以这种类型的结构我们会见得多一点。通常这些单元(上图编号3所示)没必要非是标准的RNN,最简单的RNN模型,也可以是GRU单元或者LSTM单元,并且,你也可以构建深层的双向RNN网络。由于深层的RNN训练需要很多计算资源,需要很长的时间,尽管看起来没有多少循环层,这个也就是在时间上连接了三个深层的循环层,你看不到多少深层的循环层,不像卷积神经网络一样有大量的隐含层。
这就是深层RNN的内容,从基本的RNN网络,基本的循环单元到GRU,LSTM,再到双向RNN,还有深层版的模型。这节课后,你已经可以构建很不错的学习序列的模型了。
上周我们学习了RNN、GRU单元和LSTM单元。本周你会看到我们如何把这些知识用到NLP上,用于自然语言处理,深度学习已经给这一领域带来了革命性的变革。其中一个很关键的概念就是词嵌入(word embeddings),这是语言表示的一种方式,可以让算法自动的理解一些类似的词,比如男人对女人,比如国王对王后,还有其他很多的例子。通过词嵌入的概念你就可以构建NLP应用了,即使你的模型标记的训练集相对较小。这周的最后我们会消除词嵌入的偏差,就是去除不想要的特性,或者学习算法有时会学到的其他类型的偏差。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lY3UAZEO-1575538721593)(https://www.github.com/OneJane/blog/raw/master/小书匠/68d7c930146724f39782cb57d33161e9.png)]
现在我们先开始讨论词汇表示,目前为止我们一直都是用词汇表来表示词,上周提到的词汇表,可能是10000个单词,我们一直用one-hot向量来表示词。比如如果man(上图编号1所示)在词典里是第5391个,那么就可以表示成一个向量,只在第5391处为1(上图编号2所示),我们用 O 5391 O_{5391} O5391代表这个量,这里的 O O O代表one-hot。接下来,如果woman是编号9853(上图编号3所示),那么就可以用 O 9853 O_{9853} O9853来表示,这个向量只在9853处为1(上图编号4所示),其他为0,其他的词king、queen、apple、orange都可以这样表示出来这种表示方法的一大缺点就是它把每个词孤立起来,这样使得算法对相关词的泛化能力不强。
举个例子,假如你已经学习到了一个语言模型,当你看到“I want a glass of orange ___”,那么下一个词会是什么?很可能是juice。即使你的学习算法已经学到了“I want a glass of orange juice”这样一个很可能的句子,但如果看到“I want a glass of apple ___”,因为算法不知道apple和orange的关系很接近,就像man和woman,king和queen一样。所以算法很难从已经知道的orange juice是一个常见的东西,而明白apple juice也是很常见的东西或者说常见的句子。这是因为任何两个one-hot向量的内积都是0,如果你取两个向量,比如king和queen,然后计算它们的内积,结果就是0。如果用apple和orange来计算它们的内积,结果也是0。很难区分它们之间的差别,因为这些向量内积都是一样的,所以无法知道apple和orange要比king和orange,或者queen和orange相似地多。
换一种表示方式会更好,如果我们不用one-hot表示,而是用特征化的表示来表示每个词,man,woman,king,queen,apple,orange或者词典里的任何一个单词,我们学习这些词的特征或者数值。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nZFQ19hn-1575538721593)(https://www.github.com/OneJane/blog/raw/master/小书匠/ce30c9ae7912bdb3562199bf85eca1cd.png)]
举个例子,对于这些词,比如我们想知道这些词与Gender(性别)的关系。假定男性的性别为-1,女性的性别为+1,那么man的性别值可能就是-1,而woman就是-1。最终根据经验king就是-0.95,queen是+0.97,apple和orange没有性别可言。
另一个特征可以是这些词有多Royal(高贵),所以这些词,man,woman和高贵没太关系,所以它们的特征值接近0。而king和queen很高贵,apple和orange跟高贵也没太大关系。
那么Age(年龄)呢?man和woman一般没有年龄的意思,也许man和woman隐含着成年人的意思,但也可能是介于young和old之间,所以它们(man和woman)的值也接近0。而通常king和queen都是成年人,apple和orange跟年龄更没什么关系了。
还有一个特征,这个词是否是Food(食物),man不是食物,woman不是食物,king和queen也不是,但apple和orange是食物。
当然还可以有很多的其他特征,从Size(尺寸大小),Cost(花费多少),这个东西是不是alive(活的),是不是一个Action(动作),或者是不是Noun(名词)或者是不是Verb(动词),还是其他的等等。
所以你可以想很多的特征,为了说明,我们假设有300个不同的特征,这样的话你就有了这一列数字(上图编号1所示),这里我只写了4个,实际上是300个数字,这样就组成了一个300维的向量来表示man这个词。接下来,我想用 e 5391 e_{5391} e5391这个符号来表示,就像这样(上图编号2所示)。同样这个300维的向量,我用 e 9853 e_{9853} e9853代表这个300维的向量用来表示woman这个词(上图编号3所示),这些其他的例子也一样。现在,如果用这种表示方法来表示apple和orange这些词,那么apple和orange的这种表示肯定会非常相似,可能有些特征不太一样,因为orange的颜色口味,apple的颜色口味,或者其他的一些特征会不太一样,但总的来说apple和orange的大部分特征实际上都一样,或者说都有相似的值。这样对于已经知道orange juice的算法很大几率上也会明白apple
juice这个东西,这样对于不同的单词算法会泛化的更好。
后面的几个视频,我们会找到一个学习词嵌入的方式,这里只是希望你能理解这种高维特征的表示能够比one-hot更好的表示不同的单词。而我们最终学习的特征不会像这里一样这么好理解,没有像第一个特征是性别,第二个特征是高贵,第三个特征是年龄等等这些,新的特征表示的东西肯定会更难搞清楚。尽管如此,接下来要学的特征表示方法却能使算法高效地发现apple和orange会比king和orange,queen和orange更加相似。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2WQ4tzTp-1575538721594)(https://www.github.com/OneJane/blog/raw/master/小书匠/59fb45cfdf7faa53571ec7b921b78358.png)]
如果我们能够学习到一个300维的特征向量,或者说300维的词嵌入,通常我们可以做一件事,把这300维的数据嵌入到一个二维空间里,这样就可以可视化了。常用的可视化算法是t-SNE算法,来自于Laurens van der Maaten 和 Geoff Hinton的论文。如果观察这种词嵌入的表示方法,你会发现man和woman这些词聚集在一块(上图编号1所示),king和queen聚集在一块(上图编号2所示),这些都是人,也都聚集在一起(上图编号3所示)。动物都聚集在一起(上图编号4所示),水果也都聚集在一起(上图编号5所示),像1、2、3、4这些数字也聚集在一起(上图编号6所示)。如果把这些生物看成一个整体,他们也聚集在一起(上图编号7所示)。
在网上你可能会看到像这样的图用来可视化,300维或者更高维度的嵌入。希望你能有个整体的概念,这种词嵌入算法对于相近的概念,学到的特征也比较类似,在对这些概念可视化的时候,这些概念就比较相似,最终把它们映射为相似的特征向量。这种表示方式用的是在300维空间里的特征表示,这叫做嵌入(embeddings)。之所以叫嵌入的原因是,你可以想象一个300维的空间,我画不出来300维的空间,这里用个3维的代替(上图编号8所示)。现在取每一个单词比如orange,它对应一个3维的特征向量,所以这个词就被嵌在这个300维空间里的一个点上了(上图编号9所示),apple这个词就被嵌在这个300维空间的另一个点上了(上图编号10所示)。为了可视化,t-SNE算法把这个空间映射到低维空间,你可以画出一个2维图像然后观察,这就是这个术语嵌入的来源。
词嵌入已经是NLP领域最重要的概念之一了,在自然语言处理领域。本节视频中你已经知道为什么要学习或者使用词嵌入了,下节视频我们会深入讲解如何用这些算法构建NLP算法。
上一个视频中,你已经了解不同单词的特征化表示了。这节你会看到我们如何把这种表示方法应用到NLP应用中。
我们从一个例子开始,我们继续用命名实体识别的例子,如果你要找出人名,假如有一个句子:“Sally Johnson is an orange farmer.”(Sally Johnson是一个种橙子的农民),你会发现Sally Johnson就是一个人名,所以这里的输出为1。之所以能确定Sally Johnson是一个人名而不是一个公司名,是因为你知道种橙子的农民一定是一个人,前面我们已经讨论过用one-hot来表示这些单词, x < 1 > x^{<1>} x<1> , x < 2 > x^{< 2 >} x<2>等等。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yiPYXrtW-1575538721595)(https://www.github.com/OneJane/blog/raw/master/小书匠/b4bf4b0cdcef0c9d021707c47d5aecda.png)]
但是如果你用特征化表示方法,嵌入的向量,也就是我们在上个视频中讨论的。那么用词嵌入作为输入训练好的模型,如果你看到一个新的输入:“Robert Lin is an apple farmer.”(Robert Lin是一个种苹果的农民),因为知道orange和apple很相近,那么你的算法很容易就知道Robert Lin也是一个人,也是一个人的名字。一个有意思的情况是,要是测试集里这句话不是“Robert Lin is an apple farmer.”,而是不太常见的词怎么办?要是你看到:“Robert Lin is a durian cultivator.”(Robert Lin是一个榴莲培育家)怎么办?榴莲(durian)是一种比较稀罕的水果,这种水果在新加坡和其他一些国家流行。如果对于一个命名实体识别任务,你只有一个很小的标记的训练集,你的训练集里甚至可能没有durian(榴莲)或者cultivator(培育家)这两个词。但是如果你有一个已经学好的词嵌入,它会告诉你durian(榴莲)是水果,就像orange(橙子)一样,并且cultivator(培育家),做培育工作的人其实跟farmer(农民)差不多,那么你就有可能从你的训练集里的“an orange farmer”(种橙子的农民)归纳出“a durian cultivator”(榴莲培育家)也是一个人。
词嵌入能够达到这种效果,其中一个原因就是学习词嵌入的算法会考察非常大的文本集,也许是从网上找到的,这样你可以考察很大的数据集可以是1亿个单词,甚至达到100亿也都是合理的,大量的无标签的文本的训练集。通过考察大量的无标签文本,很多都是可以免费下载的,你可以发现orange(橙子)和durian(榴莲)相近,farmer(农民)和cultivator(培育家)相近。因此学习这种嵌入表达,把它们都聚集在一块,通过读取大量的互联网文本发现了orange(橙子)和durian(榴莲)都是水果。接下来你可以把这个词嵌入应用到你的命名实体识别任务当中,尽管你只有一个很小的训练集,也许训练集里有100,000个单词,甚至更小,这就使得你可以使用迁移学习,把你从互联网上免费获得的大量的无标签文本中学习到的知识,能够分辨orange(橙子)、apple(苹果)和durian(榴莲)都是水果的知识,然后把这些知识迁移到一个任务中,比如你只有少量标记的训练数据集的命名实体识别任务中。当然了,这里为了简化我只画了单向的RNN,事实上如果你想用在命名实体识别任务上,你应该用一个双向的RNN,而不是这样一个简单的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MIpqYWwK-1575538721595)(https://www.github.com/OneJane/blog/raw/master/小书匠/8a1d58b7ade17208053c10728b2bf3b6.png)]
总结一下,这是如何用词嵌入做迁移学习的步骤。
第一步,先从大量的文本集中学习词嵌入。一个非常大的文本集,或者可以下载网上预训练好的词嵌入模型,网上你可以找到不少,词嵌入模型并且都有许可。
第二步,你可以用这些词嵌入模型把它迁移到你的新的只有少量标注训练集的任务中,比如说用这个300维的词嵌入来表示你的单词。这样做的一个好处就是你可以用更低维度的特征向量代替原来的10000维的one-hot向量,现在你可以用一个300维更加紧凑的向量。尽管one-hot向量很快计算,而学到的用于词嵌入的300维的向量会更加紧凑。
第三步,当你在你新的任务上训练模型时,在你的命名实体识别任务上,只有少量的标记数据集上,你可以自己选择要不要继续微调,用新的数据调整词嵌入。实际中,只有这个第二步中有很大的数据集你才会这样做,如果你标记的数据集不是很大,通常我不会在微调词嵌入上费力气。
当你的任务的训练集相对较小时,词嵌入的作用最明显,所以它广泛用于NLP领域。我只提到一些,不要太担心这些术语(下问列举的一些NLP任务),它已经用在命名实体识别,用在文本摘要,用在文本解析、指代消解,这些都是非常标准的NLP任务。
词嵌入在语言模型、机器翻译领域用的少一些,尤其是你做语言模型或者机器翻译任务时,这些任务你有大量的数据。在其他的迁移学习情形中也一样,如果你从某一任务A迁移到某个任务B,只有A中有大量数据,而B中数据少时,迁移的过程才有用。所以对于很多NLP任务这些都是对的,而对于一些语言模型和机器翻译则不然。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I0kSkdi4-1575538721596)(https://www.github.com/OneJane/blog/raw/master/小书匠/43943c791844cc7f077f6c6f98f1f629.png)]
最后,词嵌入和人脸编码之间有奇妙的关系,你已经在前面的课程学到了关于人脸编码的知识了,如果你上了卷积神经网络的课程的话。你应该还记得对于人脸识别,我们训练了一个Siamese网络结构,这个网络会学习不同人脸的一个128维表示,然后通过比较编码结果来判断两个图片是否是同一个人脸,这个词嵌入的意思和这个差不多。在人脸识别领域大家喜欢用编码这个词来指代这些向量 f ( x ( i ) ) f(x^{\left(i \right)}) f(x(i)), f ( x ( j ) ) f(x^{\left( j\right)}) f(x(j))(上图编号1所示),人脸识别领域和这里的词嵌入有一个不同就是,在人脸识别中我们训练一个网络,任给一个人脸照片,甚至是没有见过的照片,神经网络都会计算出相应的一个编码结果。上完后面几节课,你会更明白,我们学习词嵌入则是有一个固定的词汇表,比如10000个单词,我们学习向量 e 1 e_{1} e1到 e 10000 e_{10000} e10000,学习一个固定的编码,每一个词汇表的单词的固定嵌入,这就是人脸识别与我们接下来几节视频要讨论的算法之间的一个不同之处。这里的术语编码(encoding)和嵌入(embedding)可以互换,所以刚才讲的差别不是因为术语不一样,这个差别就是,人脸识别中的算法未来可能涉及到海量的人脸照片,而自然语言处理有一个固定的词汇表,而像一些没有出现过的单词我们就记为未知单词。
这节视频里,你看到如何用词嵌入来实现这种类型的迁移学习,并且通过替换原来的one-hot表示,而是用之前的嵌入的向量,你的算法会泛化的更好,你也可以从较少的标记数据中进行学习。接下来我会给你展示一些词嵌入的特性,这之后再讨论学习这些词嵌入的算法。下个视频我们会看到词嵌入在做类比推理中发挥的作用。
到现在,你应该明白了词嵌入是如何帮助你构建自然语言处理应用的。词嵌入还有一个迷人的特性就是它还能帮助实现类比推理,尽管类比推理可能不是自然语言处理应用中最重要的,不过它能帮助人们理解词嵌入做了什么,以及词嵌入能够做什么,让我们来一探究竟。
这是一系列你希望词嵌入可以捕捉的单词的特征表示,假如我提出一个问题,man如果对应woman,那么king应该对应什么?你们应该都能猜到king应该对应queen。能否有一种算法来自动推导出这种关系,下面就是实现的方法。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kOoDdK9d-1575538721597)(https://www.github.com/OneJane/blog/raw/master/小书匠/12242657bd982acd1d80570cc090b4fe.png)]
我们用一个四维向量来表示man,我们用 e 5391 e_{5391} e5391来表示,不过在这节视频中我们先把它(上图编号1所示)称为 e man e_{\text{man}} eman,而旁边这个(上图编号2所示)表示woman的嵌入向量,称它为 e woman e_{\text{woman}} ewoman,对king和queen也是用一样的表示方法。在该例中,假设你用的是四维的嵌入向量,而不是比较典型的50到1000维的向量。这些向量有一个有趣的特性,就是假如你有向量 e man e_{\text{man}} eman和 e woman e_{\text{woman}} ewoman,将它们进行减法运算,即
$$
e_{\text{man}} - e_{\text{woman}} = \begin{bmatrix}
类似的,假如你用 e king e_{\text{king}} eking减去 e queen e_{\text{queen}} equeen,最后也会得到一样的结果,即
$$
e_{\text{king}} - e_{\text{queen}} = \begin{bmatrix}
这个结果表示,man和woman主要的差异是gender(性别)上的差异,而king和queen之间的主要差异,根据向量的表示,也是gender(性别)上的差异,这就是为什么 e man − e woman e_{\text{man}}- e_{\text{woman}} eman−ewoman和 e king − e queen e_{\text{king}} - e_{\text{queen}} eking−equeen结果是相同的。所以得出这种类比推理的结论的方法就是,当算法被问及man对woman相当于king对什么时,算法所做的就是计算 e man − e woman e_{\text{man}}-e_{\text{woman}} eman−ewoman,然后找出一个向量也就是找出一个词,使得 e man − e woman e_{\text{man}}-e_{\text{woman}} eman−ewoman≈ e king − e ? \ e_{\text{king}}- e_{?} eking−e?,也就是说,当这个新词是queen时,式子的左边会近似地等于右边。这种思想首先是被Tomas Mikolov 和 Wen-tau Yih还有Geoffrey Zweig提出的,这是词嵌入领域影响力最为惊人和显著的成果之一,这种思想帮助了研究者们对词嵌入领域建立了更深刻的理解。
(Mikolov T, Yih W T, Zweig G. Linguistic regularities in continuous space word representations[J]. In HLT-NAACL, 2013.)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1NdtSw2W-1575538721597)(https://www.github.com/OneJane/blog/raw/master/小书匠/5a42eea162ddc75a1d37520618b4bcd2.png)]
让我们来正式地探讨一下应该如何把这种思想写成算法。在图中,词嵌入向量在一个可能有300维的空间里,于是单词man代表的就是空间中的一个点,另一个单词woman代表空间另一个点,单词king也代表一个点,还有单词queen也在另一点上(上图编号1方框内所示的点)。事实上,我们在上个幻灯片所展示的就是向量man和woman的差值非常接近于向量king和queen之间的差值,我所画的这个箭头(上图编号2所示)代表的就是向量在gender(性别)这一维的差,不过不要忘了这些点是在300维的空间里。为了得出这样的类比推理,计算当man对于woman,那么king对于什么,你能做的就是找到单词w来使得, e man − e woman ≈ e king − e w e_{\text{man}}-e_{\text{woman}}≈ e_{\text{king}} - e_{w} eman−ewoman≈eking−ew这个等式成立,你需要的就是找到单词w来最大化 e w e_{w} ew与 e king − e man + e woman e_{\text{king}} - e_{\text{man}} + e_{\text{woman}} eking−eman+ewoman的相似度,即
F i n d w o r d w : a r g m a x S i m ( e w , e king − e man + e woman ) Find\ word\ w:argmax \ Sim(e_{w},e_{\text{king}} - e_{\text{man}} + e_{\text{woman}}) Find word w:argmax Sim(ew,eking−eman+ewoman)
所以我做的就是我把这个 e w e_{w} ew全部放到等式的一边,于是等式的另一边就会是 e king − e man + e woman e_{\text{king}}- e_{\text{man}} + e_{\text{woman}} eking−eman+ewoman。我们有一些用于测算 e w e_{w} ew和 e king − e man + e woman e_{\text{king}} -e_{\text{man}} + e_{\text{woman}} eking−eman+ewoman之间的相似度的函数,然后通过方程找到一个使得相似度最大的单词,如果结果理想的话会得到单词queen。值得注意的是这种方法真的有效,如果你学习一些词嵌入,通过算法来找到使得相似度最大化的单词w,你确实可以得到完全正确的答案。不过这取决于过程中的细节,如果你查看一些研究论文就不难发现,通过这种方法来做类比推理准确率大概只有30%~75%,只要算法猜中了单词,就把该次计算视为正确,从而计算出准确率,在该例子中,算法选出了单词queen。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aWaiZbj8-1575538721598)(https://www.github.com/OneJane/blog/raw/master/小书匠/012c07b7692aed382a2875292ea8e81b.png)]
在继续下一步之前,我想再说明一下左边的这幅图(上图编号1所示),在之前我们谈到过用t-SNE算法来将单词可视化。t-SNE算法所做的就是把这些300维的数据用一种非线性的方式映射到2维平面上,可以得知t-SNE中这种映射很复杂而且很非线性。在进行t-SNE映射之后,你不能总是期望使等式成立的关系,会像左边那样成一个平行四边形,尽管在这个例子最初的300维的空间内你可以依赖这种平行四边形的关系来找到使等式成立的一对类比,通过t-SNE算法映射出的图像可能是正确的。但在大多数情况下,由于t-SNE的非线性映射,你就没法再指望这种平行四边形了,很多这种平行四边形的类比关系在t-SNE映射中都会失去原貌。
现在,再继续之前,我想再快速地列举一个最常用的相似度函数,这个最常用的相似度函数叫做余弦相似度。这是我们上个幻灯片所得到的等式(下图编号1所示),在余弦相似度中,假如在向量 u u u和 v v v之间定义相似度: sim ( u , v ) = u T v ∣ ∣ u ∣ ∣ 2 ∣ ∣ v ∣ ∣ 2 \text{sim}\left( u,v \right) = \frac{u^{T}v}{\left| \left| u \right| \right|_{2}\left| \left| v \right| \right|_{2}} sim(u,v)=∣∣u∣∣2∣∣v∣∣2uTv
现在我们先不看分母,分子其实就是 u u u和 v v v的内积。如果u和v非常相似,那么它们的内积将会很大,把整个式子叫做余弦相似度,其实就是因为该式是 u u u和 v v v的夹角的余弦值,所以这个角(下图编号2所示)就是Φ角,这个公式实际就是计算两向量夹角Φ角的余弦。你应该还记得在微积分中,Φ角的余弦图像是这样的(下图编号3所示),所以夹角为0度时,余弦相似度就是1,当夹角是90度角时余弦相似度就是0,当它们是180度时,图像完全跑到了相反的方向,这时相似度等于-1,这就是为什么余弦相似度对于这种类比工作能起到非常好的效果。
距离用平方距离或者欧氏距离来表示: ∣ ∣ u − v ∣ ∣ 2 \left| \left| u - v \right| \right|^{2} ∣∣u−v∣∣2
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L1qqZuxk-1575538721598)(https://www.github.com/OneJane/blog/raw/master/小书匠/cd01dd1ced86605f712cf080681db022.png)]
参考资料:余弦相似度
为了测量两个词的相似程度,我们需要一种方法来测量两个词的两个嵌入向量之间的相似程度。 给定两个向量 u u u和 v v v,余弦相似度定义如下:
KaTeX parse error: \tag works only in display equations
其中 u . v u.v u.v 是两个向量的点积(或内积), ∣ ∣ u ∣ ∣ 2 ||u||_2 ∣∣u∣∣2是向量 u u u的范数(或长度),并且 θ \theta θ 是向量 u u u和 v v v之间的角度。这种相似性取决于角度在向量 u u u和 v v v之间。如果向量 u u u和 v v v非常相似,它们的余弦相似性将接近1; 如果它们不相似,则余弦相似性将取较小的值。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MaEZJyy1-1575538721599)(https://www.github.com/OneJane/blog/raw/master/小书匠/cosine_sim.png)]
**图1:**两个向量之间角度的余弦是衡量它们有多相似的指标,角度越小,两个向量越相似。
从学术上来说,比起测量相似度,这个函数更容易测量的是相异度,所以我们需要对其取负,这个函数才能正常工作,不过我还是觉得余弦相似度用得更多一点,这两者的主要区别是它们对 u u u和 v v v之间的距离标准化的方式不同。
词嵌入的一个显著成果就是,可学习的类比关系的一般性。举个例子,它能学会man对于woman相当于boy对于girl,因为man和woman之间和king和queen之间,还有boy和girl之间的向量差在gender(性别)这一维都是一样的。它还能学习Canada(加拿大)的首都是Ottawa(渥太华),而渥太华对于加拿大相当于Nairobi(内罗毕)对于Kenya(肯尼亚),这些都是国家中首都城市名字。它还能学习big对于bigger相当于tall对于taller,还能学习Yen(円)对于Janpan(日本),円是日本的货币单位,相当于Ruble(卢比)对于Russia(俄罗斯)。这些东西都能够学习,只要你在大型的文本语料库上实现一个词嵌入学习算法,只要从足够大的语料库中进行学习,它就能自主地发现这些模式。
在本节视频中,你见到了词嵌入是如何被用于类比推理的,可能你不会自己动手构建一个类比推理系统作为一项应用,不过希望在这些可学习的类特征的表示方式能够给你一些直观的感受。你还看知道了余弦相似度可以作为一种衡量两个词嵌入向量间相似度的办法,我们谈了许多有关这些嵌入的特性,以及如何使用它们。下节视频中,我们来讨论如何真正的学习这些词嵌入。
接下来我们要将学习词嵌入这一问题具体化,当你应用算法来学习词嵌入时,实际上是学习一个嵌入矩阵,我们来看一下这是什么意思。
和之前一样,假设我们的词汇表含有10,000个单词,词汇表里有a,aaron,orange,zulu,可能还有一个未知词标记<UNK>。我们要做的就是学习一个嵌入矩阵 E E E,它将是一个300×10,000的矩阵,如果你的词汇表里有10,000个,或者加上未知词就是10,001维。这个矩阵的各列代表的是词汇表中10,000个不同的单词所代表的不同向量。假设orange的单词编号是6257(下图编号1所示),代表词汇表中第6257个单词,我们用符号 O 6527 O_{6527} O6527
来表示这个one-hot向量,这个向量除了第6527个位置上是1(下图编号2所示),其余各处都为0,显然它是一个10,000维的列向量,它只在一个位置上有1,它不像图上画的那么短,它的高度应该和左边的嵌入矩阵的宽度相等。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LNcbk9Eq-1575538721600)(https://www.github.com/OneJane/blog/raw/master/小书匠/ad1c7b1e85d39f56756c28787ccef892.png)]
假设这个嵌入矩阵叫做矩阵 E E E,注意如果用 E E E去乘以右边的one-hot向量(上图编号3所示),也就是 O 6527 O_{6527} O6527,那么就会得到一个300维的向量, E E E是300×10,000的, O 6527 O_{6527} O6527是10,000×1的,所以它们的积是300×1的,即300维的向量。要计算这个向量的第一个元素,你需要做的是把 E E E的第一行(上图编号4所示)和 O 6527 O_{6527} O6527的整列相乘,不过 O 6527 O_{6527} O6527的所有元素都是0,只有6257位置上是1,最后你得到的这个向量的第一个元素(上图编号5所示)就是orange这一列下的数字(上图编号6所示)。然后我们要计算这个向量的第二个元素,就是把 E E E的第二行(上图编号7所示)和这个 O 6527 O_{6527} O6527相乘,和之前一样,然后得到第二个元素(上图编号8所示),以此类推,直到你得到这个向量剩下的所有元素(上图编号9所示)。
这就是为什么把矩阵 E E E和这个one-hot向量相乘,最后得到的其实就是这个300维的列,就是单词orange下的这一列,它等于 e 6257 e_{6257} e6257,这个符号是我们用来表示这个300×1的嵌入向量的符号,它表示的单词是orange。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gsGdiCQz-1575538721600)(https://www.github.com/OneJane/blog/raw/master/小书匠/fa320bd001f9dca8ec33c7a426e20d80.png)]
更广泛来说,假如说有某个单词w,那么 e w e_{w} ew就代表单词w的嵌入向量。同样, E O j EO_{j} EOj, O j O_{j} Oj就是只有第 j j j个位置是1的one-hot向量,得到的结果就是 e j e_{j} ej,它表示的是字典中单词j的嵌入向量。
在这一小节中,要记住的一件事就是我们的目标是学习一个嵌入矩阵 E E E。在下节视频中你将会随机地初始化矩阵 E E E,然后使用梯度下降法来学习这个300×10,000的矩阵中的各个参数, E E E乘以这个one-hot向量(上图编号1所示)会得到嵌入向量。再多说一点,当我们写这个等式(上图编号2所示)的时候,写出这些符号是很方便的,代表用矩阵 E E E乘以one-hot向量 O j O_{j} Oj。但当你动手实现时,用大量的矩阵和向量相乘来计算它,效率是很低下的,因为one-hot向量是一个维度非常高的向量,并且几乎所有元素都是0,所以矩阵向量相乘效率太低,因为我们要乘以一大堆的0。所以在实践中你会使用一个专门的函数来单独查找矩阵 E E E的某列,而不是用通常的矩阵乘法来做,但是在画示意图时(上图所示,即矩阵 E E E乘以one-hot向量示意图),这样写比较方便。但是例如在Keras中就有一个嵌入层,然后我们用这个嵌入层更有效地从嵌入矩阵中提取出你需要的列,而不是对矩阵进行很慢很复杂的乘法运算。
在本视频中你见到了在学习嵌入向量的过程中用来描述这些算法的符号以及关键术语,矩阵 E E E它包含了词汇表中所有单词的嵌入向量。在下节视频中,我们将讨论学习矩阵 E E E的具体算法。
在本节视频中,你将要学习一些具体的算法来学习词嵌入。在深度学习应用于学习词嵌入的历史上,人们一开始使用的算法比较复杂,但随着时间推移,研究者们不断发现他们能用更加简单的算法来达到一样好的效果,特别是在数据集很大的情况下。但有一件事情就是,现在很多最流行的算法都十分简单,如果我一开始就介绍这些简单的算法,你可能会觉得这有点神奇,这么简单的算法究竟是怎么起作用的?稍微复杂一些的算法开始,因为我觉得这样更容易对算法的运作方式有一个更直观的了解,之后我们会对这些算法进行简化,使你能够明白即使一些简单的算法也能得到非常好的结果,我们开始吧。
假如你在构建一个语言模型,并且用神经网络来实现这个模型。于是在训练过程中,你可能想要你的神经网络能够做到比如输入:“I want a glass of orange ___.”,然后预测这句话的下一个词。在每个单词下面,我都写上了这些单词对应词汇表中的索引。实践证明,建立一个语言模型是学习词嵌入的好方法,我提出的这些想法是源于Yoshua Bengio,Rejean Ducharme,Pascal Vincent,Rejean Ducharme,Pascal Vincent还有Christian Jauvin。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4CyK9Lgg-1575538721601)(https://www.github.com/OneJane/blog/raw/master/小书匠/31347eca490e0ae8541140fb01c04d72.png)]
下面我将介绍如何建立神经网络来预测序列中的下一个单词,让我为这些词列一个表格,“I want a glass of orange”,我们从第一个词I开始,建立一个one-hot向量表示这个单词I。这是一个one-hot向量(上图编号1所示),在第4343个位置是1,它是一个10,000维的向量。然后要做的就是生成一个参数矩阵 E E E,然后用 E E E乘以 O 4343 O_{4343} O4343,得到嵌入向量 e 4343 e_{4343} e4343,这一步意味着