橢圓的真身:拿 C 當一把尺
玩完這關,你手上會多一把能同時看兩欄、而且自動考慮「這兩欄本來就會一起動」的尺 —— 而且你會知道 05 關那顆虛線橢圓,到底是從 C 的哪裡長出來的。
① 這關在解什麼問題
上一關給了你什麼
機率統計 04 ⑧ 已經把那圈橢圓畫出來了 ——
它是二維常態的等高線,也就是 03 關那張 C 的形狀被畫成地形圖。
但到機率統計 04 關結束,橢圓還只是一張圖:它告訴你哪裡人多、哪裡人少。
這一關把那張圖翻過來當工具用:同一顆橢圓,改讀成「同一圈上的每一點,離中心一樣遠」——
橢圓就從一張圖變成一把尺。
關鍵動作只有一個:把 C 倒過來寫成 C−1。
05 ④ 用 C 去找「哪個方向最散」,
這一關用 C−1 去除掉那個散開 —— 同一組主軸,反過來用。
05 關的圖上一直有一顆虛線橢圓,我只丟了一句「這是資料的 1σ 輪廓」就過去了 (見 05 關 step 4)。這句話有兩個沒交代的地方:
- 那顆橢圓是怎麼從 C 長出來的?C 是四個數字,橢圓是一條曲線,中間那一步我跳掉了。
- 畫它到底能幹嘛?當時它只是裝飾。
這一關把這兩個洞補完,並且順便交出一個真的能拿去上線的工具。
先講一個很具體的麻煩。你在做刷卡風控,手上進來一筆交易:
你手上有這個持卡人過去兩週的紀錄,於是你做了每個工程師都會先做的事 —— 各欄各算一次 z-score (z = (值 − 平均) ÷ 標準差,02 關那套):
- 只看金額:這個人上週在家電行刷過 5193、在電商刷過 4983。5400 的 z-score 只有 +1.45。不稀奇。
- 只看時刻:這個人是夜貓子,凌晨 02:40、03:20 都有便利商店紀錄。03:10 的 z-score 只有 −1.27。也不稀奇。
兩欄各自都放行,可是你一看就覺得毛毛的 —— 因為這個人的消費有個很清楚的規律:越晚金額越小 (深夜是買宵夜的時段)。「深夜」和「大額」單獨都正常,合起來違反規律。
這就是問題的核心:
單欄的標準差永遠抓不到「兩欄之間的關係被打破」這種奇怪。 因為 z-score 是一欄一欄算的,它從頭到尾不知道另一欄的存在。
你可能會想:那我把兩個 z-score 湊成一個向量,取長度不就好了? 這關的 ⑦ 會用真的數字打你的臉 —— 那筆真異常算出 1.9279,而一筆完全正常的家電行消費算出 1.9277。 差 0.00013,這把尺分不出來。
這關給你的工具叫 馬氏距離(Mahalanobis distance):一把會自動考慮「這兩欄本來就會一起動」的尺。 它的等高線不是圓,是橢圓 —— 而那顆橢圓,就是 05 關那顆虛線橢圓的真身。
② 橢圓是怎麼從 C 長出來的
這是本關最重要的一節,我會走得很慢。先把兩個字講定。
先講「等高線」
你看過地形圖。地形圖上那些一圈一圈的線,叫等高線 —— 同一條線上的每一點,海拔一樣高。 線密就是陡,線疏就是緩。你不用看整座山的立體模型,看那幾條圈就知道山長什麼樣。
我們要做一模一樣的事,只是把「海拔」換成「離資料中心多遠」。 所以下面每一張圖上的圈,意思都是:同一條圈上的每一點,在這把尺底下距離一樣遠。 圈的形狀,就是那把尺的形狀。
再把字母講定
| 符號 | 讀作 | 它是什麼(本頁一律這個意思) |
|---|---|---|
| x | x | 你要檢查的那一個點。二維就是兩個數字,例如 (時刻, 金額) |
| μ | mu | 全部資料的平均點(重心)。圖上就是橢圓的圓心 |
| x − μ | x 減 mu | 這一點離重心的位移向量。本頁所有計算都從這個差開始,不是從 x 本身 |
| C | C | 03 關的共變異數矩陣:對角線是兩欄各自的變異數,非對角線是共變異數 |
| C⁻¹ | C inverse | C 的反矩陣。矩陣世界裡「除以 C」就是「乘上 C⁻¹」 |
| d² | d squared | 馬氏距離的平方。它是本頁真正在算的那個量,讀作「幾個 σ 的平方」(它本身沒有單位 —— 為什麼見 ⑩) |
| d | d | 馬氏距離本身 = √d²。d = 2 讀作「這個點站在 2σ 那一圈上」 |
| k | k | 圈的號碼。k = 1, 2, 3 就是 1σ / 2σ / 3σ 三圈,那三條圈的方程式是 d² = k² |
| λ₁ λ₂ | lambda 1、lambda 2 | C 的兩個特徵值。λ 的意思是「沿著那根軸看過去的變異數」,λ₁ 是大的那個 |
| v₁ v₂ | v 1、v 2 | C 的兩個特徵向量 = 05 關的主軸與次軸,也就是橢圓的長軸/短軸方向 |
| √λ₁ | 根號 lambda 1 | 1σ 橢圓的長半軸長度。λ 是變異數,開根號才變回「距離」的單位 |
| σ | sigma | 標準差 = √變異數(02 關) |
| ρ | rho | 相關係數 = cov ÷ (σx·σy),範圍 −1 到 1 |
| z | z score | z = (值 − 平均) ÷ σ,意思是「離平均幾個標準差」 |
| ᵀ | transpose | 轉置:把直的向量躺平成橫的。(x−μ)T 就是「把那個位移向量橫過來」,橫的乘矩陣乘直的,結果才會是一個數字 |
| I | identity | 單位矩陣 [[1,0],[0,1]]。它是矩陣界的「1」,乘上去什麼都不變 |
| det C | determinant | 行列式。這裡的幾何意思是「橢圓面積的平方指標」,det C → 0 就是橢圓被壓成一根線 |
| tr C | trace | 跡=對角線相加=兩欄變異數的總和。tr C ÷ p 就是「平均變異數」 |
| n | n | 樣本數(有幾筆資料) |
| p | p | 欄位數(有幾個維度)。本頁絕大多數時候 p = 2 |
| α | alpha | 你願意誤報的比例。α = 0.05 就是「最極端的 5% 我當異常」 |
| δ | delta | 收縮強度(只在 踩坑/⑩ 出現):C 不可逆時往單位矩陣拉回來多少 |
| A | A | 著色矩陣(只在 ⑤ 出現):滿足 A·Aᵀ = C,用來把圓形的隨機點雲拉成符合 C 的橢圓形點雲 |
| P、Q | P、Q | ④ 對照實驗裡兩顆可以拖的測試點,P 是藍的、Q 是橘的 |
| ln | natural log | 自然對數(以 e 為底),exp 的反操作。只在把涵蓋率反解成門檻時用到。基礎 01 關 ③ 節從頭講它 |
| χ² | chi-squared /卡方 | d² 服從的那個分布(⑥):df 個獨立標準常態平方相加的分布。⑦ 的門檻選單就是查它的分位數 |
| df | degrees of freedom /自由度 | 卡方分布的唯一參數:加了幾個平方進去。本頁的 k 已經被「圈號」用掉了,所以自由度一律寫 df,不寫 k。d² 的 df = 欄位數 p,本頁通常是 2 |
| SE | standard error | 標準誤(只在 ⑤ 的表格出現):抽樣本來就會抖多少。實測值落在理論值 ±2 SE 內就算「符合」 |
兩欄不相關、變異數都是 1 → 圈是正圓
這是最沒有懸念的情況。兩欄互不相干、散開程度一樣, 所以「往哪個方向偏 1 個單位」都一樣稀奇。等高線只能是正圓, 而這把尺就是你國中學的畢氏定理距離:d² = x₁² + x₂²。
紅點在 (−1.1, 1.1):兩邊各偏 1.1 個 σ,d = √(1.1² + 1.1²) = 1.56,站在 1σ 與 2σ 之間。
變異數不同、仍不相關 → 圓被拉成軸對齊的橢圓
第一欄的 σ 是 √4 = 2、第二欄只有 √0.64 = 0.8。橫著偏 1 很平常,直著偏 1 很不平常 —— 所以尺必須每個軸各除以自己的 σ:d² = (x₁/σ₁)² + (x₂/σ₂)²。 括號裡那兩個東西,就是你天天在用的 z-score。
同一顆紅點:橫著偏 1.1 只算 1.1/2 = 0.55 個 σ,直著偏 1.1 卻算 1.1/0.8 = 1.375 個 σ。 d = √(0.55² + 1.375²) = 1.48 —— 兩軸權重完全不同了。
兩欄相關 → 橢圓歪了,只除 σ 就不夠用
兩欄的 σ 都是 2(比 step 1 散開兩倍)、但 ρ = 0.9(一起變大)。點雲斜著長, 所以「兩個一起大」很正常、「一個大一個小」很奇怪。 光除 σ 抓不到這件事 —— 除 σ 只會縮放,不會轉向。
同一顆紅點,d 跳到 2.46。資料比 step 1 散開兩倍,d 卻從 1.56 漲到 2.46 —— 因為紅點是「一負一正」,而這份資料裡幾乎不存在這種組合。它落在 2σ 圈外了。
把第三步講透:為什麼是 C⁻¹
拿 step 3 那顆紅點來手算一遍。它在 x − μ = (−1.1, 1.1), C = [[4, 3.6], [3.6, 4]]。
先看錯的做法。兩欄的 σ 都是 √4 = 2,所以 z-score 是 (−0.55, +0.55),取長度:
再看對的做法。先把 C 反過來(2×2 反矩陣:對調對角線、非對角線變號、整個除以 det C = 4·4 − 3.6·3.6 = 3.04):
然後把 (x−μ)ᵀ C⁻¹ (x−μ) 這個式子整個攤開,一項一項算 —— 這就是 05 關那個二次型,只是矩陣換成 C⁻¹:
= 1.5921 + 2.8657 + 1.5921 = 6.05
d = √6.05 = 2.46 中間那一項就是「相關性的罰款」:因為這兩欄本來該同號,你給我一正一負,我加你 2.87 分。 只除 σ 的做法完全沒有這一項,所以它算不出 2.46,只能算出 0.78。
一維的時候你會做 z = (x − μ) / σ —— 除以「散開程度」。 二維你想做同一件事,但「散開程度」不再是一個數字,是一張 2×2 的表(C)。 而矩陣世界裡沒有除法,除以一張表 = 乘上那張表的反矩陣。
所以 C⁻¹ 就是「除以 σ」的多維版,一個字都沒多。 對角線負責「各自除以自己的變異數」,非對角線負責「把兩欄糾纏在一起的那部分扣掉」。
所以結論式長這樣
而這條曲線的幾何已經全部寫在 C 的特徵分解裡了 —— 這是 04、05 兩關的成果直接套用:
| 橢圓的哪個部分 | 從 C 的哪裡讀出來 | 為什麼 |
|---|---|---|
| 圓心 | μ | 尺是從重心開始量的 |
| 長軸方向 | v₁(C 的第一特徵向量) | 資料最散開的方向,就是 05 關掃出來的主軸 |
| 短軸方向 | v₂(第二特徵向量) | 對稱矩陣的特徵向量必定互相垂直,所以短軸一定垂直長軸 |
| 長半軸長度(k = 1 時) | √λ₁ | λ₁ 是那個方向上的變異數,開根號才是距離的單位(=那個方向的 σ) |
| 短半軸長度(k = 1 時) | √λ₂ | 同上 |
| k = 2 的圈 | 兩個半軸都乘 2 | d² = k² 的 k 只是整體縮放係數 |
C 這四個數字裡面,其實藏著一顆橢圓。 把 C 做特徵分解,兩個特徵向量給你橢圓的方向,兩個特徵值開根號給你橢圓的兩個半徑。 05 關那顆虛線橢圓沒有任何額外資訊 —— 它就是 C 換一種畫法而已。
而反過來看:那顆橢圓就是一把尺的形狀。圈畫到哪裡,那裡就算「一樣遠」。
③ 主實驗:三個滑桿,把橢圓捏出來
上一節是我算給你看,這一節換你捏。三個滑桿直接控 C 的三個數字(C 是對稱的,所以只有三個自由度), 橢圓即時跟著變。
把 cov 往兩邊拉到底,滑桿會被擋住,右邊出現一行紅字。這不是 bug —— 共變異數有物理上限:|cov| ≤ σx·σy(就是 03 關的 |ρ| ≤ 1,換句話說而已)。 碰到上限時 det C → 0,橢圓被壓成一根線段,C⁻¹ 直接不存在。 我把滑桿擋在 0.96 倍,讓你看到「快壓扁」但不會炸掉。真實資料碰到這種 C 怎麼辦,見 踩坑。
④ 把這把尺命名:馬氏距離
剛剛那個式子有名字,叫馬氏距離(Mahalanobis distance,1936 年,印度統計學家 P. C. Mahalanobis)。 完整長這樣:
逐個零件拆:
| 零件 | 形狀 | 它在做什麼 |
|---|---|---|
| x − μ | 2×1 向量 | 先把原點搬到重心。不做這步你量到的是「離座標原點多遠」,那是位置不是奇怪程度 |
| C⁻¹ | 2×2 矩陣 | 除以散開程度。對角線各自除變異數,非對角線扣掉兩欄糾纏的部分 |
| (…)T … (…) | 向量ᵀ·矩陣·向量 | 這個夾法叫二次型,結果是一個純量(04、05 關都用過)。所以 d² 是一個數字,不是向量 |
| d² | 純量 | 「幾個 σ 的平方」。d² = 4 就是 d = 2,讀作「2σ 那一圈」 |
寫成程式只有三行 —— 兩行取反矩陣、一行夾二次型:
// x、mu 都是 {x, y};C 是 [[vx, cov], [cov, vy]]
function maha2(x, mu, C) {
const Cinv = LAB.M.inv(C); // C⁻¹:反矩陣。C 奇異時回傳 null
const diff = LAB.M.sub(x, mu); // x − μ:先搬到重心
return LAB.M.quadForm(Cinv, diff); // (x−μ)ᵀ C⁻¹ (x−μ),回傳 d²(純量)
}
const d = Math.sqrt(maha2(x, mu, C)); // 要「幾個 σ」就開根號
比較一下歐幾里得距離的程式,你會看到唯一的差別就是中間那個 C⁻¹:
// 歐氏:d² = (x−μ)ᵀ I (x−μ) ← 中間夾的是單位矩陣 I
function euclid2(x, mu) {
const diff = LAB.M.sub(x, mu);
return LAB.M.dot(diff, diff); // 等價於 quadForm([[1,0],[0,1]], diff)
}
歐氏距離 = 中間夾單位矩陣 I 的馬氏距離。 也就是說:用歐氏距離,等於你偷偷假設了「兩欄變異數都是 1、而且完全不相關」。 這個假設在真實資料上幾乎從來不成立。
對照實驗:兩把尺會吵架
下面是本關的高潮。兩顆可拖的點 P 與 Q, 我同時用兩把尺量它們,然後告訴你「哪一顆比較遠」。兩把尺會給出相反的答案。
當 ρ 很正的時候,這份資料的世界觀是「兩個一起大、一起小」。於是:
- P = (2.6, 2.6) 雖然離中心很遠,但它順著趨勢走 —— 對這份資料來說是「意料之中的大」。馬氏距離只給它 1.33。
- Q = (−1.1, 1.1) 離中心很近,但它逆著趨勢走(一個負一個正)—— 這是資料裡從來沒發生過的組合。馬氏距離給它 2.46。
歐氏距離只會回答「幾公分」,它不知道趨勢的存在,所以只能說 P 比較遠(3.68 對 1.56)。 馬氏距離回答的是「對這份資料而言有多罕見」,答案剛好反過來。
這兩個問題本來就不一樣,答案不一樣才對。做異常偵測時,你要的是後者。
順手收一個好處:馬氏距離不怕你換單位
歐氏距離有個很煩的毛病:它的答案跟你用什麼單位有關。 把金額從「元」改記成「千元」,數字一個都沒變、資料一模一樣,歐氏距離卻整個換了一組值。
馬氏距離沒有這個問題,因為你縮放某一欄的時候,C 會跟著縮放同樣的倍數, C⁻¹ 剛好把它抵掉。⑦ 那 15 筆刷卡資料我兩種算法都跑過:
兩欄都先轉成 z-score 再算:d²(停車費) = 7.626 ← 一個字都沒差
⑤ 為什麼是橢圓,而不是別的形狀
前面我都是「先給你橢圓,再解釋它」。這一節回答更根本的問題: 憑什麼等高線是橢圓?為什麼不是方形、不是菱形?
答案要從多元常態分佈(multivariate normal)借一句話。它的機率密度公式長這樣:
看清楚了嗎 —— x 只透過 d² 這一個數字影響密度,沒有別的入口。所以:
d² 一樣 → 密度一樣。「密度相同的點集合」就是「d² 相同的點集合」, 而 d² = 常數 是一個二次方程式,二次方程式在平面上畫出來的封閉曲線只有一種 —— 橢圓。
所以橢圓不是誰選的形狀,是 exp(−d²/2) 這個式子逼出來的。 機率密度的等高線 = 馬氏距離的等高線,同一條線,兩種讀法。
那每一圈裝了多少資料?(這裡很多人會錯)
先把「機率是什麼」升一維。機率統計 02 關講的是一維: 密度曲線底下的面積才是機率,縱軸那個高度本身不是機率。 二維的密度是一座山(機率統計 04 關那張等高線圖底下的那座山), 所以同一句話要跟著升一維:機率 = 那座山底下那一塊的體積。
於是「1σ 橢圓裝了多少機率」問的不是那條橢圓線有多長, 而是以那顆橢圓為底、垂直切下來的那一塊山有多少體積。 三維就是切一顆橢球、算超體積;再高的維度你畫不出來,但問法一字不變。
而體積會隨著維度一路漏掉。理由用一句話講得完:要落在 1σ 那一圈裡面, 每一個方向都得同時乖。一維只有一個條件要成立,二維要兩個一起成立,三維要三個 —— 每多一個維度就多乘一次折扣。一維那組 68 / 95 / 99.7 是背不過來的,實際數字是:
| 圈 | 條件 | 一維(你背的) | 二維(橢圓) | 三維(橢球) |
|---|---|---|---|---|
| 1σ | d² ≤ 1 | 68.27% | 39.35% | 19.87% |
| 2σ | d² ≤ 4 | 95.45% | 86.47% | 73.85% |
| 3σ | d² ≤ 9 | 99.73% | 98.89% | 97.07% |
這三欄不是三個獨立的巧合,是同一個分布在三個自由度下的值 —— 那個分布叫卡方,⑥ 會把它拆開。
2 維的 1σ 橢圓只裝得下 39% 的資料,不是 68%。 如果你畫了一顆 1σ 橢圓,然後跟同事說「圈外的都是異常」,你會把六成的正常資料標成異常。
為什麼會掉這麼多?因為要落在 1σ 橢圓內,兩個方向都得同時乖。 一維只要一個條件成立,二維要兩個條件一起成立,機率自然變小。 維度再高掉得更兇(3 維的 1σ 只剩 19.9%)。
二維有個很乾淨的閉式解,可以直接記: P(d² ≤ k²) = 1 − exp(−k² / 2)。 代 k = 1 → 1 − e−0.5 = 0.3935。代 k = 2 → 1 − e−2 = 0.8647。 但這條閉式解只有二維成立 —— 三維、十維要怎麼算,⑥ 給通用版。
不用相信我,現場數給你看
下面這顆按鈕會真的生成 2000 個符合指定 C 的隨機點,然後一顆一顆算 d²、數落在各圈內的比例。 生成方法是 Box–Muller + 著色(coloring),兩步都是自己寫的,程式碼在下面。
| 圈 | 理論 | 實測 | 筆數 | 差 / 容許 ±2SE |
|---|
生成程式(兩步,都自己寫)
第一步:Box–Muller。把兩個 0~1 的均勻隨機數,變成兩個獨立的標準常態隨機數 (平均 0、變異數 1)。這是把「均勻」掰成「鐘形」的經典手法:
// 均勻 → 標準常態。取極座標:半徑用 √(−2 ln u₁)、角度用 2π·u₂,
// 這樣 (x, y) 就剛好是兩個獨立的 N(0, 1)。
function normalPair(rnd) {
let u1 = rnd(); // rnd() 回傳 [0, 1)
if (u1 < 1e-12) u1 = 1e-12; // 防 log(0) = −Infinity
const u2 = rnd();
const r = Math.sqrt(-2 * Math.log(u1));
const t = 2 * Math.PI * u2;
return { x: r * Math.cos(t), y: r * Math.sin(t) };
}
第二步:著色(coloring)。現在手上是一團標準的圓形點雲 z, 我們要把它變形成指定的 C。只要找到一個矩陣 A 滿足 A·Aᵀ = C,那麼 A·z 的共變異數矩陣就正好是 C。
找 A 的方法有兩種:Cholesky 分解,或者特徵分解取對稱平方根。 我選後者,因為 04、05 關已經把特徵分解玩熟了 —— 直接沿用同一套零件:
// C 的對稱平方根:A = v₁·√λ₁·v₁ᵀ + v₂·√λ₂·v₂ᵀ
// 直覺:沿著 v₁ 拉伸 √λ₁ 倍、沿著 v₂ 拉伸 √λ₂ 倍。圓被拉成橢圓,就這樣。
function sqrtSym(C) {
const pr = LAB.M.eig2(C).pairs; // 已按 λ 由大到小
const s1 = Math.sqrt(Math.max(0, pr[0].value)); // √λ₁
const s2 = Math.sqrt(Math.max(0, pr[1].value)); // √λ₂
const a = pr[0].vector, b = pr[1].vector; // v₁, v₂(已單位化、互相垂直)
return [
[s1*a.x*a.x + s2*b.x*b.x, s1*a.x*a.y + s2*b.x*b.y],
[s1*a.x*a.y + s2*b.x*b.y, s1*a.y*a.y + s2*b.y*b.y],
];
}
// 生成一顆符合 (μ, C) 的隨機點
const A = sqrtSym(C);
const z = normalPair(rnd); // 圓形點雲
const pt = LAB.M.add(mu, LAB.M.matVec(A, z)); // 被拉成橢圓形點雲
隨機數用的是 mulberry32(32 位元的小型 PRNG,種子固定 → 每次重新整理拿到同一批點, 數字才對得起來)。按「重新抽 2000 顆」會換種子。
橢圓的形狀來自 exp(−d²/2):d² 是 x 進入密度公式的唯一入口, 所以等密度線 = 等 d² 線 = 橢圓。而 2 維的 1σ 橢圓只裝 39%,不要拿一維的 68% 來用。
下一節把「39%」這個數字的來源講完:d² 這個量自己有一個分布, 而上面那張表的九個數字,全部是從那一個分布查出來的。
⑥ d² 的分布叫卡方:門檻是從這裡查出來的
⑤ 給了你一條很好用的閉式解 P(d² ≤ k²) = 1 − exp(−k²/2)。 問題是它只有二維成立。你的資料有三欄、十欄的時候呢? 而且下一節 ⑦ 的門檻選單會冒出 5.991、9.210 這種數字 —— 它們是從哪張表查來的?
兩個問題同一個答案:d² 這個量本身有一個分布,它叫卡方分布 (chi-squared,希臘字母 χ 唸「kai」)。它的定義短到有點失望:
把 df 個 z 各自平方再加起來,得到的那個數字會服從什麼分布 —— 那就是「自由度 df 的卡方分布」。
沒有第二層意思。卡方 =「平方和」這件事的分布,df 就是你加了幾個平方進去。
為什麼 d² 剛好就是這個東西
因為 ⑤ 那個著色矩陣 A 是可以倒著走的。當時我們用 x = μ + A·z 把圓形的標準點雲拉成符合 C 的橢圓形點雲; 反過來讀就是 z = A−1(x − μ) —— A−1 把橢圓揉回正圓。
而 d² 算的,正好就是「揉回正圓之後那個點離原點多遠」的平方:
而 z = A−1(x − μ) 的兩個分量,正是兩個獨立的標準常態。
如果資料是多元常態,那 d² 服從自由度 = 欄位數的卡方分布。 兩欄 → df = 2、十欄 → df = 10。④ 那句「d² 讀作幾個 σ 的平方」 現在有了完整身分:它是幾個標準常態平方相加出來的數字。
順帶回收 ⑤ 那條閉式解:df = 2 的卡方,密度剛好是 ½·e−t/2 —— 那就是指數分布。 積分起來就是 1 − e−t/2,一字不差就是 ⑤ 給你的那條。 二維之所以有漂亮的閉式解,純粹是因為 df = 2 的卡方剛好退化成指數分布, 這是二維獨有的好運,df = 3 就沒有了。
門檻是把分布「反過來查」
這個動作 機率統計 02 關的累積分布與分位數那一節講過: CDF 問的是「小於等於某個值的機率是多少」,分位數是反過來問「機率 95% 的那個值是多少」。 所謂的「查卡方表」,就是在查卡方分布的分位數。
下一節 ⑦ 那個選單裡的四個數字,全部是 df = 2 的卡方分位:
「d² 服從卡方」這句話有一個前提被藏起來了:它假設 μ 和 C 是已知的真值。 實務上你手上只有 15 筆資料,μ 和 C 都是從這同一批資料估出來的 —— 每一筆資料都參與了「決定中心在哪、橢圓多胖」,然後又被拿去量自己離中心多遠。
嚴格算下來,這種「自己量自己」的 d² 服從的是一個縮放過的 Beta 分布,尾巴比卡方短一點(樣本越少差越多;15 筆算少的)。 n 夠大時兩者才會收斂到一起。
那為什麼還照樣用卡方?因為 ⑦ 末尾那句話: 門檻是產品決策,不是數學結論。卡方給你一個有理有據的起點, 真正要上線的門檻還是要拿你自己的資料去校準。知道自己在近似,跟不知道,是兩回事。
d² 不是一個孤零零的數字,它有分布。那個分布叫卡方,唯一的旋鈕是自由度 df = 欄位數。
於是三件事被同一個東西串起來了:⑤ 的涵蓋率是卡方的 CDF、 ⑦ 的門檻是卡方的分位數、「這筆有多罕見」是卡方的尾機率。 同一條曲線,三種讀法。
⑦ 拿去用:15 筆刷卡紀錄,抓出可疑的那兩筆
回到 ① 的案子。這是某個持卡人兩週的 15 筆紀錄,兩欄是時刻與金額。
合成資料,不是真人。規律是「越晚金額越大」(下班後才買貴的、深夜只買宵夜)。
想換成你的資料,改檔案裡 CARD 那個陣列就行。
| 商家 | 時刻 | 金額 | z 時刻 | z 金額 | z 歐氏 | d² | d | 判定 |
|---|
用 95% 門檻(d² > 5.991),恰好兩筆超標,而且跟直覺完全一致:
- 停車費 23:20 刷 120 元 d² = 7.626 —— 最晚的時刻卻是最小的金額,逆著趨勢。
- 線上遊戲點數 03:10 刷 5400 元 d² = 6.282 —— 就是 ① 那筆。
第三名是完全正常的「家電行 22:10 刷 5193」,d² 只有 2.637 —— 跟第二名差了 2.4 倍, 門檻放在哪裡都不會誤判。
然後看那兩把爛尺的成績單:
- 單欄 z-score:全部 15 筆裡,最大的 |z| 是 1.551。拿 |z| > 2 當規則,一筆都抓不到。
- z 歐氏距離:線上遊戲 1.9279、家電行 1.9277。 只差 0.00013,並列第二名。一個真異常跟一筆正常消費,這把尺認為一樣可疑。
差別只在中間夾的是 I 還是 C⁻¹。就這一個零件。
門檻要訂在哪?
如果你的資料真的是多元常態,那 d² 服從卡方分佈(chi-squared), 自由度 = 欄位數。二維就是 2 個自由度,於是「要抓最極端的 5%」直接查表得到 d² > 5.991。 上面那個下拉選單就是這張表的四個常用值 —— ⑥ 已經把這張表拆開給你看過了, 連「為什麼 d² 會服從卡方」都有現場的曲線與模擬對帳。
二維剛好有閉式解,所以你連查表都不用:由 P(d² ≤ k²) = 1 − exp(−k²/2) 反解,門檻 = −2·ln α。 代 α = 0.05 → 5.991。代 α = 0.01 → 9.210。 這條閉式解的來歷(df = 2 的卡方剛好是指數分布)在 ⑥。
真實資料通常不是漂亮的常態,所以很多團隊不查卡方表,直接用經驗分位數: 算出全部樣本的 d²,取第 99 百分位當門檻。這樣做的好處是不管分佈長怎樣, 誤報率至少被你自己控制在 1% 附近。門檻是產品決策,不是數學結論 —— 漏抓一筆盜刷的代價 vs 誤擋一筆正常消費的代價,決定它該放哪。
⑧ 工程師的「原來如此」
刷卡盜刷偵測的第一層規則,幾乎都是這個東西的加強版。特徵不只兩欄(金額、時刻、商家類別、 距離上一筆交易的公里數、當日累計筆數…),可能十幾欄,但公式一個字都不用改 —— d² = (x−μ)ᵀC⁻¹(x−μ) 在 12 維跟在 2 維長得一樣, 只是 C 變成 12×12、等高線變成 12 維的橢球。
為什麼是它而不是更潮的模型?因為它便宜、可解釋、而且不用標註資料。 算一筆 d² 是幾十次乘加,可以放在刷卡授權的毫秒級路徑上; d² 超標時你還能拆開看是哪一項貢獻最多(就是 ② 那個攤開的算式), 直接回答「為什麼擋這筆」。深度模型在這兩件事上都做不到。
順帶一提:這也是為什麼盜刷集團會先刷一筆小額測試。 小額落在橢圓正中央,d² 幾乎是 0,什麼都不會觸發。
自駕車、無人機、火箭導航都在跑卡爾曼濾波器(Kalman filter)。它每一步做兩件事: 預測下一刻的狀態,然後拿新的觀測去修正。而它的預測從來不是一個點, 是「一個點 + 一顆不確定性橢圓」—— 那顆橢圓就是預測狀態的共變異數矩陣 P, 跟本關的 C 是同一種東西。
問題來了:雷達回傳的觀測值有時候是垃圾(多重反射、雨滴、路邊的鐵皮)。 收到一顆離譜的點,如果照單全收,整個濾波器會被拉歪好幾秒。 所以每個實作都有一道守門:算新觀測到預測點的馬氏距離,超過門檻就整筆丟掉。 這道守門的正式名稱叫 validation gate 或 ellipsoidal gating, 門檻就是查卡方表 —— 跟 ⑥ 那條曲線、⑦ 的下拉選單同一張表。
這裡非得用馬氏不可,原因很具體:預測橢圓是會轉的。 車子直線加速時,「沿著行進方向」的位置很不確定(橢圓被拉長), 「左右偏移」很確定(橢圓很窄)。同樣偏 2 公尺,往前偏是正常誤差,往旁邊偏是感測器壞了。 歐氏距離看不出這個差別,會同時放掉真垃圾、擋掉真觀測。
⑨ 踩坑
C 有 p(p+1)/2 個要估的數字(p 是欄位數)。2 欄要估 3 個, 10 欄要估 55 個,50 欄要估 1275 個。樣本數沒有遠大於這個數量,C 就是雜訊。
更糟的是 C⁻¹ 比 C 更脆弱:取反矩陣會放大小特徵值的誤差 (C⁻¹ 的特徵值是 1/λ,λ 估錯一點點,1/λ 就歪很多)。 土法煉鋼的檢查:把資料隨機切兩半,各自算 C,比一下兩個 C 差多少。 差很多就別用了。經驗法則是 n 至少要 10p,想穩一點抓 n ≥ 50p。
只要有兩欄完全線性相關(例如同時放了「身高 cm」和「身高 m」,或放了
「單價」「數量」「總價」),det C = 0,LAB.M.inv 回傳 null,
整條路斷掉。就算沒有完全相關,只是高度相關(ρ = 0.999),det C 接近 0,
d² 會變成一個對雜訊極度敏感的數字 —— 隨便一顆點都能算出 d² = 900。
三種解法,由懶到勤:
- 刪欄:先算相關矩陣,把 |ρ| > 0.99 的欄砍掉一邊。最省事。
- Shrinkage(收縮):往單位矩陣拉一點回來 —— C' = (1−δ)·C + δ·(tr C / p)·I,收縮強度 δ 抓 0.05~0.2 (tr C / p 是平均變異數,乘上 I 就是「一顆同樣大小的正圓」)。 效果是「把太扁的橢圓稍微吹回圓一點」,一定可逆。Ledoit–Wolf 有自動選 δ 的公式。
- 用偽逆:SVD 之後把太小的奇異值當 0 丟掉(07 關那套), 等於只在「資料真的有厚度」的子空間裡量距離。
可以在 ③ 把 cov 拉到底,親眼看 det C 掉到 0.0x 時 C⁻¹ 的數字怎麼飛掉。
這是最容易吃悶棍的一條。假設你的使用者其實有兩群:一群早上活動、一群深夜活動。 算出來的 μ 會落在兩群中間的無人區,C 會很大(因為它把「兩群相隔很遠」也算成變異)。
結果:真正的正常使用者(在任一群裡)d² 偏大,而落在兩群之間的怪點 d² 反而接近 0。 異常偵測被完全反過來,而且不會報錯,只會靜靜地誤判。
怎麼發現:畫出 d² 的直方圖。如果它不是「左邊高、往右拖尾」, 而是中間凹、雙峰,那就是這個坑。解法是先分群再各群算自己的 C (這就走向高斯混合模型 GMM),或者換 kNN / isolation forest 這類不假設形狀的方法。
再講一次,因為這條的誤判成本最高:2 維 1σ 橢圓只涵蓋 39%,3 維只剩 20%。 拿一維的 68 / 95 / 99.7 去二維畫圈設門檻,你會誤擋一大片正常資料然後找不到原因。
反過來也一樣危險:有人知道「1σ 不夠」就直接跳到 3σ, 以為那是 99.7% —— 二維的 3σ 是 98.89%,也就是每 100 筆會漏放 1 筆, 比你預期的 3 筆/1000 筆寬鬆了三倍多。
要多少涵蓋率就去 ⑦ 的下拉選單挑,或直接算 k = √(−2·ln α)。不要憑印象。
⑩ 這關回答了什麼
那顆橢圓到底是什麼?它是資料的邊界嗎?
不是邊界,是等高線。它是「所有 d² 等於同一個值的點」連成的曲線 —— 跟地形圖上的等高線一模一樣,只是把海拔換成「馬氏距離」。
它完全由 C 決定:圓心 = μ、長短軸方向 = C 的兩個特徵向量、 1σ 時的兩個半軸長 = √λ₁ 與 √λ₂。所以它不是額外資訊, 它就是 C 這四個數字換一種畫法(見 ② 的對照表)。
特別注意:1σ 橢圓外面本來就該有點(二維有 61% 的資料在外面,見 ⑤)。 它不是「資料的範圍」,把它當邊界用是 坑 4。
為什麼公式裡是 C⁻¹ 而不是 C?
因為我們要做的是除以散開程度,不是乘。
一維你早就在做了:z = (x − μ) / σ。 某一欄很散(σ 大),同樣偏離 1 個單位就不算什麼,所以要除掉。 二維的「散開程度」是一張 2×2 的表 C,而矩陣沒有除法 —— 除以一張表就是乘上它的反矩陣。所以 C⁻¹。
用 C 會得到完全相反的行為:資料越散的方向,算出來的距離越大。 在 ③ 把 var(x) 拉大,橢圓會往橫向長(用 C⁻¹,正確); 若改用 C,橢圓會往橫向縮,變成「橫著偏一點就算很奇怪」,跟直覺完全相反。
還有一個檢查角度:C⁻¹ 的特徵值是 1/λ。 λ 大(那個方向很散)→ 1/λ 小 → 該方向的懲罰輕。這正是我們要的。
馬氏距離跟歐氏距離,差在哪一個字?
字面上差在中間夾的那個矩陣:
馬氏:d² = (x−μ)T C⁻¹ (x−μ) I 是單位矩陣。所以歐氏距離=偷偷假設了「兩欄變異數都是 1、而且互不相關」的馬氏距離。
語意上差在它們回答的是不同的問題:
- 歐氏問「離中心幾公分」—— 幾何問題,跟資料長什麼樣無關。
- 馬氏問「對這份資料而言有多罕見」—— 統計問題,答案會隨資料改變。
兩者可以給出完全相反的排序,這不是誰壞了。 ④ 那顆 preset 按鈕就是現場:P 的歐氏 3.677 遠大於 Q 的 1.556, 但 P 的馬氏 1.334 遠小於 Q 的 2.460。做異常偵測要的是後者。
還有一個實務差別:換單位。金額從元改成千元,歐氏距離整組變,馬氏距離一個字都不變 (④ 末尾有 15 筆資料的實測比對)。
什麼時候「必須」用馬氏,不能將就用歐氏?
四種情況,看到就別再用歐氏了:
- 各欄單位不同。時刻(0~24)跟金額(0~5400)擺在一起,歐氏距離會被金額整包吃掉 —— 時刻那一欄再怎麼異常都影響不了結果。
- 各欄的散開程度差很多。就算單位相同,一欄 σ = 100、一欄 σ = 1,歐氏尺等於只看第一欄。
- 欄與欄之間有相關性。這是最關鍵的一條,也是唯一「標準化也救不了」的一條 —— 先做 z-score 再算歐氏距離,可以解決前兩條,但解決不了相關性 (⑦ 那個 1.9279 vs 1.9277 就是活生生的失敗案例)。
- 不確定性本身會轉。卡爾曼濾波的預測橢圓每一步都在轉向, 「往前偏 2 公尺」和「往旁邊偏 2 公尺」的意義天差地遠(見 ⑧)。
反過來,什麼時候歐氏就夠:各欄同單位、同量級、而且你已經知道它們互相獨立。 例如同型號感測器陣列的讀數、或已經做過 PCA 白化(whitening)的座標 —— 白化之後 C 就是 I,馬氏自動退化成歐氏。
如果兩欄完全不相關,馬氏距離會退化成什麼?
退化成「z-score 的歐氏距離」。推導一行就完:cov = 0 時 C = [[σ₁², 0], [0, σ₂²]] 是對角矩陣, 對角矩陣的反矩陣就是每個對角元素各自取倒數:
d² = (x₁−μ₁)²/σ₁² + (x₂−μ₂)²/σ₂² = z₁² + z₂² 也就是「先把每欄各自轉成 z-score,再算普通的畢氏距離」。
這正是 ② 的 step 2 那張圖:橢圓的軸對齊座標軸, 只是兩個半徑不一樣。這時候你確實可以用「先標準化再算歐氏」偷懶,結果完全等價。
再退一步:如果連 σ 都一樣(C = σ²I), 那 d² 就是 歐氏距離² / σ² —— 只差一個固定倍數,排序完全一樣。 這就是 step 1 的正圓。
所以三步其實是一條線:正圓(歐氏)→ 軸對齊橢圓(z-score 歐氏)→ 歪橢圓(真馬氏), 每一步都是前一步的嚴格推廣。而只有最後一步是標準化救不了的。
C 不可逆(奇異)的時候怎麼辦?
先確認你是哪一種奇異,因為解法不同:
- 結構性奇異(欄位本身重複):你放了「單價、數量、總價」,第三欄是前兩欄的乘積; 或同時放了 cm 和 inch。直接刪欄,這是資料清理問題,不是數學問題。
- 樣本數不夠(n ≤ p):C 的秩最多 n−1,欄位比樣本多的時候必定奇異。 先降維(PCA 取前幾個主成分)或加正則化。
- 接近奇異(det C 很小但不是 0):能算,但算出來的 d² 極度不穩定。 這種最陰險,因為程式不會報錯。
工程上最常用的一招是 shrinkage:往單位矩陣拉一點回來。
其他選項:用 SVD 的偽逆(把太小的奇異值當 0 丟掉,見 07 關),等於只在資料真的有厚度的子空間裡量距離; 或加 ridge 項 C + εI(ε 是一個很小的正數,把對角線墊高一點, 讓 det C 離 0 遠一些)。三種本質上都是同一件事 —— 承認你不知道那些方向的變異,就別在那些方向上量距離。
可以在 ③ 把 cov 拉到滑桿極限,看 det C 掉到 0.0x 時 C⁻¹ 的四個數字怎麼飛。
d² 的單位是什麼?為什麼可以拿去查卡方表?
d² 沒有單位,它是無因次的(dimensionless)。 分子是「距離²」,分母(C)是「距離²」,兩者相除,單位全部消掉。 這就是為什麼換單位不影響它(④ 末尾), 也是為什麼同一張門檻表可以套在任何資料上 —— 不管你的欄位是公尺、元、還是次數。
要讀懂它,把 d² 讀成「幾個 σ 的平方」:d² = 4 → d = 2 → 「2σ 那一圈」。
能查卡方表的理由是:如果資料真的是多元常態,那 d² 剛好服從自由度 = 欄位數的卡方分佈。 直覺上不難懂 —— ⑥ 拆給你看過:把橢圓揉回正圓之後 d² = z₁² + z₂², 而「幾個獨立標準常態的平方和」的定義就是卡方分佈。二維就是 2 個自由度。
那句「如果資料真的是常態」是有代價的假設。實務上不成立的時候, 改用經驗分位數(見 ⑦ 末尾)比硬套卡方表安全。
2σ 橢圓的面積是 1σ 的幾倍?這有什麼用?
4 倍(兩個半軸都乘 2,面積乘 2×2)。一般地,k σ 橢圓的面積是 π·k²·√λ₁·√λ₂ = π·k²·√(det C)。
這件事有個很實用的推論:√(det C) 就是「這團資料佔了多大」的單一數字, 叫廣義變異數(generalized variance)。03 關 說過 det C 在兩欄高度相關時會趨近 0 —— 現在你看得到那句話的幾何意思了: 橢圓被壓成一根線段,面積趨近 0。
而面積變成 4 倍,裝到的資料卻只從 39.35% 變成 86.47%(不是 4 倍)—— 因為機率密度往外遞減得比面積長得快(exp(−d²/2) 掉得非常兇)。
你現在會用 C 量「一個點有多奇怪」了:把 C 反過來,夾成二次型 wᵀC⁻¹w,得到一個分數,然後看這個分數大不大。
下一關把同一張 C 掛到完全不同的位置上。這次不取反矩陣,直接夾 wᵀCw —— 而且 w 的意思換成「我把錢分配到各資產的比重」。 這時 wᵀCw 不再是距離,而是這個投資組合的風險。 問題也整個翻面:不再是「這個分數大不大」,而是「在預算加起來等於 1 的約束下, 哪一組 w 讓這個分數最小」。
答案是一條曲線,叫有效邊界 —— 去 最佳化 03 有效邊界:在約束裡找最優。