- Hugging Face 是一家在 NLP 和 AI 领域具有重要影响力的科技公司,他们的开源工具和社区建设为NLP研究和开发提供了强大的支持。它们拥有当前最活跃、最受关注、影响力最大的 NLP 社区,最新最强的 NLP 模型大多在这里发布和开源。该社区也提供了丰富的教程、文档和示例代码,帮助用户快速上手并深入理解各类 Transformer 模型和 NLP 技术
- Transformers 库是 Hugging Face 最著名的贡献之一,它最初是 Transformer 模型的 pytorch 复现库,随着不断建设,至今已经成为 NLP 领域最重要,影响最大的基础设施之一。该库提供了大量预训练的模型,涵盖了多种语言和任务,成为当今大模型工程实现的主流标准,换句话说,如果你正在开发一个大模型,那么按 Transformer 库的代码格式进行工程实现、将 check point 打包成 hugging face 格式开源到社区,对于推广你的工作有很大的助力作用。本系列文章将介绍 Transformers 库的基本使用方法
- 参考:
- 官方教程
- 手把手带你实战HuggingFace Transformers
文章目录
- 1. Transformer Model
- 1.1 基本架构
- 1.2 模型类型
- 1.3 Model Head
- 2. Transformer 库 Model 组件的基本使用
- 2.1 模型加载
- 2.2 模型调用
- 2.2.1 不带 Model Head 的模型调用
- 2.2.2 带 Model Head 的模型调用
- 3. 下游任务训练
1. Transformer Model
1.1 基本架构
- Transformer model 代表了以 Transformer 为基础的一系列模型
- 原始的 Transformer 是 Encoder-Decoder 模型,用于自然语言翻译任务。其 Encoder 部分接受原始序列输入并构建其完整的特征表示,Decoder 部分基于 Encoder 提供的特征和当前已经翻译的部分结果,自回归地生成目标序列(翻译结果)。 无论 Encoder 还是 Decoder,均由多个 Transformer Block 堆叠而成,每个 Transformer Block 由 Attention Layer 和 FFD Layer 组成
- 由于 Transformer Encoder 具有序列特征提取能力,Transformer Decoder 具有自回归序列生成能力,两者之后都被独立使用,Encoder-Only 衍生出属于自编码器的 BERT 类模型,Decoder-Only 衍生出属于自回归生成模型的 GPT 类模型
- Attention 机制
- Attention 机制是 Transformer 类模型的一个核心特征,在计算当前 token 的特征表示时,可以通过注意力机制有选择性地告诉模型应该使用哪部分上下文
- Encoder-Decoder / Encoder-Only / Decoder-Only 三类模型,可以归结为 attention mask 设置的不同,详见 1.2 节
1.2 模型类型
- 目前主流的 Transformer 类模型可分为以下四类
- 自编码模型:
Encoder-Only
结构,拥有双向的注意力机制,即计算每一个词的特征时都看到完整上下文 - 自回归模型:
Decoder-Only / Causal Decoder
结构,拥有单向的注意力机制,即计算每一个词的特征时都只能看到上文,无法看到下文: - 序列到序列模型:
Encoder-Decoder
结构,Encoder部分使用双向的注意力,Decoder部分使用单向注意力 - 前缀模型:
Prefix-Decoder
结构,它对输入序列的前缀部分使用双向注意力机制,后半部分使用单向注意力机制,前缀片段内部的所有 token 都能看到完整上下文,其他部分只能看到前文。这可以看作是 Encoder-Decoder 的一个变体
- 以上 3 的结构示意图已经在 1.1 节给出,它的 Encoder-Decoder 使用两个独立的 Transformer 结构,其中通过 cross attention 机制连接,1/2/4 都只使用一个 Transformer 结构,区别仅在于 attention mask 施加不同,使得序列中各个 token 能观测到的上下文区域有所区别,如下所示
- Prefix Decoder 和 Encoder-Decoder 的主要区别在于:前者对编码部分的 attention 是在每一层 Transformer Block 内部施加的,即第任意一层 Block 中的解码部分片段可以关注到该层的前缀片段;后者则是 Decoder 中每层 Block 都能只能关注到 Encoder 最后一层的编码片段结果
- Prefix Decoder 和 Decoder-Only 非常类似,它们能执行的任务类型也差不多,下图更清晰地指示了二者的不同
- 不同的模型结构适用不同的预训练方法,主要有以下几种
-
FLM (full language modeling)
:就是训练标准的语言模型,完整一段话从头到尾基于上文预测下一个token。适用于 Decoder-Only 模型 -
PLM (prefix language modeling)
:一段话分成两截,前一截作为输入,预测后一截。适用于 Encoder-Decoder 模型和 Prefix Decoder 模型 -
MLM (masked language modeling)
:遮盖住文本中的一部分token,让模型通过上下文猜测遮盖部分的token。适用于 Encoder-Only 模型
- 将任务改造成 text-to-text 形式(即 input 和 target 都是一段文本),可以适配 Encoder-Decoder 和 Prefix Decoder
- 将 input 和 target 拼接起来,可以适配 Decoder-Only
- 总结一下各类结构的经典模型和主要适用任务
模型类型 | 预训练目标 | 常用预训练模型 | 主要适用任务 |
Encoder-only | MLM | ALBERT,BERT,DistilBERT,RoBERTa | 文本分类、命名实体识别、阅读理解 |
Decoder-only | FLM | GPT,GPT-2,Bloom,LLaMA | 文本生成 |
Encoder-Decoder | PLM | BART,T5,Marian,mBART | 文本摘要、机器翻译 |
Prefix-Decoder | PLM | ChatGLM、ChatGLM2、U-PaLM | 文本摘要、机器翻译、文本生成 |
注意这里的适用任务并不绝对,比如 Decoder-only 配合指令微调,在参数规模大了之后其实什么都能做;用 MLM 目标预训练的模型,经过 PLM 或 FLM 继续训练后,也能做翻译和生成等任务,反之亦然。可以参考论文 What Language Model Architecture and Pretraining Objective Works Best for Zero-Shot Generalization?
- 额外提一句,当前最流行的模型结构是 Decoder-only,其中可能包含多方面原因,可以参考 【大模型慢学】GPT起源以及GPT系列采用Decoder-only架构的原因探讨
1.3 Model Head
- 很多 NLP 任务是可以用相同的 model 骨干完成的,在 Transformers 库的设计上,一个相同的模型骨干可以对应多个不同的任务,它们的区别仅在于最后的 model head 有所不同
比如对于 “句子情感分类” 和 “句子自回归生成” 两个任务,前者可以看作是基于前驱序列特征做二分类任务(正面情感/负面情感),后者可以看作是基于前驱序列特征做多分类任务(从词表中选择一个token索引),两个任务中 “前驱序列特征” 都是可以用 GPT 模型提取的,它们的区别仅在于前者最后接二分类头,后者最后接多分类头
- Model Head 是连接在模型后的层,通常为1个或多个全连接层
- Model Head 将模型的编码的表示结果进行映射,以解决不同类型的任务
以 BERT 模型情感二分类任务为例,设模型输入长度 128,嵌入维度 768,则 Hidden states 尺寸 1x128x768。这时 Head 可能是一个输入尺寸为 768,输出尺寸为 2 的 MLP,最后一层 Hidden states 中 [CLS] 特殊 token 位置的 768 维向量将会输入 Head,对 Head 输出计算交叉熵损失来训练模型
- Transformer 库中,模型类对象使用的 Model Head 可以从其类名后缀中观察出来
- *Model(模型本身,只返回编码结果)
- *ForCausalLM
- *ForMaskedLM
- *ForSeq2SeqLM
- *ForMultipleChoice
- *ForQuestionAnswering
- *ForSequenceClassification
- *ForTokenClassification
- …
2. Transformer 库 Model 组件的基本使用
2.1 模型加载
- 使用
AutoModel
类,可以用from_pretrained
方法直接从模型地址下载模型和权重检查点,并返回 model 对象。这类似 前文介绍过的AutoTokenizer
类。这里我们加载一个小规模的中文情感分类模型
可以看到这是一个 BertModel
- 可以通过
model.config
访问该模型的参数
可见,Bert 类模型的参数使用一个 BertConfig
类对象管理,查看其源码定义,可以看到参数的解释
- 注意到 BertConfig 类继承自
PretrainedConfig
,这意味着之前从model.config
打印的参数并不完整,进一步查看 PretrainedConfig 类的源码,可以看到模型使用的所有参数。了解模型使用的全部参数是重要的,因为我们修改模型时主要就是从修改参数入手
2.2 模型调用
2.2.1 不带 Model Head 的模型调用
- 像 2.1 节那样加载,得到的 model 是不带 model head 的,这一点可以从打印从模型结构中看出,它以一个
BertPooler
块结尾
可见输出特征还是 768 维,这意味着没有接调整到目标维度的 model head。当我们想把预训练的模型作为序列特征提取器时,这种裸模型是有用的,可以通过加载模型时传入参数 output_attentions=True
来获得模型所有层的 attention 张量
查看最后一层 hidden state
2.2.2 带 Model Head 的模型调用
- 使用带有 1.3 节所述 model head 类名后缀的模型类加载模型,即可得到带 head 的模型
注意模型现在变成了一个 BertForSequenceClassification
对象,其结构最后多了一个由 dropout
和 classifier
线性层组成的 head,而且这里提示我们 Some weights of BertForSequenceClassification were not initialized...
,说明这个线性层的参数 ckpt 中没有提供,需要我们针对下游任务特别训练
- 注意到分类头默认输出维度(类别数为2),这个可以通过参数
num_labels
控制,从模型类BertForSequenceClassification
定义进去检查。下面修改 model head 的输出维度看看
- 使用以上模型做前向传播试试
可见输出结构中存在一个 loss
成员,说明前向过程中就有计算 loss 的结构了,不妨看一下 BertForSequenceClassification
类的定义
从 forward 方法中可见,如果传入了 labels 参数,则会进一步根据输出尺寸 num_labels
自动识别任务类型,并使用相应的损失函数计算 loss 作为返回的一部分
3. 下游任务训练
- 在 2.2.2 节,我们构造了一个
BertForSequenceClassification
模型,它的 Bert 骨干加载了预训练的 ckpt 权重,而分类头权重是随机初始化的。本节我们使用 ChnSentiCorp_htl_all数据集对它做下游任务训练,该数据集由 7000 多条酒店评论数据,包括 5000 多条正向评论,2000 多条负向评论,用这些数据继续训练,可以得到一个文本情感分类模型。由于模型中绝大部分参数都有良好的初始权重,且模型规模很小,训练成本并不高 - 我们这里不使用 Transformers 库的 pipeline、evaluate、trainer 和 dataset,尽量手动实现全部代码,细节请参考注释