13.4 總結:從窮竭法到自動微分#
學習動機#
這是全書的最後一節。前面十二章,我們走過了一條長路:
公元前 250 年的阿基米德窮竭法 → 1666 年的牛頓-萊布尼茲微積分 → 1821 年柯西的 \(\varepsilon\) 語言 → 1872 年魏爾斯特拉斯的嚴格定義 → 1901 年勒貝格的測度 → 1906 年黎曼-羅朗定理 → 1906–1933 年的泛函分析 → 今天的 SymPy 與自動微分。
這條線索上,有兩條主線貫穿始終:
- 嚴格化:把模糊的直覺( \(\varepsilon\) 愈小愈好)變成可以逐條檢查的邏輯結構;
- 符號化:把「靠腦袋算」變成「靠規則算」,並且讓規則本身可以被檢查、可以被質疑。
本節要做四件事:
- 畫一張時間線(mermaid),把兩千兩百五十年的關鍵節點串起來;
- 整理「嚴格化」的三個階段:直覺 → \(\varepsilon\) - \(\delta\) → 測度與範數;
- 整理「符號化」的三個階段:手算 → 符號替換 → 自動微分;
- 給出延伸閱讀與 SymPy API 對照表,然後回到第 1 章的阿基米德,把整個故事畫上句點。
本節的態度:這不是「重點整理」,而是一次回頭看。我們要用 SymPy 把全書的代表性結果重新跑一遍,看看它們現在長什麼樣。
13.4.1 一張時間線:兩千兩百五十年的分析學#
timeline
title 分析學的兩千兩百五十年:從窮竭法到自動微分
section 古典時期
公元前 250 年 : 阿基米德《方法》:窮竭法與可證的逼近 : 圓的面積用內接正多邊形逼近 : 「方法論」的確立
1629 年 : 費馬:極值法與切線問題 : 增量的一次項 = 0
1665–1684 年 : 牛頓與萊布尼茲各自發明微積分 : 萊布尼茲的微分記號 dy/dx : 可攜的運算法則
1666 年 : 牛頓-萊布尼茲定理 : 微積分基本定理
1715 年 : 泰勒定理 : 餘項估計
1735 年 : 歐拉: Basel 問題 sum 1/n^2 = pi^2/6
1752 年 : 歐拉的力學:Euler-Lagrange 方程 : 泛函的必要條件
section 嚴格化時期
1817 年 : 玻爾查諾:連續性的新表述
1821 年 : 柯西《分析原理》:極限寫成不等式 : 從「趨近」到「對每個 eps 存在 delta」
1834 年 : Hamilton:最小作用量原理
1837 年 : 狄利克雷: Fourier 級數的收斂問題
1845 年 : Jacobi:變分法的「最後乘法因子」與充分條件
1872 年 : 魏爾斯特拉斯:eps-delta 定義的現代形式 : 嚴格化的完成
1880s 年 : Weierstrass:必要的變分不等式
section 測度與函數空間
1901 年 : 勒貝格:測度與勒貝格積分 : 「可數可加」的長度取代「可分割」的長度
1906 年 : 黎曼:黎曼-羅朗定理、範數、函數空間
1920s 年 : Banach、Hilbert:賦範空間與 Banach 代數
1933 年 : Banach:線性泛函的 Hahn-Banach 定理 : 對偶性
1951 年 : Robbins-Monro:隨機逼近與 SGD 的收斂定理
1986 年 : Rumelhart-Hinton-Williams:backpropagation
section 計算機時代
1960s 年 : Wilkinson:條件數與誤差分析
1965 年 : Kahan:補償求和
1980s 年 : 符號計算系統的興起
今日 : SymPy:極限、積分、級數、微分方程、變分法、最佳化 : 自動微分與梯度下降看這張圖的三個重點:
- 嚴格化不是一步到位的。從 1666 年(微積分基本定理)到 1872 年(魏爾斯特拉斯的定義),花了兩百零六年。而且「嚴格化」不是只有一條線:極限(第 2 章)、微積分基本定理(第 4 章)、級數收斂(第 6 章)、多重積分換序(第 7 章)、傅立葉級數(第 11 章)各自有各自的嚴格化時間表。
- 符號化與嚴格化是交錯的。柯西(1821)在給出 \(\varepsilon\) 語言的同時也推導了具體公式;Weierstrass(1872)在定義極限的同時也發展了函數逼近論。沒有人只做其中一件事。
- 最後三十年(1951–今日)的主題是「可計算性」:Robbins–Monro 讓「學習」有了收斂定理,Wilkinson 讓「誤差」有了量化語言,Kahan 讓「求和」變得可靠,backpropagation 讓「梯度」變得可行,SymPy 讓「符號運算」變成一行命令。這一切都在回答同一個問題:怎麼讓數學可以被機器執行?
13.4.2 主線一:嚴格化的三個階段#
階段一:直覺(公元前 250 – 1820 年)#
在柯西之前,「極限」是一個口語的、手勢的概念。阿基米德說「圓的面積大於內接正多邊形的面積,而夾在兩個多邊形之間」;牛頓說「當增量趨於零時」,萊布尼茲說「 \(\mathrm{d}y/\mathrm{d}x\) 愈小愈近」。
這個階段的特徵:
- 用的是幾何圖像(切線、瞬時變化率、面積的逼近);
- 論證的對象是個別的具體問題(這個函數的積分、那條曲線的切線);
- 沒有量詞,因此無法判定論證是否完整。
阿基米德的方法論(見第 1.1 章):他不是「算出」 \(\pi\) ,而是「證明 \(\pi\) 被夾在某兩個有理數之間」。這已經是嚴格化的雛形——它有明確的量詞結構(對每個 \(\varepsilon\) 存在 \(n\) )。但它是一個一個問題地做的,沒有被抽象成一般的定理。
階段二: \(\varepsilon\) - \(\delta\) (1821 – 1872 年)#
柯西(1821) 把「趨近於」翻譯成不等式;魏爾斯特拉斯(1872) 給出現代形式:
這個階段的特徵:
- 出現了量詞( \(\forall\varepsilon\,\exists\delta\) ),因此可以判定論證是否完整;
- 「趨近」被定義為一個關係而非一個過程;
- 但它處理的是單個變數、一個極限點的情況。
還剩下什麼問題? \(\delta\) 的存在性依賴「實數系的完整性」。柯西和魏爾斯特拉斯的時代,人們對「實數是什麼」還沒有共識——極限的嚴格性被卡在基礎理論上。
階段三:測度與範數(1901 年以後)#
勒貝格(1901) 的做法是:不要在每個點上纠缠,而是換一個測量長度的方式。他把「長度」從「可分割性」改成「可數可加性」,然後積分、極限、級數、換序定理全部一次性地變得乾淨:
黎曼(1906) 與 Riesz–羅朗(1906) 則給出了另一個方向:把「函數」本身放進一個有結構的空間。
範數帶來的好處:
- 「連續」變成「 \(\|f(x) - f(y)\| \le M|x-y|\) 」;
- 「可微」變成「存在有界線性算子 \(L\) 使 \(f(x+h) - f(x) = L(h) + o(\|h\|)\) 」;
- 「收斂」變成「 \(\|f_n - f\| \to 0\) 」,於是有了 Cauchy–Schwarz、Hölder、Minkowski 這些不等式工具箱。
這個階段的特徵:
- 從「逐點」走向「整體」;
- 從「一個極限」走向「一類極限的交換」;
- 從「實數上的函數」走向「賦範空間上的算子」(Banach 1920s、Hilbert 1920s)。
三個階段的關係:
它們不是互相取代的。今天我們仍然用 \(\varepsilon\) - \(\delta\) 講單點連續性(因為它最直觀),仍然用範數講泛函分析(因為它最有力),也仍然需要測度來處理「幾乎處處」的問題。
SymPy 在這條線上的位置:SymPy 不做嚴格化。它給你的是「在啟發式規則下的精確代數結果」。它假設了嚴格化已經完成(用戶的責任),然後在已經嚴格化的地基上蓋房子。 這正是第 1.3 節「先猜再驗證」態度的完整表述。
嚴格化的四個「歷史教訓」#
| 教訓 | 出處 | 說明 |
|---|---|---|
| 可證的逼近勝過直接的計算 | 阿基米德《方法》 | 「 sandwich 」技巧:給出上下界,誤差自動被夾住 |
| 定義的嚴格性決定定理的嚴格性 | 魏爾斯特拉斯 | 定義模糊 → 定理含糊 → 證明無法機器檢查 |
| 交換極限與運算需要條件 | 勒貝格 | 單調收斂、DCT、Fubini 都是「有條件」的 |
| 無限維的結構需要新的公理 | Banach、Hilbert | 完备性、稠密性、投影定理…… |
13.4.3 主線二:符號化的三個階段#
階段一:手算(公元前 250 – 1640 年代)#
阿基米德算圓面積用幾何逼近:96 邊形、192 邊形……每一步都要畫圖、量長度。費馬的極值法要手算「增量的一次項」。
這個階段的特徵:
- 每一步都是一次性的、不可重複的;
- 符號( \(x\) 、 \(y\) )是「待代入的數」,不是「可以操作的物件」;
- 符號表的規模隨步數線性膨脹——牛頓算 \(\sqrt{2}\) 要寫下 20 個數。
階段二:符號替換(第 3 章:萊布尼茲的 \(\mathrm{d}y/\mathrm{d}x\) )#
萊布尼茲在 1675 年前後做了一件極其關鍵的事:把微分寫成一個分式的形式, \(\frac{\mathrm{d}y}{\mathrm{d}x}\) ,於是
變成了「分子分母分別對應」的形式規則,可以逐步套用。
這就是「符號替換」的原型:
- \(\frac{\mathrm{d}y}{\mathrm{d}x}\) 是一個可以被整除的記號;
- 乘積法則 \(\mathrm{d}(uv) = u\,\mathrm{d}v + v\,\mathrm{d}u\) 來自「把 \(\mathrm{d}\) 當作乘數分配」;
- 鏈鎖法則 \(\mathrm{d}f(g(x)) = f'(g(x))\,\mathrm{d}g(x)\) 來自「 \(\mathrm{d}\) 可以複合」。
第 3.2 節強調的「可攜性」——同一套規則可以用在任何函數上,不需要重新發明——正是「符號化」的定義。
這個階段的瓶頸:符號表的膨脹。求 \(\frac{\mathrm{d}^{20}}{\mathrm{d}x^{20}}\tan x\) 的手算會產生一個幾乎不可讀的式子。
階段三:自動微分(第 13 章)#
自動微分把「符號替換」系統化成一個演算法:
把表達式視為一棵運算樹;對每個節點,沿樹遞迴,把「值」與「微分形式」一起傳下去。
\(sp.diff\) 就是這個演算法的符號化實作(第 13.3.3 節),而 PyTorch 的 grad 是它的張量化實作。
三個階段的對照:
| 手算 | 符號替換(萊布尼茲) | 自動微分 | |
|---|---|---|---|
| 驅動方式 | 人逐條規則 | 人逐條規則 | 機器沿樹遞迴 |
| 規則數量 | 有限但需人記憶 | 有限(乘積、鏈鎖…) | 有限,但只需實作一次 |
| 處理新函數 | 需推導導數 | 需推導導數 | 只需實作基本運算 |
| 精度 | 精確 | 精確 | 精確 |
| 複雜度 | 隨符號膨脹 | 隨符號膨脹 | ** \(O(\text{原式})\) ** |
| 誰在做 | 人 | 人 | 機器 |
自動化帶來的最大變化不是速度,而是「可檢查性」。自動微分的規則是有限且明確的,因此:
- 它可以被實現(不只是描述);
- 它可以被測試(用符號解、數值差分交叉驗證——第 13.3.4 節);
- 它可以被優化(反向模式就是最優化後的版本)。
而「符號化」這條線的終點,是機器學習:當梯度可以被自動算出來,「學習」就變成了一個最佳化問題,而最佳化問題的理論(第 13.1 節的變分法、梯度下降、Robbins–Monro)在 1951 年就已經準備好了。
SymPy 在這條線上的位置:它是符號化階段二的頂點與階段三的起點——它把萊布尼茲的手寫規則自動化了,但輸出的是符號表達式(可讀、可證明),而 PyTorch 輸出的是張量(快、但不可讀)。
13.4.4 全書的代表性結果:一次重新巡禮#
現在讓我們用 SymPy 把全書的代表性結果重新跑一遍。這不是複習,而是「用今天的工具回看昨天的問題」。
import sympy as sp
x = sp.Symbol('x', real=True)
n = sp.Symbol('n', integer=True, nonnegative=True)
print("=== 全書定理的計算機輔助抽查 ===")
# (1) e 的兩個定義相等(第 1、6 章)
e_def1 = sp.limit((1 + 1/n)**n, n, sp.oo)
e_def2 = sp.summation(1/sp.factorial(n), (n, 0, sp.oo))
print("1. e 的兩種定義一致:", sp.simplify(e_def1 - e_def2) == 0, " ->", e_def1)
# (2) ζ(2) = pi^2/6(第 6 章)
print("2. 巴塞爾問題 sum 1/n^2 =", sp.summation(1/n**2, (n, 1, sp.oo)))
# (3) 微積分基本定理(第 4 章)
f = sp.exp(-x**2)
F = sp.integrate(f, x)
print("3. FTC:d/dx int f 與 f 的差 =", sp.simplify(sp.diff(F, x) - f))
# (4) 泰勒餘項(第 5 章)
print("4. e^x 的 3 階展開 =", sp.series(sp.exp(x), x, 0, 4))
# (5) Cauchy-Schwarz 等號條件(第 13.3 節 Lagrange 乘數)
u, v = sp.symbols('u v', real=True)
print("5. CS 等號條件 grad(xy - lam(x^2+y^2-1)) =",
[sp.diff(u*v - u**2 - v**2, u), sp.diff(u*v - u**2 - v**2, v)])
# (6) 變分法:EL 方程(第 13.1 節)
ys, yp, ypp = sp.symbols('ys yp ypp', real=True)
Fg = yp**2 + ys**2
Fy = sp.diff(Fg, ys)
Fyp = sp.diff(Fg, yp)
dFyp_dx = sp.diff(Fyp, x) + sp.diff(Fyp, ys)*yp + sp.diff(Fyp, yp)*ypp
print("6. F = y'^2 + y^2 的 EL 方程左邊 =", sp.simplify(Fy - dFyp_dx))
ysol = sp.sinh(x) / sp.sinh(1)
print(" 代回 y = sinh(x)/sinh(1) ->",
sp.simplify((Fy - dFyp_dx).subs({ys: ysol, yp: sp.diff(ysol, x),
ypp: sp.diff(ysol, x, 2)})))
# (7) 誤差理論:naive vs Kahan vs Neumaier(第 13.2 節)
def kahan(vals):
s = c = 0.0
for v in vals:
y = v - c
t = s + y
c = (t - s) - y
s = t
return s
def neumaier(vals):
s = c = 0.0
for v in vals:
t = s + v
if abs(s) >= abs(v):
c += (s - t) + v
else:
c += (v - t) + s
s = t
return s + c
vals = [1.0, 1e16, 1.0, -1e16]
print("7. naive =", sum(vals), "| kahan =", kahan(vals), "| neumaier =", neumaier(vals))
# (8) 自動微分 vs 符號微分(第 13.3 節)
g = x**3 * sp.exp(-x) + sp.sin(x)
print("8. d/dx g =", sp.diff(g, x))
print(" 在 x = 1.3 的值 =", float(sp.diff(g, x).subs(x, sp.Rational(13, 10))))
實際輸出:
=== 全書定理的計算機輔助抽查 ===
1. e 的兩種定義一致: True -> E
2. 巴塞爾問題 sum 1/n^2 = pi**2/6
3. FTC:d/dx int f 與 f 的差 = 0
4. e^x 的 3 階展開 = 1 + x + x**2/2 + x**3/6 + O(x**4)
5. CS 等號條件 grad(xy - lam(x^2+y^2-1)) = [-2*u + v, u - 2*v]
6. F = y'^2 + y^2 的 EL 方程左邊 = -2*ypp + 2*ys
代回 y = sinh(x)/sinh(1) -> 0
7. naive = 0.0 | kahan = 0.0 | neumaier = 2.0
8. d/dx g = -x**3*exp(-x) + 3*x**2*exp(-x) + cos(x)
在 x = 1.3 的值 = 1.0504826700113057
八個結果的解讀:
- ** \(e\) 的兩種定義完全一致** ——這是第 1 章( \(\lim(1+\frac1n)^n\) )與第 6 章( \(\sum\frac{1}{n!}\) )的對話。SymPy 用不同的機制算出同一個物件
E,這是對 CAS 一致性的基本信任測試。 - 巴塞爾問題 \(\zeta(2) = \frac{\pi^2}{6}\) :歐拉 1735 年的結果,至今仍是最漂亮的等式之一。
- 微積分基本定理: \(\frac{\mathrm{d}}{\mathrm{d}x}\int f = f\) ,差為精確的 \(0\) ——這是第 4 章的核心,符號運算給出的是等式,不是近似。
- 泰勒展開帶餘項:第 5 章的 \(\mathcal{O}(x^4)\) 記號被 SymPy 原生支援。
- CS 等號條件 \(\nabla(xy - \lambda(x^2+y^2-1)) = (-2u + v,\ u - 2v) = 0\) 給出 \(u = v\) ——第 13.3 節的 Lagrange 乘數法。
- EL 方程 \(2y - 2y'' = 0\) ,代回 \(y = \frac{\sinh x}{\sinh 1}\) 得 \(0\) ——第 13.1 節的符號驗證。
- 三種求和的對比:naive 與 Kahan 都給 \(0\) (錯),Neumaier 給 \(2\) (對)——第 13.2 節最精彩的一行。
- 符號微分給出封閉形式,精確代入 \(\frac{13}{10}\) 給出 \(1.0504826700113057\) ——整條鏈路上沒有任何浮點近似。
把第 1 章到第 13 章的主要工具各用一行跑一遍:
import sympy as sp
x, y, z = sp.symbols('x y z', real=True)
n = sp.Symbol('n', integer=True, positive=True)
theta = sp.Symbol('theta', real=True)
print("第 1 章 極限 :", sp.limit(sp.sin(x) / x, x, 0),
"| e 的定義 =", sp.limit((1 + 1/n)**n, n, sp.oo))
print("第 3 章 導數 :", sp.diff(sp.sin(x), x), "|", sp.diff(x**3, x))
print("第 4 章 積分 :", sp.integrate(x**2, (x, 0, 3)),
"| int_0^inf e^-x dx =", sp.integrate(sp.exp(-x), (x, 0, sp.oo)))
print("第 5 章 Taylor :", sp.series(sp.exp(x), x, 0, 5))
print("第 6 章 級數 :", sp.summation(1/n**2, (n, 1, sp.oo)))
print("第 7 章 多變量 :", sp.diff(x**2*y, x), "|", sp.diff(x**2*y, y),
"| Hessian =", sp.hessian(x**2*y - 3*x, (x, y)).tolist())
print("第 8 章 向量分析 :grad(x^2+y^2+z^2) =",
[sp.simplify(c) for c in sp.Matrix([x**2+y**2+z**2]).jacobian([x, y, z])])
print("第 9 章 ODE :",
sp.dsolve(sp.Eq(sp.diff(sp.Function('u')(x), x), -sp.Function('u')(x))))
print("第 10 章 複變數 :Res(sin(z)/z^2, 0) =", sp.residue(sp.sin(z)/z**2, z, 0),
"| Res(1/(z^2+1), i) =", sp.residue(1/(z**2+1), z, sp.I))
print("第 11 章 傅立葉 :int_0^{2pi} cos^2 x dx =",
sp.integrate(sp.cos(x)**2, (x, 0, 2*sp.pi)))
print("第 12 章 測度/範數 :int_0^pi |sin x| dx =", sp.integrate(sp.Abs(sp.sin(x)), (x, 0, sp.pi)))
print("第 13.1 變分法 :EL 方程與解(見 13.1.2 節)")
print("第 13.2 誤差 :evalf 精度 =", sp.N(sp.pi, 30))
print("第 13.3 自動微分 :sp.diff =", sp.diff(x**3*sp.exp(x), x))
實際輸出:
第 1 章 極限 : 1 | e 的定義 = E
第 3 章 導數 : cos(x) | 3*x**2
第 4 章 積分 : 9 | int_0^inf e^-x dx = 1
第 5 章 Taylor : 1 + x + x**2/2 + x**3/6 + x**4/24 + O(x**5)
第 6 章 級數 : pi**2/6
第 7 章 多變量 : 2*x*y | x**2 | Hessian = [[2*y, 2*x], [2*x, 0]]
第 8 章 向量分析 :grad(x^2+y^2+z^2) = [2*x, 2*y, 2*z]
第 9 章 ODE : Eq(u(x), C1*exp(-x))
第 10 章 複變數 :Res(sin(z)/z^2, 0) = 1 | Res(1/(z^2+1), i) = -I/2
第 11 章 傅立葉 :int_0^{2pi} cos^2 x dx = pi
第 12 章 測度/範數 :int_0^pi |sin x| dx = 2
第 13.1 變分法 :EL 方程與解(見 13.1.2 節)
第 13.2 誤差 :evalf 精度 = 3.14159265358979323846264338328
第 13.3 自動微分 :sp.diff = x**3*exp(x) + 3*x**2*exp(x)
注意第 9 章的 Eq(u(x), C1*exp(-x)) :dsolve 回傳的是方程物件,不是解——這是 SymPy 的風格:它把「解」表達成一個可以繼續操作的物件,而不是一個字串。
13.4.5 回到阿基米德:把故事畫上句點#
現在讓我們回到全書的起點。第 1 章講的是阿基米德的窮竭法——用內接正多邊形逼近圓,並且證明圓的面積被夾在兩個有理數之間。
兩千兩百五十年後,我們用 SymPy 重新做一次這件事:
import numpy as np
import sympy as sp
# ============================================================
# 阿基米德方法(第 1 章):用內接正多邊形逼近圓
# ============================================================
def area_polygon(n):
"""單位圓內接正 n 邊形的面積(半徑 = 1)。"""
return 0.5 * n * np.sin(2 * np.pi / n)
print("=== 阿基米德方法:內接正多邊形逼近單位圓 ===")
print(f"{'邊數':>8} {'面積':>20} {'誤差':>14} {'誤差倍數':>10}")
prev = None
n = 6
for i in range(9):
A = area_polygon(n)
err = np.pi - A
ratio = (prev / err) if prev else float('nan')
print(f"{n:>8} {A:>20.15f} {err:>14.6e} {ratio:>10.2f}")
prev = err
n *= 2
print()
print("SymPy 給出的精確答案:int_0^1 sqrt(1-x^2) dx =",
sp.integrate(sp.sqrt(1 - sp.Symbol('x')**2), (sp.Symbol('x'), 0, 1)))
print("即 pi/4 =", sp.N(sp.pi/4, 20))
print("圓面積 pi =", sp.N(sp.pi, 20))
# ============================================================
# 阿基米德著名的 pi 上下界:223/71 < pi < 22/7
# ============================================================
lo = sp.Rational(223, 71)
hi = sp.Rational(22, 7)
print()
print("阿基米德給出的界:223/71 < pi < 22/7")
print(" 223/71 =", sp.N(lo, 20), " 22/7 =", sp.N(hi, 20))
print(" pi =", sp.N(sp.pi, 20))
print(" 上下界確實夾住 pi :", bool(lo < sp.pi < hi))
print(" 下界比 pi 小 :", sp.N(sp.pi - lo, 20))
print(" 上界比 pi 大 :", sp.N(hi - sp.pi, 20))
實際輸出:
=== 阿基米德方法:內接正多邊形逼近單位圓 ===
邊數 面積 誤差 誤差倍數
6 2.598076211353316 5.435164e-01 nan
12 3.000000000000000 1.415927e-01 3.84
24 3.105828541230249 3.576411e-02 3.96
48 3.132628613281238 8.964040e-03 3.99
96 3.139350203046867 2.242451e-03 4.00
192 3.141031950890509 5.607027e-04 4.00
384 3.141452472285462 1.401813e-04 4.00
768 3.141557607911857 3.504568e-05 4.00
1536 3.141583892148318 8.761441e-06 4.00
SymPy 給出的精確答案:int_0^1 sqrt(1-x^2) dx = pi/4
即 pi/4 = 0.78539816339744830962
圓面積 pi = 3.1415926535897932385
阿基米德給出的界:223/71 < pi < 22/7
223/71 = 3.1408450704225352113 22/7 = 3.1428571428571428571
pi = 3.1415926535897932385
上下界確實夾住 pi : True
下界比 pi 小 : 0.00074758316725802719504
上界比 pi 大 : 0.0012644892673496186802
「誤差倍數」那一欄是本節最重要的一行:每一邊加倍,誤差就乘以約 4 。這不是偶然,而是阿基米德《方法》第九卷命題 3 的內容——他證明了:
這正是「 \(O(n^{-2})\) 收斂率」的古老版本,是全書第一次出現的「誤差分析」(第 13.2 節的主題)。
而 SymPy 給出的答案是 \(\frac{\pi}{4}\) ** ——一個封閉形式,誤差為零**。
這就是整本書的縮影:
| 阿基米德(公元前 250 年) | SymPy(今日) | |
|---|---|---|
| 輸入 | 圓 | 表達式 \(\sqrt{1-x^2}\) |
| 方法 | 內接正多邊形 | 符號積分 + 模式匹配 |
| 輸出 | \(\frac{223}{71} < \pi < \frac{22}{7}\) | \(\frac{\pi}{4}\) |
| 誤差 | \(\sim 10^{-3}\) | \(0\) (封閉形式) |
| 方法論 | 可證的逼近 | 可驗證的代數 |
但兩者共享同一個精神:
不給出無法保證的答案。 阿基米德給的是「夾住 \(\pi\) 的兩個有理數」——他的答案自帶誤差界。SymPy 給的是 \(\frac{\pi}{4}\) ——一個可以再往下精確化的精確物件;當它算不出來時,它回傳未求值的
Integral物件或AccumBounds(-1, 1),而不是編一個數字。
阿基米德的《方法》第一句話是「給出機械方法用於計算的前提,以便計算某些數學命題的平均值」。這正是今天 SymPy 的自我描述。 兩千兩百五十年後,希臘人給出方法論,電腦給出執行力。
最後,讓我們回到《方法》第七卷的一句話(大意的現代翻譯):
「我習慣於先把結論的證明給出來,因為方法本身才是真正有價值的那部分。」
這句話是整本書的綱領。SymPy 給你的不是答案,而是方法。
13.4.6 延伸閱讀#
歷史與哲學#
- Tom M. Apostol, Calculus: A Complete Course —— 從阿基米德寫到 20 世紀,附大量原始文獻翻譯。「Calculus: A Complete Course」的序言本身就是最好的分析學史入門。
- Duncan Grattan-Guinness, The Foundations of Analysis —— 從 Bolzano 到 Hilbert 對實數 foundations 的處理。想要一份嚴謹的分析學編年史,這是首選。
- ** Judith Grabiner, A History of the Calculus of Variations** —— 變分法的完整歷史(Euler、Lagrange、Jacobi、Weierstrass、Lebesgue、Hilbert)。13.1 節的骨架來自這裡。
- Jean Dieudonné, History of Algebraic and Differential Topology —— 為什麼 19 世紀的數學家要「嚴格化」。
- David M. Bressoud, Proofs and Confirmations —— 關於「計算機輔助證明」最好的科普書,討論了「被檢查過的證明」是否仍然是證明。
- Reviel Netz, The Archaeology of Early Mathematics —— 巴比倫、埃及、希臘的實際計算方法,讀完會對「抽象的數學」有完全不同的理解。
嚴格化與測度論#
- Terence Tao, Analysis I, II —— 從 \(\mathbb{Q}\) 到 Lebesgue 測度的現代嚴格敘述。Tao 的《分析導論》是「嚴格化」這條線的教科書級答案。
- Elie Cartan, Differential Calculus —— 變分法與微分方程的漂亮幾何處理。
- I. M. Gelfand, S. V. Fomin, Calculus of Variations —— 變分法的標準參考書。
- Jost, Postmodern Analysis —— 無窮維的視角。
- Rudin, Real and Complex Analysis / Measure Theory —— 硬核路線的標準參考。
數值分析#
- John L. Higham, Accuracy and Stability of Numerical Algorithms(第 2 版)—— Wilkinson 傳統的現代續篇。條件數、後向穩定性、IEEE 754 標準,本書的主要參考。
- Trefethen & Bau, Numerical Linear Algebra —— 數值線性代數,GMRES、Lanczos、奇異值分解的科普級處理。
- David Kincaid & Ward Cheney, Numerical Analysis: Mathematics of Scientific Computing —— 數值分析的教科書,誤差分析部分特別好。
- N. J. Higham, Handbook of Floating-Point Arithmetic —— 浮點運算的權威參考。
- B. W. Silverman, Numerical Methods for Scientists and Engineers —— 面向應用。
最佳化與機器學習#
- J. Nocedal & S. Wright, Numerical Optimization —— 最佳化的標準教科書。梯度下降、牛頓法、線搜索、KKT 條件、投影法。
- D. P. Bertsekas, Nonlinear Programming —— 更深入、更嚴謹。
- **R. Fletcher, *Reeves, Curly:變分法與最優化的橋樑。
- Jimmy Lei Ba, Adam: A Method for Stochastic Optimization —— 自適應方法。
- Herbert Robbins & Sutton Monro, "A Stochastic Approximation Method" (1951) —— 只有 15 頁,但它是所有 SGD 論文的祖先。
- Rumelhart, Hinton & Williams, "Learning representations by back-propagating errors" (1986) —— 反向模式的原始論文。
- David Duvenaud, Designing Machine Learning Systems / Griewank & Walther, Evaluating Derivatives —— AD 的系統性處理。Griewank & Walther 是「AD 這門學科」的教科書。
SymPy 與計算機代數#
- SymPy 官方文件:https://docs.sympy.org/latest/ —— 必讀,尤其是 "Gotchas" 那一頁。
- SymPy 社群:https://community.sympy.org —— 提問與搜尋答案的地方。
- Ondřej Karlík, "The SymPy Book" —— 免費的互動式教材。
- Markus Kaltenbach, Computer Algebra and Symbolic Computation —— CAS 的原理。
- Joel S. Cohen, Computer Algebra and Symbolic Computation: A Practical Guide —— 偏應用。
- Michael B. W. SymPy 與 Sage 官方教學 —— 如果你想從 SymPy 走到 SageMath(符號 + 數值 + 繪圖 + 文檔一體化)。
13.4.7 SymPy API 對照表#
這張表把全書用過的 SymPy 工具按「用途」整理,並標上對應章節。
極限與連續(第 1、2 章)#
| API | 用途 | 例子 |
|---|---|---|
sp.limit(expr, z, z0, dir=) | 計算極限 | sp.limit(sp.sin(x)/x, x, 0) |
sp.Limit(expr, z, z0) / .doit() | 未求值的極限物件 | sp.Limit(f(x), x, 0).doit() |
sp.limit_seq(expr, n) | 數列極限 | sp.limit_seq((1+1/n)**n, n) |
sp.ask(sp.Q.positive(...)) | 嘗試判定符號 | 常常回傳 None |
微分(第 3、7 章)#
| API | 用途 | 例子 |
|---|---|---|
sp.diff(f, x) / sp.diff(f, x, k) | 第 \(k\) 階導數 | sp.diff(x**3, x, 2) |
sp.Derivative(f, x) / .doit() | 未求值的導數物件 | sp.Derivative(u(x), x) |
sp.Matrix.jacobian(vars) | 雅可比矩陣 | sp.Matrix([f]).jacobian([x, y]) |
sp.hessian(f, (x, y)) | Hessian 矩陣 | sp.hessian(f, (x, y)) |
sp.gradient(f, (x, y)) | 梯度向量 | sp.gradient(x**2*y, (x, y)) |
積分(第 4 章)#
| API | 用途 | 例子 |
|---|---|---|
sp.integrate(f, (x, a, b)) | 定積分 | sp.integrate(x**2, (x, 0, 3)) |
sp.Integral(f, (x, a, b)) / .doit() | 未求值的積分物件 | sp.Integrate(f(x), (x, 0, 1)) |
sp.integrate(f, x) | 不定積分 | sp.integrate(sp.exp(x), x) |
級數(第 5、6 章)#
| API | 用途 | 例子 |
|---|---|---|
sp.series(f, x, x0, n, dir=) | Taylor/Laurent 展開 | sp.series(sp.exp(x), x, 0, 5) |
sp.summation(f, (n, a, b)) | 級數求和 | sp.summation(1/n**2, (n, 1, sp.oo)) |
sp.Sum(f, (n, a, b)) / .doit() | 未求值的級數物件 | sp.Sum(1/n**2, (n, 1, sp.oo)) |
sp.Product(f, (n, a, b)) / .doit() | 連乘 | sp.Product((n+1)/n, (n, 1, 5)) |
sp.sequence(f, (n, 0, oo)) | 可索引序列 | sp.sequence(n**2, (n, 0, sp.oo)) |
sp.expand_log(f) | 對數代數化簡 | sp.expand_log(sp.log(a)) |
方程(第 3、9、13 章)#
| API | 用途 | 例子 |
|---|---|---|
sp.solve(eq, x) | 解方程 | sp.solve(sp.Eq(x**2-1, 0), x) |
sp.solve(eqs, (x, y)) | 解方程組 | sp.solve([f_x, f_y], (x, y)) |
sp.dsolve(ode, f(x)) | 解微分方程 | sp.dsolve(sp.Eq(f'(x), f(x))) |
sp.pdsolve(pde, f(x, y)) | 解偏微分方程 | sp.pdsolve(Eq(f.diff(x), f), f) |
sp.nsolve(expr, x0) | 數值求解 | sp.nsolve(x**3-2*x-5, 2) |
sp.lambdify(vars, expr) | 編譯成可執行函數 | sp.lambdify(x, sp.diff(f, x)) |
最佳化(第 13 章)#
| API | 用途 | 例子 |
|---|---|---|
sp.minimum(f, x) | 全域最小值 | sp.minimum(x**2+1, x) |
sp.maximum(f, x) | 全域最大值 | sp.maximum(-x**2, x) |
sp.det(M) | 行列式(Sylvester 準則) | sp.det(H[:2, :2]) |
sp.Matrix | 矩陣運算 | sp.Matrix([[1, 2], [3, 4]]) |
複變數(第 10 章)#
| API | 用途 | 例子 |
|---|---|---|
sp.residue(f, z, z0) | 留數 | sp.residue(1/(z**2+1), z, sp.I) |
sp.expand_complex(f) | 複數代數化簡 | sp.expand_complex(z**2) |
sp.conjugate(f) | 複共軛 | sp.conjugate(z) |
sp.integrate_holomorphic 等 | 留數定理的應用 | 見 10.2 節 |
數值與精度(第 13.2 章)#
| API | 用途 | 例子 |
|---|---|---|
sp.N(expr, n) | \(n\) 位有效數字的數值 | sp.N(sp.pi, 50) |
sp.Rational(p, q) | 精確有理數 | sp.Rational(1, 3) |
sp.Integer(n) | 精確整數 | sp.Integer(10**30) |
sp.Float(x, n) | 指定精度的浮點 | sp.Float(1)/3 |
sp.nsimplify(f) | 從浮點反推符號 | sp.nsimplify(0.3333333333) |
sp.evalf(n) | 物件的高精度求值 | sp.pi.evalf(30) |
sp.Rational + sp.limit | 任意精度 | sp.limit(...) 於有理數 |
自動微分與變分法(第 13.1、13.3 節)#
| API | 用途 | 例子 |
|---|---|---|
sp.diff(F, y) | 對「函數物件」求偏導 | sp.diff(F, yfun) |
sp.Function('u')(x) | 建立未定義函數 | u = sp.Function('u')(x) |
sp.total_diff / sp.Derivative | 全導數 | sp.Derivative(F, x) |
sp.lambdify | 符號 → 數值函數 | 見 13.3.6 節 |
13.4.8 全書結語#
讓我們回到阿基米德《方法》的框架,做最後一次對照。
第 1 章的阿基米德面對的是這樣的問題:圓的面積是多少?他知道答案「等於半徑平方乘 \(\pi\) 」,但 \(\pi\) 本身沒有封閉形式,他不能只說「答案是 \(\pi\) 」——他必須證明 \(\pi\) 存在、證明它被某個可計算的過程逼近、並給出誤差的界。
SymPy 面對的是同一個問題: \(\int_0^1 \sqrt{1-x^2}\,\mathrm{d}x\) 是多少?它給出 \(\frac{\pi}{4}\) ——但這個答案不是「算」出來的,而是「認出來的」(模式匹配到圓面積公式)。如果沒有這個模式匹配,它會回傳未求值的 Integral 物件。
這正是分析學兩千五百年的真正收穫:
一個問題能不能被回答,取決於我們是否有辦法把「知道答案」變成「可以驗證答案」;而「可以驗證」比「知道」重要得多。
讓我們把全書的兩條主線再說一次:
嚴格化:直覺 → \(\varepsilon\) - \(\delta\) → 測度與範數。它把「看起來對」變成「可以逐條檢查」。
符號化:手算 → 符號替換 → 自動微分。它把「靠腦袋」變成「靠規則」,並且讓規則本身可以被機器執行。
兩條線在今天交會:符號化讓分析學可以自動化,嚴格化讓自動化產生的結果值得信賴。缺任何一條,現代分析學都不成立:
- 只有符號化而沒有嚴格化 → 你得到一台快速產生錯誤答案的機器(這正是 19 世紀末期計算機輔助證明受到質疑的原因);
- 只有嚴格化而沒有符號化 → 你得到一部漂亮但無法規模化應用的理論(這正是 19 世紀末到 20 世紀初分析學的處境)。
而 SymPy 的位置是明確的:它是嚴格化假設之上的符號化工具。它不做嚴格化(那是你的責任),但它盡最大努力保持精確——用 Rational 而不是浮點、用未求值物件而不是假答案、用 AccumBounds 而不是胡說。
最後一句話,献给阿基米德:
公元前 250 年,希臘人給出了方法論——「給出機械方法用於計算的前提,以便計算某些數學命題的平均值」。這句話描述的是《方法》(Peri Methodon)。
今日,電腦給出了執行力。
而當我們用
sp.integrate(sp.sqrt(1 - x**2), (x, 0, 1))打出pi/4的那一刻,我們其實是在做阿基米德做過的事:承認自己無法直接觸摸那個對象,因此給出一個可以往下精確化的表達式,並且——如果它算不出來——老老實實地說「算不出來」。
這就是全書的句點。
習題#
習題 13.4.1(重建時間線)
(a) 為全書的每一章寫一句「這個章節解決了什麼問題」; (b) 找出三個在時間線上「嚴格化」與「符號化」交錯出現的時期,並說明為什麼它們會交錯; (c) 找出兩個「因為有了新的符號工具而使舊問題重新變得可解」的例子(提示: \(\sum\frac{1}{n^2}\) 與 \(\int e^{-x^2}\,\mathrm{d}x\) )。
習題 13.4.2(嚴格化的三階段在一個例子上的對照)
考慮「 \(\lim_{x\to0}\frac{\sin x}{x} = 1\) 」這個命題:
(a) 用阿基米德式的方式說明它(幾何圖像:內接正多邊形的邊長趨近於半徑); (b) 用柯西式的方式寫出 \(\varepsilon\) - \(\delta\) 證明的骨架(回扣第 1.3.6 節); (c) 用測度論式的方式: \(\sin x\) 在 \([0,\pi/2]\) 上連續,所以 \(\frac{\sin x}{x}\) 有最大值 \(\frac{2}{\pi}\) ……說明「連續函數在緊集上取到最值」這個定理(Weierstrass 極值定理)如何把問題變得更結構化; (d) 說明:哪一個版本最容易被機器檢查?
習題 13.4.3(符號化的三階段在同一個問題上)
對 \(\frac{\mathrm{d}}{\mathrm{d}x}\tan x = 1 + \tan^2 x\) 這個恆等式:
(a) 用手算(費馬的增量法或萊布尼茲的微分形式)推導一次; (b) 用符號替換(萊布尼茲記號)寫出推導; (c) 用自動微分(SymPy 或你自己寫的 AD)驗證一次,並說明「自動微分」在這裡省掉了什麼; (d) 用 sp.diff(sp.tan(x), x) 與 \(\sec^2 x = 1 + \tan^2 x\) 對照,說明 SymPy 的選擇恆等式(sec(x)**2 vs 1 + tan(x)**2)是怎麼來的。
習題 13.4.4(阿基米德的界與現代誤差分析)
(a) 驗證阿基米德的方法能給出 \(223/71 < \pi < 22/7\) :用內接/外接正多邊形(邊數由你選),並說明大約需要多少邊才能達到這個精度; (b) 比較 13.4.5 節的「誤差倍數 \(\approx 4\) 」與阿基米德《方法》第九卷的定理(誤差 \(\sim O(n^{-2})\) ),說明它們是同一件事; (c) 說明:為什麼阿基米德的方法不可能給出 \(\pi\) 的封閉形式?(提示:他是用有理數上下界逼近的, \(\pi\) 是無理數) (d) 討論:「給出可驗證的上下界」與「給出封閉形式」,哪一種在科學實踐中更常見?舉例。
習題 13.4.5(寫一封給未來的信)
用 SymPy 為本全書寫一份「給 2126 年的讀者的說明」,要求:
(a) 用一段程式碼展示 SymPy 五年後可能的樣子(就用今天的 API); (b) 列出你認為今天SymPy 最欠缺的三個能力; (c) 針對每個能力,說明要解決它,需要哪一章的那種數學(例如:需要第 12 章的測度論才能做「符號積分的可積性判定」)。
習題 13.4.6(SymPy API 對照表擴充)
為本對照表補上三個你尚未在本書中使用過的 SymPy 功能,每個都:
(a) 說明它屬於哪一章的範疇; (b) 給出一個可執行的例子; (c) 說明它解決了前面哪個章節留下的一個「未解決的問題」。
習題 13.4.7(終極交叉驗證)
選一個你喜歡的定積分 \(I = \int_a^b f(x)\,\mathrm{d}x\) ,用四條獨立的路徑計算它,並把四個答案與誤差一起列表:
- 符號:
sp.integrate的閉閉封閉形式; - 數值:
sp.N或 Romberg/Gauss 求積; - 級數:
sp.series逐項展開後求和; - 蒙特卡洛(如果會寫的話)或數值微分後用 Newton 迭代求原函數的端點值。
然後回答:哪一條路徑最可靠?哪一條最不可靠?為什麼?(提示:考慮病態積分 \(\int_0^1 \frac{\mathrm{d}x}{\sqrt{x}}\) 。)
全書 SymPy API 總覽#
| 章 | 核心工具 |
|---|---|
| 1 | sp.limit sp.Limit sp.limit_seq sp.sequence sp.summation sp.Sum sp.Product sp.series sp.expand_log |
| 2 | sp.limit(帶 dir)sp.ask sp.Q.positive |
| 3 | sp.diff sp.Derivative sp.solve sp.series sp.subs |
| 4 | sp.integrate sp.Integral sp.ode |
| 5 | sp.series sp.expand sp.collect sp.O |
| 6 | sp.summation sp.Sum sp.Product sp.sequence sp.limit_seq |
| 7 | sp.diff sp.Matrix.jacobian sp.hessian sp.gradient sp.integrate |
| 8 | sp.Matrix sp.curl sp.divergence sp.gradient sp.integrate(線積分) |
| 9 | sp.dsolve sp.pdsolve sp.Eq sp.Function |
| 10 | sp.residue sp.expand_complex sp.conjugate sp.integrate(複數路徑) |
| 11 | sp.integrate sp.series sp.fourier_series sp.FourierSeries |
| 12 | sp.integrate sp.Limit sp.Sum sp.AccumBounds sp.Matrix(範數) |
| 13 | sp.diff sp.solve sp.minimum sp.maximum sp.hessian sp.det sp.lambdify sp.N sp.Rational sp.nsimplify sp.nsolve |
全書完。