Facebook词向量工具FastText
FastText简介
fastText是Facebook于2016年开源的一个词向量计算和文本分类工具,在文本分类任务中,fastText(浅层网络)往往能取得和深度网络相媲美的精度,却在训练时间上比深度网络快许多数量级。在标准的多核CPU上, 能够训练10亿词级别语料库的词向量在10分钟之内,能够分类有着30万多类别的50多万句子在1分钟之内。
FastText与Word2Vec
fastText的其中的一个作者是Thomas Mikolov。也正是这个人在谷歌的时候,带领团队在2012年提出了 word2vec代替了one-hot编码,将词表示为一个低维连续嵌入,极大促进了NLP的发展。14年她去了脸书,然后提出了word2vec的改进版:fasttext。所以fastText和word2vec在结构上很相似。
相同之处:
- 图模型结构很像,都是采用 embedding 向量的形式,得到 word 的隐向量表达。
- 采用很多相似的优化方法,比如使用 Hierarchical softmax 优化训练和预测中的打分速度。
- 训练词向量时,两者都是无监督算法。输入层是 context window 内的 term。输出层对应的是每一个 term,计算某 term 的概率最大;
- 在使用层次softmax的时候,huffman 树叶子节点处是训练语料里所有词的向量。
不同之处:
- 模型的输出层:word2vec的输出层,对应的是每一个term,计算某term的概率最大;而fasttext的输出层对应的是分类的label。不过不管输出层对应的是什么内容,其对应的vector都不会被保留和使用;
- 模型的输入层:word2vec的输入层,是 context window 内的term;而fasttext 对应的整个sentence的内容,包括term,也包括 n-gram的内容;
- 两者本质的不同,体现在 Hierarchical softmax的使用。
- Wordvec的目的是得到词向量,该词向量 最终是在输入层得到,输出层对应的Hierarchical softmax也会生成一系列的向量,但最终都被抛弃,不会使用。
- fasttext则充分利用了Hierarchical softmax的分类功能,遍历分类树的所有叶节点,找到概率最大的label(一个或者N个)
FastText的优点:
- 适合大型数据+高效的训练速度:能够训练模型“在使用标准多核CPU的情况下10分钟内处理超过10亿个词汇”,特别是与深度模型对比,fastText能将训练时间由数天缩短到几秒钟。使用一个标准多核 CPU,得到了在10分钟内训练完超过10亿词汇量模型的结果。此外, fastText还能在五分钟内将50万个句子分成超过30万个类别。
- 支持多语言表达:利用其语言形态结构,fastText能够被设计用来支持包括英语、德语、西班牙语、法语以及捷克语等多种语言。它还使用了一种简单高效的纳入子字信息的方式,在用于像捷克语这样词态丰富的语言时,这种方式表现得非常好,这也证明了精心设计的字符 n-gram 特征是丰富词汇表征的重要来源。FastText的性能要比时下流行的word2vec工具明显好上不少,也比其他目前最先进的词态词汇表征要好。
- fastText专注于文本分类,在许多标准问题上实现当下最好的表现(例如文本倾向性分析或标签预测)。
- 比word2vec更考虑了相似性,比如 fastText 的词嵌入学习能够考虑 english-born 和 british-born 之间有相同的后缀,但 word2vec 却不能。
模型架构
fastText的架构和word2vec中的CBOW的架构类似,因为它们的作者Tomas Mikolov,而且确实fastText也算是word2vec所衍生出来的。
CBOW的架构

输入的是
的上下文2d个词,经过隐藏层后,输出的是
。word2vec将上下文关系转化为多分类任务,进而训练逻辑回归模型,这里的类别数量是
词库大小。通常的文本数据中,词库少则数万,多则百万,在训练中直接训练多分类逻辑回归并不现实。word2vec中提供了两种针对大规模多分类问题的优化手段,negative sampling 和 hierarchical softmax。在优化中,negative sampling 只更新少量负面类,从而减轻了计算量。hierarchical softmax 将词库表示成前缀树,从树根到叶子的路径可以表示为一系列二分类器,一次多分类计算的复杂度从
降低到了树的高度。
fastText模型架构

其中
表示一个文本中的n-gram向量,每个特征是词向量的平均值。这和前文中提到的cbow相似,cbow用上下文去预测中心词,而此处用全部的n-gram去预测指定类别。注意:此架构图没有展示词向量的训练过程。可以看到,和CBOW一样,fastText模型也只有三层:输入层、隐含层、输出层(Hierarchical Softmax),输入都是多个经向量表示的单词,输出都是一个特定的target,隐含层都是对多个词向量的叠加平均。
fastText与CBOW的不同点:
- CBOW的输入是目标单词的上下文,fastText的输入是多个单词及其n-gram特征,这些特征用来表示单个文档
- CBOW的输入单词被one-hot编码过,fastText的输入特征是被embedding过
- CBOW的输出是目标词汇,fastText的输出是文档对应的类标
值得注意的是,fastText在输入时,将单词的字符级别的n-gram向量作为额外的特征;在输出时,fastText采用了分层Softmax,大大降低了模型训练时间。
Hierarchical softmax
Softmax回归(Softmax Regression)又被称作多项逻辑回归(multinomial logistic regression),它是逻辑回归在处理多类别任务上的推广。
在逻辑回归中,我们有m个被标注的样本:
,其中
。因为类标是二元的,所以我们有
。我们的假设(hypothesis)有如下形式: ![]()
代价函数(cost function)如下:
![Rendered by QuickLaTeX.com \[J(\theta)=-[\sum_{i=1}^{m} y^{(i)} \log h_{\theta}(x^{(i)})+(1-y^{(i)}) \log (1-h_{\theta}(x^{(i)}))]\]](https://www.biaodianfu.com/wp-content/ql-cache/quicklatex.com-6363e93dd1c14fafd685b8e9eca93aad_l3.png)
在Softmax回归中,类标是大于2的,因此在我们的训练集
中,。给定一个测试输入x,我们的假设应该输出一个K维的向量,向量内每个元素的值表示x属于当前类别的概率。具体地,假设
形式如下:
![Rendered by QuickLaTeX.com \[h_{\theta}(x)=[\begin{array}{c}{P(y=1 | x ; \theta)} \\ {P(y=2 | x ; \theta)} \\ {\vdots} \\ {P(y=K | x ; \theta)}\end{array}]=\frac{1}{\sum_{j=1}^{K} e^{\theta(j)^{T} x}}[\begin{array}{c}{e^{\theta^{(1)} x} x} \\ {e^{\theta(2)^{T} x}} \\ {\vdots} \\ {e^{\theta^{(K)^{T}} x}}\end{array}]\]](https://www.biaodianfu.com/wp-content/ql-cache/quicklatex.com-773ff3e1cc5870e584be2950635d9135_l3.png)
代价函数如下:
![Rendered by QuickLaTeX.com \[J(\theta)=-[\sum_{i=1}^{m} \sum_{k=1}^{K} 1\{y^{(i)}=k\} \log \frac{e^{\theta^{(k) T} x^{(i)}}}{\sum_{j=1}^{K} e^{\theta(j) T_{x^{(i)}}}}]\]](https://www.biaodianfu.com/wp-content/ql-cache/quicklatex.com-ce366fadfbf1e2273e6ce399f1854768_l3.png)
其中
是指示函数,即 ![]()
既然我们说Softmax回归是逻辑回归的推广,那我们是否能够在代价函数上推导出它们的一致性呢?当然可以,于是:
![Rendered by QuickLaTeX.com \[\begin{aligned} J(\theta) &=-[\sum_{i=1}^{m} y^{(i)} \log h_{\theta}(x^{(i)})+(1-y^{(i)}) \log (1-h_{\theta}(x^{(i)}))] \\ &=-\sum_{i=1}^{m} \sum_{k=0}^{1} 1\{y^{(i)}=k\} \log P(y^{(i)}=k | x^{(i)} ; \theta) \\ &=-\sum_{i=1}^{m} \sum_{k=0}^{1} 1\{y^{(i)}=k\} \log \frac{e^{\theta^{(k) T} x^{(i)}}}{\sum_{j=1}^{K} e^{\theta(j) T_{x}(i)}} \end{aligned}\]](https://www.biaodianfu.com/wp-content/ql-cache/quicklatex.com-ca6ae16c0e08113dfa01659915105cc2_l3.png)
可以看到,逻辑回归是softmax回归在K=2时的特例。你可能也发现了,标准的Softmax回归中,要计算y=j时的Softmax概率:
,我们需要对所有的K个概率做归一化,这在
很大时非常耗时。于是,分层Softmax诞生了,它的基本思想是使用树的层级结构替代扁平化的标准Softmax,使得在计算
时,只需计算一条路径上的所有节点的概率值,无需在意其它的节点。
下图是一个分层Softmax示例:

树的结构是根据类标的频数构造的霍夫曼树。K个不同的类标组成所有的叶子节点,K-1个内部节点作为内部参数,从根节点到某个叶子节点经过的节点和边形成一条路径,路径长度被表示为
。于是
就可以被写成:
![Rendered by QuickLaTeX.com \[\mathrm{P}(y_{j})=\prod_{i=1}^{L(y_{j})-1} \sigma(\left \| n(y_{j}, l+1)=L C(n(y_{j}, l))\right \| \cdot \theta_{n(y_{j}, l)}^{T} X)\]](https://www.biaodianfu.com/wp-content/ql-cache/quicklatex.com-f8dbd6e6098033effbe13feb59ff3350_l3.png)
其中:
表示sigmoid函数;
表示n节点的左孩子;
是一个特殊的函数,被定义为:
;
是中间节点
的参数;X是Softmax层的输入。
上图中,高亮的节点和边是从根节点到
的路径,路径长度
,
可以被表示为:
![Rendered by QuickLaTeX.com \[\begin{aligned} \mathrm{P}(y_{2}) &=\mathrm{P}(n(y_{2}, 1), \text { left }) \cdot \mathrm{P}(n(y_{2}, 2), \text { left }) \cdot \mathrm{P}(n(y_{2}, 3), \text { right }) \\ &=\sigma(\theta_{n(y_{2}, 1)}^{T} X) \cdot \sigma(\theta_{n(y_{2}, 2)}^{T} X) \cdot \sigma(-\theta_{n(y_{2}, 3)}^{T} X) \end{aligned}\]](https://www.biaodianfu.com/wp-content/ql-cache/quicklatex.com-6b68be6d943ac617c877b97ed2bbbddf_l3.png)
于是,从根节点走到叶子节点
,实际上是在做了3次二分类的逻辑回归。通过分层的Softmax,计算复杂度一下从
降低到
。
N-gram
word2vec把语料库中的每个单词当成原子的,它会为每个单词生成一个向量。这忽略了单词内部的形态特征,比如:“book” 和“books”, “apple” 和“apples”,这两个例子中,两个单词都有较多公共字符,即它们的内部形态类似,但是在传统的word2vec中,这种单词内部形态信息因为它们被转换成不同的id丢失了。
为了克服这个问题,fastText使用了字符级别的n-grams来表示一个单词。对于单词“apple”,假设n的取值为3,则它的trigram有
“<ap”, “app”, “ppl”, “ple”, “le>”
其中,<表示前缀,>表示后缀。于是,我们可以用这些trigram来表示“apple”这个单词,进一步,我们可以用这5个trigram的向量叠加来表示“apple”的词向量。
这带来两点好处:
- 对于低频词生成的词向量效果会更好。因为它们的n-gram可以和其它词共享。
- 对于训练词库之外的单词,仍然可以构建它们的词向量。我们可以叠加它们的字符级n-gram向量。
fastText的优化
子空间量化
product quantization是一种保存数据间距离的压缩技术。PQ 用一个码本来近似数据,与传统的 keams 训练码本不同的是, PQ 将数据空间划分为 k 个子空间,并分别用 kmeans 学习子空间码本。数据的近似和重建均在子空间完成,最终拼接成结果。在 fasttext 中,子空间码本大小为 256,可以用 1 byte 表示。子空间的数量在 [2, d/2] 间取值。除了用 PQ 对数据进行量化压缩,fasttext 还提供了对分类系数的 PQ 量化选项。PQ 的优化能够在不影响分类其表现的情况下,将分类模型压缩为原大小的
。
裁剪字典内容
fasttext 提供了一个诱导式裁剪字典的算法,保证裁剪后的字典内容覆盖了所有的文章。具体而言,fasttext 存有一个保留字典,并在线处理文章,如果新的文章没有被保留字典涵盖,则从该文章中提取一个 norm 最大的词和其子串加入字典中。字典裁剪能够有效将模型的数量减少,甚至到原有的
。
FastText的使用
使用FastText训练词向量
# -*- coding: utf-8 -*-
import jieba
import os
import fasttext
def get_data():
# 清华大学的新闻分类文本数据集下载:https://thunlp.oss-cn-qingdao.aliyuncs.com/THUCNews.zip
data_dir = 'D:\\迅雷下载\\THUCNews\\THUCNews\\财经'
with open("finance_news_cut.txt", "w", encoding='utf-8') as f:
for file_name in os.listdir(data_dir):
print(file_name)
file_path = data_dir + os.sep + file_name
with open(file_path, 'r', encoding='utf-8') as fr:
text = fr.read()
seg_text = jieba.cut(text.replace("\t", " ").replace("\n", " "))
outline = " ".join(seg_text)
f.write(outline)
f.flush()
def train_model():
model = fasttext.train_unsupervised('finance_news_cut.txt')
model.save_model("news_fasttext.model.bin")
def test_model():
model = fasttext.load_model('news_fasttext.model.bin')
print(model.words)
print(model.get_word_vector("股票"))
print(model.get_nearest_neighbors('股票'))
if __name__ == "__main__":
pass
# get_data()
# train_model()
test_model() 备注:不要使用
pip install FastText进行安装,否则会出现如下报错:
AttributeError: '_FastText' object has no attribute 'get_nearest_neighbors'
安装流程:
git clone https://github.com/facebookresearch/fastText.git cd fastText pip install .
使用FastText进行文本分类
# -*- coding: utf-8 -*-
import jieba
import os
import fasttext
def get_data():
# 清华大学的新闻分类文本数据集下载:https://thunlp.oss-cn-qingdao.aliyuncs.com/THUCNews.zip
data_dir = 'D:\\迅雷下载\\THUCNews\\THUCNews'
# 生成训练数据&测试数据
with open("news_fasttext_train.txt", "w", encoding='utf-8') as train_f, open("news_fasttext_test.txt", "w",
encoding='utf-8') as test_f:
for category in os.listdir(data_dir):
print(category)
category_path = data_dir + os.sep + category
count = 0
for file_name in os.listdir(category_path):
file_path = data_dir + os.sep + category + os.sep + file_name
with open(file_path, 'r', encoding='utf-8') as fr:
count += 1
text = fr.read()
seg_text = jieba.cut(text.replace("\t", " ").replace("\n", " "))
outline = " ".join(seg_text)
outline = outline + "\t__label__" + category + "\n"
print(count)
if count < 10000:
train_f.write(outline)
train_f.flush()
continue
elif count < 20000:
test_f.write(outline)
test_f.flush()
continue
else:
break
def train_model():
model = fasttext.train_supervised("news_fasttext_train.txt", label_prefix="__label__")
model.save_model("news_fasttext.model.bin")
def test_model():
model = fasttext.load_model('news_fasttext.model.bin')
result = model.test("news_fasttext_test.txt")
print('precision:', result[1])
# fasttext只提供全部结果的p值和r值,想要统计不同分类的结果,就需要自己写代码来实现了。
labels_right = []
texts = []
with open("news_fasttext_test.txt", encoding='utf-8') as fr:
for line in fr:
line = line.rstrip()
labels_right.append(line.split("\t")[1].replace("__label__", ""))
texts.append(line.split("\t")[0])
labels_predict = [term[0] for term in model.predict(texts)[0]] # 预测输出结果为二维形式
text_labels = list(set(labels_right))
text_predict_labels = list(set(labels_predict))
A = dict.fromkeys(text_labels, 0) # 预测正确的各个类的数目
B = dict.fromkeys(text_labels, 0) # 测试数据集中各个类的数目
C = dict.fromkeys(text_predict_labels, 0) # 预测结果中各个类的数目
for i in range(0, len(labels_right)):
B[labels_right[i]] += 1
C[labels_predict[i]] += 1
if labels_right[i] == labels_predict[i].replace('__label__', ''):
A[labels_right[i]] += 1
# 计算准确率,召回率,F值
for key in B:
try:
r = float(A[key]) / float(B[key])
p = float(A[key]) / float(C['__label__' + key])
f = p * r * 2 / (p + r)
print("%s:\t p:%f\t r:%f\t f:%f" % (key, p, r, f))
except:
print("error:", key, "right:", A.get(key, 0), "real:", B.get(key, 0), "predict:", C.get(key, 0))
if __name__ == "__main__":
pass
get_data()
# train_model()
# test_model() 另外,还可以使用gensim来使用FastText,如:
from gensim.models import FastText
参考链接:
- https://fasttext.cc/
- https://github.com/facebookresearch/fastText
- https://heleifz.github.io/14732610572844.html
The post Facebook词向量工具FastText appeared first on 标点符.
Related posts: