Python 现代开发学习笔记

(十九)重复更新与最小二乘核对

1. 每轮保留当前损失

一次更新后继续用新参数预测,重新得到残差、梯度,再更新。把这个过程组织成 train:

# 续写同一文件:analysis_project/src/readingarray/regression.py
# 每轮用当前参数计算误差和梯度,再同时更新参数;history 保留更新前的损失。
def train(x, y, steps=400, learning_rate=0.1):
    x, y = checked_xy(x, y)
    if steps < 1 or not np.isfinite(learning_rate) or learning_rate <= 0:
        raise ValueError("更新次数和学习率应为正数")
    bias, weight = 0.0, 0.0
    history = []
    for _ in range(steps):
        loss, grad_bias, grad_weight = loss_and_gradient(x, y, bias, weight)
        if not np.isfinite([loss, grad_bias, grad_weight]).all():
            raise ValueError("更新产生了非有限数,检查数据尺度与学习率")
        history.append(loss)
        bias -= learning_rate * grad_bias
        weight -= learning_rate * grad_weight
    history.append(loss_and_gradient(x, y, bias, weight)[0])
    return np.array([bias, weight]), np.array(history)


def solve_least_squares(x, y):
    x, y = checked_xy(x, y)
    design = np.column_stack((np.ones(x.size), x))
    parameters, _, _, _ = np.linalg.lstsq(design, y, rcond=None)
    return parameters

history 的第 0 项对应初始参数,最后一项对应完成全部更新后的参数。因此进行 400 次更新会留下 401 个损失值。学习率增大时,一步可能走过较好的位置;实际比较每轮损失,才能看出是在稳定下降,还是不断来回跳动。

初始参数进入预测残差损失梯度更新,再以新参数开始下一轮,损失记录与更新次数逐一对应

2. 用设计矩阵表达同一个模型

solve_least_squares 把常量 1 与 x 组成两列:[[1,1],[1,2],[1,3]]。乘上参数向量 [b,w],恰好得到每条 b+w*x。

linalg 是线性代数工具模块,np.linalg.lstsq(设计矩阵,y,rcond=None) 直接求最小二乘解;返回值中第一项是参数,后面还有残差、秩和奇异值等信息。这里用解构取出第一项,得到约 [0,2],再与迭代结果比较。最小二乘接口

本例的直线可以直接求解。逐步更新的写法则让我们看清参数、预测和损失之间的依赖;后面模型变复杂时,仍会沿着这些依赖计算梯度,再更新参数。

笔记正文与全部题解 · Ctrl / ⌘ K