上一课我们搭好了 Transformer 的骨架。但一个"骨架"本身不会说话——它只会把输入"重新表示"一下。那 ChatGPT 到底是怎么做到"你问一句、它像人一样回一句"的?

答案藏在三个关键点里:

  1. 自回归生成:一个字一个字地"续写"。
  2. 预训练:在几乎整个互联网的文本上,学"下一个字是什么"。
  3. 缩放(scaling):模型越大、数据越多、算力越强,能力越强。

这一课,我们把 GPT 的"引擎"拆开看清楚。


一、GPT 到底在做什么?——预测下一个字

GPT 的核心任务简单到不可思议:给定前面所有的字,预测下一个字是什么。

比如输入 "The cat sat on the",模型输出一个概率分布,告诉你下一个字最可能是 "mat"(也可能 "floor"、"roof"……)。

输入:The cat sat on the ___
输出:下一个字的概率分布
      mat    0.52
      floor  0.19
      roof   0.12
      ...

就这么一件事。 但这件事一旦做得足够好,就会涌现出惊人的能力:会翻译、会写作、会推理、会写代码。因为"续写"逼着模型去理解语言、世界知识和逻辑。


二、先过第一关:把文字变成数字(Tokenization)

模型只能算数字,所以第一步要把文字切成一串token(词元),每个 token 映射成一个整数。

text = "the cat sat on the mat"

# 1. 词级切分
words = text.split()
print("词级 tokens:", words)
# ['the', 'cat', 'sat', 'on', 'the', 'mat']

# 2. 字符级切分(更简单,演示用)
chars = sorted(set(text))
stoi = {c: i for i, c in enumerate(chars)}   # 字符 → 数字
itos = {i: c for i, c in enumerate(chars)}   # 数字 → 字符

ids = [stoi[c] for c in text]
print("字符 ids:", ids)
print("解码回来:", ''.join(itos[i] for i in ids))
# 还原成原文 "the cat sat on the mat"

真实的大模型用的是子词(subword)切分(如 BPE),比"词"和"字符"折中:既能处理生僻词,又不会切得太碎。但原理一样——把文字变成一串整数,喂给神经网络。


三、自回归生成:一个字一个字往外"蹦"

拿到"预测下一个字"的模型后,怎么生成一整句话?用自回归(autoregressive)

1. 给定开头 "从前"
2. 预测下一个字 → "有"
3. 把 "从前有" 当新输入,预测下一个 → "座"
4. 把 "从前有座" 当新输入,预测下一个 → "山"
5. ……一直生成到结束

每生成一个字,就把它拼回输入里,再预测下一个字。 这就是 ChatGPT 打字"一个字一个字冒出来"的原因。

温度(temperature):控制"创造力"

"预测下一个字"其实给的是概率分布。怎么从分布里挑一个字?这里有个关键参数——温度

import numpy as np

def softmax(x):
    x = x - np.max(x)
    e = np.exp(x)
    return e / e.sum()

def next_probs(logits, temperature=1.0):
    # temperature 越高,分布越"平";越低,分布越"尖"
    return softmax(logits / temperature)

logits = np.array([2.0, 1.0, 0.5, 0.2, 0.1])  # 5 个候选词的原始分数

for T in [0.5, 1.0, 2.0]:
    print(f"温度 T={T}: {next_probs(logits, T).round(3)}")

跑出来你会看到:

温度效果用在
低(如 0.2)分布尖锐,几乎总选最可能的词 → 保守、确定写代码、事实问答
高(如 2.0)分布平滑,冷门词也有机会 → 多样、有创意写诗、头脑风暴

温度就是"创造力旋钮":调低就稳妥,调高就放飞。

采样策略:温度之外,还有 top-k 和 top-p

温度是"缩放"概率分布,但实际部署模型时,光有温度不够。因为即使温度很低,模型仍然有机会抽到一个极冷门的词,导致胡言乱语。于是有了两个更"硬"的截断手段:

① Top-k 采样:每步只从"概率最高的 k 个候选词"里挑,其余直接砍掉。比如 top-k=50,就只看前 50 个最可能的词。

② Top-p(核采样 / nucleus sampling):每步从"概率累加到 p 为止"的那批词里挑。比如 top-p=0.9,就把最可能的词一个个累加,直到加起来 ≥90%,剩下的全砍掉。候选数量是动态的——分布尖时就挑几个,分布平时就挑很多。

import numpy as np

def sample(logits, temperature=1.0, top_k=None, top_p=None):
    probs = np.exp(logits/temperature)
    probs = probs / probs.sum()

    if top_k:                     # 只留前 k 个
        idx = np.argsort(probs)[-top_k:]
        probs = np.array([p if i in idx else 0 for i, p in enumerate(probs)])
        probs = probs / probs.sum()

    if top_p:                     # 累加到 p 为止
        order = np.argsort(probs)[::-1]
        cum = np.cumsum(probs[order])
        keep = order[cum <= top_p]
        mask = np.zeros_like(probs, dtype=bool); mask[keep] = True
        probs = np.where(mask, probs, 0); probs = probs / probs.sum()

    return np.random.choice(len(probs), p=probs)

体会:真实的大模型生成 = 温度缩放 + top-k/top-p 截断 + 随机采样,三者配合。这是"让 AI 说话既有创意、又不发疯"的工程关键。


四、预训练:在互联网上"读"一遍世界

GPT 是怎么学会"预测下一个字"的?预训练(pre-training)

收集海量文本(网页、书籍、代码、对话……),让模型反复玩"猜下一个字"的游戏。猜错了就调参数(梯度下降,第 22 课学的),猜对越多,模型对语言、事实、逻辑的把握就越强。

这个阶段不需要人工标注——因为"下一个字"就在文本里,天然是标准答案。这叫自监督学习:数据自己给自己出题。

预训练完的模型叫基座模型(base model),它已经"知道"很多,但还不太会"聊天"。


五、微调与对齐:从"会续写"到"会聊天"

基座模型只会续写,你问它"1+1=?",它可能续写成"1+1=2 是小学数学……"(因为它见过无数类似文本)。但它不一定"听话"。

于是有了两步:

阶段做什么目的
监督微调(SFT)用"问题-标准答案"数据对训练学会"问答"格式
RLHF 对齐让人给多个回答排序,训练奖励模型,再优化让回答有用、诚实、无害

这就是为什么 ChatGPT 用起来"顺"——它被额外训练成了"会聊天"的样子。


六、LoRA 与 PEFT:花小钱微调大模型

上一节说微调(SFT)能教模型"会聊天"。但问题来了:GPT 这类模型有上千亿参数,全量微调意味着要更新所有参数、存好几份完整模型——显存和算力都烧不起,普通人根本玩不动。

于是有了 PEFT(Parameter-Efficient Fine-Tuning,参数高效微调)只动一小部分参数,也能达到接近全量微调的效果。

LoRA:冻结大模型,只训练"补丁"

PEFT 里最流行的是 LoRA(Low-Rank Adaptation,低秩适配)。它的思路特别妙:

大模型本身的权重全部冻结(不动)。在旁边挂两个很小的矩阵 A、B,让它们学一个"增量"。推理时把增量加回原权重:W + A×B

因为 A、B 很小(秩 r 很低,常取 8、16),需要训练的参数可能只有原模型的 0.1%,显存和算力大幅下降。

原权重:  W  (冻结,不更新)
增量:    ΔW = A × B   (A、B 很小,只训练这两个)
新权重:  W + ΔW = W + A × B
# LoRA 核心思路(概念示意)
# 原线性层: y = W @ x          # W 形状 (d, d),冻结
# 挂两个低秩矩阵: A (d, r)、B (r, d),r 远小于 d
delta = B @ A @ x              # 低秩增量
y = W @ x + delta              # 冻结的 W 加上增量

为什么 LoRA 有效

直觉:大模型虽然参数多,但真正需要"适应新任务"的信息量没那么大——用一个低维的"补丁"就能装下。冻结主模型、只调补丁,既省钱又不破坏模型已有的能力。

PEFT 家族(知道名字即可)

方法思路
LoRA冻结权重,加低秩矩阵 A×B
Adapter在层之间插小模块,只训这些小模块
Prefix Tuning只训练一段"前缀向量"

它们共同的信念:微调不必动全量参数。

一句话记住

  • 全量微调:贵,普通玩家玩不起。
  • PEFT:只动一小撮参数,省钱省显存。
  • LoRA:最流行的 PEFT,冻结主模型 + 训练低秩补丁 A×B,参数量可降到 0.1%。

这也是为什么现在开源社区能"人手一个大模型微调"——LoRA 让微调门槛大幅降低。


七、缩放定律:为什么"大"就是强

GPT 系列最重要的一个发现,叫缩放定律(scaling law)

模型参数、训练数据、计算量,这三样越大,模型能力就越强,而且这种提升是可预测、平滑的

GPT-1(1.17 亿参数)→ GPT-2(15 亿)→ GPT-3(1750 亿)→ GPT-4(未公开,传闻上万亿)。能力随规模一路上涨,甚至涌现出小模型没有的能力(in-context learning、推理)。

记住:Transformer 这个架构本身没大变,是"规模"带来了质变。


八、上下文窗口与提示工程

大模型没有"记忆",它每次只看你喂给它的上下文(context)。上下文能装多少 token,叫上下文窗口

  • 早期 GPT-3 是 2048 token,现在动辄几十万 token。
  • 所以和模型交流靠提示(prompt):把你想要的信息、格式、例子都写进提示里。

提示工程就是"把话说清楚",让模型在上下文里得到足够信息、按你要的方式回答。这不神秘——本质是通过上下文引导自回归生成。


九、复杂度与要点

概念说明
Tokenization文字 → 整数序列,子词切分
自回归每步预测下一个 token,拼回输入再预测
温度控制采样多样性,越低越确定
预训练海量文本上"猜下一个字",自监督
缩放定律参数/数据/算力越大,能力越强

十、动手时间 🎯

实验 1:手写一个"字符级"小模型

跑一个极简的自回归演示——用字符级 n-gram 猜下一个字符:

text = "the cat sat on the mat the cat sat on the floor"

# 统计每个字符后面最常跟的字符
from collections import defaultdict, Counter
next_chars = defaultdict(Counter)
for i in range(len(text) - 1):
    next_chars[text[i]][text[i+1]] += 1

# 从 't' 开始,生成 20 个字符
current = 't'
result = current
for _ in range(20):
    if not next_chars[current]:
        break
    nxt = next_chars[current].most_common(1)[0][0]  # 贪心选最常见
    result += nxt
    current = nxt
print("生成:", result)

体会:这已经是"自回归"的雏形了——每步根据上文选下一个字符。真实 GPT 用的是神经网络 + 概率采样,但套路一模一样。

实验 2:感受温度

跑第三节代码,改 temperature 从 0.1 到 5.0,观察概率分布怎么从"尖"变"平"。这就是 ChatGPT 的"creative"旋钮。

实验 3(挑战):调用真实的大模型 API

如果你有 OpenAI 兼容的 API key,试着调用一次补全接口,把 temperature 设成 0 和 1 各问一次,对比回答的确定性和多样性。


十一、小结

  1. GPT = 预测下一个字:给定上文,输出下一个 token 的概率分布,然后自回归地一个字一个字生成。
  2. 预训练 + 微调:先在互联网上"猜字"学知识(自监督),再用问答数据和人类反馈学"会聊天"。
  3. 缩放定律:Transformer 架构没大变,是"规模"带来了智能的涌现。

标签: none

添加新评论