在当今的AI世界里,Transformer架构无疑是自然语言处理(NLP)领域的超级英雄。从ChatGPT这样的聊天机器人,到翻译工具,再到代码生成器,Transformer都扮演着核心角色。它在2017年的论文《Attention is All You Need》中首次亮相,彻底改变了我们对语言的理解和生成方式。今天,我们就来深入解析Transformer和大型语言模型(LLMs)的核心机制,用简单易懂的方式带你领略它们的魅力。
一、Transformer和LLMs是什么?
想象一下,你在读一本书,当看到“巴黎”时,你立刻就能联想到“埃菲尔铁塔”,即使它们相隔数页。这就是Transformer的魔法——它能够瞬间连接文本中的任意两个词,无论它们相隔多远。Transformer是一种基于自注意力机制的神经网络架构,它可以让模型同时考虑句子中的每一个词(或称为“token”),并根据上下文动态调整它们的权重。这种并行处理方式比传统的循环神经网络(RNN)快得多,也更智能。
大型语言模型(LLMs)则是将Transformer架构扩展到极致的产物。它们拥有数十亿甚至数千亿的参数(可以理解为“神经元”),并在海量的文本数据上进行训练,比如整个互联网上的文本内容。像GPT-4、PaLM 2、LLaMA 3这样的模型,不仅能写诗、翻译语言、总结书籍,还能通过逐步推理解决数学问题。
1. 文本分词:把文本切成小块
在Transformer处理文本之前,它需要先把文本切分成更小的单元,也就是“token”。这就好比把一个句子切成拼图的碎片。简单的分词器会按照单词来切分(比如“I love AI”会被切分成[“I”, “love”, “AI”]),但现代的LLMs通常使用子词分词(subword tokenization),这样可以更好地处理罕见单词(比如“playing”会被切分成[“play”, “##ing”])。
为什么要这么做呢?因为分词可以把原始文本映射到一个固定的词汇表中,而不是面对一个无限大的单词字典。子词方案(比如“Un”、“bel”、“iev”、“able”、“!”)可以让模型更好地处理新出现的单词,同时保持词汇表的规模可控。
不同的模型使用不同的分词器:
- GPT-2/3/4:使用字节级BPE(Byte Pair Encoding),它可以处理任何Unicode字符,甚至连表情符号“🤯”都有对应的编码。
- BERT / RoBERTa:使用WordPiece,会合并常见的词片段。
- T5 / LLaMA:使用SentencePiece,直接在原始字节上学习合并规则,不依赖于特定语言。
分词器是模型的一部分,如果换掉分词器,整个模型的ID映射就会崩溃。分词器还会注入一些特殊标记,比如[CLS],用来标记句子的开头、分隔符或结尾。分词的数量通常不等于单词的数量,这也是为什么API定价和序列长度限制都是基于token的。
举个例子,我们用text-davinci-002模型来分词一个单词“Unbelievable!”:
# Term: Unbelievable!
['Un', 'bel', 'iev', 'able', '!'] # Tokens
[3118, 6667, 11203, 540, 0] # Token IDs
# 注意:token的数量是5,而不是2!
这里有一个简单的代码示例,展示如何使用GPT-2的分词器:
# 导入分词器
from transformers import AutoTokenizer
# 要分词的文本
text = "Unbelievable!"
# 初始化GPT-2分词器
tok = AutoTokenizer.from_pretrained("gpt2")
# 分词并打印结果
print(tok.tokenize(text)) # 输出:['Un', 'bel', 'iev', 'able', '!']
2. 嵌入(Embeddings):给单词赋予数字灵魂
分词后的token ID只是数字,但Transformer需要更有意义的数字。嵌入层就是一个巨大的查找表(比如50,000 × 768),它将每个token映射到一个密集向量中,向量的方向编码了语义信息。向量越接近,表示单词的语义越相似。
举个例子,想象一个三维星系,单词是星星:国王(king)和王后(queen)在同一个星座中闪闪发光,而香蕉(banana)则在远处轨道上运行。每个星星的坐标就是它的嵌入向量。
在预训练过程中,模型会不断调整这个矩阵,使得在相似上下文中出现的token向量更接近。这有点像Word2Vec或GloVe,但它们是与整个网络一起学习的。
静态嵌入(如Word2Vec、GloVe):每个单词只有一个向量,不考虑句子上下文。 上下文嵌入(如Transformer):深层的输出会细化基础嵌入,比如“bank”在“river bank”和“savings bank”中的嵌入会有所不同。
我们用BERT模型来获取一个“上下文嵌入”:
# 打印前5维的嵌入向量
Queen: [ 0.119-0.1380.2360.006-0.087]
King: [ 0.102-0.1380.262-0.051-0.118]
Toy: [ 0.333-0.1060.223-0.105-0.278]
这里有一个简单的代码示例,展示如何可视化GPT-2的token嵌入:
# !pip install torch transformers scikit-learn matplotlib --quiet
# =========================
# 1. 设置
# =========================
import torch
import numpy as np
import matplotlib.pyplot as plt
from transformers import AutoTokenizer, AutoModel
from sklearn.decomposition import PCA # 如果喜欢可以用TSNE替换
# =========================
# 2. 加载模型和分词器
# =========================
model_name = "gpt2" # 使用的模型
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModel.from_pretrained(
model_name,
torch_dtype=torch.float16, # 保持低内存占用,同时不影响嵌入
low_cpu_mem_usage=True,
).eval() # 将模型置于评估模式
# =========================
# 3. 分词并创建向量嵌入
# =========================
WORDS = ["Queen", "King", "Toy", "Man", "Women", "Lady"] # 要分词的单词
embed_vectors = []
for word in WORDS:
ids = tok(word, add_special_tokens=False)["input_ids"]
ids_t = torch.tensor(ids).unsqueeze(0) # 形状:(1, tokens)
with torch.no_grad():
token_vecs = model.get_input_embeddings()(ids_t) # 形状:(1, tokens, 768)
# 将子词片段合并为单个768维向量(取均值即可)
embed_vectors.append(token_vecs.mean(dim=1).squeeze().cpu().numpy())
vecs = np.stack(embed_vectors) # 列表转矩阵 | 形状:(len(WORDS), 768)
# =========================
# 4. PCA(降维到2D)并可视化
# =========================
coords = PCA(n_components=2, random_state=0).fit_transform(vecs)
plt.figure()
for (x, y), label in zip(coords, WORDS):
plt.scatter(x, y)
plt.text(x + 0.02, y + 0.02, label)
plt.title("GPT-2 Token Embeddings (PCA → 2-D)")
plt.xlabel("PC-1"); plt.ylabel("PC-2"); plt.tight_layout(); plt.show()
# 打印向量的前几维
for w, v in zip(WORDS, vecs):
print(f"{w:>5}: {np.round(v[:5], 3)}")
3. 位置编码:记住顺序
虽然Transformer可以并行处理所有token,但纯嵌入向量并不知道谁在前面谁在后面。位置编码的作用就是给每个token贴上一个独特的“GPS定位”,让注意力层能够推理出顺序关系。
位置编码就像是在每个向量中轻声提醒“我是第17个token”,从而防止Transformer变成一个简单的词袋模型,让它能够记住单词的先后顺序。
经典的正弦余弦位置编码公式(来自2017年的论文《Attention is All You Need》):为每个位置生成两个不同频率的波形,并将它们加到嵌入向量上。相邻位置的模式相似,而远处位置的模式则不同。
这里有一个简单的代码示例,展示正弦余弦位置编码是如何工作的:
import torch
# 定义序列长度和嵌入维度
seq_len, d_model = 10, 8# seq_len:位置数量,d_model:嵌入维度
# 创建位置索引张量,形状为(seq_len, 1)
pos = torch.arange(seq_len).unsqueeze(1) # [[0], [1], [2], ..., [9]]
# 创建偶数维度索引:[0, 2, 4, 6]
idx = torch.arange(0, d_model, 2)
# 计算每个维度的频率/角度
# 随着idx/d_model的增加,10000^(idx/d_model)呈指数增长
# 这样就产生了从高频率(小idx)到低频率(大idx)的变化
angle_rates = 1 / (10000 ** (idx / d_model))
# 初始化位置编码矩阵,形状为(seq_len, d_model)
pe = torch.zeros(seq_len, d_model)
# 填充偶数维度为正弦值,奇数维度为余弦值
# pos * angle_rates通过广播创建一个角度矩阵
pe[:, 0::2] = torch.sin(pos * angle_rates) # 偶数维度(0,2,4,6)为正弦值
pe[:, 1::2] = torch.cos(pos * angle_rates) # 奇数维度(1,3,5,7)为余弦值
4. 自注意力:上下文的超能力
想象一下,你在读句子“The cat, which was hiding, pounced on the toy.”时,要理解“pounced”,你自然会考虑“cat”和“toy”这两个词的上下文,即使它们之间隔着其他单词。自注意力机制就是模仿这个过程,让模型中的每个单词(或token)都能“查看”句子中的其他单词,从而获取上下文信息。
自注意力的核心问题是:“在编码这个单词时,我应该关注其他单词的哪些部分?”例如,在编码“pounced”时,模型可能会给“cat”(主语)分配较高的注意力权重,给“toy”(宾语)分配较高的权重,而对“which”或“hiding”这样的单词则分配较低的权重。这种动态加权方式,无论单词之间的距离有多远,都能让Transformer灵活地捕捉到单词之间的关系。
自注意力的步骤
- 输入嵌入 → 线性投影 → 查询(Q)、键(K)、值(V)
在任何“注意力”发生之前,每个token的嵌入向量会被送入三个独立的线性层,分别生成查询向量(Q)、键向量(K)和值向量(V)。这些投影让模型将相同的输入嵌入重新写入不同的“角色”:查询向量(Q)问“我在找什么?”,键向量(K)说“我提供什么?”,值向量(V)则包含了要融合的实际内容。
- 计算原始未归一化的注意力分数
对于每个token i,我们通过计算当前token i的查询向量(q-vector)与其他所有token的键向量(k-vector)的点积,得到原始注意力分数。
- 缩放与softmax操作
为了避免维度较大时原始分数过大,我们将其除以根号d(以确保数值稳定性)。然后,我们对缩放后的原始分数进行softmax操作,使它们的总和为1,从而将分数转换为权重。
- 加权求和值向量,得到上下文嵌入
每个token的值向量(携带输入token的实际信息)乘以其对应的注意力权重,然后将它们相加,得到一个单一的输出向量。这就是模型从原始查询的角度对序列的“融合”视图。
5. 多头注意力:多角度的视角
多头注意力通过并行运行多个自注意力“头”,进一步扩展了自注意力的能力。每个头可以专注于句子的不同方面。例如,一个头可能会关注语法结构,比如将“cat”与“pounced”联系起来以确保主谓一致,而另一个头则会捕捉语义联系,比如将“toy”与“pounced”联系起来以理解动作的目标。
如果token嵌入是一个大小为d_model(例如768)的向量,而你选择了h个头(比如12个),那么你会将这个向量分成h个相等的部分,每个部分的大小为d_model/h(这里就是768 ÷ 12 = 64)。每个头独立地将它的64维切片投影到自己的查询、键和值上,并计算注意力。最后,你将所有h个头的64维输出拼接回一个d维向量(12 × 64 = 768),并将其传递下去。
通过结合这些不同的视角,模型能够构建出更丰富、更细致的句子表示。
6. 交叉注意力:连接两个世界
交叉注意力是Transformer中的一种机制,它允许解码器(负责生成输出的部分,比如GPT风格的LLMs)关注编码器的输出(负责理解输入的部分)。在Transformer中,编码器处理输入序列(比如一个法语句子),并为每个token创建一个丰富的表示。解码器在生成输出(比如英语翻译)时,使用交叉注意力来“查看”编码器的表示,以决定下一步该说什么。
想象一下,你在翻译法语短语“Je t’aime”为英语。编码器读取法语句子,并为每个单词创建一个含义的总结。解码器在写出“I love you”时,使用交叉注意力来查看编码器对“Je”(我)、“t’”(你)和“aime”(爱)的总结,以确保它选择正确的英文单词,并且顺序正确。这就好比解码器在写翻译时,向编码器询问“法语句子中这部分说了什么?”。
交叉注意力的作用在于,它将编码器的理解与解码器的生成紧密相连,确保输出与输入相关。如果没有交叉注意力,解码器就会盲目猜测,就像在不知道原始语言的情况下尝试翻译句子一样。它在编码器-解码器Transformer(比如T5,用于翻译)中非常重要,但在像GPT这样的仅解码器LLMs中则不太常见,因为它们专注于生成文本,而没有一个明确的输入序列可供关注。
7. 遮蔽注意力:保持未来的神秘
遮蔽注意力(也称为因果注意力或遮蔽自注意力)是自注意力的一种变体,用于Transformer的解码器中。它确保每个token只能“关注”它自己以及它之前的token,而不能关注它之后的token。这是因为在文本生成任务中,模型不应该“作弊”去查看它尚未生成的未来单词。这对于仅解码器的LLMs(如GPT)至关重要,因为它们是按顺序生成文本的,而且在编码器-解码器模型(如T5)的解码器中生成时也会用到。
想象一下,你在写一部悬疑小说,写到句子“The detective opened the…”时,你可以根据“detective”和“opened”来决定下一个词(可能是“door”),但你不能偷看“door”或后面的词,因为它们还没有被写出来。遮蔽注意力就是强制执行这个规则,保持未来token的“隐藏”,让模型像人类作家一样一次生成一个词。
在自注意力中,我们会计算序列中所有token之间的注意力分数。而在遮蔽注意力中,我们会应用一个遮罩来阻止对未来的token的关注。这是通过将未来token的注意力分数设置为一个非常小的数(例如-无穷大)来实现的,这样在softmax步骤后,它们的权重就会变为零。结果是,每个token只能关注它自己和之前的token,确保了一个因果的、从左到右的流动。
遮蔽注意力是让像GPT这样的LLMs能够写出连贯、富有上下文意识的文本的关键,而不会偷看未来的内容。它模拟了人类生成语言的方式——一次一个词,基于已经说过的内容。在编码器-解码器模型中,它确保解码器以逻辑顺序生成输出,同时交叉注意力将其与输入相连。如果没有遮蔽注意力,解码器可能会“看到”未来的token,从而导致无意义的预测。
8. 前馈网络 + 残差连接
前馈网络
在自注意力汇集了所有token之间的关系之后,每个token会独立地通过一个小型的前馈网络(Feed-Forward Network, FFN),以进一步深化其表示。可以想象,每个token就像一碗半熟的食材(比如“cat”或“sat”),而FFN就是厨师,它会将这碗食材加入调料(扩展表示)、以巧妙的方式混合(应用非线性激活函数,如ReLU),并将其精美地装盘(将维度还原到原来的大小)。这个过程会根据注意力提供的上下文信息,细化token的含义,捕捉更复杂的模式。
具体来说:
- 扩展:通过一个线性层将维度从768扩展到3072。
- 激活:应用GELU非线性激活函数(其他模型可能会使用ReLU、Swish或更新的函数)。
- 投影回原维度:通过另一个线性层将维度从3072还原到768。
这个MLP模块不会混合token,它只是将每个向量重新映射到一个更丰富的空间,然后将其传递下去。
残差连接
残差(或跳跃)连接是Transformer中的一个重要特性。它通过将一个子层的输入直接添加到其输出中,让深度网络能够学习增量变化(残差),而不是完整的转换。具体来说,它会将子层的输入x传递给该子层的函数F(x),然后计算输出为F(x) + x,而不是仅仅使用F(x)。
在标准的Transformer编码器或解码器块中,你会看到这种模式两次:
y1 = LayerNorm(MultiHeadAttention(x) + x)
y2 = LayerNorm(FeedForward(y1) + y1)
这里的每个“+x”或“+y1”都是一个残差快捷方式,它保持了信息的流动,即使在块变得更深时也不会受阻。
残差连接的好处在于,它通过保持信息流动并减轻梯度消失问题,稳定了训练过程。这使得模型能够学习到增量的变化,因此如果FFN的转换没有帮助,模型可以退回到输入状态。这不仅稳定了训练,还提高了性能。
9. 层归一化:平衡交响乐
深度神经网络就像一场混乱的交响乐:当激活值在层与层之间流动时,它们的规模会发生变化,梯度会波动,训练速度也会变慢。层归一化(LayerNorm)通过让每个token的向量在进入下一个子层之前自我标准化(均值接近0,方差接近1)来解决这个问题。
可以想象,一个40人的交响乐团暂停演奏,指挥悄悄调整每个乐器的音量,以确保没有一个单独的小号声盖过其他乐器。一旦平衡,交响乐就可以继续,声音清晰且有控制。
层归一化会重新调整每个token的特征向量,使其具有零均值和单位方差,然后立即让模型学习它自己的首选比例和偏移量。这种“每个样本的归一化”稳定了训练过程,消除了批量大小带来的问题,并且对于现代Transformer的深度和性能至关重要。
具体来说,对于一个维度为d的token:
- 计算d个数字的平均值μ和方差σ²。
- 从μ中减去,除以√(σ² + ε),得到一个零均值、单位方差的向量。
它解决了以下问题:
- 内部协变量偏移(每层的输入在模型学习过程中不断变化)。
- 稳定梯度并加速训练——尤其是在像Transformer这样的非常深的网络中,通过保持每层激活值在一个共同的规模上。
想象一下,每个token是一个学生的答卷。层归一化就像是老师重新调整并重新中心化每个人的分数,使得班级平均分为0,分布为1。这样,“下一层的考官”就不会被完全不同的分数范围所混淆。
这里有一个简单的代码示例,展示层归一化是如何工作的:
import torch
from torch.nn import LayerNorm
# 创建一个张量,形状为[batch=2, seq_len=2, dim=3]
# 每个序列有2个时间步/token,每个时间步有3个特征
x = torch.tensor([
[[1.0, 2.0, 3.0], [2.0, 4.0, 6.0]], # 第一个序列
[[0.5, 1.5, 3.5], [2.5, 5.0, 7.5]] # 第二个序列
]) # 形状 (2, 2, 3)
# 应用层归一化,作用于最后一个维度(对3个特征进行归一化)
# eps防止除以零,affine=False表示没有可学习的参数
ln = LayerNorm(normalized_shape=3, eps=1e-5, elementwise_affine=False)
# 使用层归一化归一化输入张量
y = ln(x)
print("--------------\n输入\n--------------\n", x)
print("--------------\n经过层归一化后的输出\n--------------\n", y)
# 分析第一个序列(batch=0)的第一个时间步
print("--------------\n第一个序列,第一个时间步\n--------------\n", x[0,0])
sample = x[0,0] # 获取 [1., 2., 3.]
mean = sample.mean()
print("\n均值:", mean.item()) # 应该是 2.0([1,2,3] 的平均值)
# 方差计算:与均值的平方差的平均值
# ((1-2)^2 + (2-2)^2 + (3-2)^2) / 3 = (1 + 0 + 1) / 3 = 0.6667
var = sample.var(unbiased=False)
print("方差:", np.round(var.item(), 4)) # 应该是 ~0.6667
# 层归一化的公式是:(x - mean) / sqrt(variance + eps)
# 这将数据中心化到0,并使其具有单位方差
normalized = (sample - mean) / torch.sqrt(var + 1e-5)
print("\n手动归一化的值:", normalized) # 手动归一化的值:tensor([-1.2247, 0.0000, 1.2247])
# 看看没有层归一化会发生什么
print("\n--------------\n没有层归一化\n--------------")
# 创建一个尺度差异很大的张量
x_unscaled = torch.tensor([
[[0.001, 2000.0, -500.0], [0.002, 4000.0, -1000.0]], # 第一个序列 - 大尺度变化
[[0.003, 6000.0, -1500.0], [0.004, 8000.0, -2000.0]] # 第二个序列
])
# 应用层归一化以查看差异
y_normalized = ln(x_unscaled)
print("\n尺度差异很大的输入:\n", x_unscaled)
print("\n经过层归一化后(注意值已被归一化):\n", y_normalized)
10. Transformer架构(编码器和解码器)
Transformer由编码器和解码器堆叠而成。编码器负责处理输入(例如理解文本),而解码器负责生成输出(例如文本生成)。每个堆叠包含多个注意力层、前馈网络和归一化层,它们协同工作。
编码器是一个深度阅读器,它分析输入的每一个细节。解码器是一个作家,它在参考编码器的笔记时生成输出。
可以想象一个法庭:编码器是速记员,它听取整个证词并写出一份整洁的记录(上下文向量)。解码器是法官,他在宣读判决书的每一个词时,都会参考这份记录,确保每个新词都与之前的判决词以及记录保持一致。
编码器-仅编码器 vs 仅解码器 vs 编码器-解码器
- 仅编码器(BERT):适用于分类、检索、嵌入等任务。没有交叉注意力——不生成文本。
- 仅解码器(GPT):去掉编码器堆叠;使用遮蔽自注意力,使每个token只能看到过去的内容。非常适合文本补全和聊天。
- 编码器-解码器(T5、机器翻译模型):当输入≠输出时(例如英语→法语)效果最佳。解码器的交叉注意力允许它在每一步生成时关注源句子的任何部分。
编码器层内部
- 完整的自注意力(无因果遮罩):输入中的每个token都可以关注其他所有token,构建一个丰富、双向的上下文。
- 前馈网络(扩展 → GELU → 投影回原维度):独立地细化每个token。
- 在两个子块之后添加并应用层归一化,保持稳定的激活值。
解码器层内部
- 遮蔽自注意力:仅对已生成的token进行注意力计算(因果遮罩意味着每个位置t只能向左看——三角遮罩将“未来”token的注意力权重设置为-∞,防止“剧透”)。
- 交叉注意力:解码器的查询(Q)与编码器的键(K)和值(V)进行交互,允许每个新词参考源句子的任何部分。
- 前馈网络(扩展 → GELU → 投影回原维度)。
- 每个子块后添加并应用层归一化。
11. 输出概率和Logits:预测下一个token
在处理完成后,Transformer会输出logits——词汇表中每个可能的下一个token的原始分数。Softmax函数将这些分数转换为概率,从而指导模型的预测。
词汇表
词汇表是模型所知道的所有离散token的固定集合——可以将其视为模型的“字母表”(但通常是子词片段,而不仅仅是字母)。在每一步生成时,模型都会从这个有限的列表中选择下一个token。词汇表越丰富(例如50,000 vs 100,000 tokens),模型的表达就越精确。
以下是一个小的PyTorch代码片段,展示如何查看GPT-2的词汇表:
from transformers import AutoTokenizer
import numpy as np
# 初始化GPT-2分词器,它包含词汇表和编码/解码方法
tokenizer = AutoTokenizer.from_pretrained("gpt2")
# 打印GPT-2词汇表的大小
print("GPT-2词汇表大小:", tokenizer.vocab_size) # 应该是50257
# 获取词汇表作为(token, id)元组的列表,并随机打乱
vocab_items = list(tokenizer.get_vocab().items())
np.random.shuffle(vocab_items)
# 打印词汇表中的前10个随机token-ID对
# 使用repr()显示token,以显示特殊字符/空格
print("\n词汇表中的一些token→ID映射:")
for token, idx in vocab_items[:10]:
print(f"{idx:5d} → {repr(token)}")
在每一步生成时,模型不会直接输出单词——它会为词汇表中的每个token生成一个logit,然后将这些logit通过softmax转换为概率。
- Logits:原始的、无界的分数——每个词汇表条目一个数字。
- Softmax:对这些分数进行指数化并归一化,使它们的总和为1,从而得到一个有效的概率分布,表示“下一个token是什么”。
以下是一个小的PyTorch代码片段,展示如何可视化GPT-2的输出Logits和Softmax值:
# 导入所需库
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch.nn.functional as F
# 初始化模型和分词器
tokenizer = AutoTokenizer.from_pretrained("gpt2")
model = AutoModelForCausalLM.from_pretrained("gpt2")
# 输入文本,预测下一个token
text = "Hello, how are you"
# 对输入文本进行分词
inputs = tokenizer(text, return_tensors="pt")
# 获取模型预测
with torch.no_grad(): # 推理时不需要跟踪梯度
outputs = model(**inputs)
# 获取最后一个token的logits
logits = outputs.logits[:, -1, :] # 形状:[batch_size, vocab_size] -> torch.Size([1, 50257]) 在这个例子中
# 获取softmax之前的前5个token的原始logits
top_k = 5
top_logits, top_indices = torch.topk(logits[0], top_k)
# 将logits转换为概率
probs = F.softmax(logits, dim=-1)
# 获取softmax之后的前5个概率
top_probs = probs[0][top_indices]
print(f"\n在'{text}'之后的下一个token的前{top_k}个预测:")
print("-" * 50)
print("原始Logits与Softmax概率")
print("-" * 50)
for logit, prob, idx in zip(top_logits, top_probs, top_indices):
token = tokenizer.decode([idx])
print(f"Token ID: {idx} | Token: {token:10} | Logit: {logit:.4f} | Probability: {prob:.4f}")
12. 解码策略(温度、Top-p等)
在Transformer模型处理输入序列并为下一个token生成词汇表上的概率分布后,解码策略决定了如何从这个分布中选择或采样下一个token。在推理过程中,这个过程会自回归地重复,以生成一系列token。解码策略在连贯性、多样性和计算效率之间进行了权衡。以下是一些关键的解码策略及其参数(例如top-k、top-p)。
1. 贪婪解码
在每一步中,贪婪解码会选择softmax输出中概率最高的token。这种方法简单、快速,并且能够产生一致的结果。然而,它通常会生成重复或过于保守的文本,缺乏创造力或多样性,因为它只关注最有可能的token。
2. 束搜索(Beam Search)
束搜索是一种用于生成文本的解码策略,它通过同时探索多个可能的序列(称为“束”)来找到一个高概率的单词或token序列。与贪婪解码不同,束搜索会同时考虑多个可能性,就像在迷宫中同时探索几条路径一样,只保留最有希望的路径,以达到一个好的目的地。
束搜索可以找到比贪婪解码更连贯的序列,因为它考虑了多种可能性,但它的计算量比贪婪解码要大。
例如,假设你正在生成句子“The cat…”,束宽度为2:
- 第一步:模型预测下一个token:“is”(0.4)、“sits”(0.3)、“runs”(0.2)。保留概率最高的2个束:“The cat is”和“The cat sits”。
- 第二步:对于“The cat is”,预测下一个token:“happy”(0.5)、“sleeping”(0.3)。对于“The cat sits”,预测:“on”(0.4)、“quietly”(0.2)。计算序列的整体概率,如“The cat is happy”、“The cat is sleeping”、“The cat sits on”等。
- 第三步:保留概率最高的2个序列(例如,“The cat is happy”和“The cat sits on”),然后重复。
- 最终:选择整体概率最高的序列,例如“The cat is happy”。
3. 温度采样
语言模型有许多可能的下一个单词。温度是决定模型是坚持安全选择还是冒险尝试的“创造力调节器”。低值(≈0–0.3)会产生可预测的、几乎是确定性的文本,而高值(>1)则鼓励新颖和惊喜。
温度采样的工作原理如下:
- 模型为每个token输出logits(原始分数)。
- 将这些logits除以温度值T:scaled = logits / T。
- 将缩放后的分数输入softmax以获得概率。
- T = 1:对原始概率没有变化。模型直接从其自然分布中采样,平衡连贯性和多样性。
- T < 1(例如0.7):使分布更尖锐,给高概率token(可能的单词)更多权重。这使得输出更可预测、更专注、更连贯,但缺乏创造力。
- T > 1(例如1.5):使分布更平坦,增加低概率token(不太可能的单词)的机会。这使得输出更具多样性和创造力,但可能不太连贯或离题。
4. Top-k采样
Top-k采样是语言模型保持创造力而不完全失控的“窄漏斗”。语言模型的词汇表可能有50,000到250,000个token。从整个列表中采样既耗时,又容易让低质量的长尾token混入,从而破坏流畅性和连贯性。通过限制为k个最佳选项,Top-k采样可以快速生成文本,同时过滤掉无意义的单词或离题的token。
Top-k采样的机制如下:
- 模型输出每个token的logits(z)。
- 保留logits最大的k个索引(z_topk)。
- 将所有其他logits设置为-∞,这样softmax会将它们的概率归零。
- 应用softmax:p_i = exp(z_i) / Σ exp(z_topk)。
- 从结果概率p中采样一个token。
5. Top-p(核)采样
Top-p采样只保留累积概率达到p(例如0.9)的最可能的token,并丢弃其余部分。因为截断点会根据每个分布进行调整,所以它可以在模型自信时保持保守,在模型不确定时扩大选择范围。
Top-p采样的工作原理如下:
- 按降序排列logits。
- 通过softmax(或先按温度缩放)将logits转换为概率pi。
- 累积pi,直到运行总和≥p。
- 保留这个“核”中的token,将所有其他token设置为-∞,使其概率变为0。
- 重新归一化并采样。
总结
Transformer架构和LLMs的强大之处在于它们能够并行处理文本,并通过自注意力机制捕捉单词之间的复杂关系。从文本分词到嵌入,再到位置编码、自注意力、多头注意力、交叉注意力、遮蔽注意力,以及最终的输出概率计算和解码策略,每一步都为模型提供了强大的语言理解和生成能力。这些技术的结合,使得今天的AI能够以惊人的精度和创造力生成人类语言。
如何学习大模型 AI ?
由于新岗位的生产效率,要优于被取代岗位的生产效率,所以实际上整个社会的生产效率是提升的。
但是具体到个人,只能说是:
“最先掌握AI的人,将会比较晚掌握AI的人有竞争优势”。
这句话,放在计算机、互联网、移动互联网的开局时期,都是一样的道理。
我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。
我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。
这份完整版的大模型 AI 学习资料已经上传优快云,朋友们如果需要可以微信扫描下方优快云官方认证二维码免费领取【保证100%免费
】
第一阶段(10天):初阶应用
该阶段让大家对大模型 AI有一个最前沿的认识,对大模型 AI 的理解超过 95% 的人,可以在相关讨论时发表高级、不跟风、又接地气的见解,别人只会和 AI 聊天,而你能调教 AI,并能用代码将大模型和业务衔接。
- 大模型 AI 能干什么?
- 大模型是怎样获得「智能」的?
- 用好 AI 的核心心法
- 大模型应用业务架构
- 大模型应用技术架构
- 代码示例:向 GPT-3.5 灌入新知识
- 提示工程的意义和核心思想
- Prompt 典型构成
- 指令调优方法论
- 思维链和思维树
- Prompt 攻击和防范
- …
第二阶段(30天):高阶应用
该阶段我们正式进入大模型 AI 进阶实战学习,学会构造私有知识库,扩展 AI 的能力。快速开发一个完整的基于 agent 对话机器人。掌握功能最强的大模型开发框架,抓住最新的技术进展,适合 Python 和 JavaScript 程序员。
- 为什么要做 RAG
- 搭建一个简单的 ChatPDF
- 检索的基础概念
- 什么是向量表示(Embeddings)
- 向量数据库与向量检索
- 基于向量检索的 RAG
- 搭建 RAG 系统的扩展知识
- 混合检索与 RAG-Fusion 简介
- 向量模型本地部署
- …
第三阶段(30天):模型训练
恭喜你,如果学到这里,你基本可以找到一份大模型 AI相关的工作,自己也能训练 GPT 了!通过微调,训练自己的垂直大模型,能独立训练开源多模态大模型,掌握更多技术方案。
到此为止,大概2个月的时间。你已经成为了一名“AI小子”。那么你还想往下探索吗?
- 为什么要做 RAG
- 什么是模型
- 什么是模型训练
- 求解器 & 损失函数简介
- 小实验2:手写一个简单的神经网络并训练它
- 什么是训练/预训练/微调/轻量化微调
- Transformer结构简介
- 轻量化微调
- 实验数据集的构建
- …
第四阶段(20天):商业闭环
对全球大模型从性能、吞吐量、成本等方面有一定的认知,可以在云端和本地等多种环境下部署大模型,找到适合自己的项目/创业方向,做一名被 AI 武装的产品经理。
- 硬件选型
- 带你了解全球大模型
- 使用国产大模型服务
- 搭建 OpenAI 代理
- 热身:基于阿里云 PAI 部署 Stable Diffusion
- 在本地计算机运行大模型
- 大模型的私有化部署
- 基于 vLLM 部署大模型
- 案例:如何优雅地在阿里云私有部署开源大模型
- 部署一套开源 LLM 项目
- 内容安全
- 互联网信息服务算法备案
- …
学习是一个过程,只要学习就会有挑战。天道酬勤,你越努力,就会成为越优秀的自己。
如果你能在15天内完成所有的任务,那你堪称天才。然而,如果你能完成 60-70% 的内容,你就已经开始具备成为一名大模型 AI 的正确特征了。