图解Word2Vec
图解Word2Vec
嵌入(Embedding)是机器学习中最令人着迷的 idea 之一。如果你使用过 Siri、Google Assistant、Alexa、Google Translate,或者是带有预测下一个词的功能的智能手机键盘,那么你已经从这个成为 NLP 模型核心的 idea 中受益。在过去的几十年中,将嵌入用于神经网络模型方面取得了相当大的发展,如最新的 BERT、GPT2 等。
2013年以来,Word2Vec 成为了一种创建词嵌入的有效方法。除了作为词嵌入方法之外,它的一些概念已被证明在创建推荐引擎和理解顺序数据方面非常有效,甚至在商业、非语言任务中。 Airbnb、阿里巴巴、Spotify 和 Anghami 等公司都从中受益,并将其用于生产中以增强新型推荐引擎的能力。
在这篇文章中,我们将回顾嵌入的概念,以及使用 Word2Vec 生成嵌入的机制。但是让我们从一个例子开始,以熟悉使用向量来表示事物。您知道五个数字(一个向量)的列表可以代表您的个性吗?
1.个性嵌入:你是什么样的人?
在
0
0
0 到
100
100
100 的范围内,您的内向 / 外向程度如何(其中
0
0
0 表示最内向,
100
100
100 表示最外向)?你是否参加过像 MBTI 这样的性格测试,或者大五人格特质测试?这些测试会问你一系列问题,从多个方面给你打分,内向 / 外向就是其中之一。
如果我的内向 / 外向得分为
38
/
100
38 / 100
38/100,我们可以这样绘制:
让我们将范围从
−
1
-1
−1 切换到
1
1
1:
人是复杂的,一个特征往往不够描述一个人。因此,我们添加另一个维度,测试另一个特征的分数。
我们现在可以说这个向量一定程度上代表了我的个性。当你想将另外两个人与我进行比较时,这种表示的用处就来了。假设我被公共汽车撞了,我需要被性格相似的人代替。下图两个人中,哪一个更像我?
在处理向量时,计算相似度分数的常用方法是余弦相似度:
两个维度的信息不足以说明人们之间的不同。数十年的心理学研究得出了五个主要特征(以及大量子特征)。我们可以在比较中使用所有的五个维度:
五维的问题在于我们无法在二维中绘制整齐的箭头。这是机器学习中的一个常见挑战,我们经常需要在高维空间中思考。不过,余弦相似度仍然有效,它适用于任意数量的维度:
在本节的最后,提出两个中心思想:
- 我们可以将人(和事物)表示为数字向量(这对机器来说很 Nice)。
- 我们可以很容易地计算出向量之间的相似程度。
2.词嵌入(Word Embeddings)
基于上文的理解,我们就可以继续研究经过训练的词向量示例(也称为词嵌入),并开始研究它们的一些有趣属性。
这是单词 king
的词嵌入(在维基百科上训练的 GloVe 向量):
[ 0.50451 , 0.68607 , -0.59517 , -0.022801, 0.60046 , -0.13498 , -0.08813 , 0.47377 , -0.61798 , -0.31012 , -0.076666, 1.493 , -0.034189, -0.98173 , 0.68229 , 0.81722 , -0.51874 , -0.31503 , -0.55809 ,0.66421,0.1961,-0.13495,-0.11476,-0.30344,0.41177,-2.223,-1.0756,-1.0783,-1.0783,-0.34354,0.34354,0.3354,0.33505,0.33505,1.9927,1.9927,0.19927,0.199234.194234,-04234,-04234,-04.44.4.44,4.44,4.44,4.44,4.44,4.44,4.44.44,, 0.8523 , 0.1661 , 0.40102 , 1.1685 , -1.0137 , -0.21585 , -0.15155 , 0.78321 , -0.91241 , -1.6106 , -0.64426 , -0.51042 ]
这是一个包含
50
50
50 个数字的列表。仅仅查看值说明不了太多的问题,让我们把这些数字放在一行中:
我们根据它们的值对单元格进行颜色编码(如果它们接近
2
2
2,则为红色;如果它们接近
0
0
0,则为白色;如果它们接近
−
2
-2
−2,则为蓝色):
我们将忽略数字,仅查看颜色以指示单元格的值。现在让我们将 king
与其他词进行对比:
分析一下 man
和 woman
彼此之间的相似度比他们中的任何一个与 king
的相似度如何?矢量表示捕获了这些词的更多的信息、含义、关联。
这是另一个示例列表(通过垂直扫描列来查找具有相似颜色的列进行比较):
- 所有这些不同的词都有一个红色的直线。它们在那个维度上是相似的(我们也不知道每个维度编码的是什么)。
- 可以看到
woman
和girl
在很多地方是相似的。与man
和boy
相同。 boy
和girl
也有相似之处,但又不同于woman
或man
。这些会不会是一种代表青年人的编码?- 除最后一个词外,其他词都是代表人的词。我添加了一个
water
来显示类别之间的差异。例如,你可以看到蓝色柱一直向下并在嵌入water
之前停止。 king
和queen
在某些地方很相似,但又各不相同。
3.类比
我们可以添加和减去词嵌入并得出比较有意思的结果。最著名的例子是公式:king
– man
+ woman
:
我们可以像之前那样想象这个类比:
现在我们已经知道了训练后的词嵌入是个啥样的了,让我们进一步了解训练过程。但在我们开始使用 Word2Vec 之前,需要先了解一下词嵌入的父概念:神经语言模型(Neural Language Model)。
4.语言建模
如果要举一个 NLP 应用的例子,最好的例子之一就是智能手机键盘的下一个词的预测功能。
下一个单词预测是一项可以由语言模型解决的任务。语言模型可以获取单词列表(假设是两个单词),并尝试预测它们后面的单词。在上面的屏幕截图中,模型接收这两个绿色词(thou
shalt
)并返回建议列表(not
是概率最高的那个):
可以将其理解为一个黑盒模型:
在实践中,该模型不会只输出一个词。它实际上输出的是它知道的所有单词的概率分数(模型的 “词汇表”,范围从几千到超过一百万个单词)。然后键盘应用程序必须找到得分最高的单词,并将其呈现给用户。
训练后,早期的神经语言模型 (Bengio 2003) 将分三步计算预测:
在讨论嵌入时,第一步与我们最相关。训练过程的结果是一个矩阵,它包含我们词汇表中每个单词的嵌入。在预测期间,我们只是查找输入词的嵌入,并使用它们来计算预测:
现在让我们将重点放到模型训练上,来学习一下如何构建这个映射矩阵。
5.语言模型训练
相较于大多数其他机器学习模型,语言模型有一个很大有优势,那就是我们有丰富的文本来训练语言模型。我们所有的书籍、文章、维基百科、及各种类型的文本内容都可以利用。相比之下,许多其他机器学习的模型开发就需要手工设计数据或者专门采集数据。
我们通过查看单词倾向于出现在哪些其他单词旁边来获得它们的嵌入。其机制是:
- 我们获得了大量文本数据(例如,所有维基百科文章)。
- 我们有一个窗口(比如
3
3
- 滑动窗口为我们的模型生成训练样本。
当这个窗口在文本上滑动时,我们生成了一个我们用来训练模型的数据集。为了准确地了解这是如何完成的,让我们看看滑动窗口如何处理这个短语。
当我们开始时,窗口在句子的前三个词上:
我们将前两个词作为特征,第三个词作为标签:
然后我们将窗口滑动到下一个位置并创建第二个样本:
很快我们就有了一个更大的数据集,其中单词往往出现在不同单词对之后:
在实际应用中,模型往往在我们滑动窗口时就在训练。但是我觉得将 生成数据集 和 训练模型 分为两个阶段会显得更清晰易懂一些。除了使用神经网络建模之外,大家还常用一项名为 N-gams 的技术训练模型。
6.顾及两边
根据前面的信息进行填空:
在空白前面有五个单词(如果事先提及到 bus
),可以肯定,大多数人都会把 bus
填入空白中。但是如果我再给你一条信息:比如空白后的一个单词,那答案会有变吗?
这下空白处该填的内容完全变了。这时 red
这个词最有可能适合这个位置。从这个例子中我们可以看到,一个单词的前后词语都带信息。事实证明,我们需要考虑两个方向的单词(目标单词的左侧单词与右侧单词)。那我们该如何调整训练方式以满足这个要求呢?
7.Skip-Gram
我们不仅可以查看目标词之前的两个词,还可以查看目标词之后的两个词。
如果我们这样做,我们实际上正在构建和训练模型的数据集将如下所示:
这称为连续词袋(Continuous Bag of Words,CBOW)模型,在一篇 Word2Vec 论文中进行了描述。另一种模型 Skip-Gram 也往往会显示出很好的结果,但它的做法略有不同。
Skip-Gram 不是根据上下文(前后的词)来猜测词,而是尝试使用当前词来猜测相邻的词。我们可以把它在训练文本上滑动的窗口想象成这样:
绿色插槽中的单词将是输入单词,每个粉红色框将是可能的输出。粉红色的盒子有不同的阴影,因为这个滑动窗口实际上在我们的训练数据集中创建了四个独立的样本:
这种方法称为 Skip-Gram 架构。我们可以将滑动窗口可视化为执行以下操作:
这会将这四个样本添加到我们的训练数据集中:
然后我们将窗口滑动到下一个位置:
这会生成接下来的四个示例:
在移动几组位置之后,我们就能得到一批样本:
8.回顾训练过程
现在我们有了从现有运行文本中提取的 Skip-Gram 训练数据集,让我们看一下如何使用它来训练预测相邻词的基本神经语言模型。
我们从数据集中的第一个样本开始。我们抓住这个特征并提供给未经训练的模型,要求它预测一个合适的相邻词。
该模型执行这三个步骤并输出一个预测向量(为其词汇表中的每个单词分配一个概率)。由于模型未经训练,因此在这个阶段它的预测肯定是错误的。但没关系,我们知道它应该猜到什么词,这个词就是我们训练集数据中的输出标签:
“目标向量” 是目标词的概率为
1
1
1,其他所有词的概率为
0
0
0 的向量。
模型误差是多少?我们将两个向量相减得到一个误差向量:
这个误差向量现在可以用来更新模型,所以在下一轮预测中,如果用 not
作为输入,我们更有可能得到 thou
作为输出。
以上就是训练的第一步了。我们接下来继续对数据集内下一份样本进行同样的操作,直到我们遍历所有的样本。这就是一轮(epoch)了。我们再多做几轮(epoch),得到训练过的模型,于是就可以从中提取嵌入矩阵来用于其他应用了。
以上确实有助于我们理解整个流程,但这依然不是 Word2Vec 真正训练的方法。我们错过了一些关键的想法。
9.负采样
回想一下这个神经语言模型计算预测值的三个步骤:
从计算的角度来看,第三步非常昂贵,尤其是当我们将需要在数据集中为每个训练样本都做一遍(很容易就多达数千万次)。我们需要寻找一些提高表现的方法。
一种方法是将我们的目标分为两个步骤:
- 生成高质量的词嵌入(不用担心下一个词的预测)。
- 使用这些高质量的嵌入来训练语言模型(进行下一个词预测)。
在本文中我们将专注于第
1
1
1 步(因为这篇文章专注于嵌入)。要使用高性能模型生成高质量嵌入,我们可以改变一下预测相邻单词这一任务:
并将其切换到一个接受输入和输出词的模型,并输出一个分数来指示他们是否是邻居(
0
0
0 表示 “不是邻居”,
1
1
1 表示 “邻居”)。
这个简单的变换将我们需要的模型从神经网络更改为逻辑回归模型,因此计算变得更加简单和快速。
该变换需要我们切换数据集的结构:标签现在是一个值为
0
0
0 或
1
1
1 的新列。它们将全部为
1
1
1,因为我们添加的所有单词都是邻居。
现在的计算速度可谓是神速,在几分钟内就能处理数百万个例子。但是我们还需要解决一个漏洞。如果所有的例子都是邻居(目标:
1
1
1),这个 “天才模型” 可能会被训练得永远返回
1
1
1,准确性是百分百了,但它什么东西都学不到,只会产生垃圾的嵌入结果。
为了解决这个问题,我们需要在我们的数据集中引入负样本,即不是相邻单词的样本。我们的模型需要为这些样本返回
0
0
0。模型必须努力解决这个挑战,而且依然必须保持高速。
但是填写什么作为输出词呢?可以从词汇表中随机抽取单词。
这个想法受到噪声对比估计(Noise-contrastive estimation)的启发。我们将实际信号(相邻词的正例)与噪声(随机选择的非相邻词)进行对比。这导致计算和统计效率的巨大折衷。
10.带负采样的 Skip-Gram(SGNS)
我们现在已经涵盖了 Word2Vec 中的两个中心思想:负采样、Skip-Gram。
11.Word2Vec 训练过程
现在我们已经确定了 Skip-Gram 和负采样这两个中心思想,我们可以继续研究 Word2Vec 实际的训练过程。
在训练开始之前,我们对训练模型所针对的文本进行预处理。在此步骤中,我们确定词汇量的大小(我们将其称为 vocab_size
,可以将其视为
10000
10000
10000)以及哪些词属于它。
在训练阶段开始时,我们创建了两个矩阵,嵌入(Embedding)矩阵和上下文(Context)矩阵。这两个矩阵对我们词汇表中的每个单词都有一个嵌入(因此 vocab_size
是它们的维度之一)。第二个维度是每个嵌入的长度(embedding_size
),
300
300
300 是一个常见值,但我们在前文也已经看过了
50
50
50 的示例。
在训练过程开始时,我们用随机值初始化这些矩阵。然后开始训练过程。在每个训练步骤中,我们采用一个正例及其相关的负例。让我们来看看第一组:
现在我们有四个词:输入词 not
和输出 / 上下文词:thou
(实际邻居)、aaron
(反例)和 taco
(反例)。我们继续查找它们的嵌入:对于输入词,我们查看嵌入矩阵。对于上下文词,我们查看上下文矩阵(即使两个矩阵都对词汇表中的每个词有一个嵌入)。
然后,我们计算输入嵌入与每个上下文嵌入的点积。在每种情况下,结果都将是表示输入和上下文嵌入的相似性的数字。
现在我们需要一种方法将这些分数转化为看起来像概率的东西:需要它们都是正的,并且值介于
0
0
0 和
1
1
1 之间。这对于
s
i
g
m
o
i
d
sigmoid
sigmoid 逻辑运算来说不是什么难事。
我们现在可以将
s
i
g
m
o
i
d
sigmoid
sigmoid 操作的输出视为这些示例的模型输出。可以看到 taco
得分最高,而 aaron
在
s
i
g
m
o
i
d
sigmoid
sigmoid 操作前后仍然得分最低。
既然未经训练的模型已经做出了预测,并且我们有一个实际的目标标签可以与之进行比较,那么计算一下模型预测中的误差有多大。为此,我们只需从目标标签中减去
s
i
g
m
o
i
d
sigmoid
sigmoid 分数。
这是 “机器学习” 的 “学习” 部分。我们现在可以使用这个误差分数来调整 not
、thou
、aaron
和 taco
的嵌入,以便下次我们进行此计算时,结果会更接近目标分数。
训练步骤到此结束。对于这一步中涉及的词(not
、thou
、aaron
和 taco
),我们从中得到了更好一些的嵌入。我们现在继续下一步(下一个正样本及其相关的负样本)并再次执行相同的过程。
当我们循环遍历整个数据集多次时,嵌入会继续得到改进。然后我们就可以停止训练过程,丢弃 Context 矩阵,并使用 Embeddings 矩阵作为下一项任务的已被训练好的嵌入。
12.窗口大小和负样本数
Word2Vec 训练过程中的两个关键超参数是:窗口大小、负样本数量。
不同的任务适合不同的窗口大小。一种启发式方法是,使用较小的窗口大小(
2
−
15
2-15
2−15)会得到这样的嵌入:两个嵌入之间的高相似性得分表明这些单词是可互换的(注意,如果我们只查看附近距离很近的单词,反义词通常可以互换。例如,good
和 bad
经常出现在类似的语境中)。使用较大的窗口大小(
15
−
50
15-50
15−50,甚至更多)会得到相似性更能指示单词相关性的嵌入。在实际操作中,你通常需要对嵌入过程提供指导以帮助读者得到相似的 “语感”。Gensim 默认窗口大小为
5
5
5(除了输入字本身以外还包括输入字之前与之后的两个字)。
负样本的数量是训练过程的另一个因素。原始论文规定
5
−
20
5-20
5−20 是一个很好的负样本数。它还指出,当你拥有足够大的数据集时,
2
−
5
2-5
2−5 似乎就足够了。 Gensim 默认是
5
5
5 个负样本。
总结
现在你对词嵌入和 Word2Vec 算法应该有所了解。希望当你现在阅读一篇提到 带负采样的 Skip-Gram(SGNS) 的论文时(比如文章开篇提及的推荐系统论文),你对这些概念能有更好的理解。