用 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 积分。内容经审核后公开。
正在加载讨论…