一、简介


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个词,那么S出现的概率就应该等于,用条件概率的公式表示如下:

           (1)


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

                                                (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.假定存在一个连续平滑的概率模型。当输入一段词向量的序列,可以输出这段序列的联合概率。

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



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

这个网络的目标函数为:,其中是正则项(cross-entropy).

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

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




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

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


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


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

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


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

1.3 CBow & Skip-gram Model

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

               

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

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

           

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

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

1.4Hierarchical Softmax框架

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


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




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

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





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

这个时候一个节点被分为正类的概率是,其中是上一层的输出。被分为负类的概率就等于,表示一个节点的向量,对于叶节点就表示词向量。

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



那么,其实,有个比较明显的结论就是,这样我们就省去了归一化。

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

,   

其中

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

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

1.5Negative Sampling框架

还是主要以CBow模型为例。







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就很容易理解了。



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