图解Word2vec - 知乎 (
原文:The Illustrated Word2vec – Jay Alammar – Visualizing machine learning one concept at a time.)
BERT_ 英文 The Illustrated BERT, ELMo, and co. (How NLP Cracked Transfer Learning) – Jay Alammar – Visualizing machine learning one concept at a time.
深入浅出Word2Vec原理解析 - 知乎
word2vec模型深度解析 - 知乎
Word2Vec的训练过程详解 - 知乎
Word2Vec-——gensim实战教程 - 知乎
下图中,两个人中哪一个更像我?
处理向量时,计算相似度得分的常用方法是余弦相似度:
任意维度:
余弦相似度适用于任意数量的维度。这些得分比上次的得分要更好。
两个中心思想:
1.我们可以将人和事物表示为代数向量(这对机器来说很棒!)。
2.我们可以很容易地计算出相似的向量之间的相互关系。
先看单词“king”的词嵌入:
[ 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则为蓝色):
有几个要点需要指出:
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个字嵌入中最接近它的单词。
自然语言模型的输出就是模型所知单词的概率评分,我们通常把概率按百分比表示,但是实际上,40%这样的分数在输出向量组是表示为0.4
自然语言模型(请参考Bengio 2003)在完成训练后,会按如下中所示法人三步完成预测:
通过找常出现在每个单词附近的词,就能获得它们的映射关系。机制如下:
1.先是获取大量文本数据(例如所有维基百科内容)
2. 然后我们建立一个可以沿文本滑动的窗(例如一个窗里包含三个单词)
3. 利用这样的滑动窗就能为训练模型生成大量样本数据。
当这个窗口沿着文本滑动时,我们就能(真实地)生成一套用于模型训练的数据集。
我们不仅要考虑目标单词的前两个单词,还要考虑其后两个单词。
如果这么做,我们实际上构建并训练的模型就如下所示:
上述的这种架构被称为连续词袋(CBOW)
另一种架构,它不根据前后文(前后单词)来猜测目标单词,而是推测当前单词可能的前后单词。我们设想一下滑动窗在训练数据时如下图所示:
绿框中的词语是输入词,粉框则是可能的输出结果
这里粉框颜色深度呈现不同,是因为滑动窗给训练集产生了4个独立的样本:
这种方式称为Skipgram架构。我们可以像下图这样将展示滑动窗的内容。
这样就为数据集提供了4个样本:
然后我们移动滑动窗到下一个位置, 这样我们又产生了接下来4个样本:
在移动几组位置之后,我们就能得到一批样本:
目的:如何使用它来训练一个能预测相邻词汇的自然语言模型。
已有前面获得的Skipgram模型的训练数据集,即:
从数据集中的第一个样本开始。我们将特征输入到未经训练的模型,让它预测一个可能的相邻单词。
该模型会执行三个步骤并输入预测向量(对应于单词表中每个单词的概率)。因为模型未经训练,该阶段的预测肯定是错误的。但是没关系,我们已经知道应该是哪个单词——这个词就是我训练集数据中的输出标签:
目标单词概率为1,其他所有单词概率为0,这样数值组成的向量就是“目标向量”。
模型的偏差有多少?将两个向量相减,就能得到偏差向量:
现在这一误差向量可以被用于更新模型了,所以在下一轮预测中,如果用not作为输入,我们更有可能得到thou作为输出了。
改变一下预测相邻单词这一任务:
将其切换到一个提取输入与输出单词的模型,并输出一个表明它们是否是邻居的分数(0表示“不是邻居”,1表示“邻居”)。
这个简单的变换将我们需要的模型从神经网络改为逻辑回归模型——因此它变得更简单,计算速度更快。
这要求我们切换数据集的结构——标签值现在是一个值为0或1的新列。它们将全部为1,因为我们添加的所有单词都是邻居。
现在的计算速度可谓是神速啦——在几分钟内就能处理数百万个例子。但是我们还需要解决一个漏洞。如果所有的例子都是邻居(目标:1),我们这个”天才模型“可能会被训练得永远返回1——准确性是百分百了,但它什么东西都学不到,只会产生垃圾嵌入结果。
为了解决这个问题,我们需要在数据集中引入负样本 - 不是邻居的单词样本。我们的模型需要为这些样本返回0。模型必须努力解决这个挑战——而且依然必须保持高速。
对于我们数据集中的每个样本,我们添加了负面示例。它们具有相同的输入字词,标签为0。
但是我们作为输出词填写什么呢?我们从词汇表中随机抽取单词
这个想法的灵感来自噪声对比估计。我们将实际信号(相邻单词的正例)与噪声(随机选择的不是邻居的单词)进行对比。这导致了计算和统计效率的巨大折衷。
我们现在已经介绍了word2vec中的两个(一对)核心思想:负例采样,以及skipgram。
(1)预先处理训练模型的文本,确定词典的大小(我们称之为vocab_size,比如说10,000)以及哪些词被它包含在内。
(2)在训练阶段的开始,我们创建两个矩阵——Embedding矩阵和Context矩阵。两个矩阵由单词表中每个单词的嵌入(Embedding)组成,所以这两个二维矩阵的一个维度是vocab_size,另一个维度嵌入Embedding的长度(embedding_size——300是一个常见值,但我们在前文也看过50的例子)。
在训练过程开始时,我们用随机值初始化这些矩阵。然后我们开始训练过程。在每个训练步骤中,我们采取一个相邻的例子及其相关的非相邻例子。我们来看看我们的第一
组:
现在我们有四个单词:输入单词not和输出/上下文(context)单词: thou(实际邻居词),aaron和taco(负样本)。对于输入词,我们检索Embedding矩阵中对应的Embedding。对于上下文单词,我们查看Context矩阵矩阵中对应的Embedding(两个矩阵都在我们的词汇表中都有每个单词的嵌入)。
然后,我们计算输入嵌入Embedding与每个Context上下文嵌入的点积。在每种情况下,点乘结果表示输入和上下文嵌入的相似性度。
现在我们需要一种方法将这些分数转化为看起来像概率的东西——我们需要它们都是正值,并且 处于0到1之间。sigmoid这一逻辑函数转换正适合用来做这样的事情啦。
现在我们可以将sigmoid的输出视为模型输出。可以看到taco得分最高,aaron最低。
未经训练的模型已做出预测,我们目标真实标签作对比,计算模型预测中的误差。只需从目标标签中减去sigmoid分数。
error = target - sigmoid_scores
这就是“机器学习”的“学习”部分。现在,我们可以利用这个误差数来调整not
, thou
, aaron
, and taco的Embedding,这样
我们下一次做出预测计算时,结果会更接近实际目标值。
训练步骤到此结束。我们从中得到了这一步所使用词语更优一些的嵌入(not
, thou
, aaron
, and taco
)。我们现在进行下一步(下一个正样本及其相关的负样本),并再次执行相同的过程。当我们循环遍历整个数据集多次时,嵌入会继续得到改进。然后我们就可以停止训练过程,丢弃Context矩阵,并使用Embeddings矩阵作为下一项任务的已被训练好的嵌入。
Word2Vec是语言模型中的一种,它是从大量文本预料中以无监督方式学习语义知识的模型,被广泛地应用于自然语言处理中。
1.1 统计语言模型
统计语言模型是用来计算一个句子的概率的概率模型,它通常基于一个语料库来构建。那什么叫做一个句子的概率呢?假设 表示由T个词
按顺序构成的一个句子,则
的联合概率为:
p(W)被称为语言模型,即用来计算这个句子概率的模型。利用Bayes公式,上式可以被链式地分解为:
其中的条件概率 就是语言模型的参数,若这些参数已经全部算得,那么给定一个句子W,就可以很快地计算出相应地概率p(W)了。
不防假设语料库对应词典D的大小(即词汇量)为N,那么,如果考虑长度为T的任意句子,理论上就有N^{T}种可能,而每种可能都要计算T个参数,总共就需要计算 个参数。
这些参数如何计算呢?常见的方法有n-gram模型、决策树、最大熵模型、最大熵马尔可夫模型、条件随机场、神经网络等方法.
1.2 N-gram模型
从公式(1)可以看出:一个词出现的概率与它前面的所有词都相关。如果假定一个词出现的概率只与它前面固定数目的词相关呢?这就是n-gram模型的基本思想,它做了一个n-1阶的Markov假设,认为一个词出现的概率就只与它前面的n-1个词相关,即,
那么,n-gram中的参数n取多大比较合适呢?一般来说,n的选取需要同时考虑计算复杂度和模型效果两个因素。
在机器学习领域有一种通用的解决问题的方法:对所考虑的问题建模后先为其构造一个目标函数,然后对这个目标函数进行优化,从而求得一组最优的参数,最后利用这组最优参数对应的模型来进行预测。
对于统计语言模型而言,利用最大似然,可把目标函数设为:
其中,C表示语料(Corpus),Context(w)表示词w的上下文,即w周边的词的集合。当Context(w)为空时,就取 。特别地,对于前面介绍的n-gram模型,就有
。
1.3 神经概率语言模型
什么是词向量呢?简单来说就是,对词典D中的任意词w,指定一个固定长度的实值向量 , v(w) 就称为 w 的词向量,m为词向量的长度。
模型一共三层,第一层是映射层,将n个单词映射为对应word embeddings的拼接,其实这一层就是MLP的输入层;第二层是隐藏层,激活函数用tanh;第三层是输出层,因为是语言模型,需要根据前n个单词预测下一个单词,所以是一个多分类器,用Softmax。整个模型最大的计算量集中在最后一层上,因为一般来说词汇表都很大,需要计算每个单词的条件概率,是整个模型的计算瓶颈。
经过上面步骤的计算得到的 只是一个长度为N的向量,其分量不能表示概率。如果想要
的分量
表示当上下文为
时下一个词恰为词典D中第i个词的概率,则还需要做一个Softmax归一化,归一化后,
就可以表示为:
这里,需要注意的是需要提前初始化一个word embedding矩阵,每一行表示一个单词的向量。词向量也是训练参数,在每次训练中进行更新。
Softmax是一个非常低效的处理方式,需要先计算每个单词的概率,并且还要计算指数,指数在计算机中都是用级数来近似的,计算复杂度很高,最后再做归一化处理。此后很多研究都针对这个问题进行了优化,比如层级softmax、softmax tree
与n-gram模型相比,神经概率语言模型有什么优势呢?主要有以下两点:
举例来说,如果某个(英语)语料中
= "A dog is running in the room"出现了10000次,而 ="A cat is running in the room"只出现了1次。按照n-gram模型的做法,
肯定会远大于
。注意, S1和S2 的唯一区别在与dog和cat,而这两个词无论是句法还是语义上都扮演了相同的角色,因此
, 和
应该很接近才对。
事实上,由神经概率语言模型算得的 和
是大致相等的。原因在于:(1)在神经概率语言模型中假定了“相似的”的词对应的词向量也是相似的;(2)概率函数关于词向量是光滑的,即词向量中的一个小变化对概率的影响也只是一个小变化。这样一来,对于下面这些句子, 只要在语料库中出现一个,其他句子的概率也会相应的增大。
A dog is running in the room
A cat is running in the room
The cat is running in a room
A dog is walking in a bedroom
The dog was walking in the room
最后,我们回过头来想想,词向量在整个神经概率语言模型中扮演了什么角色呢?训练时,它是用来帮助构造目标函数的辅助参数,训练完成后,它也好像只是语言模型的一个副产品。但这个副产品可不能小觑,下一节将对其作进一步阐述。
自然语言处理相关任务中要将自然语言交给机器学习中的算法来处理,通常需要将语言数学化,因为机器不是人,机器只认数学符号。向量是人把自然界的东西抽象出来交给机器处理的东西,基本上可以说向量是人对机器输入的主要方式了。
词向量就是用来将语言中的词进行数学化的一种方式,顾名思义,词向量就是把一个词表示成一个向量。 我们都知道词在送到神经网络训练之前需要将其编码成数值变量,常见的编码方式有两种:One-Hot Representation 和 Distributed Representation。
一种最简单的词向量方式是One-Hot编码 ,就是用一个很长的向量来表示一个词,向量的长度为词典的大小,向量中只有一个 1 , 其他全为 0 ,1 的位置对应该词在词典中的位置。
举个例子:I like writing code,那么转换成独热编码就是:
词One-Hot 编码
I like writing code
1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1
这种One Hot编码如果采用稀疏方式存储,会是非常的简洁:也就是给每个 词分配一个数字 ID 。比如上面的例子中,code记为 1 ,like记为 4 。 如果要编程实现的话,用 Hash 表给每个词分配一个编号就可以了。这么简洁的表示方法配 合上最大熵、 SVM 、 CRF 等等算法已经能很好地完成 NLP 领域的各种主流任务。
但这种词表示有两个缺点:
(1)容易受维数灾难的困扰,尤其是将其用于 Deep Learning的一些算法时;
( 2 )词汇鸿沟,不能很好地刻画词与词之间的相似性;
(3)强稀疏性;
由于One-hot编码存在以上种种问题,所以研究者就会寻求发展,用另外的方式表示,就是Distributed Representation
Distributed Representation基本想法是:通过训练将某种语言中的每一个词 映射成一个固定长度的短向量(当然这里的“短”是相对于One-Hot Representation的“长”而言的),所有这些向量构成一个词向量空间,而每一个向量则可视为 该空间中的一个点,在这个空间上引入“距离”,就可以根据词之间的距离来判断它们之间的语法、语义上的相似性了。Word2Vec中采用的就是这种Distributed Representation 的词向量。
如何获取词向量呢?有很多不同模型可以用来估计词向量,包括有名的LSA(Latent Semantic Analysis)和LDA(Latent Dirichlet Allocation)。此外,利用神经 网络算法也是一种常用的方法,上一节介绍的神经概率语言模型就是一个很好的实例。当然,在那个模型中,目标是生成语言模型,词向量只是一个副产品。事实上, 大部分情况下,词向量和语言模型都是捆绑在一起的,训练完成后两者同时得到。在用神经网络训练语言模型方面,最经典的论文就是Bengio于2003年发表的《A Neural Probabilistic Language Model》 ,其后有一系列相关的研究工作,其中也包括谷歌Tomas Mikolov团队的Word2Vec。
Word2Vec是轻量级的神经网络,其模型仅仅包括输入层、隐藏层和输出层,模型框架根据输入输出的不同,主要包括CBOW和Skip-gram模型。 CBOW的方式是在知道词的上下文
的情况下预测当前词.而Skip-gram是在知道了词
的情况下,对词
的上下 文
进行预测,如下图所示:
为了更好的了解模型深处的原理,我们先从Simple CBOW model(仅输入一个词,输出一个词)框架说起。
如上图所示:
了解了Simple CBOW model的模型框架之后,我们来学习一下其目标函数。
输出层通过softmax归一化,u代表的是输出层的原始结果。通过下面公式,我们的目标函数可以转化为现在这个形式
了解了Simple CBOW model之后,扩展到CBOW就很容易了,只是把单个输入换成多个输入罢了(划红线部分)。
对比可以发现,和simple CBOW不同之处在于,输入由1个词变成了C个词,每个输入词 到达隐藏层都会经过相同的权重矩阵W,隐藏层h的值变成了多个词乘上权重矩阵之后加和求平均值。
有了CBOW的介绍,对于Skip-gram model 的理解应该会更快一些。
如上图所示,Skip-gram model是通过输入一个词去预测多个词的概率。输入层到隐藏层的原理和simple CBOW一样,不同的是隐藏层到输出层,损失函数变成了C个词损失函数的总和,权重矩阵W'还是共享的。
一般神经网络语言模型在预测的时候,输出的是预测目标词的概率,也就是说我每一次预测都要基于全部的数据集进行计算,这无疑会带来很大的时间开销。不同于其他神经网络,Word2Vec提出两种加快训练速度的方式,一种是Hierarchical softmax,另一种是Negative Sampling。
基于层次Softmax的模型主要包括输入层、投影层(隐藏层)和输出层,非常的类似神经网络结构。对于Word2Vec中基于层次Softmax的CBOW模型,我们需要最终优化的目标函数是 :
其中 表示的是单词w 的的上下文单词。而基于层次Softmax的Skip-gram模型的最终需要优化的目标函数是:
4.1.1 CBOW模型网络结构
下图给出了基于层次Softmax的CBOW的整体结构,首先它包括输入层、投影层和输出层:
预备知识:哈夫曼编码,如图所示:
哈夫曼树
哈夫曼编码
word2vec hierarchical softmax结构
和传统的神经网络输出不同的是,word2vec的hierarchical softmax结构是把输出层改成了一颗哈夫曼树,其中图中白色的叶子节点表示词汇表中所有的|V|个词,黑色节点表示非叶子节点,每一个叶子节点也就是每一个单词,都对应唯一的一条从root节点出发的路径。我们的目的是使的w=wO这条路径的概率最大,即: P(w=wO|wI)最大,假设最后输出的条件概率是W2最大,那么我只需要去更新从根结点到w2这一个叶子结点的路径上面节点的向量即可,而不需要更新所有的词的出现概率,这样大大的缩小了模型训练更新的时间。
我们应该如何得到某个叶子结点的概率呢?
假设我们要计算W2叶子节点的概率,我们需要从根节点到叶子结点计算概率的乘积。我们知道,本模型替代的只是原始模型的softmax层,因此,某个非叶子节点的值即隐藏层到输出层的结果仍然是uj,我们对这个结果进行sigmoid之后,得到节点往左子树走的概率p,1-p则为往右子树走的概率。关于这棵树的训练方式比较复杂,但也是通过梯度下降等方法,这里不详述,感兴趣的可以阅读论文word2vec Parameter Learning Explained。
详细理论公式推导参见:深入浅出Word2Vec原理解析 - 知乎
本小节介绍Word2Vec中的另一个模型-Skip-gram模型,由于推导过程与CBOW大同小异,因此会沿用上小节引入的记号。
4.2.1 Skip-gram模型网络结构
下图给出了Skip-gram模型的网络结构,同CBOW模型的网络结构一样,它也包括三层:输入层、投影层和输出层。下面以样本 为例,对这三层做简要说明。
5. 基于Negative Sampling的模型
详细理论公式推导参见:深入浅出Word2Vec原理解析 - 知乎
传统神经网络在训练过程中的每一步,都需要计算词库中其他词在当前的上下文环境下出现的概率值,这导致计算量十分巨大。
然而,对于word2vec中的特征学习,可以不需要一个完整的概率模型。CBOW和Skip-Gram模型在输出端使用的是一个二分类器(即Logistic Regression),来区分目标词和词库中其他的 k个词(也就是把目标词作为一类,其他词作为另一类)。下面是一个CBOW模型的图示,对于Skip-Gram模型输入输出是倒置的。
此时,最大化的目标函数如下:
其中,Qθ(D=1|w,h)为二元逻辑回归的概率,具体为在数据集 D中、输入的embedding vector θ、上下文为 h的情况下词语 w 出现的概率;公式后半部分为 k 个从 [噪声数据集] 中随机选择 kk个对立的词语出现概率(log形式)的期望值。可以看出,目标函数的意义是显然的,即尽可能的 [分配(assign)] 高概率给真实的目标词,而低概率给其他 k 个 [噪声词],这种技术称为负采样(Negative Sampling)。
这种想法来源于噪声对比评估方法(NEC),大致思想是:假设X=(x1,x2,⋯,xTd)是从真实的数据(或语料库)中抽取样本,但是样本服从什么样的分布我们不知道,那么先假设其中的每个xi服从一个未知的概率密度函数pd。这样我们需要一个相对可参考的分布反过来去估计概率密度函数pd,这个可参考的分布或称之为噪音分布应该是我们知道的,比如高斯分布,均匀分布等。假设这个噪音分布的概率密度函数pn,从中抽取样本数据为Y=(y1,y2,⋯,yTn)Y=(y1,y2,⋯,yTn),而这个数据称之为噪声样本,我们的目的就是通过学习一个分类器把这两类样本区别开来,并能从模型中学到数据的属性,噪音对比估计的思想就是“通过比较而学习”。
具体来说,word2vec里面的负采样:将输出层的V个样本分为正例(Positive Sample)也就是目标词对应的项,以及剩余V−1个负例(Negative Samples)。举个例子有个样本phone
number,这样wI=phone,wO=number, 正例就是number这个词,负例就是不太可能与phone共同出现的词。负采样的思想是每次训练只随机取一小部分的负例使他们的概率最小,以及对应的正例概率最大。随机采样需要假定一个概率分布,word2vec中直接使用词频作为词的分布,不同的是频数上乘上0.75,相比于直接使用频次作为权重,取0.75幂的好处可以减弱不同频次差异过大带来的影响,使得小频次的单词被采样的概率变大。
采样权重
负采样定义的损失函数如下:
损失函数,一部分是正样本(期望输出的词),另一部分是负采样随机抽取出来的负样本集合,V'wo是输出向量
如果大家理解的还不是很深的话,接下来将通过谷歌发布的tensorflow官方word2vec代码解析加深理解。代码链接:https://github.com/tensorflow/tensorflow/blob/master/tensorflow/examples/tutorials/word2vec/word2vec_basic.py