sklearn 源码关于梯度下降的学习
“梯度下降及其各种改进算法”,建议你把 Scikit-learn 当成一个可以阅读优化算法实现的教材。
对于确定的 scikit-learn 1.6.1,我建议按下面的顺序研究源码。
一、第一优先级:neural_network/_stochastic_optimizers.py
这是你目前最值得啃的一份源码。
GitHub:sklearn/neural_network/_stochastic_optimizers.py(1.6.1)
它只有大约 287 行,专门实现 MLP 的随机优化器,包含:
BaseOptimizer
│
├── SGDOptimizer
│
└── AdamOptimizer
源码中 BaseOptimizer.update_params() 明确体现了“参数 + 更新量”的结构;SGDOptimizer 保存 velocity,并支持 momentum、Nesterov、不同 learning-rate schedule。(GitHub)
1. 先看 BaseOptimizer
最核心的是:
updates = self._get_updates(grads)
for param, update in zip(params, updates):
param += update
数学上就是:
这是一个非常好的源码阅读入口,因为你可以把:
param
grad
update
分别对应:
源码在这里把“梯度是什么”和“参数怎么更新”分开了。(GitHub)
二、第二优先级:SGDOptimizer
这是你真正应该精读的地方。
源码:
updates = [
self.momentum * velocity - self.learning_rate * grad
for velocity, grad in zip(self.velocities, grads)
]
(GitHub)
先不要急着看复杂代码,把它还原成数学:
普通梯度下降:
其中:
加入 Momentum 后,大致变成:
你会发现源码:
self.momentum * velocity
对应:
而:
-self.learning_rate * grad
对应:
这就是非常典型的:
三、第三优先级:Nesterov Momentum
接下来研究:
if self.nesterov:
对应源码后面的第二次 update 计算。(GitHub)
你现在可以重点思考一个问题:
普通 Momentum 是“根据当前位置的梯度修正速度”,那么 Nesterov 为什么要先“看一眼即将到达的位置”再决定梯度?
这个问题搞懂后,你会真正理解:
SGD
↓
Momentum
↓
Nesterov Momentum
而不是仅仅记住三个名词。
四、第四优先级:Learning Rate Schedule
SGDOptimizer 里面这一部分也非常值得研究:
learning_rate = learning_rate_init / (time_step + 1) ** power_t
这是:
源码明确支持:
constant
invscaling
adaptive
(GitHub)
其中 adaptive 更值得注意:当训练效果长期没有改善时,学习率会除以 5;源码中可以直接看到:
self.learning_rate /= 5.0
(GitHub)
这非常适合你理解:
五、第五优先级:AdamOptimizer
这是你应该重点突破的第二个算法。
源码从这里开始:
class AdamOptimizer(BaseOptimizer):
(GitHub)
它维护两个状态:
self.ms = [...]
self.vs = [...]
也就是:
和:
源码明确写成 first moment vectors 和 second moment vectors。(GitHub)
它更新:
以及:
这就是 Adam 的核心数学思想。
你可以把它理解成:
SGD 只问“现在梯度是多少?”
而 Adam 同时在问:
“最近梯度大致往哪个方向?”
“这个方向过去有多剧烈?”
于是它根据历史梯度动态调整参数更新。
scikit-learn 1.6.1 的源码默认参数正是:
并明确引用了 Adam 原论文。(GitHub)
六、非常推荐:不要直接研究 Adam,先自己写一遍
你现在最适合做的是:
g = gradient
m = beta1 * m + (1 - beta1) * g
v = beta2 * v + (1 - beta2) * g ** 2
然后再研究源码。
这样你看到:
self.ms = [
self.beta_1 * m + (1 - self.beta_1) * grad
for m, grad in zip(self.ms, grads)
]
(GitHub)
就会马上知道:
源码就不再是“Python 技巧”,而是数学公式的程序化表达。
这非常符合你现在的学习方式。
七、第六优先级:neural_network/_multilayer_perceptron.py
前面这一份是“优化器怎么更新参数”。
这一份则是:
梯度到底是从哪里来的?
源码:
_multilayer_perceptron.py 1.6.1
这里非常值得看 _fit_stochastic()。
源码明确写了:
if self.solver == "sgd":
self._optimizer = SGDOptimizer(...)
elif self.solver == "adam":
self._optimizer = AdamOptimizer(...)
(GitHub)
这个地方非常重要,因为它把整个训练系统连接起来:
MLP
│
├── forward propagation
│
├── loss
│
├── backpropagation
│ ↓
│ grads
│
└── optimizer
├── SGD
└── Adam
也就是说:
这是理解 PyTorch 的关键。
八、第七优先级:MLP 的参数初始化
这个地方也值得你研究:
init_bound = np.sqrt(factor / (fan_in + fan_out))
(GitHub)
这里使用的是 Glorot/Xavier 初始化思想。
也就是说你可以进一步学习:
因为一个很现实的问题是:
梯度下降理论没问题,为什么网络还是训练不动?
答案就会涉及:
- 权重初始化
- 梯度消失
- 梯度爆炸
- 激活函数
这些都可以从这里继续向外扩展。
九、第八优先级:SGDRegressor / SGDClassifier
这是我非常推荐你研究的另一个入口:
sklearn/linear_model/_stochastic_gradient.py(1.6.1)
为什么?
因为这里比 MLP 简单。
你可以先研究:
线性模型
+
SGD
然后再进入:
MLP
+
SGD
这是非常好的学习阶梯。
官方文档还明确说,SGDClassifier 实现的是逐样本估计梯度并更新模型,同时支持学习率 schedule、mini-batch/online learning、averaged SGD 等。(Scikit-learn)
所以你会看到:
这一整条线。
十、第九优先级:average=True——Averaged SGD
这个很有意思,而且很容易被忽略。
SGDClassifier 支持:
average=True
它不是简单地使用最后一次参数,而是对训练过程中的参数进行平均。官方文档把它称为 Averaged SGD / ASGD。(Scikit-learn)
这可以让你进一步理解:
为什么平均:
有时候比直接使用:
更加稳定。
这是从“梯度下降”进入“随机优化统计性质”的一个很好的入口。
十一、第十优先级:LogisticRegression 的各种 Solver
如果你想真正系统地研究“优化算法家族”,那么:
LogisticRegression
是绝佳教材。
因为它的 solver 包含:
lbfgs
newton-cg
newton-cholesky
sag
saga
liblinear
源码:
sklearn/linear_model/_logistic.py(1.6.1)
例如源码中明确把:
lbfgs
newton-cg
等 solver 和不同的 loss / gradient / Hessian 计算连接起来。(GitHub)
这样你就会从:
走到:
也就是:
梯度
↓
∇J
进一步进入:
Hessian
↓
∇²J
十二、然后研究 L-BFGS——这一步很有价值
MLP 本身也支持:
solver="lbfgs"
而 _multilayer_perceptron.py 会调用:
scipy.optimize.minimize(
...,
method="L-BFGS-B",
jac=True,
...
)
(GitHub)
所以源码实际上给你展示了一条非常清晰的路线:
一阶方法
│
├── Gradient Descent
├── SGD
├── Momentum
├── Nesterov
└── Adam
二阶/拟二阶方法
│
├── Newton
├── BFGS
└── L-BFGS
而官方 MLP 文档也明确说明,L-BFGS 通过近似 Hessian 以及其逆来进行参数更新。(GitHub)
十三、所以我建议你不要“通读 Scikit-learn 源码”
而是按照这条路线:
┌── Gradient Descent
│
├── SGD
│
├── Momentum
│
├── Nesterov
│
├── Adam
│
Optimization ─────┤
├── ASGD
│
├── SAG
│
├── SAGA
│
└── L-BFGS
对应源码:
① neural_network/_stochastic_optimizers.py
★★★★★
SGD / Momentum / Nesterov / Adam
② neural_network/_multilayer_perceptron.py
★★★★★
forward / backprop / optimizer连接
③ linear_model/_stochastic_gradient.py
★★★★★
SGDClassifier / SGDRegressor / ASGD
④ linear_model/_logistic.py
★★★★☆
SAG / SAGA / L-BFGS / Newton
⑤ scipy.optimize
★★★★☆
L-BFGS、优化器基础设施
对你目前阶段,最重要的其实是这一条“源码学习链”
数学
│
├── 导数
├── 偏导
├── 梯度
└── Hessian
↓
NumPy
↓
手写 Gradient Descent
↓
手写 SGD
↓
手写 Momentum
↓
手写 Adam
↓
阅读 sklearn/_stochastic_optimizers.py
↓
阅读 MLP backprop
↓
PyTorch autograd
↓
PyTorch Optimizer
这样学,你会发现一个特别有意思的事实:
Scikit-learn 的 MLP 优化器源码其实只有几百行,但背后连着你正在学习的微积分、向量、梯度、链式法则、动量、指数移动平均、自适应学习率和数值优化。
而且这也是为什么我认为你目前选 scikit-learn 1.6.1 很合适:研究这份固定版本的源码时,可以直接对照 1.6.1 的具体实现,而不是跟着最新版源码不断变化。
下一步最适合直接从 _stochastic_optimizers.py 的 SGDOptimizer → AdamOptimizer 开始,我可以把它按源码顺序逐行拆解成“Python 语法 → NumPy 语法 → 数学公式 → 算法思想”四列对照。

浙公网安备 33010602011771号