ARK · 理解深度學習CHAPTER 01 / 21

CHAPTER 01 / 21 · PART 1 · 地基:從監督式學習到深度網路

深度學習到底在做什麼

Introduction

把深度學習還原成「從一個函數族裡挑一個成員」,並用回饋訊號的差別區分三種學習範式。

§01學習重點

§02課程內容

一、一句話說完:深度學習在挑一個函數

先把三個常被混用的詞排好。人工智慧(artificial intelligence)是最大的那個帽子,涵蓋所有讓機器展現出智慧行為的做法。這頂帽子底下有一大類根本不碰資料:把領域專家的判斷寫成一條條規則、在龐大的狀態空間裡做有策略的搜尋、用符號系統做形式推演——這些都算人工智慧,而且都跟「從資料裡學」無關。機器學習(machine learning)則是碰資料的那一支,手法很專一:拿觀測到的資料去配一個數學模型,讓模型的輸出盡量貼近資料。深度學習(deep learning)又是機器學習裡的一支,指的是所用的模型屬於深度神經網路(deep neural network)這一類,以及把這類模型配到資料上的整套流程。

請注意「深度」描述的是結構不是等級:它說的是模型由許多層運算前後串接而成,不是說它比較聰明。

有了這個定位,深度學習做的事情可以壓成一行式子:

$$ y \;=\; f[x, \boldsymbol\phi] $$

逐項拆解:\(x\) 是輸入,是你把真實世界的東西編碼成的一串數字;\(y\) 是輸出,是模型算出來的預測,可能是一個數、一串數、或一組機率;\(f[\cdot]\) 是模型本身,一套固定的運算步驟;\(\boldsymbol\phi\) 是參數,是這套運算裡所有可以調的數字。方括號是本課沿用原書的記號慣例,用來把「函數作用在什麼上」跟一般的乘法括號區分開。

關鍵在最後一項。運算步驟 \(f\) 是人設計好的、不會變;會變的只有 \(\boldsymbol\phi\)。所以「一個模型」其實不是一個函數,而是一整族函數——\(\boldsymbol\phi\) 每換一組值,你就得到族裡的另一個成員。訓練,就是在這一族函數裡挑一個成員出來。 挑的依據是資料。

「學習」這個詞容易讓人誤會,好像機器會自己想出規則。實際發生的事樸素得多:人先劃定一個很大的候選範圍,再用一個可以計算的準則從裡面挑一個。接下來的二十章講的全是這三件事怎麼做得更好——怎麼把真實世界的東西變成 \(x\)、怎麼設計函數族、怎麼挑。

比喻: 想像一座郵件分揀中心。輸入是一封信被量測到的特徵:尺寸、重量、地址欄的字樣;輸出是它該滑進哪一個出口。這座分揀中心的規則不是有人一條一條寫死的,而是從幾百萬筆「這封信最後正確送到了哪裡」的紀錄裡調校出來的——調校的對象就是那些參數。這個比喻有一個地方明確失準:分揀中心的規則是離散的、可以逐條印出來稽核,而神經網路的參數是幾百萬個連續數字,你印得出來卻讀不懂,也沒有哪一個數字單獨代表「這個郵遞區號往三號出口」。這個差異不是技術細節,它是本章最後談責任歸屬時的起點。

二、編碼:把世界壓成向量時,不能丟掉什麼

模型只吃數字。真實世界的東西要進模型,得先被壓成一串數字,這個動作叫編碼(encoding)——而編碼一定會丟東西。一封信有紙的觸感、墨水的氣味、寄件人下筆的力道,這些在編碼之後全部消失。所以問題從來不是「會不會丟」,而是哪一樣東西丟掉之後任務就垮了。這一題的答案,決定了你該用哪一種架構。

按「不能丟的是什麼」來分,輸入大致落在四個位置上,約束一個比一個強。

第一種,什麼都不必特別保留。 一封信的重量、長、寬、厚、是否掛號——這些欄位各自獨立,誰擺在前面誰擺在後面都不影響任何事。這類輸入叫表格資料(tabular data),是限制最寬鬆的一種:欄位順序可以任意換,只要訓練時和上線後用同一套排法就行。

第二種,不能丟順序。 收件地址是一串有嚴格次序的欄位:縣市、行政區、路名、段、巷、弄、號。把這些欄位當成一袋沒有次序的詞丟進模型,「三段一二〇巷五號」和「五段一二〇巷三號」會被壓成同一筆東西,而它們是同一條路上相隔好幾公里的兩個門牌。任何一段文字都有這個性質——次序本身就是資訊,在編碼那一刻丟掉就找不回來。所以處理文字的模型必須有辦法記住「誰排在誰前面」,這是第 12 章要解決的事。

第三種,不能丟鄰接關係。 輸送帶上方的相機拍下包裹,一張 256 乘 256 的彩色照片有 256×256 個像素、每個像素三個色彩值,攤平成一條向量就是接近二十萬個數字。攤平這個動作本身沒錯,麻煩在於攤平之後,原本擠成一小塊的那些像素被拆散到向量的各個角落,而「標籤貼紙」這種東西恰恰是靠一小塊區域的共同樣式被認出來的。震動感測波形有同樣的困擾:一秒鐘上萬個取樣點,任何單一點都不帶意義,意義全在相鄰點之間怎麼變化。這類輸入需要的是能就近取用資訊的結構,也就是第 10 章的卷積網路(convolutional network)。

第四種,不能丟連接關係。 前三種輸入都還能寫成一條固定長度的向量,這一種連這點都做不到。想像把一批包裹之間的轉運關係畫成一張圖:每個包裹是一個點(在圖論裡叫節點),兩個包裹之間有過轉運就連一條線。這張圖的形狀——有幾個點、誰跟誰相連——每一筆資料都不一樣,而且答案就藏在連法本身。這種「只有連接關係、沒有固定座標」的結構,數學上的名字就是圖(graph)——注意這裡的「圖」指的不是圖片,而是「一堆節點加上連接它們的邊」這個數學物件。(工程界習慣說的「網路拓樸」講的是同一件事,只是那是比較寬鬆的用語;數學裡的拓樸學研究的是另一種東西,本課用不到。)第 13 章的圖神經網路(graph neural network)處理的正是這一類。

輸出端也有一條類似的軸,只是問題換成「輸出必須滿足什麼約束」。

沒有約束的輸出就是一個實數,是多少就是多少——預測明天要排幾小時的班,這叫迴歸(regression);一次要吐好幾個實數,就是多變量迴歸(multivariate regression)。

約束是「非負且加起來等於一」 的,是分類任務(classification)。模型不會直接說出「南線」兩個字,它交出來的是一個機率向量:每條路線一個數。類別剛好兩個叫二元分類(binary classification),多於兩個叫多類分類(multiclass classification),但那只是數量差別,形式完全一樣。把一組沒有範圍的分數搬進這個約束裡,標準做法是 softmax:

$$ Pr(y = k \mid x) \;=\; \frac{\exp[s_k]}{\sum_{j=1}^{K}\exp[s_j]} $$

逐項拆解:\(K\) 是類別總數;\(s_k\) 是模型對第 \(k\) 類算出的原始分數,可正可負、沒有範圍限制;取指數 \(\exp[\cdot]\) 把它變成正數;除以所有類別的指數總和,就讓整個向量加起來剛好是一。\(Pr(y = k \mid x)\) 讀作「在看到輸入 \(x\) 的條件下,答案是第 \(k\) 類的機率」。這條式子在第 5 章談損失函數時還會回來,因為它決定了訓練目標長什麼樣。

底下這段程式把一封信編碼成六個數字,算出五條路線的分數,再轉成機率:

PYTHON
import numpy as np

rng = np.random.default_rng(0)

# 一封信編碼成 6 個數字:
# 重量、長、寬、厚、郵遞區號前兩碼、是否掛號
letter = np.array([48.0, 22.9, 11.4, 0.3, 10.0, 1.0])
routes = ["北一線", "北二線", "中線", "南線", "東線"]

# 模型參數(此處僅為示範,未經訓練)
W = rng.normal(0, 0.1, (len(routes), letter.size))
b = rng.normal(0, 0.1, len(routes))
scores = W @ letter + b          # 每條路線一個分數


def softmax(v):                  # 分數 → 機率向量
    e = np.exp(v - v.max())
    return e / e.sum()


p = softmax(scores)
print("分數:", " ".join(f"{v:.2f}" for v in scores))
print("機率:", " ".join(f"{v:.3f}" for v in p))
print("機率總和:", round(float(p.sum()), 6))
print("預測路線:", routes[int(p.argmax())])

實跑輸出是:分數為 0.43 6.95 -13.70 3.68 3.26,機率為 0.001 0.939 0.000 0.036 0.024,總和為 1.0,預測路線是「北二線」。這裡的參數是亂數,所以這個預測毫無意義——它唯一證明的事情是形狀對了:不管分數多大多小,出來的一定是一個合法的機率向量。訓練要做的,是把那些亂數換成有意義的值。

約束是「整體要說得通」 的輸出最麻煩。當模型要交出的不是一個數,而是一整組彼此牽連的值——一整份手寫地址的逐字轉錄、一整張逐像素標好類別的圖——約束就不再是加總等於一那麼單純。這裡藏著一個容易被忽略的陷阱:每個位置各自挑最像的那一個,拼起來不一定合法。逐字辨識時如果把每個字元分開判、各自取機率最高的候選,湊出來的地址很可能根本不存在。

問題出在模型其實需要兩種不同的知識,而這兩種知識的取得成本差很多。第一種是「這張照片上寫的是什麼」,它非有配對資料不可——你得看過照片和它的正確轉錄,才知道那個筆跡對應到哪個字。第二種是「什麼樣的地址是真的存在的」,它不需要配對:一份現成的門牌清單就足以判斷哪些組合站得住腳,而那份清單裡一張照片也沒有。模型該學的東西裡,有一整塊可以從完全沒有答案的資料裡長出來。 這一塊留到第四小節專門處理。

三、監督式學習:函數族、參數,以及「挑」這個動作

當訓練資料是成對的輸入與答案時,我們稱這種設定為監督式學習(supervised learning)。「監督」的意思很字面:每一筆資料都有人(或某個既有流程)給出了正確輸出,這些答案在訓練時扮演監工的角色。

挑選的準則寫成損失函數(loss function)。以最常見的最小平方為例:

$$ \mathcal{L}[\boldsymbol\phi] \;=\; \frac{1}{I}\sum_{i=1}^{I}\bigl(f[x_i, \boldsymbol\phi] - y_i\bigr)^2 $$

逐項拆解:\(I\) 是訓練資料的筆數;\(x_i\) 與 \(y_i\) 是第 \(i\) 筆的輸入與正確答案;\(f[x_i, \boldsymbol\phi]\) 是模型在目前這組參數下對第 \(i\) 筆的預測;括號裡是預測與答案的差,平方之後就不分正負、而且差得越遠罰得越重;求和後除以 \(I\) 取平均,讓損失不隨資料量膨脹。整條式子只有一個自由的東西:\(\boldsymbol\phi\)。所以 \(\mathcal{L}\) 是一個以參數為變數的函數,訓練就是要讓它變小:

$$ \hat{\boldsymbol\phi} \;=\; \arg\min_{\boldsymbol\phi} \mathcal{L}[\boldsymbol\phi] $$

\(\hat{\boldsymbol\phi}\) 上面那個尖帽子表示「挑出來的那一組」,跟任意一組 \(\boldsymbol\phi\) 區分開。

用最小的例子把這件事跑一遍。設想我們想從單日進站的郵件量預測當天需要多少工時,函數族取所有一次函數 \(f[x,\boldsymbol\phi] = \phi_0 + \phi_1 x\),只有兩個參數:

PYTHON
import numpy as np

rng = np.random.default_rng(0)

# 教學用例:單日進站郵件量(萬件)→ 分揀工時(小時)
x = np.linspace(2.0, 12.0, 24)

# 真實關係+量測雜訊
y = 3.4 + 1.15 * x + rng.normal(0, 0.8, x.shape)


def f(x, phi):            # 模型族:所有一次函數
    return phi[0] + phi[1] * x


def loss(phi):            # 最小平方損失
    return float(np.mean((f(x, phi) - y) ** 2))


for phi in ([0.0, 1.0], [3.4, 1.15], [6.0, 0.7]):
    print(f"phi={phi}  loss={loss(phi):.3f}")

# 「挑一個成員」=在參數格點上找損失最小者
p0 = np.linspace(0, 8, 161)
p1 = np.linspace(0, 2, 101)
L = np.array([[loss([a, b]) for b in p1] for a in p0])
i, j = np.unravel_index(L.argmin(), L.shape)
print(f"best=[{p0[i]:.2f}, {p1[j]:.2f}] loss={L[i,j]:.3f}")

實跑輸出:phi=[0.0, 1.0] 的損失是 19.654phi=[3.4, 1.15]0.469phi=[6.0, 0.7]2.477;格點搜尋挑出的是 best=[3.40, 1.14],損失 0.462。三件事值得記住。第一,同一個函數族裡不同成員的好壞差距很大,最差的那組損失是最好的四十倍以上。第二,格點挑出的 1.14 跟生成資料時用的 1.15 不完全一樣——資料裡有雜訊,最貼合這批樣本的參數本來就不會剛好等於真值,這個落差會在第 8 章成為主題。第三,這個做法之所以可行,只因為參數只有兩個。

橫軸是參數,縱軸是損失,訓練就是在這條曲線上找谷底。現代模型的參數動輒上億個,這條軸也就有上億維;假設每個維度只取十個格點,格點總數就是十的上億次方——這個數字大到沒有意義。所以格點搜尋只是教學示意,真正可行的做法是從某一點出發、沿著讓損失下降的方向一步步走,那是第 6 章的內容。

那麼深度神經網路特別在哪裡?在於它同時滿足兩個彼此拉扯的條件:函數族夠大,能表示輸入與輸出之間非常複雜的關係(第 3、4 章要證明的事);而在這個大族裡搜尋卻不會太難,用簡單的下坡法通常就能找到夠好的成員。表達力強的模型不難設計,難的是表達力強而且好搜。這兩件事同時成立,是深度學習能用的根本原因,也是第 20 章要追問的謎。

四、非監督式學習:沒有答案時,訓練訊號從哪裡來

把成對的答案拿掉,第一個該問的其實不是「學什麼」,而是更前面的一題:沒有人告訴你對錯,還有什麼東西可以拿來當訓練訊號? 這就是非監督式學習(unsupervised learning)要處理的處境。

訊號藏在一個地方:真實資料從來不會把它名義上的空間佔滿。 舉個具體的。假設分揀中心每天收班後記下六十四項營運量測——各條輸送帶的流量、各時段的滯留件數、各個出口的堆積高度。攤開來就是六十四個數字,所以我們說這批資料是六十四維的。

但這六十四個數字不會各走各的。當天的總進站量一動,它們全部跟著動;是不是連假前一天,又讓它們整體偏移一次。換句話說,真正能獨立變動的東西可能只有兩件,其餘的方向都只是這兩件事的附帶結果。一批資料裡能獨立變動的量有幾個,就叫它的自由度(degrees of freedom)——這裡名義維度是六十四,自由度卻可能只有二。這個「量了很多、其實沒那麼多」的落差,本身就是可以學的東西,而且學它完全不需要標籤。

落差可以直接量出來。要量它,得先問一個問題:這批資料在哪幾個方向上變化得最劇烈?線性代數有現成的工具——奇異值分解(singular value decomposition) 會把資料矩陣拆開,交出一組互相垂直的方向,以及資料在每個方向上變化的幅度(那個幅度就叫該方向的奇異值)。把某個奇異值的平方除以全部奇異值平方的總和,得到的就是「這個方向承擔了整批資料多少變化」的百分比。如果自由度真的只有二,那麼前兩個方向應該就吃掉幾乎全部的百分比。底下這段程式造一批表面上六十四維、實際上只由兩個變因控制的資料,再用這個方法檢查:

PYTHON
import numpy as np

rng = np.random.default_rng(0)

# 造一批「看起來 64 維,其實只有 2 個自由度」的資料
n, d = 300, 64
z = rng.normal(0, 1, (n, 2))       # 每筆只有 2 個真旋鈕
basis = rng.normal(0, 1, (2, d))   # 2 個變因 → 64 項量測
obs = z @ basis + rng.normal(0, 0.05, (n, d))   # 加量測雜訊

obs = obs - obs.mean(axis=0)       # 置中後做奇異值分解
s = np.linalg.svd(obs, compute_uv=False)
share = s ** 2 / np.sum(s ** 2) * 100      # 各佔變異百分比

print("奇異值:", " ".join(f"{v:.2f}" for v in s[:6]))
print("占比:", " ".join(f"{v:.3f}%" for v in share[:6]))
print(f"前 2 個成分合計解釋 {share[:2].sum():.2f}% 的變異")

實跑輸出:前六個奇異值是 146.15 122.70 1.24 1.19 1.17 1.15,對應的變異占比是 58.582% 41.293% 0.004% 0.004% 0.004% 0.004%,前兩個成分合計解釋 99.87%。剩下六十二個方向加起來只佔 0.13%,那是我們加進去的量測雜訊。

既然這批資料實際上只在兩個方向上變動,它等於是住在六十四維空間裡的一小片二維平面上——線性代數把這種「大空間裡的小空間」叫子空間(subspace)。認清這件事之後,就可以把它倒過來用:與其費力描述那一大團高維數字,不如直接描述那個低維的子空間,再學一個把低維攤開成高維的映射。這就是潛在變數(latent variable)的想法:

$$ x \;=\; g[z, \boldsymbol\phi], \qquad z \sim Pr(z) $$

逐項拆解:\(z\) 是潛在變數,一個維度很低的向量,你可以把它想成「真正在動的那幾個旋鈕」。\(Pr(z)\) 是 \(z\) 的機率分布,也就是「哪些 \(z\) 值比較常出現」的描述;這個分布是我們自己挑的,而且刻意挑簡單的,最常見的選擇是標準常態分布(平均值 0、標準差 1 的那個鐘形分布)。符號 \(\sim\) 讀作「服從」,所以 \(z \sim Pr(z)\) 的意思是「\(z\) 是從 \(Pr(z)\) 這個分布裡隨機抽出來的」。\(g[\cdot]\) 是深度網路,負責把低維的 \(z\) 攤開成高維的資料 \(x\);\(\boldsymbol\phi\) 是這個網路的參數。整條式子等於在宣告:資料看起來複雜,是因為一組簡單的東西被一個複雜的映射攤開了。

這個寫法立刻換來一項能力:你可以造出新資料。 所謂從一個分布「取樣」,意思就是照那個分布的機率去隨機抽一個值出來。於是流程變成:從 \(Pr(z)\) 抽一個訓練時沒出現過的 \(z\),餵進 \(g\),出來的東西不在訓練集裡,卻長得像訓練集裡的東西。做得到這件事的模型統稱生成模型(generative model)。

生成模型內部還有一個設計上的分岔:要不要把整個資料分布明明白白寫成一條式子。寫出來,你就多一項能力——可以回答「這一筆有多罕見」;不寫,你照樣抽得出新樣本,只是說不出機率值。第 15 到 18 章的四種主流做法,分屬這個分岔的兩邊。另外,如果把輸出的一部分先釘死、只讓模型補完其餘部分——指定「連假前一天、商業件為主」,要它生出符合這個條件的一整天營運量測——那叫條件生成(conditional generation),四種做法都能加上這一層。

比喻: 回到分揀中心,換一個工作。今天進來一批信件,地址欄全都被雨水糊掉了,沒有任何一封知道正確出口。你還是可以做事:把它們按尺寸、厚度、紙質、郵票樣式分成幾堆,然後發現這批信件的變化其實只由兩件事決定——寄件的時間段,以及是商業件還是私人件。你沒有學會「哪封信去哪裡」,但你學會了「這批信件長什麼樣」。這個比喻在一處失準:分堆是離散的,一封信只能屬於一堆;潛在變數是連續的,兩封信可以在同一條軸上相差 0.3,而中間那些沒出現過的值也對應到合理的信件。連續性正是生成模型能造出新樣本的原因。

真正棘手的不是怎麼學,而是怎麼評價。監督式學習可以拿答案對一對,非監督式學習沒有答案可對。假設有兩套模型都把那六十四項量測壓成兩個數字:一套壓出來的兩個數字剛好對應「總進站量」與「連假效應」,另一套壓出來的是兩個說不清楚是什麼的混合量。如果兩者從低維還原回六十四維的誤差一模一樣,你憑什麼說前者比較好?「學到的表示是好是壞」到今天都還沒有公認的量尺,這是第 14 章要正面處理的問題。

五、強化學習:當訓練訊號得用行動去換

前面兩節的訓練訊號有一個共同點,容易被當成理所當然:它是現成的。 監督式學習每一筆資料都自帶答案,非監督式學習整批資料就攤在你面前;你要看幾遍、算幾次都行,訊號不會因此變少。

強化學習(reinforcement learning)動到的正是這個前提。這裡的訊號不是現成的,得拿行動去換,而且換回來的東西又少又晚。

先把詞備齊。做決定的那一方叫代理(agent),它面對的其餘一切叫環境(environment)。代理在每個時間點看到一個狀態(state),據此挑一個動作(action),環境隨後給出新的狀態,偶爾附帶一個獎勵(reward)數字。有一點跟前兩節很不一樣,值得先記下來:同一個動作在不同時候未必把你帶到同一個地方——環境本身可以帶隨機性,而在監督式與非監督式學習裡,「同樣的輸入必得同樣的輸出」是預設成立的。代理要爭取的不是單步獎勵最大,而是整趟折算後的總和:

$$ R \;=\; \sum_{t=1}^{T} \gamma^{\,t-1} r_t $$

逐項拆解:\(R\) 是整趟任務的累積回報,也就是代理真正想放到最大的那個量;\(T\) 是這一趟走了幾步;\(r_t\) 是第 \(t\) 步當場拿到的獎勵。\(\gamma\)(希臘字母 gamma)是一個介於 0 與 1 之間的常數,叫折扣因子——看它的指數就明白它在做什麼:第 1 步的權重是 \(\gamma^0 = 1\),完全不打折;第 10 步的權重是 \(\gamma^9\),若 \(\gamma = 0.9\),就只剩下約 0.39。越晚拿到的獎勵,算進總和時越輕。這樣設計有兩個理由:一是它反映了「早點拿到比較好」這個真實偏好,二是當一趟任務可能拖得很長甚至沒有盡頭時,它保證這個總和不會加到無窮大。

把這裡的訊號拿去跟監督式學習的比,會發現它在三個互不相同的軸上同時退化。份量上,一整趟下來只換到寥寥幾個數字,而且這些數字只評價「這樣做好不好」,從不指示「應該怎麼做」。時間上,獎勵通常在造成它的那個動作發生之後很久才落地。密度上,絕大多數步驟什麼回饋都沒有。

三者疊在一起,就長成強化學習的核心難題:信用分配(credit assignment)——獎勵來了,功勞該記在哪一步的帳上?

這個困難有多實在,可以直接量給你看。底下這段程式模擬五個決策點,每個決策點二選一,但真正影響成敗的只有第 2 與第 5 個;代理只看得到最後的成敗結果:

PYTHON
import numpy as np

rng = np.random.default_rng(0)

N, T = 20000, 5                  # N 趟任務,每趟 T 個決策點
acts = rng.integers(0, 2, (N, T))   # 隨機策略:各丟一次銅板

# 真實規則(agent 看不到):只有第 2 與第 5 個決策影響成敗
good = (acts[:, 1] == 1) & (acts[:, 4] == 1)
p_win = np.where(good, 0.9, 0.1)
reward = (rng.random(N) < p_win).astype(float)

print("整體平均獎勵:", round(reward.mean(), 4))
for t in range(T):
    q0 = reward[acts[:, t] == 0].mean()
    q1 = reward[acts[:, t] == 1].mean()
    print(f"決策點 {t+1}:A={q0:.4f}  B={q1:.4f}"
          f"  差距={abs(q1 - q0):.4f}")

實跑輸出:整體平均獎勵是 0.3058。五個決策點兩個選項的回報差距依序是 0.00420.41010.00180.00030.4015。第 2 與第 5 個決策點的差距比其餘三個大了兩個數量級,訊號清楚浮出來了——但請注意這是跑了兩萬趟之後的結果。趟數不夠時,真正有用的訊號會淹在雜訊裡,這就是強化學習出了名地耗樣本的原因之一。作業三會請你量出這個門檻。

還有一個兩難是前兩節不會遇到的:探索與利用(exploration and exploitation)。它的根源就在本節開頭那句話——訊號得拿行動去換。代理想知道一個沒試過的動作值多少分,沒有別的辦法,只能真的做一次,然後承受做完的後果。

把兩邊的代價攤開來比,就看得出這個兩難難在哪裡。假設分揀中心目前把南線的件全部走三號轉運道,隔日送達率九成,堪用。要知道剛開通的四號轉運道行不行,唯一的辦法是把明天的一批件真的送上去;萬一它更慢,那批件的延誤是實打實的,當天就有人打電話來問。而繼續走三號的代價呢?它不會出現在任何一天的報表上——四號如果其實能做到九成六,你也永遠不會知道,因為那個數字從來沒被量出來過。一邊的代價立刻可見、而且找得到人負責;另一邊的代價永遠隱形。 這種不對稱會讓系統穩穩地停在一個「還可以」的做法上,而且停住的時候一切看起來都很正常。所以強化學習演算法必須把「拿已知的分數」和「量沒量過的東西」當成一件要主動分配的事,不能等它自然發生;第 19 章會給幾種具體的分配辦法。

那深度學習在這整套裡站在哪個位置?代理需要一個東西替它決定「現在該做什麼」,這個東西叫策略(policy);當這個角色由一個深度網路來擔任,它就叫策略網路(policy network)。要注意的是這個網路本身沒有任何新東西——它依然是第一小節那個 \(f[x, \boldsymbol\phi]\),輸入換成狀態、輸出換成動作,可調的還是那組參數。強化學習改掉的從來不是模型,而是訓練訊號怎麼來

比喻: 分揀中心到了深夜結帳,系統只回報一個數字:今天的誤投率是 1.8%。沒有人告訴你是哪一批信、哪一台分揀機、哪一個班次出的問題。你只能明天換一種排班方式再看一次數字,一天一個數字地摸索。這正是延遲、稀疏、純量回饋下的處境。這個比喻在一處失準:真實的分揀中心可以在每封信上加條碼,把責任定位到單一環節,把問題轉回監督式學習;強化學習之所以成為一個獨立的難題,正是因為許多環境根本拿不到這種逐步標註——如果拿得到,就不必用強化學習了。

六、部署之前:三個關於「誰」的問題

一個模型準確率高,不等於它可以上線。這不是道德勸說,是工程判斷——準確率衡量的是平均表現,而傷害從來不按平均分布。真正該追問的不是「它多準」,而是三個關於「誰」的問題。

第一問:這份訓練資料是替誰說話的? 假設分揀中心想學一個模型,判斷哪些包裹該送去加驗,訓練資料就取歷年的加驗紀錄與查獲結果。陷阱在這裡:過去被加驗得最兇的那幾個區域,紀錄裡的查獲件數自然也最多——沒被加驗過的地方根本不會產生查獲紀錄。模型忠實地學會「這幾區風險高」,於是這幾區被加驗得更兇,隔年的資料又更偏一點。整個過程沒有任何一步算是「出錯」,模型完全照設定執行;出問題的是設定裡從頭到尾沒有人問過「這份資料是怎麼被生出來的」。招募篩選、核貸額度、保費定價,背後都是同一個結構。

第二問:決定做出來以後,誰能檢查? 你可以問「為什麼這一件被判去南線」,並且拿到一個看得懂的說法——針對單筆決定給解釋,是可解釋 AI(explainable AI)目前相對做得動的部分。你問不了的是「這台機器整體上依據什麼在判」,因為那個答案是幾百萬個連續數字,沒有哪一個單獨對應到一條能複述的規則。第一小節比喻收尾點出的那個失準之處,到這裡就變成一個實際的治理問題:可稽核性不會因為系統夠準就自動長出來。

第三問:出事的時候,誰承擔、誰得利? 這一題最少被問,卻最要緊。訓練前沿模型的門檻高到只有少數組織跨得過去,能力因此在結構上往少數人手裡集中;而技術一旦好用,用途就不會停在開發者設想的範圍裡。承擔代價的那些人——被自動化取代的職務、被誤判的申請人、被偽造內容針對的個人——通常不在做決定的那張桌子旁邊。還有一層時間差:這類系統一旦鋪進基礎設施,傷害的形式往往要好幾年才看得清楚,而那時候要把它拔掉的成本,已經高到沒有人願意付。

這三個問題不是本章能解決的,第 21 章會完整處理。這裡只要求你養成一個習慣:看到一個模型的準確率數字時,順手多問一句——它錯的時候,帳算在誰頭上。

最後是這門課的地圖。往後二十章其實只在回答四個問題。第 2 到 9 章問的是「怎麼把函數族裡的那個成員挑得又快又準」:函數族該長什麼樣、損失從哪裡來、怎麼挑、挑完怎麼知道沒挑歪。第 10 到 13 章問「不同形狀的資料該配什麼結構」,答案是四種主力架構。第 14 到 18 章問「沒有答案時能學到什麼」。第 19 章問「訊號又晚又稀疏時該怎麼學」。剩下兩章各自收尾:第 20 章回頭追問一個到現在都還沒有好答案的問題——這一切為什麼會有效;第 21 章談責任歸屬。

§03原書對照

原書第 1 章是全書的入口,要在十六頁裡同時交代三種學習範式、倫理討論與閱讀指南。本課這一章重新排了敘事順序,改以「回饋訊號」當主軸,因此有幾個地方原書鋪陳得更細,值得進階讀者翻回去看。

開場的定位就值得對照。原書 p.1 用一整段把人工智慧、機器學習與深度學習的隸屬關係釘死,並提醒讀者這本書既不走純理論路線也不走純實作路線。接著 pp.2–3 用一張跨頁圖並排五個具體任務:房產價格預測、分子物理性質的多變量迴歸、餐廳評論的正負判定、音樂類型辨識、影像物件辨識,逐一交代每個任務的輸入怎麼構造、輸出向量的長度是多少。緊接著的 p.4 補上編碼的細節,包括文字為什麼要固定詞彙表、聲音與影像的維度量級如何估算。想要一份「輸入輸出形態」的完整對照,那幾頁比任何摘要都有效率。

第二處是結構化輸出。原書 pp.5–7 把語意分割、單目深度估計、語音轉寫、機器翻譯、以文字生成影像五類任務放在一起,指出共同困難分兩層:輸出可能本來就不唯一,以及輸出自身帶有文法。原書在那裡順勢說明了為什麼「學會輸出端的文法」會把讀者引向非監督式學習;想看這條銜接被完整鋪陳一次,那三頁值得一讀。

第三處是生成模型的實例展示。原書 pp.8–11 密集放了一整組展示圖:模型合成的貓與建築、由語言模型續寫的短篇故事、影像修補、以文字為條件生成的多張影像、人臉變化自由度的說明,以及潛在變數之間的插值結果。這些例子撐起了「潛在變數為什麼合理」的直覺,本課改用一段可執行的程式碼取代,兩種取徑互補。

第四處是監督式與非監督式的接合。原書在 p.11 用一小節說明:與其把文字直接映到影像,不如先各自找出兩邊的潛在變數再學它們之間的關係,並列出這樣做的三項好處,每一項好處各自的理由都寫了出來,也順帶解釋了同一句描述為什麼能生出好幾張不同的圖。想把這條論證補齊的人,那一頁寫得最完整。

第五處是強化學習的兩個具體場景。原書 pp.11–12 用機器人學走路與下棋來講延遲獎勵與探索利用的取捨,並在 p.13 用一張圖說明策略網路怎麼把棋盤狀態映到走法。第六處是倫理。原書 pp.12–14 列出五項關切並各附一則延伸閱讀,最後一段轉向讀者自身的處境:科學家對技術的用途應該負多少責任、你願意為什麼樣的組織工作。那幾段的語氣比本課更直接,也更值得逐句讀。

最後,原書 p.15 交代全書章節的分工方式,pp.15–16 給出一份延伸書單,涵蓋機器學習通論、電腦視覺、圖神經網路與強化學習等方向;p.16 則說明每章的正文、註記與習題該怎麼搭配閱讀,以及作者提供的線上筆記本與附錄 A 的記號約定該在什麼時機翻查。原書第 1 章對應印刷頁 pp.1–16。

§04作業和解答

作業一:把一個任務寫成輸入輸出規格

你要為一座分揀中心做一個模型,判斷輸送帶上的包裹屬於十二種處理類別中的哪一種。輸入是包裹頂視角的彩色照片,解析度 128×128,三個色彩通道。請回答:(a)輸入向量的長度是多少;(b)輸出向量的長度是多少、每個元素代表什麼、它們必須滿足什麼條件;(c)若改成收音判斷包裹裡是否有玻璃碎裂聲,錄音為 48 kHz 立體聲十秒,輸入向量長度是多少;(d)第(c)小題的輸出如果只要回答「有/沒有」,屬於哪一種任務形態。

解答 SOLUTION

(a)128 × 128 × 3 = 49,152。把每個像素的三個色彩值全部攤平接起來就是這個長度。

(b)長度 12,每個元素是「屬於該類別的機率」。兩個條件:每個元素都不小於零,且十二個元素相加等於一。這正是 softmax 保證的性質,本章第二小節的程式輸出裡機率總和為 1.0 就是在檢查這件事。

(c)48,000 × 10 × 2 = 960,000。取樣率乘秒數得單聲道樣本數,再乘聲道數。這比(a)大了將近二十倍——高輸入維度是聲音任務的常態。

(d)二元分類。輸出是長度 2 的機率向量,或等價地一個介於零與一之間的數。要注意這類任務的兩類通常極不平衡(有碎裂聲的包裹很少),準確率會失去參考價值,衡量方式要另外設計——那是第 8 章的主題。

作業二:在函數族裡挑成員

給定四筆訓練資料 \((x_i, y_i)\):\((1, 4)\)、\((2, 7)\)、\((3, 9)\)、\((4, 12)\)。函數族為所有一次函數 \(f[x,\boldsymbol\phi] = \phi_0 + \phi_1 x\),損失採本章第三小節的平均平方誤差。(a)比較 \(\boldsymbol\phi_A = (2.0, 2.5)\) 與 \(\boldsymbol\phi_B = (1.5, 2.6)\) 誰的損失小,寫出計算過程。(b)用最小平方的閉式解求出這一族裡的最佳成員,並說明它跟(a)的關係。

解答 SOLUTION

(a)\(\boldsymbol\phi_A\) 的四個預測是 4.5、7.0、9.5、12.0,殘差為 −0.5、0、−0.5、0,平方和 0.5,除以四得損失 0.125。\(\boldsymbol\phi_B\) 的四個預測是 4.1、6.7、9.3、11.9,殘差為 0.1、−0.3、0.3、−0.1,平方和 0.2,除以四得損失 0.05。所以 \(\boldsymbol\phi_B\) 比較好。

(b)閉式解為 \(\phi_1 = S_{xy}/S_{xx}\)、\(\phi_0 = \bar{y} - \phi_1 \bar{x}\)。這裡 \(\bar{x} = 2.5\)、\(\bar{y} = 8\);\(S_{xx} = 2.25 + 0.25 + 0.25 + 2.25 = 5\);\(S_{xy} = (-1.5)(-4) + (-0.5)(-1) + (0.5)(1) + (1.5)(4) = 13\)。所以 \(\phi_1 = 13/5 = 2.6\),\(\phi_0 = 8 - 2.6 \times 2.5 = 1.5\)。也就是說 \(\boldsymbol\phi_B\) 剛好就是最佳成員,損失 0.05 是這個函數族能達到的下限。

這裡有一個容易被忽略的重點:損失 0.05 不等於零,而且不管怎麼調參數都降不到零。這不是沒訓練好,是這一族函數的表達力上限——資料不完全落在一條直線上。想再降低損失只能換一個更大的函數族,而換大之後會帶來新的麻煩,那是第 8、9 章的內容。(本題數值以 numpy 重算核對過。)

作業三:量出信用分配需要多少樣本

修改本章第五小節的信用分配程式:把規則改成只有第 2 個決策點影響成敗,且成功機率只從 0.45 微幅提升到 0.55(原本是 0.1 對 0.9)。(a)用理論估計,要跑多少趟才能讓真正有效的決策點與無效決策點明顯分開;(b)用程式在 N = 400、3600、20000 三種趟數下驗證你的估計。

解答 SOLUTION

(a)先說清楚要比什麼。我們手上算出來的「兩個選項的回報差距」是一個由隨機資料估出來的數字,它自己就會抖;標準誤指的就是這種抖動的典型幅度。只有當真訊號明顯大過抖動幅度,我們才敢說「這個決策點真的有影響」。

每個決策點的兩個選項各分到約 N/2 趟。獎勵只有 0 與 1 兩種值、出現 1 的機率約 0.5,這種變數的變異數是 \(p(1-p) = 0.25\)。平均值的變異數等於單筆變異數除以筆數,所以每組平均值的變異數約 \(0.25/(N/2) = 0.5/N\);兩組相減時變異數相加,得 \(1/N\),開根號後標準誤就是 \(1/\sqrt{N}\)。真訊號的差距是 0.55 − 0.45 = 0.10。要求訊號至少達到三倍標準誤才算看得出來,需要 \(0.10 > 3/\sqrt{N}\),即 \(\sqrt{N} > 30\),也就是 N 大於 900。

(b)把原程式的 good 改成 acts[:, 1] == 1np.where(good, 0.9, 0.1) 改成 np.where(good, 0.55, 0.45),其餘不動,實跑結果如下。N = 400 時五個決策點的差距是 0.0529、0.0881、0.0498、0.0043、0.0534——真訊號 0.0881 跟雜訊 0.0534 只差不到兩倍,分不出來。N = 3600 時是 0.0263、0.1165、0.0145、0.0083、0.0163,真訊號已經比最大雜訊高出四倍多。N = 20000 時是 0.0006、0.1173、0.0053、0.0069、0.0029,差距一眼可辨。三組結果都與(a)的估計相符:門檻確實落在 N 約一千的量級,低於它就看不出來。

這一題要帶走的結論是:強化學習耗樣本不是實作沒調好,而是回饋訊號的信噪比本來就低;訊號越弱、決策點越多,需要的趟數增長得越快。第 19 章會介紹幾種在同樣資料量下把訊號擠出來的做法。

§05參考資料