一、自定义损失函数
1. 损失函数的作用与自定义意义
在深度学习中,损失函数(Loss Function)用于衡量模型预测结果与真实标签之间的差异,是模型优化的目标。PyTorch内置了多种常用损失函数(如交叉熵损失nn.CrossEntropyLoss
、均方误差nn.MSELoss
等)。但在实际任务中,可能需要针对特定问题设计自定义损失函数,例如:
- 处理类别不平衡问题(如加权交叉熵)
- 实现特殊业务需求(如对某些预测错误施加更大惩罚)
- 结合领域知识设计新颖的优化目标
2. 实现自定义损失函数的方法
方法一:函数形式(无需参数)
若损失函数无需可学习参数,可直接定义为函数。例如实现平滑L1损失(Smooth L1 Loss):
def smooth_l1_loss(pred, target, beta=1.0):
diff = pred - target
abs_diff = torch.abs(diff)
loss = torch.where(abs_diff < beta, 0.5 * diff**2 / beta, abs_diff - 0.5 * beta)
return loss.mean()
- 说明:当预测与目标的差值小于
beta
时使用平方损失,否则使用线性损失,结合了MSE和MAE的优点。
方法二:类形式(继承nn.Module
)
当损失函数需要可学习参数或复杂计算时,推荐以类形式实现。例如带权重的均方误差损失:
class WeightedMSE(nn.Module):
def __init__(self, weight):
super().__init__()
self.register_buffer('weight', weight) # 注册为不更新的参数
def forward(self, pred, target):
diff = pred - target
loss = diff.pow(2) * self.weight.expand_as(diff)
return loss.mean()
- 关键步骤:
- 继承
nn.Module
并初始化参数(使用register_buffer
保存不需要梯度但需在设备间转移的参数)。
forward
方法定义损失计算逻辑,使用PyTorch张量操作保证自动微分。
3. 示例:Focal Loss实现
Focal Loss用于解决分类任务中的类别不平衡,通过减少易分类样本的权重,使模型更关注困难样本。
class FocalLoss(nn.Module):
def __init__(self, alpha=0.25, gamma=2.0):
super().__init__()
self.alpha = alpha
self.gamma = gamma
def forward(self, preds, targets):
BCE_loss = F.binary_cross_entropy_with_logits(preds, targets, reduction='none')
pt = torch.exp(-BCE_loss) # 模型预测正确的概率
focal_loss = self.alpha * (1 - pt)**self.gamma * BCE_loss
return focal_loss.mean()
- 参数说明:
alpha
:平衡类别权重,通常为稀有类设置较大的α。
gamma
:调节困难样本的权重,γ越大,困难样本的损失贡献越大。
4. 使用自定义损失函数
实例化后即可像内置损失函数一样使用:
criterion = FocalLoss(alpha=0.5, gamma=2.0)
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
for inputs, targets in dataloader:
outputs = model(inputs)
loss = criterion(outputs, targets)
optimizer.zero_grad()
loss.backward()
optimizer.step()
5. 注意事项
- 梯度计算:确保所有运算使用PyTorch张量操作,避免使用Numpy或Python原生类型,以保留梯度。
- 输入检查:使用
assert
或异常处理验证输入形状是否匹配。
- 设备兼容性:若损失函数包含参数,需通过
to(device)
确保与模型数据处于同一设备。
- 数值稳定性:例如在概率计算中使用logits而非手动sigmoid+log,防止数值溢出。
二、动态调整学习率
1. 学习率调整的意义与作用
学习率(Learning Rate)是深度学习中最重要的超参数之一,直接影响模型收敛速度和最终性能。动态调整学习率的主要优势体现在:
- 训练初期:使用较大学习率加速收敛,逃离局部极小点
- 训练后期:减小学习率提高模型精度,稳定收敛
- 特殊场景:应对梯度爆炸/消失、突破损失平台期等
2. PyTorch内置学习率调整方法
通过torch.optim.lr_scheduler
模块实现,常用方法:
2.1 基础调整策略
方法名称 |
类名 |
公式描述 |
典型场景 |
等间隔调整 |
StepLR |
lr = lr * gamma 每step_size个epoch |
简单衰减 |
多阶段调整 |
MultiStepLR |
lr = lr * gamma 当epoch到达milestones |
复杂训练阶段 |
指数衰减 |
ExponentialLR |
lr = lr * gamma^epoch |
平滑连续衰减 |
余弦退火 |
CosineAnnealingLR |
lr = η_min + 0.5*(η_max-η_min)*(1+cos(T_cur/T)) |
跳出局部最优 |
2.2 自适应调整策略
方法名称 |
类名 |
核心思想 |
适用场景 |
按指标调整 |
ReduceLROnPlateau |
当指标停止改善时衰减 |
验证集监控 |
周期性调整 |
CyclicLR |
在基频和最大学习率之间循环 |
增强模型鲁棒性 |
Warmup |
LambdaLR |
初始阶段线性/指数增长学习率 |
大模型训练 |
2.3 代码示例:使用StepLR
import torch.optim as optim
from torch.optim import lr_scheduler
model = Net()
optimizer = optim.Adam(model.parameters(), lr=0.001)
scheduler = lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.1) # 每30个epoch衰减10倍
for epoch in range(100):
# 训练阶段
train(...)
# 验证阶段
val(...)
scheduler.step() # 更新学习率(注意:在epoch结束后调用)
3. 自定义学习率调整方法
3.1 基于Lambda函数
# 实现warmup策略:前10个epoch线性增长学习率
warmup_factor = lambda epoch: epoch / 10 if epoch < 10 else 1
scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=warmup_factor)
3.2 继承_LRScheduler类
class CustomScheduler(lr_scheduler._LRScheduler):
def __init__(self, optimizer, total_epochs, last_epoch=-1):
self.total_epochs = total_epochs
super().__init__(optimizer, last_epoch)
def get_lr(self):
# 实现余弦退火变种
return [base_lr * (1 + math.cos(math.pi * self.last_epoch / self.total_epochs)) / 2
for base_lr in self.base_lrs]
3.3 组合多个调度器
# 前5个epoch使用warmup,之后切换为余弦退火
scheduler1 = lr_scheduler.LambdaLR(optimizer, lr_lambda=lambda e: e/5)
scheduler2 = lr_scheduler.CosineAnnealingLR(optimizer, T_max=95)
for epoch in range(100):
train(...)
if epoch < 5:
scheduler1.step()
else:
scheduler2.step()
4. 高级技巧与实践经验
4.1 参数分组调整
# 不同层使用不同学习率策略
optimizer = optim.Adam([
{'params': model.features.parameters(), 'lr': 1e-4},
{'params': model.classifier.parameters(), 'lr': 1e-3}
])
scheduler = lr_scheduler.MultiStepLR(optimizer, milestones=[30, 80], gamma=0.1)
4.2 学习率监控
# 使用TensorBoard记录学习率
from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter()
for epoch in range(100):
# ...训练代码...
writer.add_scalar('lr', optimizer.param_groups[0]['lr'], epoch)
4.3 恢复训练状态
# 保存检查点时需记录scheduler状态
checkpoint = {
'model': model.state_dict(),
'optimizer': optimizer.state_dict(),
'scheduler': scheduler.state_dict(),
'epoch': epoch
}
# 加载时恢复状态
scheduler.load_state_dict(checkpoint['scheduler'])
5. 注意事项
- 调用顺序:
scheduler.step()
应在每个epoch后调用,而非每个batch
- 优化器更新:先执行
optimizer.step()
再执行scheduler.step()
- 学习率查询:通过
optimizer.param_groups[0]['lr']
获取当前学习率
- 参数同步:当使用
ReduceLROnPlateau
时需传入验证指标
- 学习率重置:部分调度器(如
CosineAnnealingLR
)在达到周期数后会重置学习率
三、模型微调-torchvision
1. 模型微调的核心概念
1.1 迁移学习与微调的关系
方法类型 |
数据量需求 |
训练策略 |
适用场景 |
从头训练 |
大量数据(百万级) |
所有参数随机初始化 |
数据充足且与现有模型差异大 |
迁移学习 |
中等数据(万级) |
仅训练顶层分类器 |
目标任务与源任务特征相似 |
微调 |
小数据(千级) |
全网络参数微调 |
目标任务与源任务特征相关但存在差异 |
1.2 torchvision模型库概览
import torchvision.models as models
# 常用预训练模型列表
model_dict = {
"视觉任务": ["resnet50", "vgg16", "mobilenet_v3"],
"检测分割": ["fasterrcnn_resnet50", "maskrcnn_resnet50"],
"视频处理": ["r3d_18", "s3d"]
}
2. 标准微调流程
2.1 模型加载与改造
from torchvision import models
# 加载预训练模型(以ResNet50为例)
model = models.resnet50(pretrained=True)
# 修改最后一层全连接层(适配新任务)
num_ftrs = model.fc.in_features
model.fc = nn.Sequential(
nn.Linear(num_ftrs, 512),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(512, num_classes) # num_classes为新任务类别数
)
2.2 参数冻结策略
# 冻结所有卷积层参数
for param in model.parameters():
param.requires_grad = False
# 解冻最后两个残差块
for param in model.layer4.parameters():
param.requires_grad = True
# 检查冻结情况
print("可训练参数:", sum(p.numel() for p in model.parameters() if p.requires_grad))
3. 优化器配置技巧
3.1 分层学习率设置
optimizer = torch.optim.SGD([
{'params': model.conv1.parameters(), 'lr': 1e-5}, # 底层微调
{'params': model.layer4.parameters(), 'lr': 1e-3}, # 高层大调
{'params': model.fc.parameters(), 'lr': 1e-2} # 分类头强化
], momentum=0.9)
3.2 渐进解冻策略
def unfreeze_layers(epoch):
if epoch == 5: # 第5轮解冻layer3
for param in model.layer3.parameters():
param.requires_grad = True
elif epoch == 10: # 第10轮解冻layer2
for param in model.layer2.parameters():
param.requires_grad = True
for epoch in range(15):
unfreeze_layers(epoch)
# 训练代码...
4. 数据增强最佳实践
4.1 通用增强组合
from torchvision import transforms
train_transform = transforms.Compose([
transforms.RandomResizedCrop(224),
transforms.RandomHorizontalFlip(),
transforms.ColorJitter(brightness=0.2, contrast=0.2),
transforms.RandomRotation(15),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
# 测试集增强(仅中心裁剪)
test_transform = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
4.2 领域特定增强
# 医疗影像增强示例
medical_transform = transforms.Compose([
transforms.RandomAffine(degrees=0, translate=(0.1, 0.1)),
transforms.RandomVerticalFlip(),
transforms.RandomApply([GaussianBlur(kernel_size=3)], p=0.5),
transforms.RandomAdjustSharpness(sharpness_factor=2)
])
5. 训练监控与调试
5.1 特征可视化
# 可视化中间层特征图
def visualize_features(model, layer_name):
features = []
def hook_fn(module, input, output):
features.append(output.detach())
handle = getattr(model, layer_name).register_forward_hook(hook_fn)
# 前向传播后分析features张量
# ...
handle.remove()
5.2 梯度监控
# 注册梯度钩子
for name, param in model.named_parameters():
if param.requires_grad:
param.register_hook(
lambda grad, name=name: print(f"梯度范数 {name}: {grad.norm().item()}")
)
6. 常见问题与解决方案
问题现象 |
可能原因 |
解决方案 |
验证集准确率波动大 |
学习率过高/数据增强过强 |
降低学习率、减少增强强度 |
训练损失不下降 |
参数冻结过多/学习率过低 |
解冻更多层、增大分类头学习率 |
过拟合严重 |
模型复杂度高/数据量少 |
增加Dropout、使用更强正则化 |
GPU内存不足 |
输入尺寸过大/批量太大 |
减小图像尺寸、使用梯度累积 |
四、半精度训练
1. 半精度训练的核心概念
1.1 FP16 vs FP32 对比
特性 |
FP32(单精度) |
FP16(半精度) |
内存占用 |
4字节/参数 |
2字节/参数(减少50%) |
计算速度 |
标准速度 |
1.5-3倍加速(NVIDIA GPU) |
数值范围 |
~1e-38 到 ~3e38 |
~6e-5 到 65504 |
精度 |
23位尾数 |
10位尾数 |
适用场景 |
常规训练 |
大模型/大batch训练 |
1.2 混合精度训练优势
- 内存优化:允许训练更大模型或使用更大batch size
- 计算加速:利用Tensor Core加速矩阵运算
- 通信效率:减少分布式训练时的数据传输量
2. PyTorch自动混合精度(AMP)实现
2.1 核心组件
from torch.cuda.amp import autocast, GradScaler
# 主要功能组件
- autocast: 自动选择合适精度执行计算
- GradScaler: 梯度缩放防止下溢
2.2 标准训练流程改造
scaler = GradScaler()
for data, target in dataloader:
optimizer.zero_grad()
# 前向传播使用自动精度转换
with autocast():
output = model(data)
loss = loss_fn(output, target)
# 反向传播与梯度缩放
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
3. 关键实现细节
3.1 梯度缩放原理
原始梯度 --> 放大2^16倍 --> 反向传播 --> 缩放回原尺度 --> 参数更新
3.2 精度敏感操作处理
操作类型 |
处理建议 |
示例 |
减少类操作 |
强制使用FP32 |
torch.mean(), torch.sum() |
大范围值操作 |
保持FP32 |
指数运算、Softmax |
小数值累积 |
使用FP32缓存 |
方差计算、BatchNorm |
五、数据增强-imgaug
1. imgaug简介与核心优势
imgaug是Python中最强大的图像增强库之一,主要特点包括:
- 支持60+种增强操作(几何变换、颜色空间变换、模糊等)
- 支持同时处理图像、关键点、边界框、分割图
- 灵活的随机组合与概率控制
- 与NumPy/PyTorch/TensorFlow完美兼容
import imgaug.augmenters as iaa # 核心增强操作库
from imgaug.augmentables.bbs import BoundingBox, BoundingBoxesOnImage # 边界框处理
2. 基础使用方法
2.1 单图像增强流程
import cv2
import matplotlib.pyplot as plt
# 加载图像
image = cv2.imread("image.jpg")[:, :, ::-1] # 转为RGB格式
# 定义增强序列
aug = iaa.Sequential([
iaa.Fliplr(0.5), # 50%概率水平翻转
iaa.GaussianBlur(sigma=(0, 3.0)), # 随机高斯模糊
iaa.AdditiveGaussianNoise(scale=(0, 0.1*255)) # 添加高斯噪声
])
# 执行增强
augmented_image = aug(image=image)
# 可视化对比
plt.figure(figsize=(10,5))
plt.subplot(121), plt.imshow(image)
plt.subplot(122), plt.imshow(augmented_image)
2.2 批量增强处理
# 处理4张256x256的随机图像
images = [np.random.rand(256,256,3)*255 for _ in range(4)]
# 应用增强并保持图像间同步变换
aug = iaa.Sequential([
iaa.Affine(rotate=(-25, 25)), # 随机旋转
iaa.Crop(percent=(0, 0.2)) # 随机裁剪
])
augmented_images = aug(images=images)
3. 常用增强操作详解
3.1 几何变换
方法 |
类名 |
主要参数 |
效果示例 |
翻转 |
Fliplr/Fliud |
概率 |
水平/垂直翻转 |
旋转 |
Affine |
rotate=(-30,30) |
随机旋转角度 |
裁剪 |
Crop |
percent=(0,0.2) |
随机区域裁剪 |
透视 |
PerspectiveTransform |
scale=(0.01,0.15) |
模拟视角变化 |
3.2 颜色变换
方法 |
类名 |
参数范围 |
效果说明 |
亮度 |
Add |
(-50,50) |
调整整体亮度 |
对比度 |
GammaContrast |
gamma=(0.5,2.0) |
非线性对比度调整 |
饱和度 |
MultiplySaturation |
(0.5,1.5) |
色彩鲜艳度控制 |
色相 |
AddToHue |
(-50,50) |
颜色色调偏移 |
3.3 特殊效果
special_aug = iaa.Sequential([
iaa.Sometimes(0.3, iaa.Dropout(p=(0,0.1))), # 30%概率添加像素丢失
iaa.OneOf([ # 随机选择一种效果
iaa.GaussianBlur((0, 3.0)),
iaa.AverageBlur(k=(3, 7)),
iaa.MedianBlur(k=(3, 5))
]),
iaa.Clouds(), # 添加云雾效果
iaa.Fog() # 添加薄雾效果
])
六、使用argparse进行调参
1. argparse核心功能概览
1.1 基础三要素
import argparse
# 创建解析器对象
parser = argparse.ArgumentParser(description='模型训练参数配置')
# 添加参数
parser.add_argument('--lr', type=float, default=0.01)
# 解析参数
args = parser.parse_args()
print(f"学习率设置为: {args.lr}")
1.2 参数类型对比
参数类型 |
使用场景 |
示例 |
位置参数 |
必需参数 |
add_argument("data_dir") |
可选参数 |
可选配置项 |
add_argument("--batch") |
布尔参数 |
开关选项 |
add_argument("--train", action='store_true') |
列表参数 |
多值输入 |
add_argument("--layers", nargs='+') |
2. 基础参数配置
2.1 常用参数设置
parser.add_argument(
'--epochs',
type=int,
default=50,
help='训练总轮数',
metavar='N'
)
parser.add_argument(
'--optimizer',
choices=['sgd', 'adam', 'rmsprop'],
default='adam',
help='优化器选择'
)
2.2 互斥参数组
group = parser.add_mutually_exclusive_group()
group.add_argument('--verbose', action='store_true')
group.add_argument('--silent', action='store_true')
3. PyTorch训练参数配置实例
3.1 完整训练参数配置
def get_args():
parser = argparse.ArgumentParser()
# 数据参数
parser.add_argument('--batch-size', type=int, default=32)
parser.add_argument('--num-workers', type=int, default=4)
# 模型参数
parser.add_argument('--model', type=str, default='resnet50')
parser.add_argument('--pretrained', action='store_true')
# 优化参数
parser.add_argument('--lr', type=float, default=1e-3)
parser.add_argument('--weight-decay', type=float, default=1e-4)
# 训练控制
parser.add_argument('--epochs', type=int, default=100)
parser.add_argument('--resume', type=str, default=None)
return parser.parse_args()
3.2 配置文件支持
# 支持从文件读取参数
parser.add_argument('--config', type=argparse.FileType('r'))
args = parser.parse_args()
if args.config:
config = json.load(args.config)
parser.set_defaults(**config)