用 numpy 做線性迴歸
從一個最簡單的問題出發:根據面積預測房價。用一條直線當模型,用均方誤差衡量好壞,再用最笨的辦法把九萬條直線挨個試一遍,看清楚"訓練"到底在找什麼。
- 約 35 分鐘
- 難度:入門
- 實測:2026-09-14 numpy 2.5,固定隨機種子
程式碼和執行結果保留原樣(簡體中文),與實際執行時完全一致。
第一部分裡,我們一直在用訓練好的大模型。第二部分要回答一個問題:模型是怎麼訓練出來的?
從最簡單的例子開始。不是神經網路,也不是大模型,只是一條直線。別小看它:接下來幾課的所有概念,損失、梯度、學習率、反向傳播,都能在這條直線上先看清楚,然後原封不動地用到神經網路上,最後用到 GPT 上。
這一課只需要 numpy:
uv add numpy
問題:根據面積預測房價
假設你收集了 50 套房子的面積和成交價,想找出一個規律,以後看到一套房子的面積,就能估計它的價格。
我們自己造一組資料,這樣知道"正確答案"是什麼,方便檢查:
import numpy as np
rng = np.random.default_rng(0)
# 造一组数据:房价(万元)= 1.2 × 面积(平方米)+ 20,再加上一些随机的波动
area = rng.uniform(40, 150, size=50)
price = 1.2 * area + 20 + rng.normal(0, 12, size=50)
default_rng(0) 固定了隨機數的種子,你執行得到的資料和我的完全一樣。真實的規律是"每平方米 1.2 萬,再加 20 萬",但每套房子都有一些隨機的偏差(標準差 12 萬),就像真實世界裡朝向、樓層、裝修會讓價格上下浮動。
前 5 套房子長這樣:
前 5 套房子:
110.1 平方米 156.4 万元
69.7 平方米 89.1 万元
44.5 平方米 73.4 万元
41.8 平方米 78.1 万元
129.5 平方米 159.9 万元
模型:一條直線
最簡單的猜想是:價格和麵積之間是一條直線的關係。
预测价格 = w × 面积 + b
w 是斜率,意思是每多一平方米,價格多多少萬;b 是截距,意思是面積為 0 時的價格(可以理解為一個固定的基礎價)。這兩個數叫做模型的參數。
def predict(w, b, x):
"""模型:一条直线。w 是斜率(每平方米多少万),b 是截距。"""
return w * x + b
"訓練模型"這件事,說到底就是:找到一組最好的參數。對這個模型來說,就是找到最好的 w 和 b。
GPT 這樣的大模型也一樣,只是它的參數不是 2 個,而是幾十億、幾千億個;模型也不是一條直線,而是一個極其複雜的函式。第 09 模組你會親手訓練一個有一百多萬個參數的小 GPT,本質上做的仍然是這件事。
損失:怎麼衡量"好"
什麼叫"最好的一條直線"?得有一個具體的、能算出數字的標準。
對每一套房子,模型會給出一個預測價格,和真實價格之間有一個差。把所有差都平方,再取平均,這個數叫做均方誤差(Mean Squared Error,MSE):
均方误差 = 平均值( (预测价格 - 真实价格)² )
def mse(w, b):
"""损失:预测值和真实值之差的平方,取平均。"""
errors = predict(w, b, area) - price
return np.mean(errors ** 2)
為什麼要平方?有兩個原因。第一,差有正有負,直接平均會互相抵消,平方之後都變成了正數。第二,平方會讓大的誤差更"顯眼":差 10 萬,平方是 100;差 30 萬,平方是 900。模型會被迫優先照顧那些錯得離譜的點。
這個衡量"模型有多差"的數,統稱為損失(loss)。損失越小,模型越好。訓練的目標可以說得更精確一些:找到讓損失最小的參數。
隨手猜幾條
先憑直覺猜幾組 w 和 b,看看損失是多少:
for w, b in [(1.0, 0.0), (1.0, 30.0), (1.5, 0.0), (1.2, 20.0)]:
print(f" w={w:<4} b={b:<5} 均方误差 {mse(w, b):9.1f}")
随手猜几条直线,看看误差:
w=1.0 b=0.0 均方误差 1810.1
w=1.0 b=30.0 均方误差 310.8
w=1.5 b=0.0 均方误差 284.5
w=1.2 b=20.0 均方误差 146.6
w=1.0, b=0 這條線離資料很遠,損失 1810。加上 30 萬的截距,損失降到 311。w=1.2, b=20 正是我們造資料用的真實規律,損失只有 146.6。
注意,就算是真實的規律,損失也不是 0。因為資料裡有隨機的波動,任何一條直線都不可能穿過所有的點。損失永遠降不到 0,這在真實的問題裡很正常。
最笨的辦法:全部試一遍
既然損失能算,那就把所有可能的 w 和 b 都試一遍,挑損失最小的那組。w 從 0 到 3,每隔 0.01 試一次;b 從 -50 到 100,每隔 0.5 試一次:
best = (None, None, float("inf"))
tries = 0
for w in np.arange(0.0, 3.0, 0.01):
for b in np.arange(-50.0, 100.0, 0.5):
tries += 1
loss = mse(w, b)
if loss < best[2]:
best = (w, b, loss)
网格搜索:试了 90000 条直线,用了 0.2 秒
最好的一条:w=1.26 b=14.5,均方误差 143.1
試了 9 萬條直線,找到的最好一條是 w=1.26, b=14.5,損失 143.1,比真實規律(w=1.2, b=20)的 146.6 還低一點。這並不奇怪:我們只有 50 個數據點,而且帶著隨機的波動,最能擬合這 50 個點的直線,不一定正好是生成它們的那條。
找到的 w 和 b 和真實值不完全相同,這件事以後會一直伴隨我們:模型學到的是"能最好地解釋眼前這些資料"的參數,資料有偏差,學到的參數就有偏差。
網格搜尋還有一個不起眼的問題:它只能找到格子上的點。w 每隔 0.01 試一次,真正最好的 w 如果是 1.2571,它就只能給出 1.26。把格子分得更細,要試的次數就成倍增加。下一課的梯度下降沒有這個限制,它能一直逼近到最好的那個值。
這個辦法為什麼不行
兩個參數,每個試幾百個值,就是 9 萬種組合,用了 0.2 秒。
如果有 3 個參數,就是幾千萬種組合;10 個參數,組合的數量是一個天文數字,宇宙的壽命都不夠試完。一個最小的神經網路也有幾十上百個參數,第 09 模組的小 GPT 有一百多萬個。
我們需要一個聰明得多的辦法:不要盲目地試,而是每一步都知道該往哪個方向改參數、改多少。這就是下一課的梯度下降。
練習
- 把資料的隨機波動從 12 改成 0(
rng.normal(0, 0, size=50)),重新執行。最好的直線是不是正好w=1.2, b=20?損失是多少? - 把損失函式換成"平均絕對誤差":
np.mean(np.abs(errors)),再做一次網格搜尋,找到的直線和均方誤差找到的一樣嗎? - 往資料里加一個極端的點:一套 60 平方米的房子賣了 500 萬(
area = np.append(area, 60); price = np.append(price, 500))。分別用均方誤差和平均絕對誤差做網格搜尋,哪一種受這個點的影響更大?為什麼?
自測
1. "訓練一個模型"指的是什麼?
找到一組最好的參數,讓模型在已有的資料上損失最小。對一條直線來說,就是找到最好的斜率 w 和截距 b。大模型也一樣,只是參數的數量多得多,模型的形式複雜得多。
2. 均方誤差為什麼要把誤差平方,而不是直接平均?
誤差有正有負,直接平均會互相抵消,一個很差的模型也可能得到接近 0 的平均誤差。平方後都是正數,而且大的誤差被放大得更多,模型會優先減小那些錯得離譜的預測。
3. 網格搜尋在這個例子裡只用了 0.2 秒,為什麼說它不能用來訓練神經網路?
需要嘗試的組合數隨著參數個數指數增長。2 個參數是 9 萬種組合,3 個參數就是幾千萬種,神經網路有成百上千甚至數十億個參數,組合數是天文數字,根本不可能試完。
提問與討論
這一課沒看懂的地方,在這裡問。看到別人的問題,也歡迎你來回答。
提問 +3 點,回答別人 +6 點。內容經審核後公開。
正在載入討論…