word2vec词向量学习笔记
原文地址:http://blog.csdn.net/hjimce/article/details/51564783
个人微博:黄锦池-hjimce
一、使用原版word2vec工具训练
1、英文编译测试
(1)到官网到下载:https://code.google.com/archive/p/word2vec/,然后选择export 到github,也可以直接到我的github克隆:
git clone https://github.com/hjimce/word2vec.git
(2)编译:make
(3)下载测试数据http://mattmahoney.net/dc/text8.zip,并解压
(4)输入命令train起来:
time ./word2vec -train text8 -output vectors.bin -cbow 1 -size 200 -window 8 -negative 25 -hs 0 -sample 1e-4 -threads 20 -binary 1 -iter 15
(5)测试距离功能:
./distance vectors.bin
2、中文训练测试
(1)中文词向量:下载数据msr_training.utf8,这个数据已经做好分词工作,如果想要直接使用自己的数据,就需要先做好分词工作
(2)输入命令train起来:
time ./word2vec -train msr_training.utf8 -output vectors.bin -cbow 1 -size 200 -window 8 -negative 25 -hs 0 -sample 1e-4 -threads 20 -binary 1 -iter 15
(3)启动相似度距离测试:
./distance vectors.bin
(4)输入相关中文词:中国,查看结果:
hjimce@hjimcepc:~/workspace/word2vec$ ./distance vectors.binEnter word or sentence (EXIT to break): 中国 Word: 中国 Position in vocabulary: 35 Word Cosine distance------------------------------------------------------------------------ 中国人民 0.502711 美国 0.480650 中国政府 0.463177 我国 0.447327 亚太地区 0.444878 加勒比 0.418471 两国 0.408678 各国 0.392190 独立自主 0.391517 世界 0.387604 国际 0.382578 中美 0.382208 欧美 0.379807 古巴 0.378412
二、算法学习阶段
因为这个算法是半年前所学的算法,最近只是简单复习一下,所以不打算写详细的算法流程笔记,原理等也不打算啰嗦。word2vec网络结构可以分成两种:CBOW、Skip-Gram,其实网络结构都非常简单,不过是一个三层神经网络罢了。本文只讲解CBOW网络结构算法、算法流程。
CBOW又有两种方案,一种叫层次softmax,另一种叫:negative sample。这两种方法如果看不懂也没关系,你完全可以用原始的softmax替代网络的最后一层,进行训练,只是训练速度比较慢。
1、CBOW+层次softmax算法总体流程
先讲解层次softmax的算法实现过程:
(1)根据训练语料库,创建词典,并对词典每个单词进行二叉树霍夫曼编码。
如下图所示,比如经过编码后,可能汉语词典中的“自”就被编码成了:110,“我”对应的编码就是:001。这个算法与word2vec的实现过程关系不大,具体霍夫曼编码过程代码怎么写,不懂也没关系。我们只需要记住,字典中的每个单词都会被编码,每个单词对应二叉树的叶节点,每个单词的编码结果对应于:从跟节点到达当前单词,所经过的节点路径。编码中的1、0表示右节点、左节点。
这边需要知道的是每一位编码的用处,因为每位的编码节点刚好可以对应到0、1输出标签,而且这颗二叉树节点就是神经网络的输出层神经元,具体可以看下面的图。假如在训练的时候,最后一层训练数据的输出是“自”,那么其实我们只需要训练节点root、node1、node3使得这三个节点的激活值分别为:1、1、0,这样就可以了。
因此对于层次softmax来说,神经网络的隐藏层其实是连接到二叉树的每个非叶子节点上(如果是原始的sotfmax,是直接连接到叶子节点上),然后对这些非叶子节点,根据输出单词的编码路径,对路径上的每个节点,根据对应的编码进行训练。
(2)根据定义窗口大小,截取出一个窗口内的单词作为一个样本,进行训练
这一步在word2vec里面,其实我们给出的参数值是一个max window size,然后word2vec底层生成一个随机大小的窗口,只要满足其范围在max window size 即可,这个可能是为了数据扩充吧。
(3)输入层-》隐藏层:对窗口内的左右单词(不包含自己)对应的词向量,进行累加,并求取平均向量Vavg。
(4)隐藏层-》非叶子节点:每个二叉树非叶子节点,链接到Vavg都有一个可学习的参数W,然后通过sigmoid可以得到每个非叶子节点的激活值(也表示概率值):
也就是说网络的参数除了词向量之外,还有隐藏层链接到二叉树结点的参数向量(从word2vec代码上看,我没有看到偏置参数b)。
(5)反向求导:根据输出文字的节点路径,更新路径上的每个非叶子节点链接到隐藏层的参数值w;并更新窗口内各个单词的词向量。
具体网络结构图如下所示:
采用层次softmax的优点在于加快训练速度,参数个数、预测速度没啥差别。
2、CBOW+Negative Sample
这个比较简单,所谓的Negative Sample,就是除了正样本标签之外,还需要随机抽取出词典中的其它单词作为负样本(以前是把整个词典的其它单词都当成负样本),这个还是具体看源码实现吧。
三、源码阅读阶段
word = sen[sentence_position];//当前单词 if (word == -1) continue; for (c = 0; c < layer1_size; c++) neu1[c] = 0;//隐藏层神经元激活值初始化为0 for (c = 0; c < layer1_size; c++) neu1e[c] = 0;//隐藏层反向求导的误差值 next_random = next_random * (unsigned long long)25214903917 + 11; b = next_random % window;//b是一个介于0~window size之间的随机数值 //cbow算法,网络第一层:直接把左右窗口内的单词相加起来 //sentence_position是当前单词 if (cbow) { cw = 0; //算法第一步:除了当前单词之外,把上下文窗口单词的词向量相加起来 for (a = b; a < window * 2 + 1 - b; a++) if (a != window) { c = sentence_position - window + a;//遍历sentence_position窗口内的上下文单词 if (c < 0) continue;//边界越界处理,因为一个文档的长度是0~sentence_length if (c >= sentence_length) continue; last_word = sen[c]; if (last_word == -1) continue; for (c = 0; c < layer1_size; c++)//把左右窗口内的单词的词向量全部相加,其中layer1_size表示词向量的维度 { neu1[c] += syn0[c + last_word * layer1_size];//syn0是词向量表 } cw++; } if (cw) { //算法第二步:平均,把上面左右单词的词向量加起来后再平均 for (c = 0; c < layer1_size; c++) { neu1[c] /= cw; } if (hs) //vocab[word].codelen表示当前单词的霍夫曼编码长度,下面也就遍历当前词的路径节点 for (d = 0; d < vocab[word].codelen; d++) { //算法第三步:启用了层次sorfmax模式,计算每个节点的概率 //f是二叉树节点的输出值,下面是计算f,f是每个节点的逻辑回归概率值 f = 0; l2 = vocab[word].point[d] * layer1_size; for (c = 0; c < layer1_size; c++) f += neu1[c] * syn1[c + l2];//syn1是从隐藏层到二叉树节点连接参数矩阵(可以把二叉树节点看成是神经元) //节点的label被定义为:1-code,而不是code,这个可能是为了方便计算吧 // 那么节点损失函数为-[(1-code)*log(f)+code*log(1-f)] if (f <= -MAX_EXP) continue; else if (f >= MAX_EXP) continue; else f = expTable[(int)((f + MAX_EXP) * (EXP_TABLE_SIZE / MAX_EXP / 2))]; //算法第四步:反向求导 // 'g' is the gradient multiplied by the learning rate g = (1 - vocab[word].code[d] - f) * alpha;//梯度×学习率 // Propagate errors output -> hidden for (c = 0; c < layer1_size; c++) neu1e[c] += g * syn1[c + l2]; // Learn weights hidden -> output for (c = 0; c < layer1_size; c++) syn1[c + l2] += g * neu1[c]; } //如果采用了negative sample if (negative > 0) for (d = 0; d < negative + 1; d++) { //正样本 if (d == 0) { target = word; label = 1; } //随机采样出负样本 else { next_random = next_random * (unsigned long long)25214903917 + 11; target = table[(next_random >> 16) % table_size]; if (target == 0) target = next_random % (vocab_size - 1) + 1; if (target == word) continue; label = 0; } l2 = target * layer1_size; f = 0; for (c = 0; c < layer1_size; c++) f += neu1[c] * syn1neg[c + l2];//同样计算输出单词,逻辑回归概率值 //似然函数为-[label*log(f)+(1-label)*log(1-f)] //更新链接到当前类别(单词)节点的权值向量 if (f > MAX_EXP) g = (label - 1) * alpha; else if (f < -MAX_EXP) g = (label - 0) * alpha; else g = (label - expTable[(int)((f + MAX_EXP) * (EXP_TABLE_SIZE / MAX_EXP / 2))]) * alpha; for (c = 0; c < layer1_size; c++) neu1e[c] += g * syn1neg[c + l2]; for (c = 0; c < layer1_size; c++) syn1neg[c + l2] += g * neu1[c]; } //从隐藏层到词向量层反向传播,更新词向量层 for (a = b; a < window * 2 + 1 - b; a++) if (a != window) { c = sentence_position - window + a; if (c < 0) continue; if (c >= sentence_length) continue; last_word = sen[c]; if (last_word == -1) continue; for (c = 0; c < layer1_size; c++) syn0[c + last_word * layer1_size] += neu1e[c]; } } }
参考文献:
1、《Efficient Estimation of Word Representations in Vector Space》
2、《word2vec Explained: Deriving Mikolov et al.'s Negative-Sampling Word-Embedding Method》
3、https://code.google.com/archive/p/word2vec/
Original url: Access
Created at: 2019-12-11 11:49:58
Category: default
Tags: none
未标明原创文章均为采集,版权归作者所有,转载无需和我联系,请注明原出处,南摩阿彌陀佛,知识,不只知道,要得到
最新评论