word2Vec之Pytorch实现_理论部分

目录

目录

1.n-gram

2.神经网络语言模型

3.word2Vec

4.训练技巧

4.1重采样

4.2负采样

4.3层序softmax


1.n-gram

一句话由许多词构成,例如:“I love NanJing University”。在语言模型里面,我们常常将某句话生成的看做是一个概率事件。我们脑海里常常构思的语言句子,那么其出现的频数也比较多,发生概率就大,胡言乱语的语句频数少,发生的概率也较小。但是从句子粒度上考虑是欠妥的,因为句子的表达式丰富多彩的:例如:“I like NanJing University”。不过可以发现,句子表达可能是无穷无尽的,但是表示句子的词语(单词)是有限的。这里我们暂且将词语认为就是构句子的原子(即最小粒度),若有所有词语的概率,然后用词语的联合分布去表示一句话,那么所有的句子就都可以为被我们表示。

因此,基于这个想法,我们可以将上面的那个例子:

 p("I love NanJing University")=p("l")p("love"|"I")p("NanJing"|"love","I")p("University"|"I","love","NanJing" ) 

这样,任何一段话的我们都可以用词的联合分布来表示。但是这样也存在一个问题,就是上式中的链式法则越往后越稀疏,如“I love NanJing”这样的句子在文章中几乎就可以理解为一句话了,这段话在文中中出现的概率几乎是0了,更不用说在其发生下的情况下再接着跟着一个“University”。这就不得不用到我们的马尔科夫假设了:

即当前事件的发生仅仅与前一时刻发生的事情有关,即

p("University"|"I","love","NanJing" )=p("University"|"NanJing")

这样,“NanJing University”同时出现的概率要比"I love NanJing University"大的多,这很大程度上缓解了数据稀疏性带来的影响,当然也有一些平滑的手段来缓解数据稀疏的问题,这里不去过多讨论。那么,刚才的例子,我们就可以表示为: 

 p("I love NanJing University")=p("l")p("love"|"I")p("NanJing"|"love")p("University"|"NanJing" )

这样与前面1个词有关的我们称为一阶马尔科夫假设(2-gram),若与前面2个词有关则为二阶马尔科夫假设(3-gram)。而具体的概率计算则为: 

 p("I")=\frac{count("I")}{\sum_{word \in V }count(word)}

其中count函数表示语料库中单词出现的次数,V表示为预料中所有的单词。而条件概率函数函数的计算如下: 

p("love"|"I")=\frac{count("I love")}{count("I")}


2.神经网络语言模型

不像图像,其本身输入到计算机中是矩阵,矩阵里的数字就隐含了其各种各样的特征,这些特征可以通过模型去学习和捕捉。而文本的输入,是字符串,这是计算机处理不了的,所以当初人们首先想到的就是独热编码,第一是这样就可以将文本转化为数字,计算机能进行处理;第二就是每个词都可以独一无二的被表示。但是这种表示丢失了词的一个很重要的特征,即语义。为了进行语义信息,联想到了分布式表示,分布式表示一个很重要的核心思想是:语义相近的词在高纬度上是相互接近的。这里用一个简单的例子来感受一下分布式表示的魅力:

比如,我们要表示三样东西“红色的正三角形”,“红色的圆正方形”,“绿色的正三角形”,如果用one-hot编码表示,则分别为[1,0,0],[0,1,0],[0,0,1]。可以发现若这样表示,三个向量在三维空间上彼此正交,其之间的潜在特征将会因为这样的表示而消失。若我们在3维空间上,第一个维度空间表示颜色(红色0绿色1),第二个维度空间上表示圆和正(正0圆1),第三个维度空间上表示形状(三角形0正方形1),那么此时这三个向量可以表示为[0,0,0],[0,1,1],[1,0,0],我们发现,这三个向量彼此之间就存在了联系,若在来一个“绿色的圆三角形”,我们也可以轻松的表示为 [1,1,0],而不是独热编码的所有向量由三维变成思维。这也是独热编码的另一个致命缺陷,数据过于稀疏。

下面一个重点问题就是,到底如何用分布式表示来表示词呢,也可以说是词表征问题。最原始的做法就是神经网络语言模型(Neural network language model,nnml),其一个精华部分在于:表面上模型用于预测下一个词,但其目的在于通过训练任务,使得训练后的语义接近的词向量在高纬空间上相互接近。nnml具体的构造如下:

 

还是以上面的例子“I love NanJing University”为例(假设我们的语料库也只有这一句话,4个单词),此时假设我们输入前面三个“I love NanJing”,来预测“University”。我们可以发现网络一共大致可以看做有四层,分别为输入层、投影层、隐藏层、以及输出层,左图也可以清晰的有四层,以下我们一层一层解析,并且分析每层的复杂度:

输入层:输入层输入的是词的one-hot编码,那么我们输入的三次词分别是: 

x_1=[1,0,0,0],x_2=[0,1,0,0],x_3=[0,0,1,0]

当然我们在真正计算的时候,肯定是以一个矩阵的形式,因此我们将输入合并写在一起则是: 

X_{input}=[x_1,x_2,x_3], X_{input} \in R^{3 \times vocab\_size}

为什么输入要进行one-hot编码呢?这是为了投影层所服务,投影层为细说这样的好处,现在我们就先简单知道输入层是每个词的one-hot编码,那么我们的输入。此层无参数,复杂度为0。

投影层:顾名思义,就是找出每个词向量在这一层的投影,投影层经历了一个线性变化:  

X_{project}= X_{input} \cdot W ,X_{project}\in R^{3 \times d\_model}

这里我们可能还细看不出来有什么,我们近一步细看: 

 X_{project}=X_{input} \cdot W_{project}=\begin{bmatrix} 1 & 0 & 0 &0 \\ 0& 1 &0 &0 \\ 0& 0 & 1 & 0 \end{bmatrix}\begin{bmatrix} c_{11} &c_{12} \\ c_{21}&c_{22} \\ c_{31}&c_{32} \\ c_{41}&c_{42} \end{bmatrix}=\begin{bmatrix} c_{11} &c_{12} \\ c_{21}&c_{22} \\ c_{31}&c_{32} \end{bmatrix}

我们发现,由于X_{input}每行词向量采用独热编码,通过线性变化以后最终会得到W_{project}中的某一行。这里提前说一下,W_{project}的每一行就是我们最终要获取的词向量,目前只是没有经过训练,经过训练以后的W_{project}每一行对应的词向量,相似语义的在高纬度空间会相互接近(满足了分布式表示)。所以输入层独热编码好处在于,在投影层会为某个词得到独一无二的词向量,这样在训练的时候,词向量之间彼此就不会互相影响(即训练自己词向量的时候修改了其他词向量的值),即训练某词向量对矩阵进行修改,其修改的知识专属于当前自己词向量,修改的元素值不会重复出现在其他词向量里。这里的参数主要就是W_{project}\in R^{vocab\_size \times d\_model},其复杂度为:V\times D

隐藏层:隐藏层并不一定只有一层,可能是多层,但是他们是非线性变化,即线性变化以后经过一个激活函数tanh。当前隐层的输出是下一隐藏层的输入。隐藏层主要作用在于捕获特征,即语义特征(这是独热编码里没有的)。不过有一个注意点就是,输入隐藏层的X_{project}要打平为[c_{11},c_{12},c_{21},c_{22},c_{31},c_{32}],这样才能进行全连接映射。为了简单表示,我们就默认为只有一层隐藏层:

X_{hidden}=tanh(X_{project}\cdot W_{hidden},X_{hidden}\in R^{3 \times d\_hidden\_size})

同理,这样的主要参数为W_{hidden}\in R^{3*d\_model,d\_hidden\_size}\Cap,因此,其复杂度为:input\_size \times D\times H

输出层:输出层即将映射到字典上去,给出字典中的每个词的概率,最大概率的词就是我们要预测的下一个词:

y_{output}=softmax(X_{hidden}\cdot W_{output},y_{output}\in R^{3 \times vocab\_size})

此时,我们取出最大概率为我们的预测值,并将预测结果与标签用交叉熵计算损失并反向传播更新投影层的词向量矩阵。这里的参数主要就是W_{output},其复杂度为:H\times V

因此,传统的nnml的复杂度至少为:

V\times D+input\_size \times D\times H+H\times V

我们可以简单分析一下,复杂度的三个地方:第一个是投影层,那里的复杂度是无法在减少的,因为我们需要选取独一无二的词向量这个特性。而在隐藏层,首先是输入的越多(input_size越大),复杂度和input_size成正比,其次随着隐藏层的加深,复杂度也会变的越来越大。每多一层,复杂度就上升H\times H。输出层其实我只希望求得我标签的概率值,但是由于softmax特性,我必须映射到字典上的所有概率。因此我们总结复杂度比较大的两个地方:

(1)隐藏层输入需要打拼,这等于说输入的越多,我们的复杂度也越大

(2)隐藏层的加深,复杂度也会上升很多

(3)输出层的映射到字典上,复杂度也很大。

大名鼎鼎的word2Vec对这三个问题都作出的改进!


3.word2Vec

word2vec一个核心思想在于:一个词的信息与其上下文有关,这也是公认的。比如“今天晚上我们在一起吃了**,好饱呀”。中间“**”的地方一定是食物;在比如“下午我们一起在网吧打“**”,他技术真的太菜了!”毫无疑问,这一定是一个游戏。也是基于此,word2Vec提出了2种模型,分别是cbow和skip-gram,即给定中心词预测周围词,给定周围词预测中心词。两种模型如下,左(cbow),右(skip-gram):

将这两个模型与nnml模型对比,我们发现一个巨大的变化就是删除了隐藏层。以下我们重点分析cbow模型(skip-gram也类似,就是没有一个叠词操作),看看他是如何降低模型的复杂度:

例子还是:“I love NanJing University”,假设我们取中心词为“love”,前后的窗口(skip-window)为1,这里我们可以联想一下n-gram模型,n-gram模型认为,当前词的产生只与前面的词有关联的。这种假设是很牵强的,或者说是有偏向性的。word2vec对此作了改进,认为前后都有关联,所以设置了一个skip-window窗口,在这个窗口内的单词都与中心词有关联。那么cbow模型就是希望,若我们输入“I”“NanJing”,我们的模型可以预测出“love”。以下我们分别从输入层,投影层,输出层进行具体的讲解:

输入层:我们的语料库还是“I love NanJing University”,我们的输入依然是one-hot编码,其实不难发现,cbow模型里面,输入的input_size=2*skip_window那么输入的“I”“NanJing”分别表示为:

x_1=[1,0,0,0],x_2=[0,0,1,0]

当然我们,在实际操作中会将其写到一起,则:

X_{input}=[x_1,x_2]

投影层:这里的投影层与nnml没有区别,都是为了为每个词投影出独一无二的词向量:

X_{project}=X_{input}\cdot W_{project} =\begin{bmatrix} 1 &0 &0 &0 \\ 0& 0& 1& 0 \end{bmatrix} \begin{bmatrix} c_{11} &c_{12} \\ c_{21} &c_{22} \\ c_{31} &c_{32} \\ c_{41} &c_{42} \end{bmatrix}=\begin{bmatrix} c_{11} &c_{12} \\ c_{31} &c_{32} \end{bmatrix}

但是cbow投影层多了一步叠词的操作,即不像原来的nnml拼接,cbow这里进行的叠词即为一个求每个维度上的平均值操作:

X_{project}=[\frac{c_{11} +c_{12}}{2},\frac{c_{31} +c_{32}}{2}]\leftarrow \begin{bmatrix} c_{11} &c_{12} \\ c_{31} &c_{32} \end{bmatrix}

叠词主要是为了方便输出层映射到V维度词典上,其次也不提升模型的复杂度。

因此投影层的复杂度主要为W_{project}\in R^{vocab\_size\times d\_model},其复杂度为:V\times D。删除隐藏层以及叠词操作解决了我们前面分析nnml存在问题的1,2

输出层:输出层即将投影层的结果映射到字典中去,这里我们先默认映射到字典中去,下一节在详细介绍word2Vec在这里的优化(负采样和层序softmax)。即:

y_{output}=X_{project}\cdot W_{output},y_{output}\in R^{vocab\_size\times 1}

最后得到的结果进行softmax求概率并与标签计算交叉熵损失然后反向传播更新词向量矩阵。

以上就是一个cbow的过程,skip-gram类似,即在训练的时候用一个词预测周围词,过程里面除了不需要叠词以外(因为输入就一个词,将cbow也是担心漏了叠词),其余都与cbow一样。接下来我们讲解数学推导:这里我们用skip-gram模型,理由在于,skip-gram模型的数学推导相对容易理解!理解skip-gram也就很容易推导cbow。我们再次放上skip-gram的模型架构:

还是以我们的“I love NanJing University”为例,那么以“love”为中心的数据集就变为:(“love”,“I”),("love","NanJing")。假设我们此时选择(“love”,“I”)代入计算,具体的计算过程模型如下:

我们假设w_{i}为字典里索引为i的词,那么一开始我们会对齐进行one-hot编码得到一个1\times V的表示向量,这个one-hot表示向量经过投影层的中心词向量矩阵(也就是那个W),选择出属于这个词独一无二的专属词向量v_c,这里的v表示是从中心词向量矩阵得到一个1\times D的专属词向量。这个专属的词向量随后经过一个背景词向量矩阵会与字典上所有词的背景词向量u_i进行内积运算,得到其各自是属于中心词向量v_c的分数u_i^Tv_c,最后通过sotfmax得到其最终的分数,即:

p(w_o|w_c)=\frac{exp\{u_o^Tv_c\}}{\sum_{i\in V}exp\{u_i^Tv_c\}}

上式展示的是某背景词的概率,真正在最后的y输出的结果向量应该为:

\begin{bmatrix} p(w_1|w_c)\\ p(w_2|w_c)\\ ...\\ p(w_V|w_c)\\ \end{bmatrix}

但是我们最后代入交叉损失最后计算的就是(因为交叉熵:loss=\sum_i y_ilogp(w_i|w_c),非背景词的标签y_i=0):

p(w_o|w_c)=\frac{exp\{u_o^Tv_c\}}{\sum_{i\in V}exp\{u_i^Tv_c\}}

由于我们预测多个词,基于全文,我们希望求解的最大概率为:

arg max \prod_{t=1}^T \prod_{-m \le j \le m }p(w^{(t+j)}|w^t)

其中T表示文本有T个字长的序列,m表示skip-gram的大小。连乘可能会带来精度溢出的情况,所以一般我们都等价转为为:

Loss=-argmin \sum_{t=1}^{T}\sum_{-m\le j\le m}log p(w^{(t+j)}|w^t)

而我们向模型求一个样本(1个背景词,V-1个非背景词)的概率计算如下:

-log p(w^{(t+j)}|w^t)=-log\frac{exp\{u_{t+j}^Tv_t\}}{\sum_{i\in V}exp\{u_i^Tv_t\}}\\ =-u_{t+j}^Tv_t+log\sum_{i\in V}exp\{u_i^Tv_t\}

我们要做的就是对这个损失进行求偏导(对v_c)优化。而对于cbow模型,由于其输入比较多,一般是将所有的输入加权平均代入运算,即v_c=avg(v_{t-m},...,v_{t-1}v_{t+1},...,v_{t+m}),实际操作(叠词)也正是如此。


4.训练技巧

4.1负采样

基于前面的分析,我们知道当前的复杂度为:

V\times H+H\times V

加号前面为了保证每个词向量的独一无二性,我们无法对其进行优化,而后面映射到词典上的概率,这就是负采样进行优化的地方。根据中心词对某一个背景词预测,为了得到这个概率,但是我们却要算出除了背景词概率以外所有词的概率,而非背景词概率在最后的损失函数计算上也用不到,这是一个十分费力不讨好的方式。然而,根据softmax性质,必须要计算出所有词的概率,这样才能最终得到背景词的概率。因此负采样是重点是针对softmax函数进行优化,即不在使用softmax函数,而是改用sigmoid函数。

我们认真思考一下softmax函数,softmax函数的引入是为了解决多分类的问题,softmax也可以认为是sigmoid函数的推广。负采样做的是,不在为了一个背景词向量计算所有非背景词向量的概率,,而是选择k个非背景词作为干扰词,通过1个背景词和K个非背景干扰词来计算所得背景词概率p(w_o|w_c)。那么这个概率如何计算?之前是选择所有非背景词作为干扰次然后多分类映射到字典V上,现在我们随机选择k个非背景干扰词(至于怎么随机选择后面细说,这里先默认以及选择好了K个非背景词),然后考虑的是二分类问题,即预测的是背景词和非背景词。此时们就可以选择sigmoid函数,即表达当前词是背景词的概率事件。假设D表示当前是否为背景词,那么:

p(D=1|w_c,w_o)=\sigma (u_o^Tv_c) ,p(D=0|w_c,w_k)=1-\sigma (u_o^Tv_c)

我们在用图来进一步解释一下:

原来的中心词词向量v_c需要和词典上所有的背景词向量u_i进行内积运算得出分数,然后在用sotfmax求解概率,即:

p(w_o|w_c)=\frac{exp\{u_o^Tv_c\}}{\sum_{i\in V}exp\{u_i^Tv_c\}}

如今,我们只要中心词词向量v_c和一个背景词向量u_o以及k个非背景词u_{k_i}内积预算得出分数,用sigmoid求解概率,那么我们的概率计算为:

p(w_o|w_c)=p(D=1|w_o,w_c)\prod _{k=1,w_k\in p(w_c)}^{K}p(D=0|w_k,w_c) \\= \sigma(u_o^Tv_c)\prod _{k=1,w_k\in p(w)}^{K}(1-\sigma(u_k^Tv_c))

其中p(w_c)表示当前中心词w_c的所有非背景词集合,K表示每个背景词选择K个非背景干扰词。

其实我们的损失函数:

Loss=argmax\prod_{t=1}^{T}\prod_{-m\le j\le m}p(w^{(t+m)}|w^t)\\\rightarrow argmin -(\sum_{t=1}^{T}\sum_{-m\le j\le m}logp(w^{(t+m)}|w^t)

而我们在向模型喂进一个样本(1个背景词K个非背景词)的概率计算如下:

-logp(w^{t+j}|w^t)=-(log\sigma(u_{t+m}^Tv_t)+\sum_{k=1,w_k\in p(w_t)}^{K}log(1-\sigma(u_k^Tv_t))\\ =-\(log\sigma(u_{t+m}^Tv_t)+\sum_{k=1,w_k\in p(w_t)}^{K}log(\sigma(-u_k^Tv_t))\

至此,负样本讲解完毕,一定要看公式推导,否则代码会写错!

在看我手画的这幅图,我们在分析一次复杂:

原来的:

V\times D+D\times V

负采样变为:

V\times D+D\times (K+1)

要知道在实际生产中的V一般为几万,而我们负采样的K一般选择为5~10

但是还有一个问题就是,如何选择K个负样本词

这里是从语料库里面随机选择K个词作为非背景词(不放回),若选择的词发现是背景词则重新选择。但是这样又来了一个新的问题,既然是从语料库中随机选择K个词作为非背景词,那么不是出现频数越多的词,越容易被选择作为背景词,而那些频数少的词,则不太容易选择。根据齐夫定律可知,定频词携带的信息可能也越多,但是这样的模型在选择非背景词的时候是偏向于频数多的词,显然不太合适。因此作者对这一步进行了一个改进,即每个词被采样的概率计算为:

p(w_i)=\frac{U(w_i)^{0.75}}{Z}

其中U(w_i)代表当前语料库中该词频率,Z表示为归一化因子。这样概率计算有一个好处就是,低频的词概率变大,高频词的概率变低。例如:词a和b的原先频率如下:

U(a)=0.01,U(b)=0.99

经过这样的计算以后:

p(a)=\frac{0.01^{0.75}}{0.99^{0.75}+0.01^{0.75}}=0.03

p(b)=\frac{0.99^{0.75}}{0.99^{0.75}+0.01^{0.75}}=0.97

我们发现,低频词被采样的概率有所缓和(其实softmax也是同样原理,大不能大太多,小也不能小太多,两头往中间拉)。

4.2重采样

根据齐夫定律我们可知,频率出现越多的词其携带的信息越少,比如“and”“with”“I”“you”等,这些词语很多,其周围词丰富多彩,携带的关键信息很少,对我们模型预测也起不了多大作用。重采样的目的就是将将高频词随机丢弃,作者对所有词都计算了一个丢弃概率:

p(w_i)=max(0,1-\sqrt{\frac{t}{U(w_i)}})

其中t是一个阈值,原文设置为1e-5,但是原文的训练样本规模是1600万的量级,若我们的规模比较小的时候,可以适当增加t。当U(w_i)的频率小于t的时,那么被丢弃的概率为0,为当U(w_i)高于t的时候,其就有可能被丢弃,大的越多,丢弃的概率越大。这样对语料库进行重采样(有些书也叫二次采样)后,会过滤掉很多高频无用词。

4.3层序softmax

层序softmax将在fasttext上有着很重要的应用,那时候我将仔细复习与研究。word2vec在显示中普遍采用负采样的方法,因为负采样的复杂度也是比层序softmax低的,效果也不输它。这里只需要一个认知即可,就是softmax以前查找以前是按顺序遍历,因此最后的实际复杂度为D\times V,而层序softmax引入哈夫曼树,将词典放入在词数据结构上,以其频数作为权重依据构建一颗哈夫曼树,此时树的高度为logV。这就代表着,我们最后映射查找词的时候,我们的复杂度为D\times logV。我们思考log函数的图像可知,logV是远小于V的。因此,这也是一种与负采样同名的word2Vec优化方法。


第一次写博客,水平有限可能有很多错误的地方,如有错误,还望指正!

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值