为什么 AI、数学顶尖人才都绕不开高阶函数?答案藏在 λ 演算里!
为什么 map、递归、微分方程、神经网络看起来毫不相关,却可以用同一种抽象描述?1936 年丘奇提出的 Lambda 演算只有“变量、抽象、应用”三条规则,却一路连接了 Lisp、函数式编程、Y 组合子、自动微分和今天的 Transformer。再往前一步,不动点又把递归、ODE 求解、PageRank 和神经网络训练串成了一条线:函数吃函数,函数返回函数,再用不动点把无限过程闭合。 这可能才是理解现代 AI 底层计算思想的一把钥匙。
"丘奇 1936 年用 λ 演算证明:所有可计算的东西,都是函数。这句话也适用于深度学习。"
Chapter 0.8 说微分方程的解是"函数返回函数"。再往前迈一步就会发现:当函数能返回函数、函数能吃函数,整个数学和编程世界就被同一种抽象贯穿 —— 这就是 1936 年阿隆佐·丘奇在《一个不可解问题的注记》里给出的 λ 演算。深度学习里堆 100 层的 Transformer、数学里求解微分方程、工程里写一个 map —— 都是同一个抽象的不同投影。这一节用 Clojure 来说,因为 Lisp(1958,McCarthy)是丘奇 λ 演算最直白的化身 —— 句法上几乎一对一。
0.9.1 一切皆函数:λ 演算的三条规则
丘奇 1936 定义 λ 演算时,只有三条原语:变量、抽象(lambda)、应用。就够了 —— 自然数、布尔、数据结构、循环、递归全部能用纯函数搭出来(丘奇编码)。
;; 1. 变量
x
;; 2. 抽象:定义一个函数
(fn [x] (* x x)) ; λx. x²
;; 3. 应用:把函数喂给一个值
((fn [x] (* x x)) 3) ; → 9
写代码的人每天都在用这三件事,但很少意识到:这三条规则足以构造整个数学。这件事和 Chapter 0.5 的母题完全一致 —— 少数几条原语反复折射。
0.9.2 map 是高阶函数,求导也是 —— 它们是同一种东西
中学课本里的"函数"是 \(f(x) = x^2\),吃数、吐数。但只要承认函数也是值,就能让函数吃函数、吐函数 —— 这就是高阶函数:
;; map:经典高阶函数 —— 吃一个 lambda + 一个列表,吐一个新列表
(map (fn [x] (* x x)) [1 2 3 4 5])
;; → (1 4 9 16 25)
;; 求导也是高阶函数:吃 f,吐 f'
(defn D [f]
(fn [x] (/ (- (f (+ x 1e-6)) (f x)) 1e-6))) ; 牛顿 1666 的"流数"
(def f (fn [x] (* x x x))) ; f(x) = x³
(def f-prime (D f)) ; f'(x) ≈ 3x²
(f-prime 2.0) ; → 12.000…
map 和"求导"结构上是同一种东西 —— 都是 (λ → 新东西) 的高阶函数。一旦戴上这副眼镜,数学和编程里到处都是相同的影子:
| 编程里的高阶函数 | 数学/物理里的对应 |
|---|---|
map : (a → b) → [a] → [b] |
在每一点逐点应用一个变换(向量场作用) |
reduce : (b → a → b) → b → [a] → b |
积分:从初值出发把函数值累加(= for 循环) |
comp : (b → c) → (a → b) → (a → c) |
链式法则 \((f\circ g)' = f'(g)\cdot g'\) |
partial : (a → b → c) → a → (b → c) |
偏导数:冻住一个变量,剩下的还是函数 |
D : (R → R) → (R → R) |
\(\dfrac{d}{dx}\) 算子(Chapter 0.8) |
solver : (ODE, y₀) → (t → y) |
微分方程的解算子 |
积分 =
reduce。一行(reduce + y0 (map f ts))就是欧拉法的全部 —— 这正是 Chapter 0.8.3 那个 for 循环的 Lisp 写法。
0.9.3 不动点:让 lambda 把自己喂给自己
高阶函数最迷人的玩法是让函数吃自己。若 \(F(x^*) = x^*\),则 \(x^*\) 是 \(F\) 的不动点。这听起来哲学,但它就是递归的数学基础:
;; cos 的不动点 ≈ 0.739085… —— 反复 cos(cos(cos(…))) 会收敛到它
(defn fixpoint [f x0 tol]
(let [x1 (f x0)]
(if (< (Math/abs (- x1 x0)) tol)
x1
(recur f x1 tol)))) ; Clojure 的 recur = "把答案留给下一个"
(fixpoint #(Math/cos %) 1.0 1e-10)
;; → 0.7390851332151606
有了不动点这副语言,很多看似不相关的事忽然变成同一件事:
| 现象 | 它是哪个算子的不动点? |
|---|---|
| ODE 解 \(y(t)\) | 积分算子 \(T[y](t) = y_0 + \int_0^t f(s, y(s))\,ds\)(Picard 迭代) |
| 训练好的神经网络权重 \(W^*\) | 一步 SGD 映射 \(W \mapsto W - \eta\nabla L(W)\)(即 \(\nabla L = 0\)) |
| PageRank 排名向量 | 转移矩阵 \(P\) 的不动点 \(Pv^* = v^*\)(特征向量) |
| 扩散模型去噪终态 | 反向 SDE 算子的不动点 |
递归函数 factorial |
Y 组合子作用在 lambda 上的不动点 |
最后一行是 λ 演算的高潮 —— Y 组合子。纯 λ 演算里没有 def、没有名字,怎么写递归?丘奇的学生 Curry 找到了答案:让函数把自己喂给自己。
;; Y 组合子:在没有"名字"的世界里实现递归
;; 形式上就是 λf. (λx. f (x x)) (λx. f (x x)) 的 strict 语言变体
(def Y
(fn [f]
((fn [x] (f (fn [v] ((x x) v))))
(fn [x] (f (fn [v] ((x x) v)))))))
;; 用 Y 实现阶乘 —— fact 自己并不知道自己叫 fact
(def fact
(Y (fn [self]
(fn [n]
(if (zero? n) 1 (* n (self (dec n))))))))
(fact 5) ; → 120
Y 把"函数返回函数"推到极限:
λ (λ (λ ...))不断高阶化,最后用不动点把这条无限链闭合。这就是用户问的 "111 lambda lambda lambda" 的精确含义。
0.9.4 神经网络 = lambda 的塔 + 训练 = 找不动点
从这个视角看,深度学习毫不神秘:
- 每一层 = 一个 lambda:
(fn [x] (relu (+ b (matmul W x)))) - 整个网络 = lambda 的复合:
(comp λ_N … λ_2 λ_1)—— 字面意义上的 "111 lambda lambda lambda" - 前向传播 = 函数应用
- 反向传播 = 链式法则在
comp上的同伴运算(自动微分) - 训练 = 在权重空间里找梯度算子的不动点:\(W^* = W^* - \eta\nabla L(W^*)\),即 \(\nabla L(W^*) = 0\)
;; N 层网络 = lambda 的复合,这就是字面意义的 "lambda lambda lambda"
(defn make-net [layers]
(apply comp (reverse layers))) ; comp 就是 ∘ 算子
(def net
(make-net
[(fn [x] (relu (linear x W1 b1))) ; λ₁
(fn [h] (relu (linear h W2 b2))) ; λ₂
(fn [h] (linear h W3 b3))])) ; λ₃
;; 训练 = 在权重空间里找梯度的不动点
(loop [params init-params]
(let [grads (backward (forward params batch))
new-params (sub params (mul lr grads))]
(if (converged? params new-params) ; 不动点判据:∇L ≈ 0
new-params
(recur new-params)))) ; recur = "把答案留给下一个"
注意 Clojure 的 recur —— 它就是 Chapter 0.8.3 的"求解留给下一个"在语言层面的关键字化。每轮把当前状态丢给下一轮,直到不动点。ODE 求解、训练神经网络、求 PageRank、Picard 迭代 —— 在 Lisp 眼里是同一段代码。
0.9.5 抽象层级的统一:从底到顶都是同一个 λ
┌─────────────────────────────────────────────────┐
│ 扩散模型 / 神经 ODE / RLHF │ ← 高级抽象:λ 的塔
├─────────────────────────────────────────────────┤
│ 反向传播 = comp 在求导意义下的同伴 │
├─────────────────────────────────────────────────┤
│ 每一层 = λ;网络 = (comp λ₁ λ₂ … λₙ) │
├─────────────────────────────────────────────────┤
│ 训练 = 在权重空间里找 ∇L = 0 的不动点 │
├─────────────────────────────────────────────────┤
│ ODE / PDE 解 = 积分算子的不动点(Picard) │ ← Chapter 0.8 的延续
├─────────────────────────────────────────────────┤
│ 积分 = reduce ;求导 = 高阶函数;map ≅ 逐点变换 │
├─────────────────────────────────────────────────┤
│ λ 演算的三条规则:变量、抽象、应用 │ ← 底层抽象(丘奇 1936)
└─────────────────────────────────────────────────┘
从最底下的三条规则,到最上面的扩散模型 —— 每一层用的都是同一种抽象,区别只在于 λ 叠多深、用哪种不动点逼近。这就是为什么 Lisp 程序员对深度学习有种"早就见过"的感觉 —— 他们 60 年前就在玩 lambda 和 fixpoint。
McCarthy 1958 年发明 Lisp 时直接照搬丘奇 1936 的 λ 演算;今天 PyTorch 的
nn.Sequential不过是(comp λ₁ λ₂ … λₙ)的另一种写法。
0.9.6 可视化与代码
- 可视化:
- 不动点迭代的"蛛网图"(cobweb plot):\(\cos\) 不动点、Logistic 映射、Picard 迭代并排
- Y 组合子的展开动画:
Y f → f (Y f) → f (f (Y f)) → …,与神经网络的(comp f f f …)同框对照 - ODE 解 = 积分算子不动点:用 Picard 迭代逐次逼近一条 ODE 的解函数
- 训练曲线 = 不动点收敛:SGD 轨迹在权重空间向 \(\nabla L = 0\) 收敛的蛛网图
- 代码:
ch00_9_lambda_fixpoint/ clojure_lambda_basics.clj—— λ 演算三条规则在 Clojure 里的最小演示map_reduce_as_calculus.clj—— 用map/reduce重写数值求导与积分y_combinator.clj—— 没有def的世界里用 Y 实现阶乘和斐波那契picard_iteration.py—— ODE 解作为积分算子的不动点(Picard 迭代收敛动画)nn_as_comp_lambdas.py—— 把一个 PyTorch 网络print成(comp λ₁ … λₙ)的形式training_as_fixpoint.py—— SGD 训练的不动点蛛网图cobweb_plot.py—— \(x_{n+1} = f(x_n)\) 蛛网图通用工具,覆盖上述所有不动点
元教学意义:本章把 0.8 的"返回函数"再推一层。看到深度学习堆 100 层、扩散模型反复去噪、ODE 求解器迭代逼近,统统问一句:"这是哪个算子的不动点?λ 被叠了几层?" —— 答案永远是这两个原语的组合。这就是 1936 年丘奇留给我们的礼物。