AI 文摘

2图解Word2vec





作者: 人工智能大讲堂 来源: 人工智能大讲堂

词嵌入用数字向量表示一个词,与Tokenization中使用ID不同,词嵌入向量更具语义信息,本文将图解Word2vec:一种词嵌入方法。本系列还有图解Tokenization,Transformer,GPT2,Bert。

如果想了解Tokenization,请看<图解Tokenization>。

本文翻译自下面这篇博客。

https://jalammar.github.io/illustrated-word2vec/

我觉得嵌入概念是机器学习中最迷人的想法之一。如果你曾经使用过Siri、Google助手、Alexa、Google翻译,甚至是带有下一个单词预测功能的智能手机键盘,那么你很有可能从这个想法中受益,这个想法已经成为自然语言处理模型的核心。在过去的几十年中,嵌入在神经模型中的应用有了相当大的发展(最近的发展包括基于上下文化的词嵌入,引领了像BERT和GPT2这样的尖端模型)。

Word2vec是一种高效创建词嵌入的方法,自2013年以来一直沿用至今。但除了作为一种词嵌入方法外,它的一些概念已被证明在创建推荐引擎和在商业非语言任务中理解连续数据方面也非常有效。像Airbnb、阿里巴巴、Spotify和Anghami这样的公司都借助NLP的力量,为一种新型的推荐引擎赋能。

在这篇文章中,我们将介绍嵌入的概念以及使用word2vec生成嵌入的机制。但让我们先从一个例子开始,以了解如何使用向量来表示事物。你知道吗,一个由五个数字(一个向量)组成的列表可以代表关于你个性的许多信息?

个性嵌入:你是什么样的人?

在0到100的尺度上,你的内向/外向程度是多少(其中0表示最内向,100表示最外向)?你是否参加过像MBTI(迈尔斯-布里格斯类型指标)或者更好的五大人格特质测试这样的人格测试?如果你没有参加过,这些测试会向你提出一系列问题,然后根据多个维度对你进行评分,其中内向/外向就是其中之一。

假设我在内向/外向评分上得分为38/100。我可以绘制成下面这样的图:

让我们把范围收缩到-1到1:

了解一个人仅凭这一条信息,你觉得你对他们了解程度如何?并不多。人是复杂的。所以让我们再增加一个维度——性格测试中的另一项特质。

我们可以将两个维度表示为图形上的一个点,或者作为从原点到该点的向量。我们拥有很棒的工具来处理即将上场的向量们。

我隐藏了我们正在绘制的人格特征,这样你就不知道每个维度代表的是什么,但仍然可以从人格的向量表示中获得很多信息。

现在我们可以说这个向量部分代表了我的个性。这种表示的有用之处在于当你想要将另外两个人与我进行比较时。假设我被一辆公共汽车撞到,需要找一个与我个性相似的人来取代我。在下面的图中,哪个人更类似于我?

处理向量时,计算相似度得分的常用方法是余弦相似度:

1号替身在性格上与我更相似。指向相同方向的向量(长度也起作用)具有更高的余弦相似度。

再一次,两个维度无法捕捉到关于不同人之间的足够信息。几十年的心理学研究已经确定了五个主要特质(以及许多次要特质)。所以让我们在比较中使用所有五个维度:

使用五个维度的问题是我们失去了在二维空间中绘制整齐小箭头的能力。这在机器学习中是一个常见的挑战,我们经常需要在更高维空间中进行思考。好消息是,余弦相似度仍然适用。它适用于任意数量的维度:

余弦相似度适用于任意数量的维度。这些得分比上次的得分要更好,因为它们是根据被比较事物的更高维度算出的。

在本节的最后,我希望提出两个中心思想:

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 ]

大家可以使用下面的在线工具亲自操作一下。

WebVectors: 交互式词向量畅玩网站,NLP也可以在线玩

这是一个由50个数字组成的列表。通过查看这些值,我们无法得出太多信息。但让我们稍微可视化一下,以便将其与其他词向量进行比较。让我们将所有这些数字放在一行中:

让我们根据值的大小对单元格进行着色(如果接近2,则为红色;如果接近0,则为白色;如果接近-2,则为蓝色):

我们将忽略数字,只关注颜色来表示单元格的值。现在让我们将“King”与其他单词进行对比:

你可以看到,“Man”和“Woman”相比比它们任何一个与“King”更相似。这告诉你一些信息。这些向量表示捕捉到了这些单词的许多信息、含义和关联。

这是另一个示例列表(通过垂直扫描列,寻找颜色相似的列进行比较):

发现几个事情:

1.在所有这些不同的单词中,有一列是红色的。它们在这个维度上相似(而我们不知道每个维度代表的是什么)。
2.你可以看到,“woman”和“girl”在许多地方相似。同样,“man”和“boy”也是如此。
3.“boy”和“girl”也有一些地方它们彼此相似,但与“woman”或“man”不同。这些可能代表了一个模糊的“youth”概念吗?可能是的。
4.除了最后一个单词外,所有单词都代表人。我添加了一个物体(“water”)来显示类别之间的差异。例如,你可以看到蓝色的列一直延伸到最下方,然后在“water”的嵌入之前停止。
5.有明显的地方,“king”和“queen”彼此相似,与其他所有单词都不同。这些可能代表了一个模糊的“royalty”概念吗?

类比

展现嵌入奇妙属性的著名例子是类比。我们可以添加、减去词嵌入并得到有趣的结果。一个著名例子是公式:“king”-“man”+“woman”:

在python中使用Gensim库,我们可以添加和减去词向量,它会找到与结果向量最相似的单词。该图像显示了最相似的单词列表,每个单词都具有余弦相似性。

我们可以像之前一样可视化这个类比:

由“king-man + woman”生成的向量并不完全等同于“queen”,但“queen”是我们在此集合中包含的400,000个字嵌入中最接近它的单词。

现在我们已经看过训练好的词嵌入,接下来让我们更多地了解训练过程。但在我们开始使用word2vec之前,我们需要看一下词嵌入的父概念:语言模型。

语言模型

如果要举自然语言处理最典型的例子,那应该就是智能手机输入法中的下一单词预测功能。这是个被数十亿人每天使用上百次的功能。

下一单词预测是一个可以通过语言模型实现的任务。语言模型会通过单词列表(比如说两个词)去尝试预测可能紧随其后的单词。

在上面这个手机截屏中,我们可以认为该模型接收到两个绿色单词(thou shalt)并推荐了一组单词(“not” 就是其中最有可能被选用的一个):

我们可以把这个模型想象为这个黑盒:

但事实上,该模型不会只输出一个单词。实际上,它对所有它知道的单词(模型的词库,可能有几千到几百万个单词)的按可能性打分,输入法程序会选出其中分数最高的推荐给用户。

自然语言模型的输出就是模型所知单词的概率评分,我们通常把概率按百分比表示,但是实际上,40%这样的分数在输出向量组是表示为0.4

自然语言模型(请参考Bengio 2003)在完成训练后,会按如下中所示分三步完成预测:

第一步与我们最相关,因为我们讨论的就是Embedding。模型在经过训练之后会生成一个映射单词表所有单词的矩阵。在进行预测的时候,我们的算法就是在这个映射矩阵中查询输入的单词,然后计算出预测值:

现在让我们将重点放到模型训练上,来学习一下如何构建这个映射矩阵。

语言模型训练

语言模型相比大多数其他机器学习模型具有巨大优势。这个优势在于我们能够使用大量的运行文本对它们进行训练。想象一下我们拥有的所有书籍、文章、维基百科内容和其他形式的文本数据。与需要手工制作特征和特殊收集数据的许多其他机器学习模型相比,这是一个很大的对比。

我们通过找常出现在每个单词附近的词,就能获得它们的映射关系。机制如下:

1.先是获取大量文本数据(例如所有维基百科内容)

  1. 然后我们建立一个可以沿文本滑动的窗(例如一个窗里包含三个单词)

  2. 利用这样的滑动窗就能为训练模型生成大量样本数据。

当这个滑动窗口在文本上滑动时,我们(虚拟地)生成一个用于训练模型的数据集。为了详细了解这是如何完成的,让我们看看滑动窗口如何处理这个短语:

在一开始的时候,窗口锁定在句子的前三个单词上:

我们把前两个单词单做特征,第三个单词单做标签:

这时我们就生产了数据集中的第一个样本,它会被用在我们后续的语言模型训练中。

接着,我们将窗口滑动到下一个位置并生产第二个样本:

这时第二个样本也生成了。

不用多久,我们就能得到一个较大的数据集,从数据集中我们能看到在不同的单词组后面会出现的单词:

实际上,在滑动窗口时通常会同时进行模型训练。但从逻辑上讲,将“数据集生成”阶段与训练阶段分开更清晰。除了基于神经网络的语言建模方法外,一种称为N-gram的技术通常用于训练语言模型(参见《Speech and Language Processing》第3章)。为了了解从N-gram到神经模型的转变如何反映在现实世界的产品中,这是来自我最喜欢的Android键盘Swiftkey的一篇2015年的博文,介绍了他们的神经语言模型并将其与之前的N-gram模型进行了比较。我喜欢这个例子,因为它展示了如何用营销用语描述嵌入向量的算法特性。

顾及两头

根据前面的信息进行填空:

这里给你的上下文是空白词前的五个单词(以及之前提到的“bus”)。我相信大多数人会猜测空白处应填入“bus”这个词。但如果我再给你一个信息——空白词后面的一个单词,会改变你的答案吗?

这完全改变了应该填入空白处的内容。现在,最有可能填入空白处的是单词"red"。我们从中可以得出结论,特定单词前后的单词都携带了信息价值。事实证明,同时考虑左侧和右侧(我们猜测的单词的左侧和右侧的单词)会导致更好的词嵌入。让我们看看如何调整我们训练模型的方式来解决这个问题。

Skipgram模型

与其只查看目标单词之前的两个单词,我们还可以查看它之后的两个单词。

如果这么做,我们实际上构建并训练的模型就如下所示:

这被称为连续词袋(Continuous Bag of Words)结构,并在word2vec的一篇论文中进行了描述。另一种架构也表现出了很好的结果,但它的做法有些不同。

这种架构不是基于上下文(前后的单词)来猜测单词,而是尝试使用当前单词来猜测邻近的单词。我们可以将其滑动窗口看作是针对训练文本的如下形式:

绿框中的词语是输入词,粉框则是可能的输出结果

这里粉框颜色深度呈现不同,是因为滑动窗给训练集产生了4个独立的样本:

这种方式称为Skipgram架构。我们可以像下图这样将展示滑动窗的内容。

这样就为数据集提供了4个样本:

然后我们移动滑动窗到下一个位置:

这样我们又产生了接下来4个样本:

在移动几组位置之后,我们就能得到一批样本:

重新审视训练过程

现在我们有了从现有运行文本中提取的skipgram训练数据集,让我们简要了解如何使用它来训练一个基本的神经语言模型,该模型可以预测邻近的单词。

我们从数据集中获取第一个样本。我们提取特征并将其输入未经训练的模型,要求它预测一个合适的邻近单词。

模型执行这三个步骤,并输出一个预测向量(对其词汇表中的每个单词分配了一个概率)。由于模型未经训练,此时的预测肯定是错误的。但没关系,我们知道它应该猜测哪个单词——即我们当前用于训练模型的行中的标签/输出单元:

目标单词概率为1,其他所有单词概率为0,这样数值组成的向量就是“目标向量”。

模型的预测与实际标签之间有多大的差距?我们将两个向量相减,得到一个误差向量:

现在可以使用这个误差向量来更新模型,这样下一次,当模型以"not"作为输入时,它更有可能猜测"thou"。

这就完成了训练的第一步。我们继续对数据集中的下一个样本进行相同的过程,然后是下一个样本,直到覆盖了数据集中的所有样本。这就完成了一个训练周期。我们可以再次进行多个周期的训练,然后我们就会得到训练好的模型,并且可以从中提取嵌入矩阵,用于任何其他应用。

虽然这扩展了我们对过程的理解,但这仍然不是word2vec的实际训练方式。我们还缺少一些关键的想法。

负例采样

回想一下这个神经语言模型计算预测值的三个步骤:

第三步从计算角度来看非常耗费资源,特别是考虑到我们需要为数据集中的每个训练样本执行一次(可能达到数千万次)。我们需要采取一些措施来提高性能。

一种方法是将目标分为两个步骤:

1.生成高质量的词嵌入(不用担心下一个单词的预测)。
2.使用这些高质量的嵌入来训练语言模型(进行下一个单词的预测)。
在本文中,我们将重点放在第一步上,因为我们关注的是嵌入。为了使用高性能模型生成高质量的嵌入,我们可以将模型的任务从预测邻近单词切换为:

然后将其切换为一个模型,该模型接受输入和输出单词,并输出一个分数,指示它们是否是邻居(0表示"不是邻居",1表示"是邻居")。

这个简单的切换将我们所需的模型从神经网络转变为逻辑回归模型,因此计算起来变得更简单、更快速。

这个切换需要我们改变数据集的结构——标签现在是一个新的列,取值为0或1。由于我们添加的所有单词都是邻居,它们都将是1。

现在可以以惊人的速度进行计算,几分钟内处理数百万个示例。但是,我们还需要解决一个漏洞。如果我们的所有示例都是正例(目标为1),那么我们就有可能面对一个总是返回1的聪明模型——实现100%的准确率,但并未学到任何东西并生成了垃圾嵌入向量。

为了解决这个问题,我们需要向数据集中引入负例样本——即不是邻居的单词样本。我们的模型需要对这些样本返回0。现在,这是一个挑战,模型必须努力解决这个问题,但仍然可以以极快的速度进行计算。

对于我们数据集中的每个样本,我们添加了负面示例。它们具有相同的输入字词,标签为0。

但是我们作为输出词填写什么呢?我们从词汇表中随机抽取单词

这个想法的灵感来自噪声对比估计。我们将实际信号(相邻单词的正例)与噪声(随机选择的不是邻居的单词)进行对比。这导致了计算和统计效率的巨大折衷。

噪声对比估计

http://proceedings.mlr.press/v9/gutmann10a/gutmann10a.pdf

基于负例采样的Skipgram(SGNS)

我们现在已经介绍了word2vec中的两个(一对)核心思想:负例采样,以及skipgram。

Word2vec训练流程

既然我们已经了解了skipgram和负采样这两个核心思想,我们可以进一步深入了解实际的word2vec训练过程。

在训练过程开始之前,我们会对要训练模型的文本进行预处理。在这一步中,我们确定了词汇表的大小(我们称之为vocab_size,可以将其视为,比如,10,000),以及属于该词汇表的单词。

在训练阶段开始时,我们创建两个矩阵——嵌入矩阵(Embedding matrix)和上下文矩阵(Context matrix)。这两个矩阵中都有词汇表中每个单词的嵌入(因此vocab_size是它们的一个维度)。第二个维度是我们希望每个嵌入的长度是多长(embedding_size——300是一个常见的值,但我们在本文前面的示例中看到了长度为50的嵌入)。

在训练过程开始时,我们使用随机值对这些矩阵进行初始化。然后我们开始训练过程。在每个训练步骤中,我们选择一个正例及其相关的负例。让我们来看看我们的第一组示例:

现在我们有四个单词:输入单词是"not",输出/上下文单词有"thou"(实际邻居),“aaron"和"taco”(负例)。我们继续查找它们的嵌入向量——对于输入单词,我们在嵌入矩阵中查找;对于上下文单词,我们在上下文矩阵中查找(尽管这两个矩阵都为词汇表中的每个单词提供了嵌入向量)。

然后,我们将输入嵌入向量与每个上下文嵌入向量进行点积运算。在每种情况下,这将得到一个数字,该数字表示输入嵌入向量与上下文嵌入向量的相似度。

现在我们需要一种将这些分数转换为类似概率的方法——我们需要将它们都转换为正数,并且取值范围在0到1之间。这正是sigmoid函数(逻辑函数)非常适合的任务。

现在,我们可以将sigmoid操作的输出视为这些示例的模型输出。可以看到,在sigmoid操作之前和之后,taco的分数最高,而aaron的分数仍然最低。

既然未经训练的模型已经进行了预测,并且我们有一个实际的目标标签可供对比,让我们计算模型预测中的误差有多大。为了做到这一点,我们只需将sigmoid分数与目标标签相减即可。

error = target - sigmoid_scores

这就是“machine learning”中的“learning习”部分。我们现在可以使用这个误差分数来调整"not"、“thou”、“aaron"和"taco"的嵌入向量,以便下次进行计算时,结果更接近目标分数。

这就完成了训练步骤。我们在这一步中获得了单词(“not”、“thou”、“aaron"和"taco”)稍微改进的嵌入向量。现在我们继续进行下一步(下一个正例及其相关的负例),并再次进行相同的过程。

在我们对整个数据集进行多次循环时,嵌入向量会持续改进。然后,我们可以停止训练过程,丢弃上下文矩阵,并将嵌入矩阵作为我们下一个任务的预训练嵌入向量使用。

窗口大小和负样本数量

在word2vec训练过程中,有两个关键的超参数,分别是窗口大小(window size)和负采样数量(number of negative samples)。

不同的任务应该有不同的窗口尺寸。一个经验法则是较小的窗口大小(2-15)会得到这样的嵌入:其中两个嵌入之间的高相似度分数表示这些单词可以互换(请注意,如果只看它们周围的单词,反义词通常是可以互换的,例如“good”和“bad”经常出现在类似的上下文中)。较大的窗口大小(15-50,甚至更大)会得到这样的嵌入:其中相似度更多地表示单词之间的相关性。在实践中,您通常需要提供指导嵌入过程的注释,以便为您的任务提供有用的相似度感知。Gensim默认的窗口大小是5(在输入词的前后各五个词,加上输入词本身)。

负样本的数量是训练训练过程的另一个因素。原始论文认为5-20个负样本是比较理想的数量。它还指出,当你拥有足够大的数据集时,2-5个似乎就已经足够了。Gensim默认为5个负样本。

更多AI工具,参考Github-AiBard123国内AiBard123

可关注我们的公众号:每天AI新工具