超學習時代

最近的 Gemini 3 Pro 真的變比較聰明,所以我取消 Monica 的訂閱,改訂 Gemini。現在想要學習最新的技術,不但學校教不了;網路課程也教不了。就算是追著科技網紅,心裡沒有譜的話,也會像個無頭蒼蠅一樣、不會授粉只會傳播細菌,哈!

以 AI 技術來說,訓練模型、微調模型、RAG (檢索增強生成) 都是舊世代的技術。次世代的技術重點在於 Reasoning 和 Agency。雖然這個發展有跡可循、合情合理,但是沒有前面的跌跌撞撞,也絕不可能一步到位。短短一兩年之間,我們有了下面的這些進化。

[觀念改變]

一個 AI model 自己角色扮演 –> 建立認知架構:記憶、規畫、反思。

LangChain –> LangGraph, 線性思考 –> 非線性思考、圖論、立體化。

Funcation call –> Tool call。錯誤檢查和自我校正。

[模型調校]

Supervised Fine-Tuning、 RLHF (reinforcement learning from human feedback) –> DPO (Direct Preference Optimization)、IPO (Identity Preference Optimization)、KTO (Kahneman-Tversky Optimization)。

全能模型 –> SLERP (Spherical Linear Interpolation)、TIES-Merging。

自我對局 (Self-Play) 強化。

[In Model Learning]

Prompt –> DSPy (Declarative Self-improving Language Programs),透過 Compiler 自動尋找 Prompt 組合。

快問快答 –> Chain of Tought、Tree of Thought –> Test-Time Compute (有節制地想久一點,時間換取品質)

[模型評估]

BLEU、ROUGE 考試 –> LLM-as-a-Judge,自動評估

多元評價 – RAGAS

  • R:Retrieval(檢索)
  • A:Accuracy(準確性)
  • G:Generality(通用性)
  • A:Adherence(遵循性)
  • S:Stability(穩定性)

[多模態]

OCR + LLM –> 原生多模態 (Native Multimodel)、audio/video tokenization。

文字到文字 –> any-to-any interaction

上述有很多新的東西,也有一些半新半舊,我打算加強 Agentic、MCP 這方面的知識,然後快速進入 DSPy 的領域。

附帶一提,雖然退訂 Monica 可以省錢,但是我還不打算把 Coursera 停掉。因為上面還是有很多 Andrew Ng 開的短課程。愈長的課程愈容易過時、短課程甚至像  Andrej Karpathy 的 Youtube 都可能有一些最新的東西。至於 ArXiv 肯定好料滿滿,但是可能要叫 NoteLLM 幫我讀了。

RAG with FAISS

RAG (Retrieval-Augmented Generation) 大家應該都知道了。至於 FAISS 是 Facebook AI Research(FAIR)開發的技術,主要用途是對海量高維度的資料作出相似度的比較。由於我是第一次看到 RAG 和 FAISS (Facebook AI Similarity Search) 一起用,所以做個筆記。

基本上 RAG 的知識庫可能是一本書、一本使用手冊、一堆 Facebook 的用戶資料等等。它們先分為不同的小段落,然後 tokenize,每個段落再 encode 為一個向量。同理,對使用者提問也做同樣的事,但此處理解為只產生一個向量。

由於知識庫的向量筆數很多, FAISS 為他們製作 Index。提問的向量用 FAISS 的函數中找出最接近的幾筆最相關的向量,再根據 index 反查出 text 原文。然後把 text 和使用者提問合在一起去問 LLM。

對 LLM 來說,它可以有自己的 tokenizer。總之,RAG 已經功成身退了。

假如不用 FAISS,純靠 Pytorch 的話,那麼要自己用 dot product 去比較相似性。因為每個 word 就可能對應到一個高維的 token,所以每個 pragraph 的向量就是所有組成 pragraph 的 word 的向量的平均值。

最後補充 DPR (Dense Passage Retriever)。顯然,它對於 question 和 context (passage) 用了兩套函數 – 所謂 dual encoder [1]。我們可以想像,對於 question 的編碼應該要力求精準,但是對於參考文件這部分,如果是一本百科全書、或是整個資料庫,那編碼時主要是求快!所以兩邊的編碼方式不太一樣、甚至 tokenizer 不太一樣 (但是相容) 應該也是合理的。

[REF]

  1. https://blog.csdn.net/qq_45668004/article/details/138256448

Adjusted R-Squared 小整理

Adjusted R2 是 R2 的進階版, 它考慮了模型中變數 (預測因子) 的數量, 懲罰那些濫竽充數的變數, 在多變量回歸 (multiple regression ) 中達到去蕪存菁的效果.

聽起來相當美好.  但我們怎麼知道何時需要它? 要是我們的 model 已經是最好了, 再調整它是否浪費時間? 我們可以先看 R2 的破綻在哪裡?

R2 = 1 – SSR/SST

若 SSR/SST < 1, 表示這是個真分數. 當我新增一個因子, 表現得和平均 (猜的期望值) 一樣好, 真分數 (分子分母加同樣數字) 愈加愈大, R 會愈來愈小. 所以加到沒用的因子, R 會變差 (Adjusted R 變差). 反過來, 如果我們是要減少變數, 也可以預測減掉一個變數是否有感?

Adjusted R² = 1 – [(1 – R²) * (n – 1) / (n – k – 1)]

  • 如果加入的變數有貢獻 (提高解釋力), Adjusted R² 會上升.
  • 如果加入的變數無貢獻 (p-value 高, 不顯著), Adjusted R² 會下降或持平.

基本上, k 如果增加一個變數, 但它對 R2 又無額外貢獻, 分母的 (n – k – 1) 變小, … 以小學程度的數學可知 Adjusted R² 變小.

順便偷渡一下 n. n -> ∞, (n – 1) / (n – k – 1) -> 1, Adjusted R² 越接近 R² (增加變數的懲罰很小). n 越小, 分母越小, 懲罰項越大, 讓 Adjusted R² 更容易小於 R², 這能更敏感地偵測冗餘變數 (overfitting). 但因為它效力強到難以分辨 feature 好壞, 所以一般不調整 n, 只調整 k.

舉例而言, 現在要估計房價模型. 變數有坪數、屋齡、噪音 (亂數) 三個, k = 3. 統計樣本 n = 100 個. 為了說明方便, 我們先在 Python 固定亂數種子. 做出一堆可以收斂的假資料.

import statsmodels.api as sm
import pandas as pd
import numpy as np

# 步驟 1: 固定亂數種子與資料(同之前)
np.random.seed(0)
n = 100
X = pd.DataFrame({
    '坪數': np.random.uniform(20, 200, n),
    '屋齡': np.random.uniform(1, 50, n),
    '噪音': np.random.normal(0, 1, n)
})
y = 5000 * X['坪數'] - 2000 * X['屋齡'] + np.random.normal(0, 10000, n)  # real y

安裝 library ( pip install statsmodels) 後, 很多東西都不用算. 包括 OLS (Ordinary Least Squares) model 都會建好. 因為太方便了, 所以定義一個會印出過程的 function.

# 函數:計算 R²/adj_r2、係數和 p-value
def compute_and_compare(model_vars, model_name):
    X_sub = sm.add_constant(X[model_vars])  # 加常數項
    model = sm.OLS(y, X_sub).fit()          # 擬合 OLS 模型
    
    # 計算 R² 和 adj_r2
    r2 = model.rsquared
    adj_r2 = model.rsquared_adj
    print(f"\n{model_name} (變數: {model_vars}):")
    print(f"  R² = {r2:.4f}")
    print(f"  Adjusted R² = {adj_r2:.4f}")
    
    # 得到估計係數
    params = model.params
    print("  估計係數(params):")
    for var in params.index:
        print(f"    {var}: {params[var]:.2f}")
    
    # 得到每個變數的 p-value
    pvalues = model.pvalues
    print("  每個變數的 p-value(顯著性檢定):")
    for var in pvalues.index:
        print(f"    {var}: {pvalues[var]:.4f} (顯著: {pvalues[var] < 0.05})")
    
    print()  # 空行分隔

讓 3 個變數的 model A 跑一下, 然後讓除掉噪音 (random noise, 不是房子旁邊有噪音), 用只有 2 個變數的 model B 跑一下.

# 步驟 2: 計算模型 A (有噪音)
compute_and_compare(['坪數', '屋齡', '噪音'], "模型 A (有噪音)")

# 步驟 3: 計算模型 B (無噪音)
compute_and_compare(['坪數', '屋齡'], "模型 B (無噪音)")

執行上面程式, 會印出:

模型 A (有噪音) (變數: ['坪數', '屋齡', '噪音']):
  R² = 0.9745
  Adjusted R² = 0.9736
  估計係數(params):
    const: -949.47
    坪數: 4995.64
    屋齡: -2003.71
    噪音: 42.60
  每個變數的 p-value(顯著性檢定):
    const: 0.9644 (顯著: False)
    坪數: 0.0000 (顯著: True)
    屋齡: 0.0000 (顯著: True)
    噪音: 0.9655 (顯著: False)


模型 B (無噪音) (變數: ['坪數', '屋齡']):
  R² = 0.9744
  Adjusted R² = 0.9737
  估計係數(params):
    const: -947.35
    坪數: 4995.64
    屋齡: -2003.72
  每個變數的 p-value(顯著性檢定):
    const: 0.9623 (顯著: False)
    坪數: 0.0000 (顯著: True)
    屋齡: 0.0000 (顯著: True)

我們先關心 Model A 的 R² = 0.9745 > Adjusted R² = 0.9736, 或曰 Adjusted R² < , 這個訊號是提示我們的 model 可能有冗餘的變數存在! 調一調會更好! 至於誰是老鼠屎? 這時還看不出來.

但我們也印出了 p-value, 噪音的 p-value = 0.9655 很不顯著, 所以就抓它了! 在 Model B, 我們只用兩個變數.

此時 R² = 0.9744 > Adjusted R² = 0.9737 仍然成立, 或曰 Adjusted R² < 就是成立, 那這個時候戰犯要抓誰呢?

const 這個 p-value 值雖然大了點, 但是通常不會是標的. 而且坪數和屋齡的 p-value 都為 0 了, 表示極為顯著! 基本上就沒得挑了. 這也告訴我們指標並非絕對的, 要綜合起來使用.

Model B 的 R² 雖然輸給 Model A (0.9744 < 0.9745), 但我們本來就是抓 overfitting (模型在訓練資料上表現好, 但在未見過的資料上表現差), 所以不會太在意這裡掉了一點. 反而是看 Adjusted R² 從 0.9736 升到 0.9737, 還拔掉一個 p-value 很大的變數, 故改用 model B 是值得肯定的方向.

R-Squared 小註解

原本寫完兩篇 R, 就要來寫 R2, 我覺得這個哏不錯! 不過呢, 因為寫著寫著歪樓了, 所以把那邊的主題讓給 P-value, R2 放到後面來.

前面說到 P-value 用來衡量變數的顯著性. 由於我舉的是單一變數的 t-test, 好像 p-value 就足以解釋整個 model. 但真正能解釋整個 model 的是 R2 , 說明如下. 假設:

全部的誤差 SST = SSR + SSE .

SSR = residual variation = model 無法解釋的 (實際觀測值 – 模型預測值)

SSE = regression model variation = model 解釋掉的部分 (模型預測值 – y 的平均值)

R2 代表模型解釋變異的能力, 所以是 model 可以解釋的變異的比重 = SSE / SST = 1 – SSR / SST. 所以怎麼看 R 都是介於 0~1 之間. R 越接近 1, 表示 model 解釋得更好.

換個生活化的例子來說明.

  • 假設抽樣調查了三對父子, 記下父親與兒子的身高 (單位:公分):
父親身高 (x)兒子身高 (y)
170172
175177
180179
  • 新手根據網路謠言, 建構了下面這個回歸模型:
  • 我們來檢驗看看. 首先將每個父親的身高 x 帶入計算, 預測兒子身高 y^.
父親身高 (x)兒子實際身高 (y)預測兒子身高 y^
170172177.4
175177178
180179178.6
  • 計算 R2
  1. 計算 yˉ​= (172+177+179) /3 ​=528/3​ ≈ 176 — 兒子的平均身高
  2. 計算 SST (Sum of squared Total) = (172−176)2 + (177−176)2 + (179−176)2 = (−4)2+(1)2+(3)2=16+1+9=26 — 兒子的身高和平均身高的差的平方和
  3. 計算 SSR (Sum of Squared Residual) = (172-177.4)2 + (177-178)2 + (179-178.6)2 = 29.16+1+0.16=30.32 — 兒子的身高和預測身高的差的平方和

R2 = 1 – SST / SSR = 1 – 30.32 /26 = 1 – 1.166 ≈ -0.166

因為 R2 < 0, 或 SST > SSR, 等於是說: 全部猜平均身高的話, 還比用 model 估來得準. 所以此 model 很不好!

如果我們一項一項分開算. SSE = (177.4−176)2 + (178−176)2 + (178.6−176)2 = 1+4+6.76 = 12.72

SST ≠ SSR + SSE 或 26 ≠30.32 + 12.72

為何會有這樣的結果呢? 主要是這個 model 不正確, 所以什麼都解釋不了. 發生這種問題可能的原因包括:

  • 模型套錯:使用的模型和資料的型態根本不吻合(例如線性模型去解釋非線性資料)
  • 資料點太少:樣本數極少,容易受到極端值影響
  • 預測變數選錯:所用的 x、公式、或回歸參數明顯偏離資料特性
  • 資料雜訊過高:資料本身的變異無法用簡單模型捕捉

讓我們只用這三筆資料來生成合理的 linear regression model, 並重新計算 R2. 這個部分就請 AI 來執行了.

1. 原始資料
父親身高 (xx)兒子身高 (yy)
170172
175177
180179
2. 計算步驟
步驟一:計算平均值
步驟二:計算斜率 (b)


b=0.7

步驟三:計算截距 (a)

a=53.5

3. 生成線性回歸方程式

​解釋為: 父親每增加 1 公分身高, 兒子的預測身高增加 0.7 公分.

4 . 重新計算 R2

這個新模型不就超準了嗎? 但我們這裡只是建立 model, 並且評估其效果. 還不可以把它視為真正能實用. 下圖可以看到網路謠言版和依照數學原理建立的 model 有何差異? 網路謠言 model 不能解釋我們的資料庫, 但實測會不會更好? 這件事無法反映得出來.

T-Test 和 P-Value

雖然今天有望創下投資市值新高 (舊紀錄是 2025/2/20) – 台股平平、日股創高、美股小漲還沒收市, 結果還不好說 . 但這篇的重點是整理上班會用到的一門統計技術.

假設我有大把的 features, 要怎麼知道每個因子對 model 有沒有影響? 以及因子之間是否過於相關 (例如華氏度和攝氏度) 而顯得多餘呢? 讓我們從基本問題開始理解.

首先我們會遇到每個因子的 scale 差異很大. 例如預測房價時, 我們可能有屋齡、坪數、坪單價、附近實價登錄總價等因子. 前兩個算它們從 0~1,000 應該就夠用. 甚至普通人應該不會買千坪豪宅、千年老宅 (古剎?). 至於後兩個就有趣了. 假設我們用萬元為單位, 那麼 後兩者的 order 可能差個 10 倍到上百倍 (10~100 坪), 更不用說和前兩個因子擺在一起比較, 總會有些因子顯得參差不齊~~~

甚至那些 one hot encoding 來的, 根本就是單位向量, 這時要怎麼辦呢? 我們可以對 skewed 的變數取對數 log, 或用標準化 (z-score) 將所有因子拉到類似範圍。這類轉換能處理加法和乘法效應的混合 [1],讓模型更公平. 這部分先帶過, 減少數學、訴求直覺就好.

接下來要篩選出有用的因子, 把雜質打掉, 才能真正抓到關聯性. 我們要先檢查因子之間的相關性 (避免如華氏/攝氏度的多餘), 並確保數據的有效性. 如果數據本身沒意義, 其他動作都是多餘的.

以下就介紹 t-test 和 p-value:它們是用來測試每個因子是否真的對目標變數 (如房價) 有顯著影響. 簡單說, t-test 測量因子的 “效果強度", p-value 則告訴你這種效果在隨機情況下發生的機率有多高— p 小表示影響顯著!

顯然, 我們要做一些測試才會產生測量因子. 假設我們用了單樣本測試 (One-sample t-test), t 公式表示如下 (t 公式還有很多型態).

例如測試新藥是否使血壓平均降至 120 mmHg, 多筆測量出的血壓為 x, 假設值 μ0 為吃了藥但無效果的血壓 (虛無假設), 此時可以看出 x 的平均值 xˉ 並不是 μ0. 假設此藥對某人藥效不佳, 吃了高血壓藥, 收縮壓從 140 變成 150, 差異是 +10, 或是對某人藥效太強, 收縮壓從 135 變成 90, 差異就是 -45.

  • 假設資料(血壓變化值,單位 mmHg):
    • 受試者 1: -5(血壓下降 5)
    • 受試者 2: -3
    • 受試者 3: -7
    • 受試者 4: -4
    • 受試者 5: -6
    • 樣本平均 xˉ=−5xˉ=−5(整體平均下降 5 mmHg)
    • 樣本標準差 s ≈ 1.58
    • 樣本大小 n = 5
    • 虛無假設 μ₀ = 0(藥物無效果)

此時 t = (-5-0)/(1.58 / √5)=-5/​0.707≈−7.07,

t 可以表示出藥效過高或試過低. 由於 t 就是一個正規化的過程. 所以 x 不需要預先做正規化處理.

另外還有個 p-value, 它的計算不是公式, 而是機率的累積函數 CDF (cumulative distribution function). 再以血壓藥為例, p 是紅線以下的面積 + 右邊對稱位置的面積 (所以 x2).

p=2×(1−CDFt​(∣−7.07∣,df=4)) = 0.0021

也就是在自由度 [2] df = n – 1 = 5 – 1 = 4 的情況下, 落在分布長尾部分的機率 (tail probability).

用實際的數字翻譯上述公式就是: t ≈ -7.07, df=4, p ≈ 0.0021 解釋為: 如果藥物無效(H0:μ=0),則發生五個受試者 t = -7.07 或更極端 (更負或更正) 的機率只有 0.2%. 隱含這種事不太可能發生, 藥物相當可能是有效的.

換個角度, 如果大家試完藥, t -> 0, 測起來像是這個藥吃了沒效. 但 p -> 1 只是表示這個測試的證據力很低, 表示無法證明這個藥有效, 而不是證明這個藥非常無效.

[REF]

  1. http://ch.whu.edu.cn/cn/article/doi/10.13203/j.whugis20210659?viewType=SUP
  2. 自由度 – 在計算樣本標準差(s)時,我們先用樣本平均值 (xˉ) 來「約束」資料: 一旦知道平均值, 前 n-1 個值可以自由變化, 但最後一個值必須調整以符合平均值. 這 “犧牲" 了 1 個自由度. 當自由度愈低, 表示愈不確定, 長尾就比較肥. 對同一個 t, p-value 相對大.