1.视频网站:mooc慕课https://mooc.study.163.com/university/deeplearning_ai#/c
2.详细笔记网站(中文):http://www.ai-start.com/dl2017/
3.github课件+作业+答案:https://github.com/stormstone/deeplearning.ai
1.3 循环神经网络模型 Recurrent Neural Network Model
上节课中,你了解了我们用来定义序列学习问题的符号。
现在我们讨论一下怎样才能建立一个模型,建立一个神经网络来学习
X
X
X 到
Y
Y
Y 的映射。
可以尝试的方法之一是使用标准神经网络。
如上图,在上节课的例子中,我们有9个输入单词。想象一下,把这9个输入单词,可能是9个one-hot向量,然后将它们输入到一个标准神经网络中,经过一些隐藏层,最终会输出9个值为0或1的项,它表明每个输入单词是否是人名的一部分。
但结果表明这个方法并不好,主要有两个问题,
- 问题1:输入和输出数据在不同例子中可以有不同的长度,不是所有的例子都有着同样输入长度 T x T_x Tx 或是同样输出长度 T y T_y Ty 的。即使每个句子都有最大长度,也许你能够填充(pad)或零填充(zero pad)使每个输入语句都达到最大长度,但仍然看起来不是一个好的表达方式。
Inputs, outputs can be different lengths in different examples.
- 问题2:一个像这样单纯的NN结构,它并不共享从文本的不同位置上学到的特征。具体来说,如果NN已经学习到了在
x
<
1
>
x^{<1>}
x<1> 出现的Harry可能是人名的一部分,那么如果Harry出现在其他位置,比如
x
<
t
>
x^{<t>}
x<t> 时,它也能够自动识别其为人名的一部分的话,这就很棒了。这可能类似于你在卷积神经网络中看到的,你希望将部分图片里学到的内容快速推广到图片的其他部分,而我们希望对序列数据也有相似的效果。和你在卷积网络中学到的类似,用一个更好的表达方式也能够让你减少模型中参数的数量。
- 上节课我们提到过 x < 1 > x^{<1>} x<1>、 x < 2 > x^{<2>} x<2>… x < T x > x^{<T_x>} x<Tx> 都是10,000维的one-hot向量,因此这会是十分庞大的输入层。如果总的输入大小是最大单词数乘以10,000,那么第一层的权重矩阵就会有着巨量的参数。
Doesn’t share features learned across different positions of text.
循环神经网络就没有上述的两个问题。
什么是循环神经网络
那么什么是循环神经网络呢?
我们先建立一个NN。如果你以从左到右的顺序读句子,第一个单词假如说是
x
<
1
>
x^{<1>}
x<1> ,我们要做的就是将第一个词输入一个神经网络层,我打算如上图这样画,第一个神经网络的隐藏层,我们可以让神经网络尝试预测输出,判断这是否是人名的一部分。(这是时间步1)
循环神经网络做的是,当它读到句中的第二个单词时,假设是
x
<
2
>
x^{<2>}
x<2> ,它不是仅用
x
<
2
>
x^{<2>}
x<2> 就预测出
y
^
<
2
>
\hat y^{<2>}
y^<2> ,它也会输入一些来自时间步1的信息。具体而言,时间步1的激活值就会传递到时间步2。
然后,在下一个时间步3,循环神经网络输入了单词
x
<
3
>
x^{<3>}
x<3> ,然后它尝试预测输出了预测结果
y
^
<
3
>
\hat y^{<3>}
y^<3> ,等等。
一直到最后一个时间步,输入了 x < T x > x^{<T_x>} x<Tx> ,然后输出了 y ^ < T y > \hat y^{<T_y>} y^<Ty> 。
在这个例子中 T x = T y T_x=T_y Tx=Ty,同时如果 T x T_x Tx 和 T y T_y Ty 不相同,这个结构会需要作出一些改变。在每一个时间步中,RNN传递一个激活值到下一个时间步中用于计算。
<图1>
要开始整个流程,在零时刻需要构造一个激活值 a < 0 > a^{<0>} a<0> ,这通常是零向量。有些研究人员会随机用其他方法初始化 a < 0 > a^{<0>} a<0> ,不过使用零向量作为零时刻的伪激活值是最常见的选择,因此我们把它输入NN。
<图2>
在一些研究论文中或是一些书中你会看到这类NN,用上图这样的图形来表示,在每一个时间步中,你输入 X X X 然后输出 Y ^ \hat Y Y^。然后为了表示循环连接有时人们会像这样画个圈,表示输回网络层,有时他们会画一个黑色方块,来表示在这个黑色方块处会延迟一个时间步。
我个人认为<图2>这些循环图很难理解,所以在本次课程中,我画图更倾向于使用<图1>这种分布画法。不过如果你在教材中或是研究论文中看到了<图2>这种图表的画法,它可以在心中将这图展开成<图1>那样。
RNN是从左向右扫描数据,同时每个时间步的参数也是共享的,所以下面我们会详细讲述它的一套参数,我们用 W a x W_{ax} Wax 来表示管理着从 x < 1 > x^{<1>} x<1> 到隐藏层的连接的一系列参数,每个时间步使用的都是相同的参数 W a x W_{ax} Wax 。
而激活值也就是水平联系是由参数 W a a W_{aa} Waa 决定的,同时每一个时间步都使用相同的参数 W a a W_{aa} Waa ,同样的输出结果由 W y a W_{ya} Wya 决定。
这些参数是如何起作用
在这个RNN中,在预测 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> 的信息可以通过这样的路径(上图绿色的路径)来帮助预测。
这个RNN的一个缺点就是它只使用了这个序列中之前的信息来做出预测 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是否是人名的一部分,第一个例子是人名,第二个例子就不是,所以你不可能只看前三个单词就能分辨出其中的区别。
所以这样特定的NN结构的一个限制是它在某一时刻的预测仅使用了从序列之前的输入信息并没有使用序列中后部分的信息,我们会在之后的双向循环神经网络
(BRNN)的课程中处理这个问题。
但对于现在,这个更简单的单向NN结构就够我们来解释关键概念了,之后只要在此基础上作出修改就能同时使用序列中前面和后面的信息来预测
y
^
<
3
>
\hat y^{<3>}
y^<3> (如上图绿色线),不过我们会在之后的课程讲述这些内容,接下来我们具体地写出这个NN计算了些什么。
上图是一张清理后的NN示意图,和我之前提及的一样,一般开始先输入
a
<
0
>
a^{<0>}
a<0>,它是一个零向量。接着就是前向传播过程,先计算激活值
a
<
1
>
a^{<1>}
a<1>,然后再计算
y
^
<
1
>
\hat y^{<1>}
y^<1>。
a
<
0
>
=
0
⃗
a^{<0>}=\vec{0}
a<0>=0
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
a
x
W_{ax}
Wax
- 第二个下标意味着 W a x W_{ax} Wax要乘以某个 x x x 类型的量,例如 x < 1 > x^{<1>} x<1>
- 第一个下标 a a a表示它是用来计算某个 a a a 类型的变量,例如 a < 0 > a^{<0>} a<0>。
同样的,可以看出 W y x W_{yx} Wyx意味着乘上了某个 a a a 类型的量,用来计算出某个 y ^ \hat y y^ 类型的量。
对于 a < 1 > a^{<1>} a<1>,RNN用的激活函数经常是tanh,不过有时候也会用ReLU,但是tanh是更通常的选择。我们有其他方法来避免梯度消失问题,将在之后进行讲述。
对于 y ^ < 1 > \hat y^{<1>} y^<1>,选用哪个激活函数是取决于你的输出,
- 如果它是一个二分问题,那么我猜你会用sigmoid函数作为激活函数,
- 如果是 k k k类别分类问题的话,那么可以选用softmax作为激活函数。
不过这里激活函数的类型取决于你有什么样类型的输出 y y y,对于命名实体识别来说 y y y只可能是0或者1,那我猜这里第二个激活函数 g 2 g_2 g2 可以是sigmoid激活函数。
如果你想区分这2个不同的激活函数的话,虽然我不经常这么做,更一般的情况下,在
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)
这些等式定义了NN的前向传播,你可以从零向量 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>等等,像上面图中这样,从左到右完成前向传播。
现在为了帮我们建立更复杂的NN,我实际要将这个符号简化一下。
我把公式 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)以更简单的形式写出来,写作 a < t > = g 1 ( W a [ a < t − 1 > , x < t > ] + b a ) a^{<t>}=g_1(W_{a}[a^{<t-1>},x^{<t>}]+b_a) a<t>=g1(Wa[a<t−1>,x<t>]+ba)。
其中, W a a a < t − 1 > + W a x x < t > W_{aa}a^{<t-1>}+W_{ax}x^{<t>} Waaa<t−1>+Waxx<t>和 W a [ a < t − 1 > , x < t > ] W_{a}[a^{<t-1>},x^{<t>}] Wa[a<t−1>,x<t>]是等价的。
所以我们定义
W
a
W_a
Wa的方式是将矩阵
W
a
a
W_{aa}
Waa和矩阵
W
a
x
W_{ax}
Wax水平并列放置,
[
W
a
a
⋮
W
a
x
]
=
W
a
[W_{aa}\vdots W_{ax}]=W_a
[Waa⋮Wax]=Wa。
举个例子,如果 a a a是100维的,然后延续之前的例子, x x x是10,000维的,那么
- W a a W_{aa} Waa就是个(100,100)维的矩阵,
- W a x W_{ax} Wax就是个(100,10000)维的矩阵。
- 因此如果将这两个矩阵堆起来,
W
a
W_a
Wa就会是个(100,10100)维的矩阵。
符号 [ a < t − 1 > , x < t > ] [a^{<t-1>},x^{<t>}] [a<t−1>,x<t>]的意思是将这两个向量堆在一起。我会用上图这个符号表示,即 [ a < t − 1 > x < t > ] \begin{bmatrix} a^{<t-1>} \\ x^<t> \end{bmatrix} [a<t−1>x<t>],最终这就是个10,100维的向量。
幸运的是,你可以自己检查一下。用矩阵 [ W a a ⋮ W a x ] = W a [W_{aa}\vdots W_{ax}]=W_a [Waa⋮Wax]=Wa乘以向量 [ 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^{<t-1>}+W_{ax}x^{<t>} Waaa<t−1>+Waxx<t>。
这种记法的好处是我们可以不使用两个参数矩阵 W a a W_{aa} Waa和 W a x W_{ax} Wax,而是将其压缩成一个参数矩阵 W a W_a Wa,所以当我们建立更复杂模型时这就能够简化我们要用到的符号。
同样对于这个例子, 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),我会用更简单的方式重写, y ^ < t > = g 2 ( W y a < t > + b y ) \hat y^{<t>}=g_2(W_{y}a^{<t>}+b_y) y^<t>=g2(Wya<t>+by)。现在 W y W_y Wy和 b y b_y by符号仅有一个下标,它表示在计算时会输出什么类型的量,所以
- W y W_y Wy就表明它是计算 y y y类型的量的权重矩阵,
- 而上面的 W a W_a Wa和 b a b_a ba则表示这些参数是用来计算 a a a类型输出或者说是激活值的。
你现在知道了基本的RNN,下节课我们会一起来讨论反向传播,以及你如何能够用RNN进行学习。