Title image
红茶馆学习笔记

算法岗面经合集


简单说说对RAG的理解,以及你们项目中是怎么应用到RAG的

说说什么是LoRA

反向传播的时候我们修改模型的参数矩阵W0,这个矩阵的参数量特别大,我们可以通过奇异值分解(SVD)的方式将它分解成两个小矩阵AxB,其中A是dxr,B是rxd,这样我们就将训练的参数量从dxd降到dxr+rxd。

在实际操作中,我们通常将LoRA挂载在Attention层上,对Q和V矩阵进行调整。研究表明,对这些层调整对模型能力的改变最有效。在训练时,我们只更新A和B的参数,并且会使用一个缩放因子α/r来控制LoRA对原始模型的影响权重。最后我们只需要将W0+(AxB)就可以直接把LoRA训练应用到原始的W0中。

你们用的什么向量检索数据库?说几条数据库用的检索指令

在 Milvus 向量数据库中,检索主要分为 向量搜索 (Search) 和 标量查询 (Query) 两大类。通常我们说的“检索”多指基于向量相似度的 Search。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 向量相似度搜索
# 基础搜索指令
results = collection.search(
data=[[0.1, 0.2, ...]], # 目标查询向量(支持批量查询)
anns_field="vector_field", # 向量字段名称
param={"metric_type": "L2", "params": {"nprobe": 10}}, # 检索参数
limit=10, # 返回前 10 个最相似的结果 (TopK)
output_fields=["title"] # 指定返回的标量字段
)

# 带条件的过滤
# 示例:搜索向量相似度最高,且 id 大于 100 且 category 为 'book' 的数据
res = collection.search(
data=query_vectors,
anns_field="vector_field",
param=search_params,
limit=5,
expr="id > 100 and category == 'book'" # 使用布尔表达式过滤
)
# 范围检索
search_params = {
"metric_type": "L2",
"params": {
"radius": 10.0, # 相似度半径(距离小于10
"range_filter": 5.0 # 过滤掉距离小于5的(可选,用于寻找特定环带)
}
}
res = collection.search(data=[vector], anns_field="vec", param=search_params, limit=10)
1
2
3
4
5
6
7
8
9
10
11
12
# 标量查询
# 示例:通过主键 ID 检索特定数据
res = collection.query(
expr="id in [1, 2, 3]",
output_fields=["vector_field", "title"]
)

# 示例:范围检索
res = collection.query(
expr="age >= 18 and age <= 30",
output_fields=["name"]
)

用过LangChain这些比较基础的框架吗

向量检索的时候遇到过什么问题,怎么解决的,有没有用过混合检索(展开:向量检索用了什么模型?混合检索是怎么做的?什么是Re-Rank?)

Cohere的Embedding模型和ReRank模型。

目前绝大多数向量模型都基于 Transformer 架构(比如 BERT 的变体)。它们采用“双编码器”(Bi-Encoder)结构,将查询词(Query)和文档(Document)分别转换为高维空间的坐标。向量检索懂语义,但有时会犯糊涂。比如搜索“钨合金”,向量检索可能会给你推荐“钢合金”、“铝合金”,因为它觉得语义很像。这时候就需要关键词搜索来救场,确保精准匹配。

语义相似度≠事实相关性。向量检索(Embedding)本质上是计算两个向量在空间里的几何距离。ReRank模型通过Cross-Encoder将问题和文档同时输入模型进行交互计算,相比检索用的Bi-Encoder精度更高,速度更慢。它的作用是将Bi-Encoder粗检索出的50-100条数据进行重新打分并排序,使LLM能够更加专注于与问题更相关的知识。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# Attention模块
import torch
import torch.nn as nn
import torch.nn.functional as F
import math

class SelfAttention(nn.Module):
def __init__(self, embed_dim):
super(SelfAttention, self).__init__()
self.embed_dim = embed_dim

# 定义 Q, K, V 的线性变换层
self.query = nn.Linear(embed_dim, embed_dim)
self.key = nn.Linear(embed_dim, embed_dim)
self.value = nn.Linear(embed_dim, embed_dim)

def forward(self, x):
# x 的维度: [batch_size, seq_len, embed_dim]
batch_size, seq_len, embed_dim = x.size()

# 1. 生成 Q, K, V
Q = self.query(x) # [batch_size, seq_len, embed_dim]
K = self.key(x) # [batch_size, seq_len, embed_dim]
V = self.value(x) # [batch_size, seq_len, embed_dim]

# 2. 计算注意力得分 (Scaled Dot-Product)
# 将 K 转置进行矩阵乘法: (Q @ K.T)
# attention_scores 维度: [batch_size, seq_len, seq_len]
attention_scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(embed_dim)

# 3. 通过 Softmax 归一化得分,得到权重
attention_weights = F.softmax(attention_scores, dim=-1)

# 4. 将权重作用于 V
# output 维度: [batch_size, seq_len, embed_dim]
output = torch.matmul(attention_weights, V)

return output, attention_weights

# 测试一下
dim = 512
sample_input = torch.randn(1, 10, dim) # 1个样本, 长度为10, 维度512
sa_layer = SelfAttention(dim)
out, weights = sa_layer(sample_input)

print(f"输出形状: {out.shape}") # torch.Size([1, 10, 512])
  • **线性映射 (nn.Linear)**:虽然输入都是 x,但我们通过三个不同的线性层把它们投影到不同的空间,增加了模型的表达能力。
  • **缩放因子 ($\sqrt{d_k}$)**:代码中的 math.sqrt(embed_dim) 非常关键。如果维度很大,点积的值会爆炸,导致 Softmax 进入梯度极小的区域。缩放一下能让训练更稳定。
  • Softmax:它保证了每一行权重的和为 1,这意味着模型在决定“注意力分配”时是一个加权平均的过程。

大模型微调有哪些手段?

从代码和工程实现的角度来看,大模型微调(Fine-tuning)主要分为 全量微调参数高效微调(PEFT) 以及 对齐微调(RLHF/DPO) 三大类。

目前业界最主流的方案是 PEFT(主要是 LoRA/QLoRA),因为它极大地降低了显存需求。

以下我将结合主流库(Hugging Face transformers, peft, bitsandbytes, trl)的代码片段来详细解析这些手段。


1. 参数高效微调 (PEFT)

核心思想: 冻结原模型的大部分参数,只训练极少量新增的参数(Adapter)。

A. LoRA (Low-Rank Adaptation)

这是目前最常用的微调手段。它在 Transformer 的 Attention 层(如 Q、V 矩阵)旁路插入低秩矩阵。

  • 代码实现核心: 使用 peft 库。
  • 关键配置: LoraConfig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from transformers import AutoModelForCausalLM
from peft import get_peft_model, LoraConfig, TaskType

# 1. 加载基座模型
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-2-7b-hf")

# 2. 定义 LoRA 配置
peft_config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
inference_mode=False,
r=8, # 低秩矩阵的秩,越大参数越多,表现可能越好但训练越慢
lora_alpha=32, # LoRA 的缩放系数,通常设为 r 的 2-4 倍
lora_dropout=0.1,
target_modules=["q_proj", "v_proj"] # 指定对哪些层进行 LoRA 注入(根据模型架构不同而变)
)

# 3. 将模型包装为 PeftModel
model = get_peft_model(model, peft_config)
model.print_trainable_parameters()
# 输出示例: "trainable params: 4,194,304 || all params: 6,742,609,920 || trainable%: 0.06"

B. QLoRA (Quantized LoRA)

在 LoRA 的基础上,将基座模型量化为 4-bit 加载,进一步通过 Double Quantization 和 Paged Optimizers 压榨显存。单卡 24G 显存即可微调 30B 级别的模型。

  • 代码实现核心: bitsandbytes + peft
  • 关键配置: BitsAndBytesConfig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import torch
from transformers import AutoModelForCausalLM, BitsAndBytesConfig
from peft import prepare_model_for_kbit_training, get_peft_model, LoraConfig

# 1. 配置 4-bit 量化参数
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16
)

# 2. 以 4-bit 加载基座模型
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-2-7b-hf",
quantization_config=bnb_config,
device_map="auto"
)

# 3. 预处理模型以支持 k-bit 训练 (开启梯度检查点等)
model = prepare_model_for_kbit_training(model)

# 4. 挂载 LoRA (配置同上)
# QLoRA 通常需要对全连接层 all-linear 挂载 adapter 效果才好
peft_config = LoraConfig(..., target_modules="all-linear")
model = get_peft_model(model, peft_config)

C. Prompt Tuning / P-Tuning v2

在输入层或每一层插入可训练的 Prompt 向量(Soft Prompt)。

  • 代码视角: 这里的改动在于 PeftConfig 的类型。
  • 现状: 代码实现简单,但目前效果普遍不如 LoRA,用得越来越少。
1
2
3
4
5
6
7
8
9
10
11
from peft import PromptTuningConfig, TaskType

peft_config = PromptTuningConfig(
task_type=TaskType.CAUSAL_LM,
num_virtual_tokens=20, # 在输入前加入 20 个可训练的 token
prompt_tuning_init="TEXT",
prompt_tuning_init_text="Classify if the tweet is positive or negative:",
tokenizer_name_or_path="meta-llama/Llama-2-7b-hf",
)
# 后续流程同 LoRA


2. 全量微调 (Full Fine-Tuning / SFT)

核心思想: 更新模型的所有权重。这通常用于数据量极大、且算力资源非常充足的场景(如企业私有化大模型继续预训练)。

  • 代码实现核心: 标准的 PyTorch 训练循环或 Trainer,但不使用 PEFT 库,也不冻结层。
  • 工程难点: 必须结合 DeepSpeedFSDP (Fully Sharded Data Parallel) 来解决显存爆炸问题。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from transformers import Trainer, TrainingArguments

# 必须配合 DeepSpeed 配置文件才能在多卡上跑起来
training_args = TrainingArguments(
output_dir="./output",
per_device_train_batch_size=4,
gradient_accumulation_steps=8,
fp16=True,
deepspeed="./ds_config_zero3.json", # 关键:使用 ZeRO-3 切分参数
)

trainer = Trainer(
model=model, # 原始模型,未被冻结
args=training_args,
train_dataset=dataset,
# ...
)


3. 对齐微调 (Alignment)

在 SFT (Supervised Fine-Tuning) 之后,为了让模型更符合人类偏好(Helpful, Harmless, Honest),通常使用以下方法。

A. RLHF (PPO - Proximal Policy Optimization)

这是 ChatGPT 早期使用的方法。需要训练一个 Reward Model,然后用强化学习更新 LLM。

  • 代码库: trl (Transformer Reinforcement Learning)。
  • 复杂度: 极高(涉及到 4 个模型:Policy, Value, Reward, Reference)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from trl import PPOTrainer, PPOConfig

# 需要准备两个模型:正在训练的模型 和 参考模型(防止跑偏)
ppo_trainer = PPOTrainer(
config=PPOConfig(...),
model=model,
ref_model=ref_model,
tokenizer=tokenizer,
dataset=dataset,
)

# 训练循环手动挡
for epoch, batch in enumerate(dataloader):
query_tensors = batch["input_ids"]
# 1. 生成回复
response_tensors = ppo_trainer.generate(query_tensors, ...)

# 2. 计算奖励 (通常调用外部 Reward Model)
rewards = reward_model(query_tensors, response_tensors)

# 3. PPO 更新一步
stats = ppo_trainer.step(query_tensors, response_tensors, rewards)

B. DPO (Direct Preference Optimization)

这是目前最火的对齐方法。它不需要训练 Reward Model,也不需要复杂的 PPO 采样,直接使用 (prompt, chosen, rejected) 数据对进行优化。

  • 代码库: trl
  • 优势: 显存占用低,训练稳定,代码极简。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from trl import DPOTrainer

# 数据集格式必须包含:['prompt', 'chosen', 'rejected']
# chosen: 人类觉得好的回答
# rejected: 人类觉得差的回答

dpo_trainer = DPOTrainer(
model=model, # 待训练模型
ref_model=None, # DPO 内部通常会自动加载一份 ref_model,或者使用 LoRA 适配器复用
beta=0.1, # 控制与参考模型的偏离程度
train_dataset=dataset,
tokenizer=tokenizer,
args=training_args
)

dpo_trainer.train()


4. 总结:代码视角下的选择指南

微调手段 核心库/类 显存需求 修改的参数量 适用场景
Full Fine-Tuning Trainer + DeepSpeed(ZeRO3) 极大 (80G x 8+) 100% 基础模型继续预训练,拥有海量算力
LoRA peft.LoraConfig 中等 < 1% 垂直领域微调,注入新知识或指令格式
QLoRA BitsAndBytesConfig + LoRA 极低 (24G单卡跑30B) < 1% 消费级显卡微调,效果接近全量
DPO trl.DPOTrainer 中等 (略高于SFT) < 1% (可结合LoRA) 让模型更听话、减少幻觉、风格对齐

如果你是个人开发者或中小企业,代码起手式通常是:transformers 加载模型 -> bitsandbytes 4bit量化 -> peft 挂载 LoRA -> trl 进行 SFT -> (可选) trl 进行 DPO。

市面主流大模型有什么区别,为什么会产生这些区别

虽然“训练数据”确实是决定模型知识边界的基石,但在数据之外,架构设计(Architecture)、训练目标(Training Objectives)、推理机制(Inference Mechanism)以及对齐策略(Alignment Strategy) 的不同,才是导致各大模型在“智商”、“性格”和“效率”上产生巨大差异的根本原因。

以下从技术原理工程实现的角度,分析主流大模型(如 GPT-4, Claude 3, Llama 3, DeepSeek-V3/R1, Gemini)的主要区别及产生原因:


1. 稠密模型 (Dense) vs. 混合专家模型 (MoE)

这是目前开源与闭源模型最大的分水岭。

  • Dense (稠密模型):Llama 3, Qwen-72B
    • 机制: 每次推理,所有的参数都会被激活参与计算。
    • 优点: 训练收敛更稳,微调更容易,社区生态好(容易量化、部署)。
    • 缺点: 随着参数变大,推理成本呈线性增长,很难做到极大参数(如 > 500B)。
  • MoE (Mixture of Experts):GPT-4, Mixtral 8x7B, DeepSeek-V3
    • 机制: 将模型的一层拆分为多个“专家”(Experts,通常是多个 FFN 层)。每次推理,路由器(Router)只选择 Top-K 个专家(比如 64 个里选 2 个)参与计算。
    • 区别产生的原因: 为了打破“参数量 vs 推理成本”的铁律。MoE 可以在拥有巨大总参数量(拥有广博的知识)的同时,保持极低的活跃参数量(推理速度快、成本低)。
    • DeepSeek 的特例: DeepSeek 进一步改进了 MoE,引入了 _Shared Expert_(共享专家)和 Load Balancing Loss 的创新,解决了专家负载不均导致的“死神经元”问题。

2. 注意力机制的变体 (Attention Mechanism)

标准的 Transformer Attention 计算复杂度是 $O(N^2)$,这限制了长文本能力。不同模型为了解决显存(KV Cache)瓶颈,采用了不同策略。

  • GQA (Grouped-Query Attention):Llama 2/3, Mistral
    • 机制: 多个 Query 头共享一组 Key/Value 头。
    • 原因: 为了减少推理时的显存占用(KV Cache)并提高解码速度。这是 Llama 能在消费级显卡跑起来的关键。
  • MLA (Multi-Head Latent Attention): DeepSeek-V2/V3 独创
    • 机制: 对 KV 矩阵进行低秩压缩(Low-Rank Compression)。
    • 原因: 极度压缩 KV Cache。这使得 DeepSeek 可以在极低显存下处理超长上下文,也是其 API 价格极其便宜的核心技术原因之一。
  • Ring Attention / Long-Context 优化: 如 **Gemini 1.5 (支持 1M+ token)**。
    • 原因: Google 针对超长文本(整本书、长视频)进行了系统级优化,使得 Attention 计算可以在 TPU 集群间切分,从而突破单卡显存限制。

3. “快思考” (System 1) vs. “慢思考” (System 2)

这是 GPT-4oOpenAI o1 / DeepSeek-R1 的本质区别。

  • 标准 LLM (System 1):
    • 机制: Next Token Prediction。根据概率直觉,一个词接一个词地吐,不回头,不反思。
    • 局限: 遇到复杂逻辑(如数学证明、复杂代码架构),容易一本正经胡说八道。
  • 推理模型 (System 2 / Reasoning Models):OpenAI o1, DeepSeek-R1
    • 机制: 在输出最终答案前,强制模型生成一段“思维链”(Chain of Thought),并且这段思维链通常经过了大规模强化学习(RL)的训练。
    • 区别产生的原因:
      • 传统的 SFT(监督微调)只能让模型模仿人类说话的格式。
      • 引入 RL(GRPO 或 PPO) 并不是为了教模型知识,而是教模型如何验证自己的步骤、如何回溯纠错。这种模型在“回答”前会先“思考”几千个 token。

4. 对齐哲学的不同 (Alignment Strategy)

为什么 Claude 看起来“小心翼翼/有道德洁癖”,而 xAI 的 Grok 看起来“放飞自我”?这取决于 RLHF 阶段的 Reward Model 设计。

  • RLHF (基于人类反馈的强化学习):GPT 系列
    • 侧重于 Helpful 和无害化,尽量让标注员满意。
  • Constitutional AI (宪法级 AI): Anthropic (Claude) 的招牌。
    • 机制: 不完全依赖人类标注(人类标注有偏见且累),而是用一个模型(根据一套宪法/原则)去监督另一个模型(RLAIF)。
    • 结果: Claude 表现出更强的原则性,但也容易出现拒答(Refusal)。
  • DPO / GRPO (直接偏好优化):Llama 3, DeepSeek
    • 机制: 跳过 Reward Model 建模,直接用偏好数据优化 Policy。
    • 结果: 训练更稳定,模型更听从指令,较少出现“废话文学”。

5. 原生多模态 vs. 拼接多模态

  • 拼接方案 (Connector-based):LlaVA, 早期 GPT-4 Vision
    • 机制: 视觉编码器(CLIP/ViT)提取图片特征 -> 投影层 -> 语言模型。
    • 问题: 视觉和语言存在“语义鸿沟”,不仅慢,而且细节丢失严重。
  • 原生方案 (Native Multimodal):GPT-4o, Gemini 1.5
    • 机制: 图片、音频直接 tokenize 进入 Transformer,和文本 token 处于同一向量空间。
    • 原因: 为了实现实时性(Real-time)。GPT-4o 能听懂你的语气停顿,是因为音频没有转成文字,而是直接作为信号被处理了,保留了副语言特征(语气、情感)。

总结:为什么会产生这些区别?

除了商业壁垒,本质是三个Trade-off(权衡)的选择:

  1. 推理成本 vs. 模型智力
    • MoE (DeepSeek/GPT-4) 是为了在维持高智力的同时降低 API 成本。
    • Dense (Llama) 是为了方便开发者私有化部署。
  2. 显存瓶颈 vs. 上下文长度
    • MLA/GQA 是为了让模型能塞进有限的 GPU 显存里,从而处理更长的文档。
  3. 直觉回答 vs. 逻辑正确
    • Reasoning (o1/R1) 是通过牺牲时间(生成更多 token)来换取数学/代码的准确率。

这也是为什么在代码实现微调时,你会发现 DeepSeek 这种模型对 Flash Attention 的版本、显存优化的配置要求与 Llama 截然不同。

不通过大规模训练让大模型达到能分辨正面/负面情感的能力,要求准确性高,并且能输出置信度

要在不进行大规模训练(即不进行全量微调或从头预训练)的前提下,利用大模型(LLM)实现高准确度的情感分析并输出置信度,最佳策略是结合Prompt Engineering(提示工程)In-Context Learning(上下文学习)以及Logits(逻辑斯特)/ Logprobs(对数概率)分析

以下是一套分层级的技术方案,从核心机制到准确率优化策略:


核心方案:基于 Logits 的 Few-Shot 分类(Logits-based Classification)

这是目前在不训练模型的情况下,获取高准确率和真实数学置信度的标准做法。

1. 原理

大模型本质上是预测下一个 Token 的概率分布。我们不需要让模型生成一长串文本(如 “This review seems positive…”),而是强制模型在两个特定 Token 之间做选择(例如 “Positive” 和 “Negative”)。

2. 具体步骤

  1. 构建 Prompt(提示词): 使用 Few-Shot(少样本) 策略,提供 3-5 个典型的正/负面示例。

    Prompt 示例: 评论: “这东西太烂了,浪费钱。” -> 情感: 负面 评论: “非常好用,超出预期!” -> 情感: 正面 评论: “中规中矩,没什么亮点。” -> 情感: 中性 评论: “[输入文本]” -> 情感:

  2. 限制输出空间(Token Constraint): 限制模型生成的下一个 Token 必须是映射标签的 Token(例如 ID 对应 “正面” 或 “负面”)。

  3. 获取 Logprobs(核心步骤): 调用 API(如 OpenAI 的 logprobs=True)或本地模型推理时,不直接取生成的文本,而是读取候选 Token 的概率值

    假设:

    • Token A (“正面”) 的 Logit 分数为 $L_{pos}$
    • Token B (“负面”) 的 Logit 分数为 $L_{neg}$
  4. 计算置信度(Softmax): 我们需要将这两个 Logits 归一化,忽略词表中的其他词。 $P(Positive) = \frac{e^{L_{pos}}}{e^{L_{pos}} + e^{L_{neg}}}$ 这个 $$P$$ 值就是你的置信度。如果 $$P > 0.9$$,说明模型非常有信心;如果 $$P \approx 0.5$$,说明模型很犹豫(可能需要人工介入)。


进阶优化:提升准确率的策略

仅依靠上述核心方案可能无法应对复杂的讽刺或隐晦表达,以下方法可进一步提升准确率:

1. 动态上下文学习 (kNN-ICL / RAG-based Few-Shot)

静态的 3 个例子可能无法覆盖所有情况。

  • 做法: 准备一个包含几百条高质量标注数据的小型向量库。
  • 流程: 当新文本进来时,先通过向量检索(RAG)找到与之语义最相似的 3-5 个已标注例子。
  • 效果: 将这些最相似的例子放入 Prompt 中作为上下文。这能显著提升模型处理特定领域(如金融、医疗或口语俚语)的能力。

2. 思维链(Chain-of-Thought, CoT)

对于复杂情感(如“虽然贵,但是值”),直接分类容易出错。

  • 做法: 修改 Prompt,让模型先解释原因,再输出标签。
  • Prompt: 请先分析这段话的情感倾向逻辑,最后输出标签。格式:分析:[原因]... 标签:[正面/负面]
  • 难点: CoT 很难直接通过 Logits 获取置信度。
  • 解决方案: 分两步走。
    1. 让模型生成分析文本。
    2. 把分析文本拼接到 Prompt 后,再询问“基于上述分析,情感是?”,此时再抓取 Logits 计算置信度。

3. 自洽性投票 (Self-Consistency) - 适用于无法获取 Logits 的场景

如果你调用的模型(如某些封闭 API)不提供 Logprobs,可以使用此方法估计置信度。

  • 做法: 设置较高的 Temperature(如 0.7),对同一条输入并行请求 5-10 次。
  • 计算: 统计结果分布。
    • 如果 10 次里有 10 次是“正面”,置信度 ≈ 100%。
    • 如果 6 次“正面”,4 次“负面”,置信度 ≈ 60%(低置信度,建议人工复核)。

专家级技巧:校准 (Calibration)

大模型通常存在过度自信(Overconfidence)的问题(即模型说 99% 确定,但实际准确率只有 80%)。如果不训练,如何解决?

  1. 无内容输入校准 (Content-Free Calibration):
    • 输入 Prompt:”评论: N/A -> 情感:” (输入空内容或无意义字符)。
    • 观察模型偏向 “正面” 还是 “负面” 的概率。
    • 如果模型天生偏向 “正面”(例如 P=0.7),则在实际推理时,需要减去这个偏差(仿射变换)。
  2. 温度调整 (Temperature Scaling):
    • 找一个小型的验证集(比如 50 条数据),计算模型的 Logits。
    • 寻找一个最佳的温度系数 $T$(通常 > 1),用 $Logits / T$ 来平滑概率分布,使其与真实的准确率对齐。这不需要反向传播,只需简单的数值优化。

总结实施路线图

如果你想明天就上线这个功能,建议按以下顺序执行:

  1. 准备数据: 准备 20-50 条涵盖不同场景的高质量标注数据(金标数据)。
  2. 构建检索器: 使用 Sentence-BERT 等模型将这些数据向量化,建立简单的索引(RAG)。
  3. 推理流程:
    • 输入用户文本。
    • 检索 Top-3 相似的历史案例。
    • 组装 Prompt(包含指令 + Top-3 案例)。
    • 调用模型(推荐 Llama-3-70B, GPT-4o, 或 Qwen-2.5-72B),开启 Logprobs
    • 关注 “正/负” 两个 Token 的 Logits。
  4. 输出处理:
    • Softmax 归一化得到置信度。
    • 设置阈值(例如置信度 < 0.75 则标记为“待人工复核”)。

这种方法既规避了训练的算力成本和灾难性遗忘风险,又能通过 RAG 和 Logits 获得接近微调模型的高准确率和可解释的置信度。

现在的Decoder模型为什么直接就能输入Token,而不需要Encoder编码的语义?

这是一个非常深刻且直指大模型(LLM)架构本质的问题。

简单直接的回答是:Decoder 模型并非“不需要”编码语义,而是它把“编码(Encoding)”和“生成(Decoding)”这两件事在同一个架构里做完了。

在 Decoder-only(如 GPT 系列)架构中,输入部分(Prompt)的处理过程本质上就是一个编码过程

为了讲清楚这个问题,我们需要打破对“Encoder”和“Decoder”的刻板印象。让我们从以下四个层面来拆解:

1. 第一步:Token 并不是直接进入模型的

你的问题中提到“直接输入 Token”,这里其实有一个中间步骤。 任何 Transformer 模型(无论是 BERT 还是 GPT),Token 进去后的第一件事都是查表,通过 Embedding Layer(嵌入层) 变成一个高维向量。

  • Token: 苹果 (ID: 1052)
  • Embedding: [0.12, -0.59, 0.88, ...] (假设是 4096 维的向量)

这个 Embedding 向量本身就已经包含了静态的语义信息(比如“苹果”和“梨”在向量空间离得很近)。所以,模型一开始拿到的就不是干巴巴的数字 ID,而是带有基础语义的数学表示。

2. 核心机制:Self-Attention(自注意力)既是编码也是解码

Encoder 和 Decoder 的核心组件都是 Self-Attention(自注意力机制)。它们的数学公式几乎一模一样,唯一的区别在于“能看谁”

  • Encoder (BERT类):双向的。看一个词时,既能看它前面的词,也能看它后面的词。这叫“全局理解”。
  • Decoder (GPT类):单向(因果)的。看一个词时,只能看它前面的词(历史),不能看后面。

为什么 Decoder 能理解输入? 当你把一句话(比如“把这句话翻译成英文:你好”)输入给 GPT 时,虽然它是 Decoder 架构,但在处理这句提示词(Prompt)时,模型会计算每一个 Token 与它之前所有 Token 的关系。

这个过程就是“编码”

  • 当模型处理到“好”这个字时,它通过 Attention 机制“看”到了前面的“翻译”、“英文”、“:”、“你”。
  • 此时,“好”这个位置的隐藏层向量(Hidden State),实际上就聚合了整句话的语义信息。
  • 结论: Decoder 的“前半段”(处理 Prompt 的过程)就是在做 Encoder 的工作——将上下文理解并压缩成向量。

3. 架构的同质化:Decoder Block ≈ Encoder Block

如果我们把 Transformer 拆开看,Encoder Block 和 Decoder Block 的内部结构有 90% 是完全一样的:

  1. Multi-Head Self-Attention
  2. Feed-Forward Network (MLP)
  3. Layer Norm & Residual Connection

唯一的区别是 Decoder 多了一个 Mask(掩码),用来防止它“偷看”后面的答案。

既然积木块是一样的,功能自然也是通用的。 Encoder 能提取特征,Decoder 也能提取特征。只要堆叠的层数够多(大模型通常有几十上百层),Decoder 就能通过一层层的迭代,把输入的浅层语义抽象成深层逻辑。

4. 视角的转变:从“翻译”到“续写”

Encoder-Decoder 架构(如 T5, Bart) 的逻辑是“翻译思维”:

先把原句完整读完,压缩成一个“思想向量”(Encoder工作),再根据这个思想把新句子写出来(Decoder工作)。

Decoder-only 架构(如 Llama, GPT) 的逻辑是“续写思维”:

我不需要先把整本书读完合上书再背诵。我是一边读(Encoding context),一边根据读到的内容预测下一个字。

在 GPT 模型中,输入(Prompt)对于模型来说,就是“已经发生的历史”。模型通过计算输入序列的注意力,已经完全获取了输入的语义。


总结

Decoder 模型之所以不需要独立的 Encoder,是因为:

  1. 功能内化: 它利用 Self-Attention 机制,在处理 Prompt 阶段就已经完成了对上下文的语义编码。
  2. 向量传递: 输入的 Token 经过 Embedding 和多层 Transformer Block 处理后,最后输出的那个向量(Last Token Representation)已经包含了极其丰富的语义信息,足以用来预测下一个 Token。
  3. 架构通用: 现代大模型证明了,单向的 Attention(只要规模够大、数据够多)足以通过“预测下一个词”的任务,倒逼模型学会理解上文的语义。

打个比方:

  • Encoder-Decoder 像是:先让一个阅读理解专家(Encoder)读题,写个摘要给写作专家(Decoder),写作专家再写答案。
  • Decoder-only 像是:一个全能专家,他读题的过程就在脑子里形成了理解,读完题的瞬间(最后一个 Token),他直接顺着思路开始写答案。






:D 获取中...


© - 0nlyb1acktea - 2022 - 2026 - Powered by Hexo Theme Quark