1. 学习率策略概述

学习率,说穿了就是模型每次更新参数时迈的步子有多大。你想想看,我们训练深度学习模型,本质上是在一个高维曲面上找最低点。梯度告诉我们应该往哪个方向走,但走多快——这个步长就是学习率。

我刚开始接触分子生成的时候,也觉得学习率不就是个超参数嘛,调一调就行了。后来发现,这玩意儿远没那么简单。尤其是分子生成任务,数据分布复杂,模型又深又大,学习率稍微设错,整个训练就崩了。

学习率在深度学习中的作用

打个比方。你是站在山腰上,要下山。如果步子太大,你可能直接跨过山谷,跳到对面山坡上,甚至滚下山崖。如果步子太小,你又走得太慢,天黑都到不了山脚。学习率就是控制这个步长的旋钮。

在深度学习中,学习率直接影响三件事:

  • 收敛速度:学习率大,前期跑得快;但太大了,loss 震荡甚至发散。
  • 收敛质量:学习率小,能更精细地找到局部极小值;但太小了会陷入很差的局部点。
  • 泛化能力:学习率策略设计得好,模型的泛化性能往往更好。这是我在多个项目中观察到的。

为了让大家更直观地理解,我列了个对比表:

学习率取值 收敛表现 常见后果
过大(如 1.0) loss 剧烈震荡 不收敛,模型权重炸掉
适中(如 1e-3) loss 平滑下降 稳定收敛,但未必最优
过小(如 1e-6) loss 几乎不动 训练极慢,容易过拟合
动态调整 前期快速下降,后期精细微调 效果最好,但需要策略

核心结论:没有万能的学习率。不同阶段、不同任务,需要不同的步长。这就是为什么需要「学习率策略」。

为什么分子生成任务尤其依赖学习率策略

分子生成不是普通的分类或回归。它通常涉及两类模型:

  • 变分自编码器(VAE):把分子结构编码成隐向量,再解码生成新分子。
  • 生成对抗网络(GAN):生成器和判别器互相对抗,生成逼真的分子。
  • 扩散模型:最近很火,逐步加噪再逐步去噪生成分子。

这些模型的损失函数都比图像分类复杂得多。比如 VAE 就有重构损失 + KL 散度两项,两项的尺度可能差好几个数量级。学习率设不好,KL 项直接崩掉,模型只会生成均值——毫无意义。

我做过一个分子性质优化的项目,用条件 VAE 生成具有特定 logP 值的分子。一开始学习率固定 1e-3,KL 项权重调了好几版都收不住。后来改用 warmup 策略,前 5 个 epoch 从 0 线性增加到 1e-3,之后用余弦退火慢慢降。结果稳定多了,生成的分子结构多样性提升了 40%。

避坑提醒:分子生成模型的梯度尺度往往差异极大。我曾经在一个 GAN 驱动的分子生成任务中,生成器的梯度范数达到 1e4,判别器却只有 1e-2。如果共用同一学习率,生成器直接起飞。这时候需要分别调整学习率,或者用梯度裁剪。

还有一个重要原因:分子数据是离散的、稀疏的。SMILES 字符串、分子图、3D 坐标……每一种表示都有它的特殊性。模型很容易在早期就出现「模式坍缩」——只会生成几种常见的骨架。合适的学习率策略能帮模型在早期多探索,后期多利用。

常见的学习率策略一览

我个人习惯把学习率策略分成三类:

  1. 固定学习率:最简单,全程不变。适合小数据、浅模型,但分子生成基本不用。
  2. 分段衰减:每 N 个 epoch 乘一个系数(比如 0.1)。经典做法,但需要提前定好步点。
  3. 自适应调整:包括 warmup、余弦退火、ReduceLROnPlateau 等。是目前主流。

下面这张图展示了这几种策略的核心思想:

学习率策略家族 学习率策略 固定学习率 分段衰减 自适应调整 Warmup 余弦退火 Plateau 适用于 小模型 / 浅任务 分子生成几乎不用 因为太容易发散

你看,自适应调整下面又分了很多具体策略。这些在分子生成任务中都非常实用。比如说,warmup 可以让模型在初期用很小的学习率「热身」,避免突然大梯度把参数甩到奇怪的位置。余弦退火则让学习率先快后慢,模拟退火的思想。

实际中如何选取学习率

我记得第一次训练分子 VAE 时,连学习率该设多少心里都没底。后来我发现一个很实用的方法:先用一个很小的 batch,跑几个 step,观察 loss 的变化。如果 loss 在一开始就爆炸,说明学习率太大了;如果 loss 几乎没有变化,那可能就是太小。

更系统的做法是「学习率范围测试」:从 1e-6 到 1,指数增长,每步记录 loss。画出一条曲线,loss 下降最快的区域就是比较合适的初始学习率。我常用的代码片段如下:

import torch
from torch.optim.lr_scheduler import LambdaLR

def lr_range_test(model, train_loader, optimizer, criterion, start_lr=1e-6, end_lr=1.0, num_iter=100):
    # 获取训练数据的一个小批次
    data, target = next(iter(train_loader))
    # 指数增长步长
    lr_mult = (end_lr / start_lr) ** (1.0 / num_iter)
    optimizer.param_groups[0]['lr'] = start_lr
    for i in range(num_iter):
        optimizer.zero_grad()
        output = model(data)
        loss = criterion(output, target)
        loss.backward()
        optimizer.step()
        current_lr = optimizer.param_groups[0]['lr']
        print(f'Step {i:3d}, LR {current_lr:.2e}, Loss {loss.item():.4f}')
        # 更新学习率
        for group in optimizer.param_groups:
            group['lr'] *= lr_mult

实践经验:分子生成任务推荐初始学习率在 1e-4 ~ 3e-3 之间。我见过很多论文都在 1e-3 附近。但千万别照搬——一定要根据你的模型和数据做一次范围测试。

总结一下本章的要点

说白了,学习率策略是分子生成模型的「油门踏板」和「刹车系统」。踩重了翻车,踩轻了原地踏步。一个好的策略能:

  • 加快收敛速度,减少训练时间
  • 提高模型生成的分子多样性和有效性
  • 避免模式坍缩和梯度爆炸

在接下来的章节中,我们会深入每种具体策略的原理和代码实现。但今天先把概念吃透——理解学习率为什么重要,以及它在分子生成这种高维、稀疏、离散的任务中有多敏感。

嗯,这章就到这。你可以在自己的项目中试试看:随便跑个分子 VAE,先固定学习率到 1e-3,再改成 warmup + 余弦退火,看看两者的 loss 曲线和生成分子质量差了多少。实践出真知。