模块 08 · 第 1 课

用 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

"训练模型"这件事,说到底就是:找到一组最好的参数。对这个模型来说,就是找到最好的 wb

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)。损失越小,模型越好。训练的目标可以说得更精确一些:找到让损失最小的参数

随手猜几条

先凭直觉猜几组 wb,看看损失是多少:

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,这在真实的问题里很正常。

最笨的办法:全部试一遍

既然损失能算,那就把所有可能的 wb 都试一遍,挑损失最小的那组。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 个点的直线,不一定正好是生成它们的那条。

找到的 wb 和真实值不完全相同,这件事以后会一直伴随我们:模型学到的是"能最好地解释眼前这些数据"的参数,数据有偏差,学到的参数就有偏差。

网格搜索还有一个不起眼的问题:它只能找到格子上的点。w 每隔 0.01 试一次,真正最好的 w 如果是 1.2571,它就只能给出 1.26。把格子分得更细,要试的次数就成倍增加。下一课的梯度下降没有这个限制,它能一直逼近到最好的那个值。

这个办法为什么不行

两个参数,每个试几百个值,就是 9 万种组合,用了 0.2 秒。

如果有 3 个参数,就是几千万种组合;10 个参数,组合的数量是一个天文数字,宇宙的寿命都不够试完。一个最小的神经网络也有几十上百个参数,第 09 模块的小 GPT 有一百多万个。

我们需要一个聪明得多的办法:不要盲目地试,而是每一步都知道该往哪个方向改参数、改多少。这就是下一课的梯度下降。

练习

  1. 把数据的随机波动从 12 改成 0(rng.normal(0, 0, size=50)),重新运行。最好的直线是不是正好 w=1.2, b=20?损失是多少?
  2. 把损失函数换成"平均绝对误差":np.mean(np.abs(errors)),再做一次网格搜索,找到的直线和均方误差找到的一样吗?
  3. 往数据里加一个极端的点:一套 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 积分。内容经审核后公开。

正在加载讨论…