线性回归是用一条直线(或多维里的一个平面)拟合「输入和输出」的关系。先把模型怎么学写清楚,再用一段可运行的 Python 把拟合结果算出来。
线性回归用一条直线描述一个输入和一个输出的关系。给定若干对 (x, y),它要找出权重 w 和偏置 b,使预测值尽量贴近真实值:
y_hat = w * x + b
多个输入时,每个特征各有一个权重:
y_hat = w1 * x1 + w2 * x2 + ... + b
「尽量贴近」用均方误差衡量。对 n 个样本,损失是预测误差平方的平均:
loss = (1 / n) * Σ (y_hat - y)²
损失越小,直线越贴合这些点。求这条直线有两种做法。样本不多时可以直接用正规方程一次算出 w 和 b。样本很多、特征很多时,用梯度下降:先随便取一组 w、b,沿损失下降的方向小步更新,重复多次。一元时每一步是:
dw = (2 / n) * Σ (y_hat - y) * x
db = (2 / n) * Σ (y_hat - y)
w = w - learning_rate * dw
b = b - learning_rate * db
下面用 8 个点拟合 y ≈ x + 1,只依赖 Python 标准库。learning_rate 是步长,epochs 是重复次数。
xs = 1, 2, 3, 4, 5, 6, 7, 8
ys = 2.1, 2.9, 4.2, 5.1, 5.8, 7.2, 8.1, 8.9
n = len(xs)
w, b = 0.0, 0.0
learning_rate = 0.01
epochs = 2000
for epoch in range(1, epochs + 1):
dw = db = loss = 0.0
for x, y in zip(xs, ys):
y_hat = w * x + b
err = y_hat - y
dw += err * x
db += err
loss += err * err
dw = (2 / n) * dw
db = (2 / n) * db
loss /= n
w -= learning_rate * dw
b -= learning_rate * db
if epoch in (1, 100, 500, 2000):
print(f"epoch={epoch} loss={loss:.4f} w={w:.4f} b={b:.4f}")
def predict(x):
return w * x + b
for x in (2, 5, 9):
print(x, round(predict(x), 2))
跑完后,损失从 35.85 降到 0.0195,参数停在 w = 0.9917、b = 1.0747。代入新的 x:
| x | 预测 y |
|---|---|
| 2 | 3.06 |
| 5 | 6.03 |
| 9 | 10.00 |
同一批数据用正规方程得到 w = 0.9917、b = 1.0750,和梯度下降几乎重合。决定系数 R² 是 0.9962,表示这些点的波动几乎都能被这条直线解释。
看结果时先看损失是否还在下降。损失震荡或变成 inf,把 learning_rate 调小,例如改成 0.001。损失下降很慢,就加长 epochs,或把步长略调大。R² 接近 1 只说明直线贴合了这批样本;用没参与训练的数据再预测一次,才能看出能不能用到新数据上