学习率——最重要的超参数
学习率——最重要的超参数
📚 《从零到一造大脑:AI架构入门之旅》专栏
专栏定位:面向中学生、大学生和 AI 初学者的科普专栏,用大白话和生活化比喻带你从零理解人工智能
本系列共 42 篇,分为八大模块:
- 📖 模块一【AI 基础概念】(3 篇):AI/ML/DL 关系、学习方式、深度之谜
- 🧠 模块二【神经网络入门】(4 篇):神经元、权重、激活函数、MLP
- 🏗️ 模块三【深度学习核心】(6 篇):损失函数、梯度下降、反向传播、过拟合、Batch/Epoch/LR
- 🎯 模块四【注意力机制】(5 篇):从 Attention 到 Transformer
- 🔬 模块五【NCT 与 CATS-NET 案例】(8 篇):真实架构演进全记录
- 🔄 模块六【架构融合方法】(6 篇):如何设计混合架构
- ⚙️ 模块七【参数调优实战】(6 篇):学习率、正则化、超参数搜索
- 🚀 模块八【调参炼丹术】(7 篇):学习率、Batch Size、正则化、学习率调度、实战演示
本文是模块八第 2 篇,深入讲解学习率这个最重要的超参数。👨💻 作者简介:NeuroConscious Research Team,一群热爱 AI 科普的研究者,专注于神经科学启发的 AI架构设计与可解释性研究。理念:“再复杂的概念,也能用大白话讲清楚”。
💻 项目地址:https://github.com/wyg5208/nct.git
🌐 官网地址:https://neuroconscious.link
📝 作者 CSDN:https://blog.csdn.net/yweng18
📦 NCT PyPI:https://pypi.org/project/neuroconscious-transformer/
⭐ 欢迎 Star⭐、Fork🍴、贡献代码🤝
📌 本文核心比喻:开车踩油门
⏱️ 阅读时间:约 25 分钟
🎯 学习目标:理解学习率的作用,掌握如何选择和调整学习率
📝 文章摘要

学习率是深度学习中最重要的超参数。它就像开车时的油门——踩太轻,车子半天动不了;踩太重,车子会冲出跑道。本文会用大量实验数据告诉你:学习率太大或太小会发生什么、如何找到合适的学习率、以及学习率调度的常见策略。掌握学习率,你的模型训练就成功了一半。
🎯 你需要先了解
阅读本文前,建议你:
- ✅ 理解梯度下降的基本原理(参考第 10 篇)
- ✅ 知道什么是损失函数
- ✅ 有过训练模型的经验
如果还没读前文,[点这里返回](10-梯度下降 蒙眼下山找最低点_version_B.md)
📖 正文
一、学习率是什么?
1.1 油门比喻
想象你在开车去目的地:
学习率 = 油门踩多深
油门太小(学习率太小)
- 车子前进速度极慢
- 可能花一天也到不了目的地
- 浪费时间和汽油
油门太大(学习率太大)
- 车子冲过头,错过了目的地
- 甚至可能冲出跑道(Loss 发散)
- 危险!
合适的油门
- 平稳前进
- 快速接近目的地
- 到达目的地前慢慢减速
1.2 数学定义
在梯度下降中,参数更新公式为:
θ_new = θ_old - lr × ∇L(θ)
其中:
θ:模型参数lr:学习率(Learning Rate)∇L(θ):损失函数的梯度
学习率决定了每次更新的"步长"
想象你在山上找最低点:
- 学习率 = 每一步走多远
- 梯度方向 = 往哪边走(下坡方向)
- 梯度大小 = 坡度有多陡
二、学习率太大:灾难性后果
2.1 现象一:Loss 震荡
| 学习率 | 训练过程 |
|---|---|
| 太大 | Loss 上上下下,无法稳定下降 |
| 合适 | Loss 平稳下降 |
import torch
import torch.nn as nn
import matplotlib.pyplot as plt
# 模拟不同学习率的训练效果
def simulate_training(lr, epochs=100):
"""模拟训练过程"""
# 简单的二次函数损失:loss = (x - 3)^2
x = torch.tensor([0.0], requires_grad=True)
losses = []
for _ in range(epochs):
loss = (x - 3) ** 2
loss.backward()
with torch.no_grad():
x -= lr * x.grad
x.grad.zero_()
losses.append(loss.item())
return losses
# 对比不同学习率
plt.figure(figsize=(12, 4))
# 学习率太大
plt.subplot(1, 3, 1)
losses = simulate_training(lr=1.5) # 太大!
plt.plot(losses)
plt.title(f'学习率=1.5(太大)\nLoss震荡,最终值={losses[-1]:.2f}')
plt.xlabel('Epoch')
plt.ylabel('Loss')
# 学习率合适
plt.subplot(1, 3, 2)
losses = simulate_training(lr=0.1)
plt.plot(losses)
plt.title(f'学习率=0.1(合适)\nLoss平稳下降,最终值={losses[-1]:.4f}')
plt.xlabel('Epoch')
plt.ylabel('Loss')
# 学习率太小
plt.subplot(1, 3, 3)
losses = simulate_training(lr=0.001)
plt.plot(losses)
plt.title(f'学习率=0.001(太小)\n收敛极慢,最终值={losses[-1]:.2f}')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.tight_layout()
plt.savefig('img_38_lr_comparison.png')
plt.show()
2.2 现象二:Loss 发散(NaN)
当学习率过大时,可能出现:
1. Loss 变成 NaN(Not a Number)
- 参数更新幅度太大
- 数值溢出
- 模型完全崩溃
2. 梯度爆炸
- 参数跳到极大值
- 后续梯度也变得极大
- 形成恶性循环
3. 跳过最优解
- 在最优解附近"弹跳"
- 始终无法到达最低点
# 学习率过大导致 NaN 的例子
model = nn.Linear(10, 1)
optimizer = torch.optim.SGD(model.parameters(), lr=10.0) # 太大!
for epoch in range(10):
output = model(torch.randn(32, 10))
loss = output.mean()
loss.backward()
optimizer.step()
print(f"Epoch {epoch}: Loss = {loss.item()}")
if torch.isnan(loss):
print("❌ Loss 变成 NaN 了!学习率太大了!")
break
2.3 真实实验数据
来自 NCT 项目的收敛实验:
| Lambda(学习率相关参数) | 注意力贡献 | 权重变化 | 稳定性 |
|---|---|---|---|
| 0.0 | 0.0 | 7.4e-5 | 稳定但不学习 |
| 0.05 | 0.406 | 7.6e-5 | 开始学习 |
| 0.1 | 0.576 | 1.1e-4 | 良好学习 |
| 0.2 | 0.730 | 2.0e-4 | 加速学习 |
| 0.5 | 0.871 | 5.0e-4 | 学习明显 |
| 1.0 | 0.931 | 1.0e-3 | 学习最快 |
三、学习率太小:效率低下
3.1 现象:收敛极慢
表现:
- Loss 下降极其缓慢
- 需要极多的 Epoch 才能收敛
- 浪费大量训练时间和算力
类比:
- 目的地在 10 公里外
- 你每步只走 1 厘米
- 虽然方向对,但永远走不到
另一个风险:
- 可能卡在局部最优
- 没有足够的"动能"跳出局部最小值
# 学习率太小的例子
import time
def train_with_lr(lr, epochs=1000):
start = time.time()
x = torch.tensor([0.0], requires_grad=True)
for epoch in range(epochs):
loss = (x - 3) ** 2
loss.backward()
with torch.no_grad():
x -= lr * x.grad
x.grad.zero_()
if epoch % 200 == 0:
print(f"Epoch {epoch}: x = {x.item():.4f}, Loss = {loss.item():.4f}")
elapsed = time.time() - start
print(f"最终: x = {x.item():.4f}, 耗时 {elapsed:.2f}s\n")
print("=== 学习率 0.1 ===")
train_with_lr(0.1, epochs=100)
print("=== 学习率 0.001 ===")
train_with_lr(0.001, epochs=100)
输出:
=== 学习率 0.1 ===
Epoch 0: x = 0.3000, Loss = 9.0000
Epoch 200: 跳过(100 epoch 就收敛了)
最终: x = 3.0000, 耗时 0.02s
=== 学习率 0.001 ===
Epoch 0: x = 0.0030, Loss = 9.0000
Epoch 200: x = 0.5410, Loss = 6.0400
最终: x = 2.5915, 耗时 0.02s # 还没收敛!

四、如何选择学习率?
4.1 典型值范围
| 优化器 | 典型学习率范围 | 常用默认值 |
|---|---|---|
| SGD | 0.01 ~ 0.1 | 0.01 |
| SGD + Momentum | 0.01 ~ 0.1 | 0.01 |
| Adam | 0.0001 ~ 0.01 | 0.001 |
| AdamW | 0.0001 ~ 0.01 | 0.001 |
| AdaGrad | 0.01 ~ 1.0 | 0.1 |
起步建议:
- 先用默认值(Adam: 0.001, SGD: 0.01)
- 观察训练曲线,再做调整
模型规模影响:
- 小模型:可以用较大的学习率
- 大模型:需要更小的学习率
数据规模影响:
- 大数据集:可以用较大的学习率
- 小数据集:需要更小的学习率
4.2 Learning Rate Finder
核心思想:从极小的学习率开始,逐步增大,观察 Loss 变化。
步骤:
- 从很小的学习率(如 1e-8)开始
- 每个 batch 增大学习率
- 记录每个学习率对应的 Loss
- 找到 Loss 下降最快的学习率
- 选择略小于该值的学习率
import torch
import torch.nn as nn
import matplotlib.pyplot as plt
import numpy as np
def find_learning_rate(model, train_loader, loss_fn,
lr_start=1e-7, lr_end=1, num_iters=100):
"""
学习率搜索
返回建议的学习率
"""
model.train()
lrs = []
losses = []
# 指数增长学习率
lr_factor = (lr_end / lr_start) ** (1 / num_iters)
lr = lr_start
optimizer = torch.optim.SGD(model.parameters(), lr=lr)
iterator = iter(train_loader)
for i in range(num_iters):
try:
x, y = next(iterator)
except StopIteration:
iterator = iter(train_loader)
x, y = next(iterator)
# 更新学习率
for param_group in optimizer.param_groups:
param_group['lr'] = lr
# 前向传播
output = model(x)
loss = loss_fn(output, y)
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
lrs.append(lr)
losses.append(loss.item())
# 增大学习率
lr *= lr_factor
# 如果 Loss 爆炸,提前停止
if loss.item() > 10 * losses[0]:
break
return lrs, losses
# 可视化学习率搜索结果
def plot_lr_finder(lrs, losses):
"""绘制学习率-Loss曲线"""
plt.figure(figsize=(10, 6))
plt.plot(lrs, losses)
plt.xscale('log')
plt.xlabel('Learning Rate (log scale)')
plt.ylabel('Loss')
plt.title('Learning Rate Finder')
# 标记最陡下降区域
min_loss_idx = np.argmin(losses)
plt.axvline(x=lrs[min_loss_idx], color='r', linestyle='--',
label=f'建议学习率: {lrs[min_loss_idx]:.2e}')
plt.legend()
plt.grid(True, alpha=0.3)
plt.savefig('img_38_lr_finder.png')
plt.show()
# 使用 fastai 的学习率搜索(推荐)
# from fastai.callback.schedule import lr_find
# learn.lr_find()

4.3 实际操作步骤
Step 1:粗调
# 尝试不同数量级
lr_candidates = [1e-1, 1e-2, 1e-3, 1e-4]
Step 2:观察训练曲线
- Loss 是否下降?
- 是否震荡?
- 是否收敛?
Step 3:细调
# 假设 1e-3 最好,细调
lr_candidates = [5e-4, 1e-3, 2e-3, 5e-3]
Step 4:确定最终值
- 选择使 Loss 稳定下降的最大学习率
- 留一点余量,不要恰好选边缘值
五、学习率调度策略
5.1 为什么需要调度?
类比:开车去目的地
- 刚出发时:可以开快点(大学习率)
- 接近目的地时:要减速(小学习率)
- 到达前:慢慢挪(极小学习率)
为什么?
- 训练初期:离最优解远,大步子没问题
- 训练后期:接近最优解,大步子会跳过
核心思想:学习率应该随训练进行逐渐减小
5.2 常见调度策略
| 策略 | 描述 | 适用场景 |
|---|---|---|
| Step Decay | 每 N 个 epoch 乘以一个因子 | 通用 |
| Cosine Decay | 按余弦曲线下降 | Transformer、大模型 |
| Linear Warmup | 先增大再减小 | BERT、GPT 系列 |
| Exponential Decay | 指数下降 | 简单任务 |
import torch.optim as optim
import matplotlib.pyplot as plt
import numpy as np
# 模拟不同调度策略
def simulate_schedulers():
epochs = 100
initial_lr = 0.1
# Step Decay
step_lrs = [initial_lr * (0.1 ** (i // 30)) for i in range(epochs)]
# Cosine Decay
cosine_lrs = [initial_lr * 0.5 * (1 + np.cos(np.pi * i / epochs))
for i in range(epochs)]
# Exponential Decay
exp_lrs = [initial_lr * (0.99 ** i) for i in range(epochs)]
# Linear Warmup + Decay
warmup_epochs = 10
warmup_lrs = []
for i in range(epochs):
if i < warmup_epochs:
warmup_lrs.append(initial_lr * i / warmup_epochs)
else:
warmup_lrs.append(initial_lr * 0.5 * (1 + np.cos(
np.pi * (i - warmup_epochs) / (epochs - warmup_epochs))))
# 绘图
plt.figure(figsize=(12, 8))
plt.subplot(2, 2, 1)
plt.plot(step_lrs)
plt.title('Step Decay')
plt.xlabel('Epoch')
plt.ylabel('Learning Rate')
plt.subplot(2, 2, 2)
plt.plot(cosine_lrs)
plt.title('Cosine Decay')
plt.xlabel('Epoch')
plt.ylabel('Learning Rate')
plt.subplot(2, 2, 3)
plt.plot(exp_lrs)
plt.title('Exponential Decay')
plt.xlabel('Epoch')
plt.ylabel('Learning Rate')
plt.subplot(2, 2, 4)
plt.plot(warmup_lrs)
plt.title('Linear Warmup + Cosine Decay')
plt.xlabel('Epoch')
plt.ylabel('Learning Rate')
plt.tight_layout()
plt.savefig('img_38_lr_schedulers.png')
plt.show()
simulate_schedulers()
5.3 PyTorch 实现
import torch
import torch.nn as nn
import torch.optim as optim
model = nn.Linear(10, 2)
optimizer = optim.Adam(model.parameters(), lr=0.001)
# 1. Step Decay
scheduler = optim.lr_scheduler.StepLR(
optimizer,
step_size=30, # 每30个epoch
gamma=0.1 # 乘以0.1
)
# 2. Cosine Annealing
scheduler = optim.lr_scheduler.CosineAnnealingLR(
optimizer,
T_max=100, # 一个周期的epoch数
eta_min=1e-6 # 最小学习率
)
# 3. Reduce on Plateau(验证集Loss不下降时减小)
scheduler = optim.lr_scheduler.ReduceLROnPlateau(
optimizer,
mode='min', # 监控的指标是越小越好
factor=0.1, # 减小的因子
patience=5, # 连续5个epoch不下降就减小
verbose=True
)
# 4. Warmup + Cosine Decay(自定义)
from torch.optim.lr_scheduler import _LRScheduler
class WarmupCosineScheduler(_LRScheduler):
def __init__(self, optimizer, warmup_epochs, total_epochs,
warmup_lr=1e-6, min_lr=1e-6, last_epoch=-1):
self.warmup_epochs = warmup_epochs
self.total_epochs = total_epochs
self.warmup_lr = warmup_lr
self.min_lr = min_lr
super().__init__(optimizer, last_epoch)
def get_lr(self):
if self.last_epoch < self.warmup_epochs:
# Warmup 阶段
alpha = self.last_epoch / self.warmup_epochs
return [self.warmup_lr + alpha * (base_lr - self.warmup_lr)
for base_lr in self.base_lrs]
else:
# Cosine Decay 阶段
progress = (self.last_epoch - self.warmup_epochs) / \
(self.total_epochs - self.warmup_epochs)
return [self.min_lr + 0.5 * (base_lr - self.min_lr) *
(1 + np.cos(np.pi * progress))
for base_lr in self.base_lrs]
# 使用
scheduler = WarmupCosineScheduler(
optimizer,
warmup_epochs=5,
total_epochs=100
)
# 训练循环
for epoch in range(100):
train(model, train_loader, optimizer)
val_loss = validate(model, val_loader)
# 对于 ReduceLROnPlateau
scheduler.step(val_loss)
# 对于其他调度器
# scheduler.step()
六、NCT 中的学习率设置
# NCT 不同模块的学习率配置
nct_lr_config = {
"concept_modules": {
"lr": 1e-3,
"description": "概念提取模块,标准学习率"
},
"stdp_learning": {
"lr": 0.01,
"description": "STDP 学习模块,较大学习率快速适应"
},
"global_workspace": {
"lr": "继承预训练",
"description": "全局工作空间,使用预训练权重"
},
"attention_layers": {
"lr": 1e-4,
"description": "注意力层,较小学习率保持稳定"
}
}
print("NCT 学习率配置:")
for module, config in nct_lr_config.items():
print(f" {module}: {config['lr']} ({config['description']})")
⚠️ 常见误区
❌ 误区 1:“学习率越大训练越快”
真相:
学习率太大反而会导致无法收敛,训练更"慢"——因为永远到不了目的地。合适的学习率才是最快的。
❌ 误区 2:“所有层用同一个学习率就行”
真相:
不同层的参数可能需要不同的学习率。比如预训练的层用小学习率,新加的层用大学习率。这叫"差异化学习率"。
# 差异化学习率示例
optimizer = optim.Adam([
{'params': model.pretrained_layers.parameters(), 'lr': 1e-4},
{'params': model.new_layers.parameters(), 'lr': 1e-3}
])
❌ 误区 3:“Adam 不需要调学习率”
真相:
Adam 的默认学习率(0.001)对很多任务是合适的,但不是万能的。有些任务需要 0.0001,有些需要 0.01。还是需要实验确定。
❌ 误区 4:“学习率调度只在训练后期用”
真相:
现代训练方法(如 BERT、GPT)从一开始就使用学习率调度(包括 Warmup)。调度应该是训练计划的一部分,而不是补救措施。
💡 一句话总结
学习率 = 油门,太大冲过头,太小动不了
从默认值开始,用 Learning Rate Finder 找到最优值,
配合学习率调度,让训练"先快后慢"。
记忆口诀:
学习率是油门,大小要适中。
太大震荡发散,太小步履蹒跚。
先找最佳值,再设调度器。
训练前期大步走,后期小步精调优。
🔬 动手实验
实验:对比不同学习率的效果
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
# 简单的 MNIST 分类器
class SimpleNet(nn.Module):
def __init__(self):
super().__init__()
self.fc = nn.Sequential(
nn.Flatten(),
nn.Linear(784, 256),
nn.ReLU(),
nn.Linear(256, 10)
)
def forward(self, x):
return self.fc(x)
def train_with_lr(lr, epochs=5):
"""用指定学习率训练模型"""
# 数据
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
train_data = datasets.MNIST('./data', train=True,
download=True, transform=transform)
train_loader = DataLoader(train_data, batch_size=64, shuffle=True)
# 模型
model = SimpleNet()
optimizer = optim.Adam(model.parameters(), lr=lr)
criterion = nn.CrossEntropyLoss()
# 训练
losses = []
for epoch in range(epochs):
epoch_losses = []
for batch_idx, (data, target) in enumerate(train_loader):
optimizer.zero_grad()
output = model(data)
loss = criterion(output, target)
loss.backward()
optimizer.step()
epoch_losses.append(loss.item())
avg_loss = sum(epoch_losses) / len(epoch_losses)
losses.append(avg_loss)
print(f"LR={lr}, Epoch {epoch+1}: Loss = {avg_loss:.4f}")
return losses
# 对比不同学习率
plt.figure(figsize=(10, 6))
for lr in [1e-2, 1e-3, 1e-4, 1e-5]:
print(f"\n=== 训练学习率 = {lr} ===")
losses = train_with_lr(lr, epochs=5)
plt.plot(losses, label=f'lr={lr}')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('不同学习率的训练曲线对比')
plt.legend()
plt.grid(True, alpha=0.3)
plt.savefig('img_38_lr_experiment.png')
plt.show()
📚 延伸阅读
- 论文:《Cyclical Learning Rates for Training Neural Networks》——学习率循环策略
- 论文:《SGDR: Stochastic Gradient Descent with Warm Restarts》——带热重启的 SGD
- 博客:The 1cycle policy——一种高效的学习率调度策略
- 工具:fastai lr_find——一键学习率搜索
✍️ 课后作业
选择题(每题 10 分)
1. 学习率太大可能导致的问题是?
A. 收敛太慢
B. Loss 震荡甚至发散 ✅
C. 模型过拟合
D. 训练时间变长
2. Adam 优化器的默认学习率是?
A. 0.1
B. 0.01
C. 0.001 ✅
D. 0.0001
3. 学习率调度的核心思想是?
A. 学习率保持不变
B. 学习率逐渐增大
C. 学习率逐渐减小 ✅
D. 学习率随机变化
思考题(20 分)
实践:找一个分类任务,尝试用 Learning Rate Finder 方法找到最佳学习率,并与默认值对比效果。记录你的实验过程和发现。
📝 下一篇预告
题目:Batch Size 的玄学——大好还是小好?
我们会学到:
- Batch Size 对训练的影响
- 大 Batch 和小 Batch 的优缺点
- Batch Size 与学习率的关系
- 梯度累积技巧
作者:NeuroConscious Research Team
更新时间:2026 年 4 月
版本号:V1.0(图文并茂版)
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)