§01學習重點
- 說出「通用逼近定理已經保證前饋網路夠用」之後,為什麼還需要新架構:答案是參數節約——用結構先驗換取更少的權重與更低的過擬合風險
- 把 RNN 的隱狀態理解為「壓縮了全部歷史的記憶」,並親手驗證:線性激活的 RNN 就是權重幾何衰減的 AR(p)——兌現第 6 章的第一條伏筆
- 用「梯度是導數的連乘」解釋梯度消失,實測 tanh 的飽和效應為什麼讓「調大循環權重」救不了長記憶
- 沿著指數平滑 → α-RNN → GRU → LSTM 的系譜理解門控機制:每一級升級都只是把一個固定係數換成可以學習、再換成隨情境變動的函數
- 白話拆解 LSTM 三道閘各自的任務,說出 GRU(一份記憶)與 LSTM(兩份記憶)的結構差異,以及「資料平穩與否」如何決定用不用門控
- 把一維卷積理解為「係數可學習的加權移動平均」,用感受野與擴張卷積處理日、週、月的多尺度依賴——兌現第 6 章的 MA/平滑伏筆
- 數值驗證線性自編碼器與 PCA 的等價(子空間夾角小於 0.05 度),並據此理解深度自編碼器是 PCA 的非線性推廣——兌現第 6 章的第三條伏筆
§02課程內容
一、一個共同的問題:參數要省在哪裡
第 1 章講過通用逼近定理:一個隱藏層加非線性激活,理論上就能逼近任何連續函數。那為什麼深度學習的動物園裡還有這麼多品種?工程上的答案很不浪漫:理論上夠用,不代表統計上養得起。把一段 100 期的時序攤平餵給前饋網路,輸入維度是 100;隱藏層 50 個神經元就是 5000 個權重。金融資料訊噪比低、有效樣本少(第 4 章反覆強調過),這種參數量正是過擬合的溫床。
本章三族架構走的是同一條路:在架構裡寫死一條關於資料結構的先驗假設,用它大幅削減參數。循環神經網路(RNN)假設「規律不隨時間改變」,讓所有時間步共用同一組權重;卷積神經網路(CNN)假設「局部形態在哪裡出現都長一樣」,讓所有位置共用同一個小濾波器;自編碼器(AE)假設「高維資料其實活在低維結構上」,強迫網路把自己壓縮過瓶頸。假設對了,你用少得多的參數學到一樣的東西——過擬合風險與訓練成本同時下降;假設錯了,架構會系統性地學不到某些形態。所以選架構本質上是在選先驗,這正是本章最後一節選型指南的邏輯。
第 6 章結尾鋪的路線圖在這裡正式兌現:計量模型是神經網路的特例,神經網路是計量模型的鬆綁。本章每介紹一族架構,都會先把它「退化」回第 6 章的某個老朋友——RNN 退回 AR、卷積退回移動平均、自編碼器退回 PCA——看清楚特例長什麼樣,再談鬆綁多出來的能力與代價。
二、RNN:把整段歷史煮成一個狀態
前饋網路的世界觀是「一題一答」:每個樣本獨立進、獨立出。時序資料的困難在於答案藏在順序裡——要預測下一期,你需要記得之前發生過什麼。RNN 的解法是維護一個隱狀態(hidden state)向量 \(h_t\),每來一筆新觀測就更新一次:
逐項拆解:\(h_{t-1}\) 是「看完前 t−1 期之後的記憶」;\(x_t\) 是本期新觀測;\(W_z\) 是循環權重——決定舊記憶帶多少進下一步;\(W_x\) 是輸入權重——決定新資訊的音量;\(\sigma\) 是非線性激活(常用 tanh);把兩者混合、彎折一下,得到新記憶 \(h_t\);最後一層 \(W_y\) 把記憶翻譯成預測。關鍵設計是所有時間步共用同一組 \(W_z, W_x\):不管序列多長,參數量不變。這就是第一節說的先驗——「市場對昨天的反應規則,和對前天的反應規則相同」。
比喻: RNN 的隱狀態是一鍋老滷。滷汁裡沒有保存任何一塊肉,但每一塊滷過的肉都在裡面留下味道;每天加新料(新觀測 \(x_t\)),老味道被稀釋一點但不歸零(\(W_z\) 決定留多少)。你嚐一口今天的滷汁(讀出 \(h_t\)),嚐到的是全部歷史的濃縮,而不是某一天的原味。整鍋滷的規則——加多少水、留多少汁——天天相同,這就是權重共享。
把遞迴沿時間攤開來畫,就是展開圖:同一個更新函數複製 p 份、首尾相接,最舊的觀測先進、最新的最後進,隱狀態像接力棒一路傳到底。展開幾步(序列窗多長)不是玄學:第 6 章的偏自相關函數(PACF)在這裡直接回收——PACF 在幾階之後截尾,窗就開多長。
兌現第一條伏筆。第 6 章說過,線性激活的 RNN 就是權重幾何衰減的 AR(p)。現在動手證:把激活換成恆等函數、退化到單一隱藏單元,設循環權重為 \(\phi_z\)、輸入權重為 \(\phi_x\),把遞迴展開 p 步,每往回一步,舊觀測就多被 \(\phi_z\) 乘一次:
逐項拆解:最新一期的權重是 \(\phi_x\);再往前一期是 \(\phi_x\phi_z\);每退一期就再乘一個 \(\phi_z\)——只要 \(|\phi_z|<1\),權重就以幾何速度衰減。這是一個 AR(p),但它的 p 個係數不是自由的,而是被兩個參數鎖成一條幾何衰減曲線。數值驗證:
import numpy as np
rng = np.random.default_rng(88)
phi_x, phi_z, p = 0.6, 0.5, 12
x = rng.normal(0.0, 1.0, size=300)
def rnn_forecast(x, t):
"""線性 RNN:z 從窗內最舊一步滾到最新"""
z = 0.0
for j in range(p, 0, -1):
z = phi_z * z + phi_x * x[t - j + 1]
return z
w_ar = phi_x * phi_z ** np.arange(p) # 幾何衰減的 AR 權重
def ar_forecast(x, t):
lags = x[t - p + 1: t + 1][::-1] # x_t, x_{t-1}, ...
return float(w_ar @ lags)
diff = max(abs(rnn_forecast(x, t) - ar_forecast(x, t)) for t in range(p, 300))
print("等價 AR 權重(前 6 個):", np.round(w_ar[:6], 4))
print("RNN 前向 vs 幾何權重 AR,最大差異:", diff)
# 輸出:
# 等價 AR 權重(前 6 個): [0.6 0.3 0.15 0.075 0.0375 0.0188]
# RNN 前向 vs 幾何權重 AR,最大差異: 4.440892098500626e-16最大差異 \(4.4\times10^{-16}\)——浮點精度極限,兩者是同一個模型。對照第 6 章的 AR(p):AR 讓每個滯後項的權重自由估計,線性 RNN 反而把權重形狀鎖死成幾何衰減、只留兩個參數——這是更節約的模型。RNN 真正的鬆綁在非線性激活:記憶的混合規則從「固定比例稀釋」升級成「依內容而變的非線性函數」,理論上能表達 AR 寫不出的形態。
穩定性這邊有一個反直覺的結論。第 6 章說 AR 要平穩,係數得落在單位圓的安全區內;RNN 呢?tanh 這類有界激活天生自帶安全帶——不管衝擊多大,隱狀態都被壓回 −1 到 1 之間,舊衝擊的影響隨滯後階數單調衰減,模型不會被一次極端值帶著跑。反倒是線性激活的 RNN 站不住:可以證明它不滿足平穩性條件——是非平穩的。所以在 RNN 的世界裡,非線性不是為了「更會彎」的奢侈品,而是換取平穩性的必需品——這跟第 1 章「非線性激活給網路表達力」的敘事互補:同一個彎折,在時序架構裡還多打了一份工。
三、梯度消失:連乘的詛咒
RNN 理論上記憶無限長,實務上卻出了名的健忘。病根在訓練:梯度要沿著展開圖往回傳,每退一個時間步,就要多乘一次「該步的局部導數」。以單神經元 tanh RNN 為例,隱狀態對 k 步之前隱狀態的敏感度是一條連乘:
逐項拆解:\(1-h_j^2\) 是 tanh 在第 j 步的斜率——tanh 輸出越接近 ±1(越飽和),斜率越接近 0,最大也只有 1;\(W_z\) 是循環權重;k 步就連乘 k 次。一串絕對值小於 1 的數連乘,衰減是幾何級的——這就是梯度消失:遠處的觀測對損失的影響傳不回來,模型等於學不到長依賴。
比喻: 耳語接龍。第一個人看到的訊息(遠處的梯度)要傳到隊伍另一頭,每個人轉述時都只保留八成——不是有人故意搗蛋,是每一棒都誠實地打了個八折。傳過 5 個人剩三成,傳過 20 個人剩百分之一。隊伍越長(序列越長),開頭的訊息越注定失真到無法辨認——問題不在任何一個人,在「連乘」這個結構本身。
實測連乘收縮的速度,順便拆掉一個直覺誤區——「把 \(W_z\) 調大,記憶不就傳得遠了」:
import numpy as np
rng = np.random.default_rng(8)
x = rng.normal(0.0, 1.0, size=64)
def grad_chain(w_z, w_x, K):
"""|dh_K/dh_0| = 各步 |tanh'(a)*w_z| 的連乘"""
h, prod, out = 0.0, 1.0, {}
for t in range(1, K + 1):
h = np.tanh(w_z * h + w_x * x[t - 1])
prod *= abs((1.0 - h * h) * w_z)
if t in (1, 5, 10, 20, 40):
out[t] = prod
return out
for w_z in (0.9, 3.0):
r = grad_chain(w_z, 0.8, 40)
print(f"tanh, w_z={w_z}: " + " ".join(f"k={k}:{v:.1e}" for k, v in r.items()))
for w_z in (0.9, 1.05, 1.2): # 線性激活對照:導數恆為 w_z
print(f"線性, w_z={w_z}: k=10:{w_z**10:.2e} k=40:{w_z**40:.2e}")
# 輸出:
# tanh, w_z=0.9: k=1:2.0e-01 k=5:7.0e-06 k=10:2.8e-08 k=20:1.2e-12 k=40:7.1e-21
# tanh, w_z=3.0: k=1:6.6e-01 k=5:2.0e-10 k=10:1.6e-16 k=20:5.8e-29 k=40:3.0e-62
# 線性, w_z=0.9: k=10:3.49e-01 k=40:1.48e-02
# 線性, w_z=1.05: k=10:1.63e+00 k=40:7.04e+00
# 線性, w_z=1.2: k=10:6.19e+00 k=40:1.47e+03三個教訓。第一,tanh 加 \(W_z=0.9\),梯度傳 10 步只剩 \(10^{-8}\)、40 步剩 \(10^{-21}\)——「學到 40 期前的事件」在數值上根本不存在。第二,把 \(W_z\) 加大到 3.0 反而死得更快:權重越大,隱狀態越常被推進 tanh 的飽和區,斜率 \(1-h^2\) 越接近零——飽和效應讓蠻力調參此路不通。第三,換線性激活雖然沒有飽和,但 \(|W_z|>1\) 時連乘變成幾何爆炸(\(1.2^{40}\approx 1470\)),訓練直接發散——而上一節說過線性 RNN 還非平穩。消失與爆炸兩面夾殺,出路只能是改結構:給記憶開一條不用連乘、可以直通遠方的通道。這就是門控。
四、門控:從指數平滑到 LSTM 的四級跳
門控機制常被講得神祕,其實第 6 章已經把梯子架好了。回想指數平滑:新估計 = α × 新資訊 + (1−α) × 舊估計——一條「帶折扣的記憶」遞迴,α 是手動指定的常數。整個門控家族,就是把這條式子升級四次。
第一級:α-RNN——把 α 變成可學參數。兌現第 6 章的第二條伏筆。在普通 RNN 外面套一層指數平滑:隱狀態先照常算出 \(\hat{h}_t\),再與平滑後的舊狀態做凸組合:
逐項拆解:\(\hat{h}_t\) 是本步新算出的隱狀態(新資訊);\(\tilde{h}_{t-1}\) 是平滑記憶(舊印象);\(\alpha\) 不再手動指定,而是跟其他權重一起從資料學出來。這一小步的回報巨大:平滑記憶是所有歷史狀態的幾何加權和,走的是加法混合而不是反覆過激活函數的連乘,於是 \(\alpha \neq 1\) 時模型帶著無限記憶——普通 RNN 的記憶止於窗長 p,α-RNN 的記憶一路延伸到序列第一筆。而當 α 學成 1,它就退化回普通 RNN:老朋友永遠是特例。
第二級:讓 α 隨情境變動。固定的 α 隱含「該記多少是常數」的假設,對平穩序列合理,對會變臉的市場太天真。升級:讓 α 自己也是一個小網路的輸出,\(\hat{\alpha}_t = \sigma(U_\alpha \tilde{h}_{t-1} + W_\alpha x_t + b_\alpha)\),依當下輸入與記憶即時決定「這一步要記多少、忘多少」。sigmoid 把輸出壓在 0 與 1 之間,天生就是門的開度。這個「動態版指數平滑」已經是一個簡易 GRU——你在文獻裡看到的遺忘門,血緣上就是長出腳的 α。
第三級:GRU——再加一道重置門。動態 α 能調節「新舊混合比例」,但做不到一件事:完全切斷遞迴、把記憶歸零。GRU 補上重置門 \(\hat{r}_t\):算新隱狀態時先把舊記憶乘上 \(\hat{r}_t\),重置門關死(輸出 0)時,新狀態只看當下輸入;再配上全開的遺忘門(α=1,不摻舊平滑記憶),整個網路瞬間變成前饋網路;重置門留著、遺忘門全開時,則退回普通 RNN。一個 GRU 因此是「前饋網路、普通 RNN、指數平滑記憶」三者的可切換聯集,切換開關由資料自己學。
第四級:LSTM——把記憶拆成兩份。LSTM 比 GRU 多一個獨立的細胞狀態 \(c_t\)(長期記憶帳本),與對外輸出的隱狀態 \(h_t\) 分開管理:
逐項拆解:\(\odot\) 是逐元素相乘;\(f_t\) 是遺忘門——帳本上每一項舊記憶這一步保留幾成(0 到 1);\(i_t\) 是輸入門——本步新算出的候選記憶 \(\tilde{c}_t\) 有幾成准予入帳;\(o_t\) 是輸出門——帳本內容有幾成翻譯給外界看。三道門各自都是「吃當下輸入與上一步隱狀態的 sigmoid 小網路」。注意第一項 \(f_t \odot c_{t-1}\):只要遺忘門開著,舊記憶走的是乘一個接近 1 的數再相加的通道,不經過激活函數的反覆彎折——這正是第三節末尾要的那條「梯度直通遠方的高速公路」,梯度消失的結構性解法。
比喻: LSTM 的細胞狀態是一座倉庫,三道門各管一件事。報廢門(遺忘門)每天巡一輪貨架,決定哪些舊庫存降價保留、哪些直接清掉;進貨門(輸入門)站在收貨口,決定今天到的新貨收幾成——旺季訊號多就多收,雜訊日就少收;出貨門(輸出門)管前台櫃檯,倉庫裡有的不必全部上架,只挑此刻用得上的示人。倉庫本身(\(c_t\))從不直接面客,面客的是櫃檯(\(h_t\))——帳、貨、展示三權分立。
GRU 與 LSTM 怎麼選?結構差異一句話:GRU 只有一份記憶(隱狀態兼任帳本),LSTM 有兩份(帳本與櫃檯分離)、參數多約三分之一。經驗法則來自資料的性格:平穩序列(例如高頻的價格變動量,第 6 章教過的 ADF 檢定可驗)用普通 RNN 常常就夠,門控的額外自由度換不到什麼;非平穩、有制度切換的序列(例如趨勢明顯的原始價位、跨越牛熊的長樣本)才是門控的主場——動態的門相當於內建了「隨時間改變行為」的能力,不必重訓模型。先驗又一次先於架構:先用第 6 章的檢定摸清資料性格,再決定買不買門控的複雜度,順序不能反過來。
五、序列預測的實務紀律
架構講完,講怎麼把資料餵進去不出事。三條紀律,全部是前面章節工具的回收再利用。
滑動窗成批。時序資料變成訓練樣本的標準做法:一個長度 T 的窗沿時間軸滑動,每個位置切出「過去 T 期特徵、未來 h 期標籤」的一對。窗長 T 用第 6 章的 PACF 定(顯著滯後的最大階數);預測跨度 h 由任務決定,且標籤有前瞻窗時,訓練與驗證的交界要挖掉等長的 gap——這是第 4 章時間切紀律的原樣搬運,滑動窗只是讓洩漏更隱蔽、不是讓它免疫。切分永遠沿時間:訓練在前、驗證在後,walk-forward 往前滾。
特徵也要是時間序列。餵給 RNN 的每一路輸入都會被當成序列處理,所以每個特徵必須自己帶有時序結構(有自相關可學),而且必須平穩——第 6 章的教訓在這裡加倍適用:拿原始價位當特徵,網路學到的是「這幾年剛好在漲」;拿報酬、價差、變動量,學到的才可能是規律。正規化的統計量(均值、標準差)只准從訓練段算,套用到驗證與測試段——用全樣本正規化是最常見、也最難察覺的前視洩漏之一。
評估先立地板。序列預測的成績單必須跟兩個廉價基準比:「猜零」(明天的報酬是零)與「猜昨天」(隨機漫步預測)。金融序列訊噪比低,深度架構跑贏這兩個地板的幅度往往小得令人清醒;跑不贏就發表的模型,多半是回測管線在某處漏了未來。第 1 章說過模型評估與策略回測是兩件事,這裡再加一句:架構越花俏,越要用越笨的基準看住它。
六、CNN:把移動平均變成可學習的濾波器
第 6 章介紹過移動平均與指數平滑:拿一組固定權重沿時間軸滑動,做局部加權平均,把雜訊濾掉。訊號處理把這類操作叫濾波,數學上它就是卷積:一個短短的權重向量(核,kernel)滑過整條序列,每個位置輸出一個內積。第 6 章的工具箱裡,這些權重是人指定的;CNN 的鬆綁只有一句話——把濾波器的係數交給資料學。
import numpy as np
rng = np.random.default_rng(83)
n = 400
x = np.cumsum(rng.normal(0.0, 1.0, n)) # 模擬一條價格路徑
# (1) 手寫滑窗 vs np.convolve:卷積就是滑動加權平均
k_han = np.array([0.25, 0.5, 0.25]) # 經典的 Hanning 平滑核
manual = np.array([k_han @ x[i-1:i+2][::-1] for i in range(1, n-1)])
conv = np.convolve(x, k_han, mode="valid")
print("手寫滑窗 vs np.convolve 最大差異:", np.abs(manual - conv).max())
# (2) 係數學習:資料由未知核生成,最小平方把核「學」回來
w_true = np.array([0.5, 0.3, 0.15, 0.05]) # 未知的真實濾波器
P = len(w_true)
X_lag = np.column_stack([x[P-1-j : n-j] for j in range(P)])
y = X_lag @ w_true + rng.normal(0.0, 0.3, size=n - P + 1)
w_hat = np.linalg.lstsq(X_lag, y, rcond=None)[0]
print("真實核:", w_true, "\n學到核:", np.round(w_hat, 3))
# 輸出:
# 手寫滑窗 vs np.convolve 最大差異: 1.7763568394002505e-15
# 真實核: [0.5 0.3 0.15 0.05]
# 學到核: [0.505 0.301 0.146 0.042]第一段驗證「卷積=滑動加權平均」到浮點精度;第二段是一顆最小的 CNN 神經元:資料由寬度 4 的未知濾波器生成,最小平方法從 400 個觀測把四個係數還原到誤差 0.008 以內。真正的一維 CNN 就是這個操作的堆疊版——多個核並排(每個核學一種形態)、輸出過非線性、再疊下一層卷積。
比喻: 卷積核是醫師的聽診器。聽頭只有一個(同一組權重),但醫師把它沿著背部一路滑(沿時間軸滑動),每個位置聽幾秒(窗內內積);聽頭的構造決定它對哪種聲音敏感——這顆聽雜音、那顆聽心律。CNN 等於讓醫師帶一整排聽頭(多個核),而且每顆聽頭該對什麼敏感,不是出廠設定,是從幾千份病歷裡學出來的。
CNN 的參數節約來自兩個結構性質。權重共享:同一個核用在序列的每個位置——先驗是「值得偵測的形態,出現在三月和出現在九月長一樣」;對照 RNN 的共享是跨時間步共用轉移規則,CNN 的共享是跨位置共用偵測器。稀疏連接:每個輸出只連到輸入的一小段窗,不像全連接層每個輸出看全部輸入。兩者相乘,一層卷積的參數量只有核寬 × 核數,跟序列長度無關。
由此多出兩個要認識的概念。感受野:一個輸出神經元「看得到」的輸入範圍。單層核寬 5 的卷積,感受野就是 5;疊 L 層,感受野線性成長。池化(pooling):把相鄰輸出做局部摘要(取最大或平均)再降採樣——訊號的粗粒度版本,換取平移容忍與更少的後續計算。
線性成長的感受野對金融是個麻煩:日資料要罩住一季的依賴就得疊幾十層。擴張卷積(dilated convolution)用跳格解決:第一層核看相鄰點,第二層核每隔 2 點取一個,第三層每隔 4 點——間隔逐層倍增,L 層的感受野變成 \(2^{L-1}(2k+1)\),指數成長。逐項拆解:\(2k+1\) 是單一核的寬度,\(2^{L-1}\) 是間隔倍增 L−1 次的累積放大。實務意義:淺淺幾層就同時罩住日、週、月三個尺度的形態——每層在不同解析度上各學各的規律,這正是語音生成模型 WaveNet 移植到時序預測的核心設計。用在預測時還有一條紀律:卷積窗只准往過去開(因果卷積),濾波不碰未來值,天生不引入前視偏差——但這只管住架構內部,資料切分的時間紀律仍照第五節執行。
CNN 在金融的自然棲地是有空間結構的資料:限價簿各價位的排隊量(價位軸是空間)、選擇權隱含波動率曲面(履約價 × 到期日是二維影像)、多資產的相關矩陣序列。時序之外它還是 RNN 的競爭者:對很多序列任務,「擴張卷積疊出來的多尺度 AR」與門控 RNN 表現相當,而卷積可平行計算、訓練快得多。
七、自編碼器:讓網路學會壓縮自己
前兩族架構做監督式學習:有特徵、有標籤。自編碼器(autoencoder)不要標籤——它的訓練目標是把輸入複製到輸出:\(\hat{Y} = F(Y)\),損失是重建誤差 \(\|Y - \hat{Y}\|^2\)。這聽起來像廢話——恆等函數不就滿分了?機關在網路中間被掐了一道瓶頸:某一層的神經元數 m 遠小於輸入維度 n,資訊必須先被壓進 m 維、再從 m 維解壓回 n 維。網路想重建得好,就被迫在瓶頸裡留下「最有代表性」的壓縮碼。訓練完成後從瓶頸剖開:前半是編碼器(壓縮),後半是解碼器(重建),瓶頸輸出就是資料的低維表示。
比喻: 搬家公司只給你三個箱子(瓶頸維度 m=3),但你有一整間房子的東西(n 維輸入)。裝箱時你被迫做取捨:留下最能代表生活的、丟掉可有可無的;到新家後憑三箱行李把生活重建出來,重建得越像原樣,代表你裝箱的取捨越高明。自編碼器的訓練就是反覆搬家幾萬次,把「該裝什麼」練成直覺——而重建誤差,就是新家與舊家的差距。
兌現第三條伏筆。第 6 章說線性激活的自編碼器會落回 PCA。理論結果很乾脆:兩層線性自編碼器 \(\hat{Y} = W^{(2)} W^{(1)} Y\) 把重建誤差最小化時,解碼權重 \(W^{(2)}\) 的行空間恰好張出前 m 個主成分載荷所張的子空間——梯度下降訓練出來的壓縮平面,就是 PCA 找到的那個平面。數值驗證,資料用模擬的殖利率曲線月變動(8 個期限、三因子生成,呼應第 6 章的晾衣繩):
import numpy as np
rng = np.random.default_rng(21)
n_obs, n_mat, m = 600, 8, 3
level = np.ones(n_mat) # 水平
slope = np.linspace(-1.0, 1.0, n_mat) # 斜率
curv = np.linspace(-1.0, 1.0, n_mat)**2 * 2 - 1.0 # 曲率
F = np.column_stack([level, slope, curv])
scores = rng.normal(size=(n_obs, 3)) * np.array([9.0, 3.5, 1.4])
Y0 = scores @ F.T + rng.normal(0.0, 0.45, size=(n_obs, n_mat))
Y0 -= Y0.mean(axis=0)
U, S, Vt = np.linalg.svd(Y0, full_matrices=False) # PCA
evr = S**2 / (S**2).sum()
P_load = Vt[:m].T
mse_pca = np.mean((Y0 - (Y0 @ P_load) @ P_load.T)**2)
W1 = rng.normal(0.0, 0.1, size=(m, n_mat)) # 線性自編碼器
W2 = rng.normal(0.0, 0.1, size=(n_mat, m))
for it in range(30000): # 全批次梯度下降
Z = Y0 @ W1.T
R = Z @ W2.T - Y0
W2 -= 2e-4 * (2 * R.T @ Z / n_obs)
W1 -= 2e-4 * (2 * (R @ W2).T @ Y0 / n_obs)
mse_ae = np.mean((Y0 - (Y0 @ W1.T) @ W2.T)**2)
Qa, _ = np.linalg.qr(W2); Qb, _ = np.linalg.qr(P_load)
ang = np.degrees(np.arccos(np.clip(
np.linalg.svd(Qa.T @ Qb, compute_uv=False), -1, 1)))
print("PCA 解釋變異(前 3):", np.round(evr[:3], 4))
print(f"重建 MSE:PCA={mse_pca:.4f} 線性AE={mse_ae:.4f}")
print("子空間主夾角(度):", np.round(ang, 4))
# 輸出:
# PCA 解釋變異(前 3): [0.929 0.0574 0.0121]
# 重建 MSE:PCA=0.1267 線性AE=0.1267
# 子空間主夾角(度): [0.0001 0.0033 0.0427]三個主夾角全部小於 0.05 度——梯度下降摸出來的三維壓縮平面,與 PCA 用特徵分解算出來的平面幾乎完全重合;重建 MSE 同為 0.1267,一分不差。第 6 章那句「線性 AE 可以證明就落回 PCA」,現在是你自己跑出來的事實。
等價歸等價,三個細節要當心,都可以在上面的實驗裡加幾行驗證(作業三會做)。其一,瓶頸座標彼此相關:PCA 的各主成分分數嚴格不相關(第 6 章的去相關機器),線性 AE 的瓶頸座標只張出同一個子空間、座標軸本身是斜的——本實驗實測 AE 瓶頸座標的最大配對相關高達 0.92,PCA 為 0.00。其二,不按變異數排序:PCA 的軸由大到小排好,AE 的三根軸沒有次序。其三,不巢狀:PCA 取前 2 個主成分就是 m=2 的最優解,AE 換瓶頸維度必須整個重訓。要從 AE 權重還原出「排好序、不相關」的正牌主成分,得對權重再做一次 SVD。
深度自編碼器才是真正的鬆綁:編碼器與解碼器各疊幾層非線性,壓縮面從「平面」升級成「彎曲的流形」。資料若貼著彎曲結構分布(金融資料常見——波動率曲面、信用利差曲線的形狀家族都不是平的),同樣的瓶頸維度 m,非線性壓縮的重建誤差可以顯著低於 PCA。注意層數本身不是重點:線性層疊再多還是線性(第 1 章摺疊實驗的教訓),非線性激活才是質變的開關。
金融用途三則。殖利率曲線壓縮:第 6 章用 PCA 把曲線壓成水平、斜率、曲率,深度 AE 做同一件事但允許因子非線性地組裝曲線,對負利率、倒掛這類極端形狀的重建更忠實。深度因子萃取:把一籃資產的報酬歷史壓過瓶頸,瓶頸座標就是「統計因子」的非線性版——代價是可解釋性與可投資性:PCA 因子是資產的線性組合、可以直接建組合複製,非線性因子是資產的複合函數、沒有現成的對沖配方。異常偵測:用正常時期的資料訓練 AE,上線後監控每天的重建誤差——模型只學會壓縮「見過的常態」,市場出現訓練分布之外的形態(流動性枯竭、相關性崩壞)時重建誤差跳升,這個訊號本身就是警報器,也呼應第 1 章「監控輸入分布漂移」的護欄哲學。
八、三族架構的選型指南
收攏本章。三族架構各壓了一條先驗、各省一種參數,選型就是問三個問題。
問題一:任務要的是預測還是表示?要標籤預測(明天的報酬、下一tick 的方向)→ RNN 或 CNN;要低維表示(因子、壓縮、異常分數)→ 自編碼器。兩者也能串接:先用 AE 把高維特徵壓成低維,再餵給預測模型。
問題二(預測路線):依賴的結構長什麼樣?記憶需要依內容決定留多少——制度會切換、非平穩、事件的影響長短不一 → 門控 RNN(GRU 起步,不夠再上 LSTM);依賴是多尺度的固定形態——日、週、月各有各的規律、形態在哪出現都一樣 → 擴張卷積 CNN,訓練還快;序列平穩、依賴短而穩定 → 普通 RNN 甚至第 6 章的 AR 就夠——永遠先跑計量基準,深度架構跑不贏素顏版就不值得部署。
問題三(表示路線):結構是平的還是彎的?先跑 PCA——它是線性 AE 的閉式解、免訓練、因子可排序可解釋;PCA 重建誤差已經夠小,就到此為止;誤差集中在少數極端形態、懷疑結構彎曲,才上深度 AE,並接受可解釋性的折損。
三族還能互相組裝:編碼器換成卷積層就是 CNN-AE(壓縮有空間結構的資料,如波動率曲面);換成 GRU 就是序列自編碼器(壓縮整段時序的動態)。架構是樂高,先驗是說明書——每加一塊,都要說得出它壓的是哪條假設、省的是哪些參數。說不出來的複雜度,就是第 1 章講的過擬合的溫床。
到這裡,第二部「時序學習」收官:第 6 章的計量地基、第 7 章的隱狀態機率觀、本章的神經網路鬆綁,合起來回答「怎麼預測序列」。第三部換問題:預測只是旁觀,決策會改變世界——下一章進入強化學習,第 1 章結尾埋的那條「從預測到決策」的線,終於要收。
§03原書對照
本課以白話重組了原書第八章的骨架,以下內容原書有、但本課未展開,供進階讀者按頁碼深入。其一,章首以參數節約總括三族架構的動機——原文稱 "the main motivation for different architectures is often parsimony of parameters"(p.239)——並定義序列預測問題的張量記法(pp.239–240)。其二,RNN 記憶的偏自共變異數分析:證明 RNN(p) 的偏自相關函數與 AR(p) 一樣在 p 階截尾,因此可用估計的 PACF 決定展開長度,含 RNN(1) 與 RNN(2) 的逐步推演(pp.244–245,式 8.4–8.12)。其三,穩定性的正式論證:把 RNN(1) 改寫為無限維非線性移動平均、以歸納法證明激活函數有界時滯後脈衝嚴格衰減(pp.245–246,式 8.13–8.17);平穩性一節則以伴隨矩陣論證線性激活不可行(p.246,式 8.18)。其四,記憶半衰期的正式定義與 tanh 數值例——該組參數下半衰期為 9 期(p.247,式 8.19、表 8.1)。其五,GRNN:把殘差的條件共變異數矩陣納入加權最小平方損失的異變異數 RNN,兩步估計程序及其與 GARCH 擴充 AR 的類比(pp.248–249,式 8.20–8.25)。其六,動態 α 平滑展開成滯後觀測加權和的完整式、常數平滑時化約為 AR(1) 算子式(pp.250–251,式 8.34–8.38),與 GRU、LSTM 的完整方程組(pp.252–255,式 8.39–8.55),含作者對 LSTM 輸入門「從時序分析角度難以論證」的評註(p.254)。其七,兩個 notebook 實證:比特幣分鐘中價 ADF 檢定無法拒絕單根、GRU 顯著優於普通 RNN;公債期貨 tick 資料平穩、兩者相當(pp.256–257,圖 8.3–8.4)——本課第四節的選型經驗法則即源於此對照。其八,二維卷積的完整機制:4×4 矩陣手算例、通道與層映射記法、零/週期/反射三種 padding、stride 與平均/最大池化(pp.261–263,式 8.71–8.81,例 8.2)。其九,擴張卷積的形式定義與感受野公式的推導脈絡(pp.264–265,式 8.82–8.84)。其十,線性自編碼器的變分細節:偏移項最適化後問題與均值無關、編碼權重為解碼權重的 Moore–Penrose 偽逆(p.267,式 8.85–8.86);等價定理的 SVD 證明綱要(p.268,式 8.87);25 年美債殖利率曲線實證——PCA 前三成分解釋 95.6%、4.07%、0.34%,自編碼器對應 95.63%、4.10%、0.27%(pp.268–270,圖 8.8–8.10)。其十一,深度自編碼器與深度因子:非線性時奇異值不再對應載荷、Heaton 等人的深度投資組合、ReLU 自編碼器的選擇權組合詮釋與可投資性難題(pp.270–271,圖 8.11)。其十二,章末三組選擇題與習題 8.1–8.7(半衰期計算、Jensen 不等式界、卷積手算、Coindesk 與高頻資料的程式作業)(pp.272–274)。原書第八章對應印刷頁 pp.239–276。
§04作業和解答
作業一:記憶半衰期實測
沿用第三節的單神經元 tanh RNN。定義「殘留比」:對系統打入一個單位脈衝後不再給輸入,隱狀態經 k 步遞迴後的殘留量,以 k=1 時為基準正規化。(1) 對 \(w_z = 0.7\) 與 \(w_z = 0.9\),分別找出殘留比首次跌破一半的步數(記憶半衰期);(2) 對 \(w_z = 1.2\) 重跑,解釋觀察到的現象,以及它與第三節「梯度照樣消失」的關係。
解答 SOLUTION
import numpy as np
def impulse_ratio(w_z, K=60):
"""單位脈衝經 k 次 tanh 遞迴的殘留量,對 k=1 正規化"""
h = np.tanh(w_z * 1.0) # k=1:脈衝剛打進來
base, vals = h, [1.0]
for _ in range(K - 1):
h = np.tanh(w_z * h) # 之後無輸入,純遞迴
vals.append(h / base)
return vals
for w_z in (0.7, 0.9, 1.2):
v = impulse_ratio(w_z)
half = next((k + 1 for k, r in enumerate(v) if r < 0.5), None)
print(f"w_z={w_z}: 半衰期={half} 前 6 步殘留比 {[round(r,3) for r in v[:6]]}")
# 輸出:
# w_z=0.7: 半衰期=3 前 6 步殘留比 [1.0, 0.661, 0.451, 0.312, 0.217, 0.152]
# w_z=0.9: 半衰期=5 前 6 步殘留比 [1.0, 0.793, 0.657, 0.559, 0.482, 0.42]
# w_z=1.2: 半衰期=None 前 6 步殘留比 [1.0, 0.914, 0.867, 0.84, 0.823, 0.812](1) \(w_z=0.7\) 的半衰期是 3 步,\(w_z=0.9\) 拉長到 5 步——循環權重越大,脈衝的餘韻越長,這是「\(w_z\) 控制記憶長度」的正面證據。(2) \(w_z=1.2\) 時 60 步內殘留比從未跌破一半,停在 0.79 附近不再下降:因為 \(w_z>1\) 時遞迴 \(h \mapsto \tanh(w_z h)\) 存在非零穩定固定點(本例 \(h^*\approx 0.659\)),脈衝的痕跡永遠留在狀態裡。但注意這與「梯度傳得遠」是兩回事:固定點附近 tanh 已進飽和區、斜率遠小於 1,第三節實測過大權重下梯度照樣幾何消失——狀態記得住,訓練訊號卻傳不回去。這正是普通 RNN 的兩難:記憶與可訓練性無法靠同一個標量權重兼得,也再次說明為什麼出路是門控那種「加法通道」的結構性解法。
作業二:擴張取樣的感受野紅利
模擬一個多尺度系統:\(y_t = 0.7\,x_{t-1} + 0.5\,x_{t-8} + \varepsilon_t\)——訊號同時藏在昨天(短尺度)與八期前(長尺度)。限制你只能用 4 個權重,比較兩種取法:(a) 連續滯後 {1,2,3,4};(b) 擴張取樣 {1,2,4,8}(間隔倍增)。用最小平方法各自擬合,比較樣本外意義下的殘差 MSE 與係數還原情形,並連回第六節的感受野公式。
解答 SOLUTION
import numpy as np
rng = np.random.default_rng(47)
n = 2000
x = rng.normal(0.0, 1.0, n)
y_sig = 0.7 * np.roll(x, 1) + 0.5 * np.roll(x, 8)
y = y_sig[16:] + rng.normal(0.0, 0.2, size=n - 16)
def fit_lags(lags):
X = np.column_stack([x[16 - j: n - j] for j in lags])
w, *_ = np.linalg.lstsq(X, y, rcond=None)
return w, np.mean((y - X @ w) ** 2)
w_a, mse_a = fit_lags([1, 2, 3, 4])
w_b, mse_b = fit_lags([1, 2, 4, 8])
print("連續 [1,2,3,4]: MSE =", round(mse_a, 4), " 權重:", np.round(w_a, 3))
print("擴張 [1,2,4,8]: MSE =", round(mse_b, 4), " 權重:", np.round(w_b, 3))
# 輸出:
# 連續 [1,2,3,4]: MSE = 0.2728 權重: [ 0.683 -0.015 0.007 -0.001]
# 擴張 [1,2,4,8]: MSE = 0.0415 權重: [ 0.69 0. -0.005 0.495]同樣 4 個權重,連續取樣的感受野只到 lag 4,天生罩不到 lag 8 的訊號——它只能抓到短尺度那份(權重 0.683 ≈ 真值 0.7),MSE 卡在 0.27——理論上漏掉的長尺度訊號貢獻 \(0.5^2=0.25\)、雜訊地板貢獻 0.04,合計約 0.29,與實測 0.27 相符(差額是抽樣波動)。擴張取樣用一樣的參數預算把感受野推到 lag 8,兩份訊號都抓到(0.690 與 0.495,對真值 0.7 與 0.5),MSE 掉到 0.0415——已貼近雜訊地板 0.04。這就是感受野公式 \(2^{L-1}(2k+1)\) 的紅利用最小例子重演一次:參數預算固定時,指數擴張的覆蓋範圍是免費的午餐,代價只是放棄中間滯後(3、5、6、7)的解析度——對「規律集中在特定尺度」的資料,這個代價幾乎為零。
作業三:瓶頸維度掃描與 PCA 的三個差異
延續第七節的殖利率曲線實驗(同一份 Y0)。(1) 對 m = 1 到 5 掃描 PCA 的重建 MSE 與累積解釋變異,指出「手肘」在哪、對應資料的什麼性質;(2) 訓練一個 m=2 的線性自編碼器,驗證它的解碼子空間與前 2 個主成分子空間重合;(3) 數值驗證第七節聲稱的差異:AE 瓶頸座標彼此相關、PCA 分數不相關。
解答 SOLUTION
import numpy as np
rng = np.random.default_rng(21)
n_obs, n_mat = 600, 8
F = np.column_stack([np.ones(n_mat),
np.linspace(-1, 1, n_mat),
np.linspace(-1, 1, n_mat)**2 * 2 - 1])
scores = rng.normal(size=(n_obs, 3)) * np.array([9.0, 3.5, 1.4])
Y0 = scores @ F.T + rng.normal(0.0, 0.45, size=(n_obs, n_mat))
Y0 -= Y0.mean(axis=0)
U, S, Vt = np.linalg.svd(Y0, full_matrices=False)
evr = S**2 / (S**2).sum()
for m in range(1, 6): # (1) 掃描瓶頸維度
P = Vt[:m].T
mse = np.mean((Y0 - (Y0 @ P) @ P.T)**2)
print(f"m={m}: MSE={mse:.4f} 累積解釋變異 {evr[:m].sum()*100:.2f}%")
m = 2 # (2) m=2 線性 AE
rng2 = np.random.default_rng(5)
V1 = rng2.normal(0.0, 0.1, size=(m, n_mat))
V2 = rng2.normal(0.0, 0.1, size=(n_mat, m))
for it in range(30000):
Z = Y0 @ V1.T
R = Z @ V2.T - Y0
V2 -= 2e-4 * (2 * R.T @ Z / n_obs)
V1 -= 2e-4 * (2 * (R @ V2).T @ Y0 / n_obs)
Qa, _ = np.linalg.qr(V2); Qb, _ = np.linalg.qr(Vt[:2].T)
ang = np.degrees(np.arccos(np.clip(
np.linalg.svd(Qa.T @ Qb, compute_uv=False), -1, 1)))
print("m=2 主夾角(度):", np.round(ang, 4))
corr_ae = np.corrcoef((Y0 @ V1.T).T)[0, 1] # (3) 座標相關性
corr_pc = np.corrcoef((Y0 @ Vt[:2].T).T)[0, 1]
print(f"瓶頸座標相關:AE={corr_ae:.3f} PCA={corr_pc:.3f}")
# 輸出:
# m=1: MSE=5.8594 累積解釋變異 92.90%
# m=2: MSE=1.1219 累積解釋變異 98.64%
# m=3: MSE=0.1267 累積解釋變異 99.85%
# m=4: MSE=0.0980 累積解釋變異 99.88%
# m=5: MSE=0.0707 累積解釋變異 99.91%
# m=2 主夾角(度): [0.0002 0.0086]
# 瓶頸座標相關:AE=0.283 PCA=0.000(1) MSE 從 m=1 的 5.86 陡降到 m=3 的 0.127,之後 m=4、m=5 只從 0.098 磨到 0.071——手肘清楚地出現在 m=3。這不是巧合:資料由三個因子生成,前三維裝的是結構,第四維起裝的是雜訊——瓶頸維度的手肘直接讀出資料的內在維度,這是把 AE/PCA 當「維度探測器」用的標準手法。(2) m=2 的自編碼器訓練完,與前 2 個主成分子空間的主夾角實測為 0.0002 度與 0.0086 度,重建 MSE 1.1220 對 PCA 的 1.1219——等價性在不同瓶頸維度下同樣成立;但注意這是重訓出來的,不是把 m=3 的解砍掉一軸(不巢狀性)。(3) AE 兩個瓶頸座標的相關係數實測 0.28——不算大,但明確非零;第七節 m=3 的實驗裡最大配對相關更高達 0.92——而 PCA 分數的相關是 0.000,機器精度的零。這印證第七節的警告:AE 給你的是「對的子空間、斜的座標」,下游若需要不相關因子(例如各因子分開建 AR 模型),要嘛用 PCA,要嘛對 AE 權重再做 SVD 正交化。
§05參考資料
- Hochreiter, S., & Schmidhuber, J. (1997). Long Short-Term Memory. Neural Computation 9(8) — LSTM 原始論文:細胞狀態與門控結構作為梯度消失的結構性解法。
- Cho, K., et al. (2014). Learning Phrase Representations using RNN Encoder-Decoder. arXiv:1406.1078 — GRU 的出處論文,更精簡的門控設計。
- Pascanu, R., Mikolov, T., & Bengio, Y. (2013). On the difficulty of training Recurrent Neural Networks. arXiv:1211.5063 — 梯度消失與爆炸的系統性分析,本章第三節連乘論證的嚴格版。
- Olah, C. (2015). Understanding LSTM Networks — 廣為流傳的 LSTM 圖解教學,與本章第四節的白話拆解互為補充。
- van den Oord, A., et al. (2016). WaveNet: A Generative Model for Raw Audio. arXiv:1609.03499 — 擴張因果卷積的代表作,感受野指數成長設計的源頭。
- Plaut, E. (2018). From Principal Subspaces to Principal Components with Linear Autoencoders. arXiv:1804.10253 — 線性自編碼器與 PCA 等價性的現代處理:如何從權重的 SVD 還原排序的主成分。
- Heaton, J. B., Polson, N. G., & Witte, J. H. (2016). Deep Portfolio Theory. arXiv:1605.07230 — 以深度自編碼器萃取投資組合因子的先驅工作,本章深度因子段落的學術參照。
- Dixon, M. F., Halperin, I., & Bilokon, P. (2020). Machine Learning in Finance: From Theory to Practice. Springer — 本課程對照之原書,第八章 pp.239–276。