深度学习笔记导航
前言
本系列文章是吴恩达深度学习攻城狮系列课程的笔记,分为五部分。
这一章主要将了循环神经网络。
我的笔记不同于一般的笔记,我的笔记更加凝练,除了结论以及公式,更多的是对知识的理解,结合课程可以加速理解,省去很多时间,但是请注意,笔记不能替代课程,应该结合使用。
传送门
结构化机器学习项目,我建议放在最后看。
首先学这一节对你后面的学习没有影响,我就是跳过去学的。而且他更多的讲的是策略,尤其是举了很多后面的例子,你听了不仅不太好懂,而且没啥意思,所以我建议放在最后看。
神经网络与深度学习(Neural Networks and Deep Learning)
改善深层神经网络:超参数调整,正则化,最优化(Hyperparameter Tuning)
卷积神经网络(Convolutional Neural Networks)
序列模型与循环神经网络(Sequence Models)
结构化机器学习项目(Structuring Machine Learning Projects)
序列模型(Sequence Models)
Recurrent Neural Networks(循环神经网络)
序列模型
输入和输出都可能是序列,而且输入和输出的序列长度并不一定能保证一致,甚至输入内部的长度也不能保证一致。
符号:以NLP举例
- x ( i ) x^{(i)} x(i),一个样本,在NLP中通常为一句话。
- x ( i ) < t > x^{(i)<t>} x(i)<t>代表第个i样本序列中第t个元素。比如一个单词
- T x ( i ) T_{x^{(i)}} Tx(i)代表第i个样本序列的长度。
- Vocabulary:一个向量,每一个元素都是一个单词,比如big,cat等,长度从3w到10w不等大的可能有100w。
- 有了这个Vocabulary,那么 x ( i ) < t > x^{(i)<t>} x(i)<t>就可以用这个单词在Vocabulary中的位置来表示,通常是用one-hot编码来把一个单词表示成一个向量。
- 如上规则,对于y也是一样的。
到现在,已经构建了x,一个维度可变的矩阵,到y,维度可变的矩阵,如何建立映射就是模型的事情了。
循环神经网络基础
如果用传统神经网络实现模型,会面临两个问题:
- 对于一个样本,他实际上是二维的,由 T x T_x Tx个one-hot向量构成,那么神经网络的参数量会特别大。
- 每一个样本的长度是不同的,传统神经网络处理需要扩展数据到同一维度,效率很低。
- 而且这种独立处理的方式,并不能学习到序列的特性,前后关系。
循环神经网络的思路就是,在输入层到输出层之间弄一个隐藏层,这一个隐藏层可以适应不通长度的输入。
对一个样本的计算过程是:
- 计算输入的长度,同时初始化
a < 0 > = 0 ⃗ a^{<0>}=\vec{0} a<0>=0 - 用前一层的a和这一层的x计算这一层的a
a < i > = σ ( W a a a < i − 1 > + W a x x < i > + b a ) a^{<i>}=\sigma(W_{aa}a^{<i-1>}+W_{ax}x^{<i>}+b_a) a<i>=σ(Waaa<i−1>+Waxx<i>+ba) - 用这一层的a计算输出
y < i > = σ ( W y a a < i > + b y ) y^{<i>}=\sigma(W_{ya}a^{<i>}+b_y) y<i>=σ(Wyaa<i>+by) - 由此递归/循环完整个长度的序列
双下标可以这么理解,第一个下标是目标,第二个下标是要利用的(要乘的)变量类型
之所以叫循环,是因为全体过程走下来,对于一个样本的每一次循环来说,用的参数都是一个参数,只不过在递归的,依次地对样本的每个词元做处理。
从这个过程来看,每一个a和前面所有的样本有关,也就是说有时序特性。缺点在于无法学习反向的联系,后面会优化。
重写一下公式:
-
a
<
t
>
=
σ
(
W
a
[
a
<
t
−
1
>
,
x
<
t
>
]
+
b
a
)
a^{<t>}=\sigma(W_a[a^{<t-1>},x^{<t>}]+b_a)
a<t>=σ(Wa[a<t−1>,x<t>]+ba)
这里注意[ ]是类似矩阵写法,实际上是竖着的分块矩阵,同样, w a w_a wa其实也是两个参数矩阵水平分块堆叠起来。用分块矩阵积可以还原回最初始的公式 -
y
<
i
>
=
σ
(
W
y
a
<
i
>
+
b
y
)
y^{<i>}=\sigma(W_{y}a^{<i>}+b_y)
y<i>=σ(Wya<i>+by)
这个很好理解,因为只需要乘a,所以没必要区分。 - 经过重写后的参数,就非常容易区分, W a , W y , b a , b y W_a,W_y,b_a,b_y Wa,Wy,ba,by
bp through time环节:
损失函数:对每一个 y < t > y^{<t>} y<t>进行内部的交叉熵计算,然后把所有T个y的交叉熵结果求和。
这样,如果把损失函数降到很低,那么总和低,每一个词元预测的正确率就都会很高。
至于具体怎么求导,就是框架的事儿了。(其实我很好奇,但是现在先还不管),知道损失函数就足够了。
RNN变体
RNN变体用于各种序列任务中。
多对多的叫many-to-many,是最经典的RNN,通常用于命名实体识别这种。
多对一的叫many-to-one,这种只在最后一个有输出。
一对一的,就是前面经典的神经网络
one-to-many。这个就是送进去第一个x,然后别x的可以用前一步输出的y替代,或者就不需要再送进来了,感觉就是个马尔科夫。
many-to-many有特殊变体,比如机器翻译中,两个序列的长度就不同,又比如有的输入不是序列,有的输出不是序列。
这个要用encoder和decoder,前面是一个多对一的,不输出,但是会把整个句子变成一个a,相当于编码,然后送到decoder里,相当于一对多,再解码。
还有双向RNN,相当于一个从前到后的+一个从后到前的RNN,然后y通过两个隐变量计算。
语言模型(重点)
基础概念
这一节比较难理解,我重点解释。
语言模型:计算一个给定句子的概率。
至于概率怎么算的,你可以记住这句话,从本质来说,是计算一系列soft-max向量,基于这些向量可以得出给定句子的概率。具体细节后面慢慢说。
语料库:首先要有一个corpus,应该是语料库,语料库里有一大堆句子,每一个句子可以理解为一个样本。
标记化tokenize:然后运用已有的vocabulary,将语料库中的每一个样本,转化为one-hot相量的矩阵,比如 y < 1 > , y < 2 > , y < 3 > y^{<1>},y^{<2>},y^{<3>} y<1>,y<2>,y<3>
- 通常,输入用x表示,用y表示一个绝对正确的句子,输出用 y ^ \hat{y} y^表示,注意区分y和 y ^ \hat{y} y^,一个是绝对正确的句子,是one-hot编码,另一个只是预测,通常是soft-max格式的。
- 这时你可能会好奇,x应该是什么,请保持疑问,往下面看。
- 且有时候句号(period)以及其他标点会被考虑
- 不认识的词可以被标记为UNK
- 而且在结尾可能会加上EOS标记。
最后就是训练参数。
训练过程详解
这里给的例子看起来比较迷惑,和前面的many-to-many很不一样,因为最开始的任务是词性分类任务,T个输入就有T个输出,对一个输入就要预测其词性。而这个任务是单步预测,预测第一个不需要任何条件,预测第二个需要第一个作为输入以此类推,令y落后一步与x,因为要落后,所以 x < 1 > = 0 , x < t > = y < t − 1 > x^{<1>}=0,x^{<t>}=y^{<t-1>} x<1>=0,x<t>=y<t−1>。这也解释了前面的疑问:x用什么?
我们再逐个解释一下 y ^ \hat{y} y^的含义。
y
^
<
1
>
=
[
P
(
a
)
P
(
c
a
t
)
⋮
P
(
Z
u
l
u
)
]
\hat{y}^{<1>}=\begin{bmatrix} P(a)\\ P(cat) \\ \vdots \\ P(Zulu) \end{bmatrix}
y^<1>=⎣
⎡P(a)P(cat)⋮P(Zulu)⎦
⎤
y
^
<
2
>
=
[
P
(
a
∣
c
a
t
)
P
(
c
a
t
∣
c
a
t
)
⋮
P
(
Z
u
l
u
∣
c
a
t
)
]
\hat{y}^{<2>}=\begin{bmatrix} P(a|cat)\\ P(cat|cat) \\ \vdots \\ P(Zulu|cat) \end{bmatrix}
y^<2>=⎣
⎡P(a∣cat)P(cat∣cat)⋮P(Zulu∣cat)⎦
⎤
y ^ < 3 > = [ P ( a ∣ c a t , a v e r a g e ) P ( c a t ∣ c a t , a v e r a g e ) ⋮ P ( Z u l u ∣ c a t , a v e r a g e ) ] \hat{y}^{<3>}=\begin{bmatrix} P(a|cat,average)\\ P(cat|cat,average) \\ \vdots \\ P(Zulu|cat,average) \end{bmatrix} y^<3>=⎣ ⎡P(a∣cat,average)P(cat∣cat,average)⋮P(Zulu∣cat,average)⎦ ⎤
首先明白,输出的是一个soft-max向量,代表着10000个词的概率。因为预测第一个词没有给出任何条件,所以就直接预测,至于预测如何,第一个是随机的。但是从第二个开始就不一样了,就变成条件概率了,至于条件,就是 y < t − 1 > y^{<t-1>} y<t−1>,而不是 y ^ < t − 1 > \hat{y}^{<t-1>} y^<t−1>,请注意区分。这也很合理,预测第一个啥也不用,预测第二个就需要前一个,那预测第三个就需要前两个,以此类推。
最后就是损失函数定义,还是如前面所说的交叉熵。
以上只是训练的过程,正因为是巡练,所以 x < t > = y < t − 1 > x^{<t>}=y^{<t-1>} x<t>=y<t−1>,但是到了预测的时候,就只会给出前半截语句,让你预测后半段,因为后半段没给,所以后半段的 x < t > = y ^ < t − 1 > x^{<t>}=\hat{y}^{<t-1>} x<t>=y^<t−1>,即,给定模型与前半截语句,那后半截的输出预测就已经确定下来了。
请注意,后半截的预测并不是结果,只是给出soft-max向量,很多时候,并不一定采用概率最大的那个词元作为预测结果。
在你明白预测相量的本质就是soft-max向量以后,我们回归最开始的定义:语言模型可以给出一个语句的概率,那这个概率是怎么算的?
计算过程:给定一系列y,丢进去语言模型,可以得到一系列 y ^ \hat{y} y^,然后 P ( y < 1 > , y < 2 > , y < 3 > ) = P ( y < 1 > ) P ( y < 2 > ∣ y < 1 > ) P ( y < 3 > ∣ y < 1 > y < 2 > ) P(y^{<1>},y^{<2>},y^{<3>})=P(y^{<1>})P(y^{<2>}|y^{<1>})P(y^{<3>}|y^{<1>}y^{<2>}) P(y<1>,y<2>,y<3>)=P(y<1>)P(y<2>∣y<1>)P(y<3>∣y<1>y<2>),至于这三个条件概率,就从对应的 y ^ < t > \hat{y}^{<t>} y^<t>中去寻找对应词的概率,可以想象到,一个词的概率本来就不高,你这T个词的概率一乘,最后得出的概率为 1 0 − 10 10^{-10} 10−10级别也很合理了,什么时候能做到0.1级别的概率就很恐怖了。
到这里你也会发现,其实语言模型不只是能计算概率,也可以进行预测,但究其根本,都是概率的计算。
新序列采样(sample novel sequences):
已经训练好了,怎么用呢?所谓的用,就是生成一个序列,可以理解为根据模型本身就代表一种分布,我们给输入,就可以对分布进行采样,得到输出。
采样原则就是根据输出的y-hat里面的概率,随机选择一个作为采样结果词元,而并不一定是直接用概率最大的那个。
理论上,如果你不给输入,也就是直接从头预测,可能每次的结果都基本相同,因为这是个马尔科夫过程。
如果要改变结果,你就需要给一些输入,即使只是第一个输入,也会极大地改变结果,输入越多,预测越准。即使是这样,普通的NLP生成的结果也有一种胡言乱语的感觉,尤其是用char作为词元的模型。
有一处不合理的就是,就是无论你输入什么,都不会改变第一个输出的概率分布。
RNN改进
梯度消失
梯度下降的原理至今没搞清楚,一方面大致是越靠前的层,就越不容易被bp调节,另一方面就是前面的信息向后传递的过程中损失了很多,所以中间隔得越长,就越学不到长期依赖。
梯度爆炸虽然也会出现,但是很明显,可以迅速处理。比如使用gradient clip,当发现有溢出,就进行缩放。
目前有GRU和LSTM两种方式改进,改进后,隐藏层对外的接口表现不变,只不过内部增加了新的变量和转化。
GRU(gated recurrent unit)
Cho, Kyunghyun, et al. “On the properties of neural machine translation: Encoder-decoder approaches.” arXiv preprint arXiv:1409.1259 (2014).
Chung, Junyoung, et al. “Empirical evaluation of gated recurrent neural networks on sequence modeling.” arXiv preprint arXiv:1412.3555 (2014).
思想:
既然单靠隐变量无法记忆较远的信息,那么干脆就创建一个记忆变量,储存较远的信息,又因为较远的信息不多,所以创建一个c变量(可以是向量)也还合理。
c变量通过门变量(也可以是向量)的控制,来确定是要采用新的c值还是保持原状。假设c保持原状,那么就代表一种记忆,也意味着隐变量只受到当前步的微小影响。如果用新的c值,那么就代表更新记忆,舍弃陈旧的记忆。
那么问题来了,看起来c和a是独立的,如何通过c影响输出呢?
那就是用这个c来替代a。
公式:
-
c
~
<
t
>
=
σ
(
W
c
[
c
<
t
−
1
>
,
x
<
t
>
]
+
b
c
)
\tilde{c}^{<t>}=\sigma(W_c[c^{<t-1>},x^{<t>}]+b_c)
c~<t>=σ(Wc[c<t−1>,x<t>]+bc)
简单版:tilde-c使用类似于隐变量的计算方式来进行传递,但是这里只是一个临时的c,并不一定会将c覆盖。 -
c
~
<
t
>
=
σ
(
W
c
[
Γ
r
c
<
t
−
1
>
,
x
<
t
>
]
+
b
c
)
\tilde{c}^{<t>}=\sigma(W_c[\Gamma_r c^{<t-1>},x^{<t>}]+b_c)
c~<t>=σ(Wc[Γrc<t−1>,x<t>]+bc)
完全版:这里加入r门表示相关性relative。这个r有时候也被叫做reset门,如果r是1,那么相当于基本循环神经网络,如果r是0,那么候选c就完全不受过往状态影响。 - Γ r = s i g m o i d ( W r [ c < t − 1 > , x < t > ] + b r ) \Gamma_r=sigmoid(W_r[c^{<t-1>},x^{<t>}]+b_r) Γr=sigmoid(Wr[c<t−1>,x<t>]+br)
-
Γ
u
=
s
i
g
m
o
i
d
(
W
u
[
c
<
t
−
1
>
,
x
<
t
>
]
+
b
u
)
\Gamma_u=sigmoid(W_u[c^{<t-1>},x^{<t>}]+b_u)
Γu=sigmoid(Wu[c<t−1>,x<t>]+bu)
门控符号也是类似于隐变量的生成方式,但是是基于上一个c生成的,最后基本就是接近于0或者1 -
c
<
t
>
=
Γ
u
×
c
~
<
t
>
+
(
1
−
Γ
u
)
×
c
<
t
−
1
>
c^{<t>}=\Gamma_u\times\tilde{c}^{<t>}+(1-\Gamma_u)\times c^{<t-1>}
c<t>=Γu×c~<t>+(1−Γu)×c<t−1>
类似于交叉熵的写法,通过门控符号决定当前层的c采用新计算出来的c还是维持上一个c不变 - a < t > = c < t > a^{<t>}=c^{<t>} a<t>=c<t>
LSTM(long short term memory)
Hochreiter, Sepp, and Jürgen Schmidhuber. “Long short-term memory.” Neural computation 9.8 (1997): 1735-1780.
这篇论文比较难,深入的讲解了gradient vanishing的原理。
公式:
-
c
~
<
t
>
=
t
a
n
h
(
W
c
[
a
<
t
−
1
>
,
x
<
t
>
]
+
b
c
)
\tilde{c}^{<t>}=tanh(W_c[a^{<t-1>},x^{<t>}]+b_c)
c~<t>=tanh(Wc[a<t−1>,x<t>]+bc)
相比于GRU,tilde-c由a,x,b决定,直观上不受上一个c影响,更加合理。有一些变体会显式地增添上一个c的影响(peephole connection) -
Γ
u
=
σ
(
W
u
[
a
<
t
−
1
>
,
x
<
t
>
]
+
b
u
)
\Gamma_u=\sigma(W_u[a^{<t-1>},x^{<t>}]+b_u)
Γu=σ(Wu[a<t−1>,x<t>]+bu)
控制update,相当于记忆 -
Γ
f
=
σ
(
W
f
[
a
<
t
−
1
>
,
x
<
t
>
]
+
b
f
)
\Gamma_f=\sigma(W_f[a^{<t-1>},x^{<t>}]+b_f)
Γf=σ(Wf[a<t−1>,x<t>]+bf)
控制forget,相当于遗忘(跳过当前步) -
Γ
o
=
σ
(
W
o
[
a
<
t
−
1
>
,
x
<
t
>
]
+
b
o
)
\Gamma_o=\sigma(W_o[a^{<t-1>},x^{<t>}]+b_o)
Γo=σ(Wo[a<t−1>,x<t>]+bo)
控制output,控制c到a的转化 -
c
<
t
>
=
Γ
u
×
c
~
<
t
>
+
Γ
f
×
c
<
t
−
1
>
c^{<t>}=\Gamma_u\times\tilde{c}^{<t>}+\Gamma_f\times c^{<t-1>}
c<t>=Γu×c~<t>+Γf×c<t−1>
长短期记忆的线性组合 -
a
<
t
>
=
Γ
o
×
c
<
t
>
a^{<t>}=\Gamma_o\times c^{<t>}
a<t>=Γo×c<t>
这一步实现了c到a的转化
我个人觉得LSTM更加合理,首先是a和输入共同转化为门,tilde-c,进一步变成c,最后变成a,一个循环走下来,实际上所有的量本质上还是由a,x决定的,这和经典RNN的本质是相同的。
同时,形式上看c和a几乎是平行的,c用来储存记忆,而是否储存记忆,是由a和x决定的,而a代表着曾经的信息,这个因果决定关系很合理,即通过从开始到现在的所有信息决定更新和遗忘的比例,进而影响a。
LSTM和GRU对比:
说实话,相比于GRU,我更喜欢LSTM,并且LSTM更早提出,更加直观,更加灵活,但是灵活的代价就是成本高,因为有三个门。
GRU只有两个门,所以成本更低,结构简单,很多人逐渐开始测试这种方式。
NLP and Word Embeddings(词嵌入与自然语言处理)
基本概念
word representation(词汇表征)
Van der Maaten L, Hinton G. Visualizing data using t-SNE[J]. Journal of machine learning research, 2008, 9(11).
平常我们写句子的时候,同义词总是可以互相替换,但是在基于频率的one-hot编码下,即使是同义词,也可能相距甚远,不能相互替换。
这个时候,就需要一种更好的编码方式,试想能否采用多种特征去描述一个词,构成一个特征向量,近义词的特征向量比较相似,这样就可以互相替换。
这就是词嵌入的理念,向量之间的相似性类似于数值的相似性,其实在最开始的线性网络中,数值的相似性就已经是隐含的了,只要输入的数值接近,结果就会接近。
之所以叫词嵌入,是因为可以将n维的特征向量放到一个n维空间里,每一个向量对应于一个点,相当于把一个点嵌入空间中。
理论上,相似的向量总是会聚在一起,就像聚类一样。词嵌入好处很多,比如信息储存量很大,比如存10000类,只需要300维的向量,相比起来,one-hot就得10000维,这样的话,词嵌入就可以有巨大的词汇量。而且,这种词嵌入是有扩展性的,新进来的类别不会影响维度,所以可以不断扩充。
为了直观查看词嵌入的效果,需要可视化,但是n维空间无法可视化,所以需要降维,这种降维算法就叫做t-SNE,作者是大名鼎鼎的Hinton。
二维化以后,就可以直观地看出词嵌入的分布了。
词嵌入是NLP领域中很重要的概念,但是思想却很朴实,所以开创性的成就不一定复杂。
using word embeddings(使用词嵌入)
词嵌入模型只是一个编码模型,和训练模型是解耦的,所以获取词嵌入模型有两种方式:
- 自己训练,这需要大量的语料库。至于怎么训练,那是后话了。训练的过程,我现在感觉就像是无监督的聚类。
- 直接下载别人的
用已经预训练好的词嵌入模型,迁移到另一个小任务中去训练。这一个训练过程,相当于在已经构建好的空间中,再次嵌入一些新的向量。
为什么是小任务呢?因为新样本的训练无疑会影响原来的向量空间,样本太多就会破坏掉原来的模型,这就和重新训练一个没太大区别了。
最后就是选择性的微调一下。
词嵌入的特性:相似性计算
词嵌入可以用于找同义词,比如男人对女人,我就可以通过国王对应到王后。
设三个embedding后的向量 e m a n , e w o m a n , e k i n g , e q u e e n e_{man},e_{woman},e_{king},e_{queen} eman,ewoman,eking,equeen
从向量角度理解, e m a n − e w o m a n ≈ e k i n g − e q u e e n e_{man}-e_{woman}\approx e_{king}-e_{queen} eman−ewoman≈eking−equeen
假设我们要找出这个 e q u e e n e_{queen} equeen,我们就需要找到满足这个条件的变量: arg max s i m ( e w , e k i n g − e m a n + e w o m a n ) \argmax sim(e_{w},e_{king}-e_{man}+e_{woman}) argmaxsim(ew,eking−eman+ewoman)
s i m ( u , v ) = u T v ∣ ∣ u ∣ ∣ 2 ∣ ∣ v ∣ ∣ 2 sim(u,v)=\dfrac{u^Tv}{||u||_2||v||_2} sim(u,v)=∣∣u∣∣2∣∣v∣∣2uTv
这个sim函数就叫cosine similarity,用于衡量向量相似性,当向量同向,sim=1,垂直就是sim=0,相反就是-1。
另一种计算方式是计算不相似性,这个就是经典的欧氏距离。
通过这种方式,就可以实现基于embedding的类比推理。
训练方法详解
词嵌入矩阵
词嵌入矩阵
E
=
[
e
1
,
e
2
,
⋯
,
e
n
]
E=[e_1,e_2,\cdots,e_n]
E=[e1,e2,⋯,en]
里面的每一列都是embedding后的向量。
如何从词嵌入矩阵中提取一个向量呢,有 E ⋅ o j = e j E\cdot o_j=e_j E⋅oj=ej,因为one-hot的特殊性,故有上面的公式,但是实际要取一个,直接用one-hot中值为1的索引对E切片就好了。
词嵌入学习
Bengio, Yoshua, Réjean Ducharme, and Pascal Vincent. “A neural probabilistic language model.” Advances in neural information processing systems 13 (2000).
建立一个语言模型可以用于学习词嵌入。
思路就是在第一层就用embedding层,之后加入语言模型,感觉有点卷积层的意思了,卷积层就是先提取特征,而我认为embedding就是词元的特征。
由于将E矩阵也在bp的范围内,所以E矩阵会朝着最大化提取信息的方向训练,最终目的是让准确率提高。
另一种理解方式,假设我们给出orange juice,apple juice,purple juice,三个水果都预测出一个juice,为了得到同一个结果,bp训练强迫E矩阵对三种水果形成相近的embeddings。
这一过程听起来比较玄学,效果和原理有待考证。
因为这里没有用到RNN,所以我们可以用多种方式选择参考的输入数据
- 利用n时间窗口,以预测位置的前n个词元作为输入
- 前后各取m,n个单词,有点完形填空的味道
- 取紧挨着的一个单词
Word2Vec算法
Mikolov, Tomas, et al. “Efficient estimation of word representations in vector space.” arXiv preprint arXiv:1301.3781 (2013).
Word2Vec是一种学习词嵌入的算法。
(context,target)配对,作为监督学习。
skip-grams
通过中心词排推断上下文一定窗口内的单词。这个听起来就很难,但是我们也不是要去解决这个问题,而是要顺带学了词嵌入
- 随机选择中心词,标记为context。实际上,并不是随机,有更多的启发式算法。
- 以中心词周边skip-window范围内,随机选择Target词配对。
- 训练链条: o c → E → e c → s o f t m a x → y ^ o_c\rightarrow E\rightarrow e_c\rightarrow softmax \rightarrow \hat{y} oc→E→ec→softmax→y^
- 其中,o和y都是one-hot向量,o是context,y是target
- 其中soft-max层,首先每一个神经元要用一个参数 θ j \theta_j θj与 e c e_c ec点乘,形成一个标量,如此用n个soft-max神经元产生n个结果,之后指数化+归一化,得到概率值。假如n=10000,则公式可以写作: p ( t ∣ c ) = e θ t T e c ∑ j = 1 10000 e θ j T e c p(t|c)=\dfrac{e^{\theta_t^Te_c}}{\sum_{j=1}^{10000}e^{\theta^T_je_c}} p(t∣c)=∑j=110000eθjTeceθtTec,公式中每一个t都代表一类,总共10000类
- 因为soft-max层要计算的太多了(nlp领域中one-hot编码太长了导致n特别大),所以为了加速,有时候会采用二分分级(hierarchical)分类的方式,而且二叉树有时候会按照赫夫曼法则去构建,让高频词更容易出现。另一种方法是后面的负采样。
负采样
Mikolov, Tomas, et al. “Distributed representations of words and phrases and their compositionality.” Advances in neural information processing systems 26 (2013).
首先构造样本,样本由1+k个样本组成:
- 给定context为中心词,保持不变
- 正采样:从窗口中取一个词作为word,标记target为1,寓意是word与context有关
- 负采样:从vocabulary中取k个次作为word,生成k个样本,所有target标记都是0,代表word与context无关,即使是word碰巧出现在了context的窗口内
规定问题:训练一个分类器,辨别context和word是正采样获得的还是负采样获得的
简化后的问题公式就变成了: P ( y = 1 ∣ c , t ) = σ ( θ t T e c ) P(y=1|c,t)=\sigma(\theta_t^Te_c) P(y=1∣c,t)=σ(θtTec),对一个context,我们理论上需要训练n个target二元分类器。
实际上,因为我们只采样了1+k个样本,所以只需要针对这1+k个分类器训练就可以。
直观理解训练原理:
- 一个绕不开的点就是,为什么需要负采样,单一个正采样不可以吗?我们可以理解为抑制。如果只有正采样,那么正采样以外的分类器都只是随机的,可以低也可以高,加了负采样,负采样部分就会被抑制,向着0训练。
- 对于一个分类器说,训练好以后,就可以明辨context-word对是正采样还是负采样来的,那如果是正采样,就意味着单词之间至少都是属于一句话的。
- 进一步讲,如果同一个context对两个不同的word都能得出同样的结果,那么可见这两个word的embedding也是类似的,这可以作为一个直观解释
CBOW(continuous bag of words)
通过上下文推断中心词。和skip-gram基本是相反的。
GloVe词向量算法
Pennington J, Socher R, Manning C D. Glove: Global vectors for word representation[C]//Proceedings of the 2014 conference on empirical methods in natural language processing (EMNLP). 2014: 1532-1543.
GloVe通过算法计算出单词之间的关联度,然后根据关联度进行embedding。
设 x i j = x c t = t a r g e t 出现在 c o n t e n t 附近的次数 x_{ij}=x_{ct}=target出现在content附近的次数 xij=xct=target出现在content附近的次数
如果窗口是对称的,比如左右5个单词的范围,那么很明显 x i j = x j i x_{ij}=x_{ji} xij=xji
训练算法: m i n ∑ i = 1 10000 ∑ j = 1 10000 ( θ i T e j − l o g X i j ) 2 min\sum_{i=1}^{10000}\sum_{j=1}^{10000}(\theta_i^Te_j-logX_{ij})^2 min∑i=110000∑j=110000(θiTej−logXij)2
训练的最终目的本质上还是soft-max,如果ij的关联大,则logX就大,要将算法训练到最小,那么 θ i T e j \theta_i^Te_j θiTej就要尽量大(匹配logX),思考一下soft-max里面的公式,这个就决定了该分类概率的大小。
换言之,这个模型和soft-max的本质一样,X越大,对应于soft-max中概率就越大。
现在有一个问题如果X=0,那么log是无限小的,所以需要调节一下,不要让结果太小,同时,有很多像the,of之类出现频率太高的词也不应该让结果太大,所以就要加一个系数
m i n ∑ i = 1 10000 ∑ j = 1 10000 f ( X i j ) ( θ i T e j − l o g X i j ) 2 min\sum_{i=1}^{10000}\sum_{j=1}^{10000}f(X_{ij})(\theta_i^Te_j-logX_{ij})^2 min∑i=110000∑j=110000f(Xij)(θiTej−logXij)2
当X=0,f(X)=0,其他情况根据算法具体而定,总之是一项启发性的东西。
最后增加的这两项,目前意义不明
m i n ∑ i = 1 10000 ∑ j = 1 10000 f ( X i j ) ( θ i T e j + b i + b j ′ − l o g X i j ) 2 min\sum_{i=1}^{10000}\sum_{j=1}^{10000}f(X_{ij})(\theta_i^Te_j+b_i+b'_j-logX_{ij})^2 min∑i=110000∑j=110000f(Xij)(θiTej+bi+bj′−logXij)2
最后的最后, e w f i n a l = e w + θ w 2 e_w^{final}=\dfrac{e_w+\theta_w}{2} ewfinal=2ew+θw,之所以采取平均,是因为这个和soft-max还是有不同,这里的 θ , e \theta,e θ,e起到的作用类似,有对称性,所以求平均。
补充
情感分类
这属于nlp的一个综合应用。
o n e − h o t → e m b e d d i n g s l a y e r → e → R N N l a y e r → s o f t − m a x l a y e r → r e s u l t one-hot\rightarrow embeddings \ layer \rightarrow e \rightarrow RNN \ layer \rightarrow soft-max \ layer \rightarrow result one−hot→embeddings layer→e→RNN layer→soft−max layer→result
embeddings的E矩阵从另一个地方来,总之是训练好的嵌入矩阵。
然后送进RNN模型,到达模型底部后,利用最后一个隐变量,进行soft-max分类,得到结果。
词嵌入除偏
其实机器学习比较诚实,喂给什么,就只会什么,就一定会什么。
如果喂不好的东西,比如涉及到各种歧视的语句,那么输出就不太好,所以要除偏。
多对多模型(Seq to Seq Models)
多对多模型是真正的应用阶段
基础模型
Sutskever I, Vinyals O, Le Q V. Sequence to sequence learning with neural networks[J]. Advances in neural information processing systems, 2014, 27.
Cho K, Van Merriënboer B, Gulcehre C, et al. Learning phrase representations using RNN encoder-decoder for statistical machine translation[J]. arXiv preprint arXiv:1406.1078, 2014.
其实在前面RNN变体中已经说过了,就是encoder-decoder模型,在这里重申一下。
比如一个机器翻译任务,首先把原文输入到encoder,经过循环,最后一步输出一个隐变量a,用这个a作为decoder的第一个a,decoder的第一个x可以选0,此后每一层循环用的x,都直接用上一层循环的输出y,直到输出整句。
Mao J, Xu W, Yang Y, et al. Deep captioning with multimodal recurrent neural networks (m-rnn)[J]. arXiv preprint arXiv:1412.6632, 2014.
Vinyals O, Toshev A, Bengio S, et al. Show and tell: A neural image caption generator[C]//Proceedings of the IEEE conference on computer vision and pattern recognition. 2015: 3156-3164.
Karpathy A, Fei-Fei L. Deep visual-semantic alignments for generating image descriptions[C]//Proceedings of the IEEE conference on computer vision and pattern recognition. 2015: 3128-3137.
又比如图片解释任务,encoder不一定要从文本中提取,还可以从图片中提取,比如用AlexNet(去掉soft-max层)作为encoder,这样就可以得到一个长度为n的特征向量,把这个向量丢进decoder就可以得到输出。
实际上,Machine translation的encoder-decoder结构中的decoder,就是一个语言模型,且是一个一对多的序列生成语言模型。
抽象一点,就是条件语言模型。
但是问题在于,输出的是一系列soft-max向量,如何选择最佳的句子成为问题。
一种简单的思路就是每个词只取最大概率的,另一种简单的思路是直接按照概率进行采样。
但是这两种都不是很靠谱,第一种可能有误,第二种基于概率就更不靠谱了。
更大的问题是,你只有确定t-1位置的输出概率分布,才能确定t位置的输出概率分布,所以你想要直接得出概率最大的句子,不太容易。进一步说,搜索空间有 1000 0 T 10000^T 10000T这么大,根本搜不完,所以这里反而回归了最优化理论。
arg max y < 1 > , ⋯ , y < T > P ( y < 1 > , ⋯ , y < T > ∣ X ) \mathclap{\argmax_{y^{<1>},\cdots,y^{<T>}}P(y^{<1>},\cdots,y^{<T>}|X)} y<1>,⋯,y<T>argmaxP(y<1>,⋯,y<T>∣X)
比如,假设第一层有AB两个选项,第二层有CD两个选项,第一个词P(A)=0.6,P(B)=0.4,很多人就会选A,但是紧接着第一层选了A以后,P(C|A)=0.5,P(D|A)=0.5,P(C|B)=1,P(D|B)=0.
如果你想当然的选择了A,那么最终结果就是0.6*0.5=0.3,但是如果你先选了B,那么最终结果就可以是0.4*1=0.4,看起来吃亏结果反而概率还更大。
以上的选择思路叫贪心,每次选择最大概率的,这样的问题在于容易陷入局部最优,贪心AC不如不贪心的BC就说明了这种方法的缺陷。
优化算法
Beam Search基本思路
首先确定搜索宽度Beam Width,这里以3举例。
对于 y < 1 > y^{<1>} y<1>,我们从中取三个概率最大的作为束的状态。
下面的符号,注意区分 y < t > 和 y ^ < t > y^{<t>}和\hat{y}^{<t>} y<t>和y^<t>,虽然前者代表通过输出选择出来的one-hot结果,后者代表一个soft-max向量,是输出本身(此处有待考证,仅仅是我的个人猜想,不排除直接把soft-max向量喂到下一个输入的可能)
此后,分别以这三个状态作为 y < 1 > y^{<1>} y<1>,分别去计算三个 y ^ < 2 > \hat{y}^{<2>} y^<2>,由此,这个就代表 P ( y < 2 > ∣ x , y < 1 > ) P(y^{<2>}|x,y^{<1>}) P(y<2>∣x,y<1>),进一步可以计算出 P ( y < 1 > , y < 2 > ∣ x ) = P ( y < 1 > ∣ x ) P ( y < 2 > ∣ x , y < 1 > ) P(y^{<1>},y^{<2>}|x)=P(y^{<1>}|x)P(y^{<2>}|x,y^{<1>}) P(y<1>,y<2>∣x)=P(y<1>∣x)P(y<2>∣x,y<1>),之后,选择 P ( y < 1 > , y < 2 > ∣ x ) P(y^{<1>},y^{<2>}|x) P(y<1>,y<2>∣x)最大的三个 y < 1 > , y < 2 > y^{<1>},y^{<2>} y<1>,y<2>组合作为新的束状态,继续进行递归的计算。
由此可见,这里的局部束搜索仍然和最优化理论中的局部束搜索一样,只不过选择最优束不是通过当前步概率,而是通过联合条件分布 P ( y < 1 > , y < 2 > ∣ x ) P(y^{<1>},y^{<2>}|x) P(y<1>,y<2>∣x)来选择。
局部束的开销并不大,束宽为n,则只需要同时维持n个RNN即可。
如果n=1,则局部束搜索退化成贪心搜索。
改进的Beam Search
首先是概率累乘造成的数值下溢问题。
P ( y < 1 > , ⋯ , y < T > ∣ x ) = arg max y ∏ t = 1 T y P ( y < t > ∣ x , y < 1 > , ⋯ , y < t − 1 > ) P(y^{<1>}, \cdots,y^{<T>}|x)=\\ \argmax_y\prod_{t=1}^{T_y}P(y^{<t>}|x,y^{<1>},\cdots,y^{<t-1>}) P(y<1>,⋯,y<T>∣x)=yargmaxt=1∏TyP(y<t>∣x,y<1>,⋯,y<t−1>)
既然是比大小,那可以施加一个单调函数,这样也不会影响到最后结果,所以进行log处理,类似于对数最大似然的处理方式,可以将趋于0的值转化为负数之和,有效解决下溢
P ( y < 1 > , ⋯ , y < T > ∣ x ) = arg max y ∑ t = 1 T y log P ( y < t > ∣ x , y < 1 > , ⋯ , y < t − 1 > ) P(y^{<1>}, \cdots,y^{<T>}|x)=\\ \argmax_y\sum_{t=1}^{T_y}\log P(y^{<t>}|x,y^{<1>},\cdots,y^{<t-1>}) P(y<1>,⋯,y<T>∣x)=yargmaxt=1∑TylogP(y<t>∣x,y<1>,⋯,y<t−1>)
前面加了单调函数相当于做了个等效变换,所以还是可以按照累乘分析。
这就引出另一个潜在问题,因为输出的长度不是固定的(直到有EOS才会停止,这也是递归的特征),而每一项概率都是小于0的,所以算法会倾向于选择更短的序列,因为少乘1项,结果肯定概率高。如果从对数角度理解,结果也是一样。
这个时候,只需要对长度进行一个修正即可,自然引出归一化。最简单的归一化就是平均,但是还应该考虑到,有时候简单的翻译确实更好,所以最好取个折中,可以继续叠参数,就是加一个指数 α \alpha α,如果指数为1,就是完全平均归一化,如果指数为0,就是完全不归一化,这个指数可以作为超参数调节。
P ( y < 1 > , ⋯ , y < T > ∣ x ) = arg max y 1 T y α ∑ t = 1 T y log P ( y < t > ∣ x , y < 1 > , ⋯ , y < t − 1 > ) P(y^{<1>}, \cdots,y^{<T>}|x)=\\ \argmax_y\dfrac{1}{T_y^\alpha}\sum_{t=1}^{T_y}\log P(y^{<t>}|x,y^{<1>},\cdots,y^{<t-1>}) P(y<1>,⋯,y<T>∣x)=yargmaxTyα1t=1∑TylogP(y<t>∣x,y<1>,⋯,y<t−1>)
局部束搜索的误差分析
假设结果不太好,那么有两种可能:
- RNN出了问题
- Beam Search出了问题
如何以最小的成本定位这两个问题是一个新的问题,毕竟有的大模型跑一次就得几千块。
先针对一个错误样本做判断。
假设 y ∗ y^* y∗是人工结果(绝对正确), y ^ \hat{y} y^是输出的结果。首先计算 P ( y ∗ ∣ x ) , P ( y ^ ∣ x ) P(y^*|x),P(\hat{y}|x) P(y∗∣x),P(y^∣x),然后比较大小,生成两种情况。
-
P
(
y
∗
∣
x
)
>
P
(
y
^
∣
x
)
P(y^*|x)>P(\hat{y}|x)
P(y∗∣x)>P(y^∣x)
理论上Beam Search应该找出最大概率的组合,但是却存在比结果更大更好的组合,说明Beam Search没找好,有优化的空间,比如增大宽度 -
P
(
y
∗
∣
x
)
<
P
(
y
^
∣
x
)
P(y^*|x)<P(\hat{y}|x)
P(y∗∣x)<P(y^∣x)
局部束搜索的结果是基于现有RNN架构上,最好的结果,但是很明显结果并不好,所以是RNN这个基础出了问题,指标就没给好,即使是遍历所有可能也无法找出实际上的最优解
很显然,考虑到局部束搜索并不是遍历,存在运气因素,所以一个样本并不能决定整个模型的错误,我们需要找出所有错误样本,判断每个样本错在哪,最后给整个模型的错误定性,比如Beam Search出问题的比例是90%,那大概率就是Beam Search出bug了。
Bleu得分(选修)
如果同时存在很好的翻译,那么该如何在这些翻译中选出最好的呢?
Bleu就是要解决这个问题,在我心目中,最好的翻译应当是信达雅,但是我不是搞这个领域的,就略过了Bleu了。
注意力模型
直观理解
Bahdanau D, Cho K, Bengio Y. Neural machine translation by jointly learning to align and translate[J]. arXiv preprint arXiv:1409.0473, 2014.
前面说到的Bleu评分用于给翻译打分,但是Bleu的对太短与太长的句子表现都不好,尤其是超过20长度以后准确率会快速下降,这是因为一次性翻译长句子本身就很难。
现实中,人翻译长句子都是一截一截翻译的,有了这种机制,就可以保证翻译长句子时的准确率。
具体到架构,前面的encoder没有太多改变,主要是decoder时,增加了一个注意力矩阵。对每一个decoder输出,都要同时参考所有encoder的隐变量,参考的权重就是注意力权重,写作 α < t , t ′ > \alpha^{<t,t^\prime>} α<t,t′>,其中, t t t是decoder输出的位置, t ′ t^\prime t′是参考的encoder位置,后面这两种记法会很常用。
更多的细节将会在后面解释。
注意力模型详解
Xu K, Ba J, Kiros R, et al. Show, attend and tell: Neural image caption generation with visual attention[C]//International conference on machine learning. PMLR, 2015: 2048-2057.
首先是encoder,实际使用中,encoder通常都是双向RNN,可能使用GRU和LSTM模型。
这样,每一个encoder位置有一正一反两个隐变量。两个隐变量构成这一步的特征向量,写作 a < t ′ > a^{<t^\prime>} a<t′>。
在decoder阶段,用 S < t > S^{<t>} S<t>表示隐变量,这里与前面的架构最为不同。前面的是一对多,用前一个输出作为后一个输入,但是这里还要增加注意力机制,使用 c < t > c^{<t>} c<t>作为上下文的输入,decoder实际上已经变成了n对n的类似于词性鉴别的架构了
我对架构有两个不确定的疑问
- S < 0 > S^{<0>} S<0>是用零向量还是用encoder的隐变量,如果用隐变量,现在的双向RNN已经没有最后的输出了,所以我更倾向于是零向量
- 每一步的输入除了 c < t > c^{<t>} c<t>貌似还有 y < t − 1 > y^{<t-1>} y<t−1>,我并不确定。
现在的架构已经改变,encoder部分是双向RNN,只负责提供隐变量,这些隐变量由注意力矩阵进行采集,构成上下文变量c,供给给decoder部分作为输入,逐层输出。
接下来自上而下说明如何计算c
-
c
<
t
>
=
∑
t
′
α
<
t
,
t
′
>
a
<
t
′
>
c^{<t>}=\sum\limits_{t^\prime}\alpha^{<t,t^\prime>}a^{<t^\prime>}
c<t>=t′∑α<t,t′>a<t′>
c是a的加权平均,权重就是注意力矩阵中的一行 -
∑
t
′
α
<
t
,
t
′
>
=
1
\sum\limits_{t^\prime}\alpha^{<t,t^\prime>}=1
t′∑α<t,t′>=1
很明显,作为权重,必然是要归一化的。 -
α
<
t
,
t
′
>
=
s
o
f
t
m
a
x
t
′
(
e
<
t
,
t
′
>
)
\alpha^{<t,t^\prime>}=\mathop{softmax}\limits_{t^\prime}(e^{<t,t^\prime>})
α<t,t′>=t′softmax(e<t,t′>)
本层的 α \alpha α通过本层的e归一化得来 -
e
<
t
,
t
′
>
=
f
(
s
<
t
−
1
>
,
a
<
t
′
>
)
e^{<t,t^\prime>}=f(s^{<t-1>},a^{<t^\prime>})
e<t,t′>=f(s<t−1>,a<t′>)
这个f是一层神经网络,需要加入架构中用梯度下降训练。有趣的是,a的权重居然要通过a来计算,但是仔细一想又很合理,注意力自然要从词本身出发,另一个参数S可以理解为,翻译一句话除了从原来句中参考,也可以从已经翻译出来的部分参考。
这就是全部算法,至于具体的维度细节,就没有细究了,这个算法的缺陷在于时间复杂度是 O ( T x , T y ) O(T_x,T_y) O(Tx,Ty),因为实际上是要构建一个注意力矩阵。好在句子一般不长,30个词顶天了,所以复杂度还可以容忍。
这个网络训练下来,会收获一个优秀的注意力权重,以及一个好的计算e的参数。
对输入信息选择性的加权,这种注意力思想可以放到各种领域中,比如图片相关,这也和人的视觉机制相似,人的眼睛视野很大,但是你只会注意直视部分,而不是余光。
至于可解释性,貌似已经有注意力机制可视化做出来了,感觉也不会很难,毕竟权重就摆在那里。
speech recognition
基本知识
语音识别是seq2seq应用的另一个领域,视频识别也是类似。
同图片的卷积,文本的embeddings,声音也需要预处理:将波形图转化为声谱图(spectrogram),横轴是时间,纵轴是频率,某一个点的颜色代表某时间某频率的能量(响度)大小,实际上这种方式也是一种仿生处理。
我感觉,频率相当于embeddings的特征维度,那这个声谱图可以理解为类似embeddings的东西,变成声谱图以后,类似的发音就会有类似的特征向量。
现在的语音识别系统都是end-to-end的,不需要手工预处理数据,以前都是先手工把音频转化为phonemes(音位),比如quick转化为kwik,类似于提取音标。
在语音识别领域,输入的维度要远远大于输出维度,因为采样率如果是100,那一秒钟就有100个帧,但是你一秒钟可能就只说了一个字。
注意力模型
这个就很经典,和翻译一模一样,但是输出是26个英文字母(我感觉用词也可以)
这里可见注意力模型的厉害了,如果没有注意力模型,那么长的输入,效果估计不会太好。
CTC模型
Graves A, Fernández S, Gomez F, et al. Connectionist temporal classification: labelling unsegmented sequence data with recurrent neural networks[C]//Proceedings of the 23rd international conference on Machine learning. 2006: 369-376.
这个模型是n对n的等长模型。
比如一个输出可能是“ttt_h_eee___<空格>__qqq__u__ii___ccc_kkk”
对应的识别就是一个“the quick”,
原理在于,下划线是一种分隔符,用于分割连续重复的字母,这样就可以融合逐帧识别产生的冗余数据了。
Trigger word detection(触发字检测)
小爱同学之类的设备,你要唤醒就得使用特定的词,这个词就叫触发词。
这是一个新型领域,所以并没有成熟算法。这里只有一个比较好的例子。
大致思路就是把输入扔进语音识别系统中,输出不是识别出来的语句,而是0和1,如果出现触发词(从头到尾说完一个词),就在结尾输出1,否则就是0.
细节没讲。