[LLM基础] Transformer 库的使用

画师:竹取工坊

大佬们好!我是Mem0rin!现在正在准备自学转码。

如果我的文章对你有帮助的话,欢迎关注我的主页Mem0rin,一起进步!


文章目录


给 LLM 基础收个尾然后就开 agent 了。希望这三篇博客可以让读者有一个 Transformer 的大致概念,希望我也是。

学习资料来自于Hugging Face。欢迎一起学习交流经验。

一、pipeline() 函数的内部处理

在下列代码中:

python 复制代码
from transformers import pipeline

classifier = pipeline("sentiment-analysis")
classifier(
    [
        "I've been waiting for a HuggingFace course my whole life.",
        "I hate this so much!",
    ]
)

pipeline() 函数内部经历了三个主要步骤:预处理、模型计算、后处理。

原始文本通过 tokenizer 进行分词,转换成计算机可处理的数字,模型再根据对应的任务转化成张量,最后再通过后处理转换为综合为 1 的概率分布,表明文本的情感倾向。

下面会从标记器(tokenizer)、模型计算和后处理进行说明。

二、Tokenizer

tokenizer 的任务是把文本转换成模型可以处理的数据,下面是三种比较经典的 tokenization 算法:

1. word-based

基本思想是把句子按照单词划分,并给每个单词都分配一个 ID,构成一个词汇表。比如 "I love you." 就会拆分成 "I"、"love"、"you."。

局限性在于:要覆盖一个语言需要的 token 是巨大的,并且我们无法得知相似的单词之间的关联性(比如 dog 和 dogs)。

如果我们只给常见的单词分配 token,并自定义一个 token 来表示词汇表以外的单词("unknown" token,通常表示为[UNK] 或者 <unk>)。那么当 tokenizer 看到过多这样的 token 就会损失信息。

2. character-based

基本思想是把句子按照字符划分,比如英语就是字母、标点符号和其他标志构成,这样词汇量就会大幅减少。

但这样的方法也并不是完美的,他意味着在部分语言(比如英语)每个 token 的信息量都会减少,并且需要处理大量的 token。

3. subword-based

基于前两种方法的思想,我们提出了基于子词的分词方法。子词分词的原则是:保留常见词,把罕见词拆分成有意义的子词。

例如,"annoyingly"可能被视为一个罕见的词,可以分解为"annoying"和"ly"。这两者都可能作为独立的子词并且出现得更频繁,同时"annoyingly"的含义通过"annoying"和"ly"的复合含义得以保留。

这样的子词提供了大量的语义信息,只需要2个 token 就可以表示一个长词!这样我们在词汇量低的时候也能获得相对良好的覆盖率。这种方法在土耳其语等粘着型语言(agglutinative languages)中特别有用,你可以通过将子词串在一起来形成(几乎)任意长的复杂词。

目前的大语言模型基本都是 subword-based 的分词方法,或者是其分化,比如:

  • Byte-level BPE,用于 GPT-2
  • WordPiece,用于 BERT
  • SentencePiece or Unigram,用于多个多语言模型

4. 代码说明

分词

分词是通过 tokenizer 的 tokenize() 方法实现的:

python 复制代码
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")

sequence = "Using a Transformer network is simple"
tokens = tokenizer.tokenize(sequence)

print(tokens)

这个方法的返回是一个分词完毕的字符串:

python 复制代码
['Using', 'a', 'transform', '##er', 'network', 'is', 'simple']
分配 imput ID

在分词完毕后,tokenizer 就会根据预处理得到的词汇表进行对应,返回对应的 input ID,token_type_ids 和 attention_mask。我们使用的是 convert_tokens_to_ids() 方法:

python 复制代码
ids = tokenizer.convert_tokens_to_ids(tokens)

print(ids)

得到的结果如下:

python 复制代码
{'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]}

三、模型计算原理

当我们用分词器把文本转换成数字表示之后,我们需要通过模型计算转换成张量。

高维向量

首先 Transformer 主体会对 tokenizer 输出的 input ID 进行处理,输出一个高维向量,通常有三个维度:

  • Batch size(批次大小):一次处理的序列数(在我们的示例中为 2)。
  • Sequence length(序列长度):表示序列(句子)的长度(在我们的示例中为 16)。
  • Hidden size(隐藏层大小):每个模型输入的向量维度。(对于较小的模型,常见的是 768,对于较大的模型,这个数字可以达到 3072 或更多)。
python 复制代码
outputs = model(**inputs)
print(outputs.last_hidden_state.shape)
python 复制代码
torch.Size([2, 16, 768])

模型头处理

Transformer 模型输出的高维向量会传入到模型头进行处理。

模型头通常由一个或几个线性层组成,它的输入是隐状态的高维向量,它会并将其投影到不同的维度。

在此图中,模型由其嵌入层和后续层表示。嵌入层将 tokenize 后输入中的每个 inputs ID 转换为表示关联 token 的向量。后续层使用注意机制操纵这些向量,生成句子的最终表示。

Transformer 体系中针对于不同的任务需求会有相应的体系结构设计,以情感分类为例,我们需要一个带有序列分类头的模型(能够将句子分类为积极或消极)。因此,我们不选用 AutoModel 类,而是使用 AutoModelForSequenceClassification 。因为前面通过 AutoModel 得到的模型并没有加载 Model head。

python 复制代码
from transformers import AutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)
outputs = model(**inputs)

outputs 是模型头把输入向量的信息压缩后的结果。

四、模型计算流程

1. 把句子转换成张量:

python 复制代码
import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)

sequence = "I've been waiting for a HuggingFace course my whole life."

tokens = tokenizer.tokenize(sequence)
ids = tokenizer.convert_tokens_to_ids(tokens)
input_ids = torch.tensor(ids)
# 这一行会运行失败
model(input_ids)

这是因为 torch.tensor() 函数需要传入一个句子列表

python 复制代码
input_ids = torch.tensor([ids])
print("Input IDs:", input_ids)

output = model(input_ids)
print("Logits:", output.logits)

2. 处理多个序列

当我们需要处理多个句子的时候,可以把这些句子都放在一个句子列表里统一转换。

但是长度不同的词无法转换成张量,例如:

python 复制代码
batched_ids = [
    [200, 200, 200],
    [200, 200]
]
填充

为了解决这个问题我们使用填充。 Padding 通过在值较少的句子中添加一个名为 padding_id 的特殊单词来确保我们所有的句子长度相同。

python 复制代码
padding_id = 100

batched_ids = [
    [200, 200, 200],
    [200, 200, padding_id],
]
attention_mask

之后我们尝试一下分别处理和批处理

python 复制代码
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)

sequence1_ids = [[200, 200, 200]]
sequence2_ids = [[200, 200]]
batched_ids = [
    [200, 200, 200],
    [200, 200, tokenizer.pad_token_id],
]

print(model(torch.tensor(sequence1_ids)).logits)
print(model(torch.tensor(sequence2_ids)).logits)
print(model(torch.tensor(batched_ids)).logits)

得到的结果如下

python 复制代码
tensor([[ 1.5694, -1.3895]], grad_fn=<AddmmBackward>)
tensor([[ 0.5803, -0.4125]], grad_fn=<AddmmBackward>)
tensor([[ 1.5694, -1.3895],
        [ 1.3373, -1.2163]], grad_fn=<AddmmBackward>)

我们会发现批处理得到的第二行和 sequence2 对应的 logits 不一样,这是因为注意力机制会兼顾上下文,因此也会关注到多余的填充token。

我们需要告诉这些注意层忽略填充token,因此需要注意力掩码来实现(attention mask)。

截断

面对更长的句子,我们由两种方案

  • 使用支持更长序列长度的模型(如 LED、Longformer)
  • 截断序列

截断序列我们可以通过设定 max_sequence_length 参数来阶段序列

python 复制代码
sequence = sequence[:max_sequence_length]

五、后处理

模型计算会返回一个张量,形如:

python 复制代码
tensor([[-1.5607,  1.6123],
        [ 4.1692, -3.3464]], grad_fn=<AddmmBackward>)

但是这样的模型预测是 logit (几何几率),而不是概率分布,因此我们还需要进行后处理,把它转化成概率。

要转换为概率,它们需要经过 SoftMax 层(所有🤗Transformers 模型的输出都是 logits,因为训练时的损失函数通常会将最后的激活函数(如 SoftMax)与实际的损失函数(如交叉熵)融合)

python 复制代码
import torch

predictions = torch.nn.functional.softmax(outputs.logits, dim=-1)
print(predictions)
python 复制代码
tensor([[4.0195e-02, 9.5980e-01],
        [9.9946e-01, 5.4418e-04]], grad_fn=<SoftmaxBackward>)

从而得到模型预测如下:

  • 第一句:消极的概率:0.0402,积极的概率:0.9598
  • 第二句:消极的概率:0.9995,积极的概率:0.0005

六、最后复习一下

从原始文本到最终的输出:

复制代码
原始文本
  ↓
Tokenizer 分词、分配ID
  ↓
input_ids + token_type_ids + attention_mask
  ↓
Transformer 主体进行模型计算
  ↓
hidden states,也就是高维向量
  ↓
模型头 model head 继续计算
  ↓
logits 张量
  ↓
后处理:softmax / label 映射
  ↓
最终结果:label + score
相关推荐
UXbot2 小时前
轻量级原型工具如何支持Web应用的完整设计到开发链路
android·前端·人工智能·ios·交互·ui设计
学习中.........2 小时前
万字硬核解析:从零看透 Transformer 与大语言模型(架构、数学与工程实践)
语言模型·架构·transformer
weixin_468466852 小时前
大语言模型智能助手核心应用场景与落地指南
人工智能·python·深度学习·神经网络·语言模型·自然语言处理·大语言模型
小新同学^O^2 小时前
简单学习 --> llm是怎么训练出来的?
人工智能·深度学习·学习
LaughingZhu2 小时前
Product Hunt 每日热榜 | 2026-05-23
人工智能·经验分享·深度学习·神经网络·产品运营
盼小辉丶2 小时前
PyTorch强化学习实战——使用高级组件复现DQN
pytorch·深度学习·强化学习
帅次2 小时前
Jetpack Compose 焦点与键盘:FocusRequester、imePadding 与 BringIntoView 实战
android·android studio·android jetpack·android runtime
AI医影跨模态组学3 小时前
Lancet Digital Health(IF=24.1)德国德累斯顿工业大学医学院:深度学习评估结直肠癌的基因型-表型相关性
人工智能·深度学习·论文·医学影像·影像组学
YOLO数据集集合3 小时前
滑坡智能识别|遥感卫星无人机多源影像数据集|深度学习语义分割开源基准
人工智能·深度学习·yolo·目标检测·视觉检测·无人机