ML 01・機器學習入門

視覺機器學習入門38 分鐘2026年10月4日

先備知識: 影像就是陣列

你將學到

  • 「從資料中學習」是什麼意思,以及它和手寫規則有什麼不同;全篇以手寫數字的小影像當作貫穿的例子。
  • 主要的學習範式:監督式學習(迴歸與分類)、非監督式學習(分群、降維、異常偵測)、自監督式學習,以及強化學習,包括「基於人類回饋的強化學習」(RLHF)如何調校聊天模型。
  • 一個學習問題的基本詞彙:模型 fw,b(x)f_{w,b}(x)、參數、特徵、目標值、預測值、訓練/驗證/測試集,以及成本函數。
  • 以均方誤差為成本的線性迴歸,以及用來最小化成本的梯度下降:更新規則、學習率、收斂,以及批次、小批次與隨機梯度下降的差別。
  • 為什麼要做特徵縮放;邏輯迴歸如何用 sigmoid 函數與交叉熵損失把一條直線變成分類器;如何用驗證資料發現過擬合,並用 L2 正則化修正。
  • 如何用準確率、精確率、召回率與 MSE 誠實地回報結果。

先看全貌

大多數程式都像食譜:有人把問題想清楚,再把每一個步驟寫下來。機器學習把這件事反過來。我們寫一個有彈性、裡面有很多可調整數字的程式,給它看大量「正確完成任務」的例子,再讓一個演算法去調整這些數字,直到程式把任務做好。範例取代了食譜。

為什麼重要:幾乎所有現代電腦視覺系統,從手機的臉部解鎖到醫學影像分析,都是學習出來的模型,而不是手寫的程式。本篇的幾個概念:帶有參數的模型、衡量誤差的成本、讓成本下降的梯度下降,以及用另外的資料檢查結果,正是今天訓練最大型神經網路所用的同一套想法,只是模型的規模不同。把這幾個概念弄清楚之後,本系列接下來的內容(ML 02 與各篇深度學習教學)主要就是在談「用哪一種模型」與「用哪一種成本」。

什麼是機器學習?

白話版。 機器學習系統藉由觀察資料來把任務做得更好,而不是由程式設計師寫下每一個決定。

用學習取代寫程式

這個想法由來已久。1959 年,Arthur Samuel 描述了一個學會下西洋跳棋(checkers)、而且下得比他本人還好的程式:它下了很多盤棋,並據此調整自己評估棋局的方式 [2]。他的重點是:一台能從經驗中學習的電腦,可以省去這項任務原本需要的大量細部手寫程式。那句著名的一行定義,把機器學習說成「讓電腦不需明確寫程式就能學習的研究領域」,其實是後人對這個想法的轉述,通常被歸於 Samuel,但並不是他論文裡的原句。

教科書中常見一個更具操作性的定義,包含三個部分:一個任務(例如辨識數字)、一個效能指標(辨識正確的比例),以及經驗(一批有標註的數字影像)。如果程式在這個任務上的效能隨著經驗增加而提升,我們就說它在學習 [3]、[7]。

影像的例子:規則與學習

我們貫穿全篇的例子是 scikit-learn 的 digits 資料集 [5]、[6]:1,797 張手寫數字的灰階影像,每張只有 8×88 \times 8 個像素,數值從 0 到 16。就像〈Images as Arrays〉介紹的,每張影像只是一個小小的數字陣列,攤平之後就是 64 個數字組成的向量。

假設我們只需要分辨 0 和 1。程式設計師可能會寫一條規則:「1 很細、0 很寬,所以數一數有幾欄有墨跡;不超過五欄就是 1。」這條規則容易理解,對大多數直立的 1 也是對的。但人們寫 1 的方式各不相同,有的傾斜、有的帶襯線或底座,而傾斜的 1 佔的欄數和 0 一樣多(圖 1)。這條規則大約只對了 79%。想修好它就得加更多規則(「除非它是斜的⋯⋯」),而每條新規則又會帶來新的例外。

機器學習的做法則不寫任何針對數字的規則。它挑一個有彈性的函數家族(這裡是邏輯迴歸,本篇後面會從頭實作),再讓演算法從範例中選出其中的 65 個數字。在沒看過的影像上,它把 0 和 1 完全分對;而且一模一樣的程式碼,不加任何新規則就能處理全部十個數字:

import numpy as np
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression

digits = load_digits()                       # 1,797 images, 8x8 pixels, values 0..16
X, y = digits.data, digits.target            # X: (1797, 64) flattened pixels; y: 0..9

# Hand-written rule for "0 versus 1": a 1 is thin, a 0 is wide.
keep = (y == 0) | (y == 1)
imgs, labels = digits.images[keep], y[keep]
width = (imgs.max(axis=1) > 4).sum(axis=1)   # how many columns contain ink
rule = np.where(width <= 5, 1, 0)
print("hand rule, 0 vs 1:", round((rule == labels).mean(), 3))           # 0.789

# Learned rule for the same task, judged on images it never saw.
X01 = imgs.reshape(len(imgs), -1)
Xa, Xb, ya, yb = train_test_split(X01, labels, test_size=0.3, random_state=0)
print("learned, 0 vs 1:", LogisticRegression(max_iter=1000).fit(Xa, ya).score(Xb, yb))   # 1.0

# The same recipe scales to all ten digits with no new rules.
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.25, random_state=0)
print("learned, 10 digits:",
      round(LogisticRegression(max_iter=5000).fit(X_tr, y_tr).score(X_te, y_te), 3))  # 0.953
左:8×8 數字影像組成的格子,一列是 0、一列是 1,其中幾個 1 是傾斜的。右:0 與 1 的「有墨跡欄數」直方圖互相重疊,五欄處有一條虛線,許多 1 落在 0 的那一側。
圖 1 — 為什麼手寫規則很脆弱。左:digits 資料集中的 0 與 1;傾斜的 1 和 0 一樣寬。右:「數有墨跡的欄數」這個特徵在兩類之間大量重疊,所以任何門檻都會犯很多錯。學習出來的模型則同時使用全部 64 個像素。

這個小實驗包含了支持機器學習的全部理由:當規則難以用文字說清楚、範例卻很容易取得時,就從範例中學出規則。它也顯示了代價:學到的模型是一串 65 個數字,比「數欄數」難讀得多。

學習範式

白話版。 各種學習問題的主要差別在於學習者得到什麼樣的回饋:每個範例都有正確答案、完全沒有答案、可以從資料本身製造答案,或是只偶爾得到一個「做得好不好」的分數。

範式資料長什麼樣子學到什麼典型例子
監督式成對的 (x,y)(x, y):輸入與「正確答案」從 xx 到 yy 的函數數字影像 → 數字標籤
非監督式只有輸入 xx結構:群組、方向、離群值把相似的數字分在一起
自監督式只有輸入 xx,但把 xx 的一部分藏起來當作目標通用的表示(representation)預測被遮住的字詞或影像區塊
強化學習代理人的動作,以及動作換來的獎勵策略:在每種情況下該採取哪個動作玩遊戲、調校聊天模型

我原本的筆記列了第一、第二和第四種;自監督式學習後來已成為預訓練大型模型的主要方式,所以這裡讓它單獨佔一列 [9]、[10]。

監督式學習

在監督式學習(supervised learning)中,學習者會拿到正確答案。資料是一組範例 {(x(i),y(i))}i=1m\{(x^{(i)}, y^{(i)})\}_{i=1}^{m},其中 x(i)x^{(i)} 是輸入,y(i)y^{(i)} 是期望的輸出(稱為標籤或目標值),mm 是範例數。目標是找到一個函數 ff,使 f(x)f(x) 接近 yy,而且對資料中沒有的新輸入也一樣。最後這一點稱為泛化(generalization),它才是真正的目標;把範例背起來很容易。

非監督式學習

在非監督式學習(unsupervised learning)中沒有答案,只有輸入 {x(i)}\{x^{(i)}\}。目標是從資料中找出有意思的東西:相似範例組成的群組、精簡的描述,或是格格不入的範例。由於沒有唯一「正確」的輸出,這類方法很適合用來探索資料,但也比較難評估。

自監督式學習

自監督式學習(self-supervised learning)使用沒有標註的資料,但把一部分藏起來、要模型預測被藏的部分,藉此把問題變成監督式問題。在文字上,遮住一個字詞,從句子的其餘部分預測它。在影像上,遮住大部分的區塊再把它們重建出來:遮罩自編碼器(masked autoencoder)會遮住一張影像 75% 的區塊,訓練網路把它們補回來 [12]。LeCun 與 Misra 認為,自監督式學習讓模型能從原始觀察中累積廣泛的背景知識,因為標籤是免費得到的 [9]。

重點不在「填空」本身,而在於:一個很會填空的模型,必定已經學到很多關於資料結構的知識。這個學到的內部表示可以再用很少的標籤,調整去做許多其他任務。這正是基礎模型(foundation model)背後的做法:在大規模、廣泛的資料上訓練(通常是自監督式),再調整去做各式各樣的下游任務 [10]。GPT-3 只是被訓練來在大量文字上預測下一個字,卻能憑提示中給的幾個例子完成許多語言任務,不需要再訓練 [11]。

強化學習

在強化學習(reinforcement learning, RL)中,一個代理人(agent)與環境互動。每一步它觀察一個狀態、選一個動作,然後得到一個獎勵(一個數字)和新的狀態。沒有人告訴它正確的動作;它必須自己發現哪些動作能在長期累積較高的總獎勵,而獎勵常常在造成它的動作之後很久才出現。學到的是一個策略(policy),也就是在每個狀態下選擇動作的規則 [8]。Samuel 的跳棋程式就是這種情境的早期例子 [2]。

今天,強化學習最為人所知的用途是調校大型語言模型。在 Ouyang 等人描述的基於人類回饋的強化學習(reinforcement learning from human feedback, RLHF)[13] 中,先用人寫的示範答案對預訓練語言模型做微調(監督式學習)。接著由人對模型針對同一個提示所產生的幾個答案排序,再訓練一個獨立的獎勵模型來預測這些排序。最後用強化學習最佳化語言模型,讓它產生獎勵模型評分高的答案。作者回報,標註人員偏好他們 13 億參數的調校模型,勝過 1,750 億參數的 GPT-3 [13]。更近期,DeepSeek-R1 用大規模強化學習,搭配可自動檢查的獎勵(例如數學答案是否正確),來提升逐步推理的能力 [14]。

注意 RLHF 串起了三種範式:自監督式預訓練、監督式微調,以及強化學習。真實的系統會自由混用各種範式;上面那張表是一張地圖,而不是一道道牆。

監督式學習:迴歸與分類

白話版。 監督式學習主要有兩種。迴歸預測一個數字;分類預測一個類別。

迴歸

在迴歸(regression)中,目標值 yy 是一個實數,所以可能的輸出有無限多個。例如:預測股價、用之前的考試成績預測學生的期末成績,或從臉部影像預測一個人的年齡。最後一個例子說明:輸入可以是一張影像,輸出仍然只是一個數字。

分類

在分類(classification)中,目標值是一小組固定類別中的某一個。例如:垃圾郵件或非垃圾郵件(兩類,二元分類)、臉型的類別,或是腫瘤類型,如良性、惡性第 1 型、惡性第 2 型(多類別分類)。類別是名稱,不是數量:「第 2 類」並不是「第 1 類」的兩倍。分類器通常會對每個類別輸出一個機率,再挑最可能的那一個。

from sklearn.datasets import make_regression, make_classification
from sklearn.linear_model import LinearRegression, LogisticRegression

# Regression: the target is a real number.
X_r, y_r = make_regression(n_samples=100, n_features=1, noise=10.0, random_state=0)
reg = LinearRegression().fit(X_r, y_r)
print(reg.predict(X_r[:3]).round(1), y_r[:3].round(1))   # numbers vs numbers

# Classification: the target is one of a few labels.
X_c, y_c = make_classification(n_samples=100, n_features=2, n_redundant=0,
                               n_clusters_per_class=1, random_state=0)
clf = LogisticRegression().fit(X_c, y_c)
print(clf.predict(X_c[:5]), y_c[:5])                     # labels vs labels
print(clf.predict_proba(X_c[:2]).round(3))               # class probabilities
左:散布的資料點與一條擬合直線,縱軸是連續的目標值。中:兩種顏色的點雲被一條直線決策邊界分開。右:0/1 標籤對一個特徵作圖,最小平方直線被幾個遠處的點拉偏,而 sigmoid 曲線能很好地貼合標籤。
圖 2 — 左:迴歸讓曲線穿過數值目標。中:分類用決策邊界分開有標籤的點。右:對 0/1 標籤擬合一條迴歸直線(灰色),輸出會超出 [0, 1],而且遠處的點會拉動它跨過 0.5 的位置;sigmoid(邏輯迴歸,橘色)才是合適的工具。

為什麼不用迴歸來做分類?

把類別編成 0 和 1、擬合一條迴歸直線、大於 0.5 就判為 1,這個做法很誘人。在簡單的資料上它也許可行,但通常表現很差,原因有兩個,都能在圖 2(右)看到。第一,直線會輸出 −0.4-0.4 或 1.71.7 這類數值,它們不是機率。第二,平方誤差會懲罰那些已經分對、只是離邊界很遠的點,所以少數極端但容易的範例會把直線拉過去,讓它跨過 0.5 的位置移到錯誤的地方。本篇後面的邏輯迴歸同時解決了這兩個問題。

非監督式學習

白話版。 即使沒有答案,我們仍然可以問:哪些範例屬於同一群?怎樣用更少的數字描述每個範例?哪些範例很奇怪?

分群

分群(clustering)把相似的範例歸在一起。經典的演算法是 k-means [15]。先決定群數 KK,從 KK 個中心點出發,然後重複兩個步驟直到不再變化:把每個範例指派給最近的中心,再把每個中心移到被指派給它的範例的平均位置。每一步都只會讓範例到其中心的總平方距離

∑i=1m∥x(i)−μc(i)∥2\sum_{i=1}^{m} \big\lVert x^{(i)} - \mu_{c^{(i)}} \big\rVert^2

變小或不變,其中 μk\mu_k 是第 kk 群的中心,c(i)c^{(i)} 是範例 ii 被指派到的群。所以演算法一定會停下來,但可能停在不好的局部解,這也是為什麼要從好幾個隨機起點各跑一次。Jain 回顧五十年分群研究的文章 [15] 強調,一般而言沒有「最好」的分群演算法:「正確」的群組取決於「相似」對這份資料代表什麼意思。

一個常見的混淆:k 近鄰法(k-nearest neighbors, KNN)不是分群方法。KNN 對一個新範例分類的方式,是讓 kk 個最相似的有標籤訓練範例投票,所以它是監督式方法 [5]。k-means 的 k 是群數;KNN 的 k 是鄰居數。

降維

降維(dimensionality reduction)用更少的數字描述每個範例,同時盡量保留有用的資訊。主成分分析(principal component analysis, PCA)找出資料變化最大的幾個方向,再把資料投影到其中前幾個方向上;新的座標彼此不相關,並依序盡可能捕捉最多的變異 [16]。兩個 PCA 座標就能把 64 個像素的數字影像變成平面上的點,讓我們看得到(圖 3 中)。

異常偵測

異常偵測(anomaly detection)找出與其他資料模式不符的範例:詐騙交易、瑕疵零件、損壞的影像。Chandola、Banerjee 與 Kumar 的綜述 [17] 依照各技術對「正常長什麼樣子」所做的假設,把眾多技術加以分類。一個簡單的版本使用 PCA:在資料上擬合幾個主成分,用它們重建每個範例,再標出重建誤差大的範例,因為主成分只描述了常見的部分。

import numpy as np
from sklearn.datasets import load_digits
from sklearn.cluster import KMeans
from sklearn.decomposition import PCA

X, y = load_digits(return_X_y=True)             # y is used only to check, never to fit

# Clustering: ten groups, found without looking at y.
km = KMeans(n_clusters=10, n_init=10, random_state=0).fit(X)
purity = sum(np.bincount(y[km.labels_ == k]).max() for k in range(10)) / len(y)
print("cluster purity:", round(purity, 3))      # 0.792: most clusters are one digit

# Dimensionality reduction: 64 numbers per image -> 2 numbers.
Z = PCA(n_components=2).fit_transform(X)        # shape (1797, 2)

# Anomaly detection: images that a 20-component PCA rebuilds badly are unusual.
pca20 = PCA(n_components=20).fit(X)
err = ((X - pca20.inverse_transform(pca20.transform(X))) ** 2).sum(axis=1)
print("most unusual images:", np.argsort(err)[-5:])
左:二維的點依 k-means 分群上色,群中心以黑色叉號標示。中:digits 資料集投影到兩個 PCA 成分上,依真實數字上色,可見部分分開的群組。右:PCA 重建誤差最大的六張數字影像,看起來寫得很怪。
圖 3 — 非監督式學習。左:k-means 把點聚集在 K 個中心周圍。中:PCA 把每個 64 像素的數字壓縮成兩個數字(顏色是 PCA 從沒看過的真實數字)。右:PCA 重建誤差最大的數字,正是看起來最奇怪的那些。

程式中的純度(purity)檢查只在分群之後才用到標籤,用來看找出的群組是否和數字吻合。大約 79% 的影像落在以它自己的數字為主的群裡,即使 k-means 從沒看過任何標籤。

學習問題的詞彙

白話版。 在談數學之前,我們需要替各部分取名字:什麼進去、什麼出來、正確答案是什麼、模型是什麼,以及怎麼衡量它的錯誤。

輸入、輸出與目標值

  • 特徵(features,輸入)xx:描述一個範例的數字。對數字影像來說是 64 個像素值;對一棟房子來說是坪數和房間數。有 nn 個特徵時寫成 x=(x1,…,xn)x = (x_1, \dots, x_n),xjx_j 是第 jj 個特徵。
  • 目標值(target)yy:正確的輸出,也叫標籤或真值(ground truth)。只有監督式學習有目標值。
  • 預測值(prediction)y^\hat{y}(讀作 y-hat):模型對某個輸入的輸出。帽子代表「估計的」。
  • 訓練範例 ii:成對的 (x(i),y(i))(x^{(i)}, y^{(i)})。括號中的上標是索引,不是次方。xj(i)x_j^{(i)} 是範例 ii 的第 jj 個特徵。

模型與參數

模型是把特徵對應到預測值的函數。我們寫成 fw,bf_{w,b},表示它取決於稱為參數的可調整數字,這裡是 ww(權重,weights)與 bb(偏差,bias):

y^=fw,b(x).\hat{y} = f_{w,b}(x).

選擇 ff 的形式(直線、曲線、神經網路)是設計上的決定;選擇 ww 和 bb 的數值則是學習要做的事。由我們手動設定的數字,例如學習率或多項式的次數,稱為超參數(hyperparameters),以和學習出來的參數區分。

成本函數

損失(loss)L(y^,y)L(\hat{y}, y) 衡量一個預測錯了多少。成本函數(cost function)J(w,b)J(w, b) 把損失對整個訓練集取平均,因此它是一個數字,說明在某組參數下整個模型錯了多少。模型是 ff;成本是 JJ。把兩者分清楚可以避免很多混淆:fw,b(x)f_{w,b}(x) 吃一個輸入、吐出預測值;J(w,b)J(w, b) 吃一組參數、吐出誤差。

於是訓練的目標就很明確:找出讓 JJ 盡可能小的參數,使所有訓練範例的 y^(i)\hat{y}^{(i)} 都接近 y(i)y^{(i)}。

訓練集、驗證集與測試集

資料會切成三份,各有不同的工作:

  • 訓練集用來擬合參數;
  • 驗證集在開發過程中用來比較模型、選擇超參數;
  • 測試集只在最後使用一次,用來估計最終模型在新資料上的表現。

測試集絕不能影響任何決定,否則估計值會過於樂觀。我們會在過擬合那一節回來說明為什麼要這樣切。

線性迴歸

白話版。 在資料中畫一條直線。這條線有兩個旋鈕:斜率和高度。學習就是轉動旋鈕,直到直線盡可能貼近所有的點。

模型

只有一個特徵時,線性迴歸(linear regression)模型是

fw,b(x)=wx+b,f_{w,b}(x) = w x + b,

其中 xx 是特徵,ww 是斜率(xx 增加 1 時預測值改變多少),bb 是截距(x=0x = 0 時的預測值)。改變 ww 會讓直線傾斜;改變 bb 會讓直線上下移動(圖 4 左)。

有 nn 個特徵時,同一個模型是加權總和:

fw,b(x)=w⋅x+b=∑j=1nwjxj+b,f_{\mathbf{w},b}(\mathbf{x}) = \mathbf{w} \cdot \mathbf{x} + b = \sum_{j=1}^{n} w_j x_j + b,

其中 w=(w1,…,wn)\mathbf{w} = (w_1, \dots, w_n) 為每個特徵各存一個權重。常見的技巧是加上一個常數特徵 x0=1x_0 = 1,偏差就變成另一個權重 w0w_0,模型縮寫成 w⋅x\mathbf{w} \cdot \mathbf{x}。無論哪種寫法,「線性」指的都是對參數是線性的;之後我們會餵給它平方或三次方的特徵,它仍然是線性迴歸。

成本函數:均方誤差

對每個範例,誤差(殘差)是 fw,b(x(i))−y(i)f_{w,b}(x^{(i)}) - y^{(i)}。取平方讓誤差變成正數,並讓大誤差比小誤差受到更重的懲罰。對 mm 個訓練範例取平均,就得到均方誤差(mean squared error, MSE)成本:

J(w,b)=12m∑i=1m(fw,b(x(i))−y(i))2,J(w, b) = \frac{1}{2m} \sum_{i=1}^{m} \left( f_{w,b}(x^{(i)}) - y^{(i)} \right)^2 ,

其中 mm 是訓練範例數,(x(i),y(i))(x^{(i)}, y^{(i)}) 是第 ii 個範例。多出來的 12\tfrac{1}{2} 只是為了方便:微分平方項時出現的 2 會和它抵消,而且它不會改變最小值的位置。

把成本想像成參數上的一片地形。如果固定 bb、只改變 ww,JJ 是一條拋物線:太平或太陡的直線誤差都很大,介於中間的某個斜率最好(圖 4 中)。同時考慮 ww 和 bb 時,JJ 是一個碗,我們把它畫成由橢圓組成的等高線圖(圖 4 右)。最好的直線就在碗底。

左:資料點與三條不同斜率的候選直線,中間那條擬合得最好,並以垂直線段標出它的殘差。中:成本 J 對 w 的拋物線,三個標記對應三條直線。右:J 在 w 與 b 上的橢圓等高線,一串點代表梯度下降從邊緣走向中心的路徑。
圖 4 — 從直線到成本。左:三條候選直線;垂直線段是最佳直線的殘差。中:固定 b 時,J(w) 是拋物線,每條直線都是上面的一個點。右:J(w, b) 是一個碗(等高線),梯度下降沿著下坡走到碗底。

為什麼這個碗只有一個底

對線性迴歸來說,MSE 成本是 (w,b)(w, b) 的凸函數(convex):曲面上任兩點之間的直線段都位於曲面上方或曲面上。凸函數沒有其他的局部最小值;每個最小值都是全域最小值。這是一個特別友善的情況。對許多其他模型,包括 ML 02 的神經網路,成本地形有許多山谷,最佳化方法可能停在不是最低的那一個。原本的筆記正是在講這一點:一般而言,我們能期待的最好結果是找到一個夠好、接近全域最小值的局部最小值。

對線性迴歸,我們甚至可以用公式直接解出碗底(正規方程式,np.polyfit 就是這麼做的)。我們仍然使用梯度下降,因為在沒有公式可用時,它依然有效。

梯度下降

白話版。 你在濃霧中站在成本地形的某處。感覺一下地面往哪邊傾斜,往下坡走一小步,再重複。等地面平了,你就到底了。

更新規則

導數 ∂J/∂w\partial J / \partial w 告訴我們 ww 稍微增加時 JJ 會怎麼變:正值表示「往右是上坡」,負值表示「往左是上坡」。它的正負號給出方向,大小說明坡有多陡。梯度下降(gradient descent)讓每個參數逆著它的導數走一小步:

w←w−α∂J(w,b)∂w,b←b−α∂J(w,b)∂b,\begin{aligned} w &\leftarrow w - \alpha \frac{\partial J(w, b)}{\partial w}, \\ b &\leftarrow b - \alpha \frac{\partial J(w, b)}{\partial b}, \end{aligned}

其中 α>0\alpha > 0 是學習率(步長),←\leftarrow 表示「以右邊取代」。兩個導數都要在目前的 (w,b)(w, b) 計算完之後才更新任何一個參數,這叫做同步更新。所有偏導數組成的向量就是梯度 ∇J\nabla J,它指向上升最快的方向,所以這個更新是朝下降最快的方向移動 [3]。

對線性迴歸的 MSE 成本,連鎖律給出

∂J∂w=1m∑i=1m(fw,b(x(i))−y(i))x(i),∂J∂b=1m∑i=1m(fw,b(x(i))−y(i)).\frac{\partial J}{\partial w} = \frac{1}{m} \sum_{i=1}^{m} \left( f_{w,b}(x^{(i)}) - y^{(i)} \right) x^{(i)}, \qquad \frac{\partial J}{\partial b} = \frac{1}{m} \sum_{i=1}^{m} \left( f_{w,b}(x^{(i)}) - y^{(i)} \right).

用文字讀:ww 的梯度是「平均誤差乘以特徵」;bb 的梯度是「平均誤差」。如果預測值平均而言太高,bb 就往下調。如果預測值主要在 xx 很大的地方太高,ww 就往下調。有 nn 個特徵時,∂J/∂wj\partial J / \partial w_j 用 xj(i)x_j^{(i)} 取代 x(i)x^{(i)}。

從頭實作

import numpy as np
from sklearn.datasets import make_regression

X, y = make_regression(n_samples=100, n_features=1, noise=10.0, random_state=0)
x = X[:, 0]                                   # one feature, shape (m,)
m = len(x)

def cost(w, b):
    return ((w * x + b - y) ** 2).sum() / (2 * m)

w, b, alpha = 0.0, 0.0, 0.1
for step in range(200):
    err = w * x + b - y                       # f_wb(x_i) - y_i for every i
    dj_dw = (err * x).sum() / m
    dj_db = err.sum() / m
    w, b = w - alpha * dj_dw, b - alpha * dj_db   # simultaneous update
    if step % 50 == 0:
        print(f"step {step:3d}  J = {cost(w, b):8.2f}")

print("gradient descent:", round(w, 3), round(b, 3))   # 42.619 -0.814
print("closed form     :", np.polyfit(x, y, 1).round(3))  # [42.619 -0.814]

成本在 50 步內從大約 800 降到 57,之後就不再變化。梯度下降和精確公式的結果在小數點後三位都一致。剩下的成本不是零,因為資料中含有任何直線都無法解釋的雜訊。

收斂:導數為零的地方

「重複直到收斂」需要一個定義。在最小值處地形是平的,所以那裡的導數為零,更新 w−α⋅0w - \alpha \cdot 0 不會改變 ww。要注意變成零的是導數,不是成本:最小值處的成本只是能達到的最小數值,在上面的例子中是 57。實務上,當兩次迭代之間成本下降得比一個小容許值還少、梯度的大小低於某個門檻,或是用完固定的步數時,就停止。

一個有用的推論:越接近最小值,導數越小,所以即使 α\alpha 固定,步伐 α ∂J/∂w\alpha\, \partial J/\partial w 也會自動變小。梯度下降在接近谷底時會自然放慢。

選擇學習率

學習率是梯度下降最重要的超參數(圖 5):

  • **太小:**每一步都很小。演算法確實在下坡,但可能需要非常多步。
  • **剛好:**成本快速下降並穩定下來。
  • **太大:**一步跨過谷底跳到山谷另一側,甚至可能比起點還高。參數來回彈跳,可能永遠停不下來(越過頭,overshoot),甚至一路飛向無限大(發散,divergence)。

標準的診斷工具是學習曲線:把 JJ 對迭代次數作圖。學習率合適時,JJ 每一步都會下降。如果 JJ 曾經上升,就是 α\alpha 太大(或梯度的程式有錯)。實用的做法是以大約 3 倍的間隔嘗試數值,例如 0.001、0.003、0.01、0.03、0.1、0.3,然後挑曲線仍平穩下降的最大那一個。

三個面板,各自畫出 J(w) 的拋物線與一串相連的點代表每一步:學習率小時點沿著一側緩慢下降;學習率合適時幾步就到達谷底;學習率大時點在谷底兩側來回跳動,而且一次比一次高。
圖 5 — 同一個成本 J(w)、同一個起點、三種學習率。太小:進展緩慢。剛好:快速收斂。太大:每一步越過得更遠,迭代發散。

許多最佳化方法會在訓練過程中調整步長:用一個排程(schedule)隨時間降低 α\alpha,或像 Adam 那樣,用每個參數過去梯度的移動統計量來縮放它的步伐 [19]。這讓「接近最小值時步伐變小」成為明確的設計,而不只是副作用。

批次、小批次與隨機梯度下降

梯度公式要對全部 mm 個範例加總。100 個範例沒問題,一億個就是麻煩了。關鍵的洞見是:梯度是一個平均值,也就是對資料的期望值,而平均值可以從樣本估計。[3] 對此有清楚的說明,Bottou、Curtis 與 Nocedal [18] 則做了深入的分析。

  • 批次梯度下降(batch gradient descent)每次更新都使用整個訓練集。每一步都精確,但也昂貴。
  • 小批次梯度下降(mini-batch gradient descent)先打亂資料,每次更新只用一個隨機的小子集(小批次),大小為 BB 個範例,通常是 32 到幾百。每一步是對完整梯度帶雜訊但不偏的估計,成本只有 B/mB/m。
  • 隨機梯度下降(stochastic gradient descent, SGD)在嚴格意義上每次更新只用一個範例(B=1B = 1)。實務上「SGD」也常指小批次的版本。

完整走過訓練資料一遍稱為一個 epoch。注意程式中的命名:深度學習函式庫中的 batch_size 參數指的是小批次的大小,而不是批次梯度下降的「整個資料集」。

小批次梯度的雜訊不只可以容忍,往往還有幫助:在相同的 epoch 數內,小批次下降比批次下降多做了非常多次更新。Bottou 等人認為,這正是隨機方法主導大規模學習的原因:它們用遠少於精確方法的計算量,就能達到有用的準確度 [18]。

import numpy as np
from sklearn.datasets import make_regression

X, y = make_regression(n_samples=10_000, n_features=5, noise=10.0, random_state=0)
m, n = X.shape
rng = np.random.default_rng(0)

def minibatch_gd(batch_size, alpha=0.05, epochs=5):
    w, b = np.zeros(n), 0.0
    for epoch in range(epochs):
        order = rng.permutation(m)                    # shuffle once per epoch
        for start in range(0, m, batch_size):
            idx = order[start:start + batch_size]
            err = X[idx] @ w + b - y[idx]
            w -= alpha * X[idx].T @ err / len(idx)    # gradient estimated on the batch
            b -= alpha * err.mean()
    return ((X @ w + b - y) ** 2).mean() / 2

for bs in [m, 64, 1]:                                 # batch, mini-batch, stochastic
    print(f"batch size {bs:5d}: J = {minibatch_gd(bs, alpha=0.05 if bs > 1 else 0.005):.2f}")
# batch size 10000: J = 6127.00   (only 5 updates in 5 epochs)
# batch size    64: J = 49.93     (785 updates)
# batch size     1: J = 50.64     (50,000 noisy updates)

同樣走過資料五遍,批次下降只走了五步,離谷底還很遠;小批次下降則基本上已經收斂(這份資料的雜訊程度讓可達到的最佳 JJ 大約是 50)。單一範例的 SGD 也能到達,但因為梯度雜訊較大,需要較小的學習率,而且無法有效利用向量化運算。

特徵縮放

白話版。 如果一個特徵以千為單位、另一個以小數為單位,成本地形會變成又長又窄的山谷,梯度下降會左右來回鋸齒狀前進。把所有特徵放到相近的尺度,山谷就會變成圓圓的碗,很容易走到底。

為什麼尺度很重要

假設特徵 x1x_1(例如以平方英尺計的房屋面積)的範圍是好幾千,特徵 x2x_2(房間數)的範圍是 1 到 5。w1w_1 稍微改變一點,預測值就變很多;w2w_2 做同樣的改變卻幾乎沒影響。所以 JJ 沿著 w1w_1 方向非常陡,沿著 w2w_2 方向非常平:等高線是細長的橢圓。學習率若小到不會在陡的方向發散,在平的方向上就幾乎沒有進展(圖 6 左)。

標準化與最小–最大縮放

最常見的解法是標準化(standardization,z 分數正規化):

xj′=xj−μjσj,x_j' = \frac{x_j - \mu_j}{\sigma_j},

其中 μj\mu_j 與 σj\sigma_j 是特徵 jj 在訓練集上計算的平均值與標準差。縮放後每個特徵的平均為 0、標準差為 1。另一個做法是最小–最大縮放(min–max scaling),xj′=(xj−min⁡j)/(max⁡j−min⁡j)x_j' = (x_j - \min_j)/(\max_j - \min_j),把訓練集的範圍對應到 [0,1][0, 1],這也是像素強度的常用選擇(8 位元影像除以 255)。兩者在 scikit-learn 中分別是 StandardScaler 與 MinMaxScaler [5]。

兩條規則可以避免隱微的錯誤。只在訓練集上計算 μj,σj\mu_j, \sigma_j,再把同一組數字套用到驗證、測試和未來的資料。並且記得縮放會改變學到的權重的意義:權重現在代表特徵每增加一個標準差時預測值改變多少。

import numpy as np
from sklearn.datasets import make_regression

X, y = make_regression(n_samples=200, n_features=2, noise=5.0, random_state=1)
X = X * np.array([1000.0, 1.0])               # feature 1 now has a ~1000x wider range

def gd(X, y, alpha, steps=500):
    m, n = X.shape
    w, b = np.zeros(n), 0.0
    for _ in range(steps):
        err = X @ w + b - y
        w -= alpha * X.T @ err / m
        b -= alpha * err.mean()
    return ((X @ w + b - y) ** 2).mean() / 2

print("raw,    alpha=1e-7:", round(gd(X, y, 1e-7), 2))    # 158.01; alpha=3e-6 diverges
mu, sigma = X.mean(axis=0), X.std(axis=0)
Xs = (X - mu) / sigma                          # z-score standardization
print("scaled, alpha=0.1 :", round(gd(Xs, y, 0.1), 2))    # 13.47

在原始特徵上,任何大於約 2×10−62 \times 10^{-6} 的學習率都會發散;用安全的學習率跑 500 步,成本仍卡在 158 左右(跑 5,000 步也還是 158 左右),因為第二個權重幾乎不動。標準化之後,α=0.1\alpha = 0.1 輕鬆到達最小值(大約 13,也就是雜訊的程度)。

左:非常細長的橢圓等高線,梯度下降的路徑在狹窄山谷中來回鋸齒前進,沿著山谷方向幾乎沒有進展。右:接近圓形的等高線,路徑幾乎直直走向中心。
圖 6 — 特徵縮放重塑了成本地形。左:尺度差異很大的特徵形成狹窄的山谷,梯度下降呈鋸齒狀前進。右:標準化之後,等高線接近圓形,同樣的步數就能到達最小值。

用於分類的邏輯迴歸

白話版。 保留線性迴歸的加權總和,但把輸出壓進 0 到 1 之間,讓它可以被解讀成機率。然後選擇權重,讓真實類別的機率越高越好。

Sigmoid 函數

Sigmoid(logistic)函數是

g(z)=11+e−z,g(z) = \frac{1}{1 + e^{-z}},

其中 zz 是任意實數。它把 −∞-\infty 對應到 0、+∞+\infty 對應到 1、00 恰好對應到 0.50.5,中間是一條 S 形曲線(圖 7 左)。它還有對稱性 g(−z)=1−g(z)g(-z) = 1 - g(z),以及方便的導數 g′(z)=g(z) (1−g(z))g'(z) = g(z)\,\big(1 - g(z)\big)。

模型與決策邊界

邏輯迴歸(logistic regression)把線性模型送進 sigmoid:

fw,b(x)=g(w⋅x+b)=11+e−(w⋅x+b),f_{\mathbf{w},b}(\mathbf{x}) = g(\mathbf{w} \cdot \mathbf{x} + b) = \frac{1}{1 + e^{-(\mathbf{w} \cdot \mathbf{x} + b)}},

並把輸出解讀為標籤為 1 的估計機率,fw,b(x)=P(y=1∣x)f_{\mathbf{w},b}(\mathbf{x}) = P(y = 1 \mid \mathbf{x})。名字雖然叫「迴歸」,它其實是分類方法。做決定時,只要這個機率至少 0.5 就預測為 1。由於 g(z)≥0.5g(z) \ge 0.5 恰好在 z≥0z \ge 0 時成立,規則就是

y^=1ifw⋅x+b≥0,y^=0 otherwise.\hat{y} = 1 \quad \text{if} \quad \mathbf{w} \cdot \mathbf{x} + b \ge 0, \qquad \hat{y} = 0 \text{ otherwise}.

滿足 w⋅x+b=0\mathbf{w} \cdot \mathbf{x} + b = 0 的集合就是決策邊界(decision boundary)。有兩個特徵時它是一條直線;一般而言它是一個平的超平面,而 w\mathbf{w} 與它垂直。離邊界很遠的點,機率接近 0 或 1;靠近邊界的點,機率接近 0.5。若想得到彎曲的邊界,就餵給模型非線性的特徵,例如 x12x_1^2 或 x1x2x_1 x_2;模型對參數仍然是線性的。

成本:交叉熵(對數損失)

為什麼不沿用平方誤差?和 sigmoid 結合之後,平方誤差成本對 (w,b)(\mathbf{w}, b) 不是凸的:它會出現平坦的高原和多個山谷。所以邏輯迴歸改用對數損失(log loss),也稱為二元交叉熵(binary cross-entropy):

L(f,y)=− ylog⁡f  −  (1−y)log⁡(1−f),L\big(f, y\big) = -\,y \log f \;-\; (1 - y) \log (1 - f),

其中 f=fw,b(x)f = f_{\mathbf{w},b}(\mathbf{x}) 是預測機率,y∈{0,1}y \in \{0, 1\} 是真實標籤。每個範例只會用到兩項中的一項。若 y=1y = 1,損失是 −log⁡f-\log f:f=1f = 1 時為零,f→0f \to 0 時無限增大。若 y=0y = 0,損失是 −log⁡(1−f)-\log(1 - f),正好是鏡像(圖 7 中)。信心滿滿卻答錯會受到很重的懲罰;信心滿滿且答對則幾乎沒有成本。

成本是整個訓練集上的平均:

J(w,b)=1m∑i=1m[− y(i)log⁡fw,b(x(i))−(1−y(i))log⁡(1−fw,b(x(i)))].J(\mathbf{w}, b) = \frac{1}{m} \sum_{i=1}^{m} \Big[ -\,y^{(i)} \log f_{\mathbf{w},b}(\mathbf{x}^{(i)}) - \big(1 - y^{(i)}\big) \log \big(1 - f_{\mathbf{w},b}(\mathbf{x}^{(i)})\big) \Big].

這個選擇並不隨意。如果把每個標籤看成一次擲硬幣,出現 1 的機率是 fw,b(x(i))f_{\mathbf{w},b}(\mathbf{x}^{(i)}),那麼 JJ 正好是訓練標籤的負對數概似(negative log-likelihood)除以 mm。最小化它就是最大概似估計(maximum likelihood estimation)[3]。而且搭配 sigmoid 時,這個成本是凸的,所以梯度下降又能找到唯一的全域最小值。

再一次梯度下降

利用 g′=g(1−g)g' = g(1 - g),對數損失的導數會化簡得非常漂亮:

∂J∂wj=1m∑i=1m(fw,b(x(i))−y(i))xj(i),∂J∂b=1m∑i=1m(fw,b(x(i))−y(i)).\frac{\partial J}{\partial w_j} = \frac{1}{m} \sum_{i=1}^{m} \left( f_{\mathbf{w},b}(\mathbf{x}^{(i)}) - y^{(i)} \right) x_j^{(i)}, \qquad \frac{\partial J}{\partial b} = \frac{1}{m} \sum_{i=1}^{m} \left( f_{\mathbf{w},b}(\mathbf{x}^{(i)}) - y^{(i)} \right).

它和線性迴歸的梯度形式完全相同:平均誤差乘以特徵。唯一的差別在 ff 的內部。這不是巧合;只要輸出函數和它相配的概似函數成對使用,整個模型家族都有這個性質,這也是梯度下降程式碼這麼容易重複使用的原因之一。

import numpy as np
from sklearn.datasets import make_classification

X, y = make_classification(n_samples=200, n_features=2, n_redundant=0,
                           n_clusters_per_class=1, class_sep=1.5, random_state=3)

def sigmoid(z):
    return 1.0 / (1.0 + np.exp(-z))

def log_loss(p, y, eps=1e-12):
    p = np.clip(p, eps, 1 - eps)              # avoid log(0)
    return -(y * np.log(p) + (1 - y) * np.log(1 - p)).mean()

w, b, alpha = np.zeros(2), 0.0, 0.5
for step in range(1000):
    p = sigmoid(X @ w + b)                    # predicted P(y = 1 | x)
    w -= alpha * X.T @ (p - y) / len(y)       # same form as linear regression
    b -= alpha * (p - y).mean()

p = sigmoid(X @ w + b)
print("log loss:", round(log_loss(p, y), 3))  # 0.06
print("accuracy:", ((p >= 0.5) == y).mean())  # 0.99
print("boundary: %.2f*x1 + %.2f*x2 + %.2f = 0" % (w[0], w[1], b))
左:S 形的 sigmoid 曲線在 z 等於 0 處通過 0.5。中:兩條損失曲線對預測機率作圖:標籤為 1 時 -log p 在 p 接近 0 時急遽上升;標籤為 0 時 -log(1-p) 在 p 接近 1 時急遽上升。右:兩團不同顏色的點,背景依預測機率上色,黑色直線是機率為 0.5 的決策邊界。
圖 7 — 邏輯迴歸。左:sigmoid 把任何分數壓進 (0, 1)。中:y = 1 與 y = 0 的對數損失,對信心滿滿的錯誤懲罰很重。右:從頭訓練的模型;底色是預測機率,黑線是決策邊界 w·x + b = 0。

超過兩個類別

當類別數 K>2K > 2 時,標準的推廣方式是讓每個類別有自己的權重向量和分數 zk=wk⋅x+bkz_k = \mathbf{w}_k \cdot \mathbf{x} + b_k,再用 softmax 函數把分數轉成總和為 1 的機率:P(y=k∣x)=ezk/∑l=1KezlP(y = k \mid \mathbf{x}) = e^{z_k} / \sum_{l=1}^{K} e^{z_l}。成本同樣是真實類別的負對數機率的平均(類別交叉熵)[3]。本篇開頭 scikit-learn 的 LogisticRegression 處理十個數字時做的就是這件事,而它也正是 ML 02 中大多數分類網路的輸出層。

過擬合、欠擬合與正則化

白話版。 太簡單的模型抓不到模式(欠擬合,underfitting)。太有彈性的模型會把訓練範例連同雜訊一起背下來,遇到新範例就失敗(過擬合,overfitting)。我們用模型沒訓練過的資料來分辨兩者,並用懲罰大權重的方式馴服過於有彈性的模型。

親眼看它發生

取 25 個帶雜訊的平滑曲線樣本,擬合不同次數的多項式,也就是在特徵 x,x2,…,xdx, x^2, \dots, x^d 上做線性迴歸(圖 8):

  • 1 次(欠擬合,高偏差)。直線跟不上曲線。不論在訓練集還是新資料上,誤差都很大。
  • **3 次(擬合良好)。**曲線跟著趨勢走,忽略雜訊。兩邊的誤差都很小。
  • **15 次(過擬合,高變異)。**曲線幾乎精確地穿過每個訓練點,所以訓練誤差接近零。但在點與點之間和點的範圍之外,它劇烈擺盪,新資料上的誤差暴增。

光看訓練誤差無法分辨好模型和過擬合的模型;它甚至偏好過擬合的那個。只有模型沒看過的資料才能揭露差別。這就是我們保留驗證集的原因。

使用驗證集與測試集

流程是:

  1. 在訓練集上擬合每個候選模型(每種次數、每種正則化強度)。
  2. 在驗證集上比較候選模型,保留最好的那個。
  3. 在測試集上回報所選模型的誤差,只做一次。

為什麼要三份而不是兩份?因為在驗證集上從眾多候選中挑出最好的,本身就是一種擬合:勝出者有一部分只是剛好在那些特定範例上運氣好。沒動過的測試集才能給出不偏的最終估計。資料很少時,可以用交叉驗證(cross-validation)取代單一的驗證集:把訓練資料切成 kk 份,訓練 kk 次,每次用不同的一份做驗證,再取平均。

要留意的模式:如果訓練誤差和驗證誤差都很高,模型欠擬合(換更有彈性的模型或更好的特徵)。如果訓練誤差很低、驗證誤差卻高得多,模型過擬合(取得更多資料、簡化模型,或加上正則化)。

L2 正則化

正則化(regularization)保留有彈性的模型,但不鼓勵極端的參數值。15 次多項式那條狂野的曲線,需要非常大、又彼此精細抵消的權重。L2 正則化在成本上加入權重平方大小的懲罰:

Jreg(w,b)=J(w,b)+λ2m∑j=1nwj2,J_{\text{reg}}(\mathbf{w}, b) = J(\mathbf{w}, b) + \frac{\lambda}{2m} \sum_{j=1}^{n} w_j^2 ,

其中 JJ 是原本的成本(MSE 或對數損失),λ≥0\lambda \ge 0 是正則化強度(一個超參數),加總的範圍是所有權重,但慣例上不包括偏差 bb。λ=0\lambda = 0 時回到原本的模型;λ\lambda 越大,權重越被拉向零,擬合出的函數越平滑;λ\lambda 太大則會欠擬合。對線性迴歸來說,這就是脊迴歸(ridge regression),由 Hoerl 與 Kennard 提出,用來在特徵高度相關時穩定最小平方估計 [20]。

從梯度下降的更新可以看出懲罰項做了什麼:

wj←wj(1−αλm)−α∂J∂wj.w_j \leftarrow w_j \left(1 - \alpha \frac{\lambda}{m}\right) - \alpha \frac{\partial J}{\partial w_j}.

在每次一般的更新之前,每個權重都先乘上一個略小於 1 的數。這就是為什麼 L2 正則化在神經網路訓練中也被稱為權重衰減(weight decay)[3]。

import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
from sklearn.linear_model import LinearRegression, Ridge
from sklearn.metrics import mean_squared_error

rng = np.random.default_rng(0)
x = rng.uniform(0, 1, 25)
y = np.sin(2 * np.pi * x) + rng.normal(0, 0.25, 25)      # true curve + noise
X = x[:, None]

# 60% train, 20% validation, 20% test
X_tmp, X_te, y_tmp, y_te = train_test_split(X, y, test_size=0.2, random_state=0)
X_tr, X_va, y_tr, y_va = train_test_split(X_tmp, y_tmp, test_size=0.25, random_state=0)

def fit(degree, lam=0.0):
    reg = Ridge(alpha=lam) if lam > 0 else LinearRegression()   # sklearn calls lambda "alpha"
    model = make_pipeline(PolynomialFeatures(degree), StandardScaler(), reg)
    return model.fit(X_tr, y_tr)

for degree, lam in [(1, 0), (3, 0), (15, 0), (15, 1e-2)]:
    model = fit(degree, lam)
    tr = mean_squared_error(y_tr, model.predict(X_tr))
    va = mean_squared_error(y_va, model.predict(X_va))
    print(f"degree {degree:2d}, lambda {lam:g}: train MSE {tr:.3f}, val MSE {va:.3g}")

best = fit(15, 1e-2)                                    # chosen on validation only
print("test MSE:", round(mean_squared_error(y_te, best.predict(X_te)), 3))

輸出說明了一切:1 次的訓練/驗證 MSE 是 0.144/0.292(欠擬合),3 次是 0.031/0.052(良好),沒有正則化的 15 次訓練 MSE 只有 0.003,驗證 MSE 卻高達數百萬,因為有些驗證點落在訓練點之間很寬的空隙中(例如 x≈0.27x \approx 0.27 到 x≈0.61x \approx 0.61 之間),多項式在那裡劇烈擺盪。對同一個 15 次模型加上溫和的 L2 懲罰(λ=0.01\lambda = 0.01),得到 0.041/0.035,測試 MSE 為 0.075。

四個面板顯示同一組取自正弦曲線的帶雜訊資料點。直線錯過曲線;三次多項式跟著曲線走;15 次多項式穿過每個訓練點並在邊緣劇烈振盪;同一個 15 次多項式加上 L2 正則化後變得平滑。第五個面板畫出訓練誤差與驗證誤差對多項式次數的變化,訓練誤差穩定下降,驗證誤差呈 U 形。
圖 8 — 欠擬合、良好擬合、過擬合,以及被 L2 正則化馴服的過擬合(藍色為訓練點,橘色為驗證點)。右:次數增加時,訓練誤差持續下降,驗證誤差則先降後升;驗證曲線的最低點就是該選的模型。

評估模型

白話版。 單一個數字永遠說不完整個故事。選一個符合「犯錯代價」的指標,而且一定要和簡單的基準比較。

迴歸指標

迴歸最常用的指標是 MSE,1m∑i(y^(i)−y(i))2\frac{1}{m}\sum_i (\hat{y}^{(i)} - y^{(i)})^2,這裡不再有只是為了訓練方便的 12\tfrac12。它的平方根 RMSE 和 yy 的單位相同,比較好解讀(「平均大約差 3 分」)。平均絕對誤差(mean absolute error),1m∑i∣y^(i)−y(i)∣\frac{1}{m}\sum_i |\hat{y}^{(i)} - y^{(i)}|,對少數大誤差比較不敏感 [5]。

分類指標

分類中,準確率(accuracy)是預測正確的比例。它容易理解,也容易騙人。在 digits 資料中,大約 10% 的影像是 9。一個永遠回答「不是 9」的「模型」有 90% 的準確率,卻完全沒用。

混淆矩陣(confusion matrix)計算二元問題的四種結果,「陽性」代表我們在意的那一類:

預測為陽性預測為陰性
實際為陽性真陽性(TP)偽陰性(FN)
實際為陰性偽陽性(FP)真陰性(TN)

由此得到

accuracy=TP+TNTP+TN+FP+FN,precision=TPTP+FP,recall=TPTP+FN.\text{accuracy} = \frac{TP + TN}{TP + TN + FP + FN}, \qquad \text{precision} = \frac{TP}{TP + FP}, \qquad \text{recall} = \frac{TP}{TP + FN}.

精確率(precision)回答「模型說是陽性時,有多常是對的?」召回率(recall)回答「所有真正的陽性中,模型找到了多少?」兩者互相拉扯:把邏輯迴歸的 0.5 決策門檻調低,會找到更多陽性(召回率較高),但也會發出更多假警報(精確率較低)。哪個比較重要取決於每種錯誤的代價:癌症篩檢不該漏掉腫瘤(高召回率),垃圾郵件過濾器則不該把真正的郵件藏起來(高精確率)。F1 分數是精確率與召回率的調和平均,把兩者合成一個數字 [5]。

import numpy as np
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import confusion_matrix, accuracy_score, precision_score, recall_score

X, y = load_digits(return_X_y=True)
y = (y == 9).astype(int)                  # "is it a 9?": only about 10% positives
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.3, stratify=y, random_state=0)

lazy = np.zeros_like(y_te)                # always answers "not a 9"
model = LogisticRegression(max_iter=5000).fit(X_tr, y_tr)
pred = model.predict(X_te)

print("lazy accuracy :", round(accuracy_score(y_te, lazy), 3), " recall:", recall_score(y_te, lazy))
print("model accuracy:", round(accuracy_score(y_te, pred), 3),
      " precision:", round(precision_score(y_te, pred), 3),
      " recall:", round(recall_score(y_te, pred), 3))
print(confusion_matrix(y_te, pred))       # rows: true 0/1, columns: predicted 0/1
# lazy accuracy : 0.9   recall: 0.0
# model accuracy: 0.974  precision: 0.87  recall: 0.87

偷懶的基準有 90% 的準確率,召回率卻是零。真正的模型準確率 97.4%,聽起來只好一點點,但 0.87 的召回率顯示它確實找到了大部分的 9。一定要回報基準;類別不平衡時,一定要看準確率以外的指標。同樣的指標會在 DIP 的圖樣分類一章再次出現,並更仔細地處理門檻與多類別的問題。

現代觀點

本篇的機制(帶參數的模型、成本、以梯度為基礎的最佳化,以及保留資料的評估)幾十年來沒有改變,下面談到的每個系統也都還在使用它們。改變的是特徵從哪裡來、標籤從哪裡來,以及模型有多大。以下幾篇回顧文章把這些轉變描繪得很好。

機器學習這個領域。 Jordan 與 Mitchell 發表在《Science》的回顧 [7] 以一個問題來界定機器學習:如何打造能透過經驗自動進步的電腦;並把它定位在電腦科學與統計學的交會處、人工智慧與資料科學的核心。他們把機器學習在科學、醫療、製造、教育與金融等領域的快速擴散,歸因於三件事同時到來:新的學習演算法與理論、可取得資料量的爆炸性成長,以及便宜的運算。這個框架是對抗炒作的好解藥:方法的好壞,取決於它得到的資料與經驗。

從特徵工程到表示學習。 很長一段時間,傳統模型的成敗主要取決於人工設計的特徵,也就是由專家來做圖 1 中「數有墨跡的欄數」這一步:邊緣直方圖、紋理描述子、關鍵點描述子(見 DIP 12)。Bengio、Courville 與 Vincent [21] 認為這種依賴是一個弱點,因為正確的表示決定了任務的難易,並主張直接從資料學習表示本身。他們的回顧涵蓋了做到這件事的工具(機率模型、自編碼器、流形學習與深度網路),以及讓表示變好的通用先驗,例如把資料背後各自獨立的解釋因子分離開來。用本篇的語言來說,深度網路仍然是「特徵加上邏輯迴歸」,只是特徵由前面的層產生,而這些層的權重也是用同樣的梯度下降學出來的。這就是 ML 02 的主題。

自監督式學習。 學習表示需要資料,而標籤是昂貴的部分。Jing 與 Tian 關於自監督式視覺特徵學習的綜述 [22] 把不需要標籤的「前置任務」(pretext task)分成四類:以生成為基礎(例如重建影像或為影像上色)、以上下文為基礎(利用空間或時間結構,例如影像區塊的排列)、以免費語意標籤為基礎(使用自動得到的標籤,例如來自繪圖引擎或傳統方法),以及跨模態(判斷兩個訊號,例如影片與聲音,是否屬於同一事件)。它也確立了至今仍是標準的評估流程:用表示遷移到分類、偵測、分割等下游任務的表現來評判它。Gui 等人較新的綜述 [23] 則把這個領域重新整理為:以上下文為基礎的方法、對比學習(利用負例、自蒸餾或特徵去相關)、以遮罩影像建模為主的生成式方法,以及結合對比與生成目標的混合方法,反映出遮罩自編碼器 [12] 之後轉向遮罩預測的趨勢。對實務工作者,Balestriero 等人的《A Cookbook of Self-Supervised Learning》[24] 把現代方法分成深度度量學習家族、自蒸餾家族、典型相關分析家族與遮罩影像建模,並坦白指出結果有多依賴資料擴增與訓練時使用的小型「投影器」(projector)網路等細節。

基礎模型。 Bommasani 等人的報告 [10] 為由此產生的範式命名:在大規模、廣泛的資料上訓練(通常是自監督式),再調整去做許多下游任務的模型。報告強調了兩個後果。湧現(emergence):像是憑提示中的幾個例子就能遵循指示 [11] 這類能力,是隨著規模出現的,而不是被設計出來的。同質化(homogenization):許多應用如今都由同樣少數幾個基礎模型調整而來,這帶來巨大的槓桿效果,但也代表基礎模型的缺陷會被建立在它之上的一切所繼承。報告也強調,我們對這些模型如何運作、何時會失敗的理解仍非常有限。

基於人類回饋的強化學習。 強化學習已經從遊戲走到語言模型訓練的最後階段 [13]、[14]。Casper 等人 [25] 整理了可能出錯的地方。他們依照 RLHF 的三個步驟(收集人類回饋、擬合獎勵模型、最佳化策略),逐一列出每一步的問題:人的回饋帶有雜訊、前後不一致,而且可能被操弄;獎勵模型只是「人們真正想要的東西」的不完美替代;而針對這個替代品用力最佳化的策略,可能會鑽它的漏洞。他們區分了在 RLHF 框架內用更好的方法就能解決的問題,以及需要換一種方法才能克服的根本限制,並主張提高透明度、把 RLHF 與其他安全措施結合,而不是只依賴它。

大規模最佳化。 Bottou、Curtis 與 Nocedal [18] 解釋了為什麼成為機器學習主力的,是樸素的隨機梯度法,而不是傳統數值分析中更精巧的最佳化方法:資料充足時,便宜而帶雜訊的一步勝過昂貴而精確的一步。Adam [19] 等自適應方法則在其上加入每個參數各自的步長。本節提到的每個模型,無論多大,都是用本篇小批次迴圈的後代訓練出來的。

未解問題。 儘管規模驚人,這篇入門教學中的幾個問題仍然是最困難的。泛化:非常大的模型幾乎完美地擬合訓練資料,卻仍能泛化,這和圖 8 中簡單的過擬合圖像不符,其原因尚未完全被理解。評估:當模型用了相當大比例的公開網路資料來訓練,就很難保證測試集沒有出現在訓練資料中,因此誠實評估的基礎,乾淨的訓練/測試分離,變得難以驗證。目標:成本函數是模型唯一會最佳化的東西,而 RLHF 顯示出要寫出一個真正反映人們需求的成本有多難。還有資料:模型能學到什麼,受限於它看到的資料與標籤,以及其中所有的缺口與偏差。

針對電腦視覺,傳統路線(人工特徵加上簡單分類器)在 DIP 的圖樣分類一章中有完整發展;學習的路線則延續到 ML 02 與〈Deep Learning for Image Processing〉。

重點整理

  • 機器學習用一個參數由範例擬合而來的彈性模型,取代手寫規則;當規則難以說清楚、範例卻容易取得時,它最能發揮。
  • 各範式的差別在於回饋:監督式(有正確答案;數字用迴歸、類別用分類)、非監督式(沒有答案;分群、降維、異常偵測)、自監督式(答案由資料本身製造),以及強化學習(動作換來獎勵)。KNN 是監督式;k-means 是分群。
  • 把模型 fw,b(x)f_{w,b}(x) 和成本 J(w,b)J(w,b) 分開:模型把輸入對應到預測值;成本把參數對應到誤差。訓練就是最小化 JJ。
  • 梯度下降重複 w←w−α ∂J/∂ww \leftarrow w - \alpha\, \partial J / \partial w。在最小值處,導數為零。學習率必須小到不會越過頭,又大到能有進展;小批次讓每一步都很便宜。
  • 做梯度下降前先縮放特徵,而且只用訓練集的統計量。
  • 邏輯迴歸對線性分數套上 sigmoid,並最小化交叉熵;它的決策邊界是 w⋅x+b=0\mathbf{w}\cdot\mathbf{x} + b = 0,梯度形式和線性迴歸相同。
  • 用模型沒訓練過的資料評判它:用驗證集做選擇、在測試集上只回報一次、模型過擬合時加上(L2)正則化,類別不平衡時要看準確率以外的指標。

練習

  1. 手算導數。 對單一特徵的 MSE 成本 J(w,b)=12m∑i(wx(i)+b−y(i))2J(w, b) = \frac{1}{2m}\sum_i (wx^{(i)} + b - y^{(i)})^2,推導 ∂J/∂w\partial J/\partial w 與 ∂J/∂b\partial J/\partial b。接著對三個點 (1,2),(2,4),(3,6)(1, 2), (2, 4), (3, 6),從 w=0,b=0w = 0, b = 0 出發,以 α=0.1\alpha = 0.1 計算一步梯度下降。
提示

對每個平方項用連鎖律得到 2(wx(i)+b−y(i))⋅x(i)2(wx^{(i)} + b - y^{(i)}) \cdot x^{(i)},2 和 12\tfrac12 抵消,就得到梯度下降一節中的公式。在 w=b=0w = b = 0 時誤差是 −2,−4,−6-2, -4, -6。所以 ∂J/∂w=(−2⋅1−4⋅2−6⋅3)/3=−28/3\partial J/\partial w = (-2 \cdot 1 - 4 \cdot 2 - 6 \cdot 3)/3 = -28/3,∂J/∂b=−12/3=−4\partial J/\partial b = -12/3 = -4。一步之後 w=0.1⋅28/3≈0.933w = 0.1 \cdot 28/3 \approx 0.933,b=0.4b = 0.4。

  1. 找出發散的門檻。 對 J(w)=12m∑i(wx(i)−y(i))2J(w) = \frac{1}{2m}\sum_i (wx^{(i)} - y^{(i)})^2(沒有偏差),證明一步梯度下降會把到最佳值 w∗w^\ast 的距離乘上 1−αs1 - \alpha s,其中 s=1m∑i(x(i))2s = \frac{1}{m}\sum_i (x^{(i)})^2。α\alpha 在什麼範圍內梯度下降會收斂?用特徵縮放一節中的 gd 函數檢查你的答案。
提示

因為 JJ 是曲率為 ss、最小值在 w∗w^\ast 的拋物線,∂J/∂w=s (w−w∗)\partial J/\partial w = s\,(w - w^\ast)。所以 wnew−w∗=(1−αs)(w−w∗)w_{\text{new}} - w^\ast = (1 - \alpha s)(w - w^\ast)。當 ∣1−αs∣<1|1 - \alpha s| \lt 1,也就是 0<α<2/s0 \lt \alpha \lt 2/s 時收斂;α=1/s\alpha = 1/s 時一步就落在最小值上。有多個特徵時,ss 換成 1mX⊤X\frac{1}{m}X^\top X 的最大特徵值,這就是為什麼一個尺度巨大的特徵會逼得 α\alpha 必須非常小。

  1. 用迴歸做分類。 用 make_classification(n_features=1, n_informative=1, n_redundant=0, n_clusters_per_class=1) 產生一維資料,再加上五個 xx 非常大的第 1 類範例。分別擬合 LinearRegression(以 0.5 為門檻)與 LogisticRegression。比較加入額外點前後兩者的準確率,並解釋差異。
提示

額外的點很容易分類,但平方誤差仍然希望直線經過它們附近。直線的斜率下降,跨過 0.5 的位置往第 1 類移動,一些靠近邊界的普通第 1 類點因此被分錯。分類正確且離得很遠的點,對數損失已經接近 0,所以邏輯迴歸幾乎不受影響。

  1. 縮放 digits。 用本篇從頭實作的邏輯迴歸,在 digits 資料上訓練「是 9 或不是 9」,一次用原始像素(0 到 16),一次用除以 16 的像素。使用相同的步數,找出各自可用的最大學習率。縮放改變的是最終準確率、速度,還是兩者都有?
提示

這裡所有像素的單位相同,所以效果比圖 6 溫和,但原始輸入大了 16 倍,梯度的大小和地形的曲率也隨之放大。原始像素的安全學習率大約小 162=25616^2 = 256 倍。學習率都調好時,兩者能達到相近的準確率;縮放過的版本只是比較容易調。

  1. 選擇 lambda。 用多項式的例子,固定 15 次,嘗試 λ∈{10−6,10−4,10−2,1,100}\lambda \in \{10^{-6}, 10^{-4}, 10^{-2}, 1, 100\}。以對數橫軸畫出訓練與驗證 MSE 對 λ\lambda 的變化。哪一端欠擬合、哪一端過擬合?你會選哪個 λ\lambda?為什麼不能用測試集來做這個選擇?
提示

很小的 λ\lambda 等於沒有正則化(過擬合:訓練誤差低、驗證誤差高)。很大的 λ\lambda 把曲線壓成接近常數(欠擬合:兩種誤差都高)。選驗證誤差最低的 λ\lambda。如果測試集參與了選擇,它就不再是新資料上效能的不偏估計。

  1. 哪一種範式? 把每個問題歸類為監督式(迴歸或分類)、非監督式、自監督式或強化學習,並說出目標值或獎勵:(a) 用附有實際結果的使用紀錄,估計手機剩餘的電池時間;(b) 把一百萬張沒有標籤的商品照片分組以便瀏覽;(c) 藉由預測每張照片缺少的四分之一來預訓練影像模型;(d) 教機械手臂堆積木,只有在積木堆穩時才給分數;(e) 在沒有任何故障標註的情況下,標出工廠感測器資料流中的異常讀數。
提示

(a) 監督式迴歸;目標值是剩餘時數。(b) 非監督式分群。(c) 自監督式;目標值是被藏起來的像素,由資料本身產生。(d) 強化學習;獎勵是積木是否堆穩,要到最後才給。(e) 非監督式異常偵測。

參考文獻

  1. Shyandram, “Introduction to Machine Learning,” blog post, 2024; updated 2026. link
  2. A. L. Samuel, “Some studies in machine learning using the game of checkers,” IBM Journal of Research and Development, vol. 3, no. 3, pp. 210–229, 1959. doi
  3. I. Goodfellow, Y. Bengio and A. Courville, Deep Learning, MIT Press, 2016. book site
  4. A. Ng, E. Shyu, A. Bagul and G. Ladwig, “Machine Learning Specialization,” DeepLearning.AI and Stanford Online, Coursera. course page
  5. F. Pedregosa, G. Varoquaux, A. Gramfort, V. Michel, B. Thirion et al., “Scikit-learn: Machine learning in Python,” Journal of Machine Learning Research, vol. 12, pp. 2825–2830, 2011. link
  6. E. Alpaydin and C. Kaynak, “Optical Recognition of Handwritten Digits,” UCI Machine Learning Repository, 1998. doi
  7. M. I. Jordan and T. M. Mitchell, “Machine learning: Trends, perspectives, and prospects,” Science, vol. 349, no. 6245, pp. 255–260, 2015. doi
  8. R. S. Sutton and A. G. Barto, Reinforcement Learning: An Introduction, 2nd ed., MIT Press, 2018. book page
  9. Y. LeCun and I. Misra, “Self-supervised learning: The dark matter of intelligence,” Meta AI blog, 2021. link
  10. R. Bommasani, D. A. Hudson, E. Adeli et al., “On the opportunities and risks of foundation models,” arXiv:2108.07258, 2021. arXiv
  11. T. Brown, B. Mann, N. Ryder, M. Subbiah, J. D. Kaplan et al., “Language models are few-shot learners,” in Advances in Neural Information Processing Systems (NeurIPS), vol. 33, 2020. link
  12. K. He, X. Chen, S. Xie, Y. Li, P. Dollár and R. Girshick, “Masked autoencoders are scalable vision learners,” in Proc. IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR), pp. 15979–15988, 2022. arXiv
  13. L. Ouyang, J. Wu, X. Jiang, D. Almeida, C. Wainwright et al., “Training language models to follow instructions with human feedback,” in Advances in Neural Information Processing Systems (NeurIPS), vol. 35, 2022. link
  14. DeepSeek-AI, “DeepSeek-R1: Incentivizing reasoning capability in LLMs via reinforcement learning,” Nature, vol. 645, pp. 633–638, 2025. arXiv
  15. A. K. Jain, “Data clustering: 50 years beyond K-means,” Pattern Recognition Letters, vol. 31, no. 8, pp. 651–666, 2010. doi
  16. I. T. Jolliffe and J. Cadima, “Principal component analysis: A review and recent developments,” Philosophical Transactions of the Royal Society A, vol. 374, no. 2065, 20150202, 2016. doi
  17. V. Chandola, A. Banerjee and V. Kumar, “Anomaly detection: A survey,” ACM Computing Surveys, vol. 41, no. 3, 2009. doi
  18. L. Bottou, F. E. Curtis and J. Nocedal, “Optimization methods for large-scale machine learning,” SIAM Review, vol. 60, no. 2, pp. 223–311, 2018. doi
  19. D. P. Kingma and J. Ba, “Adam: A method for stochastic optimization,” in Proc. International Conference on Learning Representations (ICLR), 2015. arXiv
  20. A. E. Hoerl and R. W. Kennard, “Ridge regression: Biased estimation for nonorthogonal problems,” Technometrics, vol. 12, no. 1, pp. 55–67, 1970. doi
  21. Y. Bengio, A. Courville and P. Vincent, “Representation learning: A review and new perspectives,” IEEE Transactions on Pattern Analysis and Machine Intelligence, vol. 35, no. 8, pp. 1798–1828, 2013. doi
  22. L. Jing and Y. Tian, “Self-supervised visual feature learning with deep neural networks: A survey,” IEEE Transactions on Pattern Analysis and Machine Intelligence, vol. 43, no. 11, pp. 4037–4058, 2021. doi
  23. J. Gui, T. Chen, J. Zhang, Q. Cao, Z. Sun, H. Luo and D. Tao, “A survey on self-supervised learning: Algorithms, applications, and future trends,” arXiv:2301.05712, 2023. arXiv
  24. R. Balestriero, M. Ibrahim, V. Sobal, A. Morcos, S. Shekhar et al., “A cookbook of self-supervised learning,” arXiv:2304.12210, 2023. arXiv
  25. S. Casper, X. Davies, C. Shi et al., “Open problems and fundamental limitations of reinforcement learning from human feedback,” Transactions on Machine Learning Research, 2023. TMLR · arXiv