天氣越熱,冰咖啡賣得越多——小黑板一直這麼寫。穿過銷售資料的一條直線可以驗證這句話。斜率是每升高一度多賣幾杯?
線性回歸是最小的有用模型:一個輸入、一個輸出和一條直線。親手實現它能看清「配適」到底在做什麼、預測如何產生,以及為什麼訓練點上的低誤差不等於模型能夠泛化。 你還會用 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 執行時,可能需要幾秒鐘。
練習本課
12 道題,作答後立即看到解釋。