【deepseek】最简单的线性回归 y = ax 开始理解反向传播和参数更新
假设我们有一组简单的数据,想找到最佳的系数aaa,使得yaxy = axyax能最好地预测结果。真实值 (标签):假设输入x2x = 2x2时,真实答案是ytrue4ytrue4。当前参数:随机初始化a3a = 3a3。前向传播:计算预测值ypreda×x3×26ypreda×x3×26。损失函数 (MSE):计算错误程度L12ypred−ytrue2L21。
当然可以。从一个最简单的线性回归 y = ax 开始,是理解反向传播和参数更新最直观的方式。
1. 场景设定与公式定义
假设我们有一组简单的数据,想找到最佳的系数 aaa,使得 y=axy = axy=ax 能最好地预测结果。
- 真实值 (标签):假设输入 x=2x = 2x=2 时,真实答案是 ytrue=4y_{true} = 4ytrue=4。
- 当前参数:随机初始化 a=3a = 3a=3。
- 前向传播:计算预测值 ypred=a×x=3×2=6y_{pred} = a \times x = 3 \times 2 = 6ypred=a×x=3×2=6。
- 损失函数 (MSE):计算错误程度 L=12(ypred−ytrue)2L = \frac{1}{2}(y_{pred} - y_{true})^2L=21(ypred−ytrue)2。这里乘 12\frac{1}{2}21 是为了求导时消掉平方的 2,方便计算。
代入数值:
L=12(6−4)2=12×4=2L = \frac{1}{2}(6 - 4)^2 = \frac{1}{2} \times 4 = 2L=21(6−4)2=21×4=2。
2. 反向传播 (计算梯度)
反向传播的本质是链式法则:看 aaa 的一点点变化 (∂a\partial a∂a),会引起最终错误 (∂L\partial L∂L) 多大的变化。
计算 ∂L∂a\frac{\partial L}{\partial a}∂a∂L 需要分两步走:
-
第一步:计算损失对预测值的导数 ∂L∂ypred\frac{\partial L}{\partial y_{pred}}∂ypred∂L。
∂L∂ypred=ypred−ytrue=6−4=2\frac{\partial L}{\partial y_{pred}} = y_{pred} - y_{true} = 6 - 4 = 2∂ypred∂L=ypred−ytrue=6−4=2。 -
第二步:计算预测值对参数的导数 ∂ypred∂a\frac{\partial y_{pred}}{\partial a}∂a∂ypred。
∂ypred∂a=x=2\frac{\partial y_{pred}}{\partial a} = x = 2∂a∂ypred=x=2。 -
合成梯度:∂L∂a=∂L∂ypred×∂ypred∂a=2×2=4\frac{\partial L}{\partial a} = \frac{\partial L}{\partial y_{pred}} \times \frac{\partial y_{pred}}{\partial a} = 2 \times 2 = 4∂a∂L=∂ypred∂L×∂a∂ypred=2×2=4。
这个 4 就是 aaa 的梯度。它表明:在当前状态下,如果 aaa 增大 1 个单位,损失 LLL 会随之增大 4 个单位。
3. 参数更新 (梯度下降)
既然增大 aaa 会让错误变大,那我们就应该反向调整来减小错误。
- 学习率:设定一个较小的更新幅度 η=0.1\eta = 0.1η=0.1。
- 更新公式:anew=aold−η×∂L∂aa_{new} = a_{old} - \eta \times \frac{\partial L}{\partial a}anew=aold−η×∂a∂L。
代入数值:
anew=3−0.1×4=2.6a_{new} = 3 - 0.1 \times 4 = 2.6anew=3−0.1×4=2.6。
4. 观察效果
参数从 3 更新到了 2.6,这是否变得更好了呢?我们可以算一下新的损失:
- 新的预测值:ypred_new=anew×x=2.6×2=5.2y_{pred\_new} = a_{new} \times x = 2.6 \times 2 = 5.2ypred_new=anew×x=2.6×2=5.2。
- 新的损失:Lnew=12(5.2−4)2=12×1.44=0.72L_{new} = \frac{1}{2}(5.2 - 4)^2 = \frac{1}{2} \times 1.44 = 0.72Lnew=21(5.2−4)2=21×1.44=0.72。
对比之前的损失 222,现在的损失 0.720.720.72 明显变小了。 如果不断循环这个过程,aaa 最终会收敛到最优解 2(因为 2×2=42 \times 2 = 42×2=4)。
💡 总结:这与你之前问题的关联
这个过程解释了为什么你的 C 语言训练能跑通:
- 前向传播:相当于你在 C 里写的双重
for循环计算矩阵乘法。 - 反向传播:相当于你在 C 里写另一个
for循环,倒着把误差从最后一层传到第一层。 - 梯度更新:用算出来的梯度去修正 C 语言结构体里存着的权重数组。
当你用 TinyML 在单片机上推理时,只用到了第 1 步(前向传播),因为训练在 PC 上就做完了。现在你明白反向传播的底层计算逻辑,如果想继续了解 C 语言里如何用代码实现这个链式法则,我也可以接着为你解释。
这两个问题一个关乎优化理论的源头,一个关乎工程实现的数据流。核心结论是:梯度下降不是基于牛顿法,但牛顿法是其重要的改进方向;两个观测值时,工程上有两种更新模式。
1️⃣ 梯度下降与牛顿法的关系
梯度下降不是基于牛顿法,两者是独立提出的优化思想,但共享同一个数学根基——泰勒展开。
| 对比维度 | 梯度下降法 | 牛顿法 |
|---|---|---|
| 泰勒阶数 | 一阶展开:用线性平面拟合局部曲面 | 二阶展开:用抛物曲面拟合局部曲面 |
| 迭代公式 | θnew=θold−η∇L\theta_{new} = \theta_{old} - \eta \nabla Lθnew=θold−η∇L | θnew=θold−H−1∇L\theta_{new} = \theta_{old} - H^{-1} \nabla Lθnew=θold−H−1∇L |
| 步长控制 | 固定的学习率 η\etaη(人工调参) | 自动由曲率 H−1H^{-1}H−1 决定(自适应的最优步长) |
| 计算代价 | 低,只需计算一阶导数向量 | 高,需计算并求逆 Hessian 矩阵(O(n3)O(n^3)O(n3)) |
历史渊源:梯度下降由柯西在 1847 年提出,牛顿法更早(牛顿/拉夫逊,17 世纪),二者是独立发展的。直到 20 世纪优化理论成熟,才被统一到"基于泰勒展开的数值迭代"框架下。
2️⃣ 两个观测值时的更新方式
这涉及到批量梯度下降 (BGD)、随机梯度下降 (SGD) 和小批量梯度下降 (Mini-batch GD) 的核心区别。
假设两个观测值为 (x1,y1)(x_1, y_1)(x1,y1) 和 (x2,y2)(x_2, y_2)(x2,y2),总损失 L=12(L1+L2)L = \frac{1}{2}(L_1 + L_2)L=21(L1+L2)。
| 更新模式 | 操作方式 | 每轮参数更新次数 | 优缺点 |
|---|---|---|---|
| BGD (批量) | 计算两个观测值的平均梯度,一次性更新参数 | 1 次 | 梯度准确,但计算慢 |
| SGD (随机) | 先用第 1 个更新一次,再用第 2 个更新一次 | 2 次 | 计算快,但梯度震荡大 |
| Mini-batch (小批量) | (此处 batch=2)等价于 BGD | 1 次 | 最常用的折中方案 |
现代深度学习的做法(Mini-batch):
- 将 2 个观测值打包成一个 batch
- 计算 ∇L=12(∇L1+∇L2)\nabla L = \frac{1}{2}(\nabla L_1 + \nabla L_2)∇L=21(∇L1+∇L2)
- 执行一次参数更新:θ←θ−η∇L\theta \leftarrow \theta - \eta \nabla Lθ←θ−η∇L
为什么不用 SGD 逐个更新? 虽然理论上可以,但逐个更新无法利用 GPU 的并行计算能力,且梯度方向方差大,收敛不稳定。现代框架(PyTorch/TensorFlow)默认都是向量化运算,一次处理整个 batch。
💡 与你之前问题的关联
你之前问过 y=ax 的反向传播,当时用的是单个观测值 (x=2,ytrue=4)(x=2, y_{true}=4)(x=2,ytrue=4)。如果现在有两个观测值 (2,4)(2,4)(2,4) 和 (3,6)(3,6)(3,6):
- BGD:计算平均梯度 ∇a=12((a⋅2−4)⋅2+(a⋅3−6)⋅3)\nabla a = \frac{1}{2}((a\cdot2-4)\cdot2 + (a\cdot3-6)\cdot3)∇a=21((a⋅2−4)⋅2+(a⋅3−6)⋅3),更新一次
- SGD:先用 (2,4)(2,4)(2,4) 算梯度更新 aaa,再用 (3,6)(3,6)(3,6) 算梯度再更新一次
这也是为什么你以后看到训练代码里的 batch_size 参数时,能理解它就是在控制"多少个观测值一起算一次梯度"。
你的两个问题恰好对应了凸优化中“参数数量”与“求解难度”的核心区别。结论是:
- 一个参数时(y = wx),一组观测数据理论上就可以一步到位。
- 两个参数时(y = b * a * x),需要至少两组独立的观测数据才能确定唯一解,且训练难度会指数级上升。
1. 场景一:y = wx(单参数,单神经元)
对于模型 y=wxy = w xy=wx(已知 x=2,ytrue=4x=2, y_{true}=4x=2,ytrue=4):
- 数学本质:这是一个一元一次方程 2w=42w = 42w=4。
- 求解过程:
- 损失函数:L=12(w⋅2−4)2=2(w−2)2L = \frac{1}{2}(w \cdot 2 - 4)^2 = 2(w - 2)^2L=21(w⋅2−4)2=2(w−2)2
- 梯度:∂L∂w=4(w−2)\frac{\partial L}{\partial w} = 4(w - 2)∂w∂L=4(w−2)
- 结论:因为损失函数是一个开口向上的简单抛物线,无论初始值 www 是多少,只要学习率合适,梯度下降都能稳定、平滑地滑向唯一的最低点 w=2w=2w=2。一组数据足以确定唯一解,且收敛极快。
2. 场景二:y = a * b * x(双参数,复合神经元)
对于模型 y=a⋅b⋅xy = a \cdot b \cdot xy=a⋅b⋅x(已知 x=2,ytrue=4x=2, y_{true}=4x=2,ytrue=4):
- 数学本质:方程变为 2ab=42ab = 42ab=4,即 ab=2ab = 2ab=2。
- 核心问题:这是一个不定方程。满足条件的解有无穷多对,如 (a=1,b=2),(a=2,b=1),(a=4,b=0.5)(a=1, b=2), (a=2, b=1), (a=4, b=0.5)(a=1,b=2),(a=2,b=1),(a=4,b=0.5)。在三维空间中,损失函数的形状不再是简单的“碗”,而是一条狭长、弯曲的“山谷”。
- 梯度下降的表现:
- 陷入“山谷”:无论初始值在哪,梯度下降会很快让参数组合滑到 ab=2ab=2ab=2 这条“谷底”。
- 沿着“谷底”缓慢爬行:一旦到达谷底,梯度方向几乎与谷底垂直,指向谷底的分量非常小。此时参数更新效率极低,可能迭代成千上万次也走不到最优的 (a,b)(a,b)(a,b) 组合,甚至因学习率稍大而在山谷两侧剧烈震荡。
3. 如何解决双参数难题?
要解决这个问题,工程上有两条路:
-
方法一(数据侧):增加观测数据
当只有 ab=2ab=2ab=2 一个方程时,增加第二组数据 (x=3,y=12)(x=3, y=12)(x=3,y=12),就得到了第二个方程 3ab=12⇒ab=43ab = 12 \Rightarrow ab = 43ab=12⇒ab=4。这两个方程矛盾,意味着模型永远无法完美拟合所有数据。此时,损失函数变为:
L=12[(2ab−4)2+(3ab−12)2]L = \frac{1}{2}[(2ab - 4)^2 + (3ab - 12)^2]L=21[(2ab−4)2+(3ab−12)2]
这迫使梯度下降去寻找一个能平衡所有数据误差的最优解,从而打破“不定方程”的困境。 -
方法二(模型侧):拆分神经元
你描述的y = a * b * x实际上是一个乘法门,这会导致损失函数变得非凸,产生“山谷”地形。现代神经网络避免这种情况的做法是不要乘在一起,而是写成y = w1 * x + w2 * x。虽然数学表达式等价,但两个独立的神经元各自有独立的梯度通路,训练会稳定很多。
💎 总结
| 场景 | 方程状态 | 损失函数地形 | 收敛性 |
|---|---|---|---|
| 单参数 | 恰定方程(唯一解) | 标准抛物线 (凸) | 极快、极稳 |
| 双参数(乘性耦合) | 欠定方程(无穷解) | 弯曲狭谷 (非凸/病态) | 极慢、震荡 |
这也是为什么深度学习的损失函数表面看起来是"非凸"的,虽然实际训练中存在大量平坦区域和鞍点,但配合海量数据、特定的初始化策略和优化器,模型依然能找到不错的局部最优解。
这个问题很敏锐,问到了神经网络非线性的根源。
结论非常反直觉:一个都别用。 如果你真的想在神经网络里实现 y=ax+by=ax+by=ax+b,直接一个神经元都不用,或者用一个不带激活函数的 Dense 层即可。
1. 为什么说"零个神经元"?
y=ax+by = ax + by=ax+b 在数学上叫做仿射变换 (Affine Transformation)。在神经网络组件里,它对应的不是"一个完整的神经元",而是"一个不带激活函数的全连接层 (Dense Layer)"。
标准神经元公式:y=σ(Wx+b)y = \sigma(Wx + b)y=σ(Wx+b),由于你要的是纯 ax+bax+bax+b,σ\sigmaσ 必须去掉。
2. 如果你强行用神经元,会发生什么?
假设你听了我的建议,仍然手痒想用标准的"输入-权重-加偏置-过激活函数"这个结构来拟合 y=ax+by=ax+by=ax+b:
| 激活函数 σ\sigmaσ | 拟合 y=ax+by=ax+by=ax+b 的难度 | 原因 |
|---|---|---|
| Sigmoid / Tanh | 极难 / 完全不行 | 它们天生是弯曲的、饱和的。比如 Sigmoid 输出在 (0,1)(0,1)(0,1) 之间,如果你真实 y=10x+5y=10x+5y=10x+5,网络永远无法预测出 15 这个值。 |
| ReLU | 可以,但需要 2 个神经元 | ReLU 公式是 max(0,x)\max(0, x)max(0,x)。 1 个 ReLU 只能拟合斜率为正且截距为 0 的射线。 要拟合任意斜率和截距,需要 2 个 ReLU 叠加(一个管正半轴,一个管负半轴)。 |
| Linear (无激活) | 1 个神经元 (其实就是矩阵乘法) | 这就是你要的 y=ax+by=ax+by=ax+b。在代码里这就是 Dense(1, activation='linear')。 |
3. 深层网络的真相:两个神经元才能拟合一条完整的直线
如果你在用 ReLU 的深度网络里去学 y=ax+by=ax+by=ax+b,网络会在反向传播中自动演化出 2 个神经元 来搞定这件事:
- 神经元 A:负责 x>0x>0x>0 时的斜率 w1x+b1w_1 x + b_1w1x+b1
- 神经元 B:负责 x<0x<0x<0 时的斜率 w2x+b2w_2 x + b_2w2x+b2
数学证明(快速版):
一条任意直线 y=ax+by = ax + by=ax+b 可以表示为两个 ReLU 之差:
ax+b=max(0,kx+m)−max(0,−kx−m)+constax + b = \max(0, kx + m) - \max(0, -kx - m) + \text{const}ax+b=max(0,kx+m)−max(0,−kx−m)+const
4. 一个参数 vs 两个参数的本质区别
回到你关心的参数更新问题。y=ax+by=ax+by=ax+b 比 y=a⋅b⋅xy=a \cdot b \cdot xy=a⋅b⋅x 简单在哪里?
-
y=a⋅b⋅xy=a \cdot b \cdot xy=a⋅b⋅x:乘性耦合。损失函数是弯曲的狭谷,a 和 b 互相拖累。
-
y=ax+by=ax+by=ax+b:加性解耦。
- aaa 管斜率:∂L∂a=误差⋅x\frac{\partial L}{\partial a} = \text{误差} \cdot x∂a∂L=误差⋅x
- bbb 管高度:∂L∂b=误差\frac{\partial L}{\partial b} = \text{误差}∂b∂L=误差
两者各司其职,互不干扰。这是凸函数,无论多少数据,梯度下降都能稳定且快速地找到唯一的全局最优解。
💎 总结
- 数学上的 y=ax+by=ax+by=ax+b:对应代码里的
Dense(1, activation=None)。 - 神经网络里的"1个神经元":默认带激活函数(比如 ReLU),它其实画不出完整的直线,只能画出折线。
- 参数更新难度:ax+bax+bax+b 是入门级难度,无论多少数据都是凸优化;a⋅b⋅xa \cdot b \cdot xa⋅b⋅x 是地狱级难度。
更多推荐



所有评论(0)