fastText是Facebook于2016年开源的一个词向量计算和文本分类工具,在学术上并没有太大创新。但是它的优点也非常明显,在文本分类任务中,fastText(浅层网络)往往能取得和深度网络相媲美的精度,却在训练时间上比深度网络快许多数量级。在标准的多核CPU上, 能够训练10亿词级别语料库的词向量在10分钟之内,能够分类有着30万多类别的50多万句子在1分钟之内。
其实fasttext和word2vec出自同一个人之手(大牛Tomas Mikolov),所以,fasttext和word2vec也有着千丝万缕的联系,本文将先为大家介绍一下word2vec的原理和简单应用,再为大家详细介绍fasttext的原理及应用。
word2vec就是一种词嵌入,说的简单一点就是将一个词语表征为一个向量,当然这肯定不是一个随意给的一个向量,它是在一定的约束条件下得到,那么它有什么神奇之处呢?显然,首先作为一个向量,它可以被计算机“理解”,并用来进行各种数值运算;其次,两个向量之间的相似度表征了对应的两个词语的语义相似度。word2vec非常的看重语言的逻辑性,也就是词语的前后关系。
如果一个句子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。
鉴于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了。
下图是word2vec两种模型的网络结构图,它们都包含三层:输入层、投影层、输出层。前者是在已知当前词的上下文(context)的前提下预测当前词;而后者恰恰相反,是在已知当前词的情况下,预测其上下文.注意,最后输出的不是一个词的词向量,而是对词典中的所有词的一个概率预测,维数和词典中的词(互不相同的词)的个数相同,即词典的维度。CBow模型输入的是2c个词的词向量(下图中c=2,包括当前词前面c个词和后面c个词),Skip-gram模型输出的2c个概率分布。对于CBow的投影层其实就是对输入层所有向量的一个累加求和,而对于Skip-gram模型的投影层,其实是多余的,它和输入层是一样的,之所以保留是为了和CBow模型进行比较。
CBow模型和Skip-gram模型网络结构
有了前面的介绍,我想大家也已经猜到目标函数是什么了,下面两个公式分别是两个模型的目标函数:
其中,C表示投影矩阵(词向量组成的矩阵),表示的上下文信息。
但是,仅仅有这两个模型还是不够的,无法解决计算量庞大的问题,word2vec又提出了两个框架来解决这一问题Hierarchical Softmax & Negative Sampling。
在介绍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使用的优化方法为随机梯度上升法。
还是主要以CBow模型为例。
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就很容易理解了。
fastText模型输入一个词的序列,输出这个词序列属于不同类别的概率;
序列中的词和词组成特征向量,特征向量通过线性变换映射到中间层,中间层再映射到标签;
fastText再预测标签时使用了非线性激活函数,但在中间层不使用非线性激活函数。
fastText模型架构和word2vec中的CBow很类似。不同之处在于,fastText预测标签,而CBow模型预测中间词。
对于大量类别的数据集,fastText也会使用Hierarchical Softmax框架。
既然fastText和word2vec中的CBow神相似,那么它当然也就可以用来进行词向量的训练。
# -*- 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