词向量的表示学习方法:word2vec和fasttext详解和代码示例

一、简介

fastText是Facebook于2016年开源的一个词向量计算和文本分类工具,在学术上并没有太大创新。但是它的优点也非常明显,在文本分类任务中,fastText(浅层网络)往往能取得和深度网络相媲美的精度,却在训练时间上比深度网络快许多数量级。在标准的多核CPU上, 能够训练10亿词级别语料库的词向量在10分钟之内,能够分类有着30万多类别的50多万句子在1分钟之内。

其实fasttext和word2vec出自同一个人之手(大牛Tomas Mikolov),所以,fasttext和word2vec也有着千丝万缕的联系,本文将先为大家介绍一下word2vec的原理和简单应用,再为大家详细介绍fasttext的原理及应用。

二、word2vec

1.word2vec原理

word2vec就是一种词嵌入,说的简单一点就是将一个词语表征为一个向量,当然这肯定不是一个随意给的一个向量,它是在一定的约束条件下得到,那么它有什么神奇之处呢?显然,首先作为一个向量,它可以被计算机“理解”,并用来进行各种数值运算;其次,两个向量之间的相似度表征了对应的两个词语的语义相似度。word2vec非常的看重语言的逻辑性,也就是词语的前后关系。

1.1下面我们先考虑统计语言模型(计算一个文本序列在某种语言下出现的概率)。

如果一个句子S由t个词\omega _{1}\sim \omega _{t},那么S出现的概率就应该等于,用条件概率的公式表示如下:

P(S)=P(\omega _{1},\omega_{2},...,\omega_{t})=P(\omega_{1})P({\omega_{2}}|\omega_{1})...P(\omega_{t}|\omega _{1},\omega_{2},...,\omega_{t-1})           (1)

大家可以看到公式(1)就是t个条件概率的连乘积,这个式子计算起来确实有些难为人了,另外,这么大的参数空间也限制了这个模型在实际中应用。我们可以使用上式的简化版本——Ngram模型:

P(\omega_{t}|\omega_{1},\omega_{2},...,\omega_{t-1})\approx P(\omega_{t}|\omega_{t-n+1},...,\omega_{t-1})                                                (2)

常见的如bigaram模型(n=2)和trigram模型(n=3)。事实上,由于n的增大,模型的参数出现指数型增长,模型复杂度越来越高,所以n>3的情况是不会使用的。其次,缺乏词与词之间的联系性。例如:“西安是中国的一个省会城市,拥有悠久的历史和灿烂的文化”这句话,如果在我们的语料库中有很多类似“南京是中国的一个省会城市,拥有悠久的历史和灿烂的文化”的语料,那么即使我们没见过第一句话,也可以从“西安”和“南京”之间的相似性,推测出这句话的概率。这样的任务是Ngram无法完成的。那我们该怎么办呢?其实,在信息检索(Information Retrieval,IR)领域有个向量空间模型(Vector Space Model,VSM)的概念,在这个概念下,每个word可以使用一个连续的稠密的向量去刻画,这样我们就可以利用该向量去刻画词语之间的相似度,并且建立一个从向量到概率的平滑函数模型,使得相似的词向量可以映射到距离相近的概率空间上(这句话有些太正式了,用大白话说就是这个词向量就是概率空间上的一个点,两个词向量或者说两个点之间的距离就是两个词之间的相似度)。这个词向量也被称为word的distributed representation。

1.2Neural Network Language Model

鉴于Ngram等模型的不足,2003年,一套用神经网络建立统计语言模型的框架(Neural Network Language Model,NNLM)被提了出来,这也算是word2vec的基础原型。

NNLM模型有两个很重要的假设:1.假定语料库中的每个word多对应着一个连续的特征向量;2.假定存在一个连续平滑的概率模型。当输入一段词向量的序列,可以输出这段序列的联合概率。

值得注意的是,词向量的权重和概率模型的参数都是需要学习的。

词向量的表示学习方法:word2vec和fasttext详解和代码示例_第1张图片

上图是NNLM模型的网络结构图,其实我们可以看到,这个框架是在学习公式(2)

这个网络的目标函数为:L(\theta )=\frac{1}{T}\sum_{t}logf(\omega_{t},\omega_{t-1},...,\omega_{t-n+1})+R(\theta ),其中R(\theta)是正则项(cross-entropy).

假设我们的句子是“西安城墙是历史建筑”,拆开就是“西安”、“城墙”、“是”,“历史”,一共四个词,预测下一词是什么?

首先我们先对我们语料库中的词进行one-hot编码,例如,对“西安”、“城墙”进行one-hot编码:

\begin{pmatrix} 1\\ 0\\ ...\\ 0\\ \end{pmatrix}\begin{pmatrix} 0\\ 1\\ ...\\ 0\\ \end{pmatrix}

one-hot向量的维数就是语料库(词典)的维数V,即词典中不同的词的个数,当然,上面的编码只是一个例子,现实中‘1’的位置可能在其它的位置,只要满足不同的词对应不同的位置即可,实在不懂得可以去百度一下one-hot编码,很简单。

在该模型中有一个投影矩阵C(大小为D×V),D表示词向量的维数,其实C中的每一列就是我们要学习的词向量(刚开始,这个矩阵也是随机给个初始值)。

有了输入数据的one-hot向量和投影矩阵,那我们就可以进行embedding了,每个one-hot向量和C的乘积都可以得到一个D维的向量(其实就是将one-hot 编码对应的词的词向量从投影矩阵中取出来),图中的C({\omega _{t-n+1}})就表示一个词的词向量,我们的例子中就会嵌入4个词向量。

接下来就是一个隐藏层(全连接),激活函数一般采用tanh,最后接softmax分类器,预测一下在词典中的每个词出现的概率有多大(前面已经提到,我们是要预测下一个词是什么),因此最后输出的是一个概率分布(维数为V)。

到这里,我们就可以看到,整个神经网络的训练过程不仅依赖网络中的参数,还要依赖于投影矩阵(即所有的词的词向量组成的矩阵),所以这些参数都需要不停迭代训练出来。

但,又有一个问题,NNLM模型依然是利用前面N-1个词来预测后面的一个词,虽然NNLM模型将N提高到了5,可依旧具有很大的局限性,不够灵活。另外,NNLM的训练十分的缓慢,对于现实中上千万甚至上亿的语料库,NNLM也是无能为力的。这时候就需要word2vec了。

1.3 CBow & Skip-gram Model

下图是word2vec两种模型的网络结构图,它们都包含三层:输入层、投影层、输出层。前者是在已知当前词{\omega_{t}}的上下文(context)\omega_{t-2},\omega_{t-1},\omega_{t+1},\omega_{t+2}的前提下预测当前词{\omega_{t}};而后者恰恰相反,是在已知当前词{\omega_{t}}的情况下,预测其上下文\omega_{t-2},\omega_{t-1},\omega_{t+1},\omega_{t+2}.注意,最后输出的不是一个词的词向量,而是对词典中的所有词的一个概率预测,维数和词典中的词(互不相同的词)的个数相同,即词典的维度。CBow模型输入的是2c个词的词向量(下图中c=2,包括当前词前面c个词和后面c个词),Skip-gram模型输出的2c个概率分布。对于CBow的投影层其实就是对输入层所有向量的一个累加求和,而对于Skip-gram模型的投影层,其实是多余的,它和输入层是一样的,之所以保留是为了和CBow模型进行比较。

词向量的表示学习方法:word2vec和fasttext详解和代码示例_第2张图片               词向量的表示学习方法:word2vec和fasttext详解和代码示例_第3张图片

CBow模型和Skip-gram模型网络结构

有了前面的介绍,我想大家也已经猜到目标函数是什么了,下面两个公式分别是两个模型的目标函数:

L=\sum_{\omega\in C}logp(\omega|Context(\omega))           L=\sum_{\omega\in C}logp(Context(\omega)|\omega)

其中,C表示投影矩阵(词向量组成的矩阵),Context(\omega)表示\omega的上下文信息。

但是,仅仅有这两个模型还是不够的,无法解决计算量庞大的问题,word2vec又提出了两个框架来解决这一问题Hierarchical Softmax & Negative Sampling。

1.4Hierarchical Softmax框架

在介绍Hierarchical Softmax之前,先为大家介绍一下着框架的基础—Huffman树和Huffman编码。

例.假设2014年世界杯期间,从新浪微博中抓取了若干条与足球相关的微博,经统计,“我”、“喜欢”、“观看”、“巴西”、“足球”、“世界杯”这六个词出现的次数分别为15,8,6,5,3,1.以这6个词为叶节点,以相应词频当权值,构造一颗Huffman数。

词向量的表示学习方法:word2vec和fasttext详解和代码示例_第4张图片

Huffman编码:子节点中左节点记为‘1’,右节点记为‘0’,根节点不标记,那么所有叶节点(其实就是原有的所有节点)的编码就是从根节点到叶节点所经过的所有节点的标记的拼接,即15:0,8:111,6:110,5:101,3:1001,1:1000

首先说明一下,Hierarchical Softmax技术是应用在输出层的。

词向量的表示学习方法:word2vec和fasttext详解和代码示例_第5张图片

词向量的表示学习方法:word2vec和fasttext详解和代码示例_第6张图片

以上图中词\omega='3'为例,从根节点出发到达‘3’这个叶子节点,中间共经历了4次分支,而每次分支都可以视为进行了一次二分类,不妨假设Huaman编码为‘1’的节点定义为负类,编码为‘0’的节点为正类(word2vec里就是这样定义的,其实反过来也没问题),

这个时候一个节点被分为正类的概率是\sigma (X_{\omega}^{T}\theta)=\frac{1}{1+e^{-X_{\omega}^{T}\theta}},其中X_{\omega}是上一层的输出。被分为负类的概率就等于1-\sigma (X_{\omega}^{T}\theta)\theta表示一个节点的向量,对于叶节点就表示词向量。

对于从根节点出发到达‘3’这个叶节点所经历的4次二分类,将每次分类结果的概率写出来就是:

词向量的表示学习方法:word2vec和fasttext详解和代码示例_第7张图片

那么p('3'|Context('3'))=\prod_{j=2}^{5}p(d_{j}^{\omega}|X_{\omega},\theta_{j-1}^{\omega}),其实,有个比较明显的结论就是\sum_{\omega\in D}p(\omega|Context(\omega))=1,这样我们就省去了归一化。

这时候,CBow模型的目标函数变为

L=\sum_{\omega\in C}log\prod_{j=2}^{l^{\omega}}{[\sigma(X_{\omega}^{T}\theta_{j-1}^{\omega})]^{1-d_{j}^{\omega}}*[1-\sigma(X_{\omega}^{T}\theta_{j-1}^{\omega})]^{d_{j}^{\omega}}}=\sum_{\omega\in C}\sum_{j=2}^{l^{\omega}}{L(\omega,j)},   

其中L(\omega,j)=(1-d_{j}^{\omega})*log[\sigma(X_{\omega}^{T}\theta_{j-1}^{\omega})]+d_{j}^{\omega}*log[1-\sigma(X_{\omega}^{T}\theta_{j-1}^{\omega})]

Skip-gram模型的目标函数也对应变换过来即可。

有了新的目标函数,至于为何计算速度更快,大家可以通过反向传播求导进行验证,word2vec使用的优化方法为随机梯度上升法。

1.5Negative Sampling框架

还是主要以CBow模型为例。

词向量的表示学习方法:word2vec和fasttext详解和代码示例_第8张图片

词向量的表示学习方法:word2vec和fasttext详解和代码示例_第9张图片

词向量的表示学习方法:word2vec和fasttext详解和代码示例_第10张图片

2.word2vec的python实现

from gensim.models import word2vec

# 训练word2vec模型,生成词向量
s = word2vec.LineSentence('file.txt')     #已经通过jieba分词分号了词,词与词之间空格隔开。
model = word2vec.Word2Vec(s,size=20,window=5,min_count=5,workers=4)#size表示词向量的维度, 
                                                                   # windows=2*c+1
model.save('word2vec.model')               #保存模型
model.save_word2vec_format('word2vec.vector', binary=False)
import gensim

model=gensim.model.Word2Vec.load_word2vec_format("wiki.en.text.vector", binary=False)

model.most_simlar('word')               #找出和'word'最相似的top_n个词及对应的相似度,个数可以 
                                        #通过参数topn设置
model.similarity('word_1','word_2')     #计算两个词的相似度

三、fastText

1.fastText原理

有了前面的铺垫,fastText就很容易理解了。

词向量的表示学习方法:word2vec和fasttext详解和代码示例_第11张图片

fastText模型输入一个词的序列,输出这个词序列属于不同类别的概率;

序列中的词和词组成特征向量,特征向量通过线性变换映射到中间层,中间层再映射到标签;

fastText再预测标签时使用了非线性激活函数,但在中间层不使用非线性激活函数。

fastText模型架构和word2vec中的CBow很类似。不同之处在于,fastText预测标签,而CBow模型预测中间词。

对于大量类别的数据集,fastText也会使用Hierarchical Softmax框架。

既然fastText和word2vec中的CBow神相似,那么它当然也就可以用来进行词向量的训练。

2.fastText的python实现

2.1 fastText实现文本分类

# -*- coding:utf-8 -*-
import pandas as pd
import random
import fasttext
import jieba
from sklearn.model_selection import train_test_split

cate_dic = {'technology': 1, 'car': 2, 'entertainment': 3, 'military': 4, 'sports': 5}
"""
函数说明:加载数据
"""
def loadData():


    #利用pandas把数据读进来
    df_technology = pd.read_csv("./data/technology_news.csv",encoding ="utf-8")
    df_technology=df_technology.dropna()    #去空行处理

    df_car = pd.read_csv("./data/car_news.csv",encoding ="utf-8")
    df_car=df_car.dropna()

    df_entertainment = pd.read_csv("./data/entertainment_news.csv",encoding ="utf-8")
    df_entertainment=df_entertainment.dropna()

    df_military = pd.read_csv("./data/military_news.csv",encoding ="utf-8")
    df_military=df_military.dropna()

    df_sports = pd.read_csv("./data/sports_news.csv",encoding ="utf-8")
    df_sports=df_sports.dropna()

    technology=df_technology.content.values.tolist()[1000:21000]
    car=df_car.content.values.tolist()[1000:21000]
    entertainment=df_entertainment.content.values.tolist()[:20000]
    military=df_military.content.values.tolist()[:20000]
    sports=df_sports.content.values.tolist()[:20000]

    return technology,car,entertainment,military,sports

"""
函数说明:停用词
参数说明:
    datapath:停用词路径
返回值:
    stopwords:停用词
"""
def getStopWords(datapath):
    stopwords=pd.read_csv(datapath,index_col=False,quoting=3,sep="\t",names=['stopword'], encoding='utf-8')
    stopwords=stopwords["stopword"].values
    return stopwords

"""
函数说明:去停用词
参数:
    content_line:文本数据
    sentences:存储的数据
    category:文本类别
"""
def preprocess_text(content_line,sentences,category,stopwords):
    for line in content_line:
        try:
            segs=jieba.lcut(line)    #利用结巴分词进行中文分词
            segs=filter(lambda x:len(x)>1,segs)    #去掉长度小于1的词
            segs=filter(lambda x:x not in stopwords,segs)    #去掉停用词
            sentences.append("__lable__"+str(category)+" , "+" ".join(segs))    #把当前的文本和对应的类别拼接起来,组合成fasttext的文本格式
        except Exception as e:
            print (line)
            continue

"""
函数说明:把处理好的写入到文件中,备用
参数说明:

"""
def writeData(sentences,fileName):
    print("writing data to fasttext format...")
    out=open(fileName,'w')
    for sentence in sentences:
        out.write(sentence.encode('utf8')+"\n")
    print("done!")

"""
函数说明:数据处理
"""
def preprocessData(stopwords,saveDataFile):
    technology,car,entertainment,military,sports=loadData()    

    #去停用词,生成数据集
    sentences=[]
    preprocess_text(technology,sentences,cate_dic["technology"],stopwords)
    preprocess_text(car,sentences,cate_dic["car"],stopwords)
    preprocess_text(entertainment,sentences,cate_dic["entertainment"],stopwords)
    preprocess_text(military,sentences,cate_dic["military"],stopwords)
    preprocess_text(sports,sentences,cate_dic["sports"],stopwords)

    random.shuffle(sentences)    #做乱序处理,使得同类别的样本不至于扎堆

    writeData(sentences,saveDataFile)

if __name__=="__main__":
    stopwordsFile=r"./data/stopwords.txt"
    stopwords=getStopWords(stopwordsFile)
    saveDataFile=r'train_data.txt'
    preprocessData(stopwords,saveDataFile)
    #fasttext.supervised():有监督的学习
    classifier=fasttext.supervised(saveDataFile,'classifier.model',lable_prefix='__lable__')
    result = classifier.test(saveDataFile)
    print("P@1:",result.precision)    #准确率
    print("R@2:",result.recall)    #召回率
    print("Number of examples:",result.nexamples)    #预测错的例子

    #实际预测
    lable_to_cate={1:'technology'.1:'car',3:'entertainment',4:'military',5:'sports'}

    texts=['中新网 日电 2018 预赛 亚洲区 强赛 中国队 韩国队 较量 比赛 上半场 分钟 主场 作战 中国队 率先 打破 场上 僵局 利用 角球 机会 大宝 前点 攻门 得手 中国队 领先']
    lables=classifier.predict(texts)
    print(lables)
    print(lable_to_cate[int(lables[0][0])])

    #还可以得到类别+概率
    lables=classifier.predict_proba(texts)
    print(lables)

    #还可以得到前k个类别
    lables=classifier.predict(texts,k=3)
    print(lables)

    #还可以得到前k个类别+概率
    lables=classifier.predict_proba(texts,k=3)
    print(lables)
--------------------- 
作者:john_bh 
代码来源:CSDN 
原文:https://blog.csdn.net/john_bh/article/details/79268850 

2.2 fastText训练词向量

# -*- coding:utf-8 -*-
import pandas as pd
import random
import fasttext
import jieba
from sklearn.model_selection import train_test_split

cate_dic = {'technology': 1, 'car': 2, 'entertainment': 3, 'military': 4, 'sports': 5}
"""
函数说明:加载数据
"""
def loadData():


    #利用pandas把数据读进来
    df_technology = pd.read_csv("./data/technology_news.csv",encoding ="utf-8")
    df_technology=df_technology.dropna()    #去空行处理

    df_car = pd.read_csv("./data/car_news.csv",encoding ="utf-8")
    df_car=df_car.dropna()

    df_entertainment = pd.read_csv("./data/entertainment_news.csv",encoding ="utf-8")
    df_entertainment=df_entertainment.dropna()

    df_military = pd.read_csv("./data/military_news.csv",encoding ="utf-8")
    df_military=df_military.dropna()

    df_sports = pd.read_csv("./data/sports_news.csv",encoding ="utf-8")
    df_sports=df_sports.dropna()

    technology=df_technology.content.values.tolist()[1000:21000]
    car=df_car.content.values.tolist()[1000:21000]
    entertainment=df_entertainment.content.values.tolist()[:20000]
    military=df_military.content.values.tolist()[:20000]
    sports=df_sports.content.values.tolist()[:20000]

    return technology,car,entertainment,military,sports

"""
函数说明:停用词
参数说明:
    datapath:停用词路径
返回值:
    stopwords:停用词
"""
def getStopWords(datapath):
    stopwords=pd.read_csv(datapath,index_col=False,quoting=3,sep="\t",names=['stopword'], encoding='utf-8')
    stopwords=stopwords["stopword"].values
    return stopwords

"""
函数说明:去停用词
参数:
    content_line:文本数据
    sentences:存储的数据
    category:文本类别
"""
def preprocess_text(content_line,sentences,stopwords):
    for line in content_line:
        try:
            segs=jieba.lcut(line)    #利用结巴分词进行中文分词
            segs=filter(lambda x:len(x)>1,segs)    #去掉长度小于1的词
            segs=filter(lambda x:x not in stopwords,segs)    #去掉停用词
            sentences.append(" ".join(segs))
        except Exception as e:
            print (line)
            continue

"""
函数说明:把处理好的写入到文件中,备用
参数说明:

"""
def writeData(sentences,fileName):
    print("writing data to fasttext format...")
    out=open(fileName,'w')
    for sentence in sentences:
        out.write(sentence.encode('utf8')+"\n")
    print("done!")

"""
函数说明:数据处理
"""
def preprocessData(stopwords,saveDataFile):
    technology,car,entertainment,military,sports=loadData()    

    #去停用词,生成数据集
    sentences=[]
    preprocess_text(technology,sentences,stopwords)
    preprocess_text(car,sentences,stopwords)
    preprocess_text(entertainment,sentences,stopwords)
    preprocess_text(military,sentences,stopwords)
    preprocess_text(sports,sentences,stopwords)

    random.shuffle(sentences)    #做乱序处理,使得同类别的样本不至于扎堆

    writeData(sentences,saveDataFile)


if __name__=="__main__":
    stopwordsFile=r"./data/stopwords.txt"
    stopwords=getStopWords(stopwordsFile)
    saveDataFile=r'unsupervised_train_data.txt'
    preprocessData(stopwords,saveDataFile)

    #fasttext.load_model:不管是有监督还是无监督的,都是载入一个模型
    #fasttext.skipgram(),fasttext.cbow()都是无监督的,用来训练词向量的

    model=fasttext.skipgram('unsupervised_train_data.txt','model')
    print(model.words)    #打印词向量

    #cbow model
    model=fasttext.cbow('unsupervised_train_data.txt','model')
    print(model.words)    #打印词向量
--------------------- 
作者:john_bh 
代码来源:CSDN 
原文:https://blog.csdn.net/john_bh/article/details/79268850 

 

你可能感兴趣的:(词向量的表示学习方法:word2vec和fasttext详解和代码示例)