第 46 課 · 進階 · 19 分鐘

Python 線性回歸:從直線到誤差

配適一條直線,做預測,再衡量它錯了多少。

線性回歸斜率截距均方誤差

天氣越熱,冰咖啡賣得越多——小黑板一直這麼寫。穿過銷售資料的一條直線可以驗證這句話。斜率是每升高一度多賣幾杯?

線性回歸是最小的有用模型:一個輸入、一個輸出和一條直線。親手實現它能看清「配適」到底在做什麼、預測如何產生,以及為什麼訓練點上的低誤差不等於模型能夠泛化。 你還會用 R² 把直線與平均值比較,並檢查離群值的影響。

直線就是一條預測規則

斜率表示輸入每增加 1 時預測值變化多少,截距表示輸入為 0 時的預測值。兩者合起來把輸入 x 變成一個預測輸出。

def predict(x, slope, intercept):
    return slope * x + intercept

print(predict(3, 2.0, 1.0))

兩個數字定義一條直線

用中心化求和配適

只有一個輸入時,最小平方法斜率等於 x 與 y 的協方差除以 x 的方差。先把兩組數減去平均值,可以讓算式更小。截距再讓直線穿過平均值點。

xs = [1, 2, 3]
ys = [2, 4, 6]
mean_x = sum(xs) / len(xs)
mean_y = sum(ys) / len(ys)
numerator = sum((x - mean_x) * (y - mean_y) for x, y in zip(xs, ys))
denominator = sum((x - mean_x) ** 2 for x in xs)
slope = numerator / denominator
intercept = mean_y - slope * mean_x
print(round(slope, 2), round(intercept, 2))

直線穿過平均值點

先預測,再衡量誤差

均方誤差把每個真實值與預測值之差的平方取平均。平方會放大嚴重偏差,結果單位也是輸出單位的平方。只要可能,就應在未參與配適的資料上報告指標。

xs = [1, 2, 3]
ys = [2, 4, 5]
mean_x = sum(xs) / len(xs)
mean_y = sum(ys) / len(ys)
slope = sum((x - mean_x) * (y - mean_y) for x, y in zip(xs, ys)) / sum((x - mean_x) ** 2 for x in xs)
intercept = mean_y - slope * mean_x
mse = sum((y - (slope * x + intercept)) ** 2 for x, y in zip(xs, ys)) / len(xs)
print(round(slope * 4 + intercept, 2))
print(round(mse, 2))

預測和誤差是兩個問題

守住輸入範圍

直線對任何 x 都能算出數字,但這不代表觀察範圍之外的結果可信。記錄訓練範圍,在越界時給出明確警告或回退,而不是悄悄外推。

def within_range(x, xs):
    return min(xs) <= x <= max(xs)

print(within_range(4, [1, 2, 3]))
print(within_range(2, [1, 2, 3]))

有數字不等於有保證

R² 將直線與平均值比較

R² 等於 1 減去殘差平方和與圍繞平均值的總平方和之比。接近 1 說明直線解釋了大部分變化;等於 0 說明它不比始終預測平均值更好。在新資料上,如果直線比這個基線更差,R² 可以為負。

xs = [1, 2, 3, 4]
ys = [2, 4, 5, 8]
mean_y = sum(ys) / len(ys)
mean_x = sum(xs) / len(xs)
slope = sum((x - mean_x) * (y - mean_y) for x, y in zip(xs, ys)) / sum((x - mean_x) ** 2 for x in xs)
intercept = mean_y - slope * mean_x
ss_res = sum((y - (slope * x + intercept)) ** 2 for x, y in zip(xs, ys))
ss_tot = sum((y - mean_y) ** 2 for y in ys)
print(round(1 - ss_res / ss_tot, 2))

相對常數預測的改進

一個離群值就能拉動直線

最小平方法法讓平方殘差總和最小,因此遠離模式的點會把直線拉向自己。比較包含和不包含該點的配適,檢查殘差,並報告這一決定。不要悄悄刪掉不方便的觀測。

def fit(xs, ys):
    mx = sum(xs) / len(xs)
    my = sum(ys) / len(ys)
    slope = sum((x - mx) * (y - my) for x, y in zip(xs, ys)) / sum((x - mx) ** 2 for x in xs)
    return slope, my - slope * mx

for ys in ([2, 4, 6, 8], [2, 4, 6, 20]):
    slope, intercept = fit([1, 2, 3, 4], ys)
    print(round(slope, 2), round(intercept, 2))

敏感性也是結果的一部分

本課要點

  • 簡單線性模型只儲存一個斜率和一個截距。
  • 最小平方法選擇平方誤差最小的直線。
  • MSE 適合比較模型,但單位是輸出單位的平方。
  • 誤差應報告在留出資料上,而不只是配適資料。
  • 信任外推之前,先檢查輸入是否在訓練範圍內。
  • R² 衡量相對平均值的改進;離群值可能主導最小平方法配適。

常見錯誤

  • 把訓練集完美配適當成泛化能力的證據。
  • 所有 x 相同時仍然直接除以零。
  • 直接比較不同輸出單位的 MSE。
  • 悄悄刪掉離群值,再把更乾淨的配適當成沒有做過任何決定。
輪到你

配適點 (1, 5)、(2, 8)、(3, 11),然後列印 x = 4 的預測值,保留一位小數。

動手試試

改改變數、先預測輸出,再執行程式碼。第一次點擊「執行程式碼」時會下載 Python 執行時,可能需要幾秒鐘。

瀏覽器內 Python本機執行 · 無需帳號
首次執行會從 CDN 下載 Pyodide。修改後點擊「執行程式碼」。

練習本課

12 道題,作答後立即看到解釋。

查看 12 道題答案開始練習
← 提示詞驗證梯度下降 →