pipelines
Transformers 库最基础的对象就是 pipeline()
函数,它封装了预训练模型和对应的前处理和后处理环节(分词、编解码过程)。我们只需输入文本,就能得到预期的答案。那其背后具体做了什么呢?
|
|
其背后经过了三个步骤:
- 预处理,将原始文本转换为模型可以接受的输入格式
- 将处理好的输入送入模型,根据具体任务进行推理生成
- 对模型的输出进行后处理,将其转换为人类方便阅读的格式
使用分词器进行预处理
因为神经网络模型无法直接处理文本,因此首先需要通过分词器 (tokenizer)将文本转换为模型可以理解的数字。
我们对输入文本的预处理需要与模型自身预训练时的操作完全一致,只有这样模型才可以正常地工作。注意,每个模型都有特定的预处理操作。因此我们使用 AutoTokenizer
类和它的 from_pretrained()
函数,它可以自动根据模型 checkpoint 名称来获取对应的分词器。
|
|
输出为:
|
|
可以看到,输出中包含两个键 input_ids
和 attention_mask
,其中 input_ids
对应分词之后的 tokens 映射到的数字编号列表,而 attention_mask
则是用来标记哪些 tokens 是被填充的(这里“1”表示是原文,“0”表示是填充字符)。
checkpoint
(检查点)是指在模型训练过程中,为了避免可能出现的意外情况,定期保存模型的状态,这个状态包含了所使用的模型名称、模型的参数、优化器的状态、训练步数等信息。注意,此时的到的只是分词后token对应的ID,并没有得到经过LLM编码的高维向量。
将预处理好的输入送入模型
预训练模型的下载/加载方式和分词器 (tokenizer) 类似,Transformers 包提供了一个 AutoModel
类和对应的 from_pretrained()
函数。
|
|
预训练模型的本体只包含基础的 Transformer 模块,对于给定的输入,它会输出一些神经元的值,称为 hidden states 或者特征 (features)。对于 NLP 模型来说,可以理解为是文本的高维语义表示。这些 hidden states 通常会被输入到其他的模型部分(称为 head),以完成特定的任务。

相信你还没有理解,来看接下来这一段代码:
|
|
那它们之间的区别就在于Head
部分,如果使用AutoModel
就相当于只是用预训练模型中最基础的 Transformer 模块,得到的是大模型的高维嵌入向量。而如果使用AutoModelForCausalLM
,就相当于多加了一个Head
来完成特定的任务。
Transformers 库封装了很多这样不同的结构,常见的有:
*Model
(返回 hidden states)*ForCausalLM
(用于条件语言模型)【我用过的就是这种】*ForMaskedLM
(用于遮盖语言模型)*ForMultipleChoice
(用于多选任务)*ForQuestionAnswering
(用于自动问答任务)*ForSequenceClassification
(用于文本分类任务)*ForTokenClassification
(用于 token 分类任务,例如 NER)
Transformer 模块的输出是一个维度为 (Batch size, Sequence length, Hidden size) 的三维张量,其中 Batch size 表示每次输入的样本数量,即每次输入多少个句子,上例中为 2;Sequence length 表示文本序列的长度,即每个句子被分为多少个 token,上例中为 16;Hidden size 表示每一个 token 经过模型编码后的输出向量(语义表示)的维度。
AutoModel.from_pretrained(checkpoint)
- 这个方法加载的是基础的预训练模型架构,不包含特定任务的头部(head)。基础模型通常会输出隐藏状态(hidden states),这些隐藏状态可以作为特征表示,用于下游任务的进一步处理。例如,在文本分类任务中,可以将这些隐藏状态输入到一个全连接层进行分类。
- 适用于那些需要对模型进行自定义扩展或微调的场景,可以根据自己的需求添加特定任务的头部,以适应不同的任务。
AutoModelForCausalLM.from_pretrained(checkpoint)
- 这个方法加载的是专门用于因果语言模型(Causal Language Model,CLM)任务的预训练模型。因果语言模型的目标是根据前面的上下文预测下一个词,常用于文本生成任务等。该模型已经包含了一个语言建模头部(language modeling head),可以直接用于生成文本,无需额外添加特定任务的头部。
- 使用于文本生成任务类。
完整代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
from transformers import AutoModel, AutoModelForSequenceClassification, AutoTokenizer import torch checkpoint = "/home/caijinwei/disk1/Hugging-Face/deepseek-14B" tokenizer = AutoTokenizer.from_pretrained(checkpoint) #------------------使用 AutoModel 加载基础模型----------------- model = AutoModel.from_pretrained(checkpoint) input_text = "Once upon a time" inputs = tokenizer(input_text, padding=True, truncation=True, return_tensors="pt") outputs = model(**inputs) print(outputs.last_hidden_state.shape) #------使用 AutoModelForSequenceClassification 加载模型-------- model = AutoModelForSequenceClassification.from_pretrained(checkpoint) input_text = "Once upon a time" inputs = tokenizer(input_text, padding=True, truncation=True, return_tensors="pt") outputs = model(**inputs) print(outputs.logits) print(outputs.logits.shape)
outputs.logits
是模型最终的未经过归一化处理的预测分数,如[-1.5607, 1.6123],它们并不是概率值。只是outputs
中的一部分信息。如果直接print(outputs)
输出的更全。其输出分别为:
1 2 3 4 5 6
# AutoModel输出,(batch_size, sequence_length, vocab_size) torch.Size([1, 4, 768]) # AutoModelForSequenceClassification输出,句子的情感分类值 tensor([[-1.5607, 1.6123], grad_fn=<AddmmBackward0>) torch.Size([1, 2]) # 标签,positive 或 negative
补充:查看输出的最后隐藏状态形状
1 2 3 4
last_hidden_states = outputs.last_hidden_state print(last_hidden_states.shape) #-------------------等价于------------------- print(outputs.last_hidden_state.shape)
对模型输出进行后处理
由于模型的输出只是一些数值,因此并不适合人类阅读。还要经过解码操作,比如以下代码可以得到生成的文本。
|
|
可以看到, pipeline 背后的工作原理就是最底层的实现方式,它封装好了底层代码方便使用而已。下面会具体介绍组成 pipeline 的两个重要组件模型(Models
类)和分词器(Tokenizers
类)。
outputs = model(**inputs)
这种调用方式主要用于获取模型在输入数据上的中间结果,比如隐藏状态(hidden states)、未归一化的预测分数(logits)等。这些结果通常用于进一步的任务处理。
output = model.generate(**inputs)
此调用方式专门用于文本生成任务。它会基于输入的上下文,使用特定的生成策略(如贪心搜索、束搜索等)来生成后续的文本序列。
底层–模型
在大部分情况下,我们都应该使用 AutoModel
来加载模型。这样如果我们想要使用另一个模型(比如把 BERT 换成 RoBERTa),只需修改 checkpoint,其他代码可以保持不变。
所有存储在HuggingFace
上的模型都可以通过 Model.from_pretrained()
来加载权重,参数可以像上面一样是 checkpoint 的名称,也可以是本地路径(预先下载的模型目录)。
|
|
如果本地没有下载该模型的权重文件,代码运行后会自动缓存下载的模型权重,默认保存到 ~/.cache/huggingface/transformers
。
底层–分词器
由于神经网络模型不能直接处理文本,因此我们需要先将文本转换为数字,这个过程被称为编码 (Encoding),包含两个步骤:
- 分词:使用分词器按某种策略将文本切分为 tokens;
- 映射:将 tokens 转化为对应的 token IDs(词表);
分词器的加载与模型相似,使用 Tokenizer.from_pretrained()
函数。同样地,在大部分情况下我们都应该使用 AutoTokenizer
来加载分词器。
|
|
分词器编码-方式1
可以通过 encode()
函数将上述两个步骤合并,并且 encode()
会自动添加模型需要的特殊 token,例如 BERT 分词器会分别在序列的首尾添加 [CLS] 和 [SEP] 。
|
|
其输出为如下,其中 101 和 102 分别是 [CLS] 和 [SEP] 对应的 token IDs。
|
|
分词器编码-方式2
注意,上面这些只是为了演示。在实际编码文本时,最常见的是直接使用分词器进行处理,这样不仅会返回分词后的 token IDs,还自动包含了模型需要的其他输入。例如 BERT 分词器还会自动在输入中添加 token_type_ids
和 attention_mask
。
|
|
其输出为如下
|
|
也就是说,两者区别在于:
tokenizer.encode
方法:返回的是编码后的 ID 列表tokenizer
方法:更通用,返回一个包含更多信息的字典,如 attention_mask 、token_type_ids等
分词器解码
文本解码 (Decoding) 与编码相反,负责将 token IDs 转换回原来的字符串。注意,解码过程不是简单地将 token IDs 映射回 tokens,还需要合并那些被分为多个 token 的单词。通过 decode()
函数解码前面生成的 token IDs:
|
|
输出为
|
|
底层–Padding 与 Attention Mask
在实际中,一个 batch 包含多个输入,每个输入有长有短,而输入张量必须是严格的二维矩形,维度为 (batch size,sequence length),即每一段文本编码后的 token IDs 数量必须一样多。我们需要通过 Padding
操作,在短序列的结尾填充特殊的 padding token,使得 batch 中所有的序列都具有相同的长度。
在进行 Padding 操作时,我们必须明确告知模型哪些 token 是我们填充的,它们不应该参与编码。这就需要使用到 Attention Mask
了。它且仅由 0 和 1 组成的张量,0 表示对应位置的 token 是填充符,不参与计算。
正如前面所说,在实际使用时,应该直接使用分词器来完成包括分词、转换 token IDs、Padding、构建 Attention Mask、截断等操作。
|
|
分词器的输出包含了模型需要的所有输入项。包括 input_ids
和 attention_mask
。
Padding 操作
Padding 操作通过 padding
参数来控制:
padding="longest"
: 将序列填充到当前 batch 中最长序列的长度;padding="max_length"
:将所有序列填充到模型能够接受的最大长度,例如 BERT 模型就是 512。(如果代码指定了max_length
的长度,就不是 512 了)padding=True
: 等同于padding="longest"
;
|
|
截断操作
截断操作通过 truncation
参数来控制,如果 truncation=True
,那么大于模型最大接受长度的序列都会被截断。此外,也可以通过 max_length
参数来控制截断长度。
|
|
返回格式
分词器还可以通过 return_tensors
参数指定返回的张量格式:设为 pt
则返回 PyTorch 张量;tf
则返回 TensorFlow 张量,np
则返回 NumPy 数组。
|
|
完整格式
综上所述,实际使用分词器时,我们通常会同时进行 padding 操作和截断操作,并设置返回格式为 Pytorch 张量,这样就可以直接将分词结果送入模型。
|
|
其输出为
|
|
在 padding=True, truncation=True
设置下,同一个 batch 中的序列都会 padding 到相同的长度,并且大于模型最大接受长度的序列会被自动截断。
底层–模型推理和输出
在将文本转换为模型可接受的输入格式后,就可以进行模型推理了。模型推理是指将预处理后的输入数据送入模型,让模型根据其内部的参数和结构进行计算,从而得到每个token包含上下文信息的高维嵌入。不同任务类型的模型会返回不同形式的输出。
|
|
下面是一些参数解释:
**inputs
:这是一个解包操作,inputs
通常是一个字典,包含了input_ids、attention_mask
等信息。通过**inputs
可以将字典中的键值对作为关键字参数传递给generate()
方法,自动处理 input 的所有参数输出长度
:指定生成文本的最大长度(以token为单位),当生成文本达到这个长度时,生成过程将停止max_length
生成序列的最大总长度,包含了输入加输出的token总长度max_new_tokens
仅关注新生成的token数量,而不考虑输入序列本身的长度
num_beams
:束搜索(Beam Search)的束宽。束搜索会在每一步保留num_beams
个最有可能的候选序列,可以提高生成文本的质量,从而找到更优的生成结果,但同时也会增加计算量和内存消耗no_repeat_ngram_size
:模型在生成过程中不允许出现重复的 n - gram 大小。n - gram 是指连续的 n 个标记组成的序列,例如当n = 2
时,就是不允许连续的两个标记组成的序列重复出现。用于避免生成的文本中出现重复的短语或句子,提高生成文本的多样性和质量。例如,如果生成的文本中已经出现了 “the dog”,那么在后续的生成过程中就不会再出现 “the dog” 这个 2 - gramoutput[0]
:model.generate()
方法返回的是一个包含多个生成序列的张量,output[0]
表示取第一个生成序列。在num_return_sequences = 1
的情况下,通常只生成一个序列skip_special_tokens=True
:在解码过程中是否跳过特殊符号,如[CLS]、[SEP]。设置为True
可以去除这些特殊标记,使生成的文本更加干净和易读
生成任务输出
|
|
对于文本生成任务(如使用 AutoModelForCausalLM
),模型通过 generate()
方法直接生成 token IDs,需通过分词器解码为文本。
隐藏状态提取
|
|
生成参数控制:max_length
、num_beams
(束搜索)、temperature
(采样温度)等参数可调节生成结果的质量和多样性。
完整代码
|
|
device_map="auto"
:该参数用于指定模型在设备(如 CPU、GPU)上的分布方式。
"auto"
表示让transformers
库自动分配显卡,将模型拆分到多个 GPU 上,避免内存不足的问题。
注意,如果设置了
device_map="auto"
,就不要在输入以下代码,这可能会覆盖之前的设备映射,导致模型被加载到单一设备上
1 2 3
# 检查是否有可用的 GPU,将模型移动到 GPU 设备 device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model.to(device)
例如,下面代码不可取
1 2 3 4 5 6 7
model = AutoModelForCausalLM.from_pretrained(model_name, device_map="auto") device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model.to(device) question = "介绍一下上海大学。" inputs = tokenizer(question,padding=True,return_tensors='pt').to(device)