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

QQQ 股東會小筆記

QQQ 股東將於 2025 年 10 月 24 日召開特別會議。這次的股東投票,主要是表決是否將 unit investment trust (UIT) 轉為 open-end management investment company.

目前所謂的 QQQ,全名是 Invesco QQQ TrustSM, Series 1. 顯然它是 Invesco 底下的一支信託。如果投票通過,它就變成一個開放式管理投資基金(Open-End Fund),擁有自己的董事會。

它一共要投這三個提案,全部同意才能讓這個轉型成立。

  • Proposal 1: To approve amendments to the Trust’s Trust Indenture and Agreement and Standard Terms and Conditions of Trust
    (the “Governing Instruments”) that are intended to change the Trust’s classification under the Investment Company
    Act of 1940, as amended, from a unit investment trust to an open-end management investment company.
  • Proposal 2: To approve the election of nine (9) trustees to serve on the Board of Trustees of the Trust following the amendment of
    the Governing Instruments (Proposal 1) replacing the existing trustee (a bank) with a slate of individual trustees.
  • Proposal 3: To approve an investment advisory agreement between the Trust and Invesco Capital Management LLC.

然後這對我們有啥好處呢?據說可以降低費用。轉變後,信託的費用比率將從每年最高20個基點(0.20%)降低至18個基點(0.18%),預計為信託及其股東節省超過 7000 萬美元。

以 2025/8/8 那天來說,QQQ 總共有 634,150,000 股。所以每股每年可以節省 0.11 美元。也就是每擁有 9.06 股, 每年就可以多賺一塊美金;擁有 1,000 股的股東就可以多買一支耳機或是一雙運動鞋。 好像也不多就是了。

至於股東有什麼風險呢?目前能想到的風險就是,區區一支複製大盤的基金,竟然需要 9 位董事。董事不用分錢嗎?底下不用請自己會技師、分析師、工程師嗎?此外還批准 QQQ 要找 Invesco 當投資顧問,這是免費的嗎?如果他們瓜分了省下的 7000 萬,股東可能甚至沒感覺,但 Invesco 的 CEO Brian Hartigan (未來 QQQ 的董事長?) 應該就爽翻了。 However,如果他們只花 6999 萬 9999 元做這件事,身為小股東也只好認了!不要 A 太多啊!!!

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 相對大.