tsai 中 Learner 机制深度学习笔记
目录
1. 问题动机与直觉理解
1.1 从原始训练循环说起
想象你刚开始学习深度学习,写下了第一个训练循环:
python
# 原始训练代码 - 混乱且重复
import torch
import torch.nn as nn
from torch.utils.data import DataLoader
model = MyTimeSeriesModel()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
criterion = nn.CrossEntropyLoss()
train_loader = DataLoader(train_dataset, batch_size=32)
valid_loader = DataLoader(valid_dataset, batch_size=32)
# 训练循环
best_loss = float('inf')
for epoch in range(100):
# 训练
model.train()
train_loss = 0
for batch_idx, (data, target) in enumerate(train_loader):
data, target = data.cuda(), target.cuda()
optimizer.zero_grad()
output = model(data)
loss = criterion(output, target)
loss.backward()
optimizer.step()
train_loss += loss.item()
# 验证
model.eval()
valid_loss = 0
correct = 0
with torch.no_grad():
for data, target in valid_loader:
data, target = data.cuda(), target.cuda()
output = model(data)
loss = criterion(output, target)
valid_loss += loss.item()
pred = output.argmax(dim=1)
correct += pred.eq(target).sum().item()
# 保存最佳模型
if valid_loss < best_loss:
best_loss = valid_loss
torch.save(model.state_dict(), 'best_model.pth')
# 打印日志
print(f'Epoch {epoch}: Train Loss: {train_loss:.4f}, '
f'Valid Loss: {valid_loss:.4f}, Acc: {correct/len(valid_loader.dataset):.4f}')
这段代码有很多问题:
- 高度重复:每个项目都要重写这100行代码
- 容易出错 :忘记调用
model.train()或optimizer.zero_grad() - 难以扩展:想加入学习率调度、梯度裁剪、混合精度训练?代码会更乱
- 缺乏标准化:不同人写的代码风格迥异,难以复用
- 调试困难:出问题时要排查大量代码
1.2 Learner的诞生:化繁为简
Learner的核心使命就是:把上面100行重复、易错的代码压缩成几行。
使用tsai的Learner,上述代码可以简化为:
python
from tsai.all import *
# 1. 准备数据
X, y, splits = get_UCR_data('NATOPS', split_data=False)
tfms = [None, TSClassification()]
dls = get_ts_dls(X, y, tfms=tfms, splits=splits, batch_tfms=TSStandardize())
# 2. 创建Learner(集成了模型、优化器、损失函数)
learn = ts_learner(dls, InceptionTime, metrics=accuracy)
# 3. 训练(自动处理训练/验证、日志、保存等)
learn.fit_one_cycle(100, 1e-3)
# 完成!模型已训练好,自动保存最佳版本
从100行到5行,这就是Learner的魔力。
1.3 真实应用场景
场景1:时间序列分类
你有传感器数据,需要分类不同的人体动作:
python
from tsai.all import *
# 加载NATOPS人体动作识别数据
X, y, splits = get_UCR_data('NATOPS', split_data=False)
# X: [360 samples x 24 features x 51 timesteps]
# y: 6类动作标签
# 创建分类器 - Learner自动处理一切
clf = TSClassifier(X, y, splits=splits,
arch='InceptionTimePlus',
metrics=accuracy)
# 训练 - 一行代码
clf.fit_one_cycle(50, 3e-4)
# 导出模型
clf.export('natops_classifier.pkl')
场景2:时间序列预测
预测未来的太阳黑子数量:
python
from tsai.all import *
# 获取时间序列数据
ts = get_forecasting_time_series("Sunspots").values
# 滑动窗口:用60个时间步预测未来1步
X, y = SlidingWindow(60, horizon=1)(ts)
# 创建预测器
fcst = TSForecaster(X, y,
arch="TSTPlus",
metrics=mae)
# 训练
fcst.fit_one_cycle(50, 1e-3)
场景3:迁移学习
在ImageNet预训练的模型上微调时间序列任务:
python
# 加载预训练模型
learn = ts_learner(dls, ResNet, pretrained=True)
# 冻结前面的层
learn.freeze()
# 只训练最后几层
learn.fit_one_cycle(10, 1e-3)
# 解冻并微调全部参数
learn.unfreeze()
learn.fit_one_cycle(30, 1e-4)
1.4 为什么"非它不可"?
Learner不只是"语法糖",它提供了不可或缺的价值:
价值1:经过验证的最佳实践
Learner内置了fastai社区多年积累的训练技巧:
- 1cycle学习率调度:比固定学习率快3-10倍
- 判别式学习率:不同层使用不同学习率
- 混合精度训练:自动利用GPU的Tensor Core
- 梯度累积:小显存也能用大batch size
价值2:统一的接口
无论是分类、回归还是预测,接口都一致:
python
# 分类
clf = TSClassifier(X, y, ...)
clf.fit_one_cycle(50, 1e-3)
# 回归
reg = TSRegressor(X, y, ...)
reg.fit_one_cycle(50, 1e-3)
# 预测
fcst = TSForecaster(X, y, ...)
fcst.fit_one_cycle(50, 1e-3)
# 接口完全相同!
价值3:可组合的扩展性
通过Callback机制,Learner可以轻松扩展:
python
learn = ts_learner(dls, model,
cbs=[
SaveModel(monitor='valid_loss'), # 自动保存最佳模型
EarlyStoppingCallback(patience=10), # 早停
ReduceLROnPlateau(patience=5), # 学习率衰减
MixUp(alpha=0.4), # 数据增强
GradientClip(max_norm=1.0) # 梯度裁剪
]
)
价值4:实验追踪与可复现性
python
# 自动记录超参数
learn.fit_one_cycle(50, 1e-3)
# 可视化训练过程
learn.plot_metrics()
# 导出完整的训练配置
learn.export('model.pkl') # 包含模型、数据配置、训练历史
# 完全复现
learn = load_learner('model.pkl')
learn.predict(new_data)
2. 核心思想与概念拆解
2.1 Learner的本质
Learner本质上是一个"训练协调者"(Training Orchestrator),它:
- 持有训练所需的所有组件
- 协调这些组件的交互
- 自动化训练循环的复杂逻辑
用类比来理解:
Learner就像交响乐团的指挥:
- 模型是乐器
- 数据是乐谱
- 优化器是演奏技巧
- Callback是特殊效果(渐强、渐弱、停顿)
指挥不直接演奏,但协调一切,让音乐和谐演绎。
2.2 Learner的核心组件
┌─────────────────────────────────────────┐
│ Learner │
├─────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ Model │ │ DataLoaders│ │
│ │ │ │ (train/valid)│ │
│ └──────────┘ └──────────┘ │
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │Optimizer │ │Loss Func │ │
│ └──────────┘ └──────────┘ │
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ Metrics │ │Callbacks │ │
│ └──────────┘ └──────────┘ │
│ │
│ ┌─────────────────────────┐ │
│ │ Training Loop Logic │ │
│ │ (fit, fit_one_cycle) │ │
│ └─────────────────────────┘ │
│ │
└─────────────────────────────────────────┘
组件1:DataLoaders (dls)
作用:提供训练和验证数据
python
dls = get_ts_dls(
X, y,
tfms=tfms, # 样本级变换
splits=splits, # 训练/验证划分
batch_tfms=batch_tfms, # 批次级变换
bs=32 # batch size
)
# dls包含两个DataLoader
dls.train # 训练集DataLoader
dls.valid # 验证集DataLoader
组件2:Model (model)
作用:神经网络架构
python
# 方式1:传入架构名称,Learner自动实例化
learn = ts_learner(dls, InceptionTime)
# 方式2:传入已实例化的模型
model = InceptionTime(c_in=24, c_out=6)
learn = ts_learner(dls, model)
组件3:Optimizer (opt)
作用:参数更新策略
python
from torch.optim import Adam, SGD
# 使用Adam优化器(默认)
learn = ts_learner(dls, model, opt_func=Adam)
# 使用SGD
learn = ts_learner(dls, model, opt_func=SGD, lr=0.01)
组件4:Loss Function (loss_func)
作用:衡量模型预测与真实值的差距
python
# 分类任务(自动推断为CrossEntropyLoss)
learn = ts_learner(dls, model)
# 自定义损失函数
learn = ts_learner(dls, model, loss_func=FocalLoss())
组件5:Metrics (metrics)
作用:训练过程中监控的指标
python
learn = ts_learner(dls, model,
metrics=[accuracy, F1Score(), RocAuc()])
组件6:Callbacks (cbs)
作用:在训练循环的特定时刻执行自定义逻辑
python
learn = ts_learner(dls, model,
cbs=[
SaveModel(monitor='valid_loss'),
ShowGraph(),
EarlyStoppingCallback(patience=10)
]
)
2.3 tsai中的两种Learner体系
tsai提供了两套Learner API:
体系1:传统Learner(继承自fastai)
特点:需要先创建DataLoaders,然后创建Learner
python
# 步骤1:创建DataLoaders
dls = get_ts_dls(X, y, splits=splits, tfms=tfms, batch_tfms=batch_tfms)
# 步骤2:创建Learner
learn = ts_learner(dls, InceptionTime, metrics=accuracy)
# 步骤3:训练
learn.fit_one_cycle(50, 1e-3)
适用场景:
- 需要精细控制DataLoaders
- 需要自定义复杂的数据增强
- 想要完全兼容fastai生态
体系2:sklearn-like API(新API)
特点:一步到位,自动创建DataLoaders
python
# 一步创建分类器(自动处理DataLoaders)
clf = TSClassifier(
X, y, # 直接传入数据
splits=splits,
arch='InceptionTimePlus',
tfms=tfms,
batch_tfms=batch_tfms,
metrics=accuracy
)
# 训练
clf.fit_one_cycle(50, 1e-3)
适用场景:
- 快速原型开发
- sklearn用户更熟悉的接口
- 不需要精细控制DataLoaders
对比:
| 特性 | 传统Learner | sklearn-like API |
|---|---|---|
| 代码量 | 中等(需先创建dls) | 少(一步到位) |
| 灵活性 | 高(完全控制) | 中等(自动推断) |
| 学习曲线 | 陡峭(需理解fastai) | 平缓(类似sklearn) |
| 适用人群 | fastai用户 | sklearn用户、初学者 |
2.4 训练循环的抽象层次
Learner提供了多个层次的训练方法:
高层API(推荐)
↓
fit_one_cycle(epochs, lr) # 使用1cycle策略
↓
中层API
↓
fit(epochs, lr) # 标准训练
fit_flat_cos(epochs, lr) # 余弦退火
fine_tune(epochs, lr) # 迁移学习微调
↓
低层API(罕用)
↓
_do_fit() # 底层训练循环
_do_epoch_train() # 单个epoch训练
_do_epoch_validate() # 单个epoch验证
推荐使用fit_one_cycle,它集成了最佳实践。
3. 数学建模与理论基础
3.1 监督学习的数学表述
Learner解决的核心问题是监督学习:
给定训练数据集 D = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , . . . , ( x N , y N ) } \mathcal{D} = \{(x_1, y_1), (x_2, y_2), ..., (x_N, y_N)\} D={(x1,y1),(x2,y2),...,(xN,yN)},其中:
- x i ∈ R C × T x_i \in \mathbb{R}^{C \times T} xi∈RC×T:时间序列输入(C个通道,T个时间步)
- y i ∈ Y y_i \in \mathcal{Y} yi∈Y:目标(分类标签或回归值)
我们的目标是找到一个函数 f θ : R C × T → Y f_\theta: \mathbb{R}^{C \times T} \rightarrow \mathcal{Y} fθ:RC×T→Y,使得:
θ ∗ = arg min θ E ( x , y ) ∼ D [ L ( f θ ( x ) , y ) ] \theta^* = \arg\min_\theta \mathbb{E}{(x,y) \sim \mathcal{D}} [L(f\theta(x), y)] θ∗=argθminE(x,y)∼D[L(fθ(x),y)]
其中:
- θ \theta θ:模型参数
- L L L:损失函数
- E \mathbb{E} E:期望(在实践中用经验平均近似)
3.2 训练目标的分解
实际训练中,目标函数通常包含多项:
L total = L task + λ 1 L reg + λ 2 L aux \mathcal{L}{\text{total}} = \mathcal{L}{\text{task}} + \lambda_1 \mathcal{L}{\text{reg}} + \lambda_2 \mathcal{L}{\text{aux}} Ltotal=Ltask+λ1Lreg+λ2Laux
任务损失 ( L task \mathcal{L}_{\text{task}} Ltask)
分类任务 :交叉熵损失
L CE = − ∑ i = 1 N ∑ c = 1 C y i , c log ( p i , c ) \mathcal{L}{\text{CE}} = -\sum{i=1}^N \sum_{c=1}^C y_{i,c} \log(p_{i,c}) LCE=−i=1∑Nc=1∑Cyi,clog(pi,c)
其中 p i , c = softmax ( f θ ( x i ) ) c p_{i,c} = \text{softmax}(f_\theta(x_i))_c pi,c=softmax(fθ(xi))c
回归任务 :均方误差
L MSE = 1 N ∑ i = 1 N ( f θ ( x i ) − y i ) 2 \mathcal{L}{\text{MSE}} = \frac{1}{N} \sum{i=1}^N (f_\theta(x_i) - y_i)^2 LMSE=N1i=1∑N(fθ(xi)−yi)2
预测任务 :MAE或RMSE
L MAE = 1 N ∑ i = 1 N ∣ f θ ( x i ) − y i ∣ \mathcal{L}{\text{MAE}} = \frac{1}{N} \sum{i=1}^N |f_\theta(x_i) - y_i| LMAE=N1i=1∑N∣fθ(xi)−yi∣
正则化项 ( L reg \mathcal{L}_{\text{reg}} Lreg)
L2正则化 (Weight Decay):
L L2 = λ 2 ∑ w ∈ θ w 2 \mathcal{L}{\text{L2}} = \frac{\lambda}{2} \sum{w \in \theta} w^2 LL2=2λw∈θ∑w2
在Learner中通过wd参数控制:
python
learn = ts_learner(dls, model, wd=0.01) # λ = 0.01
辅助损失 ( L aux \mathcal{L}_{\text{aux}} Laux)
例如:
- MixUp损失:混合样本的损失
- Label Smoothing:平滑标签的损失
- Knowledge Distillation:教师-学生模型的KL散度
3.3 优化问题的数学形式
训练过程本质上是求解:
min θ 1 N ∑ i = 1 N L ( f θ ( x i ) , y i ) + R ( θ ) \min_\theta \frac{1}{N} \sum_{i=1}^N L(f_\theta(x_i), y_i) + R(\theta) θminN1i=1∑NL(fθ(xi),yi)+R(θ)
使用小批量随机梯度下降(Mini-batch SGD):
θ t + 1 = θ t − η t ∇ θ L B ( θ t ) \theta_{t+1} = \theta_t - \eta_t \nabla_\theta L_B(\theta_t) θt+1=θt−ηt∇θLB(θt)
其中:
- η t \eta_t ηt:学习率(时间依赖)
- L B L_B LB:一个mini-batch上的损失
- ∇ θ \nabla_\theta ∇θ:关于 θ \theta θ的梯度
3.4 训练集与验证集的分离
Learner维护两个数据集:
D = D train ∪ D valid \mathcal{D} = \mathcal{D}{\text{train}} \cup \mathcal{D}{\text{valid}} D=Dtrain∪Dvalid
训练集 ( D train \mathcal{D}_{\text{train}} Dtrain):
- 用于计算梯度和更新参数
- 损失记为 L train L_{\text{train}} Ltrain
验证集 ( D valid \mathcal{D}_{\text{valid}} Dvalid):
- 用于评估泛化性能
- 损失记为 L valid L_{\text{valid}} Lvalid
- 不参与梯度计算
训练的终止条件通常基于验证集:
- 早停: L valid L_{\text{valid}} Lvalid 连续N个epoch不下降
- 保存最佳模型: θ ∗ = arg min t L valid ( θ t ) \theta^* = \arg\min_t L_{\text{valid}}(\theta_t) θ∗=argmintLvalid(θt)
3.5 Learner中的关键数学操作
前向传播
y ^ = f θ ( x ) \hat{y} = f_\theta(x) y^=fθ(x)
在代码中:
python
pred = learn.model(xb)
损失计算
l = L ( y ^ , y ) l = L(\hat{y}, y) l=L(y^,y)
python
loss = learn.loss_func(pred, yb)
反向传播
∂ l ∂ θ = ∂ l ∂ y ^ ⋅ ∂ y ^ ∂ θ \frac{\partial l}{\partial \theta} = \frac{\partial l}{\partial \hat{y}} \cdot \frac{\partial \hat{y}}{\partial \theta} ∂θ∂l=∂y^∂l⋅∂θ∂y^
python
loss.backward() # 自动计算梯度
参数更新
θ ← θ − η ∇ θ l \theta \leftarrow \theta - \eta \nabla_\theta l θ←θ−η∇θl
python
learn.opt.step() # 更新参数
梯度清零
∇ θ l ← 0 \nabla_\theta l \leftarrow 0 ∇θl←0
python
learn.opt.zero_grad()
4. 优化方法与训练机制
4.1 优化器家族
Learner支持PyTorch中的所有优化器,常用的有:
1. SGD(随机梯度下降)
θ t + 1 = θ t − η ∇ θ L ( θ t ) \theta_{t+1} = \theta_t - \eta \nabla_\theta L(\theta_t) θt+1=θt−η∇θL(θt)
python
learn = ts_learner(dls, model, opt_func=SGD, lr=0.01)
特点:
- ✅ 简单、稳定
- ❌ 收敛慢、对学习率敏感
2. SGD with Momentum
v t + 1 = μ v t + ∇ θ L ( θ t ) v_{t+1} = \mu v_t + \nabla_\theta L(\theta_t) vt+1=μvt+∇θL(θt)
θ t + 1 = θ t − η v t + 1 \theta_{t+1} = \theta_t - \eta v_{t+1} θt+1=θt−ηvt+1
python
learn = ts_learner(dls, model, opt_func=SGD, lr=0.01, moms=(0.9,))
特点:
- ✅ 加速收敛
- ✅ 减少震荡
3. Adam(Adaptive Moment Estimation)
m t = β 1 m t − 1 + ( 1 − β 1 ) ∇ θ L ( θ t ) m_t = \beta_1 m_{t-1} + (1-\beta_1) \nabla_\theta L(\theta_t) mt=β1mt−1+(1−β1)∇θL(θt)
v t = β 2 v t − 1 + ( 1 − β 2 ) ( ∇ θ L ( θ t ) ) 2 v_t = \beta_2 v_{t-1} + (1-\beta_2) (\nabla_\theta L(\theta_t))^2 vt=β2vt−1+(1−β2)(∇θL(θt))2
θ t + 1 = θ t − η m t v t + ϵ \theta_{t+1} = \theta_t - \eta \frac{m_t}{\sqrt{v_t} + \epsilon} θt+1=θt−ηvt +ϵmt
python
from torch.optim import Adam
learn = ts_learner(dls, model, opt_func=Adam, lr=0.001)
特点:
- ✅ 自适应学习率
- ✅ 对超参数不敏感
- ⚠️ 可能泛化性略差于SGD
4. AdamW(Adam with Weight Decay)
Adam的改进版本,正确实现了权重衰减:
θ t + 1 = ( 1 − λ η ) θ t − η m t v t + ϵ \theta_{t+1} = (1 - \lambda \eta) \theta_t - \eta \frac{m_t}{\sqrt{v_t} + \epsilon} θt+1=(1−λη)θt−ηvt +ϵmt
python
from fastai.optimizer import *
learn = ts_learner(dls, model, opt_func=Adam, wd=0.01) # fastai自动使用AdamW
推荐:默认使用Adam,它在大多数情况下表现良好。
4.2 学习率调度策略
学习率是最重要的超参数,Learner提供了多种调度策略。
策略1:1cycle Learning Rate(推荐)
核心思想:学习率先增后减,动量先减后增
学习率: /\
/ \
/ \___
/
动量: \ /
\/
/\
/ \___
数学表达:
在训练的前 p ⋅ E p \cdot E p⋅E 个epoch( p p p=0.3, E E E=总epoch数):
η t = η min + t p E ( η max − η min ) \eta_t = \eta_{\min} + \frac{t}{pE}(\eta_{\max} - \eta_{\min}) ηt=ηmin+pEt(ηmax−ηmin)
在剩余的 ( 1 − p ) ⋅ E (1-p) \cdot E (1−p)⋅E 个epoch:
η t = η max − t − p E ( 1 − p ) E ( η max − η min ) \eta_t = \eta_{\max} - \frac{t - pE}{(1-p)E}(\eta_{\max} - \eta_{\min}) ηt=ηmax−(1−p)Et−pE(ηmax−ηmin)
使用方法:
python
learn.fit_one_cycle(
50, # epochs
1e-3, # max_lr
pct_start=0.3, # 30%的时间用于warm-up
div=25, # max_lr / min_lr = 25
div_final=1e4 # 最终lr = max_lr / 1e4
)
为什么有效?
- 初期大学习率:快速探索参数空间
- 中期降低学习率:逐步精调
- 后期极小学习率:精细收敛
策略2:余弦退火(Cosine Annealing)
η t = η min + 1 2 ( η max − η min ) ( 1 + cos ( t T π ) ) \eta_t = \eta_{\min} + \frac{1}{2}(\eta_{\max} - \eta_{\min})(1 + \cos(\frac{t}{T}\pi)) ηt=ηmin+21(ηmax−ηmin)(1+cos(Ttπ))
python
learn.fit_flat_cos(
50, # epochs
1e-3, # max_lr
pct_start=0.75 # 前75%平稳,后25%退火
)
策略3:指数衰减
η t = η 0 ⋅ γ t \eta_t = \eta_0 \cdot \gamma^t ηt=η0⋅γt
python
from fastai.callback.schedule import *
learn.fit(
50, 1e-3,
cbs=ParamScheduler('lr', SchedExp(1e-3, 1e-5))
)
策略4:自适应(ReduceLROnPlateau)
当验证集损失停止下降时,降低学习率:
python
from fastai.callback.tracker import *
learn.fit(
50, 1e-3,
cbs=ReduceLROnPlateau(monitor='valid_loss', patience=5, factor=0.5)
)
4.3 判别式学习率(Discriminative Learning Rates)
核心思想:不同层使用不同的学习率
在迁移学习中,浅层(已预训练)应该用小学习率,深层(随机初始化)用大学习率。
η layer i = η base ⋅ k i \eta_{\text{layer}i} = \eta{\text{base}} \cdot k^i ηlayeri=ηbase⋅ki
其中 k < 1 k < 1 k<1 是衰减因子。
使用方法:
python
# 方式1:使用slice
learn.fit_one_cycle(10, slice(1e-5, 1e-3))
# 第一层:1e-5,最后一层:1e-3,中间层线性插值
# 方式2:冻结+解冻
learn.freeze() # 冻结所有层
learn.fit_one_cycle(5, 1e-3) # 只训练最后一层
learn.unfreeze() # 解冻
learn.fit_one_cycle(20, slice(1e-5, 1e-3)) # 用判别式学习率训练所有层
4.4 梯度处理技术
1. 梯度裁剪(Gradient Clipping)
防止梯度爆炸:
g t = { g t if ∥ g t ∥ ≤ τ τ g t ∥ g t ∥ otherwise g_t = \begin{cases} g_t & \text{if } \|g_t\| \leq \tau \\ \tau \frac{g_t}{\|g_t\|} & \text{otherwise} \end{cases} gt={gtτ∥gt∥gtif ∥gt∥≤τotherwise
python
from fastai.callback.training import *
learn = ts_learner(dls, model,
cbs=GradientClip(max_norm=1.0))
2. 梯度累积(Gradient Accumulation)
模拟更大的batch size:
python
learn = ts_learner(dls, model,
cbs=GradientAccumulation(n_acc=4))
# 相当于batch_size × 4
4.5 训练技巧
1. 学习率查找器(LR Finder)
自动找到最佳学习率:
python
learn = ts_learner(dls, model)
# 学习率范围测试
learn.lr_find()
# 会绘制loss vs lr曲线,选择loss下降最快的点
原理:
- 从极小的学习率开始
- 每个batch指数增加学习率
- 记录对应的loss
- 选择loss下降最陡的点
2. 混合精度训练(Mixed Precision)
使用FP16加速训练:
python
learn = ts_learner(dls, model).to_fp16()
好处:
- 训练速度提升2-3倍(在支持的GPU上)
- 显存占用减半
- 几乎不损失精度
原理:
- 前向传播用FP16
- 梯度用FP32累积
- 动态调整loss缩放防止下溢
3. 批量大小调优
一般原则:
- GPU显存允许的情况下,越大越好
- 但太大会影响泛化(需降低学习率)
python
# 线性缩放规则
# 如果batch_size翻倍,学习率也翻倍
bs_original = 32
bs_new = 64
lr_original = 1e-3
lr_new = lr_original * (bs_new / bs_original) # 2e-3
5. 代码实现与工程视角
5.1 创建Learner的完整流程
流程1:使用传统API
python
from tsai.all import *
# ============= 步骤1:准备数据 =============
# 加载数据
X, y, splits = get_UCR_data('NATOPS', split_data=False)
# X shape: [360, 24, 51] (样本数, 特征数, 时间步)
# y shape: [360] (6个类别的标签)
# splits: ([0,1,...,179], [180,181,...,359]) 训练/验证索引
# ============= 步骤2:定义变换 =============
# 样本级变换(应用于每个样本)
tfms = [None, TSClassification()]
# None: 输入不变换
# TSClassification(): 将标签转为类别
# 批次级变换(应用于整个batch)
batch_tfms = [
TSStandardize(by_sample=True), # 标准化
TSMagScale(sigma=0.1, p=0.5) # 数据增强
]
# ============= 步骤3:创建DataLoaders =============
dls = get_ts_dls(
X, y,
tfms=tfms,
splits=splits,
batch_tfms=batch_tfms,
bs=64, # batch size
num_workers=0 # 数据加载线程数
)
# ============= 步骤4:创建Learner =============
learn = ts_learner(
dls, # DataLoaders
InceptionTime, # 模型架构
metrics=[accuracy, F1Score()], # 评估指标
loss_func=LabelSmoothingCrossEntropy(), # 损失函数
opt_func=Adam, # 优化器
lr=1e-3, # 初始学习率
wd=0.01, # 权重衰减
cbs=[ # Callbacks
SaveModel(monitor='valid_loss'),
ShowGraph(),
EarlyStoppingCallback(patience=10)
],
path='models', # 模型保存路径
model_dir='my_model' # 模型子目录
)
# ============= 步骤5:训练 =============
learn.fit_one_cycle(50, 1e-3)
# ============= 步骤6:评估和导出 =============
# 查看训练曲线
learn.plot_metrics()
# 获取验证集预测
preds, targets = learn.get_preds()
# 导出模型
learn.export('natops_model.pkl')
流程2:使用sklearn-like API
python
from tsai.all import *
# ============= 一步到位 =============
clf = TSClassifier(
X, y, # 直接传入数据
splits=splits, # 训练/验证划分
arch='InceptionTimePlus', # 模型架构
tfms=[None, TSClassification()],
batch_tfms=[TSStandardize()],
metrics=accuracy,
bs=64
)
# ============= 训练 =============
clf.fit_one_cycle(50, 1e-3)
# ============= 预测 =============
# 获取概率
probas = clf.predict_proba(X_test)
# 获取类别
preds = clf.predict(X_test)
# ============= 导出 =============
clf.export('classifier.pkl')
5.2 模型架构的选择与配置
内置架构列表
python
from tsai.models.utils import *
# 查看所有可用架构
all_archs = [
'InceptionTime', 'InceptionTimePlus',
'ResNet', 'ResNetPlus',
'FCN', 'FCNPlus',
'LSTM', 'LSTM_FCN', 'LSTM_FCN_plus',
'GRU', 'GRU_FCN',
'TransformerModel', 'TSTPlus',
'PatchTST',
'XCM', 'XCMPlus',
# ... 还有更多
]
架构配置
方式1:使用默认配置
python
learn = ts_learner(dls, InceptionTime)
# 自动从dls推断参数
方式2:自定义配置
python
learn = ts_learner(
dls,
InceptionTime,
c_in=24, # 输入通道数(覆盖dls推断)
c_out=6, # 输出类别数
seq_len=51, # 序列长度
depth=6, # Inception模块深度
kernel_sizes=[39, 19, 9], # 卷积核大小
bottleneck_channels=32 # 瓶颈层通道数
)
方式3:使用arch_config字典
python
learn = ts_learner(
dls,
InceptionTime,
arch_config={
'depth': 6,
'kernel_sizes': [39, 19, 9],
'bottleneck_channels': 32,
'fc_dropout': 0.5
}
)
Plus架构的优势
带"Plus"后缀的架构(如InceptionTimePlus)提供额外功能:
python
learn = ts_learner(dls, InceptionTimePlus)
优势:
- ✅ 自动配置输出头(分类/回归/预测)
- ✅ 支持多任务学习
- ✅ 更好的默认超参数
- ✅ 额外的正则化选项
5.3 训练方法详解
方法1:fit_one_cycle(推荐)
python
learn.fit_one_cycle(
n_epoch=50, # 训练轮数
lr_max=1e-3, # 最大学习率
pct_start=0.3, # warm-up阶段占比
div=25, # 初始lr = lr_max / div
div_final=1e4, # 最终lr = lr_max / div_final
moms=(0.95, 0.85, 0.95), # (初始动量, 中期动量, 最终动量)
wd=None, # 权重衰减(None则使用创建时的值)
cbs=None # 额外的callbacks
)
适用场景:
- ✅ 从头训练
- ✅ 微调预训练模型
- ✅ 大多数情况的首选
方法2:fit(标准训练)
python
learn.fit(
n_epoch=50,
lr=1e-3, # 固定学习率
wd=None,
cbs=None
)
适用场景:
- 需要固定学习率时
- 想完全自定义学习率调度
方法3:fine_tune(迁移学习)
python
learn.fine_tune(
epochs=20, # 微调轮数
base_lr=1e-4, # 基础学习率
freeze_epochs=5, # 冻结前N个epoch
lr_mult=100 # 最后一层的学习率倍数
)
流程:
- 冻结除最后一层外的所有层
- 训练
freeze_epochs个epoch - 解冻所有层
- 用判别式学习率训练
epochs个epoch
适用场景:
- ✅ 迁移学习
- ✅ 预训练模型微调
方法4:fit_flat_cos(余弦退火)
python
learn.fit_flat_cos(
n_epoch=50,
lr=1e-3,
pct_start=0.75 # 前75%保持lr,后25%退火
)
5.4 常见陷阱与解决方案
陷阱1:忘记标准化数据
python
# ❌ 错误:未标准化
dls = get_ts_dls(X, y, splits=splits)
# ✅ 正确:添加标准化
dls = get_ts_dls(X, y, splits=splits,
batch_tfms=TSStandardize())
为什么重要?
- 不同特征的量纲可能差异巨大
- 未标准化会导致训练不稳定
陷阱2:学习率过大或过小
python
# ❌ 可能的问题
learn.fit_one_cycle(50, 1.0) # 太大,可能发散
learn.fit_one_cycle(50, 1e-7) # 太小,收敛极慢
# ✅ 使用lr_find找到合适的学习率
learn.lr_find()
# 根据曲线选择合适的lr
learn.fit_one_cycle(50, 1e-3)
陷阱3:数据泄漏
python
# ❌ 错误:在split之前标准化
X_normalized = (X - X.mean()) / X.std()
splits = TimeSplitter(0.8)(X_normalized)
dls = get_ts_dls(X_normalized, y, splits=splits)
# ✅ 正确:在batch_tfms中标准化
splits = TimeSplitter(0.8)(X)
dls = get_ts_dls(X, y, splits=splits,
batch_tfms=TSStandardize())
# TSStandardize只使用训练集的统计量
陷阱4:忘记设置随机种子
python
# ❌ 结果不可复现
splits = RandomSplitter()(X)
# ✅ 设置随机种子
from tsai.data.validation import *
set_seed(42)
splits = RandomSplitter()(X)
陷阱5:显存不足
python
# 问题:batch size太大导致OOM
try:
learn = ts_learner(dls, model)
learn.fit_one_cycle(50, 1e-3)
except RuntimeError as e:
if "out of memory" in str(e):
print("OOM!")
# 解决方案1:减小batch size
dls = get_ts_dls(X, y, splits=splits, bs=16) # 从64降到16
# 解决方案2:梯度累积
learn = ts_learner(dls, model,
cbs=GradientAccumulation(n_acc=4))
# 解决方案3:混合精度
learn = ts_learner(dls, model).to_fp16()
陷阱6:过拟合
症状:训练loss持续下降,验证loss不降或上升
python
# 解决方案
learn = ts_learner(dls, model,
wd=0.01, # 增加权重衰减
cbs=[
# 数据增强
TSMagWarp(sigma=0.2, p=0.5),
TSTimeWarp(sigma=0.2, p=0.5),
# Dropout
# 在arch_config中设置
]
)
# 或在模型配置中加入dropout
learn = ts_learner(dls, InceptionTime,
arch_config={'fc_dropout': 0.5})
5.5 模型保存与加载
保存策略
策略1:只保存模型参数
python
# 保存
learn.save('model_weights') # 保存到 models/model_weights.pth
# 加载
learn.load('model_weights')
策略2:保存完整learner
python
# 保存(包含模型、优化器状态、训练历史)
learn.export('full_model.pkl')
# 加载
from tsai.inference import load_learner
learn = load_learner('models/full_model.pkl')
# 直接预测
preds = learn.predict(X_new)
策略3:保存所有组件
python
# 保存(分别保存dls、model、learner)
learn.save_all(
path='export',
dls_fname='dataloaders',
model_fname='model',
learner_fname='learner'
)
# 加载
from tsai.learner import load_all
learn = load_all(
path='export',
dls_fname='dataloaders',
model_fname='model',
learner_fname='learner'
)
推荐:
- 实验阶段:使用
save保存检查点 - 生产部署:使用
export保存完整模型
6. 进阶应用场景
6.1 多任务学习
场景:同时进行分类和回归
python
# 假设y包含两个任务
y_class = y[:, 0] # 分类标签
y_reg = y[:, 1] # 回归目标
# 创建多任务DataLoader
class MultiTaskDataset:
def __init__(self, X, y_class, y_reg):
self.X = X
self.y_class = y_class
self.y_reg = y_reg
def __getitem__(self, idx):
return self.X[idx], (self.y_class[idx], self.y_reg[idx])
def __len__(self):
return len(self.X)
# 自定义损失函数
class MultiTaskLoss:
def __init__(self, w1=1.0, w2=1.0):
self.w1 = w1
self.w2 = w2
self.ce = nn.CrossEntropyLoss()
self.mse = nn.MSELoss()
def __call__(self, preds, targets):
pred_class, pred_reg = preds
target_class, target_reg = targets
loss_class = self.ce(pred_class, target_class)
loss_reg = self.mse(pred_reg, target_reg)
return self.w1 * loss_class + self.w2 * loss_reg
# 创建learner
learn = ts_learner(dls, MultiTaskModel,
loss_func=MultiTaskLoss(w1=1.0, w2=0.5))
6.2 时间序列预测
单步预测:
python
from tsai.all import *
# 获取数据
ts = get_forecasting_time_series("Sunspots").values
# 滑动窗口:用60步预测未来1步
X, y = SlidingWindow(window_len=60, horizon=1)(ts)
# 创建预测器
fcst = TSForecaster(
X, y,
arch='TSTPlus', # 使用Transformer
metrics=mae,
batch_tfms=TSStandardize()
)
# 训练
fcst.fit_one_cycle(50, 1e-3)
# 预测
future_steps = fcst.predict(X_test)
多步预测:
python
# 预测未来3步
X, y = SlidingWindow(window_len=60, horizon=3)(ts)
# y shape: [N, 3]
fcst = TSForecaster(
X, y,
arch='TSTPlus',
metrics=[mae, rmse]
)
fcst.fit_one_cycle(50, 1e-3)
递归预测(自回归):
python
def recursive_forecast(learn, X_initial, n_steps):
"""递归预测未来n步"""
forecasts = []
X_current = X_initial.copy()
for _ in range(n_steps):
# 预测下一步
pred = learn.predict(X_current)
forecasts.append(pred)
# 更新输入:移除最旧的,加入新预测
X_current = np.roll(X_current, -1, axis=-1)
X_current[..., -1] = pred
return np.array(forecasts)
# 使用
future_30_steps = recursive_forecast(fcst, X_test[0], n_steps=30)
6.3 序列到序列(Sequence-to-Sequence)
场景:输入和输出都是序列
python
# 例如:时间序列去噪
# X: [N, C, T] 噪声序列
# y: [N, C, T] 干净序列
# 使用Encoder-Decoder架构
from tsai.models.TST import TST
learn = ts_learner(
dls,
TST,
arch_config={
'n_layers': 3,
'n_heads': 8,
'd_model': 128,
'fc_dropout': 0.1
}
)
learn.fit_one_cycle(50, 1e-3)
6.4 少样本学习(Few-Shot Learning)
原型网络(Prototypical Networks):
python
# 假设每个类只有很少样本
from tsai.models.utils import *
class PrototypicalLoss:
def __call__(self, embeddings, labels):
# 计算每个类的原型(中心)
unique_labels = torch.unique(labels)
prototypes = []
for label in unique_labels:
mask = labels == label
prototype = embeddings[mask].mean(dim=0)
prototypes.append(prototype)
prototypes = torch.stack(prototypes)
# 计算距离
distances = torch.cdist(embeddings, prototypes)
# 负对数似然
log_p = F.log_softmax(-distances, dim=1)
loss = F.nll_loss(log_p, labels)
return loss
# 使用
learn = ts_learner(dls, ResNet, loss_func=PrototypicalLoss())
learn.fit_one_cycle(50, 1e-3)
6.5 域适应(Domain Adaptation)
场景:源域有标签,目标域无标签
python
# 对抗训练
class DomainAdaptationModel(nn.Module):
def __init__(self, feature_extractor, classifier, discriminator):
super().__init__()
self.feature_extractor = feature_extractor
self.classifier = classifier
self.discriminator = discriminator
def forward(self, x, alpha=1.0):
features = self.feature_extractor(x)
# 分类任务
class_output = self.classifier(features)
# 域判别(带梯度反转)
reverse_features = GradientReversal.apply(features, alpha)
domain_output = self.discriminator(reverse_features)
return class_output, domain_output
# 自定义训练循环
class DACallback(Callback):
def __init__(self, source_dl, target_dl):
self.source_dl = source_dl
self.target_dl = target_dl
def after_batch(self):
# 获取目标域batch
target_batch = next(iter(self.target_dl))
# 计算域适应损失
# ... (实现细节)
learn = ts_learner(dls_source, DAModel, cbs=[DACallback(dls_source, dls_target)])
6.6 集成学习(Ensemble)
策略1:投票集成
python
# 训练多个模型
models = []
for i in range(5):
learn = ts_learner(dls, InceptionTime)
learn.fit_one_cycle(50, 1e-3)
models.append(learn.model)
# 集成预测
def ensemble_predict(models, X):
preds = []
for model in models:
model.eval()
with torch.no_grad():
pred = model(X)
preds.append(pred)
# 平均概率
ensemble_pred = torch.stack(preds).mean(dim=0)
return ensemble_pred.argmax(dim=-1)
final_preds = ensemble_predict(models, X_test)
策略2:Stacking
python
# 第一层:基础模型
base_learners = [
ts_learner(dls, InceptionTime),
ts_learner(dls, ResNet),
ts_learner(dls, TransformerModel)
]
for learn in base_learners:
learn.fit_one_cycle(50, 1e-3)
# 获取第一层的预测作为特征
base_preds_train = []
base_preds_valid = []
for learn in base_learners:
preds_train, _ = learn.get_preds(dl=dls.train)
preds_valid, _ = learn.get_preds(dl=dls.valid)
base_preds_train.append(preds_train)
base_preds_valid.append(preds_valid)
# 第二层:元学习器
meta_X_train = torch.cat(base_preds_train, dim=1)
meta_X_valid = torch.cat(base_preds_valid, dim=1)
# 使用简单的MLP作为元学习器
from tsai.models.MLP import MLP
meta_dls = get_ts_dls(meta_X_train, y_train,
splits=(train_idx, valid_idx))
meta_learner = ts_learner(meta_dls, MLP)
meta_learner.fit_one_cycle(20, 1e-3)
7. 批判性思考与扩展
7.1 Learner的局限性
局限1:黑盒抽象的代价
问题:Learner隐藏了太多细节,出问题时难以调试
python
# 例如:不知道为什么训练这么慢
learn.fit_one_cycle(50, 1e-3) # 为什么每个epoch要10分钟?
# 可能的原因:
# - DataLoader的num_workers=0(单线程加载)
# - 批次变换太复杂
# - 模型太大
# - ...
# 解决:需要深入了解内部机制
缓解策略:
- 使用
learn.summary()查看模型结构 - 使用
time_callback监控各部分耗时 - 必要时查看源码
局限2:不适合极度自定义的场景
问题:某些研究场景需要完全自定义训练循环
python
# 例如:GAN训练(生成器和判别器交替训练)
# Learner的标准训练循环不适用
# 解决:回归原始PyTorch
for epoch in range(epochs):
# 训练判别器
for _ in range(k):
d_loss = train_discriminator(...)
# 训练生成器
g_loss = train_generator(...)
局限3:内存占用
问题:Learner会保留训练历史,占用内存
python
# 长时间训练后
learn.recorder.values # 包含所有epoch的loss和metric
# 在内存受限环境,可能需要手动清理
learn.recorder.values = []
7.2 何时不应使用Learner
情况1:简单的模型评估
python
# ❌ 过度工程化
learn = ts_learner(dls, model)
preds, targets = learn.get_preds()
# ✅ 直接使用PyTorch更简单
model.eval()
with torch.no_grad():
preds = model(X_test)
情况2:非标准的训练任务
- 强化学习
- 主动学习
- 在线学习
- ...
这些场景下,Learner的抽象不再适用。
情况3:极致性能优化
python
# 如果需要手写CUDA kernel优化
# Learner的抽象层会增加overhead
# 直接使用PyTorch的autograd和JIT
7.3 Learner的设计哲学
原则1:约定优于配置(Convention over Configuration)
python
# Learner猜测合理的默认值
learn = ts_learner(dls, InceptionTime)
# 自动推断:c_in, c_out, seq_len
# 好处:减少重复代码
# 坏处:不透明,可能猜错
原则2:渐进式披露复杂度(Progressive Disclosure)
简单使用:learn.fit_one_cycle(50, 1e-3)
↓
中等复杂:learn.fit_one_cycle(50, slice(1e-5, 1e-3), cbs=[...])
↓
完全控制:learn._do_fit(...) + 自定义callbacks
原则3:可组合性(Composability)
python
# 各组件独立,可自由组合
learn = ts_learner(
custom_dls, # 自定义DataLoaders
custom_model, # 自定义模型
loss_func=custom_loss, # 自定义损失
opt_func=custom_opt, # 自定义优化器
cbs=[custom_cb1, custom_cb2] # 自定义callbacks
)
7.4 未来发展方向
方向1:自动化机器学习(AutoML)
python
# 想象的未来API
auto_learn = AutoTSLearner(X, y)
auto_learn.auto_fit(
time_budget='2h', # 时间预算
metric='accuracy',
trials=100 # 尝试100种配置
)
# 自动搜索:架构、超参数、数据增强
方向2:联邦学习(Federated Learning)
python
# 想象的未来API
fed_learn = FederatedTSLearner(
local_dls_list=[dls1, dls2, dls3], # 多个客户端的数据
arch=InceptionTime
)
fed_learn.fit_federated(
rounds=10,
clients_per_round=2
)
方向3:持续学习(Continual Learning)
python
# 想象的未来API
continual_learn = ContinualTSLearner(dls, InceptionTime)
# 任务1
continual_learn.fit_one_cycle(50, 1e-3, task_id=1)
# 任务2(不忘记任务1)
continual_learn.fit_one_cycle(50, 1e-3, task_id=2)
方向4:可解释性(Explainability)
python
# 想象的未来API
learn.explain(X_sample)
# 自动生成:
# - 注意力可视化
# - Grad-CAM
# - SHAP值
# - ...
7.5 与其他框架的对比
| 特性 | tsai Learner | PyTorch Lightning | Keras | sklearn |
|---|---|---|---|---|
| 学习曲线 | 中等 | 陡峭 | 平缓 | 最平缓 |
| 灵活性 | 高 | 最高 | 中等 | 低 |
| 时间序列专用 | ✅ | ❌ | ❌ | ❌ |
| 训练速度 | 快 | 快 | 中等 | N/A |
| 生态系统 | fastai | PyTorch | TensorFlow | 统计学习 |
| 适用人群 | DL实践者 | DL研究者 | 初学者 | 传统ML |
选择建议:
- 快速原型:tsai Learner或Keras
- 研究创新:PyTorch Lightning
- 生产部署:PyTorch原生(最可控)
- 传统ML:sklearn
8. 小结与学习路径
8.1 核心要点回顾
🎯 Learner的核心价值
- 简化训练代码:从100行压缩到5行
- 集成最佳实践:1cycle、混合精度、判别式学习率
- 统一接口:分类、回归、预测用法一致
- 可扩展:通过Callback机制灵活扩展
📋 关键概念清单
✅ 核心组件 :DataLoaders、Model、Optimizer、Loss、Metrics、Callbacks
✅ 两套API :传统API(先dls后learn)vs sklearn-like(一步到位)
✅ 训练方法 :fit_one_cycle(首选)、fit、fine_tune、fit_flat_cos
✅ 优化技巧 :LR Finder、判别式学习率、梯度裁剪、混合精度
✅ 保存加载:save/load(参数)、export/load_learner(完整)
🛠️ 快速参考
创建Learner:
python
# 方式1:传统API
dls = get_ts_dls(X, y, ...)
learn = ts_learner(dls, arch, metrics, ...)
# 方式2:sklearn-like
clf = TSClassifier(X, y, arch=..., ...)
训练:
python
learn.fit_one_cycle(50, 1e-3) # 推荐
评估:
python
preds, targets = learn.get_preds()
保存/加载:
python
learn.export('model.pkl')
learn = load_learner('model.pkl')
8.2 进阶学习路径
阶段1:基础应用(1-2周)
目标:能够使用Learner完成基本任务
学习任务:
- ✅ 完成一个时间序列分类任务
- ✅ 尝试3种不同的模型架构
- ✅ 使用
lr_find()找到合适的学习率 - ✅ 可视化训练过程(
learn.plot_metrics())
练习项目:
python
# UCR数据集分类
from tsai.all import *
# 任务:在5个UCR数据集上训练模型,比较不同架构的性能
datasets = ['ECG200', 'GunPoint', 'ItalyPowerDemand', 'Wafer', 'Lightning7']
for dsid in datasets:
X, y, splits = get_UCR_data(dsid, split_data=False)
# 尝试3种架构
for arch_name in ['InceptionTimePlus', 'ResNetPlus', 'TSTPlus']:
clf = TSClassifier(X, y, splits=splits, arch=arch_name, metrics=accuracy)
clf.fit_one_cycle(50, 1e-3)
# 记录结果
print(f"{dsid} - {arch_name}: {clf.recorder.values[-1][2]:.4f}")
阶段2:中级技巧(2-3周)
目标:掌握高级训练技巧
学习任务:
- ✅ 使用迁移学习(fine_tune)
- ✅ 实现自定义损失函数
- ✅ 组合多个Callbacks
- ✅ 处理不平衡数据集
练习项目:
python
# 挑战:处理严重不平衡的数据
from tsai.all import *
from imblearn.over_sampling import SMOTE
# 假设类别分布:[1000, 100, 10] (严重不平衡)
# 解决方案1:类别加权
class_weights = compute_class_weight('balanced',
classes=np.unique(y),
y=y)
loss_func = CrossEntropyLoss(weight=torch.tensor(class_weights))
learn = ts_learner(dls, model, loss_func=loss_func)
# 解决方案2:过采样
sm = SMOTE()
X_resampled, y_resampled = sm.fit_resample(X.reshape(len(X), -1), y)
X_resampled = X_resampled.reshape(-1, X.shape[1], X.shape[2])
# 解决方案3:Focal Loss
from tsai.losses import FocalLoss
learn = ts_learner(dls, model, loss_func=FocalLoss())
# 比较三种方案的效果
阶段3:高级应用(3-4周)
目标:解决复杂的实际问题
学习任务:
- ✅ 实现多任务学习
- ✅ 时间序列预测(多步预测)
- ✅ 构建集成模型
- ✅ 部署到生产环境
挑战项目:
python
# 挑战:端到端的时间序列预测系统
# 要求:
# 1. 数据预处理管道
# 2. 模型训练与验证
# 3. 超参数调优
# 4. 模型集成
# 5. 部署API
# 示例框架
class TSForecastingPipeline:
def __init__(self):
self.preprocessor = None
self.models = []
self.meta_model = None
def fit(self, X, y):
# 1. 数据预处理
X_processed = self.preprocess(X)
# 2. 训练多个基础模型
for arch in ['TSTPlus', 'InceptionTimePlus', 'PatchTST']:
learn = TSForecaster(X_processed, y, arch=arch)
learn.fit_one_cycle(50, 1e-3)
self.models.append(learn)
# 3. 训练元模型(stacking)
self.fit_meta_model(X_processed, y)
def predict(self, X):
# 集成预测
...
def deploy(self, endpoint):
# 部署API
...
8.3 深入学习资源
📚 官方文档
- tsai文档:https://timeseriesai.github.io/tsai/
- fastai课程:https://course.fast.ai/
- fastai文档:https://docs.fast.ai/
📖 推荐阅读
-
论文:
- "Cyclical Learning Rates for Training Neural Networks" (Smith, 2017)
- "Super-Convergence: Very Fast Training of Neural Networks" (Smith, 2018)
-
博客:
- fastai官方博客
- "The 1cycle policy" by Sylvain Gugger
💻 源码阅读
建议按以下顺序阅读源码:
fastai/learner.py- Learner基类tsai/learner.py- tsai的Learner扩展tsai/tslearner.py- sklearn-like APIfastai/callback/core.py- Callback系统
附录
A. Learner方法速查表
训练方法
| 方法 | 用途 | 典型参数 |
|---|---|---|
fit_one_cycle |
1cycle训练(推荐) | n_epoch, lr_max |
fit |
标准训练 | n_epoch, lr |
fine_tune |
迁移学习 | epochs, base_lr |
fit_flat_cos |
余弦退火 | n_epoch, lr |
评估方法
| 方法 | 用途 | 返回值 |
|---|---|---|
get_preds |
获取预测和目标 | (preds, targets) |
predict |
单样本预测 | prediction |
validate |
验证集评估 | (loss, metrics) |
工具方法
| 方法 | 用途 | 说明 |
|---|---|---|
lr_find |
学习率查找 | 绘制loss vs lr曲线 |
plot_metrics |
可视化训练 | 绘制loss和metrics |
freeze |
冻结层 | 用于迁移学习 |
unfreeze |
解冻层 | 微调时使用 |
保存/加载
| 方法 | 用途 | 文件 |
|---|---|---|
save |
保存模型参数 | .pth |
load |
加载模型参数 | .pth |
export |
保存完整learner | .pkl |
load_learner |
加载完整learner | .pkl |
B. 常用架构对比
| 架构 | 类型 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| InceptionTime | CNN | 快速、准确 | 长序列较慢 | 通用分类 |
| ResNet | CNN | 深度可扩展 | 参数多 | 复杂模式 |
| LSTM | RNN | 捕获长期依赖 | 训练慢 | 长序列 |
| Transformer | Attention | 并行训练 | 显存占用大 | 长序列、预测 |
| PatchTST | Patch+Transformer | SOTA性能 | 较新、参数多 | 预测任务 |
C. 故障排除清单
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 训练不收敛 | 学习率太大 | 使用lr_find降低lr |
| 训练很慢 | batch size太小 | 增加batch size或梯度累积 |
| 验证集不提升 | 过拟合 | 增加正则化、数据增强 |
| OOM | 显存不足 | 减小batch size或使用fp16 |
| 精度低 | 模型容量不足 | 使用更大/更深的模型 |
🎓 结语
Learner是深度学习工程化的典范------它将复杂的训练过程封装成简洁的接口,让我们能专注于解决实际问题,而非重复造轮子。
掌握Learner,不仅意味着掌握了tsai/fastai的使用,更重要的是理解了如何设计一个优雅的深度学习系统。这种设计思想可以迁移到任何框架、任何领域。
好的抽象应该隐藏复杂性,但不隐藏控制权。Learner正是这样的设计。