高中最容易被忽略的数学归纳法,其实就是大模型"逐字生成"的骨架

2026-07-05 · Steve Chan

数学归纳法、奠基、归纳假设、归纳步、"假设 n=k 成立,证明 n=k+1"……高中学的时候觉得是个"证明套路", 考完只记得"多米诺骨牌"这个比喻。 因为老师只教你"用它证求和公式、证不等式",没告诉你两件事:

第一,数学归纳法的灵魂不是"一种证明技巧",是一台构造机器:只要定义好"起点"和"由前一步推出下一步的规则",就能覆盖无限多的情形。 第二,今天每一个大模型(LLaMA、Qwen、GPT、DeepSeek)逐字生成一句话的方式, 结构上就是数学归纳法:把"第一个词"当奠基、把"由前文推出下一个词"当归纳步,一步步递推出整句话。

这篇文章不背一个公式、不做一道归纳证明,全部用能跑起来的 PyTorch 代码, 把数学归纳法最核心的"奠基 + 归纳步"结构,一路接到大模型的自回归生成。 并且顺手回答四个灵魂拷问:

  • 数学归纳法明明是"证明数列公式"用的,怎么会和大模型生成文字扯上关系?
  • Transformer 号称"看全部前文",这算普通归纳还是"强归纳"?两者差在哪?
  • 为什么大模型一步说错,后面常常越跑越歪、彻底崩掉?
  • 每生成一个词都要重看全部前文,不是慢死了吗?它是怎么偷懒的?

0. 一句话主线

如果只能留一句话,那就是这句:

数学归纳法 = 奠基(起点)+ 归纳步(由前一步推出下一步)。自回归生成用的正是这套结构。

给定"第一个词"(奠基),再反复用"由前文推出下一个词"的规则(归纳步),就能递推出任意长的句子—— 这跟你用归纳法"由第 k 项推第 k+1 项、从而覆盖所有 n"是同一件事。

# 数学归纳法 = 奠基 + 归纳步。用它"构造"一个数列:
def build_sequence(n):
    seq = [1]                        # 奠基:第一项 = 1
    for k in range(1, n):            # 归纳步:由前一项推出下一项
        seq.append(seq[-1] + k + 1)  # 规则:a(k+1) = a(k) + (k+1)
    return seq

print(build_sequence(6))             # [1, 3, 6, 10, 15, 21] —— 三角形数
# 只定义了"第一项"和"由前推后的规则",就生成了任意长的数列 —— 这就是归纳法的构造力

记住这个画面:奠基放下第一块骨牌,归纳步保证"每一块都能推倒下一块",于是整排骨牌必然全倒。 下面所有东西都挂在"奠基 + 归纳步"这一个结构上。


1. 数学归纳法的真身:一台"由前推后"的构造机器

高中把归纳法包装成"证明题专用工具",让你以为它只能用来验证已知公式。 但它的本质是构造:奠基定义起点,归纳步定义"如何从已有的推出下一个",两者一凑,就覆盖了无限。 自回归生成,就是把"数列的项"换成"句子的词",其余结构原封不动:

import torch
import torch.nn.functional as F

torch.manual_seed(0)
V = 6
W = torch.randn(V, V)                # 玩具"下一个词"规则(由当前词给出下一个词的打分)

def generate(start, n):
    seq = [start]                    # 奠基:第一个词
    for _ in range(n):               # 归纳步:由前文推出下一个词
        logits = W[seq[-1]]
        seq.append(F.softmax(logits, dim=-1).argmax().item())
    return seq

print("自回归生成:", generate(0, 8))
# 结构和上面的 build_sequence 一模一样:定义起点 + 由前推后的规则 = 生成任意长序列

看这两段代码的骨架——奠基一个起点 + 循环里由前一步推出下一步——完全一致。 大模型所谓的"自回归(autoregressive)",翻译成高中语言就是:"用数学归纳法的方式,一项一项地把句子递推出来。"


🤔 疑惑点一:Transformer"看全部前文",是普通归纳还是"强归纳"?

是强归纳。普通归纳里,第 k+1 步只用到第 k 步的结论(P(k) ⟹ P(k+1));强归纳里,第 k+1 步可以用到前面所有步的结论(P(1)…P(k) 一起 ⟹ P(k+1))。只看"上一个词"的模型(bigram、马尔可夫链)是普通归纳;而 Transformer 的注意力,预测下一个词时会回看前面的每一个词——这正是强归纳。这个区别,就是老式模型和 Transformer 在"记忆力"上的根本差距。

用代码把"普通归纳(只看上一个)"和"强归纳(看全部前文)"摆在一起:

import torch
import torch.nn.functional as F

torch.manual_seed(0)
seq = torch.randn(5, 4)                       # 已生成的 5 个词向量

# 普通归纳:预测下一个只看"上一个词"(bigram / 马尔可夫链)
last_only = seq[-1]

# 强归纳:预测下一个看"前面全部"(注意力对整段历史加权)
q = seq[-1]
attn = F.softmax(seq @ q / 4 ** 0.5, dim=-1)  # 对全部历史算注意力权重
all_context = attn @ seq                      # 融合前文所有词

print("只看上一个词(普通归纳):", last_only.round(decimals=2).tolist())
print("看全部前文(强归纳)    :", all_context.round(decimals=2).tolist())
# Transformer 走的是强归纳:第 n+1 步依赖 P(1)…P(n) 全部,而不只是 P(n)

普通归纳只攥着"上一个词"这一条信息,一旦需要"呼应很久之前说过的话"就无能为力; 强归纳(注意力)每一步都回看全部前文,所以 Transformer 能记住段首的主语、几十个词前立下的设定。 "看多远"这件事,本质就是"用普通归纳还是强归纳"。


2. 归纳链一环断,后面全歪:这就是"曝光偏差"

数学归纳法有个隐含的脆弱点:整条链依赖"每一步都对"。只要中间断了一环,"由前推后"的保证就失效,后面全部崩塌。 大模型逐词生成时也一样——一步说错,这个错词又成了下一步的"前文",误差会顺着归纳链滚雪球。用一个敏感数列演示:

# 逻辑斯蒂映射:一个对初值极度敏感的"归纳步",用来放大"一步之差"
def logistic(x0, n, r=3.9):
    xs = [x0]
    for _ in range(n):
        xs.append(r * xs[-1] * (1 - xs[-1]))   # 归纳步:由前一项推下一项
    return xs

clean  = logistic(0.400, 16)
broken = logistic(0.402, 16)                    # 起点只差 0.002

for i in [0, 4, 8, 12, 16]:
    print(f"第{i:2d}步  正常={clean[i]:.3f}  出错后={broken[i]:.3f}  偏差={abs(clean[i]-broken[i]):.3f}")
# 起点仅差 0.002,十几步后偏差从 0.002 滚到 0.65 —— 一步错、归纳链断、后面全歪

起点只差 0.002,到第 16 步偏差已从 0.002 滚到 0.65,两条序列面目全非。这就是大模型的"曝光偏差(exposure bias)": 训练时它每一步看到的都是正确前文,但真实生成时,一旦某步选错词,错误就成了后续归纳步的输入, 沿着归纳链层层放大。 归纳法教给你的"每一步都必须成立",在这里变成了大模型最头疼的失败模式之一。


🤔 疑惑点二:每步都要重看全部前文,不是慢死了吗?它怎么偷懒的?

靠 KV-cache。强归纳每生成一个词都要回看前面所有词,如果每步都把全部前文从头重算一遍,长文本会慢得离谱。但归纳法有个天然的便利:前面那些步骤的结论(归纳假设)已经证过了,不用重证。KV-cache 就是把前文算过的中间结果(每个词的 Key、Value)缓存下来,新词来了只需补自己这一行,旧的直接复用。

用代码看"复用旧块"的等价性——这正是 KV-cache 省掉的重复计算:

import torch

torch.manual_seed(0)
seq = torch.randn(4, 8)              # 已生成的 4 个词
new = torch.randn(1, 8)             # 新来的第 5 个词

full = torch.cat([seq, new], dim=0)
recompute = full @ full.T           # 不缓存:把 5 个词的全部两两关系重算一遍

cached_block = seq @ seq.T          # 缓存:前 4 个词的关系上一轮就算过,直接复用
print("重算的旧块 == 缓存的旧块 ?",
      torch.allclose(recompute[:4, :4], cached_block))   # True
# 前 n 项的"归纳假设"不必重证,缓存即可 —— 新词只需补自己这一行,这就是 KV-cache

True 说明:新词到来时,前文之间的关系一个字节都没变,重算纯属浪费。 KV-cache 把"已经证过的归纳假设"存起来复用,让强归纳既能"看全部前文"、又不至于慢到不可用。 归纳法里"前面步骤不用重证"的常识,在工程上就落成了这个让长文本生成变快的关键优化。


🎬 动手:一个最小自回归生成器,看"归纳链"如何一步步搭起来

把上面所有画面缝进一个能跑的最小自回归生成器,把每一次"归纳步"打印出来:

import torch
import torch.nn.functional as F

torch.manual_seed(0)
V = 8
W = torch.randn(V, V)

def generate(start, n):
    seq = [start]
    print(f"奠基:放下第 1 个词 = {start}")
    for step in range(n):
        logits = W[seq[-1]]                       # 归纳步:由前文推出下一个词
        nxt = torch.multinomial(F.softmax(logits, dim=-1), 1).item()
        seq.append(nxt)
        print(f"归纳步 {step+1}:由前 {len(seq)-1} 个词  ->  推出第 {len(seq)} 个词 = {nxt}")
    return seq

out = generate(0, 5)
print("最终序列:", out)
# 奠基 + 5 次归纳步 = 一条完整的归纳链,也就是一句"生成"出来的话

每一行都是一次归纳步:由已有的前文,推出下一个词,再把它接回前文,继续推。 这条不断延长的"归纳链",就是大模型写出的每一句话。 仓库里的动画脚本把它画了出来:

python induction_autoregression_visualization.py

左边你会看到一排"骨牌"逐个落下(逐词生成),每落下一块,都有一束箭头从它指回前面所有已生成的词—— 这就是"强归纳:看全部前文"的样子; 右边则演示"归纳链断裂":两条本该一样的序列,只在某一步被扰动了一点点, 之后就沿着归纳步越拉越远——一步错、后面全歪的曝光偏差,一目了然。


缝合:把所有画面接起来

回到开头,现在每个概念都有了画面和代码出处:

归纳法概念 高中怎么讲 这篇文章怎么看(画面) 在大模型里是什么
奠基 证 n=1 成立 放下第一块骨牌(第 0、1 节) 第一个词 / 起始符
归纳步 n=k ⟹ n=k+1 由前文推出下一个词(第 1、2 节) 自回归的每一步生成
强归纳 用 P(1)…P(k) 注意力回看全部前文(疑惑点一) Transformer 的长距记忆
链条依赖每步成立 有一步不成立就断 一环断、后面全歪(第 3 节) 曝光偏差 / 错误累积
归纳假设不必重证 已证的直接用 缓存旧结果不重算(疑惑点二) KV-cache

三句话总结这篇文章:

数学归纳法的灵魂是"奠基 + 归纳步"——定义起点和"由前推后"的规则,就能构造无限(第 1 节); 自回归生成就是这套结构——第一个词是奠基、每次预测下一个词是归纳步,而注意力让它成了"看全部前文"的强归纳(第 2 节、疑惑点一); 归纳链的脆弱与高效也都继承了下来——一步错则后面全歪(曝光偏差),前项不必重证则可缓存(KV-cache)(第 3 节、疑惑点二)。

当年数学归纳法学得那么"套路化",不是因为它难,是因为没人告诉你: 那套"奠基 + 由前推后"的证明结构,最后长成了大模型逐字写出每一句话的骨架, 连它一步说错就崩、以及怎么缓存偷懒,都写在归纳法的老道理里。 现在把上面每段代码跑一遍,比高中刷十套归纳证明题都值。


备注(选题/标题): 这篇走的是"高中知识其实是 AI 基石"的钩子(和《对数》《向量》《sin 和 cos》同一路子)。 核心爽点是"自回归 = 数学归纳法"这个结构同构,且顺势带出两个真机制(强归纳↔注意力、归纳链断裂↔曝光偏差)。 可与《对数》《向量》《复数》拼成"注意力四件套",形成系列合力。 若并入"大学4年"主系列:《大学4年没让你真正搞懂的数学归纳法,被大模型的逐字生成讲透了》。