大家好,又见面了,我是你们的朋友全栈君。
聊聊Word2vec
- 1 前言
- 2 什么是Word2vec?
- 2.1 定义
- 2.1.1 分词的原理介绍
- 2.1.2 文本向量化的方式
- 2.2 数学原理
- 2.2.1 CBOW(Continuous Bag-of-Words)原理
- 2.2.2 Skip-Gram原理
- 2.2.3 为什么要有Word2vec 而不是用原来的?
- 2.2.4 Word2vec基础:霍夫曼树
- 2.2.5 Hierarchical Softmax
- 2.2.6 Negative Sampling
- 2.3 应用场景
- 2.4 优缺点
- 2.1 定义
- 3 Word2vec的Python实现
- 3.1 导入库
- 3.2 读入数据
- 3.3 模型
- 3.4 应用
- 3.4.1 应用1
- 3.4.2 应用2
- 3.4.3 应用3
- 3.4.4 应用4
- 3.4.5 李达康的词向量
- 3.4.6 侯亮平的词向量
- 3.5 利用Python计算cosine
- 参考
1 前言
最近公司项目中涉及到给每个用户推荐app,而在app数据相关处理的过程中,将app变为了一个向量,最后再转变到一个用户用一个向量来表示,而这其中用到的关键技术就是Word2Vec!之前只是大概听过,现在系统性的总结一波~
2 什么是Word2vec?
2.1 定义
首先来看看维基百科定义:
Word2vec:为一群用来产生词向量的相关模型。这些模型为浅层双层的神经网络,用来训练以重新建构语言学之词文本。网络以词表现,并且需猜测相邻位置的输入词,在word2vec中词袋模型假设下,词的顺序是不重要的。 训练完成之后,word2vec模型可用来映射每个词到一个向量,可用来表示词对词之间的关系。该向量为神经网络之隐藏层[1]。 Word2vec依赖skip-grams或连续词袋(CBOW)来建立神经词嵌入。Word2vec为托马斯·米科洛夫(Tomas Mikolov)在Google带领的研究团队创造。该算法渐渐被其他人所分析和解释[2][3]。
结合上述定义我们可以看到:
- Word2vec用来产生词向量,但其模型为神经网络模型,词向量为模型的输入,最后是通过梯度上升法不断的优化迭代这个词向量。
- Word2vec迭代产生的词向量可以自己指定向量维度
这时候不禁就会问一句,为什么要搞一个词向量?词汇为啥要表示成向量呢?
2.1.1 分词的原理介绍
在下面介绍文本向量化的时候会涉及到分词,首先介绍下分词的基本原理。
- 本质是一个N元模型,即目前位置的词汇和前面N个词汇有关。
- 在NLP中,为了简化计算,我们通常使用马尔科夫假设,即每一个分词出现的概率仅仅和前一个分词有关。
- MCMC采样时,也用到了相同的假设来简化模型复杂度。使用了马尔科夫假设,则我们的联合分布就好求了。
- 优化求解方法:维比特算法。
- 句子过长,对应很多种分词方法的时候,直接暴力求每种出现的概率然后选最优的算法复杂度过高
- 通用的求序列最短路径的方法。用概率图来进行表示
- 应用:隐式马尔科夫模型HMM解码算法求解;最优分词求解
2.1.2 文本向量化的方式
文本无法直接参与建模进行后续分析,而转化成向量之后就可以进行!所以如何将文本变为向量就是一个大学问~
但归纳起来,可以理解为两种方式:
- 方式1:基于one-hot编码的变形
- 变形1:基于频数(词袋模型,BoW)的向量化表示
- 变形2:基于Hash Trick的向量化表示
- 变形3:基于TF-IDF的向量化表示
- 方式2:Word2vec
方式1:基于频数(词袋模型,BoW)的向量化表示
- 首先对预料进行分词 预设词典 去停用词
- 统计出所有出现的词汇,同时定义位置,如果某一句话有该位置上的词,则在该位置上的取值为 该词出现的频数!
- 对每句话按照上述方式进行向量化表示!
可以结合下面结果知道,这种方法本质还是one-hot,只不过这时候的1表示为频数!而不仅仅是表示有没有出现!
Python实现:
代码语言:javascript复制from sklearn.feature_extraction.text import CountVectorizer
vectorizer=CountVectorizer()
corpus=["I come to China to travel",
"This is a car polupar in China",
"I love tea and Apple ",
"The work is to write some papers in science"]
print (vectorizer.fit_transform(corpus))
代码语言:javascript复制 (0, 4) 1
(0, 15) 2
(0, 3) 1
(0, 16) 1
(1, 3) 1
(1, 14) 1
(1, 6) 1
(1, 2) 1
(1, 9) 1
(1, 5) 1
(2, 7) 1
(2, 12) 1
(2, 0) 1
(2, 1) 1
(3, 15) 1
(3, 6) 1
(3, 5) 1
(3, 13) 1
(3, 17) 1
(3, 18) 1
(3, 11) 1
(3, 8) 1
(3, 10) 1
按位置定义的所有词汇如下:
代码语言:javascript复制print (vectorizer.fit_transform(corpus).toarray())
print('词向量的维度为: ', len(vectorizer.fit_transform(corpus).toarray()[0]))
print (vectorizer.get_feature_names())
代码语言:javascript复制[[0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 2 1 0 0]
[0 0 1 1 0 1 1 0 0 1 0 0 0 0 1 0 0 0 0]
[1 1 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0]
[0 0 0 0 0 1 1 0 1 0 1 1 0 1 0 1 0 1 1]]
词向量的维度为: 19
['and', 'apple', 'car', 'china', 'come', 'in', 'is', 'love', 'papers', 'polupar', 'science', 'some', 'tea', 'the', 'this', 'to', 'travel', 'work', 'write']
方式2:基于Hash Trick的向量化表示
什么叫Hash Trick呢?为什么要用Hash Trick?
- Hash Trick是为了避免基于词频而维度过大的情形!
- 首先定义一个特征Hash后对应的哈希表的大小,这个哈希表的维度会远远小于我们的词汇表的特征维度,因此可以看成是降维。
- 具体方法:对应任意一个特征名,我们会用Hash函数找到对应哈希表的位置,然后将该特征名对应的词频统计值累加到该哈希表位置。
- 变形:signed hash trick。
- 解决的问题:两个原始特征的哈希后位置在一起导致词频累加特征值突然变大
- 好处:哈希后的特征仍然是一个无偏的估计,不会导致某些哈希位置的值过大。
- 但Hash trick解释性比基于词频的要差。
对比基于词频的向量化 Hash Trick后的向量化:
基于词频的向量化应用场景:
- 词汇表的特征不太大
- 优势:
- 解释性很强,我们知道每一维特征对应哪一个词
- 同时还可以使用TF-IDF对各个词特征的权重修改,进一步完善特征的表示。
基于Hash Trick的向量化应用场景:
- 大规模机器学习
- 优势:
- 降维速度很快,降维后的特征仍可以帮我们完成后续的分类和聚类工作
- 解决了词汇量极大,使用向量化方法内存不够用的问题
Python实现:
将上述19维的转变为6维
代码语言:javascript复制from sklearn.feature_extraction.text import HashingVectorizer
vectorizer2=HashingVectorizer(n_features = 6,norm = None)
print (vectorizer2.fit_transform(corpus))
代码语言:javascript复制 (0, 1) 2.0
(0, 2) -1.0
(0, 4) 1.0
(0, 5) -1.0
(1, 0) 1.0
(1, 1) 1.0
(1, 2) -1.0
(1, 5) -1.0
(2, 0) 2.0
(2, 5) -2.0
(3, 0) 0.0
(3, 1) 4.0
(3, 2) -1.0
(3, 3) 1.0
(3, 5) -1.0
代码语言:javascript复制print (vectorizer2.fit_transform(corpus).toarray())
print('词向量的维度为: ', len(vectorizer2.fit_transform(corpus).toarray()[0]))
代码语言:javascript复制[[ 0. 2. -1. 0. 1. -1.]
[ 1. 1. -1. 0. 0. -1.]
[ 2. 0. 0. 0. 0. -2.]
[ 0. 4. -1. 1. 0. -1.]]
词向量的维度为: 6
方式3:基于TF-IDF的向量化表示
首先TF-IDF在之前的博客中小编已经介绍过,详情可以戳:机器学习 | TF-IDF和TEXT-RANK的区别
在此处,大概流程和上述1很类似,就是将词频换成了该词汇的TF-IDF得分!
- 首先对预料进行分词 预设词典 去停用词
- 统计出所有出现的词汇,同时定义位置,如果某一句话有该位置上的词,则在该位置上的取值为 该词的TF-IDF得分!
- 对每句话按照上述方式进行向量化表示!
至于为什么基于频数进行优化也很好理解,比如有些话中to很多,词频会很大,但其意义可能并不大,TF-IDF就可以有效解决这个问题!
Python实现:
代码语言:javascript复制from sklearn.feature_extraction.text import TfidfVectorizer
tfidf2 = TfidfVectorizer()
corpus=["I come to China to travel",
"This is a car polupar in China",
"I love tea and Apple ",
"The work is to write some papers in science"]
re = tfidf2.fit_transform(corpus)
print (re)
代码语言:javascript复制 (0, 16) 0.4424621378947393
(0, 3) 0.348842231691988
(0, 15) 0.697684463383976
(0, 4) 0.4424621378947393
(1, 5) 0.3574550433419527
(1, 9) 0.45338639737285463
(1, 2) 0.45338639737285463
(1, 6) 0.3574550433419527
(1, 14) 0.45338639737285463
(1, 3) 0.3574550433419527
(2, 1) 0.5
(2, 0) 0.5
(2, 12) 0.5
(2, 7) 0.5
(3, 10) 0.3565798233381452
(3, 8) 0.3565798233381452
(3, 11) 0.3565798233381452
(3, 18) 0.3565798233381452
(3, 17) 0.3565798233381452
(3, 13) 0.3565798233381452
(3, 5) 0.2811316284405006
(3, 6) 0.2811316284405006
(3, 15) 0.2811316284405006
向量维度以及各维度表示的含义为:
代码语言:javascript复制tfidf2.get_feature_names()
代码语言:javascript复制['and',
'apple',
'car',
'china',
'come',
'in',
'is',
'love',
'papers',
'polupar',
'science',
'some',
'tea',
'the',
'this',
'to',
'travel',
'work',
'write']
代码语言:javascript复制print('词向量的维度为: ', len(tfidf2.fit_transform(corpus).toarray()[0]))
tfidf2.fit_transform(corpus).toarray()
代码语言:javascript复制词向量的维度为: 19
array([[0. , 0. , 0. , 0.34884223, 0.44246214,
0. , 0. , 0. , 0. , 0. ,
0. , 0. , 0. , 0. , 0. ,
0.69768446, 0.44246214, 0. , 0. ],
[0. , 0. , 0.4533864 , 0.35745504, 0. ,
0.35745504, 0.35745504, 0. , 0. , 0.4533864 ,
0. , 0. , 0. , 0. , 0.4533864 ,
0. , 0. , 0. , 0. ],
[0.5 , 0.5 , 0. , 0. , 0. ,
0. , 0. , 0.5 , 0. , 0. ,
0. , 0. , 0.5 , 0. , 0. ,
0. , 0. , 0. , 0. ],
[0. , 0. , 0. , 0. , 0. ,
0.28113163, 0.28113163, 0. , 0.35657982, 0. ,
0.35657982, 0.35657982, 0. , 0.35657982, 0. ,
0.28113163, 0. , 0.35657982, 0.35657982]])
可以看到此时to就没有this重要,虽然频数大!
方式4:Word2vec
归结起来,Word2vec为2种模型 2种求解优化方法,故总共为4种方案,下面在数学原理篇将进行详细介绍!
2.2 数学原理
- 2种模型:CBOW和Skip-Gram
- 2种求解优化方法:Hierarchical Softmax和Negative Sampling
首先在Word2vec之前已经有两种模型在做词向量的工作,那就是CBOW和Skip-Gram,而Word2vec就是在这个基础上加入了两种优化方法:Hierarchical Softmax和Negative Sampling,于是就产生了4种Word2vec模型:
- CBOW Hierarchical Softmax
- CBOW Negative Sampling
- Skip-Gram Hierarchical Softmax
- Skip-Gram Negative Sampling
2.2.1 CBOW(Continuous Bag-of-Words)原理
模型作用:用来训练产生词向量
- 三层的神经网络结构(当然也可以多层),分为输入层,隐藏层和输出层(softmax层)。
- 训练输入:某个词的上下文相关词对应的词向量,训练输出:该词
- 由于CBOW使用的是词袋模型,因此这8个词都是平等的,也就是不考虑他们和我们关注的词之间的距离大小,只要在我们上下文之内即可。
- CBOW神经网络模型输入层有8个神经元,输出层有词汇表大小个神经元。
举例:
- 训练输入:上述8个词对应的词向量(一开始先初始化8个词的词向量,后面通过神经网络不断迭代)
- 训练输出:learning对应词向量【训练的目标是期望训练样本特定词对应的softmax概率最大】
- 不断训练迭代优化词向量
最后前向计算预测的时候,
- 输入:某个词汇上下文的词汇的向量
- 输出:对应所有词汇的softmax概率
2.2.2 Skip-Gram原理
- 思路和上述CBOW相反,已知某个词汇,输出该词汇对应上下文。
- 即输入:特定的一个词的词向量;输出:特定词对应的上下文词向量。
- 还是上面的例子,我们的上下文大小取值为4, 特定的这个词”Learning“是我们的输入,而这8个上下文词是我们的输出。
- Skip-Gram神经网络模型输入层有1个神经元,输出层有词汇表大小个神经元。
- 训练输入:输入是特定词的词向量
- 训练输出:输出是上下文的8个词的词向量
最后前向计算预测的时候,
- 输入:某个词汇的词向量
- 输出:概率大小排前8的softmax概率对应的神经元所对应的词即可。
2.2.3 为什么要有Word2vec 而不是用原来的?
- 原因是传统的DNN算法最后输出层是softmax激活函数,并且输出层为词汇表大小的神经元,因此计算量太大!效率低!
- 总结就是:DNN的输出层需要进行softmax计算各个词的输出概率的的计算量很大。
2.2.4 Word2vec基础:霍夫曼树
Word2vec数据结构是用霍夫曼树来代替隐藏层和输出层的神经元
优化1:对于从输入层到隐藏层的映射,没有采取神经网络的线性变换加激活函数的方法,而是采用
优化2:从隐藏层到输出的softmax层这里的计算量改进。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/145420.html原文链接:https://javaforall.cn