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

数学上就是:

\[\theta \leftarrow \theta+\Delta\theta \]

这是一个非常好的源码阅读入口,因为你可以把:

param
grad
update

分别对应:

\[\theta,\quad \nabla J,\quad \Delta\theta \]

源码在这里把“梯度是什么”和“参数怎么更新”分开了。(GitHub)


二、第二优先级:SGDOptimizer

这是你真正应该精读的地方。

源码:

updates = [
    self.momentum * velocity - self.learning_rate * grad
    for velocity, grad in zip(self.velocities, grads)
]

(GitHub)

先不要急着看复杂代码,把它还原成数学:

普通梯度下降:

\[\theta_{t+1} = \theta_t-\eta g_t \]

其中:

\[g_t=\nabla J(\theta_t) \]

加入 Momentum 后,大致变成:

\[v_t=\mu v_{t-1}-\eta g_t \]

\[\theta_t=\theta_{t-1}+v_t \]

你会发现源码:

self.momentum * velocity

对应:

\[\mu v_{t-1} \]

而:

-self.learning_rate * grad

对应:

\[-\eta g_t \]

这就是非常典型的:

\[\boxed{\text{Gradient Descent}\rightarrow\text{Momentum}} \]


三、第三优先级: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

这是:

\[\eta_t= \frac{\eta_0}{(t+1)^{p}} \]

源码明确支持:

constant
invscaling
adaptive

(GitHub)

其中 adaptive 更值得注意:当训练效果长期没有改善时,学习率会除以 5;源码中可以直接看到:

self.learning_rate /= 5.0

(GitHub)

这非常适合你理解:

\[\boxed{\text{为什么学习率不能永远固定?}} \]


五、第五优先级:AdamOptimizer

这是你应该重点突破的第二个算法。

源码从这里开始:

class AdamOptimizer(BaseOptimizer):

(GitHub)

它维护两个状态:

self.ms = [...]
self.vs = [...]

也就是:

\[m_t \]

和:

\[v_t \]

源码明确写成 first moment vectorssecond moment vectors。(GitHub)

它更新:

\[m_t = \beta_1m_{t-1} + (1-\beta_1)g_t \]

以及:

\[v_t = \beta_2v_{t-1} + (1-\beta_2)g_t^2 \]

这就是 Adam 的核心数学思想。

你可以把它理解成:

SGD 只问“现在梯度是多少?”

而 Adam 同时在问:

“最近梯度大致往哪个方向?”
“这个方向过去有多剧烈?”

于是它根据历史梯度动态调整参数更新。

scikit-learn 1.6.1 的源码默认参数正是:

\[\beta_1=0.9,\qquad \beta_2=0.999,\qquad \epsilon=10^{-8} \]

并明确引用了 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)

就会马上知道:

\[m_t=\beta_1m_{t-1}+(1-\beta_1)g_t \]

源码就不再是“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

也就是说:

\[\boxed{ \text{反向传播产生梯度} \rightarrow \text{优化器利用梯度更新参数} } \]

这是理解 PyTorch 的关键。


八、第七优先级:MLP 的参数初始化

这个地方也值得你研究:

init_bound = np.sqrt(factor / (fan_in + fan_out))

(GitHub)

这里使用的是 Glorot/Xavier 初始化思想

也就是说你可以进一步学习:

\[\boxed{ \text{梯度下降} \rightarrow \text{参数初始化} \rightarrow \text{梯度传播} } \]

因为一个很现实的问题是:

梯度下降理论没问题,为什么网络还是训练不动?

答案就会涉及:

  • 权重初始化
  • 梯度消失
  • 梯度爆炸
  • 激活函数

这些都可以从这里继续向外扩展。


九、第八优先级: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)

所以你会看到:

\[\boxed{ \text{Batch GD} \rightarrow \text{SGD} \rightarrow \text{Mini-batch SGD} \rightarrow \text{Momentum} \rightarrow \text{Adam} } \]

这一整条线。


十、第九优先级:average=True——Averaged SGD

这个很有意思,而且很容易被忽略。

SGDClassifier 支持:

average=True

它不是简单地使用最后一次参数,而是对训练过程中的参数进行平均。官方文档把它称为 Averaged SGD / ASGD。(Scikit-learn)

这可以让你进一步理解:

\[\theta_1,\theta_2,\ldots,\theta_T \]

为什么平均:

\[\bar{\theta} = \frac1T\sum_{t=1}^{T}\theta_t \]

有时候比直接使用:

\[\theta_T \]

更加稳定。

这是从“梯度下降”进入“随机优化统计性质”的一个很好的入口。


十一、第十优先级: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)

这样你就会从:

\[\boxed{\text{一阶优化}} \]

走到:

\[\boxed{\text{二阶优化}} \]

也就是:

梯度
 ↓
∇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.pySGDOptimizer → AdamOptimizer 开始,我可以把它按源码顺序逐行拆解成“Python 语法 → NumPy 语法 → 数学公式 → 算法思想”四列对照。

posted @ 2026-09-11 06:49  立体风  阅读(9)  评论(0)    收藏  举报