高中背到头秃的排列组合,其实解释了大模型为什么"不可能靠背、只能靠学"

2026-07-05 · Steve Chan

排列、组合、AⁿₘCⁿₘ、乘法原理、加法原理……高中背了一整章,考完只记得"排队问题"和"摸球问题"。 因为老师只教你"套公式数方案数",没告诉你两件事:

第一,排列组合的灵魂不是"算彩票中奖率",是量化一件事:可能性有多少种。 第二,今天每一个大模型(LLaMA、Qwen、GPT、DeepSeek)之所以是"学出来"而不是"背出来"的, 正是因为可能的句子数量是一个大到超过全宇宙原子数的组合爆炸——它根本背不完,只能学"组合规律"。

这篇文章不背一个公式、不做一道摸球题,全部用能跑起来的 Python 代码, 把排列组合最核心的"乘法原理 → 组合爆炸"本质,一路接到大模型的泛化与生成。 并且顺手回答四个灵魂拷问:

  • 排列组合明明是算排队、摸球的,怎么会和大模型扯上关系?
  • 到底有多少种"可能的句子"?这个数字大到什么地步?
  • 既然可能的句子是天文数字,模型怎么可能"见过"它要生成的那句话?
  • 生成文字时,模型是怎么在这个天文数字里找出一条路的?

0. 一句话主线

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

长度 L、词表 V 的句子,一共有 V^L 种——这是一个组合爆炸。

这个数字大到超过全宇宙的原子数。它带来两个决定性后果:其一,模型不可能"背下所有句子", 只能学会"词该怎么组合"的规律(泛化);其二,生成一句话,是在一棵每层 V 个分支的巨树里搜索一条路。

# 乘法原理:每个位置有 V 种选择,L 个位置就是 V 自乘 L 次
V, L = 3, 4        # 词表 3 个词,句子长 4
print("可能的句子总数 = V^L =", V ** L)     # 81

记住这个画面:每多一个词,可能性就乘以 V。语言的可能性,是指数级膨胀的。 下面所有东西都挂在"V^L 到底有多大"这一件事上。


1. 排列组合的灵魂:乘法原理

高中把排列组合拆成一堆"有无重复、有无顺序"的分类套路,把你埋在公式里。 但所有公式的根,只有一条乘法原理:一件事分几步做,每步的选择数相乘,就是总方案数。

import math

# 排列 A(n,k):从 n 个里有序取 k 个 = n×(n-1)×…×(n-k+1)
# 组合 C(n,k):从 n 个里无序取 k 个 = A(n,k) / k!
n, k = 10, 3
perm = math.perm(n, k)          # 有序:第1步10种、第2步9种、第3步8种
comb = math.comb(n, k)          # 无序:再除掉 k 个的内部排列 k!
print(f"排列 A({n},{k}) = {perm}")     # 720
print(f"组合 C({n},{k}) = {comb}")     # 120
print("验证 A = C × k! :", comb * math.factorial(k) == perm)   # True

排列和组合的全部区别,不过是"顺序算不算数"。但对语言来说,顺序当然算数("猫追狗"≠"狗追猫"), 所以句子的数量走的是最凶猛的那条路——每个位置独立地从 V 个词里选,方案数直接连乘成 V^L


2. 组合爆炸:V^L 到底有多大

把词表和句长换成真实量级,感受一下这个数字的恐怖:

# 真实量级:5 万词表,一个只有 20 个词的短句
V, L = 50000, 20
count = V ** L
print("可能的句子数大约有", len(str(count)), "位数")     # 94 位
print("作为对比,全宇宙的原子数大约 80 位数")
# 一个 20 词短句的可能性,就已经比整个宇宙的原子还多 10 亿亿倍

50000²⁰ 是一个 94 位的数字。而整个可观测宇宙的原子数,大约只有 80 位数。 换句话说:仅仅 20 个词的短句,它的可能排列就已经远远超过了宇宙里所有的原子。 这个数字一旦摆出来,一个残酷的事实就无法回避了——见下一节。


🤔 疑惑点一:既然句子几乎无穷,模型怎么可能"见过"它要生成的那句话?

它见不过,也不可能见过。哪怕训练集有 10 万亿句,除以那个 94 位数的可能空间,比例也几乎是 0。所以大模型生成的绝大多数句子,都是它训练时从没见过的全新组合。它靠的不是"检索记忆里的句子",而是学会了"词与词该怎么组合"的规律,再当场拼出一句新的——这就是泛化。

用一个除法,把这个"沧海一粟"算给自己看:

train_sentences = 10 ** 13         # 假设训了 10 万亿句(远超现实)
possible = 50000 ** 20             # 可能的句子空间
print("训练集 / 可能空间 =", train_sentences / possible)
# ≈ 1e-81 —— 训练集在可能空间里,比一粒沙之于整个宇宙还渺小

结果是 1e-81 这个渺小到没有实感的数。这就是"大模型不是搜索引擎"的数学根源: 它不可能靠"背题库"工作,因为题库连可能空间的亿亿亿……分之一都盖不住。 它必须做的,是从有限的例子里,抽出"语言如何组合"的规律。


3. 为什么"组合式泛化"能行:少量部件,海量组合

组合数在这里给出了泛化的底气:用少量的"部件",通过组合,就能覆盖海量的结果。

import math

# 从 n 个部件里选 k 个来组合,方式数增长得极快
for n, k in [(10, 3), (100, 5), (1000, 10)]:
    print(f"C({n},{k}) = {math.comb(n, k):,}")
# 1000 个部件里选 10 个 -> 已经 2.6 万亿种组合

这正是大模型的工作方式:它学的是有限的"部件"和"组合规则"——词的含义(词向量)、 语法结构、语义搭配——然后把它们像乐高一样拼装。学会几万个词 + 有限的组合规律, 就能应对那个 94 位数的句子空间。"有限规则生成无限句子",这句语言学的老话, 底层就是组合数学:少量部件的组合,其数量是爆炸式的。


4. 生成 = 在一棵巨树里搜索一条路

既然不能枚举所有 V^L 种句子挑最好的,模型只能一个词一个词地往下走, 在一棵"每层 V 个分支"的树里搜一条高概率的路:

# 生成就是在一棵每层 V 个分支的树里找一条路径
V, L = 5, 10
print("穷举所有路径要看", V ** L, "条 —— 不可能")     # 9,765,625

# 贪心:每步只留最优 1 条;beam(宽度 b):每步留最优 b 条
for beam in [1, 3, 5]:
    print(f"beam={beam}: 每步只评估 {beam * V} 个候选,而非 {V ** L} 全枚举")

穷举 V^L 条路径不可能,于是有了两种务实的走法:贪心(每步只选当前最优的一个词) 和 beam search(每步保留最优的 b 条半成品)。它们都把"在天文数字里找全局最优"这个不可能任务, 换成了"每步在 V 个词里做局部选择"的可行任务。


🤔 疑惑点二:为什么不干脆枚举所有句子、选概率最高的那句?

因为那要遍历 V^L 种句子,是一个 94 位数级别的计算量,宇宙毁灭都算不完。所以大模型放弃"全局最优",改用"逐词近似":每一步只在当前 V 个词里挑,走 L 步得到一句话。这是用可行性换掉了最优性——beam search 只是在"贪心"和"全枚举"之间,留了 b 条后路,稍微逼近一下全局最优而已。

用代码把"逐步近似"跑一遍——它把 V^L 的不可能,拆成了 L 次"V 选一"的可行:

import torch

torch.manual_seed(0)
V = 5
seq = []
for step in range(6):                  # 逐词生成,每步只在 V 个词里做局部选择
    logits = torch.randn(V)            # 模型这一步给出的打分
    nxt = logits.argmax().item()       # 贪心:选本步概率最高的词
    seq.append(nxt)
print("贪心逐词生成的序列:", seq)
# 从不回头枚举整棵树,只沿着一条路往前走 —— 这才把生成变得可行

🎬 动手:贪心 vs beam search,谁找到的路概率更高

把上面所有画面缝进一个能跑的最小搜索:在一棵玩具概率树里,比较贪心和 beam search 找到的路径概率:

import torch
import torch.nn.functional as F

torch.manual_seed(0)
V, L, beam = 4, 5, 3
W = torch.randn(V, V)                          # 转移打分:下一个词与当前词有关

def greedy():                                  # 贪心:每步只留最优 1 条
    cur, logp, path = 0, 0.0, [0]
    for _ in range(L):
        lp = F.log_softmax(W[cur], dim=-1)
        cur = lp.argmax().item()
        logp += lp[cur].item(); path.append(cur)
    return path, logp

def beam_search(b):                            # beam:每步留最优 b 条
    beams = [([0], 0.0)]
    for _ in range(L):
        cand = []
        for path, logp in beams:
            lp = F.log_softmax(W[path[-1]], dim=-1)
            for v in range(V):
                cand.append((path + [v], logp + lp[v].item()))
        beams = sorted(cand, key=lambda t: -t[1])[:b]   # 只保留最优 b 条
    return beams[0]

gp, gl = greedy()
bp, bl = beam_search(beam)
print(f"贪心   : 路径 {gp}  总 log 概率 = {gl:.3f}")
print(f"beam={beam}: 路径 {bp}  总 log 概率 = {bl:.3f}")
print("beam 找到了概率更高的路" if bl > gl else "两者恰好相同")

贪心每步只顾眼前最优,容易错过"这一步稍差、但后面更好"的路;beam 多留几条后路, 往往能找到总概率更高的句子。这就是排列组合最实际的落地:既然那棵 V^L 的树无法穷举, 就用有限的搜索宽度,在爆炸的可能性里捞一条尽量好的路。


缝合:把所有画面接起来

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

排列组合概念 高中怎么讲 这篇文章怎么看(画面) 在大模型里是什么
乘法原理 分步相乘 每加一词、可能性乘以 V(第 0、1 节) 句子空间 = V^L
组合爆炸 阶乘增长很快 20 词短句 > 全宇宙原子(第 2 节) 为什么不能靠"背"
组合数 C(n,k) 摸球、选人 少量部件、海量组合(第 3 节) 泛化的底气
方案枚举 列出所有情况 V^L 棵树无法穷举(第 4 节) 生成 = 树搜索
最优 vs 可行 数出最优解 逐词近似换掉全枚举(疑惑点二) 贪心 / beam search

三句话总结这篇文章:

排列组合的灵魂是"数可能性"——句子空间是 V^L 的组合爆炸,20 词短句就超过全宇宙原子(第 1、2 节); 爆炸大到背不完,所以模型只能泛化——学有限的部件与组合规律,拼出没见过的新句子(疑惑点一、第 3 节); 也大到不能枚举,所以生成是树搜索——用贪心 / beam search 在指数级的树里捞一条高概率的路(第 4 节、疑惑点二)。

当年排列组合背得那么苦,不是因为它难,是因为没人告诉你: 那个你以为只用来算彩票的"方案数",最后长成了大模型面对的整个语言空间, 而正是它大到背不完,才逼出了今天大模型"会举一反三"的泛化能力。 现在把上面每段代码跑一遍,比高中刷十套排列组合题都值。


备注(选题/标题): 这篇走的是"高中知识其实是 AI 基石"的钩子(和《对数》《向量》《sin 和 cos》同一路子)。 与其他篇不同,这篇偏"观念冲击"而非"单一机制拆解"——核心爽点是"20 词 > 全宇宙原子"那个反差, 标题也可直接押这个点:《一个 20 字的句子,可能性比全宇宙的原子还多——这就是大模型学不完、只能"悟"的原因》。 若并入"大学4年"主系列:《大学4年没让你真正搞懂的排列组合,被大模型的生成过程讲透了》。