现在讨论一下怎么才能建立一个神经网络模型来学习X到Y的映射。可以尝试的方法之一是使用标准的神经网络,在之前的例子中,我们有九个输入单词,想象一下,把这9个输入单词,可能是9个one-hot向量,然后把它们输入到一个标准神经网络,经过一些隐藏层,最终会输出9个值为0或者1的项,它表明每个输入单词是否是人名的一部分,但结果表明这个方法并不好。
主要有两个问题,第一个是输入和输出数据在不同的例子中可以有不同的长度,不是所有的例子都有着同样的输入长度 T x T_x Tx或者同样的输出长度 T y T_y Ty,即使每个句子都有最大长度,也许你能够填充(pad)或者零填充,使每个输入语句都能达到最大长度。但看起来仍然不是一个好的表达方式。
第二个问题可能更严重,一个像这样的单纯的神经网络结构,它并不共享从文本的不同位置上学到的特征。具体来说,如果神经网络已经学习到了在位置1出现的harry可能是人名的一部分,那么如果harry出现在其它位置 x < t > x^{<t>} x<t>时,它也能够自动识别其为人名的一部分的话,这就很棒了。这可能类似于你在卷积神经网络中看到的,你希望将部分图片里学习到的内容快速推广到图片的其它部分,而我们希望对序列数据也有相似的效果。
和你在卷积神经网络中学到的类似,用一个更好的表达方式也能够让你减少模型中的参数数量,之前我们提到过这些都是10000维的one-hot向量,因此这会是一个十分庞大的输入层。如果总的输入大小是最大单词数乘以10000,那么第一层的权重矩阵就会有巨量的参数。循环神经网络可以完美解决这两个问题。
那么什么是循环神经网络呢?如果你以从左到右的顺序读这个句子,第一个单词是假如说是
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^{<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不相同,这个结构需要做出一些改变,所以在每一个时间步中,循环神经网络传递一个激活值到下一个时间步中用于计算。
要开始整个流程,我们在零时刻需要编造一个激活值,这通常是零向量。有些研究人员会用其它方法随机初始化
a
<
0
>
a^{<0>}
a<0>。不过使用零向量作为零时刻的伪激活值是最常见的选择。因此我们把它输入神经网络,在一些研究论文或者书中,你会看到这类神经网络,在每一个时间步中,输入
x
<
t
>
x^{<t>}
x<t>然后输出
y
^
<
t
>
\hat{y}^{<t>}
y^<t>,然后为了表示循环连接,会在隐藏层中画一个圈,表示输回网络层,有时会画一个黑色方块来表示在这个黑色方块处会延迟一个时间步。
循环神经网络是从左往右扫描数据,同时每个时间步的参数也是共享的,我们用
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决定。
在这个循环神经网络中,它的意思是在预测 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>的信息。这个循环神经网络的一个缺点就是它只使用了这个序列中之前的信息来做出预测,尤其是当预测 y ^ < 3 > \hat{y}^{<3>} y^<3>时,它没有用到 x < 4 > x^{<4>} x<4>和 x < 5 > x^{<5>} x<5>等等的信息。
所以这就有一个问题,因为如果给定了这个句子“He said,“Teddy Roosevelt was a great President.””,为了判断Teddy是否是人名的一部分,仅仅知道句中前两个词,是完全不够的,还需知道句中后部分的信息,这也是十分有用的,因为句子也可能是这样的“He said,"Teddy bears are on sale!”。因此,如果只给定前三个单词是不可能确切地知道Teddy是否是人名的一部分。第一个例子是人名,第二个例子不是人名。所以不能仅仅通过前三个单词就能够分辨出其中的区别。
所以这个特定的神经网络结构的一个限制就是它在某一时刻的预测仅使用了当前时刻序列之前的信息,并没有使用序列中后部分的信息,之后会在双向循环神经网络(BRNN)中处理这个问题。
具体看一下这个神经网络计算了些什么,下图是一张神经网络图示,一开始先输入
a
<
0
>
a^{<0>}
a<0>,它是一个零向量,接着就是前向传播过程,计算公式为
a
<
1
>
=
g
(
W
a
a
a
<
0
>
+
W
a
x
x
<
1
>
+
b
a
)
a^{<1>}=g(W_{aa}a^{<0>}+W_{ax}x^{<1>}+b_a)
a<1>=g(Waaa<0>+Waxx<1>+ba)
y
^
<
1
>
=
g
(
W
y
a
a
<
1
>
+
b
y
)
\hat{y}^{<1>}=g(W_{ya}a^{<1>}+b_y)
y^<1>=g(Wyaa<1>+by)这里用这样的符号约定来表示矩阵下标,举个例子,
W
a
x
W_{ax}
Wax中x意味着
W
a
x
W_{ax}
Wax要乘以某个x类型的量,a的意思是它是用来计算某个a类型的量的。
循环神经网络使用的激活函数,经常是选用tanh,不过有时候也会用Relu,但tanh是更通用的选择。我们有其它方法来避免梯度消失问题。选用哪个激活函数是取决于你的输出y。如果它是一个二分问题,那可以用sigmoid作为激活函数;如果是k分类的话,可以选用softmax作为激活函数。不过这里激活函数的类型取决于你有什么样类型的输出y。对于命名实体识别来说,y只可能是0或者1。更一般的情况下在t时刻,有
a
<
t
>
=
g
(
W
a
a
a
<
t
−
1
>
+
W
a
x
x
<
t
>
+
b
a
)
a^{<t>}=g(W_{aa}a^{<t-1>}+W_{ax}x^{<t>}+b_a)
a<t>=g(Waaa<t−1>+Waxx<t>+ba)
y
^
<
t
>
=
g
(
W
y
a
a
<
t
>
+
b
y
)
\hat{y}^{<t>}=g(W_{ya}a^{<t>}+b_y)
y^<t>=g(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>,然后用
a
<
1
>
a^{<1>}
a<1>和
x
<
2
>
x^{<2>}
x<2>一起算出
a
<
2
>
a^{<2>}
a<2>和
y
^
<
2
>
\hat{y}^{<2>}
y^<2>等。
为了能帮我们建立更加复杂的神经网络,要将以下这些符号简化一下 a < t > = g ( W a a a < t − 1 > + W a x x < t > + b a ) a^{<t>}=g(W_{aa}a^{<t-1>}+W_{ax}x^{<t>}+b_a) a<t>=g(Waaa<t−1>+Waxx<t>+ba) y ^ < t > = g ( W y a a < t > + b y ) \hat{y}^{<t>}=g(W_{ya}a^{<t>}+b_y) y^<t>=g(Wyaa<t>+by)为了简化这些符号,将第一个公式中的 W a a a < t − 1 > + W a x x < y > W_{aa}a^{<t-1>}+W_{ax}x^{<y>} Waaa<t−1>+Waxx<y>以更简单的形式写出来,把公式改写为 a < t > = g ( W a [ a < t − 1 > , x < t > ] + b a ) a^{<t>}=g(W_a[a^{<t-1>},x^{<t>}]+b_a) a<t>=g(Wa[a<t−1>,x<t>]+ba)我们定义 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}|W_{ax}]=W_{a} [Waa∣Wax]=Wa。举个例子,如果a是100维的,x是10000维的,那么 W a a W_{aa} Waa就是个 ( 100 , 100 ) (100,100) (100,100)维的矩阵, W a x W_{ax} Wax就是个 ( 100 , 10000 ) (100,10000) (100,10000)维的矩阵,因此如果将这两个矩阵堆起来,所以 W a W_a Wa就是一个 ( 100 , 10100 ) (100,10100) (100,10100)维的矩阵,同样 [ a < t − 1 > , x < t > ] [a^{<t-1>},x^{<t>}] [a<t−1>,x<t>]是一个10100维的向量。这种记法的好处是我们可以不使用两个参数矩阵 W a a W_{aa} Waa和 W a x W_{ax} Wax,而是将其压缩成一个参数矩阵 W a W_a Wa。所以当我们建立更复杂模型的时候,这能够简化我们用到的符号,同样对式子 y ^ < t > \hat{y}^{<t>} y^<t>有 y ^ < t > = g ( W y a < t > + b y ) \hat{y}^{<t>}=g(W_{y}a^{<t>}+b_y) y^<t>=g(Wya<t>+by)现在 W y W_y Wy和 b y b_y by仅有一个下标,它表示计算时会输出什么类型的量。所以 W y W_y Wy表示它是计算y类型的量的权重矩阵,而上面的 W a W_a Wa和 b a b_a ba则表示这些参数是用来计算a类型的输出或者说是激活值的。