现在我们来考虑如何吃力序列信号,以文本序列为例,考虑一个句子:
通过Embedding层,可以将它转换为shape为 [ b , s , n ] [b,s,n] [b,s,n]的张量, b b b为句子数量, s s s为句子长度,n为向量长度。上述句子可以表示为shape为 [ 1 , 5 , 10 ] [1,5,10] [1,5,10]的张量,其中5代表句子单词长度,10表示词向量长度。
接下来逐步探索能够处理序列信号的网络模型,为了便于表达,我们以情感分类任务为例,如下图所示。情感分类任务通过分析给出的文本序列,提炼出文本数据表达的整体语义特征,从而预测输入文本的感情类型: 正面评价或者负面评价。从分类角度来看,情感分类问题就是一个简单的二分类问题,与图片分类不一样的是,由于输入是文本序列,传统的卷积神经网络并不能取得很好的效果。那么什么类型的网络擅长处理序列数据呢?
1. 全连接层
首先我们想到的是,对于每个词向量,分别使用一个全连接层网络
o
=
σ
(
W
t
x
t
+
b
t
)
\boldsymbol o=σ(\boldsymbol W_t \boldsymbol x_t+\boldsymbol b_t)
o=σ(Wtxt+bt)
提取语义特征,如下图所示,各个单词的词向量通过
s
s
s个全连接层分类网络1提取每个单词的特征,所有单词的特征合并,并通过分类网络2输出序列的类别概率分布,对于长度为
s
s
s的句子来说,至少需要
s
s
s个全网络层。
这种方案的缺点有:
- 网络参数量是相当可观的,内存占用和计算代价较高,同时由于每个序列的长度 s s s并不相同,网络结构是动态变化的;
- 每个全连接层子网络
W
i
\boldsymbol W_i
Wi和
b
i
\boldsymbol b_i
bi只能感受当前词向量的输入,并不能感知之前和之后的语境信息,导致句子整体语义的缺失,每个子网络只能根据自己的输入来提取高层特征,有如管中窥豹。
2. 共享权值
在介绍卷积神经网络时,我们就比较过,卷积神经网络之所以在处理局部相关数据时优于全连接网络,是因为它充分利用了权值共享的思想,大大减少了网络的参数量,使得网络训练起来更加高效。那么,我们在处理序列信号的问题上,能否借鉴权值共享的思想呢?
如下图方案所示,
s
s
s个全连接层的网络并没有实现权值共享。我们尝试将这s个网络层参数共享,这样其实相当于使用一个全连接网络来提取所有单词的特征信息,如下图所示:
通过权值共享后,参数量大大减少,网络训练变得更加稳定高效。但是。这种网络结构并没有考虑序列之间的先后顺序,将词向量打乱次序仍然能获得相同的输出,无法获取有效的全局语义信息。
3. 全局语义
如何赋予网络提取整体语义特征的能力呢?或者说,如何能够让网络能够按序提取词向量的语义信息,并积累成整个句子的全局语义信息呢?我们想到了内存(Memory)机制。如果网络能够提供一个单独的内存变量,每次提取词向量的特征并刷新内存变量,直至最后一个输入完成,此时的内存变量即存储了所有序列的语义特征,并且由于输入序列之间的先后顺序,使得内存变量内容与序列顺序紧密关联。
我们将上述Memory机制实现为一个状态张量
h
\boldsymbol h
h,如上图所示,除了原来的
W
x
h
\boldsymbol W_{xh}
Wxh参数共享外,这里额外增加了一个
W
h
h
\boldsymbol W_{hh}
Whh参数,每个时间戳t上状态张量
h
\boldsymbol h
h刷新机制为:
h
t
=
σ
(
W
x
h
x
t
+
W
h
h
h
t
−
1
+
b
)
\boldsymbol h_t=σ(\boldsymbol W_{xh} \boldsymbol x_t+\boldsymbol W_{hh} \boldsymbol h_{t-1}+\boldsymbol b)
ht=σ(Wxhxt+Whhht−1+b)
其中状态张量
h
0
\boldsymbol h_0
h0为初始的内存状态,可以初始化为全0,经过
s
s
s个词向量的输入后得到网络最终的状态张量
h
s
\boldsymbol h_s
hs,
h
s
\boldsymbol h_s
hs较好地代表了句子的全局语义信息,基于
h
s
\boldsymbol h_s
hs通过某个全连接层分类器即可完成情感分类任务。
4. 循环神经网络
通过一步步地探索,我们最终提出了一种“新型”的网络结构,如下图所示,在每个时间戳
t
t
t,网络层接受当前时间戳的输入
x
t
\boldsymbol x_t
xt和上一个时间戳的网络状态向量
h
t
−
1
\boldsymbol h_{t-1}
ht−1,经过
h
t
=
f
θ
(
h
t
−
1
,
x
t
)
\boldsymbol h_t=f_θ (\boldsymbol h_{t-1},\boldsymbol x_t)
ht=fθ(ht−1,xt)
变换后得到当前时间戳的新状态向量
h
t
\boldsymbol h_t
ht,并写入内存状态中,其中
f
θ
f_θ
fθ代表了网络的运算逻辑,
θ
θ
θ为网络参数集。在每个时间戳上,网络层均有输出产生
o
t
\boldsymbol o_t
ot,
o
t
=
g
ø
(
h
t
)
\boldsymbol o_t=g_\text{\o} (\boldsymbol h_t)
ot=gø(ht),即将网络的状态向量变换后输出。
上述网络结构在时间戳上折叠,如下图所示,网络循环接受序列的每个特征向量
x
t
\boldsymbol x_t
xt,并刷新内部状态向量
h
t
\boldsymbol h_t
ht,同时形成输出
o
t
\boldsymbol o_t
ot。对于这种网络结构,我们把它叫做循环神经网络(Recurrent Neural Network,简称RNN)。
更特别地,如果使用张量
W
x
h
\boldsymbol W_{xh}
Wxh、
W
h
h
\boldsymbol W_{hh}
Whh和偏置
b
\boldsymbol b
b来参数化
f
θ
f_θ
fθ网络,并按照
h
t
=
σ
(
W
x
h
x
t
+
W
h
h
h
t
−
1
+
b
)
\boldsymbol h_t=σ(\boldsymbol W_{xh} \boldsymbol x_t+\boldsymbol W_{hh} \boldsymbol h_{t-1}+\boldsymbol b)
ht=σ(Wxhxt+Whhht−1+b)
方式更新内存状态,我们把这种网络叫做基本的循环神经网络,如无特殊说明,一般说的循环神经网络即指这种实现。在循环神经网络中,激活函数更多地采用
tanh
\text{tanh}
tanh函数,并且可以选择不使用偏置b来进一步减少参数量。状态向量
h
t
\boldsymbol h_t
ht可以直接用作输出,即
o
t
=
h
t
\boldsymbol o_t=\boldsymbol h_t
ot=ht,也可以对
h
t
\boldsymbol h_t
ht做一个简单的线性变换
o
t
=
W
h
o
h
t
\boldsymbol o_t=\boldsymbol W_{ho} \boldsymbol h_t
ot=Whoht后得到每个时间戳上的网络输出
o
t
\boldsymbol o_t
ot。