训练管道和模型
文档说明
版本
✔️ ver_1.0.1
概述
在自己的数据上训练和更新组件并集成自定义模型
spaCy
的标记器(tagger)、解析器(parser)、文本分类器(text categorizer)和许多其他组件都是统计模型。这些组件做出的每一个“决定”(例如,分配哪个词性标签,或者一个词是否是命名实体)都是基于模型当前权重值(weight values)的预测。权重值是根据模型在训练期间输入的示例来估计的。要训练一个模型,您首先需要训练数据(通常是文本示例)以及您希望模型预测的标签。这可以是词性标签、命名实体或任何其他信息。
训练是一个迭代过程,将模型的预测与参考标注(annotation)进行比较,以估计损失的梯度。然后使用损失的梯度通过反向传播计算权重的梯度。梯度指示了应如何更改权重值,以便模型的预测随着训练过程更接近参考标签。
训练数据:示例及其标注。
文本:作为输入,模型会预测文本的标签。
标签:模型预测的标签。
梯度:数值的变化方向和速率。最小化权重的梯度会使预测更接近训练数据上的参考标签。
在训练模型时,我们不仅希望它记住我们的例子,我们希望它提出一个可以在未出现的数据中推广的理论(即模型的泛化能力)。毕竟,我们不只希望模型知道“亚马逊”在这里是一家公司,我们希望它能知道“亚马逊”,在这样上下文环境下最有可能是一个公司。这就是为什么训练数据应该始终代表我们想要处理的数据。在维基百科上训练的模型,其中第一人称的句子极其罕见,这样的模型在Twitter
数据上可能会表现不佳。同样,受过浪漫小说训练的模型在法律文本上的表现可能很差。
这也意味着,为了了解模型如何表现,以及它是否在学习正确的东西,您不仅需要训练数据同时还需要评估数据。如果您只使用训练过的数据测试模型,您就无法知道它的泛化能力如何。如果您想从头开始训练模型,通常至少需要数百个示例进行训练和评估。
提示:尝试 Prodigy 注释工具
如果您需要标记大量数据,请查看Prodigy
,这是我们开发的一种新的、以主动学习为动力的注释工具。Prodigy
快速且可扩展,并带有现代Web 应用程序,可帮助您更快地收集训练数据。它与spaCy
无缝集成,预先选择最相关的示例进行注释,并让您训练和评估现成的spaCy
管道。
快速开始(新)
推荐使用spacy train
命令在命令行上训练spaCy
管道。它只需要一个包含所有设置和超参数的config.cfg
配置文件。您可以选择在命令行上覆盖配置文件中的设置,并加载一个Python
文件以注册 自定义函数和架构。此快速入门小部件可帮助您使用针对特定用例的推荐设置生成入门配置。它也可以在spaCy
中作为init config
命令。
升级到最新版本的
spaCy
以使用快速入门小部件。对于早期版本,请按照CLI
说明生成兼容的配置。
使用小部件构建的说明
- 选择您的要求和设置。
- 使用底部的按钮将结果保存到剪贴板或
base_config.cfg
文件中。 - 运行
init fill-config
创建一个完整的配置。 - 运行
train
与你的配置和数据一起。
使用
CLI
构建的说明
- 运行
init config
命令并将您的要求和设置指定为CLI
参数。 - 运行
train
使用导出的配置和数据。
访问官网使用小部件生成入门配置
将初始配置保存到base_config.cfg
文件后,您可以使用init fill-config
命令填写剩余的默认值。训练配置应始终完整且没有隐藏的默认值,以保证您的实验可重复。
python -m spacy init fill-config base_config.cfg config.cfg
python -m spacy init fill-config base_config.cfg config.cfg
小提示:调试数据
debug data
命令可以分析和验证训练和开发数据,以获得有用的统计数据,并发现诸如无效实体注释、循环依赖、低数据标签等问题。
python -m spacy debug data config.cfg
python -m spacy debug data config.cfg
除了从快速入门小部件导出您的入门配置并自动填充它之外,您还可以使用init config
命令并将您的要求和设置指定为CLI
参数。您现在可以添加数据并用你的配置运行train
。使用convert
命令将训练数据转换为spaCy
接受的.spacy
格式的的二进制文件。您可以在[paths]
配置部分包含数据路径,也可以通过命令行传递它们。
python -m spacy train config.cfg --output ./output --paths.train ./train.spacy --paths.dev ./dev.spacy
python -m spacy train config.cfg --output ./output --paths.train ./train.spacy --paths.dev ./dev.spacy
小提示: 使用GPU(ENABLE YOUR GPU)
# 使用`--gpu-id`选项来选择`GPU`:
python -m spacy train config.cfg --gpu-id 0
# 使用`--gpu-id`选项来选择`GPU`:
python -m spacy train config.cfg --gpu-id 0
配置建议是如何生成的?
由快速入门小部件和init config
命令生成的推荐配置设置基于一些通用的最佳实践和我们在实验中发现的效果很好的东西。目标是为您提供最有用的默认值。
在底层,quickstart_training.jinja
模板定义了不同的组合。例如,如果需要针对效率与准确性优化管道时,需要更改哪些参数。quickstart_training_recommendations.yml
文件收集了每种语言的推荐设置和可用资源,包括不同的转换器(transformer)权重。对于某些语言,我们提供了不同的转换器建议,这取决于您是否希望模型更高效或更准确。 随着我们进行更多实验,这些建议将不断发展。
🪐使用项目模板:pipelines/tagger_parser_ud
最简单的入门方法是克隆一个项目模板并运行它。例如,这个端到端模板可让您在Universal Dependencies
树库上训练词性标注器和依赖项解析器。
$python -m spacy project clone pipelines/tagger_parser_ud
$python -m spacy project clone pipelines/tagger_parser_ud
训练的配置系统
训练的配置文件包括用于训练管道的所有设置和超参数。无需在命令行上提供大量参数,您只需要将config.cfg
文件传递给spacy train
。 在底层,训练配置会使用我们的机器学习库Thinc
提供的配置系统。这也使集成自定义模型和架构变得容易,这些模型和架构是在您选择的框架中编写的。spaCy
训练配置的一些主要优点和特点如下:
- 结构化部分(structured sections)。配置分为多个
section
,嵌套部分则使用符号.
定义。例如,[components.ner]
定义管道的命名实体识别器的设置。这些配置可以作为Python
字典加载。 - 对注册函数的引用。
section
可以引用注册函数,如模型架构、 优化器或调度,并定义传递给它们的参数。您还可以注册自己的函数来自定义架构或方法,在您的配置中引用它们并调整它们的参数。 - 插值(interpolation)。如果您有多个组件使用超参数或其他设置,只需定义一次然后将它们作为变量引用。
- 没有隐藏默认值的再现性。配置文件是“唯一的真实来源”,包括所有设置,不要在任何地方使用隐藏的默认设置而导致不能复现。
- 自动检查和验证。当您加载配置时,
spaCy
会检查设置是否完整以及所有值是否具有正确的类型。这可以让您及早发现潜在的错误。在您的自定义架构中,您可以使用Python
类型提示来告诉配置需要哪些类型的数据。
github
explosion/spaCy/master/spacy/default_config.cfg
explosion/spaCy/master/spacy/default_config.cfg
在底层,配置被解析为字典。它分为sections
和subsections
,由方括号和点符号表示。例如,[training]
是一个sections
而[training.batch_size]
是一个subsections
。subsections
可以定义值,就像字典一样,或者使用@
语法来引用已注册的函数。这使配置的值不仅可以定义为静态值,还可以是构建的架构、调度、优化器或任何其他自定义组件等对象。配置文件的主要顶级部分(section)如下表:
部分(section) | 描述 |
---|---|
nlp | nlp 对象的定义、其标记器和处理管道组件名称。 |
components | 定义管道组件及其模型。 |
paths | 数据和其他资产的路径。在配置中作为变量使用,例如${paths.train} ,可以在CLI 上进行覆盖。 |
system | 与系统和硬件相关的设置。在配置中作为变量使用,例如${system.seed} ,可以在CLI 上覆盖。 |
training | 训练和评估过程的设置和控制。 |
pretraining | 语言模型预训练的可选设置和控制。 |
initialize | 调用nlp.initialize 方法时传递给组件的数据资源和参数,该方法在训练之前调用(不是在运行时调用)。 |
📖 配置格式和设置
有关spaCy
的配置格式和设置的完整概述,请参阅数据格式文档和Thinc
的配置系统文档。可用于不同架构的设置记录在模型架构API
中。有关优化器和调度的信息,请参阅Thinc
文档。
运行时和训练时配置的生命周期
管道的config.cfg
在训练和运行时都被认为是“单一事实来源” 。在底层,Language.from_config
负责使用配置中定义的设置构建nlp
对象。一个nlp
对象的配置可作为nlp.config
它包括管道的所有信息,以及用于训练和初始化管道的设置。
在运行时,spaCy
仅使用配置的[nlp]
和[components]
块并加载所有数据,包括标记化(tokenization)规则、模型权重和管道目录中的其他资源。[training]
块包含用于训练模型的设置,仅在训练期间使用。类似地,[initialize]
块定义了在训练之前应如何设置初始nlp
对象,以及是否应使用向量、预训练的tok2vec
权重以及组件所需的任何其他数据对其进行初始化。
初始化设置仅在nlp.initialize
被调用(通常在训练之前)时加载和使用 。这允许您使用本地数据资源和自定义函数设置管道,并将信息保留在您的配置中,但不需要它在运行时可用。您还可以使用此机制为自定义管道组件和自定义标记器提供数据路径,有关详细信息,请参阅自定义初始化部分。
在命令行上覆盖配置设置
配置系统意味着您可以在一个地方以一致的格式定义所有设置。没有需要设置的命令行参数,也没有隐藏的默认值。但是,仍然存在您可能希望在运行spacy train
命令时覆盖配置设置的情况。 这包括向量(vectors)的文件路径,不应该在配置文件中硬编码的内容以及依赖于系统的设置。
对于这种情况,您可以设置其他命令行选项,这些选项以--
开头,对应于要覆盖的配置的section
和值。例如,--paths.train ./corpus/train.spacy
设置[paths]
块中的train
值。
python -m spacy train config.cfg --paths.train ./corpus/train.spacy --paths.dev ./corpus/dev.spacy --training.batch_size 128
python -m spacy train config.cfg --paths.train ./corpus/train.spacy --paths.dev ./corpus/dev.spacy --training.batch_size 128
只有配置文件中已有的section
和值能够被覆盖。在训练结束时,最终填充config.cfg
会与您的管道一起导出,因此您将始终拥有所用设置的记录,包括您覆盖的设置。顺便说一下,覆盖是添加在解析变量之前,因此,如果您需要在多个地方使用一个值,请在整个配置中引用它并在 CLI
上覆盖一次。
💡小提示:详细日志记录
如果您使用配置覆盖,则可以在spacy train
命令行使用--verbose
标志,它能够让spaCy
记录更多信息,包括通过CLI
和环境变量设置的覆盖。
通过环境变量添加覆盖
除了将覆盖定义为CLI
参数之外,您还可以使用SPACY_CONFIG_OVERRIDES
环境变量使用相同的参数语法。如果您将模型训练作为自动化过程的一部分进行,这将特别有用。环境变量优先于CLI
覆盖和配置文件中定义的值。
SPACY_CONFIG_OVERRIDES="--system.gpu_allocator pytorch --training.batch_size 128" ./your_script.sh
SPACY_CONFIG_OVERRIDES="--system.gpu_allocator pytorch --training.batch_size 128" ./your_script.sh
从标准输入读取
通过符号-
在命令行上设置配置路径,允许你从标准输入中读取配置,并从不同的进程从管道转发出去,例如init config
或者自定义的脚本。这对于快速试验非常有用,因为这可以快速的生成一个配置,而无需保存到磁盘再从磁盘中加载。
💡 提示:写入标准输出
运行init config
时,您可以通过-
符将输出路径设置写到标准输出。在自定义脚本中,您可以打印字符串配置,例如print(nlp.config.to_str())
。
python -m spacy init config - --lang en --pipeline ner,textcat --optimize accuracy | python -m spacy train - --paths.train ./corpus/train.spacy --paths.dev ./corpus/dev.spacy
python -m spacy init config - --lang en --pipeline ner,textcat --optimize accuracy | python -m spacy train - --paths.train ./corpus/train.spacy --paths.dev ./corpus/dev.spacy
使用变量插值(变量替换)
配置系统的另一个非常有用的功能是它支持**值和部分(section)**的变量插值。这意味着您只需要定义一次设置,就可以使用${section.value}
语法在整个配置中引用它。在这个例子中,seed
的值在[training]
块中被重复使用,整个[training.optimizer]
块[pretraining]
被重用而变成pretraining.optimizer
。
CONFIG.CFG(摘录)
[system]
seed = 0
[training]
seed = ${system.seed}
[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 = 1e-8
[pretraining]
optimizer = ${training.optimizer}
[system]
seed = 0
[training]
seed = ${system.seed}
[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 = 1e-8
[pretraining]
optimizer = ${training.optimizer}
您还可以在字符串中使用变量。在这种情况下,它的工作原理就像Python
中的f-strings
一样。如果变量的值不是字符串,则将其转换为字符串。
[paths]
version = 5
root = "/Users/you/data"
train = "${paths.root}/train_${paths.version}.spacy"
# Result: /Users/you/data/train_5.spacy
[paths]
version = 5
root = "/Users/you/data"
train = "${paths.root}/train_${paths.version}.spacy"
# Result: /Users/you/data/train_5.spacy
💡提示:在
CLI
中覆盖变量 如果您需要在训练运行之间更改某些值,您可以定义它们一次,将它们作为变量引用,然后在CLI
上覆盖它们。例如--paths.root /other/root
将更改[paths]
块中root
的值,并且更改将反映在引用此变量的值上。
准备训练数据
NLP
项目的训练数据有多种不同的格式。常见的格式如CoNLL
,spaCy
提供了可以通过命令行使用的转换器。在其他情况下,您必须自己准备训练数据。
当你在spaCy
中需要转换训练数据时,主要的事情就是创建Doc
对象。例如,如果您正在创建一个NER
管道,只需要加载您的标注并将它们设置为Doc
对象上的.ents
属性。在磁盘上,标注将会以.spacy
格式保存为DocBin
,其中的细节会自动处理。
这是从一些NER
注释创建.spacy
文件的示例。
PREPROCESS.PY
import spacy
from spacy.tokens import DocBin
nlp = spacy.blank("en")
training_data = [
("Tokyo Tower is 333m tall.", [(0, 11, "BUILDING")]),
]
# the DocBin will store the example documents
db = DocBin()
for text, annotations in training_data:
doc = nlp(text)
ents = []
for start, end, label in annotations:
span = doc.char_span(start, end, label=label)
ents.append(span)
doc.ents = ents
db.add(doc)
db.to_disk("./train.spacy")
import spacy
from spacy.tokens import DocBin
nlp = spacy.blank("en")
training_data = [
("Tokyo Tower is 333m tall.", [(0, 11, "BUILDING")]),
]
# the DocBin will store the example documents
db = DocBin()
for text, annotations in training_data:
doc = nlp(text)
ents = []
for start, end, label in annotations:
span = doc.char_span(start, end, label=label)
ents.append(span)
doc.ents = ents
db.add(doc)
db.to_disk("./train.spacy")
有关如何将更多格式的训练数据转换为用于spaCy
的可用格式的示例,请查看教程项目中的预处理步骤 。
关于
spaCy``JSON
格式?
在spaCy v2
中,推荐使用特定的JSON
格式存储训练数据,但在v3
中这种格式已被弃用。
自定义管道和训练
定义管道组件
您通常会训练一个包含一个或多个组件的管道。配置中的[components]
块定义了可用的管道组件以及它们应该如何创建(通过内置创建、自定义工厂或者 来自现有的已训练的管道)。例如,[components.parser]
定义了管道中名称为"parser"
的组件。在训练中,您可能希望以不同的方式处理您的组件,最常见的场景是:
- 根据数据从头开始训练新组件。
- 使用更多示例更新现有的组件。
- 使用(即引入管道)现有的已训练组件而不更新它。
- 使用不可训练的组件,如基于规则的
EntityRuler
或者Sentencizer
,或完全自定义的组件。
如果一个组件块定义了一个factory
,spaCy
将在内置或 自定义组件中查找它,并从头开始创建一个新组件。配置块中定义的所有设置都将作为参数传递给组件工厂。这使您可以配置模型设置和超参数。如果组件块定义了source
,则组件将从现有的训练管道中复制,并带有其现有的权重。这使您可以在管道中包含一个已经训练好的组件,或者使用更多特定的用例数据更新一个经过训练的组件。
CONFIG.CFG文件(摘录)
[components]
# "parser" and "ner" are sourced from a trained pipeline
# 从已训练的管道中使用"parser" and "ner"
[components.parser]
source = "en_core_web_sm"
[components.ner]
source = "en_core_web_sm"
# "textcat" and "custom" are created blank from a built-in / custom factory
[components.textcat]
factory = "textcat"
[components.custom]
factory = "your_custom_factory"
your_custom_setting = true
[components]
# "parser" and "ner" are sourced from a trained pipeline
# 从已训练的管道中使用"parser" and "ner"
[components.parser]
source = "en_core_web_sm"
[components.ner]
source = "en_core_web_sm"
# "textcat" and "custom" are created blank from a built-in / custom factory
[components.textcat]
factory = "textcat"
[components.custom]
factory = "your_custom_factory"
your_custom_setting = true
[nlp]
块中按顺序 定义了pipeline
中添加的组件。例如,"parser"
这里引用[components.parser]
。默认情况下,spaCy
会更新所有可以更新的组件。从头开始创建的可训练组件使用随机权重进行初始化。对于源组件,spaCy
将保留现有权重并继续训练。
(这里实质上就能够应用于管道组件的更新。)
如果您不想更新某个组件,可以通过将其添加到[training]
块中的frozen_components
列表来冻结它。冻结的组件在训练期间不会更新,并按原样包含在最终训练好的管道中。同时,在调用nlp.initialize
方法时他们也被排除在外。
关于冻结组件的注意事项
即使冻结的组件在训练期间没有更新,它们仍然会在评估期间运行。这非常重要,因为它们可能仍会影响模型的性能。例如,句子边界检测器会影响解析器(parser)或实体识别器识别有效的解析。因此,评估结果应该反映您的管道在运行时产生的结果。如果您还希望在训练期间运行(不更新)一个冻结的组件,以便下游组件可以使用它的预测,你可以将它添加到annotating_components
块中。
[nlp]
lang = "en"
pipeline = ["parser", "ner", "textcat", "custom"]
[training]
frozen_components = ["parser", "custom"]
[nlp]
lang = "en"
pipeline = ["parser", "ner", "textcat", "custom"]
[training]
frozen_components = ["parser", "custom"]
共享的
Tok2Vec
侦听器层
当管道中的组件共享一个嵌入层时,如果您继续使用相同的底层Tok2Vec
实例训练其他层,则冻结组件的性能将会下降 。根据经验,需要确保您的冻结组件在管道中真正独立。
要想使用独立的
token-to-vector
副本替换共享的token-to-vector
监听器,你可以使用replace_listeners
对源组件进行设置,指向配置中的侦听器层。有关其内部工作原理的更多详细信息,请参阅Language.replace_listeners
。
[training]
frozen_components = ["tagger"]
[components.tagger]
source = "en_core_web_sm"
replace_listeners = ["model.tok2vec"]
[training]
frozen_components = ["tagger"]
[components.tagger]
source = "en_core_web_sm"
replace_listeners = ["model.tok2vec"]
使用来自上游组件的预测 V 3.1
默认情况下,组件在训练期间单独更新,这意味着它们看不到管道中上游任何组件的预测。一个组件接收Example.predicted
作为输入并将其预测与Example.reference
对比,而不会将其注释保存在predicted
文档中。
相反,如果某些部件需要在训练中设置其标注,在[training]
块中使用annotating_components
设置指定组件列表。例如,解析器(parser)中的DEP
特征可以通过在tok2vec
的attrs
中包含DEP
并且在annotating_components
中包含parser
而将DEP
作为标记器(tagger)的特征。
CONFIG.CFG(摘录)
[nlp]
pipeline = ["parser", "tagger"]
[components.tagger.model.tok2vec.embed]
@architectures = "spacy.MultiHashEmbed.v1"
width = ${components.tagger.model.tok2vec.encode.width}
attrs = ["NORM","DEP"]
rows = [5000,2500]
include_static_vectors = false
[training]
annotating_components = ["parser"]
[nlp]
pipeline = ["parser", "tagger"]
[components.tagger.model.tok2vec.embed]
@architectures = "spacy.MultiHashEmbed.v1"
width = ${components.tagger.model.tok2vec.encode.width}
attrs = ["NORM","DEP"]
rows = [5000,2500]
include_static_vectors = false
[training]
annotating_components = ["parser"]
任何组件(包括冻结的组件)都可以作为注释组件包含在管道中。冻结组件可以在训练期间设置注释,就像它们在评估期间或运行最终管道时设置注释一样。下面的配置摘录显示了一个冻结的ner
组件和一个sentencizer
如何在训练期间为实体链接器提供所需的doc.sents
和doc.ents
:
CONFIG.CFG(摘录)
[nlp]
pipeline = ["sentencizer", "ner", "entity_linker"]
[components.ner]
source = "en_core_web_sm"
[training]
frozen_components = ["ner"]
# 提供注释(annotation)
annotating_components = ["sentencizer", "ner"]
[nlp]
pipeline = ["sentencizer", "ner", "entity_linker"]
[components.ner]
source = "en_core_web_sm"
[training]
frozen_components = ["ner"]
# 提供注释(annotation)
annotating_components = ["sentencizer", "ner"]
类似地,一个预训练的tok2vec
层可以被冻结,但同时在annotating_components
列表中指定,以确保下游组件可以使用嵌入层而无需更新它。 (不更新组件,而使用组件的预测值)
使用注释组件的训练速度
请注意,基于统计模型的非冻结注释组件将 在每个批次(batch)上运行两次,一次更新模型,一次使用模型预测文档。
使用注册函数
配置文件中定义的训练配置不仅可以使用静态值。有些设置也可以是函数。例如,batch_size
可以是一个不变的数字,也可以是一个时间表或一个复合值序列,这已被证明是一种有效的技巧(参见Smith等人,2017 年)。
使用静态值
[training]
batch_size = 128
[training]
batch_size = 128
要改为引用函数,您可以创建[training.batch_size]
部分(section)并使用@
语法来指定函数及其参数。这个例子中,在函数注册表中定义了[compounding.v1
](https://thinc.ai/docs/api-schedules#compounding) 。块中定义的所有其他值在初始化时作为关键字参数传递给函数。您还可以使用此机制来注册自定义实现和架构,并在配置中引用它们。
配置的解析过程
配置文件被解析为常规字典,并自底向上解析和验证。为注册函数提供的参数根据函数的签名和类型注释进行检查。注册函数的返回值也可以传递给另一个函数。例如,学习率计划可以作为优化器的参数提供。
使用注册函数
[training.batch_size]
@schedules = "compounding.v1"
start = 100
stop = 1000
compound = 1.001
[training.batch_size]
@schedules = "compounding.v1"
start = 100
stop = 1000
compound = 1.001
模型架构
💡模型类型注解 在文档和代码库中,您可能会遇到
Thinc
模型类型的类型注释和描述,例如Model[List[Doc], List[Floats2d]]Doc
。这种所谓的泛型类型描述了层(layer)及其输入和输出类型。在这种情况下,它以Doc
对象列表作为输入,以二维浮点数组列表作为输出。您可以在此处阅读有关定义Thinc
模型定义的更多信息。另请参阅类型检查以了解如何在您的编辑器中启用linting
以在您的输入和输出不匹配时查看实时反馈。
模型架构是一个链接了Thinc
Model
实例的函数,然后可以在一个组件中使用或者作为一个更大的网络的层使用。您可以使用Thinc
作为PyTorch
、TensorFlow
或 MXNet
等框架的瘦 包装器(wrapper around frameworks
),也可以直接在Thinc
中实现您的逻辑。有关更多详细信息和示例,请参阅层和架构的使用指南。
spaCy
的内置组件永远不会自己构建它们的Model
实例,因此您不必对组件进行子类化来更改其模型架构。您可以只更新配置,以便它引用不同的注册函数。一旦创建了组件,它的Model
实例就已经被分配了,所以你不能改变它的模型架构。该架构就像网络的食谱,一旦菜肴准备好,您就无法更改食谱。你必须创建一个新的。spaCy
包含了用于不同任务的各种内置架构。例如:
架构 | 描述 |
---|---|
HashEmbedCNN | 构建spaCy 的“标准”嵌入层,它使用具有子词特征的哈希嵌入和具有层归一化maxout 的CNN 。类型:Model[List[Doc], List[Floats2d]] |
TransitionBasedParser | 在默认的EntityRecognizer 和DependencyParser 中创建一个基于转换的解析器模型(transition-based parser)类型:Model[List[Docs], List[List[Floats2d]]] |
TextCatEnsemble | 堆叠集成词袋模型和具有内部CNN 嵌入层的神经网络模型。默认情况下使用TextCategorizer 。类型:Model[List[Doc], Floats2d] |
指标、训练输出和加权分数
当您使用spacy train
命令训练一个管道时,您将看到一个表格,显示每次传递数据后的指标。可用的指标取决于管道组件。管道组件还定义了显示哪些分数以及在决定最佳模型时如何为他们分配权重。
config.cfg
配置文件中的training.score_weights
允许您自定义表中显示的分数以及它们的加权方式。在这个例子中,标记(labeled)依赖准确度和 NER F-score
分别占最终分数的40%
,标记(tagging)准确度占剩余的 20%
。标记化(tokenization)准确率和速度都显示在表中,但不计入分数。
我为什么需要分数权重? 在训练过程结束时,您通常希望选择最佳模型。但“最佳”的含义取决于可用组件和您的特定用例。例如,与具有较低NER
和较高POS
精度的管道相比,您可能更喜欢具有较高NER
和较低POS
标记精度的管道。您可以在分数权重中表达这种偏好,例如通过分配ents_f
(NER F-score
)更高的权重。
[training.score_weights]
dep_las = 0.4
dep_uas = null
ents_f = 0.4
tag_acc = 0.2
token_acc = 0.0
speed = 0.0
[training.score_weights]
dep_las = 0.4
dep_uas = null
ents_f = 0.4
tag_acc = 0.2
token_acc = 0.0
speed = 0.0
虽然建议score_weights
的总和是1.0,但是实际应用中也可以不为1.0。当您为给定的管道生成配置时,得分权重是通过组合和规范化管道组件的默认得分权重来生成的。默认得分权重由每个管道组件通过@Language.factory
装饰器中的default_score_weights
设置。默认情况下,所有管道组件的权重相等。如果分数权重设置为null
,它将从日志中排除并且分数不会被加权。
了解训练输出和分数类型
名称 | 描述 |
---|---|
损失(Loss) | 训练损失表示优化器剩余工作量的。应该减少,但通常不会减少到0 。 |
精度(P,Precision) | 预测标注正确的百分比。应该增加。 |
召回(R,Recall) | 召回的百分比。应该增加。 |
F 分数(F,F-Score ) | 准确率和召回率的调和平均值。应该增加。 |
USA/LAS | 依赖解析器(parser)的未标记和标记附件分数,例如,正确弧(arcs)的百分比。应该增加。 |
速度(Speed) | 以每秒字数 (WPS ) 为单位的预测速度。应该保持稳定。 |
请注意,如果开发数据(
development data
)具有原始文本,则某些黄金标准(gold-standard)实体可能与预测的标记化(tokenization)不一致。这些标记化错误被排除在NER
评估之外。即使您的标记化使模型无法预测50%
的实体,您的NER F
分数可能看起来仍然不错。
自定义函数
训练配置文件中的注册函数可以参考内置实现,但您也可以插入完全自定义的实现。只需使用 @spacy.registry
装饰器注册您的函数,并使用相应注册表的名称,例如@spacy.registry.architectures
,以及要分配给函数的字符串名称。通过注册自定义函数,您可以插入在PyTorch
或TensorFlow
中定义的模型,对nlp
对象进行自定义修改,创建自定义优化器或计划(schedules
),或者在训练时传入数据并对其进行动态预处理。
每个自定义函数都可以有任意数量的参数,这些参数通过config
传入 ,只是内置函数。如果您的函数定义了默认参数值,spaCy
能够在您运行init fill-config
时自动填充您的配置。如果要确保在配置中始终明确设置给定参数,请避免为其设置默认值。
使用自定义代码进行训练
# 训练
python -m spacy train config.cfg --code functions.py
# 训练
python -m spacy train config.cfg --code functions.py
# 打包
python -m spacy package ./model-best ./packages --code functions.py
# 打包
python -m spacy package ./model-best ./packages --code functions.py
spacy trainrecipe
允许您指定一个可选参数--code
指向一个Python
文件。该文件在训练之前导入,并允许您将自定义函数和架构添加到函数注册表中,注册表可以在config.cfg
文件中设置。这使您可以使用自定义组件训练spaCy
管道,而无需重新实施整个训练工作流程。当您稍后使用spacy package
来打包训练的管道时,您可以提供一个或多个Python
文件放入包中并导入到其__init__.py
。这意味着任何自定义架构、函数或组件都将随您的管道一起提供并在加载时注册。有关详细信息,请参阅有关保存和加载管道的文档。
示例:修改 nlp 对象
对于许多用例,您不一定要从头开始实现整个Language
子类和语言数据。通常只需进行一些小的修改,例如调整标记化规则(tokenization rules
)或 语言默认值(tokenization rules
,如停用词)。配置允许您提供五个可选的回调函数, 是你可以在生命周期的不同时期访问语言类(Language class)和nlp
对象:
回调(CALBACK) | 描述 |
---|---|
nlp.before_creation | 在创建nlp 对象之前调用并接收语言子类(如,English ,注意这里不是实例)。对写信很有用Language.Defaults 除了标记器设置。 |
nlp.after_creation | 在创建nlp 对象之后立即调用,但在将管道组件添加到管道并接收nlp 对象之前调用。 |
nlp.after_pipeline_creation | 在创建和添加管道组件并接收nlp 对象后立即调用。对于修改管道组件非常有用。 |
initialize.before_init | 在初始化管道组件并接收nlp 对象以进行就地修改之前调用。用于修改标记器(tokenizer)设置,类似于基本v2 模型选项。 |
initialize.after_init | 在管道组件初始化并接收nlp 对象进行就地修改后调用。 |
@spacy.registry.callbacks
装饰器可以让你在回调注册中注册一个给定名称的自定义函数。然后,您可以使用@callbacks
键在配置块中引用该函数。如果块中包含以@
开头的键,则将其解释为对函数的引用。因为您已经注册了该函数,所以当您在配置中引用"customize_language_data"
时,spaCy
知道如何创建它。这里有一个回调的示例,它会在创建nlp
对象之前运行,并将添加自定义停用词到默认值:
配置文件
[nlp.before_creation]
@callbacks = "customize_language_data"
[nlp.before_creation]
@callbacks = "customize_language_data"
FUNCTIONS.PY 文件
import spacy
@spacy.registry.callbacks("customize_language_data")
def create_callback():
def customize_language_data(lang_cls):
lang_cls.Defaults.stop_words.add("good")
return lang_cls
return customize_language_data
import spacy
@spacy.registry.callbacks("customize_language_data")
def create_callback():
def customize_language_data(lang_cls):
lang_cls.Defaults.stop_words.add("good")
return lang_cls
return customize_language_data
请记住,注册函数始终应该是一个会被
spaCy
调用以创建某些内容的函数。在本例中,它创建了一个回调,但它本身不是回调。
任何注册函数——在这种情况下create_callback
,都可以通过config
设置接受参数。这使您可以实施和跟踪不同的配置,而无需修改您的代码。您可以为用例选择任何参数。在这个示例中,我们添加了参数extra_stop_wordsdebug
(字符串列表)和debug
(布尔值)以在函数运行时打印附加信息。
config.cfg配置文件
[nlp.before_creation]
@callbacks = "customize_language_data"
extra_stop_words = ["ooh", "aah"]
debug = true
[nlp.before_creation]
@callbacks = "customize_language_data"
extra_stop_words = ["ooh", "aah"]
debug = true
FUNCTIONS.PY
from typing import List
import spacy
@spacy.registry.callbacks("customize_language_data")
def create_callback(extra_stop_words: List[str] = [], debug: bool = False):
def customize_language_data(lang_cls):
lang_cls.Defaults.stop_words.update(extra_stop_words)
if debug:
print("Updated stop words")
return lang_cls
return customize_language_data
from typing import List
import spacy
@spacy.registry.callbacks("customize_language_data")
def create_callback(extra_stop_words: List[str] = [], debug: bool = False):
def customize_language_data(lang_cls):
lang_cls.Defaults.stop_words.update(extra_stop_words)
if debug:
print("Updated stop words")
return lang_cls
return customize_language_data
💡提示:使用
Python
类型提示spaCy
的Configs
基于我们的机器学习库Thinc
的配置系统,它支持类型提示,甚至使用pydantic
实现更高级的类型注释 。如果您注册的函数提供类型提示,则传入的值将根据预期类型进行检查。例如,debug: bool
在上面的例子中将确保作为debug
参数接收的值是一个布尔值。如果该值不能被强制转换为布尔值,spaCy
将引发错误。debug: pydantic.StrictBool
则是更严格的类型控制,它将强制该值是一个布尔值,如果不是,则会引发错误。例如,如果您的配置定义1
而不是true
。
使用functions.py
定义附加代码和更新的config.cfg
,您现在可以运行spacy train
并将参数--code
指向您的Python
文件。在加载配置之前,spaCy
将导入functions.py
模块并注册您的自定义函数。
python -m spacy train config.cfg --output ./output --code ./functions.py
python -m spacy train config.cfg --output ./output --code ./functions.py
示例:修改标记器设置
在训练新管道时,使用initialize.before_init
回调来修改标记器(tokenizer)设置。编写一个注册的回调来修改标记器设置,并在您的配置中指定此回调:
配置文件 config.cfg
[initialize]
[initialize.before_init]
@callbacks = "customize_tokenizer"
[initialize]
[initialize.before_init]
@callbacks = "customize_tokenizer"
FUNCTIONS.PY
from spacy.util import registry, compile_suffix_regex
@registry.callbacks("customize_tokenizer")
def make_customize_tokenizer():
def customize_tokenizer(nlp):
# remove a suffix
suffixes = list(nlp.Defaults.suffixes)
suffixes.remove("\[")
suffix_regex = compile_suffix_regex(suffixes)
nlp.tokenizer.suffix_search = suffix_regex.search
# add a special case
nlp.tokenizer.add_special_case("_SPECIAL_", [{"ORTH": "_SPECIAL_"}])
return customize_tokenizer
from spacy.util import registry, compile_suffix_regex
@registry.callbacks("customize_tokenizer")
def make_customize_tokenizer():
def customize_tokenizer(nlp):
# remove a suffix
suffixes = list(nlp.Defaults.suffixes)
suffixes.remove("\[")
suffix_regex = compile_suffix_regex(suffixes)
nlp.tokenizer.suffix_search = suffix_regex.search
# add a special case
nlp.tokenizer.add_special_case("_SPECIAL_", [{"ORTH": "_SPECIAL_"}])
return customize_tokenizer
训练时,使用--code
选项提供上面的函数:
python -m spacy train config.cfg --code ./functions.py
python -m spacy train config.cfg --code ./functions.py
由于此回调仅在训练前的一次性初始化步骤中调用,因此回调代码不需要与最终的管道包一起打包。但是,为了让其他人更容易复制您的训练设置,您可以选择将初始化回调与管道包一起打包或单独发布。
!!nlp.before_creation
与 initialize.before_init
nlp.before_creation
是修改语言默认值的最佳位置,除了标记器设置以外。initialize.before_init
是在训练新管道时修改标记器(tokenizer)设置的最佳位置。 与其他语言默认设置不同,分词器(tokenizer)设置使用nlp.to_disk()
与管道一起保存,因此,当从磁盘加载训练好的管道时,nlp.before_creation
所做的修改将被保存的设置破坏。
示例:自定义日志功能
在训练期间,每一步的结果都会传递给一个记录器(logger)函数。默认情况下,这些结果被写入控制台ConsoleLogger
。另外也内置支持使用WandbLogger
将日志文件写入Weights & Biases
。在每一步,记录器(logger)函数都会收到一个包含以下键的字典:
KEY | VALUE |
---|---|
epoch | 完成了多少次数据传递。类型:int |
step | 完成了多少步。类型:int |
score | 上次评估的主要分数,在开发(dev)集上测量。类型:float |
other_scores | 上次评估的其他分数,在开发集上测量。类型:Dict[str, Any] |
losses | 累积的训练损失,以组件名称为键。类型:Dict[str, float] |
checkpoints | 先前结果的列表,其中每个结果都是一个(score, step) 元组。类型:List[Tuple[float, int]] |
您可以轻松实现并插入您自己的记录器,以自定义方式记录训练结果,或将它们发送到您选择的实验管理跟踪器。在此示例中,my_custom_logger.v1
函数将表格结果写入文件:
CONFIG.CFG(摘录)
[training.logger]
@loggers = "my_custom_logger.v1"
log_path = "my_file.tab"
[training.logger]
@loggers = "my_custom_logger.v1"
log_path = "my_file.tab"
FUNCTIONS.PY
import sys
from typing import IO, Tuple, Callable, Dict, Any, Optional
import spacy
from spacy import Language
from pathlib import Path
@spacy.registry.loggers("my_custom_logger.v1")
def custom_logger(log_path):
def setup_logger(
nlp: Language,
stdout: IO=sys.stdout,
stderr: IO=sys.stderr
) -> Tuple[Callable, Callable]:
stdout.write(f"Logging to {log_path}\n")
log_file = Path(log_path).open("w", encoding="utf8")
log_file.write("step\t")
log_file.write("score\t")
for pipe in nlp.pipe_names:
log_file.write(f"loss_{pipe}\t")
log_file.write("\n")
def log_step(info: Optional[Dict[str, Any]]):
if info:
log_file.write(f"{info['step']}\t")
log_file.write(f"{info['score']}\t")
for pipe in nlp.pipe_names:
log_file.write(f"{info['losses'][pipe]}\t")
log_file.write("\n")
def finalize():
log_file.close()
return log_step, finalize
return setup_logger
import sys
from typing import IO, Tuple, Callable, Dict, Any, Optional
import spacy
from spacy import Language
from pathlib import Path
@spacy.registry.loggers("my_custom_logger.v1")
def custom_logger(log_path):
def setup_logger(
nlp: Language,
stdout: IO=sys.stdout,
stderr: IO=sys.stderr
) -> Tuple[Callable, Callable]:
stdout.write(f"Logging to {log_path}\n")
log_file = Path(log_path).open("w", encoding="utf8")
log_file.write("step\t")
log_file.write("score\t")
for pipe in nlp.pipe_names:
log_file.write(f"loss_{pipe}\t")
log_file.write("\n")
def log_step(info: Optional[Dict[str, Any]]):
if info:
log_file.write(f"{info['step']}\t")
log_file.write(f"{info['score']}\t")
for pipe in nlp.pipe_names:
log_file.write(f"{info['losses'][pipe]}\t")
log_file.write("\n")
def finalize():
log_file.close()
return log_step, finalize
return setup_logger
示例:自定义batch size计划
您还可以实现自己的batch size
计划(schedule)以在训练期间使用。@spacy.registry.schedules
装饰器让你可以注册该功能到schedules
注册表中,并为它分配一个字符串名称:
为什么名字里有版本号?
配置系统的一大好处是它使您的实验具有可重复性。我们建议对您注册的函数进行版本控制,特别是如果您希望它们发生变化(例如新的模型架构)。这样,您就知道配置引用v1
和v2
是两个不同的功能。
FUNCTIONS.PY
import spacy
@spacy.registry.schedules("my_custom_schedule.v1")
def my_custom_schedule(start: int = 1, factor: float = 1.001):
while True:
yield start
start = start * factor
import spacy
@spacy.registry.schedules("my_custom_schedule.v1")
def my_custom_schedule(start: int = 1, factor: float = 1.001):
while True:
yield start
start = start * factor
在您的配置中,您现在可以在[training.batch_size]
块中通过@schedules
引用定义的schedules
。如果块包含以@
开头的键,则将其解释为对函数的引用。随后,块中的所有其他设置将作为关键字参数传递给函数。请记住,配置不应该有任何隐藏的默认值,并且函数的所有参数都需要在配置中表示。 CONFIG.CFG(摘录)
[training.batch_size]
@schedules = "my_custom_schedule.v1"
start = 2
factor = 1.005
[training.batch_size]
@schedules = "my_custom_schedule.v1"
start = 2
factor = 1.005
定义自定义架构
标记器(tagger)或命名实体识别器等内置管道组件是使用默认神经网络模型构建的。您可以通过实现自己的自定义模型并在创建管道组件时在配置中提供这些模型来完全更改模型架构。有关更多详细信息,请参阅有关层和模型架构的文档。
配置文件config.cfg
[components.tagger]
factory = "tagger"
[components.tagger.model]
@architectures = "custom_neural_network.v1"
output_width = 512
[components.tagger]
factory = "tagger"
[components.tagger.model]
@architectures = "custom_neural_network.v1"
output_width = 512
FUNCTIONS.PY
from typing import List
from thinc.types import Floats2d
from thinc.api import Model
import spacy
from spacy.tokens import Doc
@spacy.registry.architectures("custom_neural_network.v1")
def custom_neural_network(output_width: int) -> Model[List[Doc], List[Floats2d]]:
return create_model(output_width)
from typing import List
from thinc.types import Floats2d
from thinc.api import Model
import spacy
from spacy.tokens import Doc
@spacy.registry.architectures("custom_neural_network.v1")
def custom_neural_network(output_width: int) -> Model[List[Doc], List[Floats2d]]:
return create_model(output_width)
自定义初始化
当你从头开始训练一个新模型时,spacy train
将会通知nlp.initialize
初始化管道并加载所需的数据。 所有设置都在config配置文件中的[initialize]
块中定义,因此您可以跟踪初始nlp
对象的创建方式。初始化过程通常包括以下内容:
CONFIG.CFG(摘录)
[initialize]
vectors = ${paths.vectors}
init_tok2vec = ${paths.init_tok2vec}
[initialize.components]
# Settings for components
[initialize]
vectors = ${paths.vectors}
init_tok2vec = ${paths.init_tok2vec}
[initialize.components]
# Settings for components
- 加载
[initialize]
配置中定义的数据资源,包括词向量和预 训练的tok2vec weights
。 - 使用回调调用标记器(tokenizer)(如果实现,例如对于
Chinese
)和管道组件的initialize
方法,以访问训练数据、当前nlp
对象和配置中定义的任何自定义参数都在[initialize]
配置中定义。 - 在管道组件中:如果需要的话,使用数据来推断缺失的形状,如果没有提供标签,则设置标签方案。组件还可以加载其他数据,如查找表或字典。
初始化步骤允许配置文件定义管道所需的所有设置,同时将仅应在训练之前使用以设置初始管道的设置和功能与需要在运行时可用的逻辑和配置保持分离。如果没有这种分离,将很难使用相同的、可重现的配置文件,因为训练所需的组件设置(从外部文件加载数据)将与运行时所需的组件设置不匹配(加载保存nlp
对象中包含的内容并且不依赖于外部文件)。
📖组件如何保存和加载数据 有关管道组件如何保存和加载模型权重或查找表等数据资产的详细信息和示例,以及如何在后台实现组件初始化,请参阅有关序列化和初始化组件数据的使用指南 。
初始化标签(labels)
内置管道组件,如EntityRecognizer
或者DependencyParser
需要知道它们的可用标签和相关的内部元信息来初始化它们的模型权重。使用初始化时提供的get_examples
回调,他们能够自动从训练数据中读取标签,这非常方便,但它也会减慢训练过程因为在每次运行时都要计算这些信息。
这init labels
命令可自动生成包含所有受支持组件的标签数据的JSON
文件。然后,您可以在[initialize]
块中传入各个组件的标签,以允许它们更快地初始化。
配置文件 config.cfg
[initialize.components.ner]
[initialize.components.ner.labels]
@readers = "spacy.read_labels.v1"
path = "corpus/labels/ner.json
[initialize.components.ner]
[initialize.components.ner.labels]
@readers = "spacy.read_labels.v1"
path = "corpus/labels/ner.json
python -m spacy init labels config.cfg ./corpus --paths.train ./corpus/train.spacy
python -m spacy init labels config.cfg ./corpus --paths.train ./corpus/train.spacy
在底层,命令委托给管道组件的label_data
属性,例如 EntityRecognizer.label_data
!!重要的提示
每个组件的JSON
格式都不同,一些组件需要有关其标签的额外元信息。init labels
导出的格式匹配组件需要的内容,因此您应该始终让spaCy
为您自动生成标签。
数据实用工具
spaCy
提供多种功能和实用程序,可以让您轻松的使用自己的数据训练模型、管理训练和评估语料库、转换现有注释并配置数据增强策略以获得更强大的模型。
转换现有的语料库和注释(annotations)
如果您有标准格式的训练数据,例如.conll
或.conllu
,将其转换为spaCy
格式的最简单方法是运行spacy convert
并传递给它一个文件和一个输出目录。默认情况下,该命令将根据文件扩展名选择转换器。
python -m spacy convert ./train.gold.conll ./corpus
python -m spacy convert ./train.gold.conll ./corpus
💡提示:使用 PRODIGY 进行转换 如果您使用
Prodigy
注释工具创建训练数据,则可以运行data-to-spacy
命令来合并和导出多个数据集以供spacy train
使用 . 同一文本上的不同类型的注释将被组合在一起,为您提供一个语料库来训练多个组件。
💡提示:使用项目管理多步骤工作流 训练工作流通常由多个步骤组成,从预处理数据一直到打包和部署训练模型。
spaCy
项目让您可以在一个文件中定义所有步骤、管理数据资产、跟踪更改并与您的团队共享您的端到端流程。
.spacy
二进制格式是序列化的DocBin
,其中含有一个或多个Doc
对象。它的存储效率极高,尤其是在将多个文档打包在一起时。您还可以手动创建Doc
对象,因此您可以编写自己的自定义逻辑来转换和存储现有的注释,以便在spaCy
中使用。
来自DOC对象的训练数据
import spacy
from spacy.tokens import Doc, DocBin
nlp = spacy.blank("en")
docbin = DocBin()
words = ["Apple", "is", "looking", "at", "buying", "U.K.", "startup", "."]
spaces = [True, True, True, True, True, True, True, False]
ents = ["B-ORG", "O", "O", "O", "O", "B-GPE", "O", "O"]
doc = Doc(nlp.vocab, words=words, spaces=spaces, ents=ents)
docbin.add(doc)
docbin.to_disk("./train.spacy")
import spacy
from spacy.tokens import Doc, DocBin
nlp = spacy.blank("en")
docbin = DocBin()
words = ["Apple", "is", "looking", "at", "buying", "U.K.", "startup", "."]
spaces = [True, True, True, True, True, True, True, False]
ents = ["B-ORG", "O", "O", "O", "O", "B-GPE", "O", "O"]
doc = Doc(nlp.vocab, words=words, spaces=spaces, ents=ents)
docbin.add(doc)
docbin.to_disk("./train.spacy")
使用语料库
例子
[corpora]
[corpora.train]
@readers = "spacy.Corpus.v1"
path = ${paths.train}
gold_preproc = false
max_length = 0
limit = 0
augmenter = null
[training]
train_corpus = "corpora.train"
[corpora]
[corpora.train]
@readers = "spacy.Corpus.v1"
path = ${paths.train}
gold_preproc = false
max_length = 0
limit = 0
augmenter = null
[training]
train_corpus = "corpora.train"
配置中的[corpora]
块允许您定义用于训练、评估、预训练或任何其他自定义工作流程的数据资源。corpora.train
并corpora.dev
在spaCy
的默认配置中用作约定,但您也可以定义任何其他自定义块。语料库配置中的每个部分(section)都应解析为一个Corpus
。例如,使用spaCy
的内置 语料库阅读器,它需要一个.spacy
二进制文件的路径 。配置文件的[training]
块中的train_corpus
和dev_corpus
字段指定您的语料库的位置。这使得只需更改单个配置设置即可轻松更换不同的语料库。
除了为[corpora]
数据的每个部分制作具有多个小节的块之外,您还可以使用单个函数返回一个语料库字典,以语料库名称为键,例如"train"
和"dev"
。如果您需要将单个文件拆分为为两个语料库分别进行训练和评估,而不想两次加载同一文件,这将特别有用。
默认情况下,训练数据会在每个epoch
之前加载到内存中并进行混洗(shuffled)。如果在训练期间语料库太大而无法放入内存,请使用自定义阅读器流式传输语料库,如下一节所述。
自定义数据读取和批处理
一些情况下需要流式传输数据或动态操作数据集,而不是预先生成所有数据并将其存储到磁盘上。内置的Corpus
读取器已经无法满足需要,需要使用静态文件路径,创建和注册一个自定义函数,生成Example
对象。
在下面的示例中,我们假设一个自定义函数read_custom_data
加载或生成具有相关文本分类注释的文本。 然后,输入文本的词汇变化(lexical variations)在生成最终的Example
对象之前创建。
@spacy.registry.readers
装饰器能够让你通过注册函数在readers
注册表创建的自定义阅读器,并分配一个字符串名称,然后就可以在配置中使用了。注册函数上的所有参数都可以作为配置设置使用,例如以下示例中的source
。
配置文件
[corpora.train]
# 指定使用自定义数据读取@spacy.registry.readers("corpus_variants.v1")
@readers = "corpus_variants.v1"
# 设置参数,该参数时下面stream_data的参数
source = "s3://your_bucket/path/data.csv"
[corpora.train]
# 指定使用自定义数据读取@spacy.registry.readers("corpus_variants.v1")
@readers = "corpus_variants.v1"
# 设置参数,该参数时下面stream_data的参数
source = "s3://your_bucket/path/data.csv"
FUNCTIONS.PY
from typing import Callable, Iterator, List
import spacy
from spacy.training import Example
from spacy.language import Language
import random
@spacy.registry.readers("corpus_variants.v1")
def stream_data(source: str) -> Callable[[Language], Iterator[Example]]:
def generate_stream(nlp):
for text, cats in read_custom_data(source):
# Create a random variant of the example text
i = random.randint(0, len(text) - 1)
variant = text[:i] + text[i].upper() + text[i + 1:]
doc = nlp.make_doc(variant)
example = Example.from_dict(doc, {"cats": cats})
yield example
return generate_stream
from typing import Callable, Iterator, List
import spacy
from spacy.training import Example
from spacy.language import Language
import random
@spacy.registry.readers("corpus_variants.v1")
def stream_data(source: str) -> Callable[[Language], Iterator[Example]]:
def generate_stream(nlp):
for text, cats in read_custom_data(source):
# Create a random variant of the example text
i = random.randint(0, len(text) - 1)
variant = text[:i] + text[i].upper() + text[i + 1:]
doc = nlp.make_doc(variant)
example = Example.from_dict(doc, {"cats": cats})
yield example
return generate_stream
请记住,注册函数就是
spaCy
调用以创建某些内容的函数。在这种情况下,它创建了阅读器函数,但它不是阅读器本身。
为什么训练的数据集必须
shuffle
打乱数据之间的顺序,让数据随机化,避免过拟合 以猫狗分类为例,训练数据集如下:
Dog,Dog,Dog,... ,Dog,Dog,Dog,Cat,Cat,Cat,Cat,... ,Cat,Cat
Dog,Dog,Dog,... ,Dog,Dog,Dog,Cat,Cat,Cat,Cat,... ,Cat,Cat
所有的狗都在猫前面,如果不shuffle
,模型训练一段时间内只看到了Dog
,必然会过拟合于Dog
,一段时间内又只能看到Cat
,必然又过拟合于Cat
,这样的模型泛化能力必然很差。
如果语料库太大而无法加载到内存中,或者语料库阅读器是无限生成器,请使用max_epochs = -1
设置来指示应该流式传输训练语料库。通过这种设置,训练语料库只是流式传输和批处理,而不会混洗(shuffled),因此任何混洗都需要在语料库阅读器中实现。在下面的示例中,用于生成句子语料阅读器包含偶数或奇数对应数量不受限制的训练语料 示例和限制数量的开发语料的示例。
在评估过程中,开发语料库应该始终是有限的并且适合内存。max_steps
和(或)patience
用于确定训练何时停止。
CONFIG.CFG
[corpora.dev]
@readers = "even_odd.v1"
limit = 100
[corpora.train]
@readers = "even_odd.v1"
limit = -1
[training]
max_epochs = -1
patience = 500
max_steps = 2000
[corpora.dev]
@readers = "even_odd.v1"
limit = 100
[corpora.train]
@readers = "even_odd.v1"
limit = -1
[training]
max_epochs = -1
patience = 500
max_steps = 2000
FUNCTIONS.PY
from typing import Callable, Iterable, Iterator
from spacy import util
import random
from spacy.training import Example
from spacy import Language
@util.registry.readers("even_odd.v1")
def create_even_odd_corpus(limit: int = -1) -> Callable[[Language], Iterable[Example]]:
return EvenOddCorpus(limit)
class EvenOddCorpus:
def __init__(self, limit):
self.limit = limit
def __call__(self, nlp: Language) -> Iterator[Example]:
i = 0
while i < self.limit or self.limit < 0:
r = random.randint(0, 1000)
cat = r % 2 == 0
text = "This is sentence " + str(r)
yield Example.from_dict(
nlp.make_doc(text), {"cats": {"EVEN": cat, "ODD": not cat}}
)
i += 1
from typing import Callable, Iterable, Iterator
from spacy import util
import random
from spacy.training import Example
from spacy import Language
@util.registry.readers("even_odd.v1")
def create_even_odd_corpus(limit: int = -1) -> Callable[[Language], Iterable[Example]]:
return EvenOddCorpus(limit)
class EvenOddCorpus:
def __init__(self, limit):
self.limit = limit
def __call__(self, nlp: Language) -> Iterator[Example]:
i = 0
while i < self.limit or self.limit < 0:
r = random.randint(0, 1000)
cat = r % 2 == 0
text = "This is sentence " + str(r)
yield Example.from_dict(
nlp.make_doc(text), {"cats": {"EVEN": cat, "ODD": not cat}}
)
i += 1
CONFIG.CFG
[initialize.components.textcat.labels]
@readers = "spacy.read_labels.v1"
path = "labels/textcat.json"
require = true
[initialize.components.textcat.labels]
@readers = "spacy.read_labels.v1"
path = "labels/textcat.json"
require = true
如果训练语料库是流式传输的,则初始化步骤会查看语料库中的前 100 个示例以查找每个组件的标签。如果这还不够,您需要为[initialize]
块中的每个组件提供标签。init labels
可用于生成正确格式的JSON
文件,您可以使用完整的标签集对其进行扩展。
我们还可以通过在注册表中注册一个新的批处理器函数来自定义批处理策略。批处理器(batcher)将项目流转换为批次流。spaCy
有几个有用的可自定义大小的内置批处理策略,同时实现自定义的批处理策略也很简单。例如,以下函数采用生成的Example
对象流,并删除那些具有相同底层原始文本的对象,以避免在每个批次中出现重复。请注意,在更实际的实现中,您还需要检查注释是否相同。
CONFIG.CFG
[training.batcher]
@batchers = "filtering_batch.v1"
size = 150
[training.batcher]
@batchers = "filtering_batch.v1"
size = 150
FUNCTIONS.PY
from typing import Callable, Iterable, Iterator, List
import spacy
from spacy.training import Example
@spacy.registry.batchers("filtering_batch.v1")
def filter_batch(size: int) -> Callable[[Iterable[Example]], Iterator[List[Example]]]:
def create_filtered_batches(examples):
batch = []
for eg in examples:
# Remove duplicate examples with the same text from batch
if eg.text not in [x.text for x in batch]:
batch.append(eg)
if len(batch) == size:
yield batch
batch = []
return create_filtered_batches
from typing import Callable, Iterable, Iterator, List
import spacy
from spacy.training import Example
@spacy.registry.batchers("filtering_batch.v1")
def filter_batch(size: int) -> Callable[[Iterable[Example]], Iterator[List[Example]]]:
def create_filtered_batches(examples):
batch = []
for eg in examples:
# Remove duplicate examples with the same text from batch
if eg.text not in [x.text for x in batch]:
batch.append(eg)
if len(batch) == size:
yield batch
batch = []
return create_filtered_batches
数据增强
数据增强是对训练数据进行小的修改的过程。它对于标点符号和大小写替换特别有用。例如,如果您的语料库仅使用智能引号并且您希望使用常规引号,或者通过混合包含大写和小写示例来使模型对大写不那么敏感。
在训练期间使用数据增强的最简单方法是为训练语料库提供一个augmenter
,例如在配置的[corpora.train]
部分(section)。内置的orth_variants
增强器创建一个使用orth-variant
替换的数据增强回调。
CONFIG.CFG (EXCERPT)
[corpora.train]
@readers = "spacy.Corpus.v1"
path = ${paths.train}
gold_preproc = false
max_length = 0
limit = 0
[corpora.train.augmenter]
@augmenters = "spacy.orth_variants.v1"
# Percentage of texts that will be augmented / lowercased
level = 0.1
lower = 0.5
[corpora.train.augmenter.orth_variants]
@readers = "srsly.read_json.v1"
path = "corpus/orth_variants.json"
[corpora.train]
@readers = "spacy.Corpus.v1"
path = ${paths.train}
gold_preproc = false
max_length = 0
limit = 0
[corpora.train.augmenter]
@augmenters = "spacy.orth_variants.v1"
# Percentage of texts that will be augmented / lowercased
level = 0.1
lower = 0.5
[corpora.train.augmenter.orth_variants]
@readers = "srsly.read_json.v1"
path = "corpus/orth_variants.json"
这个orth_variants
参数允许您传入替换规则的字典,通常从JSON
文件中加载。有两种类型的orth
规则: "single"
用于需要被替换的单个标记(token,例如连字符);"paired"
用于替换一对标记(例如引号)。
ORTH_VARIANTS.JSON
{
"single": [{ "tags": ["NFP"], "variants": ["…", "..."] }],
"paired": [{ "tags": ["``", "''"], "variants": [["'", "'"], ["‘", "’"]] }]
}
{
"single": [{ "tags": ["NFP"], "variants": ["…", "..."] }],
"paired": [{ "tags": ["``", "''"], "variants": [["'", "'"], ["‘", "’"]] }]
}
英语和德语的完整示例
explosion/spacy-lookups-data/master/spacy_lookups_data/data/en_orth_variants.json
重要的提示
添加数据增强时,请记住,通常只将其应用于训练语料库才有意义,而不是开发数据。
编写自定义数据增强器
使用@spacy.augmenters
注册,您可以注册自己的数据增强回调。回调应该是一个接受当前nlp
对象和训练的Example
并产生一系列Example
对象。请记住,增强器应生成您想在语料库中使用的所有示例,而不仅仅是增强示例(除非您想增强所有示例)。
这是在“SpOnGeBoB cAsE”
中生成文本变体的自定义增强回调示例。注册的函数接受一个可以通过配置设置的randomize
参数,并决定是否随机应用大写/小写转换。增强器产生两个Example
对象:原始示例和增强示例。
CONFIG.CFG
[corpora.train.augmenter]
@augmenters = "spongebob_augmenter.v1"
randomize = false
[corpora.train.augmenter]
@augmenters = "spongebob_augmenter.v1"
randomize = false
import spacy
import random
@spacy.registry.augmenters("spongebob_augmenter.v1")
def create_augmenter(randomize: bool = False):
def augment(nlp, example):
text = example.text
if randomize:
# Randomly uppercase/lowercase characters
chars = [c.lower() if random.random() < 0.5 else c.upper() for c in text]
else:
# Uppercase followed by lowercase
chars = [c.lower() if i % 2 else c.upper() for i, c in enumerate(text)]
# Create augmented training example
example_dict = example.to_dict()
doc = nlp.make_doc("".join(chars))
example_dict["token_annotation"]["ORTH"] = [t.text for t in doc]
# Original example followed by augmented example
yield example
yield example.from_dict(doc, example_dict)
return augment
import spacy
import random
@spacy.registry.augmenters("spongebob_augmenter.v1")
def create_augmenter(randomize: bool = False):
def augment(nlp, example):
text = example.text
if randomize:
# Randomly uppercase/lowercase characters
chars = [c.lower() if random.random() < 0.5 else c.upper() for c in text]
else:
# Uppercase followed by lowercase
chars = [c.lower() if i % 2 else c.upper() for i, c in enumerate(text)]
# Create augmented training example
example_dict = example.to_dict()
doc = nlp.make_doc("".join(chars))
example_dict["token_annotation"]["ORTH"] = [t.text for t in doc]
# Original example followed by augmented example
yield example
yield example.from_dict(doc, example_dict)
return augment
创建修改Example
对象的一种简单方法是使用Example.from_dict
方法,使用一个从修改后的文本中创建的新的Doc
引用。在这种情况下,只有大小写发生变化,因此在原始示例和增强示例之间tokens
的ORTH
值会不同。
请注意,如果您的数据增强策略涉及更改tokenizations
(例如,删除或添加标记)并且您的训练示例包括基于标记(token)的注释(例如依赖项解析或实体标签),则您需要注意调整Example
对象,使其注释匹配并保持有效。
使用 Ray 进行并行和分布式训练
安装 INSTALLATION
pip install -U spacy[ray]
# Check that the CLI is registered
python -m spacy ray --help
pip install -U spacy[ray]
# Check that the CLI is registered
python -m spacy ray --help
Ray
是一个快速而简单的框架,用于构建和运行 分布式应用程序。您可以使用Ray
在一台或多台远程机器上训练spaCy
,这可能会加快您的训练过程。不过,并行训练并不总是更快——这取决于您的批次大小、模型和硬件。
要想在
spaCy
中使用Ray
,您需要安装spacy-ray
安装包。安装包会自动将ray
命令添加到spaCy CLI
。
spacy ray train
命令与spacy train
遵循相同的API
,以及一些额外的选项来配置Ray
设置。您可以使用--address
选项指定您的Ray
集群。如果未设置,Ray
将在本地运行。
python -m spacy ray train config.cfg --n-workers 2
python -m spacy ray train config.cfg --n-workers 2
使用项目模板: integrations/ray
使用我们的项目模板开始并行训练。它让您使用Ray
进行并行训练,并在Universal Dependencies Treebank
上训练一个简单的模型。
$ python -m spacy project clone integrations/ray
$ python -m spacy project clone integrations/ray
并行训练的工作原理
每个worker
接收数据的一个分片,并从config.cfg
中创建一个模型和优化器(optimizer)的副本. 它还具有将梯度和参数传递给其他worker
的通信渠道。此外,每个worker
都获得了参数数组子集的所有权。每个参数数组都由一个worker
拥有,并且worker
被赋予一个映射,以便他们知道哪个worker
拥有哪个参数。
随着训练的进行,每个worker
都将计算所有模型参数的梯度。当他们计算不属于他们的参数的梯度时,他们会将这些梯度连同版本标识符一起发送给拥有该参数的worker
,以便所有者可以决定是否丢弃梯度。worker
使用他们收到的梯度和他们在本地计算的梯度来更新他们拥有的参数,然后将更新后的数组和新版本ID
广播给其他工作人员。
这个训练过程是异步和非阻塞的。worker
们总是推送他们的梯度增量和参数更新,而不必主动拉取并阻塞结果,因此转移可以在后台发生,与实际训练工作重叠。worker
也不必在每批(batch)开始时停下来互相等待(“同步”)。这对spaCy
非常有用,因为spaCy
通常是在长文档上训练的,这意味着批次的大小可能会有很大差异。不均匀的工作负载使同步梯度下降效率低下,因为如果一个批次很慢,所有其他worker
员都需要等待它完后才能继续。
内部训练API
spaCy
可让您完全控制训练循环。但是,对于大多数用例,建议通过spacy train
命令结合config.cfg
跟踪您的设置和超参数,而不是从头编写您自己的训练脚本。自定义注册函数通常应该为您提供使用spacy train
训练自定义管道所需的一切。
使用Python脚本训练 V 3.2
如果您想从Python
脚本运行训练而不是使用spacy train
CLI 命令,你可以直接调用train
辅助函数。它需要配置文件的路径、一个可选的输出目录和一个可选的config overrides
字典。
from spacy.cli.train import train
train("./config.cfg", overrides={"paths.train": "./train.spacy", "paths.dev": "./dev.spacy"})
from spacy.cli.train import train
train("./config.cfg", overrides={"paths.train": "./train.spacy", "paths.dev": "./dev.spacy"})
内部训练循环 API
本节记录了训练循环和更新
nlp
对象内部是如何工作的。除非您正在编写自己的可训练组件,否则您通常不必在Python
中实现它。要训练管道,请使用spacy train
或者train
代替辅助函数。
Example
对象包含带注释的训练数据,也称为黄金标准( gold standard)。它使用Doc
对象初始化并将保存预测,以及另一个保存黄金标准注释的Doc
对象。如果它们在标记化(tokenization)方面不同,它还包括这两个文档之间的对齐。Example
类确保spaCy
可以依靠一个标准化的格式通过管道传递。例如,假设我们要定义黄金标准的词性标签:
words = ["I", "like", "stuff"]
predicted = Doc(vocab, words=words)
# create the reference Doc with gold-standard TAG annotations
tags = ["NOUN", "VERB", "NOUN"]
tag_ids = [vocab.strings.add(tag) for tag in tags]
reference = Doc(vocab, words=words).from_array("TAG", numpy.array(tag_ids, dtype="uint64"))
example = Example(predicted, reference)
words = ["I", "like", "stuff"]
predicted = Doc(vocab, words=words)
# create the reference Doc with gold-standard TAG annotations
tags = ["NOUN", "VERB", "NOUN"]
tag_ids = [vocab.strings.add(tag) for tag in tags]
reference = Doc(vocab, words=words).from_array("TAG", numpy.array(tag_ids, dtype="uint64"))
example = Example(predicted, reference)
由于这非常冗长,因此有一种替代方法可以使用黄金标准注释创建参考Doc
。Example.from_dict
函数采用带有指定注释的关键字参数的字典,例如tags
, entities
。使用生成的Example
对象及其黄金标准注释,可以更新模型以学习三个单词的句子,并为其分配词性标签。
words = ["I", "like", "stuff"]
tags = ["NOUN", "VERB", "NOUN"]
predicted = Doc(nlp.vocab, words=words)
example = Example.from_dict(predicted, {"tags": tags})
words = ["I", "like", "stuff"]
tags = ["NOUN", "VERB", "NOUN"]
predicted = Doc(nlp.vocab, words=words)
example = Example.from_dict(predicted, {"tags": tags})
这是显示如何定义黄金标准命名实体的另一个示例。字母前加的标签是BILUO
方案的标签。其中,O
代表token
实体在实体之外(通常实体标记也是一种序列标记,会标记token在实体外,实体的开始,实体的结束),U
是一个实体单元,B一个实体的开头,I
代表在一个实体内,L
表示当前token
是实体的最后一个token
。
doc = Doc(nlp.vocab, words=["Facebook", "released", "React", "in", "2014"])
example = Example.from_dict(doc, {"entities": ["U-ORG", "O", "U-TECHNOLOGY", "O", "U-DATE"]})
doc = Doc(nlp.vocab, words=["Facebook", "released", "React", "in", "2014"])
example = Example.from_dict(doc, {"entities": ["U-ORG", "O", "U-TECHNOLOGY", "O", "U-DATE"]})
从 v2.x 迁移
从v3.0
开始,Example
对象代替GoldParse
类。它可以以非常相似的方式构建。从 一个Doc
对象和一个注释字典。有关更多详细信息,请参阅迁移指南。
- gold = GoldParse(doc, entities=entities)
+ example = Example.from_dict(doc, {"entities": entities})
- gold = GoldParse(doc, entities=entities)
+ example = Example.from_dict(doc, {"entities": entities})
当然,仅展示一个模型的单个example
一次是不够的。特别是如果您只有很少的示例,您将需要训练多次迭代。在每次迭代中,训练数据都会被打乱,以确保模型不会根据示例的顺序进行任何泛化。另一种改善学习结果的技术是设置dropout rate
,即随机“丢弃”单个特征和表示。这使得模型更难记住训练数据。例如,0.25
丢弃率意味着每个特征或内部表示都有1/4
的可能性被丢弃。
nlp
:nlp
对象包括管道组件及其模型的对象。nlp.initialize
:初始化管道并返回一个优化器来更新组件模型权重。Optimizer
:在更新之间保持状态的函数。nlp.update
:使用示例(examples)更新组件模型。Example
:持有预测和黄金标准注释的对象。nlp.to_disk
:将更新后的管道保存到一个目录中。
示例训练循环
optimizer = nlp.initialize()
for itn in range(100):
random.shuffle(train_data)
for raw_text, entity_offsets in train_data:
doc = nlp.make_doc(raw_text)
example = Example.from_dict(doc, {"entities": entity_offsets})
nlp.update([example], sgd=optimizer)
nlp.to_disk("/output")
optimizer = nlp.initialize()
for itn in range(100):
random.shuffle(train_data)
for raw_text, entity_offsets in train_data:
doc = nlp.make_doc(raw_text)
example = Example.from_dict(doc, {"entities": entity_offsets})
nlp.update([example], sgd=optimizer)
nlp.to_disk("/output")
nlp.update
方法采用以下参数:
名称 | 描述 |
---|---|
examples | Example 对象。update 方法会使用Example 对象的序列作为参数,因此您可以批量处理您的训练示例。 |
drop | 丢弃率。使模型更难以记住数据。 |
sgd | 一个Optimizer 对象,用于更新模型的权重。如果未设置,spaCy 会创建一个新的并保存以供进一步使用。 |