word2vec思想
word2vec的核心是神经网络,采用 CBOW(Continuous Bag-Of-Words,即连续的词袋模型)和 Skip-Gram 两种模型,将词语映像到同一坐标系,输出为数值型向量的方法。简而言之,就是将人类才可以看懂的文字,转换为机器也可以识别、操作、处理的数值,将一串文字转化为一个数值型向量的过程。
Word2vec的产生是一个必然的过程,随着人类对非结构化数据(文字、语音、图像等)分析的需求,尤其是大量文本类数据的分析,必然需要一些让计算机“理解”文字的方法,最直接有效的自然就是将文字转化为数字;换言之,将全部的文本映射到数值空间中就是word2vec做的事情。这里需要引入一个概念——语言模型,我们不会在此深入的去探索语言模型,只是简单向读者介绍这个概念。
语言最大的特征就是上下文的联系,比如中文当中,两个特定的字会组成一个词语,若干词语和字组成句子,多个句子组成文章,如果文章中的全部文字都随机的打乱顺序排列,那么我想哪怕正在读文章的你是中文系的高材生,要完全看懂这篇文章也要费九牛二虎之力,请看下面这两段文字:
“自然语言处理是计算机科学领域与人工智能领域中的一个重要方向。它研究能实现人与计算机之间用自然语言进行有效通信的各种理论和方法。自然语言处理是一门融语言学、计算机科学、数学于一体的科学。因此,这一领域的研究将涉及自然语言,即人们日常使用的语言,所以它与语言学的研究有着密切的联系,但又有重要的区别。”
“与言将学处处方联及数言理语言信理各、语区工计系别这之和论切领所因人使中间究密的它有然的的与涉有自的要一言算语要效现体进的机融,语学究科科的计然行的学、但实又研是然。与重门能,究然常语域们语科是。自通人种日。言用领域机领域一智着机一人有即言用以言于能研方学的学它自重算向。算学自,计法此研个理一,语”
好吧,看到这里你可能要骂人了,第二段是人话么?答案是,第二段话就
是第一段话,只是对第一段话随机打乱了顺序,这个不倒150字的段落,也许你用30分钟一个小时可以将它还原成“人话”,如果是长篇大论的呢?这里就看出了语言的逻辑性,词语前后关系的重要性。语言模型就是在做这样的事情,考察一个句子出现的可能性(也就是概率)。如果一个句子S由n个词w1~wn,那么S出现的概率就应该等于P(w1,w2,…,wn),用条件概率的公式即得到共识①如下:
P(S)=P(w1,w2,…,wn)=P(w1)P(w2│w1)…P(wn|w1,w2,…,w(n−1))
不懂这个公式丝毫不影响后面的学习,这个公式翻译成白话就是:词语wn出现的概率依赖于它前面n−1个词。当n很大时,P(wn│w1,w2,…,w(n−1))的计算是非常麻烦甚至无法估算,于是产生了一个叫做马尔科夫假设的概念并由此得到“二元模型”。马尔科夫假设的意思是“任意一个词w_k只与它前面的词即w(k−1)有关”。那么这样,公式①就可以写作下面的公式②的形式:
P(S)=P(w1)P(w2│w1)…P(wn|w(n−1))
语言模型正是为了考察句子,或者说由词a到词b存在在句子中的概率而存在的。如果将我们的语言模型即为f,那么word2vec就是去训练这个f,不关注这个模型是否完美,而是要获取模型产生的参数,对于word2vec,就是神经网络的权重参数,将这些参数作为句子S的替代,由这些参数组成的向量,就是我们所谓的“词向量”。这也就是word2vec的输出。
word2vec从大量文本语料中以无监督的方式学习语义信息,即通过一个嵌入空间使得语义上相似的单词在该空间内距离很近。比如“机器”和“机械”意思很相近,而“机器”和“猴子”的意思相差就很远了,那么由word2vec构建的这个数值空间中,“机器”和“机械”的距离较“机器”和“猴子”的距离而言是要近很多的。
Skip-gram 和 CBOW
不要被这两个看起来高大上的词吓到,其实很简单。由word2vec思想,即考察前后文的关联性产生了这两个模型:Skip-gram是通过一个词a去预测它周围的上下文;而CBOW相反,是通过上下文来预测其间的词。CBOW一般用于数据,而Skip-Gram通常用在数据量较大的情况。
image
图1比较直接的展示了比较常规的CBOW和Skip-gram,图1左侧的CBOW给出了文本中一个词语wt左右各两个词,输出为wt。然后通过单隐藏层的神经网络去输出文本中每个词按照这个输入数据,输出为wt的概率。右侧的Skip-gram相反,用一个输入词wt去预测左右两个词语,同样输出概率值。
前文说过,word2vec输出是神经网络的权重值,用这些值组成词向量,它的输入就是我们文本中的词(将文本按一定颗粒度切分为词语和字),但是输入进计算机的文字依然是无法被计算机识别啊?该怎么进入这个过程呢?答案就是——one hot encoder。one hot encoder就是一个只含有1个1,其他都为0的值构成的向量。如果全世界的词一共有N个,为了简单起见,假设N是3,这3个词分别是“我”、“是”、“帅哥”(别骂我不要脸~~~),那么很显然,“我”就可以被编码为(1,0,0),“是”编码为(0,1,0),“帅哥”编码为(0,0,1)。这样的数值向量是完全可以被计算机识别和处理的。
言归正传,我们先来看一看Skip-gram模型。我们的输入已经明确了,再明确一下我们的输出:Skip-gram是一个单隐藏层的神经网络结构,那么我们要找的词向量其实就是输入层到第一个隐藏层的权重(也就是图1中Project层的权重值)。
这里,引入“窗口”和“skip-num”的概念,举个例子,比如有这么一句话:“中秋月亮比往常圆”,“中秋”、“月亮”、“比”、“往常”、“圆”作为6个独立的词,如果我们的输入是“月亮”,窗口设为2,skip-num也是2,那么我们会得到两组输入-输出:(月亮,中秋)和(月亮,比),假如先使用(月亮,中秋)作为神经网络的输入和输出进行训练,那么神经网络会输出整个文本中每个词作为“月亮”的输出的概率,也就是给定这组输入输出数据时,输入是“月亮”,输出结果是“中秋”的概率,即文本中每个词与输入值“月亮”相邻出现的概率(当然,这个例子没用对文本进行one hot encoder,是为了方便解释和理解,读着可以自行把它们假想为0-1向量,不想也没关系,小编觉得这样看得见摸得着不抽象的例子才能让大家都看懂)。
现在假设我们的Skip-gram是一个最简单的情况,用一个输入词去预测它相邻的一个词,如图2和图3所示(图3是图2的展开、细化模式)。那么请接着看图4,如果我们的词表中有10000个词,那么每个词进行one-hot编码后,就会是一个10000维的向量,其中9999个都是0,只有一个是1。从输入层到隐藏层没有使用激活函数,但在输出层中使用了softmax(不用怕,这个东西很简单,后面会介绍的,目前你只要把它简单理解成为类似逻辑回归中的logstic变换即可)。
image
image
输出层理所当然也是10000维的向量,每个值就是按照输入的这个词,可以得到自己本身的概率,这个10000维的向量其实就是一个概率分布。
我们关注的点在隐藏层,如果我们想用将每个词表示为一个维度为100的向量,即每个词有100个特征,那么隐藏层就是一个10000行×100列的矩阵(也就是隐藏层有100个结点),这个10000×100的矩阵就是我们最后想要的结果,它会将10000个词中的每一个表示为一个100维的向量,有木有发现,10000维的词瞬间被降维到了100维,当然这个权重矩阵中的值就不是0-1了。输入是一个1×10000的向量,隐藏层是一个10000×100的矩阵,这两个矩阵做乘法运算看似需要耗费很多的计算资源,其实并没有,计算机根本没有在运算(也没有在偷懒哈),它只是做了一个“查表”的工作,也就是对权重矩阵做了一个“提取”的动作,只提识别1×10000的向量中那个不为0的值对应的索引,并且从10000×100的矩阵中提取该索引所对应的行。简单展示如下:
image
最终得到这个1×100的向量。在输出层通过softmax进行变换,使输出层每个结点将会输出一个0-1之间的值(概率),这些所有输出层结点的概率之和为1。
CBOW是同样的道理(如果按照最简单的情况,Skip-gram用一个输入词预测它后面的词,CBOW用一个输出词去预测它前面的一个词,其实是一模一样的套路)。
一点补充:理论部分的最后一点就是对前文的softmax进行一点简单的说明:softmax函数如下所示,zj是原始向量的某一个值,σ(z)j是它变换后的值,分母就是一个正则项,是对于每个z对应的e的z次幂求和,这样的变换会使得σ(z)j是一个属于(0,1)范围内的实数,也就是word2vec的神经网络输出层所输出的概率值。
image
Python实现word2vec
好了,到此为止,word2vec的原理相比读者已经了如指掌了,是不是有些蠢蠢欲动,摩拳擦掌了?那么我们用一个简单的小例子来实现一下word2vec吧请读者自行搭建开发环境,小编使用的是Python3+加JupyterNotebook的环境,当然,读者可以使用Pycharm、Spyder等任何IDE进行练习。请安装好jieba和gensim。在命令行使用pip install jieba和pip install genism即可安装。
好了,小编我选取的文本是小说《神雕侠侣》,其实网上有很多开源的语料,不过比较大,这里为了方便大家练习,故找了一个内容比较多且数据比较小的文本(当然,语料越充实,效果会越好),这个语料大家可以去网上随意搜索。语料如图5所示。
image
import jieba
novel = open('E:/神雕侠侣.txt','r')
line = novel.readline()
novel_segmented = open('E:/神雕侠侣_segmented.txt','w')
while line:
cutword = jieba.cut(line,cut_all=False)
seg = ' '.join(cutword).replace(',','').replace('。','').replace('“','').replace('”','').replace(':','').replace('…','')\
.replace('!','').replace('?','').replace('~','').replace('(','').replace(')','').replace('、','').replace(';','')
print(seg,file=novel_segmented)
novel.close()
novel_segmented.close()
首先,加载jieba库,对文本进行分词,以行为单位逐行读入文本,对每行(line)进行分词,cut_all=False为精确模式(默认情况也是精确模式),然后将文本中的标点符号替换为空,即去除标点符号,然后将分词结果存入novel_segmented中。
分词后的部分结果如图6所示。
image
from gensim.models import word2vec
# 训练word2vec模型,生成词向量
s = word2vec.LineSentence('E:/神雕侠侣_segmented.txt')
model = word2vec.Word2Vec(s,size=20,window=5,min_count=5,workers=4)
model.save('E:result.txt')
接下来,加载gensim.models的word2vec,训练我们的模型,并生成词向量,将模型保存。我们的模型就是上面代码中的model啦~~~已经接近成功了!其中s使我们传入的分词后的文本,size就是前文中所说的词向量的维度,window即前文中引入的“窗口”概念,min_count是一个阈值,如果词语的频数少于这个阈值,将会从词表中删除或者说忽略该词(默认也是5),workers是控制并行数的参数,如果没有特殊需求,使用默认值一般就可以得到非常不错的效果。
好了,模型已经诞生了!那么来看看我们的模型可以带给我们哪些信息吧。首先,我们会有词表中每个词语的词向量表示,比如我的偶像杨过大侠,来看看“杨过”的词向量吧,图7中给出了一个20维(维度即size的值)的向量,这就是“杨过”的词向量。
image
前文中提到过,word2vec表示的词语的词向量有这样的特征:经常一起出现的词,其词向量的相似度会高,那么我们来看看图8给出的杨过和一些主角的相似度吧!果然,杨过小龙女永远是珍爱啊!!!
image
网友评论