一、说明
Spacy 是一个功能强大的 NLP 库,其中许多 NLP 任务(如标记化、词干提取、词性标记和命名实体解析)均通过预训练模型提供开箱即用的功能。所有这些任务都由管道对象以及逐步应用于给定文本的不同函数的内部抽象来包装。该管道可以通过自编写的函数进行定制和扩展。
在本文中,您将了解如何将文本分类添加到 Spacy 管道。给定带有文档和标签的训练数据,额外的分类任务可以学习预测其他文档的标签。您将了解如何构建训练数据、如何使用分类步骤扩展默认管道,以及如何添加您自己的分类实现。
本文的技术背景是Python v3.11
和spacy v3.7.2
。所有示例也应该适用于较新的库版本。
本文最初出现在我的博客admantium.com。
二、自定义 Spacy 管道示例
注意:以下所有解释和代码片段均来自三个来源:官方 Rasa API 文档、关于 使用 Spacy 3.0 进行文本分类的博客文章,以及一篇关于构建生产级 Spacy 文本分类的博客文章。
添加文本分类任务可归结为以下步骤:
- 准备训练数据
- 将训练数据转换为 Spacy DocBin 对象
- 创建默认文本分类配置
- 训练模型
- 使用模型
以下部分详细介绍了这些步骤。
三、步骤一:准备训练数据
训练数据需要是原始文本及其单标签或多标签类别。让我们通过使用 NLTK 库中包含的路透社新闻数据集来实现这一点。
以下代码片段显示了如何加载数据集,然后打印文本及其类别。
from nltk.corpus import reuters
print(len(reuters.fileids()))
# 10788
print(reuters.raw(reuters.fileids()[42]))
# JAPAN GIVEN LITTLE HOPE OF AVOIDING U.S. SANCTIONS
# A top U.S. Official said Japan has little
# chance of persuading the U.S. to drop threatened trade
# sanctions, despite the efforts of a Japanese team that left for
# Washington today.
print(reuters.categories(reuters.fileids()[42]))
# ['trade']
该数据集共有 90 个类别。此外,有些文章有多个标签。
print(len(reuters.categories()))
# 90
print(reuters.categories())
# ['acq', 'alum', 'barley', 'bop', 'carcass', 'castor-oil', 'cocoa', 'coconut', 'coconut-oil', 'coffee', 'copper', 'copra-cake', 'corn', 'cotton', 'cotton-oil', 'cpi', 'cpu', 'crude', 'dfl', 'dlr', 'dmk', 'earn', 'fuel', 'gas', 'gnp', 'gold', 'grain', 'groundnut', 'groundnut-oil', 'heat', 'hog', 'housing', 'income', 'instal-debt', 'interest', 'ipi', 'iron-steel', 'jet', 'jobs', 'l-cattle', 'lead', 'lei', 'lin-oil', 'livestock', 'lumber', 'meal-feed', 'money-fx', 'money-supply', 'naphtha', 'nat-gas', 'nickel', 'nkr', 'nzdlr', 'oat', 'oilseed', 'orange', 'palladium', 'palm-oil', 'palmkernel', 'pet-chem', 'platinum', 'potato', 'propane', 'rand', 'rape-oil', 'rapeseed', 'reserves', 'retail', 'rice', 'rubber', 'rye', 'ship', 'silver', 'sorghum', 'soy-meal', 'soy-oil', 'soybean', 'strategic-metal', 'sugar', 'sun-meal', 'sun-oil', 'sunseed', 'tea', 'tin', 'trade', 'veg-oil', 'wheat', 'wpi', 'yen', 'zinc']
四、步骤 2:将训练数据转换为 Spacy DocBin 对象
Spacy 要求其输入数据集使用附加的 属性来预处理 Doc
对象。类别应该是一个 对象,其中键是类别,值反映该类别是否适用于文章。这是一个整数值。cats
dict
以下是如何转换路透社新闻数据集第一篇文章的示例:
nlp = spacy.load('en_core_web_lg')
fileid = reuters.fileids()[42]
cat_dict = {cat: 0 for cat in reuters.categories()}
doc1 = nlp.make_doc(reuters.raw(fileid))
for cat in reuters.categories(fileid):
cat_dict[cat] = 1
doc1.cats = cat_dict
print(doc1.cats)
# {'acq': 0, 'alum': 0, 'barley': 0, 'bop': 0, ..., 'trade': 1, ...}
此外,数据需要转换为Spacy自定义数据对象DocBin
。预计数据需要分为训练和测试,以下方法创建多标签分类,其中 doc
对象处理每篇文章的原始文本,并包含 dict
所有适用类别的对象。
def convert_multi_label(dataset, filename):
db = DocBin()
nlp = spacy.load('en_core_web_lg')
total = len(dataset)
print(f'{time()}: start processing {filename} with {total} files')
for index, fileid in enumerate(dataset):
print(f'Processing {index+1}/{total}')
cat_dict = {cat: 0 for cat in reuters.categories()}
for cat in reuters.categories(fileid):
cat_dict[cat] = 1
doc = nlp(get_text(fileid))
doc.cats = cat_dict
db.add(doc)
print(f'{time()}: finish processing {filename}')
db.to_disk(filename)
#convert(training, 'reuters_training.spacy')
convert_multi_label(training, 'reuters_training_multi_label.spacy')
# 1688400699.331844: start processing reuters_training_single_label.spacy with 7769 files
# Processing 1/7769
# Processing 2/7769
# ...
二进制文件的预览:
五、步骤 3:创建默认文本分类配置
Spacy 提供了一个方便的命令行界面来创建用于多种目的的默认配置文件。从 spacy init config
开始,可以添加额外的 pipeline
步骤。对于分类,两个可能的值是 textcat
(针对单标签)和 textcat_multilabel
(针对多标签分类)。此外,通过包含选项 --optimize accuracy
,训练过程将使用预训练模型中的词向量来表示文本。
这些考虑因素导致以下命令:
> python -m spacy init config --pipeline textcat_multilabel --optimize accuracy spacy_categorization_pipeline.cfg
2023-07-02 13:26:37.103543: I tensorflow/core/platform/cpu_feature_guard.cc:182] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations.
To enable the following instructions: AVX2 FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.
ℹ Generated config template specific for your use case
- Language: en
- Pipeline: textcat
- Optimize for: efficiency
- Hardware: CPU
- Transformer: None
✔ Auto-filled config with all values
✔ Saved config
spacy_categorization_pipeline.cfg
You can now add your data and train your pipeline:
python -m spacy train spacy_categorization_pipeline.cfg --paths.train ./train.spacy --paths.dev ./dev.spacy
让我们看一下这个文件的某些部分:
[paths]
train = null
dev = null
vectors = "en_core_web_lg"
init_tok2vec = null
#...
[nlp]
lang = "en"
pipeline = ["tok2vec","textcat_multilabel"]
batch_size = 1000
disabled = []
before_creation = null
after_creation = null
after_pipeline_creation = null
tokenizer = {"@tokenizers":"spacy.Tokenizer.v1"}
[components]
[components.textcat_multilabel]
factory = "textcat_multilabel"
scorer = {"@scorers":"spacy.textcat_multilabel_scorer.v2"}
threshold = 0.5
[components.textcat_multilabel.model]
@architectures = "spacy.TextCatEnsemble.v2"
nO = null
[components.textcat_multilabel.model.tok2vec]
@architectures = "spacy.Tok2VecListener.v1"
width = ${components.tok2vec.model.encode.width}
upstream = "*"
[components.tok2vec]
factory = "tok2vec"
[components.tok2vec.model]
@architectures = "spacy.Tok2Vec.v2"
#...
[training]
dev_corpus = "corpora.dev"
train_corpus = "corpora.train"
seed = ${system.seed}
gpu_allocator = ${system.gpu_allocator}
dropout = 0.1
accumulate_gradient = 1
patience = 1600
max_epochs = 0
max_steps = 20000
eval_frequency = 200
frozen_components = []
annotating_components = []
before_to_disk = null
before_update = null
#...
[training.logger]
@loggers = "spacy.ConsoleLogger.v1"
progress_bar = false
[training.optimizer]
@optimizers = "Adam.v1"
beta1 = 0.9
beta2 = 0.999
L2_is_weight_decay = true
L2 = 0.01
grad_clip = 1.0
use_averages = false
eps = 0.00000001
learn_rate = 0.001
此配置公开了所有与训练相关的参数,从包含训练和测试数据的输入文件、使用的语料库、数据的记录和批量分离以及具有优化器和学习率的训练算法开始。对所有选项的详细讨论超出了本文的范围 - 目前,所有默认值均保持原样。
六、第 4 步:训练模型
首先,需要将数据分为训练数据集和测试数据集。方便的是,路透社的文章已经预先分割。以下代码片段划分数据并显示训练数据中的示例文章及其类别。
training = [doc for doc in reuters.fileids() if doc.find('training') == 0]
testing = [doc for doc in reuters.fileids() if doc.find('test') == 0]
print(len(training))
# 7769
print(len(testing))
# 3019
训练过程再次通过命令行启动。需要传递配置文件的名称、训练和测试数据集文件名以及输出目录。这是一个例子:
run python -m spacy train spacy_multi_label_categorization_pipeline.cfg \
--paths.train reuters_training_multi_label.spacy \
--paths.dev reuters_testing_multi_label.spacy \
--output textcat_multilabel_model
输出日志显示进度...
=========================== Initializing pipeline ===========================
[2023-07-03 18:24:10,639] [INFO] Set up nlp object from config
[2023-07-03 18:24:10,656] [INFO] Pipeline: ['tok2vec', 'textcat_multilabel']
[2023-07-03 18:24:10,660] [INFO] Created vocabulary
[2023-07-03 18:24:12,365] [INFO] Added vectors: en_core_web_lg
[2023-07-03 18:24:12,365] [INFO] Finished initializing nlp object
[2023-07-03 18:24:31,806] [INFO] Initialized pipeline components: ['tok2vec', 'textcat_multilabel']
✔ Initialized pipeline
============================= Training pipeline =============================
ℹ Pipeline: ['tok2vec', 'textcat_multilabel']
ℹ Initial learn rate: 0.001
# ...
✔ Saved pipeline to output directory
textcat_multilabel_model/model-last
…以及一个显示训练过程和几个指标的表格。
E # LOSS TOK2VEC LOSS TEXTC... CATS_SCORE SCORE
--- ------ ------------ ------------- ---------- ------
0 0 2.51 0.39 48.98 0.49
0 200 24.56 5.04 39.44 0.39
0 400 0.00 2.86 39.94 0.40
0 600 0.01 3.87 40.27 0.40
0 800 1.02 3.39 46.44 0.46
0 1000 0.00 3.60 46.68 0.47
0 1200 0.00 3.69 45.55 0.46
0 1400 0.00 3.79 45.18 0.45
0 1600 0.00 3.80 45.04 0.45
该表包含以下信息:
E
:训练运行的纪元数#
:训练步数LOSS TOK2VEC
:用于向量化输入数据的松散函数LOSS TEXTCAT
和SCORE
:分类的损失函数及其归一化值。
七、第 5 步:使用模型
在训练过程中,创建的模型存储在给定的文件夹名称中。 Spacy 保留了最新且最好的模型,即 F1 分数最高的模型。要使用这些模型,您可以直接使用 Spacy 加载它们,然后处理任何文本并导出计算的类别。
以下是与上面使用的同一文章的示例:
import spacy
nlp = spacy.load('textcat_multilabel_model/model-best')
fileid = reuters.fileids()[1024]
text = reuters.raw(fileid)
doc = nlp(text)
print(doc.cats)
# {'acq': 0.9506559371948242, 'alum': 0.3440687954425812, 'barley': 0.044086653739213943, #...
doc.cats
属性是一个 Dictionary 对象,其中包含概率的键值映射,其中最可能的类别是估计的标签。
让我们比较一下使用单标签模型的一些文章的预期标签和估计标签:
import spacy
nlp = spacy.load('textcat_singlelabel_model/model-best')
def compare_labels(fileid):
text = reuters.raw(fileid)
expected_cat = reuters.categories(fileid)
doc = nlp(text)
estimated_cats = sorted(doc.cats.items(), key=lambda i:float(i[1]), reverse=True)
estimated_cat = estimated_cats[0]
print(f'fileid {fileid} :: expected = {expected_cat} // estimated = {estimated_cat}')
compare_labels(training[1024])
# fileid training/1158 :: expected = ['ipi'] // estimated = ('cpi', 0.39067542552948)
compare_labels(training[2678])
# fileid training/2080 :: expected = ['acq'] // estimated = ('acq', 0.9778384566307068)
compare_labels(testing[1024])
# fileid test/16630 :: expected = ['orange'] // estimated = ('orange', 0.40097832679748535)
compare_labels(testing[2678])
# fileid test/20972 :: expected = ['earn'] // estimated = ('earn', 0.9994577765464783)
正如您所看到的,在三种情况下,估计的类别是匹配的。
八、比较多标签和单标签分类训练
为了让您对训练过程有一个印象,让我们比较一下多标签和单标签分类过程。
8.1 多标签分类
对 7769 个条目的完整训练集进行基线多标签分类大约需要 30 分钟。
这是日志输出:
=========================== Initializing pipeline ===========================
[2023-07-03 18:24:10,639] [INFO] Set up nlp object from config
[2023-07-03 18:24:10,656] [INFO] Pipeline: ['tok2vec', 'textcat_multilabel']
[2023-07-03 18:24:10,660] [INFO] Created vocabulary
[2023-07-03 18:24:12,365] [INFO] Added vectors: en_core_web_lg
[2023-07-03 18:24:12,365] [INFO] Finished initializing nlp object
[2023-07-03 18:24:31,806] [INFO] Initialized pipeline components: ['tok2vec', 'textcat_multilabel']
✔ Initialized pipeline
============================= Training pipeline =============================
ℹ Pipeline: ['tok2vec', 'textcat_multilabel']
ℹ Initial learn rate: 0.001
E # LOSS TOK2VEC LOSS TEXTC... CATS_SCORE SCORE
--- ------ ------------ ------------- ---------- ------
0 0 2.51 0.39 48.98 0.49
0 200 24.56 5.04 39.44 0.39
0 400 0.00 2.86 39.94 0.40
0 600 0.01 3.87 40.27 0.40
0 800 1.02 3.39 46.44 0.46
0 1000 0.00 3.60 46.68 0.47
0 1200 0.00 3.69 45.55 0.46
0 1400 0.00 3.79 45.18 0.45
0 1600 0.00 3.80 45.04 0.45
✔ Saved pipeline to output directory
textcat_multilabel_model/model-last
8.2 单标签分类
7769 个条目的完整训练集的基线单标签分类需要超过 3 小时 30 分钟。两个训练步骤本身都比较慢,并且训练会持续几个 epoch,因为 F1 分数增加非常缓慢。
要使用单一类别标签,我们首先需要更改提供训练和测试数据的方法。在与文章相关的所有类别中,仅使用第一个:
def convert_single_label(dataset, filename):
db = DocBin()
# ...
for index, fileid in enumerate(dataset):
cat_dict = {cat: 0 for cat in reuters.categories()}
cat_dict[reuters.categories(fileid).pop()] = 1
# ...
对于训练运行,使用特殊的单标签配置文件。
python -m spacy train spacy_single_label_categorization_pipeline.cfg \
--paths.train reuters_training_single_label.spacy \
--paths.dev reuters_testing_single_label.spacy \
--output textcat_singlelabel_model
以下是训练结果:
To enable the following instructions: AVX2 FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.
✔ Created output directory: textcat_singlelabel_model
ℹ Saving to output directory: textcat_singlelabel_model
ℹ Using CPU
=========================== Initializing pipeline ===========================
[2023-07-04 16:38:34,515] [INFO] Set up nlp object from config
[2023-07-04 16:38:34,529] [INFO] Pipeline: ['tok2vec', 'textcat']
[2023-07-04 16:38:34,532] [INFO] Created vocabulary
[2023-07-04 16:38:37,277] [INFO] Added vectors: en_core_web_lg
[2023-07-04 16:38:37,278] [INFO] Finished initializing nlp object
[2023-07-04 16:39:02,505] [INFO] Initialized pipeline components: ['tok2vec', 'textcat']
✔ Initialized pipeline
============================= Training pipeline =============================
ℹ Pipeline: ['tok2vec', 'textcat']
ℹ Initial learn rate: 0.001
E # LOSS TOK2VEC LOSS TEXTCAT CATS_SCORE SCORE
--- ------ ------------ ------------ ---------- ------
0 0 0.00 0.01 0.09 0.00
0 200 31.16 2.00 1.29 0.01
0 400 34.35 1.92 1.51 0.02
0 600 41.41 1.76 2.27 0.02
0 800 56.29 1.76 2.12 0.02
0 1000 124.84 1.73 2.07 0.02
0 1200 45.42 1.75 2.42 0.02
0 1400 58.10 1.51 3.93 0.04
0 1600 90.27 1.51 2.10 0.02
0 1800 93.87 1.57 3.22 0.03
0 2000 172.70 1.48 3.64 0.04
0 2200 215.72 1.42 4.30 0.04
0 2400 177.34 1.30 4.72 0.05
0 2600 866.50 1.22 5.47 0.05
0 2800 1877.17 1.16 5.09 0.05
0 3000 4186.50 1.02 6.94 0.07
1 3200 4118.06 1.05 5.33 0.05
1 3400 6050.67 0.96 8.27 0.08
1 3600 7368.26 0.82 6.07 0.06
1 3800 9302.68 1.03 8.25 0.08
1 4000 12089.54 0.98 7.82 0.08
1 4200 10829.92 0.99 6.49 0.06
1 4400 10462.12 0.91 7.15 0.07
2 4600 13104.09 0.96 9.11 0.09
2 4800 14439.08 0.87 10.31 0.10
2 5000 12240.43 0.86 9.27 0.09
尽管训练需要更长的时间,但估计的类别与预期的类别更加匹配。
九、结论
本文介绍了如何向 Spacy 项目添加文本分类任务。使用默认模型,Spacy 可以执行多项基本和高级 NLP 任务,例如标记化、词形还原、词性标记和命名实体解析。要添加文本分类,需要执行以下步骤:a) 准备训练数据,b) 将训练数据转换为 DocBin
格式,以便预处理 Doc
对象提供带有字典属性 cats
c) 生成和自定义 Spacy 训练配置,d) 使用 cli 命令训练模型。因此,特别是训练配置是高度可定制的,暴露了学习率和损失函数等细节。训练完成后,可以通过 Spacy 加载生成的模型,并且所有解析的文档现在都将具有估计的类别。