為什麼直線無法處理「是/否」問題
在這條學習路徑到目前為止,迴歸都在預測一個數字:價格、溫度、考試分數。配適出的那條線回答的是「有多少?」這個問題。但現實中有極大一部分問題根本不是「有多少?」——而是「是,還是否?」這位顧客會不會退訂?這封信是不是垃圾郵件?這位病人有沒有這個疾病?這位學生及不及格?答案只是兩個標籤之一,而我們的工作,就是從資料去預測那個標籤。
我們把它具體化。想像有 200 位學生。對每一位,我們記錄他讀了幾個小時,以及期末考有沒有及格——及格寫成 1,不及格寫成 0。如果把時數放在橫軸、把及格/不及格這個標記放在縱軸,每個點都只會落在兩個高度之一:底部一排在 0(不及格),頂部一排在 1(及格)。世界上沒有「1.7 個及格」這種東西。
剛學完線性迴歸的你,第一個直覺也許是用最小平方法在這團散點中畫一條直線。在中段它好像還行——讀得多的學生確實比較常及格——但有兩件事會嚴重出問題。第一,這條線在兩端永遠不會停下來:把它往右延伸夠遠,它會預測出 1.3 的「及格機率」;往左延伸夠遠,又會預測出 −0.2。這兩個都不是真正的機率。第二,這條線堅持「每多讀一小時,及格機率到處都增加同樣多」,但常識告訴我們:從 1 小時到 2 小時的那一跳,應該遠比從 20 小時到 21 小時的那一跳重要得多。
我們真正想預測的,並不是那個光禿禿的標籤,而是該標籤出現的機率——一個介於 0 與 1 之間的數,例如「這位學生有 0.73 的機率及格」。機率永遠不能離開 0 到 1 這個區間,而且在兩端應該變得平坦:當你已經幾乎確定時,再多讀一點點書幾乎動不了那根指針。所以我們需要的是一條 S 形曲線——靠近 0 時平緩、中段陡峭、靠近 1 時又再次平緩——而不是一條直線。這條曲線,就是邏輯迴歸整個構想的核心。
一團散點,中間畫了一條直線迴歸線;這條線往兩個方向都延伸到資料範圍之外。
邏輯(S 形)曲線
我們需要的那條 S 形曲線有個名字:邏輯函數(logistic function),又叫做 S 形函數(sigmoid,源自希臘文「S 形」)。把它想成一台小機器:你丟給它任何一個數——從負一百萬到正一百萬都行——它都會溫和地把這個數壓進 0 與 1 之間的開區間,於是輸出永遠可以被讀成一個機率。
用文字描述它的行為:丟給它一個很負的數,它回傳一個略大於 0 的值;丟給它正好是 0,它回傳正好 0.5;丟給它一個很正的數,它回傳一個略小於 1 的值。在這之間,它以那條招牌般的 S 形平滑上升。把輸入記作 z,寫成公式就是:
邏輯/S 形函數。這裡的 e 是數學常數,約等於 2.718;無論你放進什麼樣的 z,輸出都嚴格落在 0 與 1 之間。
現在把它接上預測變數。在裡頭,z 就是我們在一般迴歸裡熟悉的線性式——一個截距,加上每個預測變數乘上它的係數。只有一個預測變數(讀書時數)時,z = β₀ + β₁·x。邏輯迴歸把這個直線分數 z 送進 S 形函數,將它彎折成一個機率:
只有一個預測變數的邏輯迴歸模型:先算出線性分數,再用 S 形函數把它壓成一個預測機率 p̂。
具體的數字會讓它瞬間清楚起來。假設配適出的係數是 β₀ = −4、β₁ = 0.5(每小時)。一位讀了 4 小時的學生,z = −4 + 0.5·4 = −2,而 σ(−2) ≈ 0.12——大約 12% 的及格機率。一位讀了 10 小時的學生,z = −4 + 0.5·10 = 1,於是 σ(1) ≈ 0.73——73% 的機率。讀了正好 8 小時的學生,z = 0,剛好給出 0.5。曲線通過 50% 的那個預測變數值(這裡是 8 小時),就是這個模型天然的臨界點。
β₀ 與 β₁ 是怎麼選出來的?不是用最小平方法——平方誤差對 0/1 資料的表現很糟。邏輯迴歸改用最大概似估計:電腦去搜尋一組係數,讓我們實際觀察到的那些及格與不及格,在模型之下盡可能地有可能發生。你幾乎永遠不會手算這個過程——一行軟體就做完了——但知道「這條曲線是被配適成讓觀測資料看起來最有可能,而不是去最小化某個垂直距離」會很有幫助。
對數勝算與勝算比
S 形函數很優雅,卻讓係數 β₁ 很難直接解讀:因為曲線是彎的,「多讀一小時」在不同起點上增加的機率並不一樣。為了找回一個乾淨、固定不變的解讀方式,我們從機率切換到勝算(odds)——這正是賭盤莊家所使用的貨幣。
勝算很單純。如果及格的機率是 p,那麼及格的勝算就是 p 除以不及格的機率 1 − p。機率 0.8 代表勝算 0.8 / 0.2 = 4——也就是「4 比 1」,發生的可能性是不發生的四倍。機率 0.5 對應勝算為 1(「等同勝算」)。機率與勝算承載的資訊一模一樣,只是穿了不同的衣服。
勝算把機率轉成「會發生」對「不會發生」的比值。p = 0.8 給出勝算 4;p = 0.5 給出勝算 1。
現在就是奇妙之處。對勝算取自然對數——也就是對數勝算,又稱 logit——那條雜亂的 S 曲線就完全被拉直了:對數勝算正好等於線性分數 z。所以,機率是預測變數的 S 形函數,但對數勝算卻是它們的一條平直直線。這就是邏輯迴歸的定義方程式:
邏輯迴歸其實是一個普通的線性模型——只不過對象是對數勝算,而不是機率本身。那條直線,才是我們真正在配適的東西。
在對數勝算這把尺上,β₁ 就是一個普通的斜率:每多讀一小時,只是替對數勝算加上 β₁。但沒有人是用對數勝算在思考的。把係數取指數,你就得到一個真正有感的東西:e^{β₁} 就是勝算比——預測變數每增加一個單位,勝算被乘上的那個倍數。
勝算比:把係數取指數,就能把它讀成「該預測變數每變動一個單位,勝算被乘上的倍數」。
再看數字。若 β₁ = 0.5,則 e^{0.5} ≈ 1.65,所以每多讀一小時,學生及格的勝算被乘上約 1.65 倍——勝算增加 65%。若係數是 0.7,則 e^{0.7} ≈ 2.0——每一個單位讓勝算翻倍。勝算比等於 1,代表這個預測變數毫無作用(係數為 0);大於 1 代表它提高勝算;小於 1 代表它降低勝算。
選擇門檻
邏輯迴歸交給你的是一個機率——0.73、0.08、0.51。但決策通常是「是/否」:要不要把這封信丟進垃圾郵件夾;要不要把病人召回做切片。要把機率變成標籤,你得挑一個門檻(cutoff,分界值):如果預測機率不低於門檻,就預測「是」;否則預測「否」。大家順手就用的預設值是 0.5——但 0.5 只是慣例,不是自然律。
門檻是一個關於商業與倫理的決定,不是統計上的決定。把它調低(比方說調到 0.2),你會把更多案例標為陽性:幾乎每個真案例都抓得到,但也會引發大量假警報。把它調高(調到 0.8),你只標出非常有把握的案例:假警報很少,卻會漏掉許多真案例。要把它設在哪裡,完全取決於哪一種錯誤的代價比較大。
一組具體對比。對癌症篩檢來說,漏掉一個真案例(告訴生病的人「你沒事」)遠比一次假警報(一通嚇人、但最後是良性的召回)要糟糕得多。所以你會刻意把門檻設低——寧可接受許多假警報,也要盡量少漏掉真案例。但對自動垃圾郵件過濾器來說,盤算就反過來了:把一封真正重要的信丟進垃圾夾,對使用者造成的困擾,可能遠比偶爾放過一封垃圾信還大,所以你會設一個比較保守、比較高的門檻。同一個模型、同樣的機率——不同的分界值,因為代價不同。
要精準地談論「假警報」與「漏掉」——並且公平地比較不同門檻——我們需要一塊計分板,把每一種對與錯的答案都數出來。這塊計分板,就是混淆矩陣。
混淆矩陣
一旦門檻把機率變成「是/否」的預測,每一筆預測都會落入四個格子之一,取決於「我們預測了什麼」與「事實是什麼」。依慣例,我們把想要偵測的那個結果稱為陽性(得了病、是垃圾郵件、會流失)。這四個格子是:
- 真陽性(TP):我們預測「是」,事實也真的是「是」——一個真案例被正確抓到。
- 真陰性(TN):我們預測「否」,事實也真的是「否」——一個健康的人被正確放行。
- 偽陽性(FP):我們預測「是」,但事實是「否」——一次假警報(也叫做型一錯誤)。
- 偽陰性(FN):我們預測「否」,但事實是「是」——一次漏失(也就是型二錯誤)。
把這四個計數排進一張 2×2 的表格——一軸是「事實是什麼」,另一軸是「我們預測了什麼」——你就得到了混淆矩陣,這是描述一個分類器最有用的單一摘要。我們用真實的數字把它填起來。
假設我們替 1,000 人篩檢某疾病,其中 100 人實際得病(所以 900 人健康)。我們的模型在它選定的門檻下,把 120 人標為「疑似生病」。這 120 人裡,80 人真的生病(TP),40 人是健康的假警報(FP)。在它放行的 880 人中,60 人其實有病卻溜了過去(FN),820 人是真正健康的(TN)。快速核對:80 + 40 + 60 + 820 = 1,000,而真正生病的人合計 80 + 60 = 100。對得起來。
一張 2×2 方格,一軸是事實的是/否、另一軸是預測的是/否;四個格子分別是真陽性、偽陰性、偽陽性、真陰性。
最直觀的分數是準確率(accuracy):所有預測中答對的比例,也就是 (TP + TN) 除以全部人數。這裡是 (80 + 820) / 1,000 = 0.90——漂亮的 90%。聽起來很棒,直到你發現:一個偷懶的模型,只要對每個人都預測「健康」,也能拿到 900 / 1,000 = 90%,卻一個生病的人都沒抓到。當某一類很罕見時(這裡只有 10% 生病),準確率就成了一個只是好看的指標:它獎勵你去忽略那些你最在乎的案例。這個類別不平衡(class imbalance)的問題,正是我們需要更銳利度量的原因。
準確率:所有預測中答對的比例。當各類別人數均衡時很有用;當不均衡時,則危險地具有誤導性。
精確率、召回率與 ROC 曲線
有兩個問題能直接穿透這個不平衡陷阱,而且它們問的是真正不同的事。精確率(precision):當模型說「是」的時候,它有多常是對的?召回率(recall,又叫敏感度 sensitivity,或真陽性率):在所有真的是「是」的案例中,模型究竟抓到了多少?一個關乎「警報能不能信」,另一個關乎「真案例會不會被漏掉」。
精確率:在所有被我們標為陽性的人當中,真的是陽性的比例。
召回率:在所有真正是陽性的人當中,我們成功抓到的比例。
回到篩檢的數字。精確率 = 80 / (80 + 40) = 0.67——我們每標一個人,有三分之二的機率是對的。召回率 = 80 / (80 + 60) = 0.57——我們抓到了真正生病者中的 57%。再注意:那個偷懶的「人人健康」模型,召回率現在是 0——正是 90% 準確率悄悄掩蓋掉的那場災難性失敗。
精確率與召回率會透過門檻彼此拉扯。把分界值調低,你標出更多人:召回率上升(漏掉的真案例變少),但精確率通常下降(更多的標記其實是假警報)。把分界值調高,則正好相反。你通常無法同時把兩者都最大化——你得依問題的需求去選一個平衡點。如果真的需要一個單一數字,F1 分數(precision 與 recall 的調和平均)是常見的折衷,只是它會掩蓋你究竟偏向了哪一邊。
一條以精確率對召回率所畫的曲線;隨著召回率往 1 增加,精確率傾向下滑。
若想一次看清一個分類器在所有門檻下的品質,就畫出 ROC 曲線(receiver operating characteristic,受試者操作特徵曲線)。對每一個可能的分界值,在縱軸畫上真陽性率(也就是召回率),對上橫軸的偽陽性率——也就是真正為陰性、卻被錯標為陽性的比例。一個沒用的、等同擲硬幣的分類器,只會沿著對角線走;一個好的分類器,則會往左上角鼓起,抓到許多真陽性、同時只引發少數假警報。
那條曲線底下的面積——也就是 AUC(曲線下面積)——把整幅圖壓成一個介於 0.5(不比亂猜好)與 1.0(完美)之間的數字。AUC 甚至有一個很美的白話意義:它就是「模型給一個隨機挑出的真陽性案例打的分數,高於一個隨機挑出的真陰性案例」的機率。
一條從左下升向右上、並鼓起於對角線之上的曲線;曲線下方的面積被填上了底色。
一個完整的分類範例
讓我們把整條流程完整跑一遍,套在一個新問題上:預測哪些顧客會流失(churn)——也就是下個月會退訂。陽性類別是「已流失」。假設我們有 5,000 名顧客一整年的資料:他們每月的消費金額、距離上次登入過了幾天,以及最後究竟有沒有流失。
我們配適一個邏輯迴歸,以流失為結果、那兩個預測變數為輸入。實務上只需要幾行:
from sklearn.linear_model import LogisticRegression # X: columns [monthly_spend, days_since_login]; y: 1 = churned, 0 = stayed model = LogisticRegression() model.fit(X_train, y_train) # predicted PROBABILITIES, not hard labels probs = model.predict_proba(X_test)[:, 1] # turn probabilities into yes/no with a chosen threshold predictions = (probs >= 0.30)
假設「距離上次登入的天數」這個係數算出來是每天 0.04。把它取指數:e^{0.04} ≈ 1.04,所以每多一天沒登入,顧客流失的勝算就被乘上約 1.04——每天大約讓勝算上升 4%。而且它會複利累積:30 天沒登入,勝算被乘上 1.04³⁰ ≈ 3.2。這一個數字,就明確告訴留客團隊該把火力對準哪裡——對準那些已經安靜下來的顧客。
因為挽回一個動搖中的顧客很便宜(一封信、一張小折扣),但失去一個顧客卻很昂貴,所以我們把門檻設低,設在 0.30——高召回率,接受一些假警報。在一份保留下來、共 1,000 名顧客的測試集(其中 120 人真的流失)上,模型標出了 200 人:其中 90 人真的流失(TP)、110 人是假警報(FP)、30 名真正流失者溜了過去(FN)、770 人被正確判定為安全(TN)。
現在算度量。精確率 = 90 / 200 = 0.45;召回率 = 90 / 120 = 0.75。所以四分之三的流失者被抓到了——正是我們想要的——代價是 45% 的精確率,意思是被我們標記的顧客中,真的會走的還不到一半。在介入成本很低的情況下,這是個划算的交換:我們很樂意把留客優惠也發給一些本來就不會走的人。但如果介入成本很高,我們就會調高門檻來拉升精確率,只把資源花在幾乎確定會流失的人身上。