嵌入、Transformers与迁移学习
spaCy支持多种迁移学习和多任务学习工作流程,这些方法通常能帮助提升您的处理流程效率或准确性。迁移学习指的是诸如词向量表和语言模型预训练等技术。这些技术可用于将原始文本中的知识导入您的处理流程,从而使您的模型能够更好地从标注样本中进行泛化。
您可以将FastText和Gensim等流行工具中的词向量进行转换,或者如果您安装了spacy-transformers,可以加载任何预训练的transformer模型。您还可以通过spacy pretrain命令进行自己的语言模型预训练。您甚至可以在多个组件之间共享您的transformer或其他上下文嵌入模型,这可以使长流程效率提高数倍。要使用迁移学习,您至少需要一些用于预测目标的标注示例。否则,您可以尝试使用向量和相似度的"一次性学习"方法。
Transformers 是强大但庞大的神经网络,能提供更高的准确度,但由于需要GPU才能高效运行,在生产环境中部署较为困难。词向量是一种稍早的技术,虽然对模型准确度的提升较小,但能带来一些额外的功能。
词向量与上下文语言模型(如Transformer)之间的关键区别在于,词向量建模的是词汇类型,而非具体词例。 若您处理的是一组没有上下文的术语列表,像BERT这样的Transformer模型实际上无法提供帮助。BERT的设计初衷是理解上下文中的语言,而这并非您当前的需求。此时,词向量表会更适合您的任务。 不过,如果您确实拥有带上下文的词汇——完整的句子或连续文本段落——词向量只能对文本主题提供非常粗略的近似表示。
Word vectors are also very computationally efficient, as they map a word to a vector with a single indexing operation. Word vectors are therefore useful as a way to improve the accuracy of neural network models, especially models that are small or have received little or no pretraining. In spaCy, word vector tables are only used as static features. spaCy does not backpropagate gradients to the pretrained word vectors table. The static vectors table is usually used in combination with a smaller table of learned task-specific embeddings.
词向量与大多数transformer模型不兼容,但如果你正在训练其他类型的NLP网络,几乎总是值得将词向量添加到模型中。除了提高最终准确率外,词向量通常还能使实验更加一致,因为你达到的准确率对网络随机初始化的敏感度会降低。由于随机性导致的高方差会显著拖慢研究进度,因为你需要进行大量实验才能从噪声中筛选出有效信号。
在训练前需要启用词向量特征,并且在运行时也需要提供相同的词向量表。一旦模型已经训练完成,就无法再添加词向量特征,而且通常不能在不导致性能显著下降的情况下替换词向量表。
共享嵌入层
spaCy允许您在多个组件之间共享单个transformer或其他token-to-vector("tok2vec")嵌入层。您甚至可以更新共享层,实现多任务学习。在组件间复用tok2vec层可以显著加快管道运行速度并生成更小的模型。然而,这可能会降低管道的模块化程度,使得交换组件或重新训练管道部分变得更加困难。多任务学习可能影响您的准确率(可能是正面或负面影响),并且可能需要重新调整超参数。
| 共享 | 独立 |
|---|---|
| smaller: 模型只需包含嵌入的单一副本 | larger: 模型需要包含每个组件的嵌入 |
| 更快: 为整个流程一次性嵌入文档 | 更慢: 为每个组件重新运行嵌入 |
| 组合性较差:所有组件在流水线中都需要相同的嵌入组件 | 模块化:组件可以自由移动和替换 |
您可以通过在管道起始位置附近添加Transformer或Tok2Vec组件,来在多个组件之间共享单个transformer或其他tok2vec模型。管道中靠后的组件可以通过在其模型中包含像Tok2VecListener这样的监听层来"连接"到它。
在训练开始时,Tok2Vec组件会获取管道中其他相关监听器层的引用。当它处理一批文档时,会将预测结果传递给监听器,使监听器在被调用时能够复用这些预测结果。类似的机制也用于将梯度从监听器传回模型。Transformer组件和TransformerListener层对transformer模型执行相同操作,但Transformer组件还会将transformer输出保存到Doc._.trf_data扩展属性中,以便在管道运行结束后访问这些数据。
示例:共享配置与独立配置
配置系统允许您为共享和独立的嵌入层表达模型配置。共享设置使用一个带有Tok2Vec架构的Tok2Vec组件。所有其他组件,如实体识别器,使用Tok2VecListener层作为其模型的tok2vec参数,该参数连接到tok2vec组件模型。
共享
在独立设置中,实体识别器组件会定义自己的Tok2Vec实例。其他组件也会采取相同做法。这使得它们完全独立,不需要在管道中存在上游的Tok2Vec组件。
独立
使用transformer模型
Transformer是一类神经网络架构,能够为文档中的标记计算密集且上下文敏感的表示。流水线中的下游模型可以将这些表示作为输入特征,从而提升预测效果。您可以将多个组件连接到单个transformer模型,其中任意或所有组件都能向transformer提供反馈,以针对您的任务进行微调。spaCy的transformer支持与PyTorch和HuggingFace transformers库互操作,让您能在流水线中使用数千个预训练模型。虽然有许多优秀指南介绍transformer模型,但实际应用中,您可以简单将其视为即插即用的替代方案——以更高的训练和运行成本为代价,换取更高的准确率。
设置与安装
安装CUDA后,我们建议按照PyTorch安装指南根据您的包管理器和CUDA版本安装PyTorch。如果跳过此步骤,pip会在后续安装PyTorch作为依赖项,但可能无法为您的配置找到最佳版本。
示例:使用pip安装适用于CUDA 11.3的PyTorch 1.11.0版本
接下来,安装带有适用于您CUDA版本和transformers扩展的spaCy。CUDA扩展(例如cuda102、cuda113)会安装正确版本的cupy,它类似于numpy,但专为GPU设计。如果您的CUDA运行时安装在非标准位置,您可能还需要设置CUDA_PATH环境变量。综合起来,如果您将CUDA 11.3安装在/opt/nvidia/cuda目录下,您需要运行:
使用CUDA安装
对于transformers v4.0.0及以上版本以及需要SentencePiece的模型(例如ALBERT、CamemBERT、XLNet、Marian和T5),请通过以下命令安装额外依赖项:
安装sentencepiece
运行时用法
Transformer模型可以作为其他类型神经网络的即插即用替代品,因此您的spaCy管道可以以对用户完全透明的方式集成它们。用户将以标准方式下载、加载和使用该模型,就像使用其他spaCy管道一样。除了直接将transformers用作子网络外,您还可以通过Transformer管道组件来使用它们。
Transformer组件设置了Doc._.trf_data扩展属性,使您可以在运行时访问transformer的输出。spaCy提供的基于transformer的预训练pipelines以_trf结尾,例如en_core_web_trf。
示例
你也可以通过指定自定义的set_extra_annotations函数来定制Transformer组件如何将注释设置到Doc上。这个回调函数会接收整个批次的原始输入和输出数据,以及批次的Doc对象,让你能够实现任何需要的功能。注释设置器会接收一批Doc对象和一个包含该批次transformer数据的FullTransformerBatch。
训练使用
推荐的训练工作流程是使用spaCy的配置系统,通常通过spacy train命令实现。训练配置文件将所有组件设置和超参数集中定义在一处,并允许您通过引用创建函数(包括您自己注册的函数)来描述对象树。有关如何开始训练自己的模型的详细信息,请参阅训练快速入门。
config.cfg中的[components]部分描述了流水线组件及其构建所用的设置,包括它们的模型实现。以下是一个Transformer组件的配置片段及对应的Python代码。在此例中,[components.transformer]块描述了transformer组件:
config.cfg
[components.transformer.model] 块描述了传递给transformer组件的model参数。它是一个将被传入组件的Thinc Model对象。这里它引用了在architectures注册表中注册的函数spacy-transformers.TransformerModel.v3。如果块中的键以@开头,它会被解析为一个函数,所有其他设置都将作为参数传递给该函数。在本例中,这些参数是name、tokenizer_config和get_spans。
get_spans 是一个接收批量Doc对象并返回可能重叠的Span对象列表的函数,这些对象将由transformer处理。系统提供了多个内置函数可供使用——例如处理整个文档或单个句子的函数。当配置解析完成后,该函数会被创建并作为参数传入模型。
name 值是任何 HuggingFace 模型的名称,首次使用时将自动下载。您也可以使用本地文件路径。完整详情请参阅 TransformerModel 文档。
支持多种PyTorch模型,但某些模型可能无法运行。如果发现模型无法正常工作,欢迎提交issue。另外请注意,在spaCy中加载的Transformers仅能用于张量运算,预训练的任务特定头部或文本生成功能无法作为transformer流水线组件的组成部分使用。
自定义设置
要修改任何设置,您可以编辑config.cfg并重新运行训练。若要更改函数(如span getter),可以替换引用的函数名称——例如将@span_getters = "spacy-transformers.sent_spans.v1"改为处理句子。您还可以通过span_getters注册表注册自定义函数。例如,以下自定义函数会返回遵循句子边界的Span对象,除非某个句子超过特定数量的标记,这种情况下将返回最多包含max_length个标记的子句。
code.py
为了在训练过程中解析配置,spaCy需要了解您的自定义函数。您可以通过--code参数使其可用,该参数可以指向一个Python文件。有关使用自定义代码进行训练的更多详细信息,请参阅训练文档。
自定义模型实现
Transformer组件需要传入一个Thinc Model对象作为其model参数。您不仅限于使用spacy-transformers提供的实现——唯一的要求是您注册的函数必须返回Model[List[Doc],FullTransformerBatch]类型的对象:即一个接收Doc对象列表并返回包含transformer数据的FullTransformerBatch对象的Thinc模型。
同样的思路也适用于驱动下游组件的任务模型。
spaCy大部分内置模型创建函数都支持tok2vec参数,
该参数应为类型Model[List[Doc], List[Floats2d]]的Thinc层。这里我们将使用
TransformerListener层来接入我们的transformer模型,
该层巧妙地委托给Transformer管道组件。
config.cfg (节选)
TransformerListener层需要一个池化层作为参数pooling,该参数类型必须为Model[Ragged,Floats2d]。该层决定了如何根据每个spaCy标记所对齐的零个或多个源行来计算其向量。这里我们使用reduce_mean层,它对wordpiece行进行平均计算。也可以选择使用reduce_max层,或者自定义编写的函数。
您可以配置多个组件同时监听同一个transformer模型,并且所有组件都会将梯度回传给它。默认情况下,所有梯度都会被同等加权。您可以通过grad_factor设置来控制这一点,该参数允许您对不同监听器产生的梯度进行重新加权。例如,设置grad_factor = 0将禁用某个监听器的梯度,而grad_factor = 2.0会将梯度乘以2。这类似于为每个组件设置自定义学习率。除了常量值外,您还可以提供调度方案,从而允许您在训练开始时冻结共享参数。
静态向量
如果您的流程中包含词向量表,您就可以在Doc、Span、Token和Lexeme对象上使用.similarity()方法。您还可以通过.vector属性访问向量,或者直接使用Vocab对象查找一个或多个向量。带有词向量的流程还可以将这些向量作为统计模型的特征,这可以提高组件的准确性。
spaCy中的词向量是"静态"的,这意味着它们不是统计模型的学习参数,且spaCy本身不包含任何学习词向量表的算法。您可以使用floret、Gensim、FastText或GloVe等工具训练词向量表,或下载现有的预训练向量。init vectors命令允许您转换向量以供spaCy使用,并会提供一个目录,您可以在训练配置中加载或引用该目录。
在模型中使用词向量
许多神经网络模型能够使用词向量表作为附加特征,这有时会显著提高准确性。spaCy内置的嵌入层MultiHashEmbed可通过include_static_vectors标志配置为使用词向量表。
创建自定义嵌入层
MultiHashEmbed 层是spaCy推荐的为神经网络模型构建初始词表示的策略,但您也可以实现自己的方法。您可以将任何函数注册到一个字符串名称,然后在配置中引用该函数(更多详情请参阅训练文档)。要尝试此功能,您可以将以下小示例保存到一个新的Python文件中:
如果你将文件路径传递给spacy train命令
并使用--code参数,你的文件将被导入,这意味着
注册函数的装饰器将会运行。你的函数现在与spaCy内置函数
处于同等地位,因此你可以用它替换任何具有相同输入和输出
签名的其他模型。例如,你可以如下所示在标注器模型中使用它:
现在您已经将自定义函数接入网络,可以开始实现您感兴趣的逻辑。例如,假设您想尝试一种相对简单的嵌入策略,该策略利用静态词向量,但通过求和方式与一个较小的学习嵌入表相结合。
创建自定义向量实现 v3.7
您可以在[nlp.vectors]下指定自定义注册的向量类,以便使用Vectors不支持的格式的静态向量。继承抽象类BaseVectors来实现您的自定义向量。
例如,以下BPEmbVectors类实现了对BPEmb子词嵌入的支持:
要在您的流程中使用此功能,请在配置文件的[nlp.vectors]部分指定此注册函数:
或者在创建空白管道时指定它:
在使用spacy train和spacy package时,请记得包含--code参数。
预训练
spacy pretrain 命令允许您使用原始文本中的信息来初始化模型。如果没有预训练,组件的模型通常会随机初始化。预训练背后的理念很简单:随机初始化很可能不是最优的,因此如果我们有一些文本可供学习,就可能找到让模型获得更好初始状态的方法。
预训练使用与常规训练相同的config.cfg配置文件,这有助于保持设置和超参数的一致性。额外的[pretraining]部分包含几个配置子项,这些子项与训练块中的配置类似:[pretraining.batcher]、[pretraining.optimizer]和[pretraining.corpus]的工作方式相同,并期望相同类型的对象,尽管对于预训练来说,您的语料库不需要任何注释,因此通常会使用不同的读取器,例如JsonlCorpus。
你可以通过设置init config或init fill-config上的--pretraining标志,向配置中添加一个[pretraining]块:
然后你可以使用更新后的配置运行spacy pretrain,并传入可选配置覆盖项,比如原始文本文件的路径:
以下默认设置用于[pretraining]块,当您运行init config或init fill-config并带有--pretraining参数时,这些设置会被合并到您现有的配置中。如果需要,您可以配置这些设置和超参数,或更改目标函数。
explosion/spaCy/master/spacy/default_config_pretraining.cfg
预训练的工作原理
spacy pretrain的效果因情况而异,但如果您没有使用transformer模型且拥有相对较少的训练数据(例如少于5,000个句子),通常值得尝试。一个经验法则是,预训练通常能为您的模型带来与使用词向量相似的准确度提升。如果词向量使您的错误率降低了10%,使用spaCy进行预训练可能会再降低10%,总共降低20%的错误率。
spacy pretrain 命令会从您的某个组件中提取特定子网络,并添加额外层来构建一个临时任务网络,迫使模型学习句子结构和词语共现统计的相关知识。
预训练会生成一个二进制权重文件,可以在训练开始时通过配置选项initialize.init_tok2vec重新加载。该权重文件指定了一组初始权重,之后训练会正常进行。
每次只能对管道中的一个子网络进行预训练,且该子网络必须具有类型Model[List[Doc], List[Floats2d]](即必须是一个"tok2vec"层)。最常见的工作流程是使用Tok2Vec组件为管道的多个组件创建共享的token-to-vector层,并对其整个模型应用预训练。
配置预训练
spacy pretrain 命令通过配置文件中的 [pretraining] 部分进行配置。component 和 layer 设置告诉 spaCy 如何找到待预训练的子网络。layer 设置可以是空字符串(表示使用整个模型),或者一个节点引用。spaCy 大多数内置模型架构都有一个名为 "tok2vec" 的引用,该引用会指向正确的网络层。
config.cfg
将预训练与训练连接起来
为了从预训练中获益,您的训练步骤需要知道如何用预训练步骤学到的权重初始化其tok2vec组件。您可以通过将initialize.init_tok2vec设置为预训练中要使用的.bin文件名来实现这一点。
一个预训练步骤运行5个epoch,输出路径为pretrain/,例如会生成从pretrain/model0.bin到pretrain/model4.bin的文件,并将最后一次迭代的副本保存为pretrain/model-last.bin。此外,你可以配置n_save_epoch来告诉预训练在哪个epoch间隔保存当前训练进度。要使用最终输出来初始化你的tok2vec层,你可以在配置文件中填写这个值:
config.cfg
预训练目标
提供了两种预训练目标,它们都是BERT中引入的完形填空任务Devlin et al. (2018)的变体。该目标可以通过[pretraining.objective]配置块来定义和配置。
-
PretrainCharacters:"characters"目标要求模型预测单词的若干前导和尾随UTF-8字节。例如,设置n_characters = 2时,模型将尝试预测单词的前两个和后两个字符。 -
PretrainVectors:"vectors"目标要求模型从静态嵌入表中预测单词的向量。这需要训练并加载一个词向量模型。该向量目标可以优化余弦损失或L2损失。我们通常发现余弦损失表现更好。
这些预训练目标使用了一种我们称之为近似输出语言建模(LMAO)的技巧。该技巧的动机在于预测精确的词ID会引入大量附带复杂性。你需要一个庞大的输出层,即便如此词汇量仍然过大,这促使了与实际词边界不对齐的分词方案出现。在训练结束时,输出层无论如何都会被丢弃:我们只是需要一个能迫使网络建模词语共现统计特征的任务。预测首尾字符已经足够胜任这项工作,因为如果能准确预测开头和结尾字符,原始词序列就能以高准确率被还原。对于向量目标,预训练使用了由GloVe或Word2vec等算法学习到的嵌入空间,使模型能够专注于我们真正关心的上下文建模。