引言
词向量模型应该是任何一个NLP工程师都应该掌握的基础。还记得17年刚在实验室实习开始接触时,大家都一直在用word2vec。到了18年在公司实习时,实验大多基于glove模型。到了现在Bert大热,各种基于Bert的词向量模型层出不穷,在各个任务各显神威。最近由系统的学习了下词向量模型,发现其实每个词向量背后都蕴藏着很直观的思想或者很优美的数学推理,我相信对我们现在一些任务都会有启发。在这里记录一些自己的学习心得,如有不当之处,还望指教,多多交流。
在这个(一)里主要记录的是一些词向量的远古模型。包括独热、tfidf、word2vec、glove、fast-text等等。希望能给大家带来一些帮助。
深入浅出语言模型(一)——语言模型及其有趣的应用
深入浅出语言模型(二)——静态语言模型(独热编码、Tf-idf、word2vec、FastText、glove、Gussian Embedding、Pointcare Embedding )
深入浅出语言模型(三)——语境化词向量表示(CoVe、ELMo、ULMFit、GPT、BERT)
深入浅出语言模型(四)——BERT的后浪们(RoBERTa、MASS、XLNet、UniLM、ALBERT、TinyBERT、Electra)
语言模型
什么是语言模型
首先说一下什么是语言模型?简单来说语言模型就是判断一句话是否在语法上通顺。举个小例子,拿一个训练好的语言模型去衡量下面两个句子,应该是:
p
(
今
天
学
习
比
较
枯
燥
)
>
p
(
今
天
枯
燥
比
较
学
习
)
p\left( {今天学习比较枯燥} \right) > p\left( {今天枯燥比较学习} \right)
p(今天学习比较枯燥)>p(今天枯燥比较学习)
简单来说就是看起来更像人话。计算一个句子的概率,
p
(
s
)
=
p
(
w
1
,
w
2
,
w
3
…
w
n
)
p\left( s \right) = p\left( {{w_1},{w_2},{w_3} \ldots {w_n}} \right)
p(s)=p(w1,w2,w3…wn),拿刚才的例子来说:
p
(
今
天
,
学
习
,
比
较
,
枯
燥
)
=
p
(
今
天
)
p
(
学
习
∣
今
天
)
p
(
比
较
∣
今
天
,
学
习
)
p
(
枯
燥
∣
今
天
,
学
习
,
比
较
)
p\left( {今天,学习,比较,枯燥} \right) = p\left( {今天} \right)p\left( {学习|今天} \right)p\left( {比较|今天,学习} \right)p\left( {枯燥|今天,学习,比较} \right)
p(今天,学习,比较,枯燥)=p(今天)p(学习∣今天)p(比较∣今天,学习)p(枯燥∣今天,学习,比较)
但是我们注意一下这个概率
p
(
枯
燥
∣
今
天
,
学
习
,
比
较
)
p\left( {枯燥|今天,学习,比较} \right)
p(枯燥∣今天,学习,比较),它的条件很长,这样的话在语料库中出现的概率很小,例如“比较枯燥”是经常出现的,“学习比较枯燥”也会出现几次,但是“今天学习比较枯燥”可能语料库中很少或者没有,这样就导致了一个问题,数据的稀疏性sparsity。
N-gram
我们如何解决稀疏性问题呢,我们可以使用马尔可夫假设(Markov Assumption)。马尔可夫的一阶假设可以把上述概率看作
p
(
枯
燥
∣
今
天
,
学
习
,
比
较
)
=
p
(
枯
燥
)
p\left( {枯燥|今天,学习,比较} \right) = p\left( {枯燥} \right)
p(枯燥∣今天,学习,比较)=p(枯燥), 我们把他称为Unigram-Model。同理二阶假设可以看作
p
(
枯
燥
∣
今
天
,
学
习
,
比
较
)
=
p
(
枯
燥
∣
比
较
)
p\left( {枯燥|今天,学习,比较} \right) = p\left( {枯燥|比较} \right)
p(枯燥∣今天,学习,比较)=p(枯燥∣比较), 我们把它称为Bigram-Model。类似的,还有Trigram-Model,这就是Nlp非常经典的N-gram Model。
如果用2-gram Model去建模一个句子的话,如下表示:
p
(
s
)
=
p
(
w
1
,
w
2
,
w
3
…
w
n
)
=
p
(
w
1
)
∏
i
=
2
n
(
w
n
∣
w
n
−
1
)
p\left( s \right) = p\left( {{w_1},{w_2},{w_3} \ldots {w_n}} \right)=p(w_1) \prod\limits_{i = 2}^n {\left( {{w_n}|{w_{n - 1}}} \right)}
p(s)=p(w1,w2,w3…wn)=p(w1)i=2∏n(wn∣wn−1)
关于gram的数量如何确定也是一个需要balance的超参数。如果gram数量越少,假设越粗暴,那么里准确值的误差就越大;如果gram数量过大呢有个问题就是条件概率十分稀疏,这个统计可能是没有意义的,本身数量太少。一般大家都采用2-gram,语料库较大的话可以使用3-gram。
Smoothing技术
当然我们利用简单的统计来进行语言模型的学习时会遇到一点小问题,例如有一个句子(今天没有更新博客)这是一个很合理的句子。但是可能由于语料库中没有出现过“没有“和”更新”这两个词语的组合对,那么
p
(
更
新
∣
没
有
)
=
0
p(更新|没有)=0
p(更新∣没有)=0,那么整个句子的概率就为0了,那么这是我们不想看到的情况。
如何解决这个问题呢,很直观的一种方式就是将那些条件概率为0的赋一个很小的值,例如将
p
(
更
新
∣
没
有
)
=
1
0
−
6
p(更新|没有)=10^{-6}
p(更新∣没有)=10−6,这就叫做Smoothing技术。
简单介绍两种常用的Smoothing技术。第一个是Add-one Smoothing(Laplace Smoothing),公式变化如下,其中
V
V
V是词库的大小:
p
(
w
i
∣
w
i
−
1
)
=
c
(
w
i
−
1
,
w
i
)
c
(
w
i
−
1
)
→
p
A
d
d
−
1
(
w
i
∣
w
i
−
1
)
=
c
(
w
i
−
1
,
w
i
)
+
1
c
(
w
i
−
1
)
+
V
p\left( {{w_i}|{w_{i - 1}}} \right) = \frac{{c\left( {{w_{i - 1}},{w_i}} \right)}}{{c\left( {{w_{i - 1}}} \right)}} \to {p_{Add - 1}}\left( {{w_i}|{w_{i - 1}}} \right) = \frac{{c\left( {{w_{i - 1}},{w_i}} \right) + 1}}{{c\left( {{w_{i - 1}}} \right) + V}}
p(wi∣wi−1)=c(wi−1)c(wi−1,wi)→pAdd−1(wi∣wi−1)=c(wi−1)+Vc(wi−1,wi)+1
为什么要加
V
V
V呢,因为通过这样可以保证概率归一化,条件概率之和都是1的。
类似的,还有Add-
K
K
K Smoothing:
p
(
w
i
∣
w
i
−
1
)
=
c
(
w
i
−
1
,
w
i
)
c
(
w
i
−
1
)
→
p
A
d
d
−
K
(
w
i
∣
w
i
−
1
)
=
c
(
w
i
−
1
,
w
i
)
+
K
c
(
w
i
−
1
)
+
K
V
p\left( {{w_i}|{w_{i - 1}}} \right) = \frac{{c\left( {{w_{i - 1}},{w_i}} \right)}}{{c\left( {{w_{i - 1}}} \right)}} \to {p_{Add - K}}\left( {{w_i}|{w_{i - 1}}} \right) = \frac{{c\left( {{w_{i - 1}},{w_i}} \right) + K}}{{c\left( {{w_{i - 1}}} \right) + KV}}
p(wi∣wi−1)=c(wi−1)c(wi−1,wi)→pAdd−K(wi∣wi−1)=c(wi−1)+KVc(wi−1,wi)+K
语言模型的衡量
我们根据统一个语料库可以构建不同的语言模型。具体来说可以是由于数据预处理的不同,或者smotthing技术的不同,还有gram选择的不同。所以说如何衡量一个语言模型好坏呢?
语言模型单独拿出来比较是没有意义的,我们比较的方式可以放在一个特定任务里,比如机器翻译、比如拼写纠错等等。将多个语言模型用在该任务中,然后比较在对应的任务中性能表现。
当然人们还专门提出了一个用来衡量语言模型好坏的指标—困惑值(perplexity)。它的基本思想是:给测试集的句子赋予较高概率值的语言模型较好,当语言模型训练完之后,测试集中的句子都是正常的句子,那么训练好的模型就是在测试集上的概率越高越好。公式表达如下:
P
e
r
p
e
l
e
x
i
t
y
=
2
−
(
x
)
Perpelexity = {2^{ - (x)}}
Perpelexity=2−(x)
其中
x
x
x是测试集句子的average log likelihood。
对于一个2-gram的句子
(
w
1
,
w
2
,
w
3
,
.
.
.
,
w
n
)
(w_1, w_2,w_3,...,w_n)
(w1,w2,w3,...,wn)来说,
x
x
x如下表示:
(
log
p
(
w
1
)
+
l
p
g
p
(
w
2
∣
w
1
)
+
.
.
.
+
log
p
(
w
n
∣
w
n
−
1
)
)
/
n
(\log p\left( {{w_1}} \right) + lpgp\left( {{w_2}|{w_1}} \right) + ... + \log p({w_n}|{w_{n - 1}}))/n
(logp(w1)+lpgp(w2∣w1)+...+logp(wn∣wn−1))/n
由公式可知,句子概率越大,语言模型越好,迷惑度越小。下图是一个简单的实验结果:
语言模型的一些有趣应用
拼写纠错(Spell Correction)
例如用户想要输入的是“篮球比赛”
, 然而手动输入的是“蓝球比赛”
;还有用户想输入"人工智能"
,然而手动输入的确实“人工只能”
等等,这样的错误很常见,那么我们应该如何拼写纠错呢?
很自然的一个想法,我们纠错之后的结果要和原来的结果要有个很大的相似度,挤编辑距离很小。那么可以首先去寻找和原来输入最小的编辑距离的单词集合,形成一个候选的词典。然而我们不能从一个单词来考虑,还要考虑他的上下文。那么怎么考虑上下文信息呢?这就用到我们的语言模型了。
我们具体的语言模型可以分为两步走。
第一步:生成和错误单词编辑距离为1,2的字串。
第二步:进行过滤,将改正后的单词放到上下文中去衡量修改后的句子在语言模型中得到的概率分数。
假设一句话中错误单词是
w
w
w,我们要将其改为正确的形式
c
c
c,我们看一看数学推导吧。
c
∗
=
arg
max
c
∈
d
i
c
t
P
(
c
∣
w
)
=
arg
max
c
∈
d
i
c
t
p
(
w
∣
c
)
p
(
c
)
p
(
w
)
=
arg
max
c
∈
d
i
c
t
p
(
w
∣
c
)
p
(
c
)
{c^*} = \mathop {\arg \max }\limits_{c \in dict} P\left( {c|w} \right) = \mathop {\arg \max }\limits_{c \in dict} \frac{{p\left( {w|c} \right)p\left( c \right)}}{{p\left( w \right)}} = \mathop {\arg \max }\limits_{c \in dict} p\left( {w|c} \right)p\left( c \right)
c∗=c∈dictargmaxP(c∣w)=c∈dictargmaxp(w)p(w∣c)p(c)=c∈dictargmaxp(w∣c)p(c)
其中
p
(
w
∣
c
)
p(w|c)
p(w∣c)是用来衡量
c
c
c和
w
w
w的相关分数的,具体分数由他们的编辑距离来决定。
p
(
c
)
p(c)
p(c)是用来衡量词语
c
c
c放到语言模型中所得到的分数。
这里举个简单的例子,例如原句为 My favourite foot is apple。 其中我们想要对“foot”进行修改。我们有如下替换候选词(food, float…)。
先看
p
(
w
∣
c
)
p(w|c)
p(w∣c)。那么food和foot的编辑距离为1,而float和foot编辑距离为2,那么我们可以令
p
(
f
o
o
t
∣
f
o
o
d
)
=
0.8
p(foot|food) = 0.8
p(foot∣food)=0.8,
p
(
f
o
o
t
∣
f
l
o
a
t
)
=
0.4
p(foot|float) = 0.4
p(foot∣float)=0.4。当具体建模这个分数时,还可以加入一些先验条件,例如百度或者搜狗等公司会有一些用户输入记录,例如用户拼写
c
c
c的时候,有多大概率拼成
w
w
w等等,也可以根据这种信息进行建模。
再看
p
(
c
)
p(c)
p(c)的计算方式。以food为例,我们将food代替foot放入原句,得到 My favourite food is apple,用我们刚才说的语言模型去衡量这个句子是否通顺,即:
p ( c ) ∼ p ( M y ) p ( f a v o u r i t e ∣ M y ) p ( f o o d ∣ f a v o u r i t e ) p ( i s ∣ f o o d ) p ( a p p l e ∣ i s ) p\left( c \right) \sim p\left( {My} \right)p\left( {favourite|My} \right)p\left( {food|favourite} \right)p\left( {is|food} \right)p\left( {apple|is} \right) p(c)∼p(My)p(favourite∣My)p(food∣favourite)p(is∣food)p(apple∣is)
分词
现在分词的工具有很多,例如Jieba分词、HanNLP分词等等。
最原始的一种方式是最大前向匹配(forward-max matching)。例如一个句子(今天学习比较枯燥),会从第一个字符开始去词库里有没有找对应词,没有的话在加一个字符,去词库寻找。例如,“今”去词库里没有找到,“今天”找到了,那么就把“今天”给分出来了。然后再从“学”开始。这样的方法很简单,缺点却很明显,因为分词的方式显然不只是这么一种。
那么我们可以生成所有可能的分割,然后根据语言模型选择出一个最好的。其实这也是动态规划的一个小问题。我们设函数
f
(
i
)
f(i)
f(i)表示到第
i
i
i为结尾,也就是划分到第
i
i
i个字符的语言模型最高分数。
例如一个句子
S
=
w
1
w
2
w
3
.
.
.
w
n
S = {w_1}{w_2}{w_3}...{w_n}
S=w1w2w3...wn,词典里有[
w
1
w
2
w_1w_2
w1w2,
w
3
w_3
w3,
w
1
w
2
w
3
w_1w_2w_3
w1w2w3,…]。,那么
f
(
3
)
=
m
a
x
(
w
1
w
2
+
w
3
,
w
1
w
2
w
3
)
f(3)=max(w_1w_2+w_3, w_1w_2w_3)
f(3)=max(w1w2+w3,w1w2w3),以此类推,最后求得
f
(
n
)
f(n)
f(n)就是最后的划分。
最后说一句,语言模型是一种经典的无监督算法,不需要任何的标签信息(label)