返回

LLM系列-4:LLM的使用

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库中相关类

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_logitsend_logits 来输出答案的位置。需要从模型输出的 tokens 中提取出 起始位置结束位置 的答案,然后将其通过 convert_ids_to_tokens 转换为字符串。decode 方法主要用于生成文本,而不是处理直接的预测位置(例如起始和结束位置)。

在问答任务中,我们不是生成每个 token,而是从模型输出的 start_logitsend_logits 中直接找到答案的位置。

HuggingFace Transformers

AutoModel

AutoModelHuggingFace Transformers 的基础类,这是底层模型加载器,适用于各种预训练 Transformer 模型(如 BERT、MPNet、RoBERTa…)

  • 它输出的是 每个 token 的向量[batch_size, seq_len, hidden_size]
  • 不做 pooling,也不做语义对比学习

SentenceTransformer

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 找到:

image-20250430171349871

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 通过词表转换成高维嵌入,最终经过大模型处理生成我们期望的输出结果。

fb3c64ab5417435

此外,还有一些相关概念:

  • 最大输出长度(输出限制):模型单次生成文本的最大长度
  • 上下文长度:指的是模型在处理输入时能够 “看到” 或考虑的文本范围,即模型在生成输出时,会参考的前文的 token 数量。
  • 上下文截断:应对超长对话的策略。

上下文截断是一种在工程层面实施的策略,而非模型本身固有的能力。其具体指的是:如果用户在多轮对话中累积的输入和输出 Token 数量超出最大上下文长度的限制,服务端通常会保留最近的内容,而丢弃早期输入,即只能 “记住最近的,遗忘久远的”。

模型的最大输入 token 长度有多种不同叫法:

  • 最大位置嵌入(Max Position Embeddings):位置嵌入用于标记每个 token 在输入序列中的位置,规定了模型能处理的输入序列最大长度

  • 上下文窗口大小(Context Window Size):指的是模型在处理输入时能够考虑的上下文范围,也就是输入序列的最大长度。这个概念在自回归模型中常用,模型生成每个 token 时会参考之前的上下文,上下文窗口大小决定了能参考的最大 token 数量。

参考链接

大模型关键参数解读

光终究会洒在你的身上,你也会灿烂一场!
本博客已稳定运行