指南

语言处理流水线

当你对文本调用nlp时,spaCy首先会对文本进行分词以生成Doc对象。然后Doc会经过多个不同步骤的处理——这也被称为处理管道训练好的管道通常包含词性标注器、词形还原器、依存分析器和实体识别器。每个管道组件都会返回处理后的Doc对象,然后传递给下一个组件。

The processing pipeline
名称组件创建内容描述
tokenizerTokenizerDocSegment text into tokens.
处理流程
taggerTaggerToken.tagAssign part-of-speech tags.
parserDependencyParserToken.head, Token.dep, Doc.sents, Doc.noun_chunksAssign dependency labels.
nerEntityRecognizerDoc.ents, Token.ent_iob, Token.ent_typeDetect and label named entities.
lemmatizerLemmatizerToken.lemmaAssign base forms.
textcatTextCategorizerDoc.catsAssign document labels.
customcustom componentsDoc._.xxx, Token._.xxx, Span._.xxxAssign custom attributes, methods or properties.

处理管道的能力始终取决于其组件、模型及训练方式。例如,一个命名实体识别管道需要包含训练好的命名实体识别器组件,该组件需配备统计模型和权重,使其能够预测实体标签。这就是为什么每个管道都会在config中指定其组件及配置:

统计组件如词性标注器或解析器通常是独立的,彼此之间不共享任何数据。例如,命名实体识别器不会使用标注器和解析器设置的任何特征,依此类推。这意味着您可以替换它们,或从处理流程中移除单个组件而不影响其他组件。然而,组件可能会共享一个"token-to-vector"组件,如Tok2VecTransformer。您可以在嵌入层文档中了解更多相关信息。

自定义组件可能依赖于其他组件设置的注解。例如,一个自定义词形还原器可能需要已分配的词性标签,因此它只有在标注器之后添加才能正常工作。解析器会遵循预定义的句子边界,因此如果流水线中的前一个组件设置了这些边界,其依存关系预测可能会有所不同。同样地,在统计实体识别器之前或之后添加EntityRuler也很重要:如果在之前添加,实体识别器在做出预测时会考虑现有实体。而将命名实体解析为知识库ID的EntityLinker,应该位于能识别实体的流水线组件(如EntityRecognizer)之后。

分词器是一个"特殊"组件,不属于常规处理流程的一部分。 它也不会出现在nlp.pipe_names中。原因是实际上只能有一个分词器,而其他所有流程组件都接收Doc并返回它,分词器则是接收文本字符串并将其转换为Doc。不过您仍然可以自定义分词器。nlp.tokenizer是可写的,因此您既可以Tokenizer class from scratch从头创建自己的分词器类,也可以用entirely custom function完全自定义的函数来替换它。


处理文本

当你对文本调用nlp时,spaCy会先对其进行分词,然后按顺序调用每个组件处理Doc对象。最终返回处理后的Doc供你使用。

在处理大量文本时,如果让统计模型批量处理文本,通常效率会更高。spaCy的nlp.pipe方法接收可迭代的文本并生成处理后的Doc对象。批处理是在内部完成的。

在这个示例中,我们使用nlp.pipe以流式方式处理一个(可能非常大的)可迭代文本集合。由于我们只需要访问doc.ents中的命名实体(由ner组件设置),我们将在处理过程中禁用所有其他组件。nlp.pipe会生成Doc对象,因此我们可以遍历它们并访问命名实体预测结果:

可编辑代码spaCy v3.7 · Python 3 · 通过 Binder

你可以使用as_tuples选项,在使用nlp.pipe时为每个文档传递额外的上下文。如果as_tuples设为True,那么输入应该是(text, context)元组序列,输出将是(doc, context)元组序列。例如,你可以在上下文中传递元数据并将其保存在自定义属性中:

可编辑代码spaCy v3.7 · Python 3 · 通过 Binder

多进程处理

spaCy内置支持通过nlp.pipe使用n_process选项进行多进程处理:

根据您的平台,使用多进程启动大量进程可能会带来很大的开销。特别是macOS/OS X(从Python 3.8开始)和Windows中使用的默认启动方法spawn,对于较大的模型可能会很慢,因为模型数据会在内存中为每个新进程复制。更多详情请参阅Python多进程文档

对于较短任务,尤其是使用spawn时,采用较少进程配合较大批量处理可能更快。最佳batch_size设置取决于流水线组件、文档长度、进程数量以及可用内存大小。

Pipelines and built-in components

spaCy可以非常方便地创建由可复用组件组成的自定义流程管道——这包括spaCy默认的词性标注器、依存解析器和实体识别器,同时也支持您自定义的处理函数。管道组件可以被添加到已有的nlp对象中,在初始化Language类时指定,或在管道包内定义。

当你加载一个流程时,spaCy首先会查阅 meta.jsonconfig.cfg文件。配置文件告诉spaCy应该使用什么语言类,流程中包含哪些组件,以及这些组件应该如何创建。spaCy随后会执行以下操作:

  1. 通过get_lang_class加载给定ID对应的语言类和数据并进行初始化。Language类包含共享词汇表、分词规则以及特定语言的设置。
  2. 遍历管道名称并在[components]块中查找每个组件名称。factory告诉spaCy使用哪个组件工厂来通过add_pipe添加组件。设置参数会被传入工厂。
  3. 通过调用from_disk并传入数据目录路径,使模型数据可供Language类使用。

所以当你调用这个…

... 管道的 config.cfg 文件指示 spaCy 使用语言 "en" 和管道组件 ["tok2vec", "tagger", "parser", "ner", "attribute_ruler", "lemmatizer"]。spaCy 随后会初始化 spacy.lang.en.English,创建每个管道组件 并将其添加到处理管道中。接着它会从数据目录加载模型数据, 并返回修改后的 Language 类供您作为 nlp 对象使用。

从根本上说,一个spaCy管道包包含三个组成部分: 权重数据(即从目录加载的二进制数据)、按顺序调用的函数管道, 以及语言数据(如分词规则和语言特定设置)。例如,西班牙语NER管道需要 与英语解析和标注管道不同的权重数据、语言数据和组件。这也是为什么管道状态始终由 Language类持有。spacy.load会将这些整合在一起, 返回一个设置了管道并可访问二进制数据的Language类实例:

spacy.load 内部机制解析(抽象示例)

当你对文本调用nlp时,spaCy会先对其进行分词,然后按顺序依次调用每个组件处理Doc对象。由于模型数据已加载,各组件可以访问这些数据来为Doc对象添加标注,进而作用于TokenSpan——它们只是Doc的视图,自身并不持有数据。所有组件都会返回修改后的文档,供流程中的下一个组件继续处理。

底层处理流程解析

当前的处理流程可通过nlp.pipeline获取,它会返回一个由(name, component)元组组成的列表;或者通过nlp.pipe_names获取,它仅返回人类可读的组件名称列表。

内置管道组件

spaCy内置了多个预定义的管道组件,这些组件通过字符串名称进行注册。这意味着您可以通过调用nlp.add_pipe并传入组件名称来初始化它们,spaCy会自动知道如何创建这些组件。完整可用的管道组件和组件函数列表请参阅API文档

字符串名称组件描述
taggerTaggerAssign part-of-speech-tags.
parserDependencyParserAssign dependency labels.
nerEntityRecognizerAssign named entities.
entity_linkerEntityLinkerAssign knowledge base IDs to named entities. Should be added after the entity recognizer.
entity_rulerEntityRulerAssign named entities based on pattern rules and dictionaries.
textcatTextCategorizerAssign text categories: exactly one category is predicted per document.
textcat_multilabelMultiLabel_TextCategorizerAssign text categories in a multi-label setting: zero, one or more labels per document.
lemmatizerLemmatizerAssign base forms to words using rules and lookups.
trainable_lemmatizerEditTreeLemmatizerAssign base forms to words.
morphologizerMorphologizerAssign morphological features and coarse-grained POS tags.
attribute_rulerAttributeRulerAssign token attribute mappings and rule-based exceptions.
senterSentenceRecognizerAssign sentence boundaries.
sentencizerSentencizerAdd rule-based sentence segmentation without the dependency parse.
tok2vecTok2VecAssign token-to-vector embeddings.
transformerTransformerAssign the tokens and outputs of a transformer model.

禁用、排除和修改组件

如果您不需要流水线中的某个特定组件——例如词性标注器或解析器,您可以禁用或排除它。这有时会产生显著差异,提高加载和推理速度。您可以使用两种不同的机制来实现:

  1. 禁用:该组件及其数据会随管道加载,但默认处于禁用状态,不会作为处理管道的一部分运行。如需运行,可通过调用nlp.enable_pipe显式启用。当保存nlp对象时,被禁用的组件会被包含在内但默认处于禁用状态。
  2. 排除: 不加载该组件及其数据到管道中。一旦管道加载完成,将不再引用被排除的组件。

可以将禁用和排除的组件名称作为列表提供给spacy.load

除了disable之外,spacy.load()还接受enable参数。如果设置了enable,那么除了enable中包含的组件外,其他所有组件都会被禁用。如果enabledisable发生冲突(即同一个组件同时出现在两个参数中),则会引发错误。

作为快捷方式,您可以使用nlp.select_pipes上下文管理器来临时禁用特定组件。在with代码块结束时,被禁用的管道组件将自动恢复。或者,select_pipes会返回一个对象,允许您调用其restore()方法在需要时恢复被禁用的组件。当您希望避免对大段代码进行不必要的缩进时,这会很有用。

禁用块

如果只想启用一个或少数几个管道而禁用其他所有管道,可以使用enable关键字。与disable关键字类似,它接受管道名称列表或定义单个管道的字符串。

nlp.pipe 方法还支持 disable 关键字参数,如果您只想在处理过程中禁用某些组件:

最后,你也可以使用remove_pipe方法 从现有管道中移除组件,使用 rename_pipe方法重命名它们,或者使用 replace_pipe方法完全替换为 自定义组件(更多细节请参阅 自定义组件章节)。

Language对象公开了不同的属性,这些属性可让您检查所有可用组件以及当前作为管道一部分运行的组件。

名称描述
nlp.pipeline(name, component) tuples of the processing pipeline, in order.
nlp.pipe_namesPipeline component names, in order.
nlp.componentsAll (name, component) tuples, including disabled components.
nlp.component_namesAll component names, including disabled components.
nlp.disabledNames of components that are currently disabled.

从现有管道中获取组件 v3.0

可以独立运行的管道组件也可以跨管道重复使用。 除了添加一个新的空白组件外,您还可以通过设置nlp.add_pipe上的source参数,从训练好的管道中复制现有组件。第一个参数随后将被解释为源管道中的组件名称——例如"ner"。这对于训练管道特别有用,因为它允许您混合搭配组件,并使用更新后的训练组件和针对您数据训练的新组件创建完全自定义的管道包。

可编辑代码spaCy v3.7 · Python 3 · 通过 Binder

分析流水线组件 v3.0

nlp.analyze_pipes 方法会分析当前流水线中的组件,并输出相关信息,例如它们在DocToken上设置的属性、是否会对Doc进行重新分词,以及训练过程中产生的评分。如果组件需要但前置组件未设置所需的值(例如使用了实体链接器但前置组件未设置命名实体),该方法还会显示警告。设置pretty=True将以美观的表格形式输出,而非仅返回结构化数据。

可编辑代码spaCy v3.7 · Python 3 · 通过 Binder

结构化

美观

创建自定义管道组件

管道组件是一个接收Doc对象、修改并返回该对象的函数——例如,通过使用当前权重进行预测并在文档上设置一些注释。通过向管道添加组件,您可以在处理过程中的任何时间点访问Doc——而不仅限于事后修改。

参数类型描述
docDocThe Doc object processed by the previous component.

@Language.component装饰器可以将一个简单函数转换为流水线组件。它至少需要一个参数,即组件工厂的名称。您可以使用此名称将组件实例添加到流水线中。该名称也可以列在流水线配置中,从而能够保存、加载和训练使用该组件的流水线。

可以通过add_pipe方法将自定义组件添加到处理流程中。可选地,您可以指定组件添加的前或后位置,告诉spaCy将其添加在流程的首位或末位,或者定义一个自定义名称。如果未设置名称且组件上没有name属性,则使用函数名称。

参数描述
lastIf set to True, component is added last in the pipeline (default). bool
firstIf set to True, component is added first in the pipeline. bool
beforeString name or index to add the new component before. Union[str, int]
afterString name or index to add the new component after. Union[str, int]

示例:简单的无状态管道组件

以下组件接收管道中的Doc并打印其相关信息:包括词符数量、词符的词性标注标签,以及基于文档长度的条件性消息。@Language.component装饰器允许您以"info_component"名称注册该组件。

可编辑代码spaCy v3.7 · Python 3 · 通过 Binder

这是另一个管道组件的示例,它实现了自定义逻辑来改进依赖解析器设置的句子边界。因此,自定义逻辑应在分词之后应用,但在依赖解析之前——这样,解析器也可以利用句子边界。

可编辑代码spaCy v3.7 · Python 3 · 通过 Binder

组件工厂与有状态组件

组件工厂是可调用对象,它们接收设置参数并返回一个管道组件函数。当您的组件具有状态且需要自定义创建方式时,或者需要访问当前nlp对象或共享词汇表时,这非常有用。组件工厂可以使用@Language.factory装饰器进行注册,它们至少需要两个命名参数,这些参数会在组件被添加到管道时自动填充:

参数描述
nlpThe current nlp object. Can be used to access the shared vocab. Language
nameThe instance name of the component in the pipeline. This lets you identify different instances of the same component. str

所有其他设置都可以由用户通过nlp.add_pipe上的config参数传入。@Language.factory装饰器还允许您定义一个default_config作为备用配置。

使用配置

@Language.component装饰器本质上是一个快捷方式,适用于不需要任何设置的无状态管道组件。这意味着如果不需要传递状态,您不必总是编写返回函数的函数——spaCy可以为您处理这个问题。以下两个代码示例是等效的:

是的,@Language.factory装饰器可以添加到函数或类上。如果添加到类,它期望__init__方法接收nlpname参数,并将从配置中填充所有其他参数。也就是说,通常将工厂设为单独的函数会更清晰直观。spaCy内部也是这样做的。

语言特定的工厂 v3.0

在许多应用场景中,您可能希望流水线组件具备语言特异性。有时这需要为每种语言实现完全不同的逻辑,有时仅需调整配置或数据。spaCy允许您在Language基类及其子类(如EnglishGerman)上注册同名工厂函数。系统会优先从具体子类开始解析工厂函数,若子类未定义该名称的组件,spaCy将检查Language基类。

这是一个管道组件的示例,它会用特定语言的查找表中的条目覆盖标记的规范化形式Token.norm_。该组件以名称"token_normalizer"注册了两次——一次使用@English.factory,另一次使用@German.factory

可编辑代码spaCy v3.7 · Python 3 · 通过 Binder

示例:带设置的有状态组件

这个示例展示了一个有状态的管道组件,用于处理缩写词: 基于字典,它能够双向检测缩写词及其完整形式,并将它们添加到自定义的doc._.acronyms 扩展属性列表中。在底层实现中,它使用了 PhraseMatcher来查找短语实例。

工厂函数接收三个参数:共享的nlp对象和组件实例name(这两个参数由spaCy自动传入),以及一个case_sensitive配置项(该设置会使匹配和首字母缩写检测区分大小写)。

可编辑代码spaCy v3.7 · Python 3 · 通过 Binder

初始化和序列化组件数据

许多有状态的组件依赖于数据资源,比如字典和查找表,这些资源最好应该是可配置的。例如,将上面示例中的DICTIONARY作为注册函数的参数就很有意义,这样AcronymComponent就可以与不同的数据一起重复使用。一个合理的解决方案是将其作为组件工厂的参数,并允许使用不同的字典进行初始化。

然而,直接传入字典是有问题的,因为这意味着如果组件保存其配置和设置,config.cfg将包含整个数据的转储,因为这是创建组件时使用的配置。如果数据不可JSON序列化,它也会失败。

选项1:使用已注册函数

如果你传入的内容无法被JSON序列化——例如像model这样的自定义对象——那么保存组件配置将变得不可能,因为spaCy无法知晓该对象是如何被创建的,以及该如何重新创建它。这使得保存、加载和训练带有自定义组件的定制流程变得异常困难。一个简单的解决方案是注册一个返回资源的函数registry允许你将字符串名称映射到创建对象的函数上,因此给定名称和可选参数后,spaCy就能知道如何重建该对象。要注册一个返回自定义字典的函数,你可以使用@spacy.registry.misc装饰器并传入单个参数(即名称):

资产注册功能

在你的default_config(以及后续的训练配置)中,现在可以通过@misc键引用注册为"acronyms.slang_dict.v1"名称的函数。这会告诉spaCy如何创建该值,当你的组件被创建时,注册函数的执行结果会作为"dictionary"键的值传入。

使用已注册的函数还意味着您可以轻松地将自定义组件包含在您训练的管道中。为确保spaCy知道在哪里找到您的自定义@misc函数,您可以通过--code参数传入一个Python文件。如果其他人使用您的组件,他们只需注册自己的函数并替换名称即可自定义数据。顺便说一下,注册函数也可以接受参数,这些参数也可以在配置中定义 - 您可以在使用自定义代码训练的文档中了解更多信息。

选项2:通过管道保存数据并在初始化时一次性加载

就像模型在调用nlp.to_disk时会保存其二进制权重一样,组件也可以序列化任何其他数据资产——例如缩略语词典。如果流水线组件实现了自己的to_diskfrom_disk方法,这些方法将被nlp.to_disk自动调用,并接收要保存或加载的目录路径。然后组件可以执行任何自定义的保存或加载操作。如果用户对组件数据进行了更改,这些更改将在保存nlp对象时反映出来。更多示例请参阅序列化方法的使用指南。

自定义序列化方法

现在该组件可以保存到目录并从目录加载。剩下的唯一问题是:如何加载初始数据?在Python中,你可以直接调用管道的from_disk方法。但如果你要将该组件添加到训练配置中,spaCy需要知道如何从头到尾设置它,包括用于初始化的数据。

虽然你可以使用一个注册函数或像srsly.read_json.v1这样的文件加载器作为组件工厂的参数,但这种方法存在问题:组件工厂会在每次创建组件时运行。这意味着它会在训练前创建nlp对象时运行,但也会在用户每次加载你的管道时运行。因此,你的运行时管道要么依赖于文件系统上的本地路径,要么会被加载两次:一次是在创建组件时,然后当数据通过from_disk时再次加载。

为了解决这个问题,您的组件可以实现一个单独的方法initialize, 该方法将在可用时由nlp.initialize调用。 这通常发生在训练之前,但在加载管道的运行时不会发生。有关这方面的更多背景信息,请参阅关于 配置生命周期自定义初始化的使用指南。

Illustration of pipeline lifecycle

组件的initialize方法至少需要接收两个命名参数:一个提供训练样本访问权限的get_examples回调函数,以及当前的nlp对象。这主要被可训练组件用来从数据中初始化它们的模型和标签方案,因此我们可以忽略这些参数。该方法的所有其他参数都可以通过配置定义——在本例中是一个字典data

自定义初始化方法

nlp.initialize在训练前运行(或当你在自己的代码中调用它时),配置中的[initialize]区块会被加载并用于构建nlp对象。自定义的缩写组件随后会接收到从JSON文件加载的数据。训练完成后,nlp对象会被保存到磁盘,这将运行组件的to_disk方法。当之后将管道加载回spaCy使用时,from_disk方法会将数据重新加载进来。

Python类型提示与验证 v3.0

spaCy的配置文件由我们的机器学习库Thinc的配置系统驱动,该系统支持类型提示,甚至可以使用pydantic实现高级类型注解。如果您的组件工厂提供了类型提示,传入的值将根据预期类型进行检查。如果值无法转换为整数,spaCy会报错。pydantic还提供了严格类型如StrictFloat,它会强制要求值为整数,否则报错——例如,当您的配置定义的是浮点数时。

以下示例展示了一个用于调试的自定义管道组件。它可以被添加到管道的任何位置,并记录有关nlp对象和流经的Doc的信息。log_level配置设置允许用户自定义显示哪些日志语句——例如,"INFO"将显示信息日志和更关键的日志语句,而"DEBUG"将显示所有内容。该值被标注为StrictStr,因此它只接受字符串值。

可编辑代码spaCy v3.7 · Python 3 · 通过 Binder

可训练组件 v3.0

spaCy的TrainablePipe类可帮助您实现自定义的可训练组件,这些组件拥有自己的模型实例,能够对Doc对象进行预测,并可通过spacy train进行更新。这使您能够将完全定制的机器学习组件接入您的流程管道。

Illustration of Pipe methods

你需要以下内容:

  1. 模型: 一个Thinc Model实例。这可以是一个在Thinc中实现的模型,或者是在PyTorch、TensorFlow、MXNet中实现的包装模型,或是完全自定义的解决方案。该模型必须接收一个Doc对象列表作为输入,并可以有任何类型的输出。
  2. TrainablePipe子类: TrainablePipe的一个子类,至少需要实现两个方法:TrainablePipe.predictTrainablePipe.set_annotations
  3. 组件工厂: 一个通过@Language.factory注册的组件工厂,它接收nlp对象和组件name以及配置提供的可选设置,并返回您的可训练组件实例。
名称描述
predictApply the component’s model to a batch of Doc objects (without modifying them) and return the scores.
set_annotationsModify a batch of Doc objects, using pre-computed scores generated by predict.

默认情况下,TrainablePipe.__init__接收共享词汇表、Model以及该组件在流水线中的实例名称(您可将其作为损失函数中的键使用)。所有其他关键字参数将作为TrainablePipe.cfg可用,并会随组件一起序列化。

spaCy的配置系统采用自底向上的方式解析描述管道组件和模型的配置。这意味着它会首先注册架构创建Model,验证其参数,然后将对象传递给组件。这种机制使得配置能够表达非常复杂的嵌套对象树,但对象无需将模型设置一直传递到组件。这也使组件更加模块化,并允许你在配置中交换不同架构,以及重复使用模型定义。

config.cfg (节选)

因此,您的可训练管道组件工厂应始终接收一个model参数,而不是在组件内部实例化Model。要注册自定义架构,您可以使用@spacy.registry.architectures装饰器。详情请参阅训练指南

对于某些使用场景,覆盖额外的方法以自定义模型如何从示例中更新、如何初始化、如何计算损失以及向训练输出添加评估分数也是有意义的。

名称描述
updateLearn from a batch of Example objects containing the predictions and gold-standard annotations, and update the component’s model.
initializeInitialize the model. Typically calls into Model.initialize and can be passed custom arguments via the [initialize] config block that are only loaded during training or when you call nlp.initialize, not at runtime.
get_lossReturn a tuple of the loss and the gradient for a batch of Example objects.
scoreScore a batch of Example objects and return a dictionary of scores. The @Language.factory decorator can define the default_score_weights of the component to decide which keys of the scores to display during training and how they count towards the final score.

扩展属性

spaCy允许您在DocSpanToken上设置任何自定义属性和方法,这些属性和方法可以通过Doc._Span._Token._访问——例如Token._.my_attr。这使您能够存储与应用程序相关的附加信息,为spaCy添加新特性和功能,并实现使用其他机器学习库训练的自己的模型。它还让您可以利用spaCy的数据结构和Doc对象作为"单一事实来源"。

写入._属性而非直接操作Doc对象能保持更清晰的分离,并更容易确保向后兼容性。例如,如果您实现了自己的.coref属性而spaCy某天也声明该属性,就会破坏您的代码。同样地,仅通过查看代码,您就能立即分辨哪些是内置功能、哪些是自定义扩展——例如doc.sentiment属于spaCy,而doc._.sent_score则不是。

扩展定义——即传递给set_extension的默认值、方法、getter和setter——存储在Underscore类的类属性中。如果您写入扩展属性,例如doc._.hello = True,数据会被存储在Doc.user_data字典中。为了将下划线数据与其他字典条目分开,字符串"._."会被放置在一个元组中的名称之前。


主要有三种扩展类型,可以通过 Doc.set_extensionSpan.set_extensionToken.set_extension方法来定义。

描述

  1. 属性扩展。 为属性设置默认值,该值可以随时手动覆盖。属性扩展的工作方式类似于"普通"变量,是在DocSpanToken上存储任意信息的最快捷方式。

  2. 属性扩展。 定义一个getter和可选的setter函数。如果未提供setter,则该扩展为不可变的。由于getter和setter函数仅在您检索属性时才会被调用,因此您还可以访问先前添加的属性扩展的值。例如,Doc的getter可以对Token属性进行平均计算。对于Span扩展,您几乎总是希望使用属性——否则,您将不得不写入Doc中的每一个可能的Span来正确设置值。

  3. 方法扩展。 分配一个函数使其可作为对象方法使用。方法扩展始终是不可变的。如需更多详情和实现思路,请参阅这些示例

在使用自定义扩展之前,您需要通过set_extension方法在目标对象(例如Doc)上注册该扩展。请注意,扩展总是全局添加的,而不仅限于特定实例。如果同名属性已存在,或者尝试访问未注册的属性,spaCy会抛出AttributeError错误。

示例

注册自定义属性后,您还可以使用内置的 setgethas方法来修改和检索属性。当您想传入字符串而非调用 doc._.my_attr时,这特别有用。

示例:通过REST API实现GPE实体和国家元数据的管道组件

这个示例展示了一个流水线组件的实现,该组件通过REST Countries API获取国家元数据,为国家设置实体标注,并在DocSpan上设置自定义属性——例如首都、经纬度坐标甚至国旗。

可编辑代码spaCy v3.7 · Python 3 · 通过 Binder

在这种情况下,所有数据都可以在初始化时通过一次请求获取。然而,如果您处理的文本包含不完整的国家名称、拼写错误或外语版本,您也可以实现一个like_country风格的getter函数,该函数向搜索API端点发出请求并返回最佳匹配结果。

用户钩子

虽然通常建议使用Doc._Span._Token._代理来添加自定义属性,但spaCy提供了一些例外情况,允许自定义内置方法,例如通过自定义钩子来修改Doc.similarityDoc.vector,这些钩子可以依赖于您自己训练的组件。例如,您可以提供自己的实时句子分割算法或文档相似度计算方法。

钩子(Hooks)允许您通过向管道添加组件来自定义DocSpanToken对象的某些行为。例如,要自定义Doc.similarity方法,您可以添加一个组件,将自定义函数设置到doc.user_hooks["similarity"]。内置的Doc.similarity方法会检查user_hooks字典,如果设置了自定义函数就会委托给您的函数。通过向Doc.user_span_hooksDoc.user_token_hooks设置函数也可以实现类似的效果。

名称自定义项
user_hooksDoc.similarity, Doc.vector, Doc.has_vector, Doc.vector_norm, Doc.sents
user_token_hooksToken.similarity, Token.vector, Token.has_vector, Token.vector_norm, Token.conjuncts
user_span_hooksSpan.similarity, Span.vector, Span.has_vector, Span.vector_norm, Span.root

添加自定义相似度钩子

开发插件和封装器

我们对spaCy社区扩展和插件带来的全新可能性感到非常兴奋,迫不及待想看看您用它构建出什么!为了帮助您入门,这里提供一些技巧、窍门和最佳实践。See here查看其他spaCy扩展的示例。

使用场景

  • 添加新功能并集成模型。 例如,可以添加情感分析模型,或您偏好的词形还原/情感分析解决方案。spaCy内置的词性标注器、依存分析器和实体识别器会保留之前在流水线步骤中已对Doc设置的注解。
  • 集成其他库和API。 例如,您的流水线组件可以直接将额外的信息和数据写入DocToken作为自定义属性,同时确保在此过程中不会丢失任何信息。这些可以是其他库和模型生成的输出,或是通过REST API访问的外部服务。
  • 调试和日志记录。 例如,一个组件可以存储和/或导出关于当前处理文档状态的相关信息,并将其插入到处理流程的任何节点。

最佳实践

扩展可以声明自己的._命名空间,并作为独立包存在。 如果您正在开发工具或库,并希望让他人能够轻松地将其与spaCy一起使用并添加到他们的流程中,您只需公开一个接收Doc、修改它并返回它的函数即可。

  • 请为您的流水线组件类选择一个描述性强且具体明确的名称,并将其设置为name属性。避免使用过于通用或可能与内置组件或用户其他自定义组件冲突的名称。虽然将您的包命名为"spacy_my_extension"是可以接受的,但请避免在组件名称中包含"spacy",因为这很容易造成混淆。

  • 在写入DocTokenSpan对象时,尽可能使用getter函数, 避免显式设置值。Token和Span本身不拥有任何数据,它们是作为C扩展类实现的—— 因此通常不能像大多数纯Python对象那样为它们添加新属性。

  • 始终将您的自定义属性添加到全局DocTokenSpan对象上,而不是它们的特定实例。尽可能尽早添加这些属性,例如在扩展的__init__方法中或模块的全局作用域内。这意味着在命名空间冲突的情况下,用户会立即看到错误,而不是等到运行管道时才出现。

  • 如果您的扩展程序在DocTokenSpan上设置属性, 请包含一个选项允许用户更改这些属性名称。这样 可以更容易避免命名空间冲突,并适应具有 不同命名偏好的用户。我们建议在类的 __init__方法中添加一个attrs参数,这样您可以将名称写入类属性 并在组件中重复使用它们。

  • 理想情况下,扩展应该是独立包,将spaCy和(可选的)其他包指定为依赖项。它们可以自由地分配到自己的._命名空间,但应仅限于此。如果您的扩展唯一功能是提供更好的.similarity实现,并且您的文档明确说明了这一点,那么写入user_hooks并覆盖spaCy的内置方法没有问题。但是,第三方扩展绝不应静默覆盖内置功能或其他扩展设置的属性。

  • 如果您希望发布一个依赖于自定义流水线组件的流水线包,您可以选择在包的依赖项中要求它,或者——如果该组件是特定且轻量级的——选择将其与您的流水线包一起发布。只需确保注册自定义组件的@Language.component@Language.factory装饰器在包的__init__.py中运行,或通过入口点暴露。

  • 当你准备与他人分享你的扩展时,请确保添加文档和安装说明(你可以始终链接到此页面获取更多信息)。让他人能够轻松安装和使用你的扩展,例如通过将其上传到PyPi。如果你在GitHub上分享代码,别忘了用spacyspacy-extension标签标记,以帮助他人找到它。

封装其他模型和库

假设你有一个自定义的实体识别器,它接收一个字符串列表并返回它们的BILUO标签。给定像["A", "text", "about", "Facebook"]这样的输入,它会预测并返回["O", "O", "O", "U-ORG"]。为了将其集成到你的spaCy管道中,并使它将那些实体添加到doc.ents,你可以将其包装在一个自定义管道组件函数中,并将从组件接收的Doc对象中的token文本传递给它。

training.biluo_tags_to_spans 在这里非常有用,因为它接收一个 Doc 对象和基于标记的 BILUO 标签,并返回带有添加标签的 Doc 中的 Span 对象序列。因此,您的包装器只需计算实体跨度并覆盖 doc.ents 即可。

然后可以使用nlp.add_pipecustom_ner_wrapper添加到空白管道中。您也可以使用nlp.replace_pipe替换已训练管道的现有实体识别器。

这是另一个自定义模型your_custom_model的示例,该模型接收一个词符列表并返回细粒度词性标注、粗粒度词性标注、依存关系标签和中心词索引的列表。这里我们可以使用Doc.from_array通过这些值创建一个新的Doc对象。要创建numpy数组我们需要整数索引,因此可以在StringStore中查找字符串标签。doc.vocab.strings.add方法在这里非常有用,因为它会返回字符串的整数ID并确保其被添加到词汇表中。当自定义模型使用与spaCy默认模型不同的标签方案时,这一点尤为重要。