什么是 fastText?
fastText是一个用于高效学习词表示和句子分类的库。
Requirements
fastText可以在现代的Mac OS和Linux发行版上构建。由于它使用了C++11特性,因此需要一个具有良好C++11支持的编译器。这些包括:
(gcc-4.6.3 or newer) or (clang-3.3 or newer)
编译是使用Makefile进行的,所以你需要有一个可用的make。对于词相似度评估脚本,你需要:
python 2.6 or newer
numpy & scipy
Text classification
文本分类是许多应用的核心问题,如垃圾邮件检测、情感分析或智能回复。在本教程中,我们将描述如何使用fastText工具构建一个文本分类器。
1.什么是 text classification?
文本分类的目标是将文档(如电子邮件、帖子、短信、产品评论等)分配到一个或多个类别中。这些类别可以是评论分数、垃圾邮件与非垃圾邮件,或者文档输入的语言。如今,构建此类分类器的占主导地位的方法是机器学习,即从示例中学习分类规则。为了构建这样的分类器,我们需要标记数据,这些数据由文档及其相应的类别(或标签)组成。
例如,我们构建一个分类器,它能够自动将关于烹饪的stackexchange问题分类到几个可能的标签之一,如锅、碗或烘焙。
1. Installing fastText
本教程的第一步是安装和构建fastText。它只需要一个对c++11支持良好的c++编译器。
让我们从下载最新版本开始:
$ wget https://github.com/facebookresearch/fastText/archive/v0.9.2.zip
$ unzip fastText-0.9.2.zip
这里如果下载有问题可以使用我下载好的包,存储在csdn资源中, 这里
移动到fastText目录并构建它
$ cd fastText-0.9.2
# for command line tool :
$ make
make
c++ -pthread -std=c++11 -march=native -O3 -funroll-loops -DNDEBUG -c src/args.cc
c++ -pthread -std=c++11 -march=native -O3 -funroll-loops -DNDEBUG -c src/autotune.cc
c++ -pthread -std=c++11 -march=native -O3 -funroll-loops -DNDEBUG -c src/matrix.cc
c++ -pthread -std=c++11 -march=native -O3 -funroll-loops -DNDEBUG -c src/dictionary.cc
c++ -pthread -std=c++11 -march=native -O3 -funroll-loops -DNDEBUG -c src/loss.cc
c++ -pthread -std=c++11 -march=native -O3 -funroll-loops -DNDEBUG -c src/productquantizer.cc
c++ -pthread -std=c++11 -march=native -O3 -funroll-loops -DNDEBUG -c src/densematrix.cc
c++ -pthread -std=c++11 -march=native -O3 -funroll-loops -DNDEBUG -c src/quantmatrix.cc
c++ -pthread -std=c++11 -march=native -O3 -funroll-loops -DNDEBUG -c src/vector.cc
c++ -pthread -std=c++11 -march=native -O3 -funroll-loops -DNDEBUG -c src/model.cc
c++ -pthread -std=c++11 -march=native -O3 -funroll-loops -DNDEBUG -c src/utils.cc
c++ -pthread -std=c++11 -march=native -O3 -funroll-loops -DNDEBUG -c src/meter.cc
c++ -pthread -std=c++11 -march=native -O3 -funroll-loops -DNDEBUG -c src/fasttext.cc
c++ -pthread -std=c++11 -march=native -O3 -funroll-loops -DNDEBUG args.o autotune.o matrix.o dictionary.o loss.o productquantizer.o densematrix.o quantmatrix.o vector.o model.o utils.o meter.o fasttext.o src/main.cc -o fasttext
# for python bindings :
$ pip install .
/software_work/fastText-0.9.2
Preparing metadata (setup.py) ... done
Collecting pybind11>=2.2 (from fasttext==0.9.2)
Using cached pybind11-2.13.6-py3-none-any.whl.metadata (9.5 kB)
Requirement already satisfied: setuptools>=0.7.0 in /opt/anaconda3/envs/ollama_llamaindex_env/lib/python3.9/site-packages (from fasttext==0.9.2) (75.1.0)
Requirement already satisfied: numpy in /opt/anaconda3/envs/ollama_llamaindex_env/lib/python3.9/site-packages (from fasttext==0.9.2) (1.26.4)
Using cached pybind11-2.13.6-py3-none-any.whl (243 kB)
Building wheels for collected packages: fasttext
Building wheel for fasttext (setup.py) ... done
Created wheel for fasttext: filename=fasttext-0.9.2-cp39-cp39-macosx_14_0_arm64.whl size=282107 sha256=1f534a1137278a8c3c1d4462625b32774e68774d9d1b3738c59187c6ad06ed79
Stored in directory: /Library/Caches/pip/wheels/c0/08/8d/0ba724ecdf97554e67d943ab13b3071f21b620a027bcaace75
Successfully built fasttext
Installing collected packages: pybind11, fasttext
Successfully installed fasttext-0.9.2 pybind11-2.13.6
调用帮助函数将显示库的高级文档:
>>> import fasttext
>>> help(fasttext.FastText)
在fasttext模块中对fasttext.FastText的帮助:
NAME
fasttext.FastText
DESCRIPTION
# Copyright (c) 2017-present, Facebook, Inc.
# All rights reserved.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
FUNCTIONS
load_model(path)
加载给定文件路径的模型,并返回一个模型对象。
read_args(arg_list, arg_dict, arg_names, default_values)
tokenize(text)
给定一段文本字符串,对其进行分词并返回一个令牌列表。
train_supervised(*kargs, **kwargs)
训练一个监督模型并返回一个模型对象。
输入必须是文件路径。输入文本不需要按照tokenize函数进行分词,但它必须经过预处理并编码为UTF-8。你可能需要参考标准的预处理脚本,例如在这里提到的tokenizer.perl:http://www.statmt.org/wmt07/baseline.html
输入文件必须每行至少包含一个标签。例如,可以参考fastText仓库中的一部分示例数据集,例如由classification-example.sh脚本获取的数据集。
train_unsupervised(*kargs, **kwargs)
训练一个无监督模型并返回一个模型对象。
输入必须是文件路径。输入文本不需要按照tokenize函数进行分词,但它必须经过预处理并编码为UTF-8。你可能需要参考标准的预处理脚本,例如在这里提到的tokenizer.perl:http://www.statmt.org/wmt07/baseline.html
输入字段不得包含任何标签或使用指定的标签前缀,除非那些词被忽略是可以接受的。例如,可以参考fastText仓库中的一部分示例脚本word-vector-example.sh获取的数据集。
在本教程中,我们主要使用train_supervised函数,它返回一个模型对象,并在这个对象上调用test和predict方法。这对应于学习(和使用)文本分类器。要了解fastText的其他功能,请参阅关于学习词向量的教程。
2.获取和准备数据
如引言中所述,我们需要标记数据来训练我们的监督分类器。在本教程中,我们感兴趣的是构建一个分类器,自动识别关于烹饪的Stackexchange问题的主题。让我们从Stackexchange的烹饪部分下载问题示例及其相关的标签:
>> wget https://dl.fbaipublicfiles.com/fasttext/data/cooking.stackexchange.tar.gz && tar xvzf cooking.stackexchange.tar.gz
>> head cooking.stackexchange.txt
__label__sauce __label__cheese How much does potato starch affect a cheese sauce recipe?
__label__food-safety __label__acidity Dangerous pathogens capable of growing in acidic environments
__label__cast-iron __label__stove How do I cover up the white spots on my cast iron stove?
__label__restaurant Michelin Three Star Restaurant; but if the chef is not there
__label__knife-skills __label__dicing Without knife skills, how can I quickly and accurately dice vegetables?
__label__storage-method __label__equipment __label__bread What's the purpose of a bread box?
__label__baking __label__food-safety __label__substitutions __label__peanuts how to seperate peanut oil from roasted peanuts at home?
__label__chocolate American equivalent for British chocolate terms
__label__baking __label__oven __label__convection Fan bake vs bake
__label__sauce __label__storage-lifetime __label__acidity __label__mayonnaise Regulation and balancing of readymade packed mayonnaise and other sauces
文本文件的每一行都包含一个标签列表,后跟相应的文档。所有标签都以__label__前缀开头,这就是fastText识别什么是标签什么是单词的方式。然后,模型被训练来预测给定文档中的单词的标签。
在训练我们的第一个分类器之前,我们需要将数据分为训练集和验证集。我们将使用验证集来评估学习到的分类器在新的数据上的表现如何。
wc cooking.stackexchange.txt
15404 169582 1401900 cooking.stackexchange.txt
我们的完整数据集包含15404个示例。让我们将其分为一个包含12404个示例的训练集和一个包含3000个示例的验证集:
>> head -n 12404 cooking.stackexchange.txt > cooking.train
>> tail -n 3000 cooking.stackexchange.txt > cooking.valid
3.训练一个分类器
import fasttext
model = fasttext.train_supervised(input="cooking.train")
Read 0M words
Number of words: 14598
Number of labels: 734
Progress: 100.0% words/sec/thread: 75109 lr: 0.000000 loss: 10.708354 eta: 0h0m
输入参数指示包含训练示例的文件。我们现在可以使用模型变量来访问有关训练模型的信息。我们还可以调用save_model将其保存为文件,并稍后使用load_model函数加载它。
model.save_model("model_cooking.bin")
现在我们可以测试一下训练好的分类器
model.predict("Which baking dish is best to bake a banana bread ?")
((u'__label__baking',), array([0.15613931]))
预测的标签是baking,这与这个问题非常吻合。现在让我们尝试第二个例子:
model.predict("Why not put knives in the dishwasher?")
((u'__label__food-safety',), array([0.08686075]))
模型预测的标签是food-safety,这与问题不相关。在某种程度上,模型似乎在简单示例上失败了。
为了更好地了解其质量,让我们在验证数据上测试它,运行:
model.test("cooking.valid")
(3000L, 0.124, 0.0541)
输出是样本数量(这里3000),精确度为1(0.124)和召回率为1(0.0541)。
我们还可以计算精确度为5和召回率为5,使用:
model.test("cooking.valid", k=5)
(3000L, 0.0668, 0.146)
延伸阅读: precision and recall
精确度是指fastText预测的标签中正确标签的数量。召回率是指成功预测的标签中,所有真实标签的数量。让我们举一个例子来更清楚地说明这一点:
Why not put knives in the dishwasher?
在Stack Exchange上,这个句子被标记了三个标签:equipment, cleaning 和 knives。模型预测的前五个标签可以通过以下方式获得:
model.predict("Why not put knives in the dishwasher?", k=5)
((u'__label__food-safety', u'__label__baking', u'__label__equipment', u'__label__substitutions', u'__label__bread'), array([0.0857 , 0.0657, 0.0454, 0.0333, 0.0333]))
预测的五个标签中,有一个是正确的,即equipment,这给出了0.20的精确度。在三个真实标签中,只有一个被模型预测,即设备,这给出了0.33的召回率。
更多详情,请参阅相关的维基百科页面。
4. 提高模型的准确率
通过使用默认参数运行fastText获得的模型在分类新问题方面表现相当差。让我们尝试通过更改默认参数来提高性能。
数据准备
观察数据,我们发现有些单词包含大写字母或标点符号。提高我们模型性能的第一步是应用一些简单的预处理。可以使用命令行工具(如sed和tr)来获得粗略的规范化:
>> cat cooking.stackexchange.txt | sed -e "s/\([.\!?,'/()]\)/ \1 /g" | tr "[:upper:]" "[:lower:]" > cooking.preprocessed.txt
>> head -n 12404 cooking.preprocessed.txt > cooking.train
>> tail -n 3000 cooking.preprocessed.txt > cooking.valid
5. 使用预处理的数据重新训练模型
model = fasttext.train_supervised(input="cooking.train")
Read 0M words
Number of words: 9012
Number of labels: 734
Progress: 100.0% words/sec/thread: 82041 lr: 0.000000 loss: 5.671649 eta: 0h0m
>>> model.test("cooking.valid")
(3000L, 0.164, 0.0717)
我们观察到,由于预处理,词汇量变小了(从14k个单词减少到9k个)。精确度也提高了4%!
6. 更多的周期和更大的学习率
默认情况下,fastText在训练期间只看到每个训练示例五次,这对于我们只有12k训练示例的训练集来说是非常小的。每个示例被看到的次数(也称为周期数)可以使用-epoch选项增加:
import fasttext
model = fasttext.train_supervised(input="cooking.train", epoch=25)
Read 0M words
Number of words: 9012
Number of labels: 734
Progress: 100.0% words/sec/thread: 77633 lr: 0.000000 loss: 7.147976 eta: 0h0m
然后测试一下新的模型
>>> model.test("cooking.valid")
(3000L, 0.501, 0.218)
这好多了!另一种改变我们模型学习速度的方法是增加(或减少)算法的学习率。这对应于模型在处理每个示例后变化的程度。学习率为0意味着模型根本不变化,因此,它不会学习任何东西。学习率的良好值在0.1 - 1.0的范围内。
model = fasttext.train_supervised(input="cooking.train", lr=1.0, epoch=25)
Read 0M words
Number of words: 9012
Number of labels: 734
Progress: 100.0% words/sec/thread: 76394 lr: 0.000000 loss: 4.350277 eta: 0h0m
>>> model.test("cooking.valid")
(3000L, 0.585, 0.255)
现在,让我们添加更多的功能来进一步提高我们的性能!
最后,我们可以通过使用词二元语法(bigrams)而不是仅使用词元(unigrams)来提高模型的性能。这对于词序重要的分类问题尤为重要,例如情感分析。
model = fasttext.train_supervised(input="cooking.train", lr=1.0, epoch=25, wordNgrams=2)
Read 0M words
Number of words: 9012
Number of labels: 734
Progress: 100.0% words/sec/thread: 75366 lr: 0.000000 loss: 3.226064 eta: 0h0m
model.test("cooking.valid")
(3000L, 0.599, 0.261)
通过几个步骤,我们能够将精确度从12.4%提高到59.9%。重要的步骤包括:
预处理数据;
更改周期数(使用选项-epoch,标准范围[5 - 50]);
更改学习率(使用选项-lr,标准范围[0.1 - 1.0]);
使用词n-gram(使用选项-wordNgrams,标准范围[1 - 5])。
2. 延伸阅读: 什么是Bigram?
'unigram’指的是单个不可分割的单位或令牌,通常作为模型的输入。例如,unigram可以是单词或字母,取决于模型。在fastText中,我们工作在单词级别,因此unigrams是单词。
类似地,我们用’bigram’表示两个连续令牌或单词的拼接。类似地,我们经常谈论n-gram来指代任何n个连续令牌的拼接。
例如,在句子“Last donut of the night”中,unigrams 是 ‘last’, ‘donut’, ‘of’, ‘the’ 和 ‘night’。Bigrams 包括 ‘Last donut’, ‘donut of’, ‘of the’ 和 ‘the night’。
Bigrams 特别有趣,因为对于大多数句子,你可以仅通过查看 n-gram 的集合来重构单词的顺序。
让我们通过一个简单的练习来说明这一点,给出以下 bigrams,尝试重构原始句子:‘all out’, ‘I am’, ‘of bubblegum’, ‘out of’ 和 ‘am all’。通常将一个单词称为 unigram。
扩大规模
既然我们的模型只在上千个示例上进行训练,训练过程只需几秒钟。但是,在更大的数据集上,带有更多标签的训练可能会开始变得缓慢。为了加快训练速度,一个潜在的解决方案是使用层次softmax,而不是常规的softmax。这可以通过选项 -loss hs: 来实现。
model = fasttext.train_supervised(input="cooking.train", lr=1.0, epoch=25, wordNgrams=2, bucket=200000, dim=50, loss='hs')
Read 0M words
Number of words: 9012
Number of labels: 734
Progress: 100.0% words/sec/thread: 2199406 lr: 0.000000 loss: 1.718807 eta: 0h0m
现在训练应该不到一秒钟。
3. 扩展阅读: hierarchical softmax
层次softmax是一种损失函数,它通过更快的计算来近似softmax。
这个想法是构建一个二叉树,其叶子节点对应于标签。每个中间节点都有一个二元决策激活(例如,sigmoid),它是可训练的,并预测我们是应该向左还是向右走。输出单元的概率然后由从根到输出单元叶子的路径上中间节点的概率的乘积给出。
如果您想要更详细的解释,可以观看这个视频。
在fastText中,我们使用霍夫曼树,这样对于更频繁的输出,查找时间更快,因此输出的平均查找时间是最优的。
4. 多标签分类
当我们想要将一个文档分配给多个标签时,我们仍然可以使用softmax损失,并调整预测参数,即要预测的标签数量和预测概率的阈值。然而,调整这些参数可能会很棘手且不直观,因为概率之和必须等于1。
处理多个标签的一种方便方法是使用独立的二元分类器对每个标签进行分类。这可以通过使用 -loss one-vs-all 或 -loss ova 来实现。
model = fasttext.train_supervised(input="cooking.train", lr=0.5, epoch=25, wordNgrams=2, bucket=200000, dim=50, loss='ova')
Read 0M words
Number of words: 14543
Number of labels: 735
Progress: 100.0% words/sec/thread: 72104 lr: 0.000000 loss: 4.340807 ETA: 0h 0m
与其他损失函数相比,降低学习率是一个好主意。
现在让我们来看看我们的预测,我们希望尽可能多的预测(参数 -1),并且我们只想要概率高于或等于0.5的标签:
model.predict("Which baking dish is best to bake a banana bread ?", k=-1, threshold=0.5)
((u''__label__baking, u'__label__bananas', u'__label__bread'), array([1.00000, 0.939923, 0.592677]))
我们还可以使用测试函数来评估我们的结果。
model.test("cooking.valid", k=-1)
(3000L, 0.702, 0.2)
总结
在本教程中,我们简要介绍了如何使用fastText来训练强大的文本分类器。我们简要回顾了一些最重要的调优选项。