Lagrange 乘子與 KKT:圍欄上的最低點
玩完這關,你會把最佳化 01 關那個「谷底的梯度是 0」升級成「被圍欄夾住的時候,梯度不是 0,而是跟圍欄的法向量平行」, 而且你會知道那個比例 λ 不是計算殘渣 —— 它就是影子價格:多給你一單位資源,答案能改善多少。
① 這關在解什麼問題
最佳化 01 關給你的判斷法是「谷底的梯度是 0」 —— 一句話複述:站在真正的最低點,你往任何方向踏一步都不會更低,所以那一點的 ∇f(往上最陡的方向)長度是 0。 這條規則很好用,因為它把「找最低點」變成「解一條方程式 ∇f = 0」。
問題是:你真實碰到的問題幾乎都有約束。預算就這麼多、產能就這麼多、 錢加起來要等於 100%、權重不能是負的。 而只要有約束,答案就幾乎永遠卡在圍欄上,不會停在那個自由的谷底。
而圍欄上那一點的 ∇f 不是 0。你手上唯一的工具,在你最需要它的地方失效了。 這關就是把工具修好。
先講具體的麻煩,不要先講數學。
你管兩條產線。A 線的產量記作 x、B 線記作 y(單位:萬件/月)。 工廠有一個「最順的跑法」:A 線 2 萬件、B 線 1 萬件。偏離它就會多花錢 —— 開太慢養不起固定成本,開太快要加班、要搶共用的瓶頸工站。 這個「多花的錢」就是你的目標函數 f(x, y),它的谷底在 (2, 1),谷底的成本是 0。
到這裡最佳化 01 關的工具還夠用:答案就是谷底,做 (2, 1) 就好。然後業務進來了:
「這個月客戶要 6 萬件,兩條線加起來要湊到 6。」
現在 (2, 1) 只有 3 萬件,不合法。你被迫離開谷底。而且你有無窮多個選擇:
- (6, 0)?全靠 A 線硬撐。
- (0, 6)?全靠 B 線。
- (3, 3)?各出一半,聽起來公平,但憑什麼?
- (4.3, 1.7) 跟 (4.4, 1.6) 哪個便宜?你憑什麼判斷?
你不能一個一個試。你需要一條方程式,直接把答案解出來 —— 而且你還想知道第二件事:如果客戶願意少要一點,你能省多少? 這兩件事是同一個答案的兩面,那個答案叫 Lagrange 乘子 λ; 把它推廣到「不能超過 / 至少要」這種不等式,那套規則叫 KKT 條件。
不推完整證明。Lagrange 的嚴格版本要談正則性條件(constraint qualification)、要談二階條件、 要區分必要與充分。這些都是真的,但它們不會讓你更會用這個工具。
這關的目標只有一個:看得懂、用得下去。你要能看著一張圖說出「所以這裡的 λ 是 2.73, 意思是客戶多要一萬件我就多花 2.73 萬」,然後在自己的問題上照著做一次。
② 先把符號攤開,再看一個最小的例子
本頁所有符號,一次講完
這一頁只有九個符號。每一個都在下表講清楚是什麼、唸什麼、現在的值是多少 (「現在的值」欄是 ③ 的預設狀態,由頁面載入時實際算出來填進去的)。
| 符號 | 唸作 | 它是什麼 | 現在的值 |
|---|---|---|---|
| x, y | — | 你能決定的兩個數字:A 線與 B 線的產量(萬件)。合起來寫成一個向量 v = (x, y) | — |
| f(v) | f | 目標函數:你想壓到最小的東西。這裡是「偏離最順跑法多花的成本」(萬元) | — |
| m | m | 無約束的谷底:不管訂單的話最省的跑法。這裡固定是 (2, 1),f(m) = 0 | (2, 1) |
| A | A | 地形矩陣:決定這個碗有多陡、往哪邊拉長。跟機率統計 05 關的橢圓是同一種二次型,只是這裡它量的是成本而不是距離 | [[1, 0.6], [0.6, 2]] |
| ∇f | nabla f /梯度 | 往上最陡的方向(一個向量)。長度=那個方向有多陡。最佳化 01 關的主角 | — |
| g(v) | g | 約束函數:被限制的那個量。這裡 g(v) = x + y=總產量 | — |
| b | b | 約束的常數:客戶要的量。約束就寫成 g(v) = b(剛好)或 g(v) ≥ b(至少) | — |
| ∇g | nabla g | 約束的法向量:垂直於那條圍欄、指向「這個量變大」的方向。g = x + y 的話它永遠是 (1, 1) | (1, 1) |
| λ | lambda /乘子 | 本關主角:∇f 是 ∇g 的幾倍。也等於「b 多一單位,最低成本上升多少」 | — |
| φ(b) | phi of b | 最優值函數:在約束 g = b 之下能達到的最低成本。b 一變它就變 | — |
| c | c | 等高線的高度:f(v) = c 這一圈上的每一點成本都是 c。最佳化 01 關的等高線 | 見 ④ |
綠 = 結果:你現在站的點 / 最優解 紅 = 誤差:沿線還沒榨完的下降量
紫 = 主軸:等高線與最優解的軌跡(兩者都是 A 的二次型幾何) 青 = 資料點:⑥ 暴力撒出來的驗證點
最佳化 01 關的碗寫成 f(v) = ½(v − m)TA(v − m),這關用同一個寫法、同一個 ½, 只是係數換成 A = [[1, 0.6], [0.6, 2]]、谷底換成 m = (2, 1) —— 同族的橢圓碗,係數略不同,換數字是為了讓答案落在畫面好看的位置。
留著 ½ 的理由很實際:這樣梯度剛好沒有多餘的 2, ∇f(v) = A(v − m),等一下算 λ 的時候可以直接用眼睛除。
兩個寫法上的小差別,先講明白免得你以為看錯:
- 最佳化 01 關把谷底叫 c,這關叫 m —— 因為這一頁的 c 已經被「等高線的高度」佔走了。
- 最佳化 01 關把梯度的長度寫成 ‖∇f‖,這一頁寫 |∇f|, 同一個意思(一個向量有多長)。
最小的例子:先砍到一維
兩條產線太多,先只留一條。成本 f(x) = (x − 2)²,谷底在 x = 2, 導數 f′(x) = 2(x − 2),在谷底是 0。最佳化 01 關的規則成立。 (這個一維例子刻意不放 ½ —— 寫成 (x − 2)² 比較好認。 下一節回到二維就會把 ½ 補回來,那時候梯度才會乾淨。)
現在加約束:客戶要 5 萬件,就這一條線,所以 x 一定要等於 5。
這就是整關的病灶,用最小的劑量呈現:「導數為 0」描述的是「往兩邊走都不會更好」, 但約束把你能走的方向拿掉了。在一維的等式約束下,能走的方向剩下零個,所以導數是幾都無所謂。
那 6 是垃圾嗎?不是。它就是 λ。意思是: 客戶如果只要 4 萬件,你大約能省 6 萬元(實際上省 f(5) − f(4) = 9 − 4 = 5, 差別是因為 6 只在「小幅度」下準 —— 這件事 ⑤ 會現場量給你看)。
所以這一段的結論是:約束下的最優點,梯度不是 0,而是「剩下的那股力氣全被圍欄擋掉了」。 擋掉多少,就是 λ。接下來回到二維 —— 那裡你還有一個方向可以走,事情才真的有趣。
③ 把問題擺出來:只能沿著一條線走
回到兩條產線。地形是同一個橢圓碗,谷底在 m = (2, 1)。約束是一行字:
注意這條約束跟 最佳化 03 關的 w1 + w2 + w3 = 1 是同一種東西:幾個數字加起來等於一個常數。那關是「錢分完」,這關是「訂單湊滿」。
- 你會看到什麼:
- 左圖是成本地形:淡紫細圈是背景等高線(越內圈越便宜),
紫實線圈是你現在站的那一圈,橘直線是約束 x + y = 6,
小灰點是無約束谷底 m,綠點是你。
藍箭頭是 ∇f(往上最陡),
紅箭頭是「把藍箭頭壓到線上、再反向」=沿線還能往下走多少。
右圖是沿著線走的高度剖面:橫軸是 A 線產量 x(B 線自動是 6 − x),縱軸是成本。 左圖那條線被拉直、攤平成右圖這條曲線。 - 你可以動什麼:
- 拉滑桿,或直接用滑鼠拖左圖的綠點(它只能沿著橘線滑,拖到哪都會被拉回線上)。 右圖的光標會同步跑。
- 要看出什麼:
- 把綠點滑到紅箭頭縮成 0 的位置 —— 那就是最低點,右圖的光標剛好落在曲線谷底。 然後看讀數盤:那裡的 ∇f 長度不是 0(大約 3.87), 只有「沿線那一份」是 0。這就是最佳化 01 關的規則失效、而新規則接手的那一刻。
在最低點 (4.333, 1.667):沿線的一維斜率是 0(左右踏一步都變貴), 但二維的 ∇f 不是 0(它是 (2.733, 2.733),長度 3.87)。
那個沒被抵銷掉的 ∇f 指向哪裡?它剛好垂直於約束線。 這不是巧合 —— 如果它有一點點沿著線的分量,你就還能沿線往下走一點,那就還沒到最低點。 「沿線分量 = 0」就是「∇f 完全垂直於線」。④ 把這件事變成幾何。
滑桿只讓 x 走到 0 和 6。那其實是兩條不等式約束: x ≥ 0 和 y ≥ 0(產量不能是負的)。 但最低點在 (4.333, 1.667),兩個數字都是正的 —— 這兩條約束完全沒在擋你。 有約束、但不作用。⑥ 就在講這種約束該怎麼處理, 以及它為什麼跟 最佳化 03 關的「不能放空」是同一件事。
④ 相切:從碰不到,到剛好碰一點,到穿過去
③ 是「站在線上找最低」。這一節換一個看法,而它是整關的核心。
不要動點,改成動等高線。把 f(v) = c 這一圈的高度 c 從很小慢慢拉大,看它跟約束線的關係怎麼變:
- c 很小 → 那一圈還縮在谷底附近,碰不到約束線(交點 0 個)。 意思是:「花這麼少錢就湊到 6 萬件」這件事做不到。
- c 拉大 → 某一刻剛好碰到一點(交點 1 個)。這是「做得到」的最便宜版本。
- c 再大 → 圈子穿過去,交點變 2 個。這些都做得到,但你多花錢了。
從「碰不到」到「穿過去」的臨界點,就是只碰一點的那一刻 —— 那一點就是最低點, 而那裡兩條線相切。
「相切」講的是兩條曲線在該點的方向一樣。既然方向一樣,它們的法向量(垂直方向)也一樣 —— 等高線的法向量就是 ∇f,約束線的法向量就是 ∇g。 所以相切 ⇔ ∇f 與 ∇g 平行。
「相切」的正式定義(切線=割線在 h → 0 的極限位置)在 微積分 · 導數關 ②, ∇ 為什麼垂直於等高線在多變量關 ②。 這一節用幾何就講得完,那兩頁是補課用的。
- 你會看到什麼:
- 紫圈是你正在控制的那條等高線 f = c, 橘線是約束 x + y = 6, 綠點是它們的交點(0 個、1 個或 2 個)。 每個交點上會長出兩支箭頭:藍=∇f、 橘=∇g。兩支箭頭只畫方向,長度統一, 因為這一節只在乎它們平不平行。
- 你可以動什麼:
- 滑桿控制等高線的高度 c,從 0 拉到 8。 「跳到相切」會把 c 設成正好的臨界值。
- 要看出什麼:
- 盯著讀數盤的交點個數和 |sin θ|(兩支箭頭夾角的正弦,也就是外積除以長度)。 交點 2 個的時候,兩支箭頭明顯歪開、|sin θ| 是零點幾; 交點縮成 1 個的那一瞬間,兩支箭頭疊在一起、|sin θ| 掉到 0。
把 c 想成預算。f(v) ≤ c 是一個實心橢圓(碗被水位 c 淹掉的那一塊), 邊界就是等高線。你要的是「花得起、又碰得到約束線」的最小 c。
c 太小 → 橢圓整個在線的一側,碰不到。慢慢放大 → 橢圓長大。 它第一次觸到那條線的時候,只會摸到一點(因為再小一點就完全碰不到)。 那個 c 就是最小可行成本 φ(b),那一點就是最優解。
這正是 最佳化 03 關那句「風險等高線第一次碰到可行域」的一般版本。 當時只給了你這張圖的直覺,現在你知道它其實是一個可以寫成方程式的條件。
⑤ 乘子 λ:兩支向量的比例,也是影子價格
④ 得到的條件是「兩支法向量平行」。平行寫成數學,就是一支是另一支的倍數:
∇g(v) —— 約束函數的梯度,就是那條圍欄的法向量。這關 g = x + y,所以永遠是 (1, 1)。
λ —— 那個倍數(一個純量,可正可負)。這就是 Lagrange 乘子。
整條式子讀作:「在最優點,目標的爬坡方向完全被圍欄的法向量吃掉,一點都沒剩。」
加上約束本身,你就有一組方程式,未知數是 x, y, λ:
g(v) = b (1 條) 3 條方程式、3 個未知數 —— 這就是「解得出來」的意思。λ 多出來的那個未知數不是負擔, 它是你多拿到的一個答案。
這一節再往下會用中央差分(把 b 往左右各推 0.001)現場量 φ 的斜率來驗 λ。那個動作叫導數, 定義與「為什麼要推兩邊而不是一邊」在微積分 · 導數關 ②; 而「λ 是切線、走遠就不準」的量化版在同關 ④ 線性近似。
先用眼睛除一次
在 ③ 的最低點 (4.333, 1.667),我們算過 ∇f = (2.733, 2.733), 而 ∇g = (1, 1)。兩個座標各除一次:
λ = 2.733 ÷ 1 = 2.733 (y 那一格) 兩格算出同一個數 —— 這正是「平行」的意思。如果兩格算出不同的數,那兩支向量就不平行,那一點就不是最優解。
那 2.733 是什麼單位?
這是 λ 最有用、也最常被跳過的一段。
φ(b) 是「客戶要 b 萬件時,你最少要花多少」。它是一條曲線。 λ 就是這條曲線在當前 b 的斜率。
- 你會看到什麼:
- 左圖是地形俯視:橘線是約束,它會隨滑桿平移;
綠點是當前的最優解;紫虛線是最優解隨 b 移動掃出來的軌跡(是一條直線,不是巧合);
藍箭頭與橘箭頭是最優解上的 ∇f 與 ∇g,永遠疊在一起。
右圖是 φ(b) 這條最低成本曲線:橫軸是客戶要的量 b,縱軸是你的最低成本。 綠點是當前的 (b, φ(b)),穿過它的藍直線就是斜率為 λ 的切線。 - 你可以動什麼:
- 滑桿平移約束線(改客戶要的量 b,從 0 拉到 8)。左右兩圖同時更新。
- 要看出什麼:
- 右邊讀數盤有兩個要對照的數字:公式算的 λ 與 數值微分量出來的斜率(把 b 往兩邊各推 0.001,重新求一次最優值再除)。 兩者永遠相等到小數第六位以上 —— 這不是我在講故事,是頁面現場算給你看。 另外注意最下面那行「多接一整萬件實際多花」跟 λ 差了一成多: 影子價格只在小幅度下成立。
把上面那個滑桿拉到 b = 4 / 6 / 8,讀數盤會給你這三行(下表是同一份程式算出來的,你可以逐格核對):
| b | 最低成本 φ(b) | λ(公式) | 數值斜率(Δb = ±0.001) | 誤差 | 多接一整單位:成本實際變化 |
|---|---|---|---|---|---|
| 4 | 0.455556 | 0.911111 | 0.911111 | < 10−9 % | 1.366667(差 50%) |
| 6 | 4.100000 | 2.733333 | 2.733333 | < 10−9 % | 3.188889(差 16.7%) |
| 8 | 11.388889 | 4.555556 | 4.555556 | < 10−9 % | 5.011111(差 10.0%) |
「數值斜率」那一欄是真的重新求解算出來的:頁面對 b ± 0.001 各跑一次 沿線的三分搜尋(完全不用 λ 的公式),拿到兩個最低成本再相減除以 0.002。 兩邊對得上,才代表「λ 是斜率」這句話是真的,不是我抄來的。
最後一欄是刻意放大步長:客戶一次多要一整萬件。這時 λ 就低估了實際成本 (因為 φ 是往上彎的),而且 b 越小低估越嚴重。影子價格是切線,不是弦。
λ 的正負在講什麼
約束在往上推你
客戶要的量比谷底的 3 還多(b > 3)。b 再變大,成本跟著變大。 這條約束在花你的錢,你會想跟客戶談少一點。
約束在往下壓你
b < 3:客戶要的比你最順的跑法還少,你被迫減產、也要花錢。 這時候 b 變大反而讓你更便宜,所以斜率是負的。
注意這裡的 λ 可正可負,因為約束是等式(你被釘在線上,兩邊都不能動)。 等 ⑥ 換成不等式,λ 就只能 ≥ 0 了 —— 原因很直白,等下就看到。
⑥ KKT:把「必須剛好」換成「不能越過」
現實裡的約束很少是「剛好等於」。業務更常說的是:
「這個月至少要出 b 萬件,多出無妨。」
這一改,事情反而變簡單了,因為只剩兩種可能:
① 沒碰到:約束不作用
谷底 m 本來就在合法區裡(b ≤ 3)。 那你根本不用理這條約束 —— 答案跟沒約束一樣,就是 m = (2, 1),成本 0。
這時 ∇f = 0,所以 λ = 0。 最佳化 01 關的老規則直接可用。
② 碰到了:退化成等式
谷底不合法(b > 3),你被推到圍欄上。 最優解一定正好落在線上(往裡面多走一步只會更貴),所以整個問題退化成 ③④⑤ 的等式版本。
這時 λ > 0:圍欄真的在往上推你。
先給「鬆弛量」一個名字:鬆弛量 = g(v) − b,意思是「你比要求多做了多少」。 合法就代表鬆弛量 ≥ 0。
白話:一條不等式約束,不是綁住你、就是沒作用,不會有中間狀態。 沒有「有點擋到你」這種事 —— 要嘛你貼在圍欄上、它有價格;要嘛你離它有距離、它免費。
把三件事寫在一起,就是這關的最終工具 —— KKT 條件(Karush–Kuhn–Tucker,三個人的姓):
ⓑ 符號: λ ≥ 0,且 g(v) ≥ b(合法)
ⓒ 互補鬆弛: λ · (g(v) − b) = 0 三條合起來把「無窮多個候選」壓成「幾個可以逐一檢查的情況」。 λ 為什麼只能 ≥ 0:約束是「至少 b」,所以它只會把你往 g 變大的方向推。 推的力氣不可能是負的 —— 負的意思是「約束在幫你」,那它就不是約束了。
- 你會看到什麼:
- 淡橘底的那一片是可行區(x + y ≥ b), 橘線是它的邊界;灰點是谷底 m; 綠點是 KKT 解;紫圈是穿過解的等高線; 青色小點是暴力驗證:在畫布裡用固定種子撒點、只留合法的,看有沒有人比綠點更便宜。
- 你可以動什麼:
- 滑桿把圍欄推來推去(改 b)。四顆按鈕跳到代表性位置。 「再撒一次」換一個隨機種子(種子值顯示在讀數盤最後一行,同一顆種子永遠給同一批點)。
- 要看出什麼:
- 把 b 從 0 慢慢拉到 8,盯著讀數盤那三行 KKT 條件的燈號,以及 λ 和鬆弛量的乘積永遠是 0。 在 b = 3 之前,綠點完全不動(黏在谷底)、λ 一直是 0; 過了 3 之後綠點才開始被推走、λ 才開始長大。
青色那些點是「隨機撒、只留合法的」。約束不作用的時候(b ≤ 3), 最優解在區域內部,隨機撒點還算撒得到附近,暴力法的最好成績離真答案不遠。
但約束一作用(b > 3),最優解躲在邊界線上 —— 一條線的面積是 0, 隨機撒點撒不到。你會看到暴力法的最好成績跟 KKT 解差一截,而且怎麼加點都補不上。 這就是為什麼要解方程式,而不是亂試。
回頭看最佳化 03 關那兩條約束
現在你有工具了,回去看 最佳化 03 關的投資組合問題。那裡有兩條約束, 而它們正好是這一關的兩種型別:
| 最佳化 03 關的約束 | 型別 | 作用狀況 | 意思 |
|---|---|---|---|
| w1 + w2 + w3 = 1 錢要分完 |
等式 | 永遠作用 | 你被釘在那張平面上,沒有「不作用」的選項。所以它的乘子可正可負, 就像 ⑤ 的 λ。 |
| wi ≥ 0 不能放空 |
不等式 | 有時作用 | 某一檔的最佳權重本來就是正的 → 這條不作用、乘子 0; 本來想放空(負的)→ 這條作用,那一檔被釘在 0、乘子 > 0。 |
最佳化 03 關 ⑦ 那個解法裡有一行提示,它會告訴你 「有幾檔權重被壓到 0 —— 『不能放空』這條約束正在作用」,或者反過來說 「三檔權重都是正的,『不能放空』沒有在擋你」。
當時那句話只是描述現象。現在你知道它在講的是互補鬆弛: 「權重被釘在 0」=鬆弛量為 0 =那條約束的乘子 > 0; 「權重是正的」=鬆弛量 > 0 =那條約束的乘子 = 0。 去把那個滑桿拉一遍,看提示怎麼在兩句話之間切換 —— 你現在看得懂它為什麼只有兩種說法了。
⑦ 數學長怎樣:當工具用的操作手冊
先給程式版。這就是這一頁所有數字的來源,沒有省略:
// ---- 設定:目標與約束 ----
// f(v) = ½·(v − m)ᵀ A (v − m) 目標:要壓到最小的成本
// ∇f(v) = A(v − m) 梯度(留了 ½ 所以沒有多餘的 2)
// g(v) = pᵀv 約束的那個量;這裡 p = (1, 1),g = x + y
// ∇g(v) = p 線性約束的梯度是常數向量
// ---- 等式約束 g(v) = b:直接解 ----
// 條件 ⓐ:A(v − m) = λ·p → v = m + λ·A⁻¹p
// 代進約束:pᵀm + λ·(pᵀA⁻¹p) = b
const s = dot(p, matVec(inv(A), p)); // 只跟 A、p 有關的常數,算一次就好
const lam = (b - dot(p, m)) / s; // ← 乘子
const v = add(m, scale(matVec(inv(A), p), lam)); // ← 最優解
const phi = f(v); // ← 最低成本
// ---- 不等式約束 g(v) ≥ b:檢查兩種情況 ----
if (dot(p, m) >= b) {
// 情況 ①:谷底本來就合法 → 約束不作用
return { v: m, lam: 0 }; // 鬆弛量 > 0,所以 λ = 0
} else {
// 情況 ②:谷底不合法 → 退化成等式,重用上面那三行
return { v: v, lam: lam }; // 鬆弛量 = 0,所以 λ > 0
}
符號版。三條 KKT 條件,逐字翻譯:
ⓐ ∇f(v*) = λ ∇g(v*)
ⓑ λ ≥ 0, g(v*) ≥ b
ⓒ λ · (g(v*) − b) = 0 v* —— 上標星號=「最優的那個 v」,跟一般的 v 區分開。
ⓐ 停滯(stationarity) —— 目標的爬坡方向被圍欄的法向量整條吃掉,沒有沿著可行方向的殘量。
ⓑ 符號與可行 —— 乘子不能是負的(不等式只會單向推你),而且答案本身要合法。
ⓒ 互補鬆弛 —— λ 和鬆弛量至少一個是 0。「不是綁住你、就是沒作用」。
多條約束的話,每一條配自己的 λi,ⓐ 變成 ∇f = Σi λi ∇gi,ⓒ 每一條各自成立。
因為它在傳統寫法裡是乘在約束前面的那個數。把目標和約束綁成一個函數:
這個包裝很漂亮,但你不需要它才能做事。你需要的是那三條 KKT 條件,和「λ 是影子價格」這句話。
⑧ 工程師的「原來如此」
你在排訓練任務。目標是壓總完成時間,約束是「這個 team 的 GPU 配額只有 8 張」。 排程器解完之後,除了「哪個 job 放哪張卡」之外,它手上還有一個數字:GPU 這條約束的 λ。
那個 λ 直接回答了你去跟 infra 吵架時需要的東西:配額從 8 張加到 9 張,總完成時間會少多少。 如果 λ 幾乎是 0,代表你的瓶頸根本不是 GPU(可能是資料載入或某個序列依賴)—— 這時候再買卡是純浪費,而且這件事你光看 utilization 圖是看不出來的。
反過來,如果 λ 很大,你就有一個可以寫進提案的數字: 「多一張卡每天省 X 分鐘 pipeline 時間」。這比「感覺卡不夠」有力一百倍。 雲端計費、rate limit、connection pool 大小、Kubernetes resource quota —— 全都是同一個結構。
SVM 要找一條把兩類分開、而且離兩邊都盡量遠的線。寫成最佳化問題: 目標是最小化 ½·|w|²(|w| 就是那個係數向量的長度, 壓小它等價於把邊界推寬), 約束是每一個訓練樣本一條不等式:「這一點要落在自己那一側,而且離邊界至少一個單位」。
一萬筆資料就是一萬條不等式,每一條配一個 λi。 互補鬆弛說:只有「正好貼在邊界上」的那幾點,λi 才會大於 0; 其他所有點的 λi 都是 0。
而最終解 w = Σi λi yi xi —— λi = 0 的點在這個加總裡貢獻 0。 所以一萬筆資料裡,真正決定那條線的可能只有幾十筆。那幾十筆就叫支援向量(support vector)。
這也解釋了兩件 SVM 的實務現象:模型可以存得很小(只留支援向量); 以及刪掉一個非支援向量,模型完全不變(它的 λ 本來就是 0,刪了沒差)。 「support」這個字就是從互補鬆弛來的。
踩坑
新手最常犯的錯:解完方程式,把 λ 當成答案的一部分交出去。 不是。答案是 v*(你要做什麼),λ 是價格(那條約束值多少)。
兩者單位都不一樣。這一頁 v* 的單位是「萬件」, λ 的單位是「萬元 ÷ 萬件」=每萬件的邊際成本。 把價格當數量交出去,下游一定爆。
這一頁的地形是凸的(一個碗),所以 KKT 條件找到就是全域最優。 這是運氣好,不是常態。
地形一旦不凸(多個谷、鞍點、山脊),KKT 就退化成必要條件: 最優解一定滿足它,但滿足它的點不一定是最優解 —— 可能是局部最小、可能是鞍點、 甚至可能是局部最大(那裡的梯度也跟法向量平行)。
實務上的意思:你解出一組 KKT 點之後,還要逐個算 f 值比大小,或者從多個起點重跑。 「解出來了」不等於「找到最好的了」。凸不凸這件事要在動手前先確認,不是解完再說。
目標函數寫錯,答案通常會很離譜,你一眼就看出來。約束寫錯不會 —— 它會給你一個「看起來很合理」的錯答案。
最常見的三種:不等式方向反了(≥ 打成 ≤,可行區換到另一邊, 答案跑到對面去但依然「像個答案」);漏了一條(例如忘了寫非負,於是解裡出現負產量、 而下游系統很可能默默把它當 0 處理);單位不一致(一邊萬件、一邊件,差一萬倍, 約束變成幾乎不作用,λ 也就一直是 0)。
自保方式很簡單:解完之後把每條約束逐條代回去印出來 —— 印 g(v*)、 印鬆弛量、印 λ。這一頁的讀數盤就是在示範這件事。 如果每條約束的 λ 都是 0,先別高興,那通常代表你的約束根本沒接上。
λ 是切線斜率,是「無窮小的一步」的兌換率。 拿它去估「配額直接加一倍」會錯得很難看。
⑤ 那張表就是證據:b = 6 時 λ = 2.733, 但實際多接一整萬件要多花 3.189,低估 16.7%;b = 4 時更慘,低估 50%。 而且方向是系統性的:凸目標的 φ 往上彎,所以 λ 永遠低估大步的成本。
更糟的情況是「作用集」變了:幅度一大,可能有另一條原本不作用的約束突然開始擋你, 這時 λ 會整個跳掉,連「低估」都談不上(φ 在那裡有折點,斜率不連續)。 所以拿 λ 做決策時,一律講清楚適用範圍: 「在目前配置附近,每多一張卡值 X」,不要講成「每張卡都值 X」。
⑨ 這關回答了什麼
為什麼有約束時梯度不是 0?
「梯度為 0」的真正含意是「往任何方向走都不會更低」。它成立的前提是:你真的可以往任何方向走。
約束把方向拿掉了。在 x + y = 6 這條線上,你只剩兩個方向可走(沿線前後), 原本垂直於線的那兩個方向是違法的。所以最優點只需要滿足「沿線走不會更低」, 垂直方向剩下多少陡度,都無所謂 —— 因為你不能往那邊走。
② 的一維例子是最乾淨的版本:x 必須等於 5, 可走的方向剩零個,所以 f′(5) = 6 完全不影響「5 就是答案」。 ③ 則是二維版:最低點的 ∇f = (2.733, 2.733)、長度 3.87, 但它沿線的分量是 0。
相切為什麼是最優的條件?
兩種講法,同一件事。
幾何講法:把 f(v) ≤ c 想成「花 c 元能走到的範圍」,是一個實心橢圓。 把 c 從小放大,橢圓長大,它第一次觸到約束線的那一刻只會摸到一點 —— 再小一點就完全碰不到。 那個 c 就是最小可行成本。④ 的滑桿讓你親手看交點從 0 個變 1 個變 2 個。
代數講法:如果等高線在該點穿過(不是相切)約束線, 那等高線和約束線就會在該點附近分出「比較低的那一側」,你沿線往那邊走就能更便宜 —— 所以那一點不是最優。 只有相切時,沿線兩邊都是往上,才走不掉。
而相切 ⇔ 兩條曲線方向相同 ⇔ 兩條的法向量平行 ⇔ ∇f ∥ ∇g。 ④ 的讀數盤現場算兩支的 |sin θ|:把 c 拉到 8(交點 2 個)時是 0.4446 和 0.7420,拉到 5 是 0.2682 和 0.3738;按「跳到相切」的那一刻,它掉到 0(外積是 10−16 量級的浮點殘渣,讀數盤直接印給你看)。
λ 是什麼意思?
兩個意思,都要記住。
算的時候它是比例:∇f = λ∇g, λ 就是「目標的梯度是約束法向量的幾倍」。 ⑤ 讓你直接用眼睛除:(2.733, 2.733) ÷ (1, 1),兩格都是 2.733。
用的時候它是價格:λ = dφ/db, 「約束的常數 b 多一單位,最低成本會多多少」。這叫影子價格。 ⑤ 的讀數盤把「公式的 λ」跟「重新求解再做數值微分得到的斜率」並排, 兩者對到小數第六位以上 —— 這不是類比,是同一個數字。
工程上第二個意思更有用:它直接回答「這條限制解開一點值不值得」。 λ ≈ 0 的約束,放寬它是白花錢。
λ 的正負代表什麼?
等式約束下 λ 可正可負,它就是 φ(b) 的斜率的正負:
- λ > 0:b 變大會讓你更貴 —— 約束在花你的錢,你想跟客戶談少一點。這一頁是 b > 3 的情況。
- λ = 0:約束剛好貼在無約束谷底上,鬆不鬆它都一樣。這一頁是 b = 3。
- λ < 0:b 變大反而更便宜 —— 約束是從另一邊壓你(要求太少、迫使你減產)。這一頁是 b < 3。
不等式約束(g ≥ b)下 λ 只能 ≥ 0。 原因很直白:不等式只會單向推你。如果算出來 λ < 0,那代表「約束在幫你」, 意思是你其實想往合法區內部走 —— 那這條約束根本沒在擋,正確答案是把它當不作用、令 λ = 0。 所以 λ < 0 在不等式問題裡不是一個答案,是一個「你猜錯作用集了」的訊號。
不等式約束跟等式差在哪?
差在「約束有沒有在作用」變成一件要判斷的事。
等式(g(v) = b):你被釘在那條線上,永遠作用,沒有選擇。 乘子可正可負。最佳化 03 關的「錢要分完」就是這種。
不等式(g(v) ≥ b):可行區是一整片半平面。可能有兩種結局 —— 谷底本來就在裡面(不作用,答案跟沒約束一樣,λ = 0), 或谷底在外面(作用,退化成等式,λ > 0)。乘子只能 ≥ 0。 最佳化 03 關的「不能放空」就是這種。
所以解不等式問題的實際流程是:先猜哪些約束在作用,把它們當等式解, 然後檢查 ⓑⓒ 有沒有被違反;違反了就換一組猜。真正的 solver 管這個叫 active-set method, 最佳化 03 關 ⑥ 那個「取區間端點還是取拋物線頂點」的判斷就是它的小型手工版。
互補鬆弛白話怎麼講?
一條不等式約束,不是綁住你、就是沒作用,不會有中間狀態。
拆成兩半來記:
- 你離圍欄有距離(鬆弛量 > 0)→ 那它現在對你免費,λ = 0。
- 它有價格(λ > 0)→ 那你一定正貼在圍欄上,鬆弛量 = 0。
寫成一行就是 λ · (g(v) − b) = 0 —— 兩個非負的數乘起來是 0,就代表至少一個是 0。
生活版:房租上限對你有沒有影響?如果你本來就只想付上限以下的錢,那條規定對你不存在(免費)。 如果它把你卡住了,那你一定正好付在上限(貼著)。不會出現「上限有點影響我但我沒付到上限」這種狀態。
⑥ 的讀數盤永遠印著 λ × 鬆弛量, 你把滑桿從頭拉到尾,那個乘積一直是 0,只是「哪一個因子是 0」會在 b = 3 換手。
λ 真的可以直接從畫面上讀出來嗎?
可以,而且這一頁刻意設計成讀得出來。因為約束是 g = x + y, 它的梯度永遠是 (1, 1) —— 除以 1 等於不用除。
所以在 ⑤ 的讀數盤,你看到 ∇f = (2.733, 2.733) 的那一刻, λ 就是 2.733,不需要任何額外計算。兩個分量必須相等, 不相等就代表那一點不是最優解(兩支向量不平行)。
換成別的約束(例如 最佳化 01 關的 x + 2y = b, ∇g = (1, 2))就得真的除:λ = ∇fx / 1 = ∇fy / 2, 兩式要算出同一個數。這仍然是一個很好用的手動檢查:拿你 solver 吐出來的解, 算一次 ∇f,看它是不是 ∇g 的倍數。不是,就是哪裡錯了。
那我什麼時候該自己寫 KKT,什麼時候直接叫 solver?
約束少(一兩條)而且結構簡單(線性)時,自己解通常更好:你會拿到閉式解, 沒有迭代、不會不收斂、不用調參數,而且順手拿到 λ。 ⑦ 那段程式碼總共十行,就是完整解法。
約束一多(幾十條以上、混著等式與不等式),「猜哪些在作用」的組合數會爆炸,
這時候用現成的 QP / 內點法 solver。但你還是需要這一關 ——
因為 solver 會把 λ(常常叫 dual 或 duals)一起回傳給你,
而看不看得懂那組數字,決定了你是在用 solver 還是在拜 solver。
另外,λ 也是最好的 debug 訊號:所有 λ 都是 0 → 約束大概沒接上;某條 λ 大得離譜 → 那條約束可能單位寫錯或本身矛盾。
工具齊了。你現在手上有:目標函數與等高線(最佳化 01 關)、梯度為 0(最佳化 01 關)、 相切條件、乘子 λ、影子價格、KKT 三條件、互補鬆弛(這關)。
但這一頁的問題是我編給你的:兩條產線、一條線性約束、一個漂亮的碗。 下一關把它套在一個真實形狀的問題上 —— 最佳化 03 關的投資組合:目標是 wTCw(用的是 03 關的共變異數矩陣), 約束是 Σw = 1(等式,永遠作用)和 w ≥ 0(不等式,有時作用)。
去的時候帶著這一關的眼睛:看到「相切」就想到 ∇f = λ∇g, 看到「某檔權重被壓到 0」就想到互補鬆弛。那頁的每一張圖你都已經看得懂了。