2025-08-10 李沐深度学习15——数据增广与微调

1 数据增强

**数据增强(Data Augmentation)**是指通过对已有数据进行变换,来增加其多样性,从而提高模型的泛化能力。虽然这个概念不仅限于图片,也可以应用于文本和语音等数据。

image-20250810232651725

1.1 案例

  • 案例: 一家做智能售货机的国内公司,在拉斯维加斯CES展会上进行产品演示时,发现模型效果很差。

  • 原因分析:

    1. 光照与色温问题: CES展馆的灯光昏暗且偏黄,色温(约3000K)与公司内部测试环境(约5000K)差异很大,导致图片颜色发生严重偏差。
    2. 反光问题: 机器被放置在一个反光的亮面桌子上,灯光反射导致采集到的图片质量变差。
  • 解决方法:

    1. 色温问题: 工程师连夜在现场采集新数据,发回国后重新训练模型。
    2. 反光问题: 购买桌布铺在桌子上,解决了反光问题。

总结: 这个案例说明了训练环境和实际部署环境可能存在巨大差异。数据增强的目标,就是在训练时尽可能地模拟部署时可能遇到的各种场景(如不同的光照、背景噪音等),从而提高模型对未见过数据的泛化能力。

1.2 常用方法

图片数据增强的方法有很多,最常用的包括:

1.2.1 翻转 (Flipping)

  • 左右翻转: 将图片沿垂直中线进行翻转。这是最常用的方法之一。
  • 上下翻转: 将图片沿水平中线进行翻转。但这种方法要根据数据集的性质来决定是否使用。例如,翻转猫的图片会导致头朝下,这在现实中很不常见;但如果是叶子分类等对称性较强的数据集,则可以考虑使用。
image-20250811005044853

1.2.2 裁剪 (Cropping)

  • 目的: 从图片中裁剪出一部分区域,并将其缩放到固定尺寸(例如,ImageNet常用的 224x224)。
  • 常用做法: 随机裁剪。每次训练时,从同一张图片中随机选择不同的区域进行裁剪。这种随机性通常体现在三个方面:
    1. 随机宽高比: 在一个预设的范围(如 3/4 到 4/3)内随机选取宽高比。
    2. 随机缩放比例: 在一个预设的比例(如 8% 到 100%)内随机选取裁剪区域占原图的百分比。
    3. 随机位置: 在图片中的随机位置进行裁剪。
image-20250811005106437

1.2.3 颜色变换 (Color Jittering)

  • 目的: 改变图片的颜色属性,以模拟不同的光照条件。
  • 常用属性:
    • 色调(Hue): 改变图片的颜色倾向(如偏黄、偏蓝、偏红)。
    • 饱和度(Saturation): 改变颜色的“浓度”。
    • 明亮度(Brightness): 改变图片的亮度。
  • 实现方式: 通常是在当前值的基础上,在一定范围内随机调整。例如,将明亮度在 0.5 到 1.5 的范围内随机调整,这意味着亮度可以降低 50% 或增加 50%。
image-20250811005157498

1.3 实现细节

image-20250810233026660
  • 在线生成: 数据增强通常在训练过程中“在线”进行。这意味着,你不会预先生成并保存所有增强后的图片。每次训练时,从原始数据集中读取图片,然后随机应用不同的增强方法,生成一张新的图片后,立即将其送入模型进行训练。
  • 仅用于训练: 数据增强只在训练阶段使用,而测试验证时通常不使用。这是因为数据增强可以被视为一种正则化手段,它通过增加训练数据的多样性来防止模型过拟合,提高泛化能力。
image-20250810233058147

1.4 其他方法

除了上述三种最常用的方法外,还有许多其他高级方法,例如:

  • 高斯模糊(Gaussian Blur)
  • 锐化(Sharpening)
  • 添加随机黑块
  • 几何变形(Geometric Transformation)

这些方法可以看作是图片处理软件(如 Photoshop)中的各种滤镜和工具。是否使用这些方法,取决于你认为在实际部署环境中是否可能出现类似效果的图片。

image-20250811010033135

1.5 代码实现

from io import BytesIO
import requests
import os
from PIL import Image
from matplotlib import pyplot as plt
import torch
import torchvision
from torch import nn

url = "https://pytorch.org/assets/images/pytorch-ecosystem.png"
# 发送请求获取图片
response = requests.get(url, stream=True)
response.raise_for_status()  # 检查请求是否成功

# 将响应内容转换为PIL Image对象
image = Image.open(BytesIO(response.content))

# 数据增强方法
rhf_aug = torchvision.transforms.RandomHorizontalFlip(p=1)  # 水平翻转概率为 1
rvf_aug = torchvision.transforms.RandomVerticalFlip(p=1)  # 垂直翻转概率为 1
shape_aug = torchvision.transforms.RandomResizedCrop((200, 200), scale=(0.1, 1), ratio=(0.5, 2))  # 随机裁剪到200x200大小
# 亮度(brightness)、对比度(contrast)、饱和度(saturation)、色调(hue)
cj_aug = torchvision.transforms.ColorJitter(brightness=0, contrast=0, saturation=0, hue=0.5)  # 随机颜色抖动


def show_images(imgs, num_rows, num_cols, titles=None, scale=1.5):
    """绘制图像列表"""
    figsize = (num_cols * scale, num_rows * scale)
    _, axes = plt.subplots(num_rows, num_cols, figsize=figsize)
    # axes = axes.flatten()
    for i, (ax, img) in enumerate(zip(axes, imgs)):
        # PIL图片
        ax.imshow(img)
        ax.axes.get_xaxis().set_visible(False)
        ax.axes.get_yaxis().set_visible(False)
    plt.show()
    return axes


# 显示图片
show_images(
    [image, rhf_aug(image), rvf_aug(image), shape_aug(image), cj_aug(image)],
    1, 5,
    titles=['原图', '水平翻转', '垂直翻转', '随机裁剪', '颜色抖动'],
    scale=2.5
)
image-20250811015353571

2 微调

微调(Fine-tuning),也称为迁移学习(Transfer Learning),是计算机视觉,特别是深度学习领域中一个至关重要的技术。它的核心思想是:在一个大型数据集上预先训练好的模型,可以作为基础,来帮助我们提升在小型数据集上的任务表现。

这个思想类似于人类的学习方式:一个具有丰富经验的人,在面对一个新事物时,通常只需要很少的例子就能快速掌握,而不是从头开始学习。

image-20250811015853661
  • 数据集大小的差距: 像 ImageNet 这样的大型数据集包含了 1000 多万张图片和 1000 个类别,其标注成本高昂。而我们实际工作中遇到的数据集通常要小得多,比如可能只有 5 万张图片和 100 个类别。
  • 小数据集的局限性: 即使是 5 万张图片,对于训练一个大型深度学习模型来说也可能不够,容易导致过拟合,模型泛化能力差。
  • 利用先验知识: 微调允许我们利用在大规模数据集上学到的通用知识(如边缘、纹理、形状等特征),并将其迁移到我们的特定任务中。
image-20250811015656400

2.1 工作原理

一个典型的深度神经网络通常可以分为两部分:

  1. 特征提取层: 位于网络的前几层,负责将原始像素信息转换为更抽象、更有意义的特征表示。
  2. 分类层: 位于网络的最后一层,通常是一个全连接层加上一个 Softmax 回归,负责根据特征进行最终的分类。
image-20250811015919232

微调的核心思路是:

  1. 加载预训练模型: 使用一个在大数据集(如 ImageNet)上训练好的模型,这个模型通常被称为预训练模型(Pre-trained Model)

  2. 复用特征提取层: 认为预训练模型的特征提取层已经学习了通用的视觉特征,这些特征对于我们的新任务同样有用。因此,我们将这些层的权重(参数)原封不动地复制过来,作为我们新模型的初始化参数。

    image-20250811020006472
  3. 替换并重新训练分类层: 由于我们的新任务的类别数量与预训练模型不同,所以需要替换掉预训练模型的最后一层分类器,并用随机初始化的新分类层代替。

    image-20250811020147929
  4. 进行训练: 在我们自己的小型数据集上,使用上述初始化的模型进行训练。这时,模型会根据我们自己的数据,对特征提取层进行“微调”,同时训练新的分类层。

通过这种方式,模型一开始就拥有了很好的特征表示能力,只需对网络进行小幅度的调整,就能很快地适应新的任务,从而实现更高的精度和更快的训练速度。

2.2 微调的常用技巧

  • 更小的学习率: 在微调时,通常会使用比从头训练时更小的学习率。因为预训练模型的参数已经很接近最优解,我们只需要对其进行小幅度的调整,而不是大刀阔斧地改变。
  • 更少的迭代次数: 微调通常只需要更少的 epochs(数据迭代次数)。
image-20250811020246782
  • 冻结底层: 一个更强的正则化方法是**冻结(freeze)**底层。由于神经网络的底层通常学习的是更通用的特征(如边缘、颜色),而高层学习的是与具体任务更相关的语义特征。当你的数据集非常小时,可以固定住底层参数不进行更新,只训练高层参数。这能有效降低模型复杂度,防止过拟合。
image-20250811020626087
  • 预训练模型的质量: 预训练模型的质量至关重要。一个在更大、更复杂数据集上训练出来的模型,其特征表示能力通常也更强,微调效果也会更好。
image-20250811020719895

2.3 代码步骤

2.3.1 获取和准备数据集

首先,你需要准备好你的目标数据集。在这个例子中,我们使用一个包含热狗和非热狗图像的数据集。

  • 下载数据集: 使用 d2l 库中的 download_extract 函数下载并解压数据集。数据集结构通常是 traintest 文件夹,每个文件夹下又包含按类别命名的子文件夹(hotdognot-hotdog)。

    #@save
    d2l.DATA_HUB['hotdog'] = (d2l.DATA_URL + 'hotdog.zip',
                             'fba480ffa8aa7e0febbb511d181409f899b9baa5')
    
    data_dir = d2l.download_extract('hotdog')
    
  • 加载数据: 使用 torchvision.datasets.ImageFolder 来加载数据集,它可以自动识别文件夹结构作为类别。

    train_imgs = torchvision.datasets.ImageFolder(os.path.join(data_dir, 'train'))
    test_imgs = torchvision.datasets.ImageFolder(os.path.join(data_dir, 'test'))
    
  • 定义数据增强和标准化:

    • 训练集(train_augs): 为了增加数据多样性并防止过拟合,对训练集应用数据增强技术。常用的包括随机裁剪(RandomResizedCrop)和随机水平翻转(RandomHorizontalFlip)
    • 测试集(test_augs): 测试集通常只进行标准化处理,以保证评估的准确性。通常包括调整大小(Resize)和中心裁剪(CenterCrop)
    • 标准化(Normalize): 对图片的 RGB 三个通道进行标准化,即减去均值并除以标准差。这有助于模型更快地收敛。
    # 定义标准化方法,使用在ImageNet上训练模型的均值和标准差
    normalize = torchvision.transforms.Normalize(
    [0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    
    # 训练集的数据增强
    train_augs = torchvision.transforms.Compose([
    torchvision.transforms.RandomResizedCrop(224),
    torchvision.transforms.RandomHorizontalFlip(),
    torchvision.transforms.ToTensor(),
    normalize])
    
    # 测试集的数据增强
    test_augs = torchvision.transforms.Compose([
    torchvision.transforms.Resize([256, 256]),
    torchvision.transforms.CenterCrop(224),
    torchvision.transforms.ToTensor(),
    normalize])
    

2.3.2 定义和初始化模型

这是微调的核心步骤。你需要加载一个预训练模型,并对其进行修改。

  • 加载预训练模型: 使用 torchvision.models 加载一个预训练模型,例如 ResNet-18。通过设置 pretrained=True,PyTorch 会自动下载在 ImageNet 上训练好的模型参数。
pretrained_net = torchvision.models.resnet18(pretrained=True)
  • 修改输出层: 检查预训练模型的输出层。对于 ResNet-18,输出层是一个全连接层(fc),其输出维度是 1000(因为 ImageNet 有 1000 个类别)。我们需要将其替换为适应我们新任务(热狗和非热狗,共 2 个类别)的新层。
  • 随机初始化新层: 新的全连接层参数需要随机初始化,因为它是针对新任务从零开始学习的。使用 nn.init.xavier_uniform_ 可以进行良好的随机初始化。
# 获取预训练模型
finetune_net = torchvision.models.resnet18(pretrained=True)

# 获取原全连接层的输入特征数
num_features = finetune_net.fc.in_features

# 替换为新的全连接层,输出类别数为2
finetune_net.fc = nn.Linear(num_features, 2)

# 随机初始化新层的参数
nn.init.xavier_uniform_(finetune_net.fc.weight)

2.3.3 设置训练参数

在微调过程中,训练参数的设置至关重要。

  • 不同的学习率: 预训练部分的参数(所有层,除了最后一层)已经训练得很好,我们只需要对其进行微调,所以使用较小的学习率。而新初始化的输出层需要从头开始训练,所以使用更大的学习率。通常,输出层的学习率设置为其他层的几倍(例如 10 倍)。
  • 定义优化器: 使用 torch.optim.SGD 优化器,并为不同参数组设置不同的学习率。
# 将参数分为两组:预训练层和新输出层
params_1x = [param for name, param in net.named_parameters()
             if name not in ["fc.weight", "fc.bias"]]

trainer = torch.optim.SGD([{'params': params_1x},  # 预训练层
                           {'params': net.fc.parameters(), 'lr': learning_rate * 10}], # 新输出层
                          lr=learning_rate,
                          weight_decay=0.001)

2.3.4 训练模型

最后,调用训练函数进行微调。

  • 训练函数: 使用一个通用的训练函数,它接受网络、学习率、批量大小和迭代次数等参数。
  • 比较: 为了证明微调的有效性,可以与一个从头开始训练的相同模型进行比较。从头训练的模型需要更大的学习率,并且通常表现不如微调模型,因为它的初始参数是随机的。
# 如果param_group=True,输出层中的模型参数将使用十倍的学习率
def train_fine_tuning(net, learning_rate, batch_size=128, num_epochs=5,
                      param_group=True):
    train_iter = torch.utils.data.DataLoader(torchvision.datasets.ImageFolder(
        os.path.join(data_dir, 'train'), transform=train_augs),
        batch_size=batch_size, shuffle=True)
    test_iter = torch.utils.data.DataLoader(torchvision.datasets.ImageFolder(
        os.path.join(data_dir, 'test'), transform=test_augs),
        batch_size=batch_size)
    devices = d2l.try_all_gpus()
    loss = nn.CrossEntropyLoss(reduction="none")
    if param_group:
        params_1x = [param for name, param in net.named_parameters()
             if name not in ["fc.weight", "fc.bias"]]
        trainer = torch.optim.SGD([{'params': params_1x},
                                   {'params': net.fc.parameters(),
                                    'lr': learning_rate * 10}],
                                lr=learning_rate, weight_decay=0.001)
    else:
        trainer = torch.optim.SGD(net.parameters(), lr=learning_rate,
                                  weight_decay=0.001)
    d2l.train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs,
                   devices)

# 微调模型,使用较小的学习率
train_fine_tuning(finetune_net, 5e-5)

# 从头训练的模型,使用较大的学习率
# scratch_net = torchvision.models.resnet18()
# scratch_net.fc = nn.Linear(scratch_net.fc.in_features, 2)
# train_fine_tuning(scratch_net, 5e-4, param_group=False)
### 深度学习笔记中的数据增广技术细节 在处理图像分类任务时,为了提升模型性能并增强其泛化能力,采用数据增广是一种有效手段。通过应用各种变换操作于原始图片之上,不仅能够扩充可用的数据量,而且有助于减少过拟合现象的发生。 #### 图像增广的作用机制 通过对训练图像执行一系列随机变化来创建相似却有所区别的新样本,以此方式增加训练集多样性[^3]。具体而言: - **扩展数据规模**:生成更多样化的输入实例供网络学习; - **减轻偏差影响**:防止模型过分关注特定特征或模式; - **改善泛化表现**:使模型更稳健地应对未曾见过的真实场景下的测试案例; #### 常见的图像增广方法 针对不同应用场景需求,可以选择多种类型的转换策略组合使用,包括但不限于以下几种基本形式: - **几何变形**:如翻转(水平/垂直)、旋转、缩放和平移等; - **颜色调整**:亮度调节、对比度修改、饱和度变动及色调偏移等; - **噪声注入**:向原图添加高斯白噪或其他形式干扰信号; - **裁剪填充**:截取部分区域作为新的子图或将边界处补全至指定尺寸; #### 使用PyTorch实现简单的图像增广流程 下面给出一段基于`torchvision.transforms`库构建自定义Transform对象的例子,该对象可以在加载器阶段自动应用于每一批次传入的数据上: ```python from torchvision import transforms transform = transforms.Compose([ transforms.RandomHorizontalFlip(), # 随机水平翻转 transforms.ColorJitter(brightness=0.2, contrast=0.2), # 调整色彩属性 transforms.ToTensor() # 将PIL Image 或 numpy.ndarray转化为tensor ]) ``` 上述代码片段展示了如何利用链式调用来串联多个预处理步骤,并最终形成一个完整的转化管道。每当有新批次的数据被读取进来之后,都会依次经历这里所设定的各项处理环节。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蔗理苦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值