目前各种Pretraining的Transformer模型层出不穷, 虽然这些模型都有开源代码, 但是它们的实现各不相同, 我们在对比不同模型时也会很麻烦。Huggingface Transformer能够帮我们跟踪流行的新模型, 并且提供统一的代码风格来使用BERT、XLNet和GPT等等各种不同的模型。而且它有一个模型仓库, 所有常见的预训练模型和不同任务上fine-tuning的模型都可以在这里方便的下载。
0. 大模型与Transformer
0.1 大模型
从图中可以看到所谓大模型家族都有同一个根(elmo 这一支除外)即 Transformer, 我们知道 transformer 由 encoder-decoder 两部分组成, encoder 部分负责编码, 更侧重于信息理解;而 decoder 部分负责解码, 更侧重于文本生成;这样在模型选型方面就会有 3 种不同的选型, 即:
- only-encoder: 这部分以大名鼎鼎的 Bert 为代表
- only-decoder: 这部分的代表就是我们的当红炸子鸡 GPT 系列
- encoder-decoder: 这部分相比于其他两个部分就显得略微暗淡一些, 但同样也有一些相当不错的成果, 其中尤以 T5 为代表。
个人理解 T5 更像一个过渡产品, 通过添加一些 prefix 或者 prompt 将几乎所有 NLP 任务都可以转换为 Text-to-Text 的任务, 这样就使得原本仅适合 encoder 的任务(classification)也可以使用 decoder 的模式来处理。
0.2 Transformer 解决了哪些问题
在没出现Transformer之前, NLP领域几乎都是以RNN模型为主导, RNN有两个比较明显的缺陷:
- RNN模型是一个串行模型, 只能一个时序一个时序的依次来处理信息, 后一个时序需要依赖前一个时序的输出, 这样就导致不能并行, 时序越长性能越低同时也会造成一定的信息丢失
- RNN模型是一个单向模型, 只能从左到右或者从右到左进行处理, 无法实现真正的双向编码
Transformer摒弃了RNN的顺序编码方式, 完全使用注意力机制来对信息进行编码, Transformer的计算过程是完全并行的, 可以同时计算所有时序的注意力得分。另外Transformer是真正的双向编码, 在计算input#2的注意力得分时, input#2是可以同时看见input#1、input#3的且对于input#2而言input#1、input#3、甚至input#n都是同等距离的, 没有所谓距离的概念, 真正的天涯若比邻的感觉。
0.3 Tranformer 的庐山真面目
下面这张图就是我们 Transformer 的架构图, 从图中可以看出, Transformer 由左右两部分组成, 左边这部分是 Encoder, 右边这部分就是 Decoder 了。Encoder 负责对信息进行编码而 Decoder 则负责对信息解码 。下面我们从下往上对下图的每个部分进行解读。
1. 入门
1.1 安装
pip install transformers
# 建议安装开发版本, 几乎带有所有用例需要的依赖项
pip install transformers[sentencepiece]
2. 模型简介 Transformer models
2.1 pipelines 简单的小例子
Transformers 库中最基本的对象是pipeline()函数。它将模型与其必要的预处理和后处理步骤连接起来, 使我们能够直接输入任何文本并获得答案:
from transformers import pipeline
classifier = pipeline("sentiment-analysis") # 情感分析
classifier("I've been waiting for a HuggingFace course my whole life.")
# 输出
# [{'label': 'POSITIVE', 'score': 0.9598047137260437}]
目前可用的一些pipeline 有:
- feature-extraction 特征提取: 把一段文字用一个向量来表示
- fill-mask 填词: 把一段文字的某些部分mask住, 然后让模型填空
- ner 命名实体识别: 识别文字中出现的人名地名的命名实体
- question-answering 问答: 给定一段文本以及针对它的一个问题, 从文本中抽取答案
- sentiment-analysis 情感分析: 一段文本是正面还是负面的情感倾向
- summarization 摘要: 根据一段长文本中生成简短的摘要
- text-generation文本生成: 给定一段文本, 让模型补充后面的内容
- translation 翻译: 把一种语言的文字翻译成另一种语言
- zero-shot-classification
2.2 各种任务的代表模型
Model | Examples | Tasks |
---|---|---|
Encoder 编码器模型 |
ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa | Sentence classification, named entity recognition, extractive question answering 适合需要理解完整句子的任务, 例如句子分类、命名实体识别(以及更一般的单词分类)和提取式问答 |
Decoder 解码器模型 |
CTRL, GPT, GPT-2, Transformer XL | Text generation 解码器模型的预训练通常围绕预测句子中的下一个单词。这些模型最适合涉及文本生成的任务 |
Encoder-decoder 序列到序列模型 |
BART, T5, Marian, mBART | Summarization, translation, generative question answering 序列到序列模型最适合围绕根据给定输入生成新句子的任务, 例如摘要、翻译或生成式问答。 |
3. 使用 Using Transformers
3.1 Pipeline 背后的流程
3.2 Models
3.2.1 创建Transformer
from transformers import BertConfig, BertModel
# Building the config
config = BertConfig()
# Building the model from the config
model = BertModel(config)
3.2.2 不同的加载方式
from transformers import BertModel
model = BertModel.from_pretrained("bert-base-cased")
3.2.3 保存模型
model.save_pretrained("directory_on_my_computer")
3.2.4 使用Transformer model
import torch
sequences = ["Hello!", "Cool.", "Nice!"]
encoded_sequences = [
[101, 7592, 999, 102],
[101, 4658, 1012, 102],
[101, 3835, 999, 102],
]
model_inputs = torch.tensor(encoded_sequences)
3.3 Tokenizers
3.3.1 Loading and saving
from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained("bert-base-cased")
tokenizer("Using a Transformer network is simple")
# 输出
'''
{'input_ids': [101, 7993, 170, 11303, 1200, 2443, 1110, 3014, 102],
'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0],
'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]}
'''
# 保存
tokenizer.save_pretrained("directory_on_my_computer")
3.3.2 Tokenization
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
sequence = "Using a Transformer network is simple"
tokens = tokenizer.tokenize(sequence)
print(tokens) # 输出 : ['Using', 'a', 'transform', '##er', 'network', 'is', 'simple']
# 从token 到输入 ID
ids = tokenizer.convert_tokens_to_ids(tokens)
print(ids) # 输出: [7993, 170, 11303, 1200, 2443, 1110, 3014]
3.3.3 Decoding
decoded_string = tokenizer.decode([7993, 170, 11303, 1200, 2443, 1110, 3014])
print(decoded_string) # 输出: 'Using a Transformer network is simple'
4. 相关概念
4.1 Tokenizer
模型只能处理数字, 因此我们需要找到一种将原始文本转换为数字的方法。这就是标记器(tokenizer)所做的, 并且有很多方法可以解决这个问题。目标是找到最有意义的表示——即对模型最有意义的表示——并且如果可能的话, 找到最小的表示。
与其他神经网络一样, Transformer 模型不能直接处理原始文本, 故使用分词器进行预处理。使用AutoTokenizer类及其from_pretrained()方法。
from transformers import AutoTokenizer
checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
# 若要指定我们想要返回的张量类型(PyTorch、TensorFlow 或普通 NumPy), 我们使用return_tensors参数
raw_inputs = [
"I've been waiting for a HuggingFace course my whole life.",
"I hate this so much!",
]
inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="pt")
print(inputs)
4.2 Model
Transformers 提供了一个AutoModel类, 它也有一个from_pretrained()方法:
from transformers import AutoModel
checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = AutoModel.from_pretrained(checkpoint)
# 如果我们将预处理过的输入提供给我们的模型, 我们可以看到:
outputs = model(**inputs)
print(outputs.last_hidden_state.shape)
在此图中, 模型由其嵌入层和后续层表示。嵌入层将标记化输入中的每个输入ID转换为表示关联标记(token)的向量。后续层使用注意机制操纵这些向量, 以生成句子的最终表示。
Transformers中有许多不同的体系结构, 每种体系结构都是围绕处理特定任务而设计的。以下是一个非详尽的列表:
- *Model (retrieve the hidden states)
- 使用 AutoModel类, 当您希望从检查点实例化任何模型时, 这非常方便。
- *ForCausalLM
- *ForMaskedLM
- *ForMultipleChoice
- *ForQuestionAnswering
- *ForSequenceClassification
- *ForTokenClassification
- 以及其他
4.3 Post-Processing
模型最后一层输出的原始非标准化分数。要转换为概率, 它们需要经过一个SoftMax层(所有 Transformers 模型都输出 logits, 因为用于训练的损耗函数一般会将最后的激活函数(如SoftMax)与实际损耗函数(如交叉熵)融合 。
import torch
predictions = torch.nn.functional.softmax(outputs.logits, dim=-1)
print(predictions)
4.4 输入词id(input_ids)
它们是每个句子中标记的唯一标记(token)
将文本翻译成数字被称为编码(encoding).编码分两步完成: 标记化, 然后转换为输入 ID –> input_ids
4.5 注意力遮罩(attention_mask)
这个公式的输出就是注意力得分, 怎么来理解这个式子, 我们用一个例子来类比。想象一下我们在百度进行搜索的一个场景, _Q_就相当于我们在输入框输入的关键词, 当我们输入关键词之后搜索引擎会根据我们的关键词与文档的相似度输出一个快照列表, K_就是这个快照列表, 每个文档与我们输入的关键词的相似度不同, 所以排在第一个的是搜索引擎认为最重要的文档, 打分就高, 其他依次降序排列;然后你点进去阅读了这篇文章, 那么这篇文章的内容我们就可以类比为_V。这是一个搜索引擎的检索过程, 而 Attention 的计算过程与搜索的过程几乎完全相同, 我们结合下面这张图来详细的说明一下注意力的计算过程。
注意力遮罩(attention_mask) , 但我们在那个时候没有讨论类型标记ID(token_type_ids)
Attention masks是与输入ID张量形状完全相同的张量, 用0和1填充: 1s表示应注意相应的标记, 0s表示不应注意相应的标记(即, 模型的注意力层应忽略它们)。
4.6 类型标记ID(token_type_ids)
类型标记ID(token_type_ids)的作用就是告诉模型输入的哪一部分是第一句, 哪一部分是第二句。
inputs = tokenizer("This is the first sentence.", "This is the second one.")
inputs
# tokenizer.convert_ids_to_tokens(inputs["input_ids"])
# ['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]']
4.6 填充 padding
4.7 截断 truncation
4.8 Special tokens
分词器在开头添加特殊词[CLS], 在结尾添加特殊词[SEP]。
Transformers模块的矢量输出通常较大。它通常有三个维度:
- Batch size: 一次处理的序列数(在我们的示例中为2)。
- Sequence length: 序列的数值表示的长度(在我们的示例中为16)。
- Hidden size: 每个模型输入的向量维度。
4.9 Multi Head Attention(MHA)
为什么要使用多头注意力?多头注意力机制提供了多个表示子空间, 每个头独享不同得_Q_, K, _V_权重矩阵, 这些权重矩阵每一个都是随机初始化, 在训练之后, 每个头都将输入投影到不同的表示空间, 多个 head 学习得注意力侧重点可能略微不同, 这样给了模型更大的容量。(可以想象一下 CNN 中不同的滤波器分别关注着不同的特征一样)。
这里说一下 Multi Query Attention(MQA), 这也是在一些大模型中使用的对 MHA 进行改造的手段, 比如:Falcon、PaLM 等。MQA 就是在所有的注意力头上共享_K_, V, 提升推理性能、减少显存占用。就这么简单。