分類與筆記:讓 agent 成為學習夥伴
前兩關你在檢查 agent,這一關反過來,讓它當你的推導夥伴: 寫程式、驗證公式、然後把整個推導過程整理成一份你之後真的會回去看的筆記。
先理解分類、損失與梯度的意思,再完成程式與梯度檢查;LaTeX 筆記若現場時間不夠,可以課後補完。概似推導、誤差階數與線性可分的完整證明放在深入補充,課後可以慢慢讀;不必先會全部推導才開始操作。
任務
從 digits 裡挑出數字 3 和 8(357 筆),手刻一個 logistic 迴歸做二元分類。
二元分類是從兩個答案中選一個。這裡把像素值當輸入、數字 3 或 8 當已知答案;模型先給出機率,再依機率判斷類別。損失函數用一個數字表示預測和答案差多少,訓練就是尋找讓它變小的參數。
梯度記錄損失對各個參數的變化率,幫我們決定參數往哪邊調。本關會用小幅改動參數的方法,檢查程式算出的梯度是否合理。
「二元分類」在做什麼?logistic 迴歸為什麼長那樣?
跟 Lab 1 的分群不同,這次我們有答案:每張圖都知道它是 3 還是 8。 目標是學一個規則,看到新的圖能判斷它是哪一個。這叫監督式學習。
最直覺的想法,以及它的問題
最簡單的規則是「算一個加權總和 $z=\mathbf{w}^\top\mathbf{x}+b$,$z>0$ 說是 8,否則說是 3」。 這就是一個超平面把空間切兩半,完全是線代的東西。
問題是:這個規則只吐 0 或 1,沒有「有多確定」的資訊, 而且它不連續,沒辦法微分,也就沒辦法用微積分找最佳的 $\mathbf{w}$。
sigmoid:把實數壓成機率
所以改成先算 $z$,再用一個函數把它壓到 $(0,1)$ 當成「是 8 的機率」:
$$\sigma(z)=\frac{1}{1+e^{-z}}$$這個函數有幾個剛好合用的性質:
- $z\to+\infty$ 時趨近 1,$z\to-\infty$ 時趨近 0,$z=0$ 時剛好 0.5
- 處處可微,而且導數漂亮得不像話:$\sigma'(z)=\sigma(z)\bigl(1-\sigma(z)\bigr)$ (值得自己動手驗一次,就是商法則)
- 單調遞增,所以「$\sigma(z)>0.5$」跟「$z>0$」是同一件事, 決策邊界仍然是那個超平面,沒有變複雜
名字裡有「迴歸」卻在做分類,是歷史遺留的命名,別被騙了。
什麼是「概似」?為什麼要取 log?
模型說「第 $i$ 張圖是 8 的機率是 $p_i=\sigma(z_i)$」。 如果它的真實答案 $y_i=1$(是 8),模型給的機率就是 $p_i$; 如果 $y_i=0$(是 3),模型給的機率是 $1-p_i$。兩種情況可以合寫成
$$P(y_i\mid\mathbf{x}_i)=p_i^{\,y_i}(1-p_i)^{1-y_i}$$(把 $y_i=0$ 或 $1$ 代進去檢查一下,就是上面那兩句話。) 假設各筆資料獨立,看到整批資料的機率就是連乘:
$$L(\mathbf{w})=\prod_{i=1}^{n}p_i^{\,y_i}(1-p_i)^{1-y_i}$$這個 $L$ 叫概似(likelihood)。 「最大概似估計」就是:挑一組參數,讓「模型認為這批資料會發生」的機率最大。 這是統計學裡最基本的參數估計原則。
為什麼取 log
- 連乘變連加:$\log\prod=\sum\log$,微分立刻好做很多
- 數值不會爆炸:許多介於 0 和 1 的數連乘,可能小到浮點數無法表示而變成 0
- 不改變答案:$\log$ 是嚴格遞增的,最大化 $L$ 跟最大化 $\log L$ 的解完全相同
習慣上會再取負號、除以 $n$,把「最大化概似」變成「最小化平均損失」, 這樣就跟其他最佳化問題長得一樣了。得到的就是本關要驗證的那個 $L(\mathbf{w})$。
接下來找最小值要用的梯度、偏微分、鏈鎖律,全部是你微積分課上的東西, 只是變數多了一點。
梯度下降在做什麼?
你在微積分學過:梯度 $\nabla L(\mathbf{w})$ 指向函數上升最快的方向。 那想下降就往反方向走:
$$\mathbf{w}\leftarrow\mathbf{w}-\eta\,\nabla L(\mathbf{w})$$$\eta$ 是步伐大小(learning rate),本關用 0.5。反覆走,直到走不動為止。
為什麼不像 k-means 那樣「令導數為零、直接解」?因為 $\nabla L=\mathbf{0}$ 在這裡是一組沒有閉式解的非線性方程式($\sigma$ 卡在裡面), 只能用迭代的方式逼近。
這也是為什麼「驗證梯度公式對不對」這件事在本關這麼重要: 梯度是整個過程唯一的方向感,公式寫錯,後面跑再多次都是往錯的方向走。
過擬合與正則化是什麼?(本關最後會用到)
過擬合:模型把訓練資料背下來了,包括裡面的雜訊, 所以在訓練資料上表現完美,換一批新資料就崩。 症狀之一是權重變得很大:模型用極端的係數去遷就個別樣本。
正則化的做法是在要最小化的目標裡,加一項懲罰大權重的東西。 最常見的 L2 正則化:
$$L_{\text{reg}}(\beta,b)=L(\beta,b)+\frac{\lambda}{2}\|\beta\|^2$$這裡 $\beta$ 是特徵的權重,$b$ 是截距;平方範數只計算權重,不包含截距。$\lambda$ 越大,懲罰越重。 效果是逼模型「除非證據夠強,否則不要把係數調大」。
本關會看到正則化的另一個作用,比防過擬合更基本:
在本例兩個類別都存在時,正的 L2 懲罰能讓原本沒有有限最小值點的問題得到有限且唯一的解。
稍後 sklearn 的 L2 範例,在未加樣本或類別權重時,對應 $\lambda=1/(nC)$,其中 $n$ 是訓練樣本數;$C$ 越小,懲罰越重。
sklearn 的 fit / predict 慣例
scikit-learn 裡幾乎每個模型都長同一個樣子,學會一個就會全部:
| 寫法 | 意思 |
|---|---|
m = LogisticRegression(...) | 建立模型,括號裡放超參數(例如 C、max_iter) |
m.fit(X, y) | 訓練。把資料餵進去,模型把學到的參數存在自己身上 |
m.coef_ m.intercept_ | 學到的權重與截距。結尾有底線是 sklearn 的慣例,代表「訓練完才有的東西」 |
m.predict(X) | 吐出預測的標籤(0 或 1) |
m.predict_proba(X) | 吐出機率,兩欄分別是「是 0 的機率」「是 1 的機率」 |
m.score(X, y) | 準確率,也就是猜對的比例 |
PCA 那類轉換器則是 fit 之後用 transform(做轉換)
與 inverse_transform(轉回去)。Lab 2 用的就是這組。
把像素矩陣加上一欄 1 得到 $X_b$,並把截距放在參數向量 $w$ 的最後一項。令 $z=X_bw$、$\sigma(z)=\dfrac{1}{1+e^{-z}}$,平均對數損失與其梯度為
$$L(w)=\frac1n\sum_i\Bigl[\log\bigl(1+e^{z_i}\bigr)-y_iz_i\Bigr], \qquad \nabla_wL=\frac1nX_b^\top\bigl[\sigma(X_bw)-y\bigr]$$agent 會很快寫出來。問題是:你怎麼知道它沒寫錯?
動手
先說明公式,再要求程式
我要手刻一個二元 logistic 迴歸,資料是 digits 裡的 3 和 8。
先用兩三句話說明:損失函數衡量什麼,梯度告訴我們什麼。
依本頁公式 L(w) = (1/n) Σ [log(1+e^z) - y z],
以及 ∇L = (1/n) Xb^T (σ(Xb w) - y) 實作。
說明後寫成 lab3_logistic.py。本輪只實作資料讀取、loss(w) 與 grad(w),
先不要訓練、切分資料、增加其他演算法或產生新資料。
使用全部 digits 的 3 與 8:像素除以 16,8 設為 1、3 設為 0;
最後加一欄 1 作為截距,所以 Xb 有 65 欄。loss 使用平均值。
用 uv run python lab3_logistic.py 執行,印出資料 shape 後停止,等我給下一步。
先對照本頁公式,確認損失與梯度各自的意義。待會的數值檢查只能檢查「程式內部是否一致」,不能單獨確認「公式本身正確」。完整的概似推導可在課後慢慢閱讀。
有限差分梯度檢查
這是數值方法裡最乾淨的自我驗證:用損失函數本身去逼近梯度,跟解析梯度比。
請加一段有限差分梯度檢查:
- 用中央差分,檢查**全部** 65 個維度(不要只抽樣幾個)
- 步長 h 掃過 1e-2 到 1e-9,每個 h 印出最大絕對誤差與最大相對誤差
- 使用 np.random.RandomState(0).normal(size=65)*0.01 作為檢查點 w0
- 最大絕對誤差小於 1e-8 記為 PASS,否則記為需調查;相對誤差分母至少取 1e-12
- 做成表格,用 uv run python lab3_logistic.py 執行
- 本輪只加梯度檢查,不訓練、不新增資料或演算法;完成後停止
深入:為什麼步長太大或太小都可能 FAIL
為什麼兩頭都會 FAIL
中央差分的總誤差有兩個來源,方向相反:
$u$ 是浮點精度(float64 約 $2.2\times10^{-16}$)。 $h$ 太大時泰勒展開的高階項來不及被忽略; $h$ 太小時 $L(w+h)$ 與 $L(w-h)$ 幾乎相等,相減會損失有效位數。 令導數為零可得最佳量級 $h\sim u^{1/3}\approx 6\times10^{-6}$。 這份輸出的最佳位置約在 $10^{-4}$ 附近;實際位置還受常數、資料與誤差判準影響。
上表兩頭的 FAIL,程式完全沒問題。
所以看到梯度檢查 FAIL 時,正確的說法是「有不一致,要查原因」:
可能是公式錯、步長不當、捨入誤差,或該維梯度本身接近 0。
反過來說,PASS 也只提供局部數值證據:如果 loss() 和 grad()
一起照著同一個錯誤的目標函數寫,這關照樣全過。因此仍要對照目標公式,不能只看數值 PASS。
訓練,然後撞上一個陷阱
現在用梯度下降訓練(lr=0.5,20000 次迭代),
並在第 100、1000、5000、10000、20000 次時印出 ||w|| 與損失。
訓練完跟 sklearn 的 LogisticRegression 比對。
注意 sklearn 1.8 起 penalty 參數已棄用,無正則化要用 C=np.inf。
損失一直往 0 掉,但 $\|w\|$ 一直長,沒有要停的跡象。 而 sklearn 算出來的 $\|w\|$ 是 97.9,是手刻版的五倍。 兩邊準確率都是 1.0000。誰對?
這份資料能被一個線性邊界完全分開;下面的補充用程式檢查這件事。現場先觀察係數持續增大,再比較預測結果。
深入:用線性規劃確認可分性與推導其影響
證明它真的線性可分
「$\|w\|$ 一直變大」只是個徵兆,不是證明。要真的確認,解一個線性可行性問題: 以下 $\mathbf{x}_i$ 表示已加上常數 1 的第 $i$ 筆資料,$v$ 的最後一項是截距。令 $t_i=2y_i-1\in\{-1,+1\}$,找 $v$ 使得
如果這組不等式有解,就代表存在一個嚴格分隔超平面。
請用 scipy.optimize.linprog 解這個線性可行性問題:
找 v 使得對所有 i 都有 t_i * (x_i · v) >= 1,其中 t_i = 2*y_i - 1。
如果有解,v 的最後一項是截距;印出幾何間隔 min(t*(Xb@v)) / ||v[:-1]||,分母不含截距。
然後對找到的 v,計算 c = 1, 5, 20, 100, 500 時的 L(c*v)。
這就是完整的論證:存在嚴格分隔向量 $v$,沿著它放大 $c$,損失 $L(cv)=\frac1n\sum_i\log\bigl(1+e^{-c\,t_i\mathbf{x}_i^\top v}\bigr)\to 0$, 而任何有限的 $w$ 都給出 $L(w)>0$。
對線性可分的資料,無正則化 logistic 迴歸的最大概似估計不存在有限解。 所以「手刻係數 vs sklearn 係數」根本沒有標準答案可以比。兩邊都只是在往無窮遠的路上停在不同位置。
「把 $w$ 放大損失一定更小」這句話只在 $w$ 已經分對全部樣本時才成立。 如果還有分錯的點,放大只會讓那些點的損失爆掉。 另外,這個結論只適用於這 357 筆樣本,不代表所有手寫的 3 和 8 都線性可分。
那準確率都 1.0,是不是代表兩邊一樣?
請比較手刻與 sklearn 的:
1. 預測標籤是否完全一致
2. 預測機率的最大差
3. 兩個權重向量的夾角餘弦
所以「兩邊準確率都是 1.0」不代表決策邊界相同。 不同的超平面可以把同一批訓練資料分對,但對新資料的機率預測會不一樣。 準確率相同只能算是「表現檢查」,不能當作實作正確的證明。
正則化把問題變回有解
加上 L2 正則化(sklearn 用 l1_ratio=0, C=1.0)再跑一次,
比較 ||w|| 跟準確率。
$\|w\|$ 從 97.9 掉到 6.0,準確率完全沒變。
這裡 sklearn 的 L2 懲罰作用於不含截距的特徵權重 $\beta$,以本章平均損失的記法,懲罰項為 $\|\beta\|^2/(2nC)$。上方顯示的 $\|w\|$ 則仍包含截距,方便與前面的輸出對照。
在本例兩類樣本都存在的條件下,加入正的 L2 懲罰後有有限且唯一的解。它除了限制權重大小,也解決了這個例子原本沒有有限最小值點的問題。
最後:把它變成一份筆記
把今天已完成的程式、檢查與發現整理成筆記。 這些東西如果只留在終端機的捲動紀錄裡,下週就沒了。
請把今天已完成的步驟整理成 notes/logistic.md,寫給之後複習的我看。
請說明:我們用什麼資料、損失與梯度各代表什麼、梯度檢查看到了什麼,
以及為什麼準確率相同還不能判斷兩個模型完全相同。
引用這次實際執行的指令、輸出與數字,數學式用 $...$ 與 $$...$$。
沒有完成的推導或檢查列在「接下來想弄懂的事」,不要寫成已完成。
最後給我一個可以不用看筆記就回答的問題,答案分開寫。
課後:補成包含完整推導的筆記
完成前面的深入補充後,再用這份需求擴充筆記。範例中的數值要核對自己實際的執行結果。
請把今天這一輪的推導與發現整理成 notes/logistic.md,內容包含:
1. 從負對數概似到 L(w) 的完整推導(用 $...$ 與 $$...$$ 寫數學式)
2. 梯度的推導
3. 有限差分梯度檢查:原理、誤差分解 E(h) ≈ A h² + B u/h、為什麼兩頭都會 FAIL
4. 線性可分導致 MLE 不存在的完整論證(含那個「只在已分對時成立」的但書)
5. 為什麼準確率相同不代表決策邊界相同(附上我們實測的機率差 0.0341)
6. 正則化怎麼讓問題重新有解
寫給三個月後的我看,那時我已經忘記細節了。
因為檔案可以進 git。下次你叫 agent 補充或修改這份筆記,
git diff 會告訴你它到底改了哪幾行。
你不必重讀整份文件去找差異,也不會不知不覺讓它把你原本寫對的東西改掉。
筆記因此變成會累積的資產,而不是散落在各個對話視窗裡的碎片。
詳見第 07 章。
課後若想保留筆記的修改歷程,再依Git 安全網建立版本紀錄。現場先確認 notes/logistic.md 存在,並讀過內容。
檢查清單
如果還有時間
課後練習:從概似推導損失與梯度
理解前面的操作後,可以請 agent 陪你逐步推導,遇到不熟的符號就停下來問:
先定義 Bernoulli 模型的成功機率,解釋為什麼把獨立樣本的機率相乘。
從負對數概似逐步推導本頁的平均損失,再推導梯度。
每個新符號先定義,每一步說明用了哪個微分規則;先不要寫程式。- 把梯度檢查的表格畫成 log-log 圖($h$ 對最大誤差),觀察是否有步長過大或過小時誤差增加的趨勢。截斷誤差常呈 $h^2$、捨入項常呈 $1/h$ 的量級,但有限的取樣點與具體函數不保證畫出理想斜率。
- 換成 3 vs 5(比較難分的一對),重跑可分性檢查。若未找到嚴格分隔,先核對求解器狀態;這仍不足以保證無正則化的 MLE 有有限且唯一的解。還可能有準完全分離或特徵相依,因此不要直接要求兩套方法的係數一致。
- 掃過 $C$ 從 $10^{-3}$ 到 $10^{3}$,畫 $\|w\|$ 與訓練/測試準確率的曲線,看正則化路徑長什麼樣子。