文章目录
本文为李弘毅老师【Speech Recognition - Listen, Attend, Spell】的课程笔记,课程视频youtube地址,点这里👈(需翻墙)。
下文中用到的图片均来自于李宏毅老师的PPT,若有侵权,必定删除。
文章索引:
上篇 - 1-1 overview
1 内容简述
本文要讲的模型是2015年出自William Chan的Listen, Attend and Spell,论文地址点这里👈。
这是一个标准的seq2seq with attention的模型,listen就是一个encoder,spell就是一个decoder,Attend自然就是attention啦。下面来逐一说明各个模块。
2 模型详述
2.1 Listen
Listen即是模型的Encoder部分,这个部分的输入是一个长度
T
T
T的声音信号特征向量,输出也是一个长度
T
T
T的特征向量。我们希望经过这个Encoder之后,输出的特征可以保留和内容有关的信息,去除不相关的噪声和不同人说话带来的variance。
i
n
p
u
t
:
{
x
1
,
x
2
,
.
.
.
,
x
T
}
o
u
t
p
u
t
:
{
h
1
,
h
2
,
.
.
.
,
h
3
}
input: \{x^1, x^2, ...,x^T\}\\ output: \{h^1, h^2, ...,h^3\}
input:{x1,x2,...,xT}output:{h1,h2,...,h3}
既然这是一个Encoder,那目前用来做序列特征提取的方法就都可以用上,比如单向或者双向的RNN,1-D的CNN,self-attention,论文原文中所使用的是双向的LSTM。
RNN Encoder
RNN大家应该比较熟悉,就不多讲,不明白的可以参看李老师的RNN教学视频,或者参考我的这篇课程笔记。
1D-CNN Encoder
用1-D的CNN去抽取sequence的特征的方式如下图所示。比如我们有一个kernel_size是3的filter,先把
[
0
,
x
1
,
x
2
]
[0, x^1, x^2]
[0,x1,x2]塞进kernel得到
b
1
b^1
b1中的一点,再把
[
x
1
,
x
2
,
x
3
]
[x^1, x^2, x^3]
[x1,x2,x3]塞进同一个kernel得到
b
2
b^2
b2中的一点,依次类推可以得到和输入长度相等的
T
T
T的点,再换一个kernel_size是3的filter再来一遍,就又得到了一组长度为
T
T
T的点,这些点concat起来就得到了输出的特征。当然这里
b
1
b^1
b1只考虑到了
[
x
1
,
x
2
]
[x^1, x^2]
[x1,x2]的信息,
b
2
b^2
b2只考虑到了
[
x
1
,
x
2
,
x
3
]
[x^1, x^2, x^3]
[x1,x2,x3]的信息,为了让最终输出的特征可以参考到更多的信息,可以再叠加一层1-D的CNN。怎么样,get到我说的意思了吧?没错,CNN也可以用来提取sequence的特征。
那这两者,哪一种更好呢?文献中通常会把CNN和RNN相结合,前几层CNN一下,后几层RNN一下。
Self-attentinon Encoder
还有一种近期常用的方法,叫做self-attention,它也是输入一个sequence,输出一个sequence,具体的细节可参见李老师的Transformer教学视频,也可以参考我的这篇课程笔记。
Down Sampling
而在语音辨识中,由于输入的特征非常长( T T T很大),直接硬train的话,很难train。所以通常也会加一些down sampling的操作,常见的有Pyramid RNN(原文的方案),Pooling over time,Time-delay DNN,Truncated Self-attention。其中,Time-delay DNN其实就是1-D dilated CNN,我觉得stride搞大点,也可以有down sampling的效果。
其目的在于让输入Encoder的sequence变得短一点。
2.2 Attend
在Listen完之后呢,我们就得到了一个sequence,叫做 h h h,我们需要对这个 h h h做attention来得到decoder中每一个time step的context vector。说简单粗暴点,就是我们要不断对 h h h的每个time step做加权平均,得到decoder中每个time step的输入(context vector)。
我们来看看下面这张图,图中的
h
1
h^1
h1,
h
2
h^2
h2,
h
3
h^3
h3和
h
4
h^4
h4就是Listen之后得到的结果的每个time step的vector。然后我们会有一个decoder的hidden state叫做
z
i
z^i
zi(原文中其实叫做
s
i
s_i
si,这里是为了和李老师的PPT保持一致),这个最初的
z
0
z^0
z0可以是一个随机初始化的向量。这个
z
0
z^0
z0会去和每个
h
i
h^i
hi做一个attentioin的计算,得到一个表示他们相关性的数值
α
0
i
\alpha^i_0
α0i。然后这些
α
0
i
\alpha^i_0
α0i会经过一层softmax,归一化一下,也就得到了每个
h
i
h^i
hi在
z
0
z^0
z0下的权重,最后加权求和一下得到了
c
0
c^0
c0。之后的每一个
c
i
c^i
ci都是如是得到的。
c
i
=
A
t
t
e
n
t
i
o
n
C
o
n
t
e
x
t
(
z
i
,
h
)
c^i = AttentionContext(z^i, h)
ci=AttentionContext(zi,h)
其中,用来做attention的方法很多,最常用的就是Dot-product Attention和Additive Attention。原文中使用了前者,也就是用个矩阵去乘一下,也就是加了层全连接。
α
t
i
=
<
ϕ
(
z
t
)
,
ψ
(
h
i
)
>
\alpha^i_t=<\phi(z^t), \psi(h^i)>
αti=<ϕ(zt),ψ(hi)>
其中,
ϕ
\phi
ϕ和
ψ
\psi
ψ是不同的全连接层。
2.3 Spell
Spell的部分,其实就是用RNN来做了decoder,利用
c
i
c^i
ci和
z
i
z^i
zi作为输入,和正常的seq2seq没有什么区别,直到遇到终止符后结束。不过它的输出是一个概率分布,也就是它的每个time step的输出为一个size和vocabulary size大小一致的vector。整个输出就是一个
V
×
T
V \times T
V×T的概率矩阵,
V
V
V表示字典的大小,
T
T
T表示time step的长度。
我们要从这个概率矩阵中找到一条概率乘积最大的路径来作为最终的结果。一般情况下,直接取每个time step中概率最大的那个值作为该time step的输出就可以了,这种方法叫做greedy search。不过,为了让结果更精确,可以采用牺牲时间的方法,比如beam search。
2.4 Beam Search
所谓Beam Search就是每个time step保留概率组合最大的前
n
n
n个组合,这个
n
n
n就被称为beam_size。当
n
=
1
n=1
n=1时,就退化为了greedy search。比如下图就是一个
V
=
2
V=2
V=2,
T
=
3
T=3
T=3,
n
=
2
n=2
n=2的例子。
time step 1:只有A和B,故A和B都保留;
time step 2:AA=0.24,AB=0.36,BA=0.04,BB=0.36,故保留AB和BB;
time step 3:ABA=0.144,ABB=0.216,BBA=0.036,BBB=0.324,故保留ABB和BBB。
由于time step 3是最后一个time step,故取保留下来中的概率最大组合BBB作为最终结果。
如果采用greedy search,每次取最大的话,结果就是ABB。可见不同的beam_size,结果是会有区别的。beam_size越大,结果也就越准,但消耗的时间也就越久。
2.5 Training
训练的时候使用了cross entropy作为loss,这也是常规的做法,希望每个time step输出的的概率向量中和label对应的输出的概率越大越好。不过这里有一点要注意的是,和inference的时候不同,在training的时候,前一个time step的输出是不会作为下一个time step的输入的。我们是直接使用前一个time step的label作为下一个time step的输入,这个方法也叫做teacher forcing。因为刚开始训练的时候,往往输出都是比较乱七八糟的,用训练的输出作为输入的话,很难train起来。
2.6 Back to Attention
在做attention的时候,实际上会有两种处理方式,一种是把由
z
t
z^t
zt产生的
c
t
c^t
ct作为下一个time step的输入,另一种把由
z
t
z^t
zt产生的
c
t
c^t
ct作为当前time step的输入。
当然,也有我全都要的做法,就是产生的
c
t
c^t
ct既会影响当前的time step,也会影响下一个time step。
不过,attention用在语音这里有一点杀鸡用牛刀的意思。因为用了attention之后,每个time step输出的feature可以考虑整个输入序列的。但是语音这个领域,在生成第一个字的时候,我们完全没有必要去参考以下最后一个字,我们希望这个attention时可以只关心对应的局部的,然后随着time step的增大,关心的位置也在不断地往后平移。于是,就有了location-aware attention。
所谓的location-aware attention就是我们在产生当前这个attention的时候,我们会额外考虑前一个time step生成的attention在这个time step的附近的权重是怎么样的。通过这个方式,模型就可以学到说,产生的attention是一个考虑局部特征的attention。