DataWhale 二月组队学习-深入浅出pytorch-Task04

一、自定义损失函数

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()
  • 关键步骤
    1. 继承nn.Module并初始化参数(使用register_buffer保存不需要梯度但需在设备间转移的参数)。
    2. 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. 注意事项

  1. 梯度计算:确保所有运算使用PyTorch张量操作,避免使用Numpy或Python原生类型,以保留梯度。
  2. 输入检查:使用assert或异常处理验证输入形状是否匹配。
  3. 设备兼容性:若损失函数包含参数,需通过to(device)确保与模型数据处于同一设备。
  4. 数值稳定性:例如在概率计算中使用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. 注意事项

  1. 调用顺序scheduler.step()应在每个epoch后调用,而非每个batch
  2. 优化器更新:先执行optimizer.step()再执行scheduler.step()
  3. 学习率查询:通过optimizer.param_groups[0]['lr']获取当前学习率
  4. 参数同步:当使用ReduceLROnPlateau时需传入验证指标
  5. 学习率重置:部分调度器(如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)

你可能感兴趣的:(DataWhale组队学习,学习,pytorch,人工智能)