读完这篇,你能做什么
能手推一维回归梯度,在固定小数据上拟合已知斜率和截距,并说明这个小实验能验证什么、不能证明什么。
学习理由、准备要求与知识点
为什么学它
线性回归把预测、损失、求梯度和改参数串成一个能手算的小例子,方便以后理解更复杂的训练循环。
先读这些内容
知识点清单
- 特征目标与参数
- 线性预测和截距
- 残差与半均方误差
- 批量梯度推导
- 学习率与同步更新
- 每轮重置梯度累加器
- 损失归约和形状
- 拟合与泛化的区别
运行前先确认Java 17;Python 3.14 标准库 CPU 教学实验。手工计算梯度与更新,无需安装训练框架;框架源码作为后续对照阅读。无 GPU、多机或真实任务泛化结论。
01先弄懂它是什么
如果手里有输入和实际结果,就可以试着找到一条预测规则,让预测尽量接近这些记录,这叫拟合。对于样本 (x_i,y_i),本篇用 yhat_i=w*x_i+b 预测结果:w 控制直线有多陡,b 控制整体上下位置,x 是输入特征,y 是实际目标。训练时调整的是 w 和 b;本例的样本数据固定,不是把每个样本的 x 也拿去随意修改。
判断预测好不好,可以先看预测值减去实际值,也就是残差 e_i=yhat_i-y_i。正负误差直接相加可能相互抵消,所以这里先平方再平均,使用 L=(1/(2n))*sum(e_i^2) 作为损失。前面的二分之一是为了让求导更方便。实验用 Python 数值和列表实现;后续框架的默认 MSE 通常没有这个二分之一,比较结果时要注意损失和梯度的数值尺度也会不同。
梯度给出损失局部增加最快的方向,因此梯度下降使用相反方向:w_new=w-lr*dw,b_new=b-lr*db。lr 为正学习率,控制一步走多远。局部下降方向不保证任意大步都降低损失,曲率较大或特征尺度较大时,过高学习率可能造成振荡甚至数值发散。
文中这几个词,先认一认
- 参数
- 训练中需要调整的模型内部数值。本篇只调整直线的 w 和 b。
- 残差
- 预测值减去真实目标,表示预测比目标高了或低了多少。
- 梯度下降
- 先算出损失在当前位置增长最快的方向,再朝反方向小步调整参数,反复尝试减小损失。
02看看它怎么工作
由链式法则,单项半平方误差对预测的导数为 e_i,对 w 的导数再乘 x_i,对 b 的导数再乘一。累加并平均后,dw=sum(e_i*x_i)/n,db=sum(e_i)/n。这两项来自同一批旧参数下的预测,应先完整计算,再同时更新参数,不能更新 w 后又用新 w 计算同一步的 b 梯度。
采用 x=[-2,-1,0,1,2]、y=2x+1,从 w=b=0 开始。初始损失为四点五,dw=-4、db=-1,学习率零点一使第一步到 w=0.4、b=0.1。这个可手算的起点很重要:它能检查符号、平均分母和半因子,而不只观察最后损失似乎变小。
训练循环包含前向预测、计算损失、求导和参数更新。纯 Python 实验每次调用 gradients 都把两个累加器初始化为零,因此不会混入上一轮结果。后续框架也需要显式管理梯度清零或有意累积;这是相同数学约定的不同实现。本篇每一步都使用完整五个样本,只演示全批梯度下降,不要求提前掌握张量与自动微分。
这组数据也能用公式直接求出最小二乘解(闭式解),作为小例子的参考,但并非所有问题都适合直接求逆。特征矩阵可能秩不足,使多个参数组合在这组数据上产生同样预测;显式求逆还可能放大数值误差。实际线性求解通常考虑 QR、SVD 或正则化,梯度法则更容易扩展到大数据和后续非线性模型。
拟合到 w≈2、b≈1,只证明在这组人工线性数据及运行配置上,计算链条能恢复已知关系。真实数据包含噪声、遗漏变量、分布变化和数据泄漏风险,需要独立划分与评估。训练损失下降不能单独证明因果关系,也不能说明部署后的预测一定准确。
先跑通一个最小例子
只需 Python 3.14 标准库,无需 NumPy 或 PyTorch。将代码保存为 experiment.py,在文件所在终端执行 python experiment.py;程序用列表保存五个样本,用数值变量保存两个参数。
import math
x=[-2.,-1.,0.,1.,2.]
y=[2*v+1 for v in x]
def gradients(w,b):
gw=gb=0.0 # 每次调用从零开始,不混入前一轮梯度。
for xi,yi in zip(x,y):
error=w*xi+b-yi
gw+=error*xi
gb+=error
return gw/len(x),gb/len(x)
def loss(w,b):
return math.fsum((w*xi+b-yi)**2 for xi,yi in zip(x,y))/(2*len(x))
w=b=0.0
assert loss(w,b)==4.5
gw,gb=gradients(w,b)
assert gw==-4.0
assert gb==-1.0
for _ in range(100):
gw,gb=gradients(w,b)
w,b=w-0.1*gw,b-0.1*gb # 右侧先计算完,再同时写回。
assert loss(w,b)<1e-7
assert abs(w-2)<1e-3
assert abs(b-1)<1e-3
assert len(x)==len(y) # 严格一一对应,列表不会自动做张量广播。
print("PASS 7; w =",round(w,6),"b =",round(b,6),"loss =",loss(w,b))
核对运行结果打印 PASS 7,w 接近 2、b 接近 1,损失小于 1e-7;这是固定五点 CPU 拟合结果。
正在加载流程图…
查看流程图源码(Mermaid)
flowchart LR
A["固定样本与当前参数"] --> B["预测 yhat"]
B --> C["半均方误差"]
C --> D["求两个参数梯度"]
D --> E["同时更新 w 与 b"]
E --> A按顺序看,每一步做了什么
- 明确样本形状与预测公式。
- 定义损失及归约分母。
- 手算初始梯度作为参考。
- 每轮重置累加器,完成手工梯度与同步更新。
- 核对参数和损失,再另做新数据评估。
03对照实现代码
PyTorch v2.9.1;torch/nn/functional.py · mse_loss
以下为原创精简教学实现,不是原样库源码;只演示本文说明的机制,省略真实实现的优化、调度和完整兼容边界。
def half_mse(prediction,target):
if len(prediction)!=len(target) or not prediction:
raise ValueError("same nonempty shape")
errors=[a-b for a,b in zip(prediction,target)]
# 原创一维模型明确拒绝广播,且主动加入二分之一。
return sum(e*e for e in errors)/(2*len(errors))
# PyTorch 默认 mse_loss 没有二分之一,并支持 Tensor 归约路径。
PyTorch v2.9.1 的 mse_loss 默认 reduction 为 mean。它检查输入与目标形状,不一致时发出警告,然后仍通过 broadcast_tensors 扩展张量。由此可见,出现警告不一定阻止错误形状继续计算;调用方最好在任务边界明确断言预测与目标对应。
不带权重时,函数将广播后的输入、目标和归约枚举传给底层 mse_loss。带权重时,源码显式形成平方误差和加权误差,并根据 none、sum、mean 返回不同结果。加权平均的分母也有具体规则,不能默认所有 mean 都仅除以样本行数。
原创模型只有一维同形列表,并使用本文定义的半均方误差,不是原样框架实现。省略广播有利于初学者清楚看到每个预测只对应一个目标;真实多维输出需要进一步说明是按样本还是按全部元素平均,这会影响梯度尺度。
源码模块解释损失如何构造,优化器更新则是另一条链。调用 mse_loss 不会自动清零梯度、执行 backward 或调整参数。将前向目标、自动微分和优化器三个职责分开,有助于定位损失正常但参数不变、或梯度不断累积的问题。
代码先调用谁,再调用谁
- 预测与目标进入
mse_loss - 检查形状并执行广播
- 选择权重与 reduction 分支
- 底层计算平方误差归约
- 调用者再执行 backward 与优化器 step
04动手写一个例子
同步梯度更新的一维线性拟合
有限非空样本数组使用半均方误差,从 w=b=0 开始全批梯度下降。学习率为正有限数,迭代次数非负;无外部训练库。
- 输入 1
x=[-2,-1,0,1,2], y=2x+1, lr=.1, steps=100- 输出 1
w≈2,b≈1
- 输入 2
同样数据 steps=0- 输出 2
w=b=0, loss=4.5
每一轮扫描全部样本,依据旧 w、b 计算残差和两个梯度和,再除以 n。只有完成所有样本后才一起更新参数,确保这确实是同一个损失位置的梯度。若边扫边更新,就变成另一种在线优化过程,不能继续使用相同的手算期望。
方法对样本、学习率和步数进行入口验证,并检查更新后的数值是否有限。它没有承诺所有正学习率都收敛;过大步长可能使有限输入逐步产生非有限参数,因此错误信息指向数值发散而不是伪装成成功拟合。
返回参数与最终损失,测试同时检查两者。仅损失接近零在欠定问题中未必唯一确定参数;本例数据的特征有足够变化,斜率和截距可以辨认。另用零步、全零目标、空输入和非法学习率检查实现是否尊重契约。
为什么这样写是对的一轮内所有残差来自同一组参数;轮末参数按该组参数的完整批量梯度同时更新。
每轮一次样本扫描,最终再扫描计算损失;对固定两个参数,无需保存每个样本的计算图。
保存为 Main.java,运行 javac --release 17 -encoding UTF-8 Main.java,再运行 java Main。程序包含固定输入自测,断言失败时会抛出错误。
public class Main {
record Fit(double w,double b,double loss){}
static Fit fit(double[] x,double[] y,double rate,int steps){
if(x==null||y==null||x.length==0||x.length!=y.length||!Double.isFinite(rate)||rate<=0||steps<0)throw new IllegalArgumentException("input");
for(int i=0;i<x.length;i++)if(!Double.isFinite(x[i])||!Double.isFinite(y[i]))throw new IllegalArgumentException("finite");
double w=0,b=0;
for(int t=0;t<steps;t++){
double gw=0,gb=0;
for(int i=0;i<x.length;i++){double e=w*x[i]+b-y[i];gw+=e*x[i];gb+=e;} // 全批使用旧参数。
double nw=w-rate*gw/x.length,nb=b-rate*gb/x.length;
if(!Double.isFinite(nw)||!Double.isFinite(nb))throw new ArithmeticException("diverged");
w=nw;b=nb; // 两个参数在梯度完整计算后同步更新。
}
double sum=0;for(int i=0;i<x.length;i++){double e=w*x[i]+b-y[i];sum+=e*e;}
double loss=sum/(2*x.length); // 半因子与梯度推导一致。
if(!Double.isFinite(loss))throw new ArithmeticException("loss");
return new Fit(w,b,loss);
}
static void near(double a,double b){if(!Double.isFinite(a)||Math.abs(a-b)>1e-3)throw new AssertionError(a+" != "+b);}
static void bad(Runnable r){try{r.run();}catch(IllegalArgumentException e){return;}throw new AssertionError();}
public static void main(String[] args){
double[] x={-2,-1,0,1,2},y={-3,-1,1,3,5};
Fit f=fit(x,y,.1,100);near(f.w,2);near(f.b,1);if(f.loss>1e-7)throw new AssertionError();
Fit zero=fit(x,y,.1,0);near(zero.w,0);near(zero.b,0);near(zero.loss,4.5);
near(fit(x,new double[5],.1,10).loss,0);
bad(()->fit(new double[0],new double[0],.1,10));
bad(()->fit(x,y,0,10));
bad(()->fit(new double[]{1},new double[]{1,2},.1,10));
System.out.println("PASS 6");
}
}这些特殊情况也要试一试
- 空数据无法定义平均损失。
- 零步返回初始模型而非偷偷训练一次。
- 正学习率不保证收敛。
- 预测与目标形状必须一致。
进阶选读 · 多特征与病态线性问题
场景特征尺度差异大或高度相关,梯度下降缓慢。
做法分析 X^T X 的条件性质,使用训练集标准化、适当正则化或稳定线性求解,比较优化误差与统计误差。
复杂度全批梯度通常每步 O(n*d),直接求解成本依算法与维度而异。
好处与代价缩放改善优化不等于增加信息,秩不足仍可能需要约束或更多数据。
05项目里怎么用
训练前保存数据定义和损失约定,比盲目增加迭代次数更重要。确认目标是否使用相同单位、缺失值怎样处理,以及样本权重是否正确。训练曲线异常时,先检查首批数据和可手算梯度,再逐步排查学习率、尺度与代码路径。
参数可辨识性影响结果解释。如果所有 x 都相同,多个 w、b 组合可能给出同样预测,优化器选到某组参数不代表发现唯一真实规律。现实回归中的相关关系也不自动构成因果关系,需要研究设计和额外假设;本篇人工线性关系只是用来检验计算闭环。
实际评估需要独立验证数据,并避免用测试集反复选择学习率或训练步数。训练误差可能继续下降而新数据误差开始上升,这是泛化问题而非优化器一定失效。后续文章会展开数据划分与正则化,当前实验结论严格限于固定小数据和 CPU 执行。
容易踩的坑
- 忘记平方损失是否带二分之一。
- 先更新一个参数再计算同一步另一个梯度。
- 梯度未清零导致非预期累积。
- 训练误差低就宣称对新数据有效。
几种做法,怎么选
表格有四列,窄屏可左右滑动查看代价和适用条件。
| 方案 | 优势 | 代价与限制 | 适用条件 |
|---|---|---|---|
| 全批梯度下降 | 更新确定,推导直观 | 每步扫描全部数据 | 教学和小型数据 |
| 小批随机梯度 | 便于大数据迭代 | 存在抽样噪声与更多状态 | 实际训练 |
| 稳定最小二乘求解 | 小中型线性问题可直接求解 | 受维度和数值条件影响 | 线性参考基线 |
06检验是否读懂了
先自己想、自己写,再展开答案核对。场景题是假设的练习情境;遇到还没学过的内容,可以做完基础后再回来。
先用自己的话解释
01为什么梯度前要减号?
展开参考答案与推导
梯度指向局部增加损失最快的方向,负梯度在足够小步长下给出下降方向。减号并不保证任意大步都会下降,步长仍需要控制。
02半均方误差与默认 MSE 梯度相同吗?
展开参考答案与推导
相差固定二倍尺度。本文乘二分之一使梯度公式更简洁;使用库时应显式对齐定义,不要在推导与代码之间忽略这个常数。
03为什么每轮重置梯度累加器?
展开参考答案与推导
每一步需要当前参数位置的批量梯度;混入旧参数位置的梯度会改变算法。实验在函数入口初始化两个和为零,后续框架也要明确清零或有意累积的约定。
换个情况,还会处理吗
01参数不变但 loss 能计算,先检查什么?
展开参考答案与推导
先确认求得的梯度不是意外恒零,再检查学习率、更新语句是否执行以及新参数是否写回。计算损失只评估当前模型,不会自动调整参数;后续框架同样区分求导与更新职责。
02学习率很大时 loss 上升怎么办?
展开参考答案与推导
先核对梯度符号和尺度,再降低学习率或改善特征缩放。正方向设置正确仍可能因步长跨越局部下降区域而发散。
03所有样本 x 都相同还能解释斜率吗?
展开参考答案与推导
通常无法唯一辨认斜率和截距,因为只约束一个组合。即便预测误差很小,也不能声称恢复了唯一参数,需要更多变化信息或明确约束。
写代码试一试
01手算首步更新。
- 输入 / 条件
dw=-4,db=-1,w=b=0,lr=.1- 输出 / 预期
w=.4,b=.1
提示 减去梯度。
展开参考答案与推导
负梯度使两个参数增加,分别增加零点四和零点一;先算好两个梯度再写回参数,保持同一步定义一致。
02计算初始损失。
- 输入 / 条件
预测全零,y=[-3,-1,1,3,5]- 输出 / 预期
4.5
提示 平方和除以2n。
展开参考答案与推导
平方和为四十五,n 为五,除以十得到四点五。若使用没有半因子的默认 MSE,则结果为九。
03检查错误广播。
- 输入 / 条件
预测形状[5,1],目标形状[5]- 输出 / 预期
应先修正为对应形状
提示 库可能广播为二维。
展开参考答案与推导
显式确认每个预测只对应一个标签,再按任务选择 squeeze 或调整目标形状;不能因为损失返回标量就认为配对正确。
读完之后,独立完成
课后作业:自己做一遍
本篇共 10 道作业,先完成 8 道基础与综合任务,再按前置知识挑战进阶题。其中包含 1 道思维拓展,试着换个条件,看看原来的办法是否还管用。不附参考答案。
能手推一维回归梯度,在固定小数据上拟合已知斜率和截距,并说明这个小实验能验证什么、不能证明什么。
本篇重点加练
梯度大小受损失归约与数据表示影响,加练与样本复制、权重变化有关的等价性。
- 批量梯度推导
- 损失归约和形状
重点内容会多练几次。先说清楚怎么做,再试试空数据、出错或条件改变时会发生什么。
先自己做,卡住时记下问题,再回来查。问答题写清“为什么、什么时候适用”;动手题留下输入、输出和运行步骤,方便下次重做。
基础必做
- 作业 01代码推演
数据 x=[-1,0,2]、y=[-1,1,4],初始斜率 0.5、截距 -0.5。写出预测、残差、半均方误差和两个参数的批量梯度。
- 提交内容
- 完整手算记录
- 怎样算完成
- 损失定义一致
- 截距梯度包含所有样本
练到的知识点特征目标与参数 · 线性预测和截距 · 残差与半均方误差 · 批量梯度推导
- 作业 02原理问答
比较在同一轮内依次修改参数和用同一份旧参数统一更新的含义;写出两种算法明确的状态转移定义。
- 提交内容
- 两份更新契约
- 怎样算完成
- 新旧状态标注完整
- 累加器生命周期明确
练到的知识点学习率与同步更新 · 每轮重置梯度累加器
- 作业 03原理问答
把每条训练记录复制 k 次,分别研究 sum 与 mean 损失的参数梯度;写出哪些训练设置才能让一次更新保持一致。
- 提交内容
- 带条件的推导
- 怎样算完成
- 损失分母明确
- 复制数据与新增信息区分
练到的知识点批量梯度推导 · 损失归约和形状
综合应用
- 作业 04编程实践
用
NumPy实现双特征线性回归训练器,固定随机种子生成 40 条数据;保存每轮损失并对至少两个参数做独立梯度检查。- 提交内容
- CPU 程序与训练记录
- 怎样算完成
- 不依赖自动求导
- 梯度校验与拟合分开
练到的知识点批量梯度推导 · 学习率与同步更新 · 每轮重置梯度累加器 · 损失归约和形状
- 作业 05排错实验
构造目标形状 [4,1]、预测形状 [4] 的训练案例,比较接口期望和实际参与损失的元素;加入能够捕获这一类问题的检查。
- 提交内容
- 复现代码与修订测试
- 怎样算完成
- 记录实际张量形状
- 测试防止错误广播
练到的知识点每轮重置梯度累加器 · 损失归约和形状
- 作业 06面试场景
训练误差持续降低,而保留数据误差上升。设计仅改变训练时长的对照实验,写出模型选择与最终报告规则。
- 提交内容
- 实验协议与曲线记录
- 怎样算完成
- 只用验证集选型
- 最终评价不参与调参
练到的知识点特征目标与参数 · 拟合与泛化的区别
- 作业 07编程实践
实现含非负样本权重的双参数线性回归,自己声明样本权重总和为零时的处理规则;用三条记录比较权重表示与整数次数复制表示的损失和梯度。
- 提交内容
- CPU 程序与等价性测试
- 怎样算完成
- 权重归约明确
- 两种表示使用同一目标
练到的知识点批量梯度推导 · 损失归约和形状
- 作业 08排错实验
给线性回归输入增加一个始终为零的特征列,在同一初值上比较各参数梯度;故意将残差与错误特征相乘,检查测试能否定位错列。
- 提交内容
- 故障复现与特征梯度表
- 怎样算完成
- 特征身份可检查
- 正常对照与故障分开
练到的知识点批量梯度推导
进阶挑战 · 掌握前置后选做
- 作业 09设计评审
对同一数据将一个特征放大 1000 倍,比较三组学习率的收敛行为;解释稳定性报告需要保留哪些条件。
- 提交内容
- 尺度实验与分析报告
- 怎样算完成
- 其他因素控制一致
- 失败运行也被记录
练到的知识点残差与半均方误差 · 学习率与同步更新 · 拟合与泛化的区别
思维拓展
先写出你的猜想,再用例子或实验验证。答案可以不同,但要说清依据、适用情况,以及还有什么没弄懂。
- 作业 10思维拓展 · 条件变化
将一个目标值的单位从米改成厘米,“沿用学习率就能等价训练”是否成立?明确参数和损失单位,提出可核验的等价条件或反例。
- 提交内容
- 单位变更论证与小实验
- 怎样算完成
- 参数与目标同步约定
- 相同预测与相同更新分开
练到的知识点批量梯度推导 · 损失归约和形状
完成后,再检验一次
给每题标记“独立完成 / 查阅后完成 / 尚未完成”。查过资料或没做出的题,记下卡在哪里。隔一天换一组输入再试,能解释结果和出错原因,再把这个知识点标为掌握。
继续阅读
先看能帮你完成当前练习的部分。例子跑通后,再读更深入的解释。
- 必读参考文档英文
PyTorch v2.9.1:functional.mse_loss 的归约与广播 (新标签页)↗
核对
mse_loss的形状警告、广播、权重与归约行为,区分损失构造和参数更新。 - 深入参考文档英文
Stanford CS229 Notes (新标签页)↗
阅读线性回归、最小二乘与梯度下降推导。
- 深入参考文档英文
PyTorch 优化循环教程 (新标签页)↗
对照清零梯度、backward 和
optimizer.step的职责。 - 深入参考文档英文
PyTorch 2.9 MSELoss (新标签页)↗
核对 mean、sum、none 的定义与输出形状。
- 实践实践指南中文
机器学习速成课程:用图和小练习理解模型 (新标签页)↗
先看先修说明,再学回归、分类与数据切分。小练习:写出一个预测任务,列出输入、答案来源,以及哪些数据只留到最后检查;解释为何不能用考试题反复改模型。 课程页面公开,部分练习使用 Colab,账号与访问条件按练习说明确认。中文入口不代表所有视频都是中文。
- 实践实践指南中文
动手学深度学习:步子变大,为什么反而离目标更远 (新标签页)↗
先看 11.3.1 的
f(x)=x² 与 x←x−η·2x,其中 η 是学习率,决定每一步迈多大。从 x=10 出发,分别取 η=0.2 和 1.1,记录前三步的 x 与 x²,交一张对照表。首轮跳过泰勒展开证明、牛顿法和多维推导;简单函数上的结果不保证真实网络每步都下降。 中文正文和图公开可读,无需账号。整页绘图代码依赖 d2l 和相应框架;纸上或标准库循环计算无需安装它们。配套标准库小练习不依赖教材的绘图环境。