from transformers import pipeline
classifier = pipeline("sentiment-analysis")
Transformer 库 pipline 背后的动作
有三个主要动作
- Tokenizer 预处理
- 将输入传递给模型
- 后处理
1. 使用 Tokenizer 进行预处理
模型不会理解文字的,故将文本输入转换为模型能够理解的数字。所以使用tokenizer。一个 tokenizer 做的事情包括:
- 将输入分割成单词、子词或符号(如标点符号),这些被称为 tokens
- 将每个标记映射 map 到一个整数,input IDs 是 token 的唯一标记,所以称为 ids。
- 添加可能对模型有用的额外输入
上述的预处理需要与模型预训练时结构完全相同。使用 AutoTokenizer 类及其 from_pretrained() 方法,会自动获取与模型分词器相关联的数据并将其缓存。(比如第一次使用摸个模型时,会自动下载相应的词汇表 vocab.txt)
sentiment-analysis pipline的默认 checkpoint 是 distilbert-base-uncased-finetuned-sst-2-english
from transformers import AutoTokenizer
checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
如此得到了 tokenizer。我的句子传给 tonkenizer,得到 input IDs,即数字表达文字。然后需要将 inputIDs 变为 tensor,作为模型的输入。transformers 库中,指定 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)
得到tensor 结果:
{
'input_ids': tensor([
[ 101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012, 102],
[101, 1045,5223,2023,2061,2172, 999, 102, 0, 0, 0, 0, 0, 0, 0, 0]
]),
'attention_mask': tensor([
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]
])
}
attention_mask 用于指示模型应该关注输入序列中的哪些位置,1 表示有效位置,0 表示 padding 位置。
2. 传入模型
from transformers import AutoModel
checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = AutoModel.from_pretrained(checkpoint)
使用 已下载的 checkpint 实例化一个模型。
上述的模型只有 Transformer 的模块,给出 input,transformer 模型会输出 hidden states(也称为features).对于每一个input,模型会输出一个包含 hidden states 的高维向量,表示 Transformer 模型对于 input 的理解。
hidden states 是 Transformer 最后一层的输出(特征向量)。上文提到,不同的任务可以使用相同的架构完成,但不同的任务都会关联一个不同的头部 head。模型 head 是 一个组件,通常由一层或几层组成,用于将 Transformer 输出特征向量转换为特定任务的输出。所以 head 接在 Transformer 模块之后(这时的输出包含了多层Attention的丰富信息,是特定任务的基础),作为特定任务Head的输入。(即便模型有 28 层 Transformer,Hidden States 也是最后一个 Transformer 的输出)
Transformer 模型输出的高维向量有 3 个维度
- Batch size: 一次处理的序列数量(示例中为 2)
- Sequence length: 序列的数值表示的长度(示例中为 16)。
- Hidden size: 每个模型输入的向量维度。
隐藏大小可以非常大。如:
outputs = model(**inputs)
print(outputs.last_hidden_state.shape)
torch.Size([2, 16, 768])
2.1 模型头 model head
Model heads 的作用是让数字变得有意义,Transformer 模型的 hidden state(上下文表示)输出映射到特定任务的输出,如分类(概率分布)、回归(数值)或生成(下一个token)。作用是让 Hidden State 的数字表示变得有意义,适配具体任务需求。所以 Head 是在模型的尾部,Transformers结束之后的位置:
| 注意 model head 的位置 |
Model head 的数学模型通常是一个线性层,对 Transformer 输出的 hidden state(向量 $ h \in \mathbb{R}^d $)进行变换:$ y = W \cdot h + b $,其中 $ W $ 和 $ b $ 是可学习的权重和偏置,输出 $ y $ 适配任务。不同任务和架构的 LLM 使用不同 model head。例如,BERT用分类head(线性+softmax用于分类),GPT用语言建模head(线性层预测词汇表概率)。
Transformer 库提供了多种不同的 model Head,比如序列分类的 head:
from transformers import AutoModelForSequenceClassification
checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)
outputs = model(**inputs)
这里使用的就不是 AutoModel 了,而是具体的 AutoModelForSequenceClassification。现在如果看一下输出的形状,维度会低得多:模型头接收之前看到的高维向量作为输入,并输出包含两个值(每个标签一个)的向量:
print(outputs.logits.shape)
torch.Size([2, 2])
由于我们只有两个句子和两个标签,并且是分类任务, 模型结果就是 2x2 的。没有 hidden states。
总结,不同的 model head 对应不同的输出维度大小。
3. 后处理
模型输出的值本身不一定有意义,比如接上例中的输出:
print(outputs.logits)
tensor([[-1.5607, 1.6123],
[ 4.1692, -3.3464]], grad_fn=<AddmmBackward>)
模型对第一句预测了 [-1.5607, 1.6123] ,对第二句预测了 [ 4.1692, -3.3464] 。这些不是概率,而是 logits,即模型最后一层的原始未归一化分数。
要转换为概率,它们需要通过 Softmax 层。
所有 Transformers 模型直接输出 logits,未归一化。因为训练的 loss 函数通常会将融合最后一层激活函数与实际损失函数融合。比如,训练 loss 可以是 softmax 融合 cross-entropy。
import torch
predictions = torch.nn.functional.softmax(outputs.logits, dim=-1)
print(predictions)
tensor([[4.0195e-02, 9.5980e-01],
[9.9946e-01, 5.4418e-04]], grad_fn=<SoftmaxBackward>)
可以看出,模型对第一句预测了 [0.0402, 0.9598] ,对第二句预测了 [0.9995, 0.0005] 。
进一步地,要获取每个位置的对应标签,我们可以检查模型配置的 id2label 属性:{0: 'NEGATIVE', 1: 'POSITIVE'} 这就是有人可读的信息了。
到此,pipline 背后的 3 个步骤都明了了。
Stay curious and keep asking questions! 🧠✨