一、引例
我发现嵌入的概念是机器学习中最迷人的想法之一。如果您曾经使用过 Siri、Google Assistant、Alexa、Google Translate,甚至是带有下一个词预测功能的智能手机键盘,那么您很可能已经从这个已成为自然语言处理模型核心的想法中受益。在过去的几十年中,将嵌入用于神经模型方面取得了相当大的发展(最近的发展包括上下文词嵌入导致了BERT和 GPT2等尖端模型)。
Word2vec 是一种有效创建词嵌入的方法,自 2013 年以来一直存在。但除了作为词嵌入方法的实用性之外,它的一些概念已被证明在创建推荐引擎和理解顺序数据方面非常有效,甚至在商业、非语言任务中。Airbnb、阿里巴巴、Spotify和Anghami等公司都受益于从 NLP 世界中挖掘出这一出色的机器,并将其用于生产中以增强新型推荐引擎的能力。
在这篇文章中,我们将回顾嵌入的概念,以及使用 word2vec 生成嵌入的机制。但是让我们从一个例子开始,以熟悉使用向量来表示事物。您知道五个数字(一个向量)的列表可以代表您的个性吗?
在 0 到 100 的范围内,您的内向/外向程度如何(其中 0 表示最内向,100 表示最外向)?你有没有参加过像 MBTI 这样的性格测试——或者更好的是,大五人格特质测试?如果你还没有,这些测试会问你一系列问题,然后在多个轴上给你打分,内向/外向就是其中之一。
想象一下,我的内向/外向得分为 38/100。我们可以这样绘制:
让我们将范围从 -1 切换到 1:
你觉得你认识一个人只知道关于他们的一条信息有多好?不多。人是复杂的。因此,让我们添加另一个维度——测试中另一个特征的分数。
我隐藏了我们正在绘制的特征,这样您就可以习惯于不知道每个维度代表什么——但仍然可以从一个人的个性的矢量表示中获得很多价值。
我们现在可以说这个向量部分代表了我的个性。当您想将另外两个人与我进行比较时,这种表示的用处就来了。假设我被巴士撞到,我需要被性格相似的人代替。下图中,两个人哪个更像我?
在处理向量时,计算相似度分数的常用方法是cosine_similarity:
第一个人在性格上更像我。指向同一方向的向量(长度也有影响)具有更高的余弦相似度得分。再一次,两个维度不足以捕捉足够的关于人们有多么不同的信息。数十年的心理学研究得出了五个主要特征(以及大量子特征)。因此,让我们在比较中使用所有五个维度:
五维的问题在于我们无法在二维中绘制整齐的小箭头。这是机器学习中的一个常见挑战,我们经常需要在高维空间中思考。不过,好消息是 cosine_similarity 仍然有效。它适用于任意数量的维度:
cosine_similarity 适用于任意数量的维度。这些分数要好得多,因为它们是根据所比较事物的更高分辨率表示计算得出的。在本节的最后,我希望我们提出两个中心思想:1.我们可以将人(和事物)表示为数字向量(这对机器来说很棒!)。2.我们可以很容易地计算出向量之间的相似程度。
二、词嵌入
有了这种理解,我们就可以继续研究经过训练的词向量示例(也称为词嵌入),并开始研究它们的一些有趣属性。
这是单词“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 , -0.34354 , 0.33505 , 1.9927 , -0.04234 , -0.64319 , 0.71125 , 0.49159 , 0.16754 , 0.34344 , -0.25663 , -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 个数字的列表。我们不能通过查看值来说明太多。但是让我们稍微形象化一下,以便我们可以将它与其他词向量进行比较。让我们将所有这些数字放在一行中:
让我们根据它们的值对单元格进行颜色编码(如果它们接近 2,则为红色,如果它们接近 0,则为白色,如果它们接近 -2,则为蓝色):
我们将忽略数字,仅查看颜色以指示单元格的值。现在让我们将“King”与其他词进行对比:
看看“男人”和“女人”彼此之间的相似度比他们中的任何一个与“国王”的相似度如何?这告诉你一些事情。这些矢量表示捕获了这些词的相当多的信息/含义/关联。
这是另一个示例列表(通过垂直扫描列来查找具有相似颜色的列进行比较)
有几点需要指出:
1.所有这些不同的词都有一个直的红色柱子。它们在那个维度上是相似的(我们不知道每个维度的代码是什么)
2.你可以看到“女人”和“女孩”在很多地方是多么相似。与“男人”和“男孩”相同
3.“boy”和“girl”也有相似之处,但又不同于“woman”或“man”。这些会不会是一种模糊的青年概念的编码?可能的。
4.除最后一个词外,其他词都是代表人的词。我添加了一个对象(水)来显示类别之间的差异。例如,您可以看到蓝色柱一直向下并在嵌入“水”之前停止。
5.“国王”和“王后”在某些地方很相似,但又各不相同。这些可能是在为一个模糊的版税概念编码吗?
类比
展示嵌入令人难以置信的特性的著名例子是类比的概念。我们可以添加和减去词嵌入并得出有趣的结果。最著名的例子是公式:“国王”-“男人”+“女人”:
使用python 中的Gensim库,我们可以添加和减去词向量,它会找到与结果向量最相似的词。该图像显示了最相似单词的列表,每个单词都有其余弦相似度。我们可以像之前那样想象这个类比:
来自“king-man+woman”的结果向量并不完全等于“queen”,但“queen”是我们在这个集合中拥有的 400,000 个词嵌入中最接近它的词。现在我们已经了解了经过训练的词嵌入,让我们进一步了解训练过程。但在我们开始使用 word2vec 之前,我们需要先了解一下词嵌入的概念父类:神经语言模型。
三、语言建模
“先知不会被过去、现在和未来的幻想所转移。语言的固定性决定了这种线性区分。先知们拿着一把打开语言锁的钥匙。
这不是一个机械宇宙。事件的线性进展是由观察者强加的。因果?根本不是这样。先知说出了决定性的话。你瞥见了一件“注定要发生”的事情。但预言的瞬间释放出某种具有无限预兆和力量的东西。宇宙经历了一场幽灵般的转变。” ~沙丘神帝
如果要举一个 NLP 应用的例子,最好的例子之一就是智能手机键盘的下一个词预测功能。这是一个每天有数十亿人使用数百次的功能。
下一个单词预测是一项可以通过语言模型解决的任务。语言模型可以获取单词列表(假设是两个单词),并尝试预测它们后面的单词。
在上面的屏幕截图中,我们可以将模型视为接收这两个绿色词 ( thou shalt) 并返回建议列表(“不是”是概率最高的那个)的模型:
我们可以认为模型看起来像这个黑盒子:
但在实践中,模型不会只输出一个词。它实际上输出它知道的所有单词的概率分数(模型的“词汇表”,范围从几千到超过一百万个单词)。然后键盘应用程序必须找到得分最高的单词,并将其呈现给用户。
神经语言模型的输出是模型知道的所有单词的概率分数。我们在这里将概率称为百分比,但 40% 实际上在输出向量中表示为 0.4。
经过训练后,早期的神经语言模型 ( Bengio 2003 ) 将分三步计算预测:
在我们讨论嵌入时,第一步与我们最相关。训练过程的一个结果是这个矩阵,它包含我们词汇表中每个单词的嵌入。在预测期间,我们只是查找输入词的嵌入,并使用它们来计算预测:
现在让我们转向训练过程,以了解有关如何开发此嵌入矩阵的更多信息。
四、语言模型训练
与大多数其他机器学习模型相比,语言模型具有巨大优势。这个优势是我们能够在运行文本上训练他们——我们有丰富的文本。想一想我们身边所有的书籍、文章、维基百科内容和其他形式的文本数据。将此与许多其他需要手工制作的功能和专门收集的数据的机器学习模型进行对比。
单词通过我们查看它们倾向于出现在哪些其他单词旁边来获得它们的嵌入。其机制是
1.我们获得了大量文本数据(例如,所有维基百科文章)。然后
2.我们有一个窗口(比如,三个词),我们可以在所有文本上滑动。
3.滑动窗口为我们的模型生成训练样本
当这个窗口在文本上滑动时,我们(虚拟地)生成了一个我们用来训练模型的数据集。为了准确地了解这是如何完成的,让我们看看滑动窗口如何处理这个短语:
当我们开始时,窗口在句子的前三个词上:
我们将前两个词作为特征,第三个词作为标签:
我们现在已经在数据集中生成了第一个样本,稍后我们可以使用它来训练语言模型。然后我们将窗口滑动到下一个位置并创建第二个样本:
现在生成了第二个示例。很快我们就有了一个更大的数据集,其中的单词往往出现在不同的单词对之后:
在实践中,模型倾向于在我们滑动窗口时进行训练。但我发现从逻辑上将“数据集生成”阶段与训练阶段分开会更清楚。除了基于神经网络的语言建模方法外,一种称为 N-gram 的技术通常用于训练语言模型(请参阅:语音和语言处理的第 3 章)。要了解从 N-gram 到神经模型的这种转变如何反映在现实世界的产品上,这里是我最喜欢的 Android 键盘Swiftkey 在 2015 年发表的一篇博文,介绍了他们的神经语言模型并将其与之前的 N-gram 模型进行了比较。我喜欢这个例子,因为它向您展示了如何在营销演讲中描述嵌入的算法属性。
Look both ways
知道你从帖子前面知道的内容,填空:
我在这里给你的上下文是空白词之前的五个词(以及之前提到的“bus”)。我敢肯定大多数人会猜到这个词bus会填入空白。但是,如果我再给你一条信息——空格后的一个词,那会改变你的答案吗?
这完全改变了应该放在空白处的内容。这个词red现在最有可能进入空白。我们从中学到的是特定单词前后的单词都具有信息价值。事实证明,考虑到两个方向(我们猜测的单词左侧和右侧的单词)会导致更好的词嵌入。让我们看看我们如何调整我们训练模型的方式来解决这个问题。
Skipgram
我们不仅可以查看目标词之前的两个词,还可以查看目标词之后的两个词。
如果我们这样做,我们实际上正在构建和训练模型的数据集将如下所示:
这称为连续词袋体系结构,在一篇 word2vec 论文[pdf]中进行了描述。另一种架构也往往会显示出很好的结果,但它的做法略有不同。
这种架构不是根据上下文(前后的词)来猜测词,而是尝试使用当前词来猜测相邻的词。我们可以把它在训练文本上滑动的窗口想象成这样:
绿色插槽中的单词将是输入单词,每个粉红色框将是可能的输出。粉红色的盒子有不同的阴影,因为这个滑动窗口实际上在我们的训练数据集中创建了四个独立的样本:
这种方法称为skipgram架构。我们可以将滑动窗口可视化为执行以下操作:
这会将这四个样本添加到我们的训练数据集中:
然后我们将窗口滑动到下一个位置:
这会生成我们接下来的四个示例:
几个职位之后,我们有更多的例子:
重温训练过程
现在我们有了从现有运行文本中提取的 skipgram 训练数据集,让我们看一下我们如何使用它来训练预测相邻词的基本神经语言模型。
我们从数据集中的第一个样本开始。我们抓住这个特征并提供给未经训练的模型,要求它预测一个合适的相邻词。
该模型执行这三个步骤并输出一个预测向量(为其词汇表中的每个单词分配一个概率)。由于模型未经训练,它的预测在这个阶段肯定是错误的。但没关系。我们知道它应该猜到什么词——我们当前用来训练模型的行中的标签/输出单元格:
模型有多远?我们减去两个向量得到一个误差向量:
这个误差向量现在可以用来更新模型,这样下次它就更有可能猜测thou它何时作为not输入。
训练的第一步到此结束。我们继续对数据集中的下一个样本执行相同的过程,然后是下一个,直到我们覆盖了数据集中的所有样本。一个epoch的训练到此结束。我们在多个 epoch 中重复此操作,然后我们将拥有经过训练的模型,我们可以从中提取嵌入矩阵并将其用于任何其他应用程序。
虽然这扩展了我们对过程的理解,但它仍然不是 word2vec 的实际训练方式。我们缺少几个关键想法。
负采样
回想一下这个神经语言模型如何计算其预测的三个步骤:
从计算的角度来看,第三步非常昂贵——尤其是知道我们将为数据集中的每个训练样本执行一次(很容易数千万次)。我们需要做一些事情来提高性能。
一种方法是将我们的目标分为两个步骤:
1.生成高质量的词嵌入(不用担心下一个词的预测)。2.使用这些高质量的嵌入来训练语言模型(进行下一个词预测)。我们将专注于本文中的步骤 1,因为我们专注于嵌入。要使用高性能模型生成高质量嵌入,我们可以将模型的任务从预测相邻词切换为:
并将其切换到一个接受输入和输出词的模型,并输出一个分数来指示他们是否是邻居(0 表示“不是邻居”,1 表示“邻居”)。
这个简单的开关将我们需要的模型从神经网络更改为逻辑回归模型——因此计算变得更加简单和快速。
此切换需要我们切换数据集的结构——标签现在是一个值为 0 或 1 的新列。它们将全部为 1,因为我们添加的所有单词都是邻居。
现在可以以惊人的速度进行计算——在几分钟内处理数百万个示例。但是我们需要堵上一个漏洞。如果我们所有的例子都是正面的(目标:1),我们将自己打开一个总是返回 1 的 smartass 模型的可能性——达到 100% 的准确率,但什么也没学到并生成垃圾嵌入。
为了解决这个问题,我们需要在我们的数据集中引入负样本——不是相邻单词的样本。我们的模型需要为这些样本返回 0。现在这是一个模型必须努力解决的挑战——但仍然以极快的速度。
但是我们填写什么作为输出词呢?我们从我们的词汇表中随机抽取单词
这个想法受到噪声对比估计[pdf] 的启发。我们将实际信号(相邻词的正例)与噪声(随机选择的非相邻词)进行对比。这导致计算和统计效率的巨大折衷。
带负采样的 Skipgram (SGNS)
我们现在已经涵盖了 word2vec 中的两个中心思想:作为一对,它们被称为带负采样的 skipgram。
五、Word2vec 训练过程
既然我们已经确立了 skipgram 和负采样这两个中心思想,我们就可以继续深入了解实际的 word2vec 训练过程。
在训练过程开始之前,我们对训练模型所针对的文本进行预处理。在此步骤中,我们确定词汇量的大小(我们称其vocab_size为 10,000)以及属于它的单词。
在训练阶段开始时,我们创建两个矩阵——一个Embedding矩阵和一个Context矩阵。这两个矩阵对我们词汇表中的每个单词都有一个嵌入(vocab_size它们的维度之一也是如此)。第二个维度是我们希望每个嵌入的长度(embedding_size– 300 是一个常见值,但我们在本文前面已经看过 50 的示例)。
在训练过程开始时,我们用随机值初始化这些矩阵。然后我们开始训练过程。在每个训练步骤中,我们采用一个正例及其相关的负例。让我们来看看我们的第一组:
现在我们有四个词:输入词not和输出/上下文词:(thou实际的邻居),aaron和taco(反例)。我们继续查找它们的嵌入——对于输入词,我们在矩阵中查找Embedding。对于上下文词,我们在Context矩阵中查找(即使两个矩阵都有我们词汇表中每个词的嵌入)。
然后,我们将输入嵌入与每个上下文嵌入进行点积。在每种情况下,这都会产生一个数字,该数字表示输入和上下文嵌入的相似性
现在我们需要一种方法将这些分数转化为看起来像概率的东西——我们需要它们都是正的并且值介于 0 和 1 之间。这对于sigmoid逻辑运算来说是一项艰巨的任务。
我们现在可以将 sigmoid 操作的输出视为这些示例的模型输出。您可以看到它taco的得分最高,并且aaron在 S 形操作之前和之后仍然得分最低。
既然未经训练的模型已经做出了预测,并且看起来好像我们有一个实际的目标标签可以进行比较,让我们计算模型预测中的误差有多大。为此,我们只需从目标标签中减去 sigmoid 分数。
“机器学习”的“学习”部分来了。我们现在可以使用这个错误分数来调整not、thou、aaron和的嵌入taco,以便下次我们进行此计算时,结果会更接近目标分数。
训练步骤到此结束。对于这一步中涉及的词,我们从中得到了稍微更好的嵌入(not、thou、aaron和taco)。我们现在继续下一步(下一个正样本及其相关的负样本)并再次执行相同的过程。
当我们多次循环遍历整个数据集时,嵌入将继续得到改进。然后我们可以停止训练过程,丢弃Context矩阵,并将Embeddings矩阵用作下一个任务的预训练嵌入。
窗口大小和负样本数
word2vec训练过程中的两个关键超参数是窗口大小和负样本数量。
不同的窗口大小可以更好地完成不同的任务。一种启发式方法是,较小的窗口大小 (2-15) 会导致嵌入,其中两个嵌入之间的高相似度分数表明单词是可以互换的(请注意,如果我们只看周围的单词,反义词通常是可以互换的——例如好和坏经常出现在类似的上下文中)。较大的窗口大小(15-50,甚至更多)会导致嵌入,其中相似性更能表明单词的相关性。在实践中,你经常需要提供注释指导嵌入过程,从而为您的任务带来有用的相似感。Gensim 默认窗口大小为 5(除了输入词本身之外,输入词之前的五个词和输入词之后的五个词)。
负样本的数量是训练过程的另一个因素。原始论文规定 5-20 是一个很好的负样本数。它还指出,当您拥有足够大的数据集时,2-5 似乎就足够了。Gensim 默认是 5 个负样本。
结论
您现在对词嵌入和 word2vec 算法有所了解。我也希望现在当你阅读一篇提到“带负采样的 skip gram”(SGNS) 的论文时(就像顶部的推荐系统论文一样),你对这些概念有更好的理解。
参考资料和进一步阅读
Distributed Representations of Words and Phrases and their Compositionality [pdf]
Efficient Estimation of Word Representations in Vector Space [pdf]
A Neural Probabilistic Language Model [pdf]
ONE MORE THING
咪豆AI圈(Meedo)针对当前人工智能领域行业入门成本较高、碎片化信息严重、资源链接不足等痛点问题,致力于打造人工智能领域的全资源、深内容、广链接三位一体的在线科研社区平台,提供AI导航网、AI版知乎,AI知识树和AI圈子等服务,欢迎AI未来儿一起来探索(www.meedo.top/)