08. 写出第一次训练循环
上一篇已经能从当前位置算出坡度,再修改一次参数。但一步通常还没到最低处:走到新位置后,坡度也变了,需要重新测量、继续调整。把这个过程交给循环重复执行,就得到一个能训练的小程序。
本篇沿用第 02 篇的 Python 环境和实验目录,只使用标准库。先看一次循环如何对应手算,再让程序运行 40 次,最后保存结果、退出训练,由另一次程序运行读取参数来预测。
一轮只做清楚的几件事
沿图读一圈:用当前参数算预测,得到残差和损失,计算两个方向的梯度,最后更新参数。由输入向输出计算预测,通常称为前向计算;每完成一次参数更新,就完成一个训练步(Step)。这三条样本每步全部使用,因此 40 步就是把整套三条记录反复用于更新 40 次。
两个坡度必须在同一个位置测量。应先用旧参数算完两项梯度,再一起更新;若先改 w,再用新位置计算 b 的梯度,做的就是另一种更新过程。
对三条教学数据,核心计算为:
python
loss = dw = db = 0.0
for x, y in samples:
residual = w * x + b - y
loss += 0.5 * residual ** 2
dw += residual * x
db += residual
loss /= len(samples)
dw /= len(samples)
db /= len(samples)
w, b = w - lr * dw, b - lr * db这段是完整脚本的核心,samples 是三条记录,lr 是学习率。residual 保存当前残差;dw 和 db 分别累加两个方向的梯度,代码中的名字对应上一篇的 ∂L/∂w 和 ∂L/∂b。** 2 表示平方,+= 表示“在原值上加上”,len(samples) 得到样本数 3,/= 表示除完再存回原变量。
先逐条相加、再除以 3,正对应平均损失和平均梯度。最后一行右边的两个表达式先用旧值算完,再赋给左边的 w,b,因而是同时更新。外层循环每次回到这段开头,都要将 loss,dw,db 重新置零,就像给新一轮计分前清空计分板。否则会把不同参数位置的旧结果也带进来。
完整脚本与运行入口
下载完整训练与重载脚本到自己的 ai-foundations 目录,激活环境后执行:
bash
python train_regression.py --lr 0.1 --steps 40 --output run.json
python train_regression.py --load run.json --weight 2.5第一条命令中的 --lr 0.1 设置学习率,--steps 40 设置更新次数,--output 指定保存文件。第二条命令的 --load 读取这个文件,--weight 2.5 交入一个新重量。
每次启动一次程序,操作系统都会给它一个独立的运行实例,称为进程。第一条命令结束后,内存里的变量就不能直接交给第二次运行;run.json 像记下结果的笔记,保存教学数据、训练设置、每一步记录及最终参数。第二次运行只需读回参数,就能计算预测。
在 Python 3.12 的标准库环境中,40 次更新的损失从 13.833333 降到约 0.00027649,得到 w≈2.027312,b≈0.937914。把新重量代入:2.5×2.027312+0.937914≈6.006193 元,与构造规则给出的 6 元接近。计算机用有限位数存储小数,末尾可能有很小的舍入差异,称为浮点误差,核对时看这些近似值即可。
轨迹不是直线冲向已知答案。梯度由当前位置计算,在不同方向上的变化尺度不同,后期更新会更慢。这里没有把真实参数 2,1 直接写入训练结果。
用失败对照检查理解
单样本模式与上一篇手算保持一致,固定 b=0:
bash
python train_regression.py --single --lr 0.1 --steps 1 --output small-step.json
python train_regression.py --single --lr 1 --steps 1 --output large-step.json两次初始损失都是 2,结束时分别为 0.72 与 18。将第一个命令的步数改成 10,查看每步记录;不要只看最后一行,就认定中间没有异常。
还可以把三样本模式的学习率改成 1。观察损失和参数越来越大、没有靠近低处的现象,就叫发散。脚本在参数超出数值保护范围时停止,并记录 parameter_limit,避免继续放大;要改善结果仍需调整训练设置。输入 --weight -1 或 nan 这样的无效数值时,应得到明确输入错误。
结果如何知道不是算错了
上一篇曾把 w 从 1 增到 1.01,通过损失差估计坡度。脚本使用同样的想法,只是分别在当前位置的两边各试一点:计算 L(w+ε) 和 L(w-ε),相减后除以两点间距 2ε。ε 读作“艾普西龙”,这里只代表一个很小的试探量。这种用小范围数值差估计导数的方法叫有限差分。
脚本把估计值与按公式算出的梯度比较,后者称为解析梯度。二者接近,可以帮助发现求导或代码中的错误。它检查的是“坡度算对了吗”;数据和目标是否选得合理,还要另行检查。
这三条样本没有独立测试集,也没有噪声。现在已经验证:程序能把这几条记录拟合好、保存参数、重新使用。遇到新地区或阶梯计费时,还需真实数据检验。后续数字识别项目会先建立基线,也就是一个简单、可重复的参照方案,再判断复杂模型是否比它更好,而不是只看自己的分数有没有下降。
已经完成样本进入模型、计算损失、求梯度、更新、保存及新进程预测的闭环。继续理解模型怎样输出类别,可阅读神经网络与反向传播;需要按现有知识核对评估边界,可阅读泛化与模型评估。
