LLM输出向量池化方式
LLM 输出池化(Output Pooling)是一种对大模型输出进行处理的操作,旨在将模型生成的一系列特征向量(多个token)或表征转换为一个固定长度的向量。
平均池化
平均池化会计算序列中所有向量的平均值,从而得到一个固定长度的向量。这种方式能有效综合序列里所有元素的信息。
1
2
3
4
5
6
|
import torch
# 假设 hidden_states 是模型的输出,形状为 (batch_size, sequence_length, hidden_size)
hidden_states = torch.randn(2, 10, 768)
pooled_output = torch.mean(hidden_states, dim=1) # 在序列长度维度上求平均
print(pooled_output.shape) # 输出: torch.Size([2, 768])
|
最大池化
最大池化会在序列中每个维度选取最大值,以此生成一个固定长度的向量。它能提取序列中的关键特征。
1
2
3
4
5
6
|
import torch
# 假设 hidden_states 是模型的输出,形状为 (batch_size, sequence_length, hidden_size)
hidden_states = torch.randn(2, 10, 768)
pooled_output, _ = torch.max(hidden_states, dim=1) # 在序列长度维度上取最大值
print(pooled_output.shape) # 输出: torch.Size([2, 768])
|
[CLS] 池化
也就是 CLS Token Pooling 。在使用预训练模型(像 BERT 这类)时,通常会在输入序列开头添加一个特殊的 [CLS]
标记。[CLS]
池化就是直接选取这个标记对应的输出向量作为整个序列的表示。
1
2
3
4
5
6
|
import torch
# 假设 hidden_states 是模型的输出,形状为 (batch_size, sequence_length, hidden_size)
hidden_states = torch.randn(2, 10, 768)
pooled_output = hidden_states[:, 0, :] # 选取每个样本的第一个向量
print(pooled_output.shape) # 输出: torch.Size([2, 768])
|
加权平均池化
也就是 Weighted Mean Pooling ,加权平均池化会依据每个向量的重要性为其分配不同的权重,再计算加权平均值。这样可以更有针对性地综合序列信息。
1
2
3
4
5
6
7
8
9
10
|
import torch
# 假设 hidden_states 是模型的输出,形状为 (batch_size, sequence_length, hidden_size)
hidden_states = torch.randn(2, 10, 768)
# 假设 weights 是每个向量的权重,形状为 (batch_size, sequence_length)
weights = torch.rand(2, 10)
weights = torch.softmax(weights, dim=1) # 确保权重和为 1
weighted_hidden_states = hidden_states * weights.unsqueeze(-1)
pooled_output = torch.sum(weighted_hidden_states, dim=1)
print(pooled_output.shape) # 输出: torch.Size([2, 768])
|
Last 池化
也就是 Last Token 池化。选取序列中最后一个 token 对应的向量作为整个序列的表示。这种池化方式在某些场景下是有效的,例如在处理文本生成或者问答任务时,模型最后一个输出的 token 可能包含了整个序列处理后的关键信息。
1
2
3
4
5
6
|
import torch
# 假设 hidden_states 是模型的输出,形状为 (batch_size, sequence_length, hidden_size)
hidden_states = torch.randn(2, 10, 768)
pooled_output = hidden_states[:, -1, :] # 选取每个样本的最后一个向量
print(pooled_output.shape) # 输出: torch.Size([2, 768])
|
在 transformers
库中,AutoModel
和相关类提供了一个统一的接口来加载不同类型的预训练模型。你可以通过 AutoModel
系列类来简化模型加载过程,而无需关心特定模型的类型。
常用的 AutoModel类
AutoModel
: 加载基础模型,适用于不带头的 Transformer 模型。
AutoModelForSequenceClassification
: 加载用于序列分类任务的预训练模型。
AutoModelForTokenClassification
: 加载用于标记分类(如命名实体识别)的模型。
AutoModelForQuestionAnswering
: 加载用于问答任务的模型。
AutoModelForCausalLM
: 加载用于自回归语言建模(生成任务)的模型。
AutoTokenizer
: 用于加载与模型匹配的标记器(tokenizer),负责文本预处理和编码。
AutoFeatureExtractor
: 用于加载与图像或其他类型输入匹配的特征提取器。
基础模型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
from transformers import AutoModel, AutoTokenizer
import torch
model_name = "bert-base-uncased"
model = AutoModel.from_pretrained(model_name,device_map="auto")
tokenizer = AutoTokenizer.from_pretrained(model_name)
question = "Hugging Face is creating a tool that democratizes AI."
inputs = tokenizer(question,padding=True,return_tensors='pt').to(model.device)
outputs = model(**inputs)
last_hidden_state = outputs.last_hidden_state # 获取模型的最后一层隐藏状态
print("Last hidden state shape:", last_hidden_state.shape)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
# -----------------查看隐藏层形状----------------------
print(output.last_hidden_state.shape)
# ------------获取输入转换后的 token-------------------
tokens = tokenizer.tokenize(question)
print(tokens)
# ----------------获取输入转换后的 token---------------
# 方式一
print(inputs)
# 方式二
input_ids = inputs["input_ids"][0]
print(input_ids)
# -------------查看每个 token 对应的文字----------------
# 方式一
tokens = tokenizer.convert_ids_to_tokens(input_ids)
print("每个 token 对应的文字:",tokens)
# 方式二()
decoded_text = tokenizer.decode(inputs["input_ids"][0], skip_special_tokens=True)
print("Decoded text:", decoded_text)
|
1
2
3
|
# 查看大模型词表大小(也可以根据config.json查看)
embeddings = model.get_input_embeddings()
print(f'嵌入矩阵大小: {embeddings.weight.size()}')
|
生成任务
用于加载 自回归语言模型 的类。自回归语言模型(Causal LM)在给定一些上下文时,会生成下一个可能的单词或 token。通常用于生成任务。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
model_name = "gpt2"
model = AutoModelForCausalLM.from_pretrained(model_name,device_map="auto")
tokenizer = AutoTokenizer.from_pretrained(model_name)
question = "Hugging Face is creating a tool that democratizes AI."
inputs = tokenizer(question,padding=True,return_tensors='pt').to(model.device)
output = model.generate(
**inputs, # 输入ID(token化后的文本)
max_length=50, # 生成的最大长度
num_return_sequences=1, # 返回生成的序列数
no_repeat_ngram_size=2, # 防止生成重复的n-gram
temperature=0.7, # 温度(控制随机性,值越大越具创造性)
top_k=50, # 只考虑前k个最可能的词
top_p=0.95, # 采用 nucleus sampling(前p%的词)
do_sample=True # 启用采样(避免确定性输出)
)
generated_text = tokenizer.decode(output[0], skip_special_tokens=True)
print(generated_text)
|
top_k
:从生成词汇中选择前 k 个最有可能的词进行采样。较小的值意味着选择更有限的词汇。
top_p
:从概率累积值中选择前 p
% 的词汇,控制采样的多样性。与 top_k
类似,也是用于限制候选词的范围。
do_sample
:是否启用采样。如果为 True
,模型将从预测的概率分布中随机采样生成下一个 token;如果为 False
,它将选择概率最大的 token(即确定性生成)。启用采样机制可以增加生成文本的多样性,避免每次生成的文本都相同。
序列分类任务
如果在做分类任务(例如情感分析),可以使用这个类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
from transformers import AutoModelForSequenceClassification, AutoTokenizer
import torch
model_name = "bert-base-uncased"
# 假设是二分类任务
model = AutoModelForSequenceClassification.from_pretrained(model_name,device_map="auto",num_labels=2)
tokenizer = AutoTokenizer.from_pretrained(model_name)
question = "Hugging Face is creating a tool that democratizes AI."
inputs = tokenizer(question,padding=True,return_tensors='pt').to(model.device)
# 前向传播
outputs = model(**inputs)
logits = outputs.logits # 获取分类任务的输出logits
print("Logits:", logits)
print(outputs.logits.shape) # 查看输出形状
|
模型的输出并不是概率值,而是模型最后一层输出的 logits 值。要将他们转换为概率值,还需要让它们经过一个 SoftMax 层。
1
2
3
|
import torch
predictions = torch.nn.functional.softmax(outputs.logits, dim=-1)
print(predictions)
|
标记分类任务
假设做的是命名实体识别任务,可以使用这个类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
from transformers import AutoModelForTokenClassification, AutoTokenizer
import torch
model_name = "bert-base-uncased"
model = AutoModelForTokenClassification.from_pretrained(model_name,device_map="auto")
tokenizer = AutoTokenizer.from_pretrained(model_name)
question = "Hugging Face is creating a tool that democratizes AI."
inputs = tokenizer(question,padding=True,return_tensors='pt').to(model.device)
# 前向传播
outputs = model(**inputs)
logits_token_class = outputs.logits # 获取每个token的分类结果
print("Token classification logits:", logits_token_class.shape)
|
问答任务类
通常用于从给定的上下文中回答问题。模型通过读取文本(context)和问题(question),然后返回答案。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
from transformers import AutoModelForQuestionAnswering, AutoTokenizer
import torch
model_name = "distilbert-base-uncased-distilled-squad"
model = AutoModelForQuestionAnswering.from_pretrained(model_name,device_map="auto")
tokenizer = AutoTokenizer.from_pretrained(model_name)
question = "What is the capital of France?"
context = "France is a country in Europe. Paris is its capital and the largest city."
inputs = tokenizer(question, context, return_tensors="pt").to(model.device)
# `start_logits` 和 `end_logits` 分别表示答案的起始和结束位置
with torch.no_grad():
outputs = model(**inputs)
start_position = torch.argmax(outputs.start_logits) # 答案起始位置
end_position = torch.argmax(outputs.end_logits) # 答案结束位置
# 7. 解码答案
answer = tokenizer.convert_tokens_to_string(
tokenizer.convert_ids_to_tokens(inputs['input_ids'][0][start_position:end_position+1])
)
print("Question:", question)
print("Answer:", answer)
|
在问答任务中,模型通过 start_logits
和 end_logits
来输出答案的位置。需要从模型输出的 tokens 中提取出 起始位置 和 结束位置 的答案,然后将其通过 convert_ids_to_tokens
转换为字符串。decode
方法主要用于生成文本,而不是处理直接的预测位置(例如起始和结束位置)。
在问答任务中,我们不是生成每个 token,而是从模型输出的 start_logits
和 end_logits
中直接找到答案的位置。
AutoModel
AutoModel
是 HuggingFace Transformers
的基础类,这是底层模型加载器,适用于各种预训练 Transformer 模型(如 BERT、MPNet、RoBERTa…)
- 它输出的是 每个 token 的向量(
[batch_size, seq_len, hidden_size]
)
- 不做 pooling,也不做语义对比学习
SentenceTransformer
是 sentence-transformers 库提供的高级类。是对 HuggingFace 中 AutoModel
的高级封装,专门用于生成句子级别嵌入(sentence embeddings)的工具。
- 它是专门为 语义嵌入(sentence embedding)任务设计的封装器
- 内部仍然是基于
AutoModel
,但额外做了:
- Pooling(Mean/CLS/Max)
- 对比学习微调(例如:SimCSE、Triplet loss 等)
- 更易用的
.encode()
接口,输出句子级别嵌入向量
1
2
3
4
5
|
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("all-mpnet-base-v2")
embedding = model.encode("This is a test")
print(embedding.shape) # e.g. [768]
|
一些句子级嵌入大模型,可以在 HuggingFace - Models - Libraries - sentence-transformers 找到:
Sentence-Transformers 文档
大模型词表嵌入
模型 |
词表嵌入维度 |
参数数量 |
GPT-2 |
1,024维 |
15亿 |
GPT-3 |
12,288维 |
1750亿 |
BERT-base |
768维 |
1.1亿 |
BERT-large |
1,024维 |
3.4亿 |
LLaMA2-7B |
4,096维 |
70亿 |
LLaMA-2-13B |
5,120维 |
130亿 |
LLaMA-3-8B |
4,096维 |
80亿 |
deepseek-R1-14B |
5120维 |
140亿 |
qwen2.5-7B |
3,584维 |
70亿 |
Bloom-7B1 |
4,096维 |
71亿 |
Bloom-1B7 |
2,048维 |
17亿 |
Bloom-560M |
1,024维 |
5.6亿 |
BERT模型分为24层和12层两种,BERT-base使用的是12层的Transformer Encoder结构,BERT-Large使用的是24层的Transformer Encoder结构。
对于不同的大型语言模型,它们能处理的最大token长度(也称为上下文窗口大小)是由模型本身决定的。
大模型内部都存在一个词表,存储了一系列token的集合。它决定了模型所 “认识” 的词汇和符号的范围。
在LLM的config.json
文件中,一般可以找到相关参数。
- 如在 deepseek-r1-14b 的配置文件中,
max_position_embeddings
参数定义了模型能够处理的最大位置嵌入数,也就是模型能够接受的最大token长度
- 而
vocab_size
参数就是词表的大小(即多少行);hidden_size
参数就是每一个token所对应的嵌入向量大小(即多少列)
LLM流程
Token是LLM处理文本的基本单位。当我们将文本输入 LLM 时,模型首先会将文本切分成 Token 序列,然后再对这些 Token 通过词表转换成高维嵌入,最终经过大模型处理生成我们期望的输出结果。
此外,还有一些相关概念:
- 最大输出长度(输出限制):模型单次生成文本的最大长度
- 上下文长度:指的是模型在处理输入时能够 “看到” 或考虑的文本范围,即模型在生成输出时,会参考的前文的 token 数量。
- 上下文截断:应对超长对话的策略。
上下文截断是一种在工程层面实施的策略,而非模型本身固有的能力。其具体指的是:如果用户在多轮对话中累积的输入和输出 Token 数量超出最大上下文长度的限制,服务端通常会保留最近的内容,而丢弃早期输入,即只能 “记住最近的,遗忘久远的”。
模型的最大输入 token 长度有多种不同叫法:
-
最大位置嵌入(Max Position Embeddings):位置嵌入用于标记每个 token 在输入序列中的位置,规定了模型能处理的输入序列最大长度
-
上下文窗口大小(Context Window Size):指的是模型在处理输入时能够考虑的上下文范围,也就是输入序列的最大长度。这个概念在自回归模型中常用,模型生成每个 token 时会参考之前的上下文,上下文窗口大小决定了能参考的最大 token 数量。
参考链接
大模型关键参数解读