高中背了三年的数列,原来是 GPT 一个字一个字蹦出来的秘密
等差、等比、递推公式、数列求和……高中刷了三年题,考完只记得几个求和公式。 因为老师把数列讲成了"求第 n 项、求前 n 项和"的计算题,没告诉你一个真相:
数列的灵魂根本不是求和公式,是"递推"——用前面的项,算出后面的项。 而今天 GPT 一个字一个字地写文章、RNN 记住上文、强化学习算回报、Adam 更新参数, 全都是在"解数列"。你当年做烂的递推公式
aₙ₊₁ = f(aₙ),就是大模型的生成方式本身。这篇文章不背一个公式、不证一个定理,全部用能跑起来的 PyTorch 代码, 把数列最核心的"递推"本质,一路接到大模型。并且顺手回答四个灵魂拷问:
- 数列刷了三年,除了求和公式,到底还剩下什么真正有用的东西?
- GPT 看起来是"一口气"写完一段话的,它究竟怎么一个字一个字定下来的?
- 为什么 RNN 记不住长句子的开头?这和数列有什么关系?
- 强化学习里的"折扣回报"、Adam 里的"移动平均",凭什么都是等比数列?
0. 一句话主线
如果只能留一句话,那就是这句:
数列 = 一个初始项 + 一条递推规则。给定开头,用"前面的项算后面的项",一项一项往下推。
高中你被"求通项、求和"绑架了,其实数列的核心动作只有一个:递推。 而 GPT 生成文字,就是把这个动作重复很多次——每一个新字,都由"前面所有字"算出来,再接回去,继续算下一个。 它写的不是文章,是一个数列。
# 数列的本质:一个初始项 + 一条"用前一项算后一项"的规则
a = [1.0] # 初始项 a₁
for _ in range(5):
a.append(a[-1] + 3) # 递推规则:aₙ₊₁ = aₙ + 3(等差,公差 3)
print("等差数列:", a) # [1, 4, 7, 10, 13, 16]
g = [1.0]
for _ in range(5):
g.append(g[-1] * 2) # 递推规则:gₙ₊₁ = gₙ × 2(等比,公比 2)
print("等比数列:", g) # [1, 2, 4, 8, 16, 32]
看清楚这个循环的形状:拿最后一项,套一个规则 f,得到新一项,接回去。
记住这个 append 循环——下面 GPT 的生成、RNN 的记忆,长得一模一样。
1. 等差、等比,只是"最简单的递推规则"
高中把等差(每次 +d)和等比(每次 ×r)当两个孤立的公式背。
但换到"递推"视角,它俩只是同一件事的两种规则:都是 aₙ₊₁ = f(aₙ),只不过 f 一个是加、一个是乘。
def unfold(a0, rule, n): # 给一个初始项和一条规则,展开成数列
seq = [a0]
for _ in range(n):
seq.append(rule(seq[-1])) # 反复套用递推规则
return seq
print(unfold(1.0, lambda x: x + 3, 5)) # 等差:+3
print(unfold(1.0, lambda x: x * 2, 5)) # 等比:×2
print(unfold(2.0, lambda x: x**2, 4)) # 换条规则:平方 —— 立刻得到一个全新数列
unfold 这个函数才是数列的真身:"初始项 + 规则"就能生成整条序列。
规则可以是加、是乘、是平方、是任意函数——规则越复杂,能生成的数列就越丰富。
把这条规则换成一个几亿参数的神经网络,你就得到了 GPT。
2. 递推 = 自回归生成:GPT 就是在一项一项地"解数列"
这是全篇的枢纽。GPT 生成文本的方式,教科书叫"自回归(autoregressive)", 翻译成高中话就是一句:它在解一个递推数列。
- 初始项:你给的提示词(prompt)。
- 递推规则
f:那个巨大的 Transformer——输入"前面所有的字",输出"下一个字"。 - 展开:算出下一个字,接回序列末尾,再喂回去算下下个字……直到写完。
和第 0 节那个 append 循环,是同一个形状:
import torch
import torch.nn as nn
torch.manual_seed(0)
vocab, dim = 20, 16
embed = nn.Embedding(vocab, dim) # 把 token 变成向量
head = nn.Linear(dim, vocab) # 由"状态"预测下一个 token
seq = [1] # 初始项:第一个 token(相当于 prompt)
for _ in range(8): # 一项一项地"解这个数列"
ctx = embed(torch.tensor(seq)).mean(0) # 用前面所有项,汇总出一个"状态"
nxt = head(ctx).argmax().item() # 递推规则 f:算出下一项
seq.append(nxt) # 接回序列,继续递推
print("生成的 token 数列:", seq)
# (模型没训练,token 本身没意义——重点是这个循环的"形状"就是数列递推)
看这个循环:seq 就是数列,embed+head 就是递推规则 f,append 就是"推出下一项"。
真实的 GPT 只是把 f 换成了一个几千亿参数、带注意力的庞然大物,
但骨架分毫不差——它写的每一段话,都是一条 aₙ₊₁ = f(a₁, a₂, …, aₙ) 的递推数列。
所以"GPT 一口气写完一段话"是错觉。它和你高中一项一项算数列一样, 一次只定一个字,定完接回去,再定下一个。 你在聊天框里看到的"流式输出", 就是这条数列被一项一项推出来的过程,肉眼可见。
3. RNN 的记忆,就是一条"状态数列"
GPT 每步要回看全部历史;它的前辈 RNN 更纯粹——它把历史压进一个"状态 h"里,
每读一个字,就用上一个状态算出下一个状态:hₜ = f(hₜ₋₁, xₜ)。
这不多不少,就是一条以状态为项的递推数列:
import torch
torch.manual_seed(0)
W = torch.randn(4, 4) * 0.5 # 状态 -> 状态 的变换
U = torch.randn(4, 3) * 0.5 # 新输入 -> 状态 的变换
h = torch.zeros(4) # 初始项 h₀(空白记忆)
xs = torch.randn(5, 3) # 5 个时间步的输入(一句话的 5 个字)
for t, x in enumerate(xs):
h = torch.tanh(W @ h + U @ x) # 递推公式:hₜ = f(hₜ₋₁, xₜ)
print(f"h_{t+1} =", h.round(decimals=2))
# 每个状态都由上一个状态推出来 —— 一条不折不扣的递推数列
RNN 的"记忆"没有任何魔法:它就是一条状态数列 h₁, h₂, h₃, …,每一项由前一项递推而来。
第 5 个状态里,理论上浓缩了前面 5 个字的全部信息。理论上——下一节我们就会看到它为什么会"忘"。
4. 等比数列的 rⁿ:为什么深层递推会"梯度消失/爆炸"
高中等比数列有个刻进骨子里的结论:公比 r 的 n 次方 rⁿ,要么炸到无穷,要么缩到 0。
r > 1 时 rⁿ → ∞,r < 1 时 rⁿ → 0,只有 r = 1 恰好不动。
这个高中结论,正是 RNN 记不住长句子、深层网络难训练的数学死因。
原因很简单:递推套了 n 层,训练时梯度要穿过这 n 层往回传,每穿一层就乘一个近似固定的因子 r,
穿 n 层就乘了 rⁿ——一条等比数列。它的命运,高中早就替你算好了:
for r in [0.7, 1.0, 1.3]:
grads = [r ** n for n in range(1, 11)] # 梯度沿时间回传:一条等比数列 rⁿ
print(f"r={r}: 第1步 {grads[0]:.2f} -> 第10步 {grads[-1]:.4f}")
# r=0.7 -> 0.0282 梯度消失:早期的字对 loss 几乎没影响,模型学不到长依赖
# r=1.0 -> 1.0000 恰好守恒(可惜现实里几乎不可能正好等于 1)
# r=1.3 -> 13.79 梯度爆炸:数值失控,训练崩掉
RNN 记不住长句开头,就是因为 r < 1 时 rⁿ 指数级衰减——传了十几步,开头那个字的梯度早就趋近 0 了。
理解了这条等比数列,你就理解了深度学习里一大半的设计动机:
LSTM 的"门"、残差连接(ResNet)里那条
x + f(x),本质都是在想办法把这个公比r摁在 1 附近, 让rⁿ别爆也别消。你上一篇讲 RNN 被淘汰的文章,数学根子就在这条等比数列上。
🤔 疑惑点一:强化学习里的"折扣回报",凭什么是无穷等比数列?
因为它就是高中那个"无穷等比数列求和"——只不过每一项前面乘的不是常数,而是奖励。折扣因子 γ 就是公比,γ < 1 保证它收敛,正是高中那句"公比绝对值小于 1"。
你在做 GRPO / 强化学习时那个"折扣回报"Gₜ = Σ γᵏ · rₜ₊ₖ,看着吓人,其实是老朋友。
高中学过:|r| < 1 时,1 + r + r² + r³ + … = 1/(1−r)。RL 把每一项配上奖励,公比换成 γ:
import torch
gamma = 0.9
rewards = torch.ones(100) # 假设每一步奖励都是 1
k = torch.arange(100).float()
G = (gamma ** k * rewards).sum() # 折扣回报 = Σ γᵏ·rₖ,一条加权的等比数列
print("折扣回报 G =", round(G.item(), 4)) # ≈ 10.0
print("高中公式 1/(1-γ) =", 1 / (1 - gamma)) # = 10.0 —— 完全一致
当每步奖励都是 1,折扣回报正好收敛到高中的 1/(1−γ) = 10。一字不差。
为什么 RL 里
γ必须小于 1?不是玄学,是收敛条件:γ ≥ 1时这条等比数列发散到无穷,回报没法算。 高中那句"公比绝对值小于 1 才能求无穷和",就是强化学习能定义"回报"的前提。 你 MathGPT 里的每一次 return 计算,背后都是这条无穷等比数列。
🤔 疑惑点二:Adam 里的"移动平均",为什么权重是等比数列?
因为"指数移动平均"展开来,就是一条等比数列在给历史梯度加权——越老的梯度,权重按 βᵏ 指数衰减。"指数"二字,指的正是等比。
动量、Adam 里都有一步 m ← β·m + (1−β)·g(用旧的平均和新梯度,滚出新的平均)。
这本身是个递推(回到主线!)。而把它一层层展开,就露出了等比数列的真身:
import torch
beta = 0.9
grads = torch.tensor([1.0, 2.0, 3.0, 4.0, 5.0]) # 从旧到新的 5 步梯度
m = 0.0
for g in grads:
m = beta * m + (1 - beta) * g # 指数移动平均:一个递推公式
# 把递推展开,等价于给历史梯度按等比数列 βᵏ 加权
k = torch.arange(len(grads) - 1, -1, -1).float()
weights = (1 - beta) * beta ** k # 权重 = (1-β)·βᵏ,一条等比数列
print("递推算出的 EMA:", round(m, 4)) # 1.3144
print("等比加权求和 :", round((weights * grads).sum().item(), 4)) # 1.3144 —— 相等
print("各步权重 :", weights.round(decimals=4)) # 越老的梯度权重越小
两种算法结果完全相同:"指数移动平均"= 用等比数列 βᵏ 给历史梯度加权求和。
β = 0.9 意味着每往前一步,权重就乘 0.9——十步前的梯度只剩 0.9¹⁰ ≈ 0.35 的分量。
"越久远的梯度越不重要",这个直觉被一条等比数列精确地量化了。
🎬 动手:把 GPT 的生成,画成一条正在被"解"出来的数列
把第 2 节的自回归循环展开,一步一步打印出来——你会亲眼看到"生成"就是"解数列":
import torch
import torch.nn as nn
torch.manual_seed(0)
vocab, dim = 20, 16
embed = nn.Embedding(vocab, dim)
head = nn.Linear(dim, vocab)
seq = [1] # 初始项(prompt)
print("初始项 a₁ =", seq[0])
for step in range(8):
ctx = embed(torch.tensor(seq)).mean(0) # 汇总前面所有项 -> 状态
logits = head(ctx)
nxt = logits.argmax().item() # 递推规则 f -> 下一项
seq.append(nxt)
print(f"第 {step+1} 步:由前 {len(seq)-1} 项推出 a_{len(seq)} = {nxt},"
f"当前数列 = {seq}")
# 每一行都是"用前面的项,推出后面的项"—— 一条正在被解开的递推数列
仓库里的动画脚本把这个过程做成了逐帧动图:
python sequence_recurrence_visualization.py
画面左边,一条数列一项一项地被"推"出来,每个新项都由前面的项经过规则
f算出,用箭头连回去; 右边同时演示第 4 节那条等比数列rⁿ:拖动公比r,看它在r<1时如何塌向 0(梯度消失)、 在r>1时如何冲向天花板(梯度爆炸)、只有r=1时稳如水平线。 一张图看懂:生成是递推,遗忘是等比。
缝合:把所有画面接起来
回到开头,现在每个概念都有了画面和代码出处:
| 数列概念 | 高中怎么讲 | 这篇文章怎么看(画面) | 在大模型里是什么 |
|---|---|---|---|
递推公式 aₙ₊₁=f(aₙ) |
求通项的手段 | 用前一项算后一项的循环(第 0、1 节) | GPT 自回归生成的骨架(第 2 节) |
| 状态递推 | — | 用上一状态算下一状态(第 3 节) | RNN 的"记忆"hₜ=f(hₜ₋₁,xₜ) |
等比数列 rⁿ |
求第 n 项、求和 | rⁿ 要么炸要么消(第 4 节) | 梯度消失/爆炸、残差连接的动机 |
无穷等比求和 1/(1−r) |
|r|<1 才收敛 |
折扣回报的收敛(疑惑点一) | RL 的 Gₜ=Σγᵏrₖ,γ 就是公比 |
| 递推 + 等比加权 | — | 越老权重越小(疑惑点二) | Adam/动量的指数移动平均 |
三句话总结这篇文章:
数列的灵魂是"递推"——初始项 + 规则,一项一项往下推(第 0、1 节); GPT 生成文字、RNN 记忆,都是在解这条递推数列,一次只定一个字/一个状态(第 2、3 节); 而等比数列
rⁿ决定了递推的命运——它是梯度消失/爆炸的根源,也是 RL 折扣回报、Adam 移动平均的数学身份(第 4 节、疑惑点一二)。
当年数列刷得那么苦,不是因为它难,是因为没人告诉你: 你一项一项算出来的那条数列,就是今天 GPT 一个字一个字蹦出来的方式。 现在把上面每段代码跑一遍,比高中刷十套数列卷子都值。
备注(选题/标题): 这篇走的是"高中知识其实是 AI 基石"的钩子(和《求根公式》《sin/cos → RoPE》同一条正在当红的脉络,刻意避开已经跑疲的"大学4年…PyTorch"模板)。 备选标题: 《高中做烂了的递推数列,其实就是 GPT 写字的方式》 / 《数列不是用来求和的:它是 GPT 一个字一个字生成的秘密》。