跳轉到

13.1 變分法、梯度下降與最佳化#

學習動機#

前面十二章,我們處理的都是固定的對象:給定一個函數 \(f\) ,求它的極限、導數、積分、級數。但有另一整類問題是這樣的:

給定一個函數空間,在其中找出使某個「泛函」 \(J[y]\) 取到極值的函數。

泛函(functional)是把函數空間映到數的映射。最經典的例子:

  • 最短曲線問題: \(J[y] = \int_{a}^{b} \sqrt{1 + y'(x)^2}\,\mathrm{d}x\) 在給定端點條件下取最小值(答案是直線);
  • 懸鏈線問題: \(J[y] = \int_{a}^{b} y(x)\sqrt{1 + y'(x)^2}\,\mathrm{d}x\) (均勻鏈條的重力位能)取最小值(答案是懸鏈線);
  • brachistochrone 問題: \(J[y] = \int \frac{\sqrt{1+y'^2}}{\sqrt{y}}\,\mathrm{d}x\) (滑落的總時間)取最小值(答案是擺線);
  • 最小作用量原理(Hamilton 1834): \(J[q] = \int L(q, \dot q, t)\,\mathrm{d}t\) 在真實運動軌跡上取極值。

這些問題在十八、十九世紀是物理與幾何的真正難題,因為人沒有「對函數求導」的工具。第 3 章我們學過 \(\frac{\mathrm{d}f}{\mathrm{d}x}\) ,它是對數值求導;現在我們需要一個新的算子——對函數求導,即變分(variation)。

本節的任務有四個:

  1. 弄清楚問題的來源:最小作用量原理、最短曲線/等周問題,是怎樣從物理中長出來的;
  2. 建立 Euler–Lagrange 方程:Euler 在 1752 年如何從力學導出它,Jacobi 在 1845 年如何用「最後乘法因子」把它推到更一般的形式;
  3. 看見嚴格化:Legendre 與 Weierstrass 如何把「必要條件」升級為真正的定理;
  4. 連到現代:變分法與今天的梯度下降、隨機梯度下降(Robbins–Monro 1951)、機器學習是同一件事的不同外衣。

最後我們要用 SymPy 實際推出 Euler–Lagrange 方程,並且用數值梯度下降離散化同一個泛函,看兩條路是否收斂到同一個答案。這是全書唯一一次「歷史 → 符號 → 數值」三路合流的示範。


13.1.1 問題的來源:物理如何逼出「對函數求導」#

(a) 最小作用量原理#

牛頓在 1687 年的《自然哲學的數學原理》裡給出的力學定律是「加速度正比於力」。這個表述好用,但有一個缺陷:它是一階微分方程,給定初始位置與速度就能解出運動。但物理學家常常想知道的是相反的問題:已知運動軌跡,問「力是什麼?」

Hamilton 在 1834 年(更早的 Maupertuis 在 1744 年已提出雛形)給出了激動人心的答案:

最小作用量原理:物體在兩個固定時刻 \(t_1, t_2\) 之間的實際軌跡 \(q(t)\) ,使作用量

\[ S[q] = \int_{t_1}^{t_2} L\left(q, \frac{\mathrm{d}q}{\mathrm{d}t}, t\right)\,\mathrm{d}t \]

取到駐值(stationary value)。

這是一個驚人的「反演」:牛頓的力 \(F = -\frac{\partial L}{\partial q}\) 只是 Euler–Lagrange 方程在 \(t\) 依賴顯式時的一個特例。

(b) 幾何的來源:最短曲線與等周問題#

幾何這邊的問題同樣自然:

  • Dido 問題(公元前 1 世紀,狄多諾):給定兩端點,求連結它們的最短曲線。答案:直線。
  • 等周問題(isoperimetric problem):在周長固定的所有平面封閉曲線中,哪一條面積最大?答案:圓形。這是「帶約束的泛函極值」的原型。
  • Dido 的「等周曲線」:對弧長 \(s\) 而言, \(J[y] = \int y\,\mathrm{d}s\) 是面積;「弧長固定」就是一個積分等式約束。
  • 最小曲面(soap film):Plateau 問題——給定邊界,撐開的肥皂膜是什麼?這導向「面積泛函」與 1880 年代 Weierstrass 的嚴格化。

(c) 一個共同的結構#

把上面所有問題寫成一個模板:

\[ J[y] = \int_{a}^{b} F\left(x, y(x), y'(x)\right)\,\mathrm{d}x \]

** \(J\) 的自變數是函數 \(y\) 本身,不是數。** 因此「求導」意味著:

\[ \text{泛函導數}[y] \quad := \quad \lim_{\varepsilon \to 0} \frac{J[y + \varepsilon \eta] - J[y]}{\varepsilon} \]

其中 \(\eta\) 是任意零端點的光滑函數(稱為變分 \(\delta y\) )。

一階展開(由微積分基本定理與分部積分)給出:

\[ J[y + \varepsilon\eta] = J[y] + \varepsilon \int_a^b \left(\frac{\partial F}{\partial y}\eta + \frac{\partial F}{\partial y'}\eta'\right)\mathrm{d}x + O(\varepsilon^2) \]

對 \(\eta'\) 分部積分( \(\eta(a) = \eta(b) = 0\) ):

\[ \int_a^b \frac{\partial F}{\partial y'}\eta'\,\mathrm{d}x = -\int_a^b \frac{\mathrm{d}}{\mathrm{d}x}\left(\frac{\partial F}{\partial y'}\right)\eta\,\mathrm{d}x \]

所以泛函導數的「核心部分」是

\[ \frac{\partial F}{\partial y} - \frac{\mathrm{d}}{\mathrm{d}x}\left(\frac{\partial F}{\partial y'}\right) \]

這就是 Euler–Lagrange 方程的來源。


13.1.2 Euler 的力學(1752)與 Euler–Lagrange 方程#

Leonhard Euler 在 1752 年到 1760 年代處理「用積分求微分方程」的問題時,系統地得到了這條方程。今天我們把它寫成:

\[ \boxed{\ \frac{\partial F}{\partial y} - \frac{\mathrm{d}}{\mathrm{d}x}\left(\frac{\partial F}{\partial y'}\right) = 0\ } \]

其中「 \(\mathrm{d}/\mathrm{d}x\) 」是全導數: \(\frac{\partial F}{\partial y'}\) 一般是 \(x, y, y'\) 的函數,所以要沿著曲線求導:

\[ \frac{\mathrm{d}}{\mathrm{d}x} G(x, y, y') = \frac{\partial G}{\partial x} + \frac{\partial G}{\partial y}\,y' + \frac{\partial G}{\partial y'}\,y'' \]

關鍵對照:這與第 3 章的導數定義是同一個「極限 + 差商」結構,只是被求導的對象從「數」升級為「函數」。

第 3 章 本節
被求導的對象 數 \(x\) 函數 \(y(x)\)
增量 \(h\) (實數) \(\varepsilon\eta(x)\) (函數)
極限 \(\lim_{h\to 0}\frac{f(x+h)-f(x)}{h}\) \(\lim_{\varepsilon\to 0}\frac{J[y+\varepsilon\eta]-J[y]}{\varepsilon}\)
結果 導數 \(f'(x)\) Euler–Lagrange 方程

SymPy 怎麼處理「對函數求導」?

SymPy 沒有直接的 variation 函式,但它有兩件工具可以拼出來:

  1. 把 \(y\) 視為未定義函數 sp.Function('y')(x),則 sp.diff(F, y) 就是 \(\frac{\partial F}{\partial y}\) (偏導數, \(y\) 被視為獨立座標),sp.diff(F, yp) 就是 \(\frac{\partial F}{\partial y'}\) ;
  2. 全導數要手動展開成 \(\frac{\partial G}{\partial x} + \frac{\partial G}{\partial y}y' + \frac{\partial G}{\partial y'}y''\) —— 或者,直接對含 Derivative(y(x), x) 的表達式呼叫 sp.diff(..., x) ,SymPy 會自動做鏈鎖法則,得出同樣的結果。

下面這段程式碼把整條推導寫成可執行程序,並驗證兩個具體解(懸鏈線與擺線)。

import sympy as sp

x = sp.symbols('x', real=True)
y_s, yp, ypp = sp.symbols('y yp ypp', real=True)


def euler_lagrange(F):
    """回傳 EL 方程的左邊 L = F_y - d/dx(F_{y'}),展開成 y, yp, ypp 的形式。

    F 依賴三個符號 x, y_s(= y(x)), yp(= y'(x))。
    全導數 d/dx G = G_x + G_y * y' + G_{y'} * y''。
    """
    Fy = sp.diff(F, y_s)
    Fyp = sp.diff(F, yp)
    dFyp_dx = (sp.diff(Fyp, x)
               + sp.diff(Fyp, y_s) * yp
               + sp.diff(Fyp, yp) * ypp)
    return sp.simplify(Fy - dFyp_dx)


def verify(el, ysol):
    """把候選解 ysol(x) 及其一、二階導數代入 EL 方程,結果應為 0。"""
    return sp.simplify(el.subs({y_s: ysol,
                                yp: sp.diff(ysol, x),
                                ypp: sp.diff(ysol, x, 2)}))


a, b = sp.symbols('a b', positive=True)

# --- 案例 1:最短曲線(Dido 問題),F = sqrt(1 + y'^2) ---
F1 = sp.sqrt(1 + yp**2)
el1 = euler_lagrange(F1)
print("案例 1  最短曲線:F =", F1)
print("  EL 方程左邊 =", el1)
m, c = sp.symbols('m c', real=True)
print("  代回直線 y = m*x + c :", verify(el1, m * x + c))

# --- 案例 2:懸鏈線(均勻鏈條的重力位能),F = y*sqrt(1 + y'^2) ---
F2 = y_s * sp.sqrt(1 + yp**2)
el2 = euler_lagrange(F2)
print()
print("案例 2  懸鏈線:F =", F2)
print("  EL 方程左邊 =", el2)
print("  代回懸鏈線 y = a*cosh((x-b)/a) :", verify(el2, a * sp.cosh((x - b) / a)))

# --- 案例 3:brachistochrone(最短下降時間),F = sqrt(1 + y'^2)/sqrt(y) ---
F3 = sp.sqrt(1 + yp**2) / sp.sqrt(y_s)
el3 = euler_lagrange(F3)
print()
print("案例 3  brachistochrone:F =", F3)
print("  EL 方程左邊 =", el3)

# Beltrami 恆等式:沿極值曲線 F - y'*F_{y'} = 常數(省掉 x 的依賴)
beltrami = sp.simplify(F3 - yp * sp.diff(F3, yp))
print("  Beltrami 量 F - y'F_{y'} =", beltrami)

# 擺線 x = a(t - sin t), y = a(1 - cos t):以參數形式驗證 EL 殘差為 0
t = sp.symbols('t', positive=True)
xs_p = a * (t - sp.sin(t))
ys_p = a * (1 - sp.cos(t))
yp_p = sp.diff(ys_p, t) / sp.diff(xs_p, t)
ypp_p = sp.diff(yp_p, t) / sp.diff(xs_p, t)
print("  沿擺線的 EL 殘差 =", sp.simplify(el3.subs({y_s: ys_p, yp: yp_p, ypp: ypp_p})))

實際輸出:

案例 1  最短曲線:F = sqrt(yp**2 + 1)
  EL 方程左邊 = -ypp/(yp**2 + 1)**(3/2)
  代回直線 y = m*x + c : 0

案例 2  懸鏈線:F = y*sqrt(yp**2 + 1)
  EL 方程左邊 = (-y*ypp + yp**2 + 1)/(yp**2 + 1)**(3/2)
  代回懸鏈線 y = a*cosh((x-b)/a) : 0

案例 3  brachistochrone:F = sqrt(yp**2 + 1)/sqrt(y)
  EL 方程左邊 = (-2*y*ypp - yp**2 - 1)/(2*y**(3/2)*(yp**2 + 1)**(3/2))
  Beltrami 量 F - y'F_{y'} = 1/(sqrt(y)*sqrt(yp**2 + 1))
  沿擺線的 EL 殘差 = 0

逐行解讀:

  • 案例 1 的 EL 方程是 \(\frac{y''}{(1+y'^2)^{3/2}} = 0\) ,即 \(y'' = 0\) ——弧線是直線。這個結論太簡單到讓人懷疑,但想想看:兩點之間的直線當然最短。真正的趣味在於下一個案例。
  • 案例 2 的 EL 方程是 \(y\,y'' = 1 + y'^2\) ,這正是懸鏈線的微分方程。代回 \(y = a\cosh\frac{x-b}{a}\) 得 ** \(0\) ** ,證實了它是極值曲線。
  • 案例 3 的 EL 方程是 \(2y\,y'' + y'^2 + 1 = 0\) ,即 brachistochrone 方程。代回擺線參數式,殘差為 ** \(0\) ** 。
  • Beltrami 量:若 \(F\) 不顯含 \(x\) ,則沿極值曲線 \(F - y'F_{y'} = \text{常數}\) 。這是化簡二階 EL 方程為一階的捷徑,也是今天最佳化理論中「對偶性」的思想雛形。

注意這裡的「嚴格性」層次:我們用 SymPy 證明了「若 \(y\) 滿足 EL 方程,則 \(J\) 的泛函導數為零」,但沒有證明「滿足 EL 方程 ⟹ 一定是極值」(這需要二階變分與 Weierstrass 的充分條件,第 13.1.4 節會說明)。符號運算給出的是必要條件,不是充分條件。


13.1.3 Jacobi 的「最後乘法因子」(1845)#

Euler 的方法有個明顯的侷限:它直接處理 \(\int F(x,y,y')\,\mathrm{d}x\) 這種形式。但很多物理問題裡,作用量裡顯含時間:

\[ S = \int_{t_1}^{t_2} L(q, \dot q, t)\,\mathrm{d}t \]

這時 EL 方程變成

\[ \frac{\partial L}{\partial q} - \frac{\mathrm{d}}{\mathrm{d}t}\left(\frac{\partial L}{\partial \dot q}\right) = 0 \]

(外加一個 \(-\frac{\mathrm{d}L}{\mathrm{d}t}\) 的項,若 \(L\) 顯含 \(t\) )。Jacobi 在 1845 年(正是在本節的標題年份)找到了把所有這些情形統一起來的方法。

Jacobi 的洞察是這樣的:

不要急著找 \(\delta y\) ,而是先找一個乘子 \(M(x, y, y')\) 使得

\[ M \cdot \delta y \]

能被「湊成」 \(\delta(\text{某個表達式})\) 的形式,而這個表達式對 \(y\) 是線性的。

這個 \(M\) 就是他稱為的 「最後乘法因子」(the last multiplier) 。做法是:

  1. 從 \(\delta y\) 的任意變分出發,構造 \(\delta(\text{邊界項})\) ,使得
\[ \delta\left(\text{邊界項}\right) = M\,\delta y + (\text{只含 } \delta y' \text{ 的項}) \]
  1. 把後者再「積分掉」一次,得到
\[ \delta\left(\text{某個 }\Phi(x, y, y')\right) = M \cdot \left[\frac{\partial F}{\partial y} - \frac{\mathrm{d}}{\mathrm{d}x}\frac{\partial F}{\partial y'}\right]\delta y + \text{邊界項} \]
  1. 令括號中的 EL 表達式為零(因為 \(\delta y\) 任意)。

這個方法的力量在於它是「模」的(modular):它說明 EL 方程是「作用量的某個函數 \(\Phi\) 的一階變分等於零」這個更一般原理的特例。

現代的對應:今天在最佳化理論裡,這個思想叫做 Lagrange 乘子法(第 13.3 節會用 SymPy 實作)與對偶性。Jacobi 1845 年的「最後乘法因子」,正是 Lagrange 1784 年「帶約束極值」的對偶版本。

順帶一提,Jacobi 對變分法的貢獻還包括 Jacobi 場(Jacobi fields)與 Jacobi 充分條件:他證明了二階變分的正定性可以保證某個解是嚴格極小值,而不只是駐值。這是「必要條件」升級為「充分條件」的第一步。


13.1.4 Legendre 與 Weierstrass 的嚴格化#

十九世紀中葉,Euler–Lagrange 方程被大量使用,但它的地位一直很含糊:它是必要條件?充分條件?兩者皆是?在不同的問題裡答案不同。

年份 人物 貢獻
1786 Legendre 引入二階變分 \(\delta^2 J\) ,並區分「極小」與「極大」;提出 Legendre 條件 \(\frac{\partial^2 F}{\partial y'^2} \ge 0\)
1834 Hamilton 最小作用量原理的完整陳述,並把變分法用於力學
1845 Jacobi 「最後乘法因子」;Jacobi 充分條件(二階變分正定 ⟹ 嚴格極小)
1850–1860 Weierstrass Weierstrass 必要條件:極值曲線必須滿足一個關於「速度」的嚴格不等式;並給出 Hilbert 過剩變分的準備
1900s Hilbert Hilbert 的第 20 問題:證明把變分問題正則化後,極值曲線是分段解析的(Kneser 1902 給出部分答案)

Weierstrass 的必要條件尤其深刻。它說:若 \(y\) 是極值曲線,則對任意可微的變分 \(\eta\) 與任意 \(\varepsilon\) ,都必須滿足

\[ \frac{\partial F}{\partial y}\eta + \frac{\partial F}{\partial y'}\left(\eta' - \varepsilon\right) - \left(\frac{\partial F}{\partial y'}\right)' \varepsilon \ \ge\ 0 \]

其中 \(\varepsilon\) 是一個「速度」參數。這個不等式比 EL 方程強:EL 方程只是令 \(\varepsilon = 0\) 的特例。Weierstrass 條件的直觀意義是:在極值曲線的每一點,力的方向必須「朝向可行方向」,而不只是「平衡」。

Legendre 條件 \(\frac{\partial^2 F}{\partial y'^2} \ge 0\) 則是一個局部凸性判準:它保證在端點固定、 \(\eta\) 足夠小時, \(J\) 的變分是凸的。

這裡有一個重要的「計算機時代」註腳:所有這些條件,現在都可以用 SymPy 半自動檢查——因為它們全是偏導數與全導數的代數式。我們在 13.1.2 節的 verify() 函式正是這個思路的最小版本。


13.1.5 從變分法到梯度下降:泛函導數就是「無窮維梯度」#

現在來做本節最重要的概念轉換。回到第 7 章(多變量微積分)的梯度:

對有限維的 \(f : \mathbb{R}^n \to \mathbb{R}\) ,梯度 \(\nabla f\) 的定義是

\[ \nabla f(\mathbf{x}) = \left(\frac{\partial f}{\partial x_1}, \dots, \frac{\partial f}{\partial x_n}\right), \qquad f(\mathbf{x} + h\mathbf{v}) \approx f(\mathbf{x}) + \langle \nabla f, \mathbf{v} \rangle \]

對泛函 \(J : C[a,b] \to \mathbb{R}\) ,完全類比:

\[ \delta J[y](\eta) = \lim_{\varepsilon \to 0} \frac{J[y+\varepsilon\eta] - J[y]}{\varepsilon}, \qquad \delta J[y](\eta) = \int_a^b \left(\frac{\partial F}{\partial y} - \frac{\mathrm{d}}{\mathrm{d}x}\frac{\partial F}{\partial y'}\right)\eta\,\mathrm{d}x \]

這是同一件事,只是「內積」從 \(\mathbb{R}^n\) 上的點積換成了 \(L^2\) 空間上的積分。

於是:

有限維最佳化 變分法
\(f(\mathbf{x})\) \(J[y]\)
梯度 \(\nabla f\) 泛函導數 \(\delta J / \delta y\)
梯度下降 \(\mathbf{x} \leftarrow \mathbf{x} - \alpha\nabla f\) 梯度下降 \(y \leftarrow y - \alpha\,\delta J/\delta y\)
Hessian 正定 ⟹ 唯一極小 二階變分正定 ⟹ 嚴格極小(Legendre–Jacobi 條件)
拉格朗日乘數 帶約束的變分(Erdmann 角點條件)

「梯度下降」在有限維與無窮維之間沒有本質差別。 差別只在於:

  1. 無窮維的「梯度下降步」是一個函數,不是向量;
  2. 要把無窮維問題變成可計算的,必須離散化——而離散化正是 13.3 節「符號最佳化」與現代計算機「數值最佳化」的會合處。

這裡我們做一個關鍵的示範:把泛函 \(J[y] = \int_0^1 \sqrt{1+y'^2}\,\mathrm{d}x\) 離散化成 \(N\) 個變數的有限維問題,然後直接用梯度下降求解,並與 SymPy 算出的 EL 方程解對照。

import numpy as np
import sympy as sp

# ============================================================
# (a) 把泛函離散化:J[y] = int_0^L sqrt(1 + y'^2) dx(最短曲線)
# ============================================================
L, N = 1.0, 21
xs = np.linspace(0.0, L, N)
h = L / (N - 1)

def J_discrete(y):
    """梯形/矩形離散化:J ≈ sum sqrt(1 + y'^2) * h。"""
    d = np.diff(y) / h
    return float(np.sum(np.sqrt(1.0 + d**2)) * h)


def grad_discrete(y):
    """離散泛函對內部節點的梯度。

    dJ/dy_j = d_{j-1}/sqrt(1+d_{j-1}^2) - d_j/sqrt(1+d_j^2)
    (端點固定,故只有內部節點有梯度。)
    """
    d = np.diff(y) / h
    g = np.zeros_like(y)
    g[1:-1] = d[:-1] / np.sqrt(1 + d[:-1]**2) - d[1:] / np.sqrt(1 + d[1:]**2)
    return g


# 先用中心差分「獨立」核對解析梯度
rng = np.random.default_rng(7)
yt = rng.normal(size=N) * 0.1
yt[0] = yt[-1] = 0.0
ga = grad_discrete(yt)
eps = 1e-6
fd = np.zeros_like(yt)
for j in range(1, N - 1):
    yp_ = yt.copy(); yp_[j] += eps
    ym_ = yt.copy(); ym_[j] -= eps
    fd[j] = (J_discrete(yp_) - J_discrete(ym_)) / (2 * eps)
print("解析梯度與中心差分梯度最大偏差 =", np.max(np.abs(ga[1:-1] - fd[1:-1])))

# 帶回溯線搜索的梯度下降
y = 0.3 * np.sin(3 * np.pi * xs) + 0.2
y[0] = y[-1] = 0.0
print("初始 J =", J_discrete(y))
for it in range(300):
    g = grad_discrete(y)
    step = 0.5
    for _ in range(40):                      # 回溯線搜索
        cand = y - step * g
        cand[0], cand[-1] = 0.0, 0.0
        if J_discrete(cand) < J_discrete(y):
            break
        step *= 0.5
    y = cand
print("梯度下降後 J =", J_discrete(y))
print("最大偏差(理論解為直線 y=0)=", np.max(np.abs(y)))

# ============================================================
# (b) 符號對照:EL 方程
# ============================================================
x = sp.symbols('x', real=True)
y_s, yp, ypp = sp.symbols('y yp ypp', real=True)
F = sp.sqrt(1 + yp**2)
Fy = sp.diff(F, y_s)
Fyp = sp.diff(F, yp)
dFyp_dx = sp.diff(Fyp, x) + sp.diff(Fyp, y_s) * yp + sp.diff(Fyp, yp) * ypp
el = sp.simplify(Fy - dFyp_dx)
print()
print("F =", F, "   EL 方程左邊 =", el)
m, c = sp.symbols('m c', real=True)
print("直線 y = m*x + c 代回 =", sp.simplify(el.subs({y_s: m * x + c, yp: m, ypp: 0})))

實際輸出:

解析梯度與中心差分梯度最大偏差 = 2.211770766535892e-10
初始 J = 2.501493052381671
梯度下降後 J = 1.0000028458485466
最大偏差(理論解為直線 y=0)= 0.0010694722222667084

F = sqrt(yp**2 + 1)    EL 方程左邊 = -ypp/(yp**2 + 1)**(3/2)
直線 y = m*x + c 代回 = 0

逐行解讀:

  • 第一行是可信度測試:解析梯度與獨立算出來的中心差分梯度吻合到 \(2\times10^{-10}\) 。這正是第 1.3.6 節「把猜升級成證」的精神:符號推導給出的公式,必須用一個獨立路徑核對。
  • **梯度下降後 \(J \approx 1.0000028\) ** :最短曲線的理論值是 \(\sqrt{1 + 0^2}\cdot 1 = 1\) (端點同高的直線長度),吻合到 \(10^{-6}\) 。
  • 符號端:EL 方程是 \(y'' = 0\) ,解是直線,代回得 \(0\) 。數值端收斂到直線,符號端說直線是解——兩條獨立的路給出同一個答案。

把嚴格化條件也算出來:Legendre 與 Weierstrass#

13.1.4 節提到的那些「嚴格化條件」,在 SymPy 裡全是偏導數的代數式。我們把它們逐條算出來:

import sympy as sp

x = sp.symbols('x', real=True)
y_s, yp, ypp = sp.symbols('y yp ypp', real=True)


def euler_lagrange(F):
    """EL 方程左邊 L = F_y - d/dx(F_{y'})。"""
    Fy = sp.diff(F, y_s)
    Fyp = sp.diff(F, yp)
    dFyp_dx = (sp.diff(Fyp, x)
               + sp.diff(Fyp, y_s) * yp
               + sp.diff(Fyp, yp) * ypp)
    return sp.simplify(Fy - dFyp_dx)


def legendre_condition(F):
    """Legendre 必要條件:d2F/dy'^2 >= 0(局部凸性)。"""
    return sp.simplify(sp.diff(F, yp, 2))


def weierstrass_condition(F, v):
    """Weierstrass 必要條件:E := F - y'F_{y'} - v F_{y'} 對 v 單調不減。"""
    Fyp = sp.diff(F, yp)
    E = sp.simplify(F - yp * Fyp - v * Fyp)
    return E, sp.simplify(sp.diff(E, v))


cases = [
    ("最短曲線 F = sqrt(1+y'^2)", sp.sqrt(1 + yp**2)),
    ("懸鏈線 F = y*sqrt(1+y'^2)", y_s * sp.sqrt(1 + yp**2)),
    ("brachistochrone F = sqrt(1+y'^2)/sqrt(y)", sp.sqrt(1 + yp**2) / sp.sqrt(y_s)),
    ("退化案例 F = y'^4", yp**4),
]

for name, F in cases:
    print(name)
    print("   EL 方程左邊          =", euler_lagrange(F))
    print("   d2F/dy'^2 (Legendre)  =", legendre_condition(F))
    E, dE = weierstrass_condition(F, sp.Symbol('v'))
    print("   Weierstrass E         =", E)
    print("   dE/dv                 =", dE)
    print()

# Jacobi 充分條件:二階變分 delta^2 J[eta][eta] 需正定
F = y_s * sp.sqrt(1 + yp**2)
eta = sp.Function('eta')(x)
etap = sp.diff(eta, x)
d2Fy = sp.simplify(sp.diff(F, y_s, 2))
d2Fyyp = sp.simplify(sp.diff(sp.diff(F, y_s), yp))
d2Fyp = sp.simplify(sp.diff(F, yp, 2))
d2 = sp.simplify(d2Fy * eta**2 + 2 * d2Fyyp * eta * etap + d2Fyp * etap**2)
print("懸鏈線的二階變分 =", d2)
print("其中 d2F/dy'^2 =", d2Fyp)

實際輸出:

最短曲線 F = sqrt(1+y'^2)
   EL 方程左邊          = -ypp/(yp**2 + 1)**(3/2)
   d2F/dy'^2 (Legendre)  = (yp**2 + 1)**(-3/2)
   Weierstrass E         = (-v*yp + 1)/sqrt(yp**2 + 1)
   dE/dv                 = -yp/sqrt(yp**2 + 1)

懸鏈線 F = y*sqrt(1+y'^2)
   EL 方程左邊          = (-y*ypp + yp**2 + 1)/(yp**2 + 1)**(3/2)
   d2F/dy'^2 (Legendre)  = y/(yp**2 + 1)**(3/2)
   Weierstrass E         = y*(-v*yp + 1)/sqrt(yp**2 + 1)
   dE/dv                 = -y*yp/sqrt(yp**2 + 1)

brachistochrone F = sqrt(1+y'^2)/sqrt(y)
   EL 方程左邊          = (-2*y*ypp - yp**2 - 1)/(2*y**(3/2)*(yp**2 + 1)**(3/2))
   d2F/dy'^2 (Legendre)  = 1/(sqrt(y)*(yp**2 + 1)**(3/2))
   Weierstrass E         = (-v*yp + 1)/(sqrt(y)*sqrt(yp**2 + 1))
   dE/dv                 = -yp/(sqrt(y)*sqrt(yp**2 + 1))

退化案例 F = y'^4
   EL 方程左邊          = -12*yp**2*ypp
   d2F/dy'^2 (Legendre)  = 12*yp**2
   Weierstrass E         = yp**3*(-4*v - 3*yp)
   dE/dv                 = -4*yp**3

懸鏈線的二階變分 = (y*Derivative(eta(x), x) + 2*yp*(yp**2 + 1)*eta(x))*Derivative(eta(x), x)/(yp**2 + 1)**(3/2)
其中 d2F/dy'^2 = y/(yp**2 + 1)**(3/2)

幾點解讀:

  • 「最短曲線」與「brachistochrone」的 Legendre 條件恆正: \((1+y'^2)^{-3/2} > 0\) 與 \(\frac{1}{\sqrt{y}(1+y'^2)^{3/2}} > 0\) 。這正是為什麼這兩個問題「好解」 —— 泛函是凸的,EL 方程的解就是唯一的全域最小值點。「凸性」這個詞在第 7 章(多元函數的 Hessian)出現過,這裡是它的無窮維版本。
  • **「懸鏈線」的 Legendre 條件是 \(\frac{y}{(1+y'^2)^{3/2}}\) ** ,符號取決於 \(y\) 。在鏈條下垂的區域 \(y > 0\) ,條件成立(局部凸)。
  • 退化案例 \(F = y'^4\) ** 是最有教育意義的: \(\frac{\partial^2F}{\partial y'^2} = 12y'^2 \ge 0\) 居然成立,但 \(F\) 在 \(y' \to \infty\) 時無界,泛函沒有最小值。這說明「Legendre 條件成立」既不充分、也不是全局性的判準。**
  • Weierstrass 條件 \(\frac{\partial E}{\partial v} \ge 0\) 在這些例子中並不總成立(例如最短曲線的 \(\frac{\partial E}{\partial v} = -\frac{y'}{\sqrt{1+y'^2}}\) ,只有在 \(y' \le 0\) 時成立)——這正好說明:Weierstrass 條件是對「特定方向的變分」而言的,不是無條件成立。

把這些條件與第 7 章對照會很清楚:

有限維 變分法
Hessian \(H\) 正定 Legendre 條件 \(\frac{\partial^2F}{\partial y'^2} \ge 0\) + Jacobi 條件
目標函數凸 \(F\) 關於 \((y,y')\) 凸
對偶性(KKT 條件) Jacobi 的「最後乘法因子」、帶約束的 EL 方程

SymPy 的角色在這裡非常精確:它不做凸性分析、不做全域最佳化保證,它只做代數。把代數結論升級成定理,仍然是人的工作。


13.1.6 從變分法到機器學習:Robbins–Monro(1951)#

1951 年,Herbert Robbins 與 Sutton Monro 發表了一篇短文,處理的是隨機逼近問題:

給定 \(E[Y_n] = f(x)\) 的隨機觀測 \(Y_n\) ,求 \(x\) 使得 \(f(x) = 0\) 。

他們的定理(Robbins–Monro 條件)說,若步長 \(\alpha_n\) 滿足

\[ \sum_{n=1}^{\infty} \alpha_n = \infty, \qquad \sum_{n=1}^{\infty} \alpha_n^2 < \infty \]

則迭代 \(x_{n+1} = x_n - \alpha_n (f(x_n) + \text{噪聲})\) 以機率 1 收斂到 \(f\) 的根。

** \(t^{-\alpha}\) 型步長**( \(0 < \alpha \le 1\) )正好滿足這兩個條件: \(\sum t^{-\alpha} = \infty\) (走得夠久)而 \(\sum t^{-2\alpha} < \infty\) (抖動會被抹平)。

今天深度學習裡的 SGD 用的是同一個定理,只是把 \(f\) 換成損失函數的梯度、把噪聲換成 mini-batch 抽樣的方差。機器學習的「學習律」有一半的數學根基在 1951 年就埋好了。

回到變分法的語言:SGD 就是用隨機估計的泛函導數做梯度下降:

\[ y \leftarrow y - \alpha_n\, \widehat{\delta J} , \qquad E\left[\widehat{\delta J}\right] = \delta J \]

我們用一個線性回歸做示範,把「批量梯度下降」與「SGD」並排比較。

import numpy as np

# ============================================================
# Robbins-Monro(1951):隨機逼近與 SGD 的步長條件
# ============================================================
rng = np.random.default_rng(2026)

n = 200
X = rng.uniform(-2, 2, n)
w_true = np.array([1.5, -0.7])
y_obs = w_true[0] * X + w_true[1] + rng.normal(0, 0.3, n)

all_idx = np.arange(n)


def run_gd(lr, steps=400):
    """批量梯度下降:用全部資料算梯度。"""
    w = np.array([0.0, 0.0])
    for _ in range(steps):
        r = y_obs - (w[0] * X + w[1])
        g = np.array([-2 * np.mean(r * X), -2 * np.mean(r)])
        w = w - lr * g
    return w


def run_sgd(lr0, power, steps=20000, batch=8, seed=1):
    """SGD:每次只用 batch 個樣本估計梯度,步長 lr0 / t**power。"""
    r_ = np.random.default_rng(seed)
    w = np.array([0.0, 0.0])
    for t in range(1, steps + 1):
        idx = r_.integers(0, n, size=batch)
        r = y_obs[idx] - (w[0] * X[idx] + w[1])
        g = np.array([-2 * np.mean(r * X[idx]), -2 * np.mean(r)])
        w = w - (lr0 / t**power) * g
    return w


w_gd = run_gd(0.05)
print("批量梯度下降  w =", np.round(w_gd, 6))
for p in [0.5, 0.6, 0.75]:
    w_sgd = run_sgd(0.5, p)
    print(f"SGD(步長 0.5/t^{p}) w =", np.round(w_sgd, 6))
print("真實 w =", w_true)

# 步長太大會發散:Robbins-Monro 條件不是裝飾
w_bad = run_sgd(0.8, 0.0, steps=2000)
print("步長過大且不遞減的 SGD w =", np.round(w_bad, 3))

實際輸出:

批量梯度下降  w = [ 1.468258 -0.694645]
SGD(步長 0.5/t^0.5) w = [ 1.468541 -0.695211]
SGD(步長 0.5/t^0.6) w = [ 1.46853  -0.694102]
SGD(步長 0.5/t^0.75) w = [ 1.469363 -0.69433 ]
真實 w = [ 1.5 -0.7]
步長過大且不遞減的 SGD w = [28.074  4.638]

解讀:

  • 批量 GD 與三種 SGD 都收斂到接近真值的位置。注意 SGD 的解比 GD 略「散」 ——因為 SGD 永遠停在「噪聲球」裡,這個現象在機器學習裡叫 noise floor。
  • 步長 \(0.5/t^{0.6}\) 收斂得最好(偏差最小),這不是巧合:指數 \(p\) 要在 \(1/2\) 與 \(1\) 之間,太小則噪聲壓不下去,太大則收斂太慢——這正是 Robbins–Monro 兩個條件的權衡。
  • 最後一行:步長固定為 \(0.8\) 且不遞減時,SGD 發散( \(w\) 跑到 28 附近)。這就是為什麼今天所有的深度學習框架都強制要求學習率要衰減。

泛函導數的「隨機估計」長什麼樣?#

回到變分法:在機器學習裡,「泛函」是損失函數

\[ J[\theta] = \frac{1}{n}\sum_{i=1}^{n} \ell(f_\theta(x_i), y_i) \]

這是有限和,它的「梯度」是可以精確算的:

\[ \frac{\partial J}{\partial \theta} = \frac{1}{n}\sum_{i=1}^{n} \frac{\partial \ell}{\partial \theta} \]

SGD 用 mini-batch 估計這個總和。這裡沒有真正的「無窮維泛函導數」了——因為神經網路的參數雖然多,但仍是有限維。所以現代機器學習嚴格來說不是變分法,而是「用變分法的直覺處理的有限維最佳化」。

但這兩者之間有一個真實的、值得學習的張力:

  • 變分法求的是整個函數 \(y(x)\) ,一次性給出解析形式(懸鏈線、擺線);
  • 機器學習求的是有限維但天文數字般大的參數向量,只能迭代逼近,得到的往往是「函數的近似」而非閉式。

SymPy 在這兩者之間的位置非常獨特:它是少數能直接處理符號泛函( \(F(x, y, y')\) 的符號表達式)的工具。第 13.3 節我們會看到,SymPy 甚至提供了 sp.minimum / sp.maximum 這類直接處理「函數極值」的指令。


13.1.7 一個完整的示範:符號 × 數值 × 圖#

我們做一次三路合流:符號算出 EL 方程與精確解 → 數值用離散化梯度下降逼近 → 畫在一起比較。

這次挑一個「行為良好」的泛函:

\[ J[y] = \int_0^1 \left(y'(x)^2 + y(x)^2\right)\,\mathrm{d}x, \qquad y(0) = 0, \ y(1) = 1 \]

為什麼挑它?因為它是嚴格凸的: \(F = y'^2 + y^2\) 關於 \((y, y')\) 的 Hessian 是 \(2I\) ,恆正定。唯一極小值點存在、全域唯一、數值上非常穩定。 這讓我們可以把注意力放在「符號與數值是否一致」上,而不用擔心最佳化本身的困難。

順帶一個重要的教訓:懸鏈線問題 \(J[y] = \int y\sqrt{1+y'^2}\,\mathrm{d}x\) 若不加上「鏈長固定」的約束,其泛函是無下界的——把曲線往下拉得越深,位能越負,沒有最小值。物理上真正的懸鏈線問題必須加上「總長度 \(L\) 固定」這個等周約束, \(L\) 就是 Lagrange 乘子要對付的對象。這是「符號代數給出必要條件,但充分性需要人來把關」的又一例證,也是第 13.3 節 Lagrange 乘數法的自然入口。

import numpy as np
import sympy as sp
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

# ============================================================
# (a) 符號端:EL 方程、精確解、Hessian
# ============================================================
x = sp.symbols('x', real=True)
y_s, yp, ypp = sp.symbols('y yp ypp', real=True)
F = yp**2 + y_s**2
Fy = sp.diff(F, y_s)
Fyp = sp.diff(F, yp)
dFyp_dx = (sp.diff(Fyp, x)
           + sp.diff(Fyp, y_s) * yp
           + sp.diff(Fyp, yp) * ypp)
el = sp.simplify(Fy - dFyp_dx)
print("F =", F)
print("Euler-Lagrange 方程左邊 =", el)

exact_expr = sp.sinh(x) / sp.sinh(1)
print("代回精確解 y = sinh(x)/sinh(1) :",
      sp.simplify(el.subs({y_s: exact_expr, yp: sp.diff(exact_expr, x),
                           ypp: sp.diff(exact_expr, x, 2)})))

Hm = sp.Matrix([[sp.diff(F, y_s, 2), sp.diff(sp.diff(F, y_s), yp)],
                [sp.diff(sp.diff(F, yp), y_s), sp.diff(F, yp, 2)]])
print("Hessian d2F/d(y,y') =", Hm.tolist(), "  特徵值 =", Hm.eigenvals())

# ============================================================
# (b) 數值端:離散化 + 梯度下降
# ============================================================
M = 101
xg = np.linspace(0.0, 1.0, M)
dx = xg[1] - xg[0]


def J_disc(yy):
    """梯形法離散化 J = int (y'^2 + y^2) dx。"""
    d = np.diff(yy) / dx
    return float(np.sum((d**2 + 0.5 * (yy[:-1]**2 + yy[1:]**2)) * dx))


def grad_J(yy):
    """離散泛函的解析梯度:g_j = 2(d_{j-1} - d_j) + 2 y_j dx。"""
    d = np.diff(yy) / dx
    g = np.zeros_like(yy)
    g[1:-1] = 2 * (d[:-1] - d[1:]) + 2 * yy[1:-1] * dx
    return g


# 先用中心差分「獨立」核對解析梯度
rng = np.random.default_rng(11)
yt = rng.normal(size=M) * 0.1
yt[0], yt[-1] = 0.0, 1.0
ga = grad_J(yt)
fd = np.zeros(M)
eps = 1e-6
for j in range(1, M - 1):
    p = yt.copy(); p[j] += eps
    m = yt.copy(); m[j] -= eps
    fd[j] = (J_disc(p) - J_disc(m)) / (2 * eps)
print()
print("解析梯度 vs 中心差分:最大偏差 =", np.max(np.abs(ga[1:-1] - fd[1:-1])))

yv = np.linspace(0.0, 1.0, M) + 0.2 * np.sin(2 * np.pi * xg)
yv[0], yv[-1] = 0.0, 1.0
print("初始 J =", J_disc(yv))
for it in range(4000):
    gr = grad_J(yv)
    step = 0.5
    for _ in range(50):
        cand = yv - step * gr
        cand[0], cand[-1] = 0.0, 1.0
        if J_disc(cand) < J_disc(yv):
            break
        step *= 0.5
    yv = cand
print("梯度下降後 J =", J_disc(yv))

exact = np.array([float(exact_expr.subs(x, xv)) for xv in xg])
print("最大偏差 =", float(np.max(np.abs(yv - exact))))
print("精確解的 J =", float(sp.integrate(
    F.subs({y_s: exact_expr, yp: sp.diff(exact_expr, x)}), (x, 0, 1))))

# ============================================================
# (c) 畫圖
# ============================================================
fig, ax = plt.subplots(figsize=(7, 4.5))
ax.plot(xg, yv, "o-", ms=3, label="gradient descent (discretized)")
ax.plot(xg, exact, "r-", lw=2, label="exact  sinh(x)/sinh(1)")
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_title("Variational problem: discrete gradient descent vs exact solution")
ax.legend()
ax.grid(alpha=0.3)
plt.tight_layout()
plt.savefig("/tmp/ch13_var.png", dpi=120)
print("saved /tmp/ch13_var.png")

實際輸出:

F = y**2 + yp**2
Euler-Lagrange 方程左邊 = 2*y - 2*ypp
代回精確解 y = sinh(x)/sinh(1) : 0
Hessian d2F/d(y,y') = [[2, 0], [0, 2]]   特徵值 = {2: 2}

解析梯度 vs 中心差分:最大偏差 = 3.247422730723315e-08
初始 J = 2.0790175967838276
梯度下降後 J = 1.3131686360633823
最大偏差 = 0.004577196301303155
精確解的 J = 1.3130352854993312
saved /tmp/ch13_var.png

三路合流:

路徑 給出的答案
符號(EL 方程 + 代回驗證) \(y'' = y\) ,代回精確解殘差 \(=0\)
符號(Hessian) \(2I\) ,特徵值 \(2, 2 > 0\) :嚴格凸,唯一極小
符號(直接積分) \(J[\text{精確解}] = 1.3130352854993312\)
數值(離散化 + 梯度下降) \(J = 1.3131686360633823\) ,曲線最大偏差 \(0.0046\)

注意最後兩行的關係: \(J\) 的誤差是 \(1.3\times10^{-4}\) ,而曲線的最大偏差是 \(4.6\times10^{-3}\) ,大了約 35 倍。這是本節最實用的一個觀察:

泛函的值比泛函的極值點更準。

原因很直觀: \(J\) 是二次型, \(J[y] - J[y_*] = \int \left((\delta y)'^2 + (\delta y)^2\right)\mathrm{d}x\) ,對偏差 \(\delta y\) 是平方關係;而曲線本身的位置偏差是一階的。這是第 13.2 節誤差理論的核心直覺之一,在最佳化裡就已經出現。

** \(0.0046\) 的偏差是什麼量級?** 此處 \(h = 1/100\) ,而偏差 \(\approx 0.0046 \approx 0.46h\) ——** \(O(h)\) ** ,來自離散化本身。這提醒我們:符號與數值不是對立的,而是互補的:符號給出結構與精確形式,數值給出在符號難以處理時的可行解,並且用數值獨立驗證符號的結論。


13.1.8 一張圖串起本節#


習題#

習題 13.1.1(推導 Euler–Lagrange 方程)

設 \(J[y] = \int_0^{\pi/2} \left(y'(x)^2 + y(x)^2\right)\,\mathrm{d}x\) ,端點 \(y(0) = 0\) 、 \(y(\pi/2) = 1\) 。

(a) 用 13.1.2 節的 euler_lagrange() 模板推出 EL 方程; (b) 用 sp.dsolve 解出 \(y(x)\) ; (c) 用 verify() 驗證你的解代回 EL 方程殘差為 \(0\) ; (d) 說明為什麼這個 EL 方程是線性的,並指出「線性泛函的 EL 方程是線性的」的一般原因。

習題 13.1.2(Beltrami 恆等式與降階)

對 brachistochrone 的 \(F = \frac{\sqrt{1+y'^2}}{\sqrt{y}}\) :

(a) 證明 Beltrami 量 \(F - y'F_{y'}\) 沿極值曲線為常數(用 \(F\) 不顯含 \(x\) 這件事); (b) 由 Beltrami 恆等式導出一階方程 \(y' = \sqrt{\frac{1}{c^2 y} - 1}\) ( \(c\) 為常數); (c) 用 sp.integrate 或 sp.dsolve 由此導出擺線的參數式,並與 13.1.2 節的符號結果對照。

習題 13.1.3(等周問題與 Lagrange 乘數)

「周長固定為 \(L\) 的封閉曲線中,面積最大的是圓」。

(a) 把面積寫成泛函 \(A[y] = \int y\,\mathrm{d}s = \int y\sqrt{1+y'^2}\,\mathrm{d}x\) ; (b) 周長約束寫成 \(\int \sqrt{1+y'^2}\,\mathrm{d}x = L\) ; (c) 用帶約束的變分(Jacobi 的「最後乘法因子」就是這個工具)引入乘子 \(\lambda\) ,推導出帶 \(\lambda\) 的 EL 方程; (d) 證明 \(\lambda = 1\) 時解是圓( \(y(x) = \sqrt{R^2 - (x-x_0)^2}\) 的某一段)。並說明:為什麼 Jacobi 的方法比 Lagrange 1784 年的方法更一般?

習題 13.1.4(Legendre 必要條件)

對 \(J[y] = \int_0^1 F(x,y,y')\,\mathrm{d}x\) , \(F = y'^{\,4}\) 與 \(F = \sqrt{1+y'^2}\) 兩個案例:

(a) 計算 \(\frac{\partial^2 F}{\partial y'^2}\) ; (b) 說明為什麼 \(F = y'^4\) 的 EL 方程 \(4y'^3 = 0\) 只有一個解( \(y'=0\) ),而這正是 Legendre 條件 \(\frac{\partial^2F}{\partial y'^2} \ge 0\) 不成立(因為 \(12y'^2\) 在 \(y'\ne0\) 時為正,但函數在 \(\infty\) 處發散)的反映; (c) 討論: \(F = y'^4\) 的泛函是否有最小值?若沒有,這說明「EL 方程給出的是必要條件」這句話的什麼重要性?

習題 13.1.5(Robbins–Monro 的步長條件)

(a) 對 \(\alpha_n = n^{-\gamma}\) ( \(0 < \gamma \le 1\) ),證明 \(\sum \alpha_n = \infty\) 且 \(\sum \alpha_n^2 < \infty\) ; (b) 若 \(\gamma > 1\) ,說明為什麼 SGD 會收斂但停不到最優點; (c) 用 13.1.6 節的程式碼,掃描 \(\gamma \in \{0.3, 0.5, 0.7, 0.9, 1.0, 1.2\}\) ,記錄最終 \(w\) 與真值的距離,畫出曲線並說明你的觀察。

習題 13.1.6(泛函導數的離散對應)

對 \(J[y] = \int_0^1 y(x)^2 + y'(x)^2\,\mathrm{d}x\) :

(a) 推導 EL 方程並求出解(端點 \(y(0)=y(1)=0\) ); (b) 用 \(N = 21, 41, 81\) 離散化,用梯度下降求數值解,與 (a) 的精確解比較,觀察誤差量級; (c) 說明:為什麼這個例子中「 \(J\) 的誤差」與「曲線的誤差」量級不同?(提示: \(J\) 是二次型。)


小結#

  • 變分法的問題來源:Dido 問題與等周問題(幾何)、Hamilton 的最小作用量原理(物理)。它們共同的形式是 \(J[y] = \int_a^b F(x,y,y')\,\mathrm{d}x\) ,其中被最佳化的對象是函數。
  • Euler–Lagrange 方程(Euler 1752): \(\frac{\partial F}{\partial y} - \frac{\mathrm{d}}{\mathrm{d}x}\frac{\partial F}{\partial y'} = 0\) ,其中全導數要展開成 \(\frac{\partial G}{\partial x} + \frac{\partial G}{\partial y}y' + \frac{\partial G}{\partial y'}y''\) 。它是必要條件,不是充分條件。
  • Jacobi 的「最後乘法因子」(1845) 用乘子把「顯含時間」等所有情形統一,並導出 Jacobi 充分條件。
  • 嚴格化三部曲:Legendre 1786(二階變分)→ Jacobi 1845(充分條件)→ Weierstrass 1850s(必要不等式);Legendre–Jacobi 條件在今天就是「Hessian 正定」。
  • 泛函導數就是無窮維的梯度: \(\delta J[y](\eta) = \int \left(F_y - \frac{\mathrm{d}}{\mathrm{d}x}F_{y'}\right)\eta\,\mathrm{d}x\) ,對應梯度下降 \(y \leftarrow y - \alpha\,\delta J\) 。
  • 離散化是橋樑:把泛函變成 \(N\) 個變數的有限維問題後,梯度下降、線搜索、牛頓法全部可用。符號(EL 方程)與數值(梯度下降)會收斂到同一個答案,偏差是 \(O(h)\) 。
  • Robbins–Monro(1951) 給出 SGD 的收斂定理,條件是 \(\sum \alpha_n = \infty\) 且 \(\sum \alpha_n^2 < \infty\) ;這是今天深度學習裡學習率必須衰減的理論依據。

下一節我們離開「最佳化」這條線,轉向誤差:符號運算給出的是精確答案,但在真實計算中我們面對的是浮點數、截斷誤差、捨入誤差與病態問題——計算機時代的微積分是一門關於「答案有多可靠」的學問。