关于本文的补充:具体介绍基于Hierarchical Softmax的word2vec两种模型(CBOW与Skip-Gram)
目录
1.BOG词袋模型下的文本向量 - discrete representation
2.word2vec词向量 - Distributed representation
2.1神经网络语言模型-Neural Network Language Mode
2.2 word2vec基础之霍夫曼树
2.3 CBOW
2.4 Skip-Gram
3 word2vec实现
文本表示有离散表示(如one-hot)和分布式表示(如word2vec)两种常见形式
词向量:对词典D中的任意词w来说,指定一个固定长度的实值向量V,V(w)就是词w的词向量。
所谓BOW,就是将文本/Query看作是一系列词的集合。由于词很多,所以咱们就用袋子把它们装起来,简称词袋。
词袋模型是在自然语言处理和信息检索中的一种简单假设。在这种模型中,文本(段落或者文档)被看作是无序的词汇集合,忽略语法甚至是单词的顺序。
词袋模型被用在文本分类的一些方法当中。当传统的贝叶斯分类被应用到文本当中时,贝叶斯中的条件独立性假设导致词袋模型。另外一些文本分类方法如LDA和LSA也使用了这个模型。
磁带模型的特点:
离散:无法衡量上下文各个词之间的关系。
高维:词表长度随着语义库的增长而增长,n-gram序列增长得更快。
稀疏:特征向量长度过长,甚至特征数远大于文本样本数。
总结BOG模型的步骤:
1.1.分词与去除停用词。词袋模型的主要思想,是构建各类文本的词典,然后针对每一个文本,计算该文本每个词在词典中对应位置出现的次数。因此词袋模型首要的是分词,这方面有很多开源实现,比如jieba分词。在分词后的结果集中,一般会包含很多停用词,例如:“标点符号”,“的”,“得”等等,这些词汇可以看作无效词,会以噪音的形式影响后续运算,需要去除。一般可以构建停用词库,以配置文件的形式保存起来,分词后调用一下,将停用词过滤掉以得到更精炼的分词结果。
1.2.文本特征选择及词典构建。对于长文本文档,在构建词典前有必要通过特征选择方法来选择一批特征词,然后使用这些特征词构建词典,否则构建的词典将会非常庞大,即不利于存储,也不利于后续词频统计运算等。常用的特征选择方法有基尼系数、互信息、信息增益、卡方检验等。通过比较,信息增益和卡方检验两种方法效果较好。以卡方检验为例,一般设置卡方阈值或者词个数阈值,即可在卡方检验运算结果中返回一系列特征词。将全部特征词合并到一起,就构建出了文本词典。
1.3.特征词权重计算。词典构建完成后,接下来就可以利用词典将文档映射成向量。一般可以选择one-hot表示的向量及tf-idf表示的向量。所谓one-hot,就是向量中的一维对应于词典中的一项。如果以词频表示,则向量中该维的值即为词典中该单词在文档中出现的频率,那么特征词权重也就是词频。而tf-idf中,tf表示词频,即一个词在文本中出现的频率;idf表示逆文档频率,即一个词在所有文本中出现的频率倒数。因此,一个词在某文本中出现的越多,在其他文本中出现的越少,权重就越大,则这个词能很好地反映文本的内容。与one-hot相比,tf-idf更加准确。但是实际使用时还是因算法和使用场景而异,有时one-hot照样可以取得很好的效果。
1.3.1one-hot表示的向量实现
from sklearn.feature_extraction.text import 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"]
vectorizer = CountVectorizer(binary = Ture)
CorpusWordFrenVec = vectorizer.fit_transform(corpus).toarray()
1.3.2词频表示向量TF
from sklearn.feature_extraction.text import 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"]
vectorizer = CountVectorizer()
CorpusWordFrenVec = vectorizer.fit_transform(corpus).toarray()
1.3.3TF-IDF表示
from sklearn.feature_extraction.text import TfidfTransformer
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"]
transformer = TfidfTransformer()
tfidf = transformer.fit_transform(vectorizer.fit_transform(corpus))
1.4.向量降维。虽然前面比如采用卡方检验抽取了特征词,降低了词典体积,但是实际生成的向量维度可能仍然非常高,并且数据也非常稀疏,比如词典包含5000个词,那么文档映射后生成的向量将有5000维。此外,一些文本的主题或者说中心思想,并不能很好地通过文本中的词来表示,能真正概括这篇文本内容的词可能没有直接出现在文本中。因此,可以使用LSA(Latent Semantic Analysis,隐性语义分析)模型来分析文本潜在的主题。LSA属于概率主题模型的一种,通过奇异值分解的方法计算出文本中各个主题的概率分布,假设指定100个主题,那么通过LSA模型,文本向量就可以降到100维。由于该模型可以指定要分析的主题数目,所以可以将原始高维向量降到指定维数,大大节省存储空间及计算时间。
One hot representation用来表示词向量非常简单,这种离散表示但是却有很多问题。最大的问题是我们的词汇表一般都非常大,比如达到百万级别,这样每个词都用百万维的向量来表示简直是内存的灾难。这样的向量其实除了一个位置是1,其余的位置全部都是0,表达的效率不高,具有极大的稀疏性。
Distributed representation其基本思想是通过训练将每个词映射成 K 维实数向量(K 一般为模型中的超参数), 通过词之间的距离(比如 cosine 相似度、欧氏距离等) 来判断它们之间的语义相似度。 而word2vec 使用的就是这种 Distributed representation 的词向量表示方式。
Distributed representation可以解决One hot representaxtion的问题,它的思路是通过训练,将每个词都映射到一个较短的词向量上来。所有的这些词向量就构成了向量空间,进而可以用普通的统计学的方法来研究词与词之间的关系。这个较短的词向量维度是需要我们在训练时自己来指定。
本章节参考出处:《Deep Learning 实战之 word2vec》- 邓澍军、陆光明、夏龙
我们只要得到了词汇表里所有词对应的词向量,那么我们就可以记性下一步的任务。不过,怎么训练得到合适的较短的词向量呢?一个很常见的方法是使用神经网络语言模型。
NNLM采用的是 Distributed Representation,即每个词被表示为一个浮点向量。其模型图如下:
本小结参考出处:1)http://www.cnblogs.com/pinard/p/7243513.html
2)《Deep Learning 实战之 word2vec》- 邓澍军、陆光明、夏龙
word2vec也使用了CBOW与Skip-Gram来训练模型与得到词向量,但是并没有使用传统的DNN模型。最先优化使用的数据结构是用霍夫曼树来代替隐藏层和输出层的神经元,霍夫曼树的叶子节点起到输出层神经元的作用,叶子节点的个数即为词汇表的小大。 而内部节点则起到隐藏层神经元的作用。
霍夫曼树的建立其实并不难,过程如下:
输入:权值为(w1,w2,...wn)的n个节点
输出:对应的霍夫曼树
1)将(w1,w2,...wn)看做是有n棵树的森林,每个树仅有一个节点。
2)在森林中选择根节点权值最小的两棵树进行合并,得到一个新的树,这两颗树分布作为新树的左右子树。新树的根节点权重为左右子树的根节点权重之和。
3) 将之前的根节点权值最小的两棵树从森林删除,并把新树加入森林。
4)重复步骤2)和3)直到森林里只有一棵树为止。
word2vec一般分为CBOW(Continuous Bag-of-Words 与Skip-Gram两种模型。
CBOW模型的训练输入是某一个特征词的上下文相关的词对应的词向量,而输出就是这特定的一个词的词向量。
模型如下:
CBOW 是 Continuous Bag-of-Words Model 的缩写,是一种与前向 NNLM 类似的模型,不同点在于 CBOW 去掉了最耗时的非线性隐层且所有词共享隐层。 可以看出, CBOW 模型是预测。
1 输入层:上下文单词的onehot. {假设单词向量空间dim为V,上下文单词个数为C}
2 所有onehot分别乘以共享的输入权重矩阵W. {VN矩阵,N为自己设定的数,初始化权重矩阵W}
3 所得的向量 {因为是onehot所以为向量} 相加求平均作为隐层向量, size为1N.
4 乘以输出权重矩阵W’ {NV}
5 得到向量 {1V} 激活函数处理得到V-dim概率分布 {PS: 因为是onehot嘛,其中的每一维斗代表着一个单词}
6 概率最大的index所指示的单词为预测出的中间词(target word)与true label的onehot做比较,误差越小越好(根据误差更新权重矩阵)
所以,需要定义loss function(一般为交叉熵代价函数),采用梯度下降算法更新W和W’。训练完毕后,输入层的每个单词与矩阵W相乘得到的向量的就是我们想要的词向量(word embedding),这个矩阵(所有单词的word embedding)也叫做look up table(其实聪明的你已经看出来了,其实这个look up table就是矩阵W自身),也就是说,任何一个单词的onehot乘以这个矩阵都将得到自己的词向量。有了look up table就可以免去训练过程直接查表得到单词的词向量了。
从直观上理解,Skip-Gram是给定input word来预测上下文。
接下来我们来看看如何训练我们的神经网络。假如我们有一个句子“The dog barked at the mailman”。
首先我们选句子中间的一个词作为我们的输入词,例如我们选取“dog”作为input word;
有了input word以后,我们再定义一个叫做skip_window的参数,它代表着我们从当前input word的一侧(左边或右边)选取词的数量。如果我们设置skip_window=2,那么我们最终获得窗口中的词(包括input word在内)就是[‘The’, ‘dog’,‘barked’, ‘at’]。skip_window=2代表着选取左input word左侧2个词和右侧2个词进入我们的窗口,所以整个窗口大小span=2x2=4。另一个参数叫num_skips,它代表着我们从整个窗口中选取多少个不同的词作为我们的output word,当skip_window=2,num_skips=2时,我们将会得到两组 (input word, output word) 形式的训练数据,即 (‘dog’, ‘barked’),(‘dog’, ‘the’)。
神经网络基于这些训练数据将会输出一个概率分布,这个概率代表着我们的词典中的每个词是output word的可能性。这句话有点绕,我们来看个栗子。第二步中我们在设置skip_window和num_skips=2的情况下获得了两组训练数据。假如我们先拿一组数据 (‘dog’, ‘barked’) 来训练神经网络,那么模型通过学习这个训练样本,会告诉我们词汇表中每个单词是“barked”的概率大小。
模型的输出概率代表着到我们词典中每个词有多大可能性跟input word同时出现。举个栗子,如果我们向神经网络模型中输入一个单词“中国“,那么最终模型的输出概率中,像“英国”, ”俄罗斯“这种相关词的概率将远高于像”苹果“,”蝈蝈“非相关词的概率。因为”英国“,”俄罗斯“在文本中更大可能在”中国“的窗口中出现。我们将通过给神经网络输入文本中成对的单词来训练它完成上面所说的概率计算。
面的图中给出了一些我们的训练样本的例子。我们选定句子“The quick brown fox jumps over lazy dog”,设定我们的窗口大小为2(window_size=2),也就是说我们仅选输入词前后各两个词和输入词进行组合。下图中,蓝色代表input word,方框内代表位于窗口内的单词。Training Samples(输入, 输出)
我们的模型将会从每对单词出现的次数中习得统计结果。例如,我们的神经网络可能会得到更多类似(“中国“,”英国“)这样的训练样本对,而对于(”英国“,”蝈蝈“)这样的组合却看到的很少。因此,当我们的模型完成训练后,给定一个单词”中国“作为输入,输出的结果中”英国“或者”俄罗斯“要比”蝈蝈“被赋予更高的概率。
再次提醒,最终我们需要的是训练出来的权重矩阵。
论文:Efficient Estimation of Word Representations in Vector Space
参考出处:https://www.cnblogs.com/jiangxinyang/p/10207273.html
在python的第三方库gensim中有自带的Word2Vec函数来训练自己语料库的词向量,
gensim库的使用还需进一步的学习,先放上一个他人的实践实例。
class gensim.models.word2vec.Word2Vec(sentences=None, corpus_file=None, size=100, alpha=0.025, window=5, min_count=5, max_vocab_size=None, sample=0.001, seed=1, workers=3, min_alpha=0.0001, sg=0, hs=0, negative=5, ns_exponent=0.75, cbow_mean=1, hashfxn=, iter=5, null_word=0, trim_rule=None, sorted_vocab=1, batch_words=10000, compute_loss=False, callbacks=(), max_final_vocab=None)
1) sentences:我们要分析的语料,可以是一个列表,或者从文件中遍历读出(word2vec.LineSentence(filename) )。
2) size:词向量的维度,默认值是100。这个维度的取值一般与我们的语料的大小相关,如果是不大的语料,比如小于100M的文本语料,则使用默认值一般就可以了。如果是超大的语料,建议增大维度。
3) window:即词向量上下文最大距离,window越大,则和某一词较远的词也会产生上下文关系。默认值为5,在实际使用中,可以根据实际的需求来动态调整这个window的大小。如果是小语料则这个值可以设的更小。对于一般的语料这个值推荐在[5;10]之间。
4) sg:即我们的word2vec两个模型的选择了。如果是0, 则是CBOW模型;是1则是Skip-Gram模型;默认是0即CBOW模型。
5) hs:即我们的word2vec两个解法的选择了。如果是0, 则是Negative Sampling;是1的话并且负采样个数negative大于0, 则是Hierarchical Softmax。默认是0即Negative Sampling。
6) negative:即使用Negative Sampling时负采样的个数,默认是5。推荐在[3,10]之间。这个参数在我们的算法原理篇中标记为neg。
7) cbow_mean:仅用于CBOW在做投影的时候,为0,则算法中的xw为上下文的词向量之和,为1则为上下文的词向量的平均值。在我们的原理篇中,是按照词向量的平均值来描述的。个人比较喜欢用平均值来表示xw,默认值也是1,不推荐修改默认值。
8) min_count:需要计算词向量的最小词频。这个值可以去掉一些很生僻的低频词,默认是5。如果是小语料,可以调低这个值。
9) iter:随机梯度下降法中迭代的最大次数,默认是5。对于大语料,可以增大这个值。
10) alpha:在随机梯度下降法中迭代的初始步长。算法原理篇中标记为η,默认是0.025。
11) min_alpha: 由于算法支持在迭代的过程中逐渐减小步长,min_alpha给出了最小的迭代步。
from gensim.models.word2vec import Word2Vec
# 读取数据,用gensim中的word2vec训练词向量
file = open('sentence.txt')
sss=[]
while True:
ss=file.readline().replace('\n','').rstrip()
if ss=='':
break
s1=ss.split(" ")
sss.append(s1)
file.close()
model = Word2Vec(size=200, workers=5,sg=1) # 生成词向量为200维,考虑上下5个单词共10个单词,采用sg=1的方法也就是skip-gram
model.build_vocab(sss)
model.train(sss,total_examples = model.corpus_count,epochs = model.iter)
model.save('./data/gensim_w2v_sg0_model') # 保存模型
new_model = gensim.models.Word2Vec.load('w2v_model') # 调用模型
sim_words = new_model.most_similar(positive=['女人'])
for word,similarity in sim_words:
print(word,similarity) # 输出’女人‘相近的词语和概率
print(model['女孩']) # 输出’女孩‘的词向量
模型的保存于加载
# 训练模型,词向量的长度设置为200, 迭代次数为8,采用skip-gram模型,模型保存为bin格式
model = gensim.models.Word2Vec(sentences, size=200, sg=1, iter=8)
model.wv.save_word2vec_format("./word2Vec" + ".bin", binary=True)
# 加载bin格式的模型
wordVec = gensim.models.KeyedVectors.load_word2vec_format("word2Vec.bin", binary=True)