YOLO入门教程(番外):计算机视觉—图像增广
1、图像增广
大型数据集是成功应用深度神经网络的先决条件。
图像增广在对训练图像进行一系列的随机变化之后,生成相似但不同的训练样本,从而扩大了训练集的规模。
此外,应用图像增广的原因是,随机改变训练样本可以减少模型对某些属性的依赖,从而提高模型的泛化能力。
例如,我们可以以不同的方式裁剪图像,使感兴趣的对象出现在不同的位置,减少模型对于对象出现位置的依赖。 我们还可以调整亮度、颜色等因素来降低模型对颜色的敏感度。
可以说,图像增广技术对于AlexNet的成功是必不可少的。本节将讨论这项广泛应用于计算机视觉的技术。
# 这是Jupyter Notebook/JupyterLab的魔法命令,用于在Notebook中内嵌显示Matplotlib图形
# 在常规Python脚本中不需要此命令
%matplotlib inline# 导入PyTorch深度学习框架的核心库
# PyTorch提供了张量计算、自动求导和神经网络构建等功能
import torch# 导入TorchVision计算机视觉库
# 提供常用的数据集、模型架构和图像转换工具
import torchvision# 从PyTorch中导入神经网络模块(nn)
# 包含各种神经网络层、损失函数和模型容器
from torch import nn# 从d2l库中导入PyTorch相关的实用函数
# d2l是《动手学深度学习》教材的配套工具库,提供可视化、数据加载等辅助功能
from d2l import torch as d2l
1.1 常用的图像增广方法
在对常用图像增广方法的探索时,我们将使用下面这个尺寸为400 x 500 的图像作为示例:
# 设置Matplotlib图表输出的默认大小,使其在Jupyter Notebook中显示得更大、更清晰
# 这是一个d2l库提供的便利函数,避免了手动设置figure size的麻烦
d2l.set_figsize()# 使用d2l库封装的Image.open方法打开一张图片
# 注意:这里的路径'../img/cat1.jpg'是一个相对路径,表示上一级目录的img文件夹中的cat1.jpg文件
# d2l.Image实际上是PIL.Image的引用,这是一个强大的图像处理库
img = d2l.Image.open('../img/cat1.jpg')# 使用Matplotlib的imshow函数显示图像
# d2l.plt是matplotlib.pyplot的别名,由之前的from d2l import torch as d2l导入
# 行末的分号(;)用于抑制不必要的文本输出(如图像数组信息),只显示图像本身
d2l.plt.imshow(img);
大多数图像增广方法都具有一定的随机性。
为了便于观察图像增广的效果,我们下面定义辅助函数apply。 此函数在输入图像img上多次运行图像增广方法aug并显示所有结果。
【图片】
# 定义一个名为apply的函数,用于对输入图像应用多次数据增强并可视化结果
# 参数说明:
# img: 输入的原始图像(通常是PIL图像对象或张量)
# aug: 数据增强函数,该函数接收一个图像并返回增强后的图像
# num_rows: 显示结果图像的行数,默认为2行
# num_cols: 显示结果图像的列数,默认为4列
# scale: 图像显示的缩放比例,默认为1.5倍
def apply(img, aug, num_rows=2, num_cols=4, scale=1.5):# 使用列表推导式生成多个增强后的图像# 对原始图像应用aug函数num_rows*num_cols次(默认为8次)# 每次应用都会产生一个随机增强版本的图像# 下划线_表示我们不需要循环变量的值,只需要执行指定次数的循环Y = [aug(img) for _ in range(num_rows * num_cols)]# 使用d2l库的show_images函数将所有增强后的图像显示在一个网格中# 参数说明:# Y: 包含所有增强后图像的列表# num_rows: 网格行数# num_cols: 网格列数# scale: 图像显示大小的缩放因子d2l.show_images(Y, num_rows, num_cols, scale=scale)
1.1.1. 翻转和裁剪
左右翻转图像通常不会改变对象的类别。
这是最早且最广泛使用的图像增广方法之一。
接下来,我们使用transforms模块来创建RandomFlipLeftRight实例,这样就各有50%的几率使图像向左或向右翻转。
# 调用之前定义的apply函数,对图像应用随机水平翻转增强
# 参数说明:
# img: 之前加载的猫的图像
# torchvision.transforms.RandomHorizontalFlip(): 创建随机水平翻转增强器
apply(img, torchvision.transforms.RandomHorizontalFlip())
【图片】
上下翻转图像不如左右图像翻转那样常用。
但是,至少对于这个示例图像,上下翻转不会妨碍识别。接下来,我们创建一个RandomFlipTopBottom实例,使图像各有50%的几率向上或向下翻转。
# 调用apply函数,对图像应用随机垂直翻转增强
# 参数说明:
# img: 之前加载的猫的图像
# torchvision.transforms.RandomVerticalFlip(): 创建随机垂直翻转增强器
apply(img, torchvision.transforms.RandomVerticalFlip())
图片
在我们使用的示例图像中,猫位于图像的中间,但并非所有图像都是这样。 在 6.5节中,我们解释了汇聚层可以降低卷积层对目标位置的敏感性。
另外,我们可以通过对图像进行随机裁剪,使物体以不同的比例出现在图像的不同位置。 这也可以降低模型对目标位置的敏感性。
下面的代码将随机裁剪一个面积为原始面积10%到100%的区域,该区域的宽高比从0.5~2之间随机取值。 然后,区域的宽度和高度都被缩放到200像素。 在本节中(除非另有说明),a
和 b 之间的随机数指的是在区间 [a, b]中通过均匀采样获得的连续值。
# 创建一个随机缩放裁剪的数据增强器
# 参数说明:
# (200, 200): 输出图像的尺寸,所有增强后的图像都会被调整为200×200像素
# scale=(0.1, 1): 随机裁剪面积比例范围,从原始图像的10%到100%
# ratio=(0.5, 2): 随机宽高比范围,从1:2到2:1之间
shape_aug = torchvision.transforms.RandomResizedCrop((200, 200), scale=(0.1, 1), ratio=(0.5, 2))# 应用形状增强并可视化结果
apply(img, shape_aug)
1.1.2. 改变颜色
另一种增广方法是改变颜色。 我们可以改变图像颜色的四个方面:亮度、对比度、饱和度和色调。 在下面的示例中,我们随机更改图像的亮度,随机值为原始图像的50%(1-0.5)到150%(1+0.5)之间。
# 应用颜色增强,只调整图像亮度
# 使用torchvision.transforms.ColorJitter创建颜色抖动增强器
# 参数说明:
# brightness=0.5: 亮度调整强度为0.5(最大亮度变化±50%)
# contrast=0: 对比度不进行调整
# saturation=0: 饱和度不进行调整
# hue=0: 色调不进行调整
apply(img, torchvision.transforms.ColorJitter(brightness=0.5, contrast=0, saturation=0, hue=0))
同样,我们可以随机更改图像的色调。
# 应用颜色增强,只调整图像色调
# 使用torchvision.transforms.ColorJitter创建颜色抖动增强器
# 参数说明:
# brightness=0: 亮度不进行调整
# contrast=0: 对比度不进行调整
# saturation=0: 饱和度不进行调整
# hue=0.5: 色调调整强度为0.5(最大色调变化±0.5,但在实际中会被限制到合理范围)
apply(img, torchvision.transforms.ColorJitter(brightness=0, contrast=0, saturation=0, hue=0.5))
我们还可以创建一个RandomColorJitter实例,并设置如何同时随机更改图像的亮度(brightness)、对比度(contrast)、饱和度(saturation)和色调(hue)。
# 创建一个综合的颜色抖动增强器,同时调整亮度、对比度、饱和度和色调
# 参数说明:
# brightness=0.5: 亮度调整强度为0.5(最大亮度变化±50%)
# contrast=0.5: 对比度调整强度为0.5(最大对比度变化±50%)
# saturation=0.5: 饱和度调整强度为0.5(最大饱和度变化±50%)
# hue=0.5: 色调调整强度为0.5(最大色调变化±0.5)
color_aug = torchvision.transforms.ColorJitter(brightness=0.5, contrast=0.5, saturation=0.5, hue=0.5)# 应用综合颜色增强并可视化结果
apply(img, color_aug)
1.1.3. 结合多种图像增广方法
在实践中,我们将结合多种图像增广方法。
比如,我们可以通过使用一个Compose实例来综合上面定义的不同的图像增广方法,并将它们应用到每个图像。
# 典型的训练数据增强管道
train_transforms = torchvision.transforms.Compose([torchvision.transforms.RandomResizedCrop(224), # 随机裁剪torchvision.transforms.RandomHorizontalFlip(), # 随机翻转torchvision.transforms.ColorJitter(0.4, 0.4, 0.4), # 颜色增强torchvision.transforms.ToTensor(), # 转换为张量torchvision.transforms.Normalize(mean, std) # 标准化
])# 验证/测试数据通常使用较简单的变换
val_transforms = torchvision.transforms.Compose([torchvision.transforms.Resize(256), # 调整大小torchvision.transforms.CenterCrop(224), # 中心裁剪torchvision.transforms.ToTensor(), # 转换为张量torchvision.transforms.Normalize(mean, std) # 标准化
])
1.2. 使用图像增广进行训练
让我们使用图像增广来训练模型。 这里,我们使用CIFAR-10数据集,而不是我们之前使用的Fashion-MNIST数据集。 这是因为Fashion-MNIST数据集中对象的位置和大小已被规范化,而CIFAR-10数据集中对象的颜色和大小差异更明显。 CIFAR-10数据集中的前32个训练图像如下所示。
# 加载CIFAR-10训练数据集
# 参数说明:
# train=True: 加载训练集(如果为False则加载测试集)
# root="../data": 指定数据集存储的根目录,这里为上级目录的data文件夹
# download=True: 如果数据集不存在,则自动下载
all_images = torchvision.datasets.CIFAR10(train=True, root="../data",download=True)# 显示CIFAR-10数据集中的32张样本图像
# 参数说明:
# [all_images[i][0] for i in range(32)]: 获取数据集中前32个样本的图像部分
# 4: 显示4行图像
# 8: 显示8列图像(4×8=32张图像)
# scale=0.8: 图像显示缩放比例为0.8
d2l.show_images([all_images[i][0] for i in range(32)], 4, 8, scale=0.8)
Downloading https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz to ../data/cifar-10-python.tar.gz0%| | 0/170498071 [00:00<?, ?it/s]Extracting ../data/cifar-10-python.tar.gz to ../data
为了在预测过程中得到确切的结果,我们通常对训练样本只进行图像增广,且在预测过程中不使用随机操作的图像增广。
在这里,我们只使用最简单的随机左右翻转。 此外,我们使用ToTensor实例将一批图像转换为深度学习框架所要求的格式,即形状为(批量大小,通道数,高度,宽度)的32位浮点数,取值范围为0~1。
# 定义训练数据的数据增强和预处理管道
# 训练阶段通常使用数据增强来提高模型的泛化能力
train_augs = torchvision.transforms.Compose([# 随机水平翻转增强:以50%的概率水平翻转图像# 这是一种简单但有效的数据增强技术,增加训练数据的多样性torchvision.transforms.RandomHorizontalFlip(),# 将PIL图像或numpy数组转换为PyTorch张量# 同时会自动将像素值从[0, 255]范围缩放到[0.0, 1.0]范围# 这是必须的步骤,因为PyTorch模型需要张量作为输入torchvision.transforms.ToTensor()
])# 定义测试数据的预处理管道
# 测试阶段通常不使用数据增强,以保持数据的原始分布
test_augs = torchvision.transforms.Compose([# 只进行必要的转换:将图像转换为张量# 不包含任何随机增强,确保测试结果的可重复性和公平性torchvision.transforms.ToTensor()
])
接下来,我们定义一个辅助函数,以便于读取图像和应用图像增广。PyTorch数据集提供的transform参数应用图像增广来转化图像。
# 定义一个通用的CIFAR-10数据加载函数
# 参数说明:
# is_train: 布尔值,True表示加载训练集,False表示加载测试集
# augs: 数据增强和预处理管道,根据训练/测试阶段传入不同的预处理方式
# batch_size: 批量大小,指定每个批次包含的样本数量
def load_cifar10(is_train, augs, batch_size):# 创建CIFAR-10数据集对象# 参数说明:# root="../data": 数据集存储的根目录# train=is_train: 根据is_train参数决定加载训练集还是测试集# transform=augs: 应用传入的数据增强和预处理管道# download=True: 如果数据集不存在则自动下载dataset = torchvision.datasets.CIFAR10(root="../data", train=is_train,transform=augs, download=True)# 创建数据加载器(DataLoader),用于批量加载数据# 参数说明:# dataset: 上面创建的数据集对象# batch_size=batch_size: 每个批次包含的样本数量# shuffle=is_train: 仅在训练时打乱数据顺序(增强随机性)# num_workers=d2l.get_dataloader_workers(): 使用d2l库提供的优化工作线程数dataloader = torch.utils.data.DataLoader(dataset, batch_size=batch_size,shuffle=is_train, num_workers=d2l.get_dataloader_workers())# 返回创建好的数据加载器return dataloader
1.2.1. 多GPU训练
我们在CIFAR-10数据集上训练 ResNet-18模型。 回想一下 对多GPU训练的介绍。 接下来,我们定义一个函数,使用多GPU对模型进行训练和评估。
#@save # 这是一个特殊的注释,表示这个函数应该被保存到d2l库中
def train_batch_ch13(net, X, y, loss, trainer, devices):"""用多GPU进行小批量训练"""# 处理输入数据X:如果X是列表(例如BERT需要多个输入),将每个元素移动到设备# 否则直接将整个X张量移动到设备if isinstance(X, list):# 微调BERT中所需(处理多个输入的情况)X = [x.to(devices[0]) for x in X]else:X = X.to(devices[0])# 将标签y移动到设备y = y.to(devices[0])# 设置模型为训练模式(启用dropout、batch norm等训练特定行为)net.train()# 清空优化器中的梯度(防止梯度累积)trainer.zero_grad()# 前向传播:计算模型预测pred = net(X)# 计算损失l = loss(pred, y)# 反向传播:计算梯度# 使用.sum()是因为在多GPU环境下,损失可能是分布计算的l.sum().backward()# 更新模型参数trainer.step()# 计算当前批次的损失总和和准确率train_loss_sum = l.sum()train_acc_sum = d2l.accuracy(pred, y)return train_loss_sum, train_acc_sum
#@save # 这是一个特殊的注释,表示这个函数应该被保存到d2l库中
def train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs,devices=d2l.try_all_gpus()):"""用多GPU进行模型训练"""# 初始化计时器和批次计数器timer, num_batches = d2l.Timer(), len(train_iter)# 创建动画绘制器,用于可视化训练过程animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs], ylim=[0, 1],legend=['train loss', 'train acc', 'test acc'])# 使用DataParallel将模型复制到多个GPU上# devices[0]是主设备,所有GPU的梯度会在这里聚合net = nn.DataParallel(net, device_ids=devices).to(devices[0])# 开始训练循环for epoch in range(num_epochs):# 创建累加器,用于累积4个指标:损失总和、准确率总和、样本数、预测数metric = d2l.Accumulator(4)# 遍历训练数据集的每个批次for i, (features, labels) in enumerate(train_iter):# 开始计时timer.start()# 训练一个批次l, acc = train_batch_ch13(net, features, labels, loss, trainer, devices)# 累积指标:损失、准确率、样本数量、元素总数metric.add(l, acc, labels.shape[0], labels.numel())# 停止计时timer.stop()# 每完成1/5的批次或最后一个批次时,更新可视化if (i + 1) % (num_batches // 5) == 0 or i == num_batches - 1:animator.add(epoch + (i + 1) / num_batches,(metric[0] / metric[2], metric[1] / metric[3],None))# 在每个epoch结束后,计算测试集准确率test_acc = d2l.evaluate_accuracy_gpu(net, test_iter)# 更新可视化,添加测试准确率animator.add(epoch + 1, (None, None, test_acc))# 打印最终结果print(f'loss {metric[0] / metric[2]:.3f}, train acc 'f'{metric[1] / metric[3]:.3f}, test acc {test_acc:.3f}')# 打印训练速度(每秒处理的样本数)print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec on 'f'{str(devices)}')
现在,我们可以定义train_with_data_aug函数,使用图像增广来训练模型。
该函数获取所有的GPU,并使用Adam作为训练的优化算法,将图像增广应用于训练集,最后调用刚刚定义的用于训练和评估模型的train_ch13函数。
# 设置训练的超参数和模型
# batch_size: 批量大小设置为256,这是一个适合多GPU训练的中等批量大小
# devices: 自动检测并尝试使用所有可用的GPU
# net: 创建一个ResNet-18模型,输出类别数为10(CIFAR-10的10个类别),输入通道数为3(RGB图像)
batch_size, devices, net = 256, d2l.try_all_gpus(), d2l.resnet18(10, 3)# 定义权重初始化函数
# 这个函数将应用于网络的每一层
def init_weights(m):# 检查当前模块是否为线性层或卷积层if type(m) in [nn.Linear, nn.Conv2d]:# 使用Xavier均匀初始化方法初始化权重# 这种方法可以根据输入输出维度自动调整初始化范围,有助于训练稳定性nn.init.xavier_uniform_(m.weight)# 将权重初始化函数应用到网络的所有层
net.apply(init_weights)# 定义完整的数据增强训练函数
# 参数说明:
# train_augs: 训练数据的数据增强管道
# test_augs: 测试数据的数据增强管道
# net: 要训练的神经网络模型
# lr: 学习率,默认为0.001
def train_with_data_aug(train_augs, test_augs, net, lr=0.001):# 加载训练数据,应用训练数据增强train_iter = load_cifar10(True, train_augs, batch_size)# 加载测试数据,应用测试数据预处理(通常不包含随机增强)test_iter = load_cifar10(False, test_augs, batch_size)# 定义损失函数:交叉熵损失# reduction="none"表示不自动求平均或求和,保留每个样本的损失# 这样可以在train_ch13中更灵活地处理损失loss = nn.CrossEntropyLoss(reduction="none")# 定义优化器:Adam优化器# Adam是常用的自适应学习率优化算法,通常不需要太多调参trainer = torch.optim.Adam(net.parameters(), lr=lr)# 调用之前定义的多GPU训练函数进行训练# 参数说明: 网络、训练迭代器、测试迭代器、损失函数、优化器、训练轮数、设备train_ch13(net, train_iter, test_iter, loss, trainer, 10, devices)
让我们使用基于随机左右翻转的图像增广来训练模型。
# 启动数据增强训练流程
# 使用之前定义的:
# - train_augs: 训练数据增强管道(包含随机水平翻转和转换为张量)
# - test_augs: 测试数据预处理管道(仅转换为张量)
# - net: 已经初始化的ResNet-18模型
train_with_data_aug(train_augs, test_augs, net)
loss 0.173, train acc 0.941, test acc 0.854
4183.9 examples/sec on [device(type='cuda', index=0), device(type='cuda', index=1)]
1.3. 小结
-
图像增广基于现有的训练数据生成随机图像,来提高模型的泛化能力。
-
为了在预测过程中得到确切的结果,我们通常对训练样本只进行图像增广,而在预测过程中不使用带随机操作的图像增广。
-
深度学习框架提供了许多不同的图像增广方法,这些方法可以被同时应用。
1.4. 练习
- 在不使用图像增广的情况下训练模型:train_with_data_aug(no_aug, no_aug)。比较使用和不使用图像增广的训练结果和测试精度。这个对比实验能支持图像增广可以减轻过拟合的论点吗?为什么?
- 定义“不使用图像增广”的预处理管道
首先,我们需要定义一个不做任何随机增强的预处理管道。这通常只包括将图像转换为张量,有时还包括归一化(但为了公平对比,我们暂时不使用归一化)。
# 定义不使用任何数据增强的预处理管道
# 只进行最基本的转换:将PIL图像转换为PyTorch张量
no_aug = torchvision.transforms.Compose([torchvision.transforms.ToTensor()
])
- 执行对比实验
现在我们在完全相同的条件下运行两个实验,唯一变量是是否使用数据增强。
实验1:不使用图像增广
# 重新初始化网络权重,确保公平比较
net.apply(init_weights)
print("训练不使用图像增广的模型...")
train_with_data_aug(no_aug, no_aug, net)
实验2:使用图像增广(您之前已经运行的)
# 重新初始化网络权重
net.apply(init_weights)
print("训练使用图像增广的模型...")
train_with_data_aug(train_augs, test_augs, net)
- 预期结果分析
基于理论和实践经验,我们预期会观察到以下模式:
指标 | 不使用图像增广 | 使用图像增广 | 说明 |
---|---|---|---|
训练损失 | 较低 (如0.05-0.15) | 较高 (如0.1-0.3) | 增广使训练任务变难 |
训练准确率 | 较高 (如95%-99%) | 较低 (如90%-96%) | 增广防止模型过拟合训练集 |
测试准确率 | 较低 (如80%-88%) | 较高 (如85%-92%) | 增广提高模型泛化能力 |
泛化差距 (训练acc-测试acc) | 较大 (如10%-15%) | 较小 (如3%-8%) | 增广减小过拟合程度 |
- 这个对比实验能支持"图像增广可以减轻过拟合"的论点吗?
是的,完全支持。 原因如下:
理论机制:
-
增加数据多样性:图像增广通过随机变换(翻转、裁剪、颜色调整等)创建了"新"的训练样本,相当于扩大了训练集规模,使模型看到更多样的数据变体。
-
引入归纳偏置:增广技术编码了我们对视觉不变性的先验知识(如物体翻转后类别不变、颜色变化后类别不变等),引导模型学习更本质的特征。
-
正则化效应:随机增广相当于向训练过程注入噪声,起到正则化作用,防止模型过度依赖训练集中的特定像素模式。
预期实验证据:
如果观察到以下模式,就强有力地支持了图像增广减轻过拟合的论点:
- 不使用增广:训练准确率很高但测试准确率相对较低,两者差距大 → 明显的过拟合
- 使用增广:训练准确率可能略低但测试准确率更高,两者差距小 → 更好的泛化能力
为什么这种对比有效:
-
控制变量:两个实验使用相同的网络架构、初始化权重、超参数和训练轮数,唯一区别是是否使用增广。
-
直接测量过拟合:过拟合的本质是模型在训练集上表现很好但在未见数据上表现差。通过比较训练-测试性能差距,可以直接量化过拟合程度。
-
可重复的结论:这个模式在大量学术研究和实践中被反复验证,是计算机视觉中的基本共识。
- 实际项目中的意义
这个实验不仅验证了理论,还具有重要实践价值:
- 指导模型设计:当发现模型过拟合时(训练远好于测试),首先应该考虑增加数据增强。
- 超参数调优:可以通过调整增强强度来平衡拟合能力和泛化能力。
- 资源分配:相比于收集更多真实数据,数据增强是一种低成本、高效率的提高模型性能的方法。
通过这个对比实验,您可以直观地看到数据增强如何像"魔法"一样,通过创造性的方式利用现有数据,显著提升模型的泛化性能,这是深度学习实践中最重要的技术之一。
- 在基于CIFAR-10数据集的模型训练中结合多种不同的图像增广方法。它能提高测试准确性吗?
在大多数情况下,精心选择和组合多种不同的图像增广方法,几乎总是能提高模型在CIFAR-10上的测试准确性。**
但这并非一个简单的“是”或“否”的问题,其有效性取决于如何组合以及组合的强度。
核心结论:为什么组合增广能提高测试准确性?
根本原因在于减轻过拟合和提高模型泛化能力。
- CIFAR-10的本质:它是一个相对较小的数据集(50k训练图像),模型很容易记住训练集的特有噪声和细节,从而导致过拟合(训练精度高,测试精度低)。
- 组合增广的作用:通过组合多种增广方法(如裁剪+翻转+颜色抖动),你实际上为模型创造了近乎“无限”多样性的训练样本。模型无法再简单地记忆固定的训练图片,而是被迫学习更鲁棒、更本质的特征(例如,“猫”的概念,而不是“某一张在某个特定位置、有着特定亮度的猫图片”)。
有效的组合增广策略
一个经过实践检验的、用于CIFAR-10的标准组合增广策略通常包括:
# 一个强大的组合增广示例
train_augs = torchvision.transforms.Compose([torchvision.transforms.RandomResizedCrop(32, scale=(0.8, 1.0), ratio=(0.9, 1.1)), # 随机缩放裁剪torchvision.transforms.RandomHorizontalFlip(p=0.5), # 随机水平翻转torchvision.transforms.ColorJitter(brightness=0.2, # 亮度抖动contrast=0.2, # 对比度抖动saturation=0.2, # 饱和度抖动hue=0.1 # 色调抖动(要很小)),torchvision.transforms.ToTensor(),# 通常还会加上数据标准化torchvision.transforms.Normalize(mean=[0.4914, 0.4822, 0.4465],std=[0.2023, 0.1994, 0.2010])
])test_augs = torchvision.transforms.Compose([torchvision.transforms.ToTensor(),torchvision.transforms.Normalize(mean=[0.4914, 0.4822, 0.4465],std=[0.2023, 0.1994, 0.2010])
])
组合增广如何提升性能:一个假设性示例
让我们通过一个表格来对比不同策略的预期结果:
训练策略 | 训练精度 | 测试精度 | 泛化差距 | 说明 |
---|---|---|---|---|
无任何增广 | 非常高 (~98%) | 一般 (~86%) | 大 | 模型严重过拟合,记住了训练集噪声。 |
仅水平翻转 | 高 (~96%) | 较好 (~88%) | 中等 | 减轻了部分过拟合,但多样性仍不足。 |
组合增广 (裁剪+翻转+颜色) | 稍低 (~93%) | 最高 (~92%) | 小 | 模型无法记忆,被迫学习鲁棒特征,泛化能力最强。 |
关键点:请注意,使用组合增广后,训练精度可能会下降,但这是好事!这说明模型不再能轻易地“作弊”和过拟合。它学习速度变慢了,但学到的知识更扎实,最终在未见过的测试数据上表现更好。测试精度的提升和泛化差距的缩小是支持增广有效性的最直接证据。
需要注意的方面(为什么有时可能无效)
虽然理论上总是有效,但在实践中如果使用不当,也可能看不到提升甚至导致性能下降:
- 增广强度过大:如果颜色抖动得太离谱,或者裁剪得只剩下一小块无关的背景,图像的根本语义信息会被破坏。模型无法从这些“损坏”的图片中学习到有用的特征。例如,把一只猫的图片色调调整到完全变成蓝色,可能会让模型困惑。
- 不适合任务的增广:对于一些任务,某些增广是不合理的。例如,在数字识别中,垂直翻转“6”会变成近似“9”,这会引入错误标签。但在CIFAR-10中,水平翻转动物、车辆等物体通常是安全的。
- 计算开销:更复杂的增广 pipeline 会增加每个epoch的训练时间。但通常,由此带来的精度提升远高于其成本。
- 需要微调超参数:在使用强增广后,最优的学习率、权重衰减等超参数可能会发生变化,可能需要进行轻微的重新调整。
结论
是的,在CIFAR-10上,合理地组合多种图像增广方法是提高测试准确性、提升模型泛化能力的最有效且成本最低的策略之一。
它通过以下方式工作:
- 创造数据多样性:模拟了更多可能的输入变化。
- 引入正则化:防止模型过度依赖训练集中的特定视觉模式(如物体位置、颜色、大小)。
- 迫使模型学习更本质的特征:模型必须学会“猫”在各种变形下仍然是“猫”,从而学到其核心特征。
要获得最佳效果,建议从标准的、经过验证的组合开始(如上述代码示例),然后可以根据具体模型和任务进行微调。几乎所有在CIFAR-10上达到高精度的模型都广泛使用了组合数据增广技术。
- 参阅深度学习框架的在线文档。它还提供了哪些其他的图像增广方法?
除了之前提到的 RandomHorizontalFlip
, RandomResizedCrop
, 和 ColorJitter
,现代框架提供了丰富得多的工具库。
PyTorch (TorchVision) 中的其他增广方法
TorchVision 的 transforms
模块是功能最丰富的之一。以下是其提供的一些关键增广方法:
- 几何变换 (Geometric Transformations)
| 方法 | 描述 | 用途 |
| :— | :— | :— |
|RandomRotation
| 随机旋转图像一定角度(如 ±30°) | 增强旋转不变性,对纹理、数字等有效 |
|RandomAffine
| 随机仿射变换(旋转、平移、缩放、剪切) | 综合的几何形变,模拟视角变化 |
|RandomPerspective
| 随机透视变换 | 模拟3D视角的形变,更真实 |
|ElasticTransform
| 弹性变换(类似“橡皮筋”拉伸) | 模拟非刚性形变,对医学影像特别有用 |
- 颜色与色调变换 (Color & Tone Transformations)
| 方法 | 描述 | 用途 |
| :— | :— | :— |
|Grayscale
| 将图像转换为灰度图 | 减少模型对颜色的依赖,关注形状纹理 |
|RandomGrayscale
| 随机地以一定概率转换为灰度 | 更灵活,大部分图像保留颜色,偶尔丢弃 |
|RandomAdjustSharpness
| 随机调整锐度 | 模拟焦距变化,增强对模糊/清晰图像的鲁棒性 |
|RandomAutocontrast
| 自动调整对比度 | 优化图像对比度,模拟不同光照条件 |
|RandomPosterize
| 减少每个通道的位数(如8位->4位) | 模拟颜色深度低的设备,增强鲁棒性 |
|RandomSolarize
| 反转所有高于阈值的像素值 | 创造特殊视觉效果,有时能提升性能 |
|RandomEqualize
| 随机进行直方图均衡化 | 增强图像对比度 |
- 模糊与滤波器 (Blur & Filters)
| 方法 | 描述 | 用途 |
| :— | :— | :— |
|GaussianBlur
| 应用高斯模糊 | 模拟图像失焦或远距离拍摄,防止模型过度关注细节 |
|RandomInvert
| 随机反转像素颜色 | 创造负片效果,是一种强增广 |
- 自动增广 (Auto-Augmentation)
这是最强大的功能之一,它基于学习到的策略自动应用一系列增广。
| 方法 | 描述 |
| :— | :— |
|AutoAugment
| 应用在ImageNet上学习到的最佳增广策略 |
|RandAugment
| 更简单的自动增广,超参数更少,效果通常更好 |
|TrivialAugmentWide
| 另一种自动增广,计算成本低且性能优异 |
- 组合与随机选择
| 方法 | 描述 |
| :— | :— |
|RandomApply
| 随机地以给定概率应用一个变换列表中的某个变换 |
|RandomChoice
| 从一系列变换中随机选择一种应用 |
TensorFlow / Keras 中的增广方法
Keras 的 tf.keras.layers
提供了一些内置层,可以直接嵌入到模型中,在GPU上进行加速。
方法 (Layer) | 描述 |
---|---|
RandomRotation | 随机旋转 |
RandomZoom | 随机缩放(可能伴随裁剪) |
RandomTranslation | 随机平移 |
RandomContrast | 随机调整对比度 |
RandomCrop | 随机裁剪(注意:与ResizedCrop 不同,它不缩放回原大小) |
一个强大的组合增广示例代码 (PyTorch)
from torchvision import transforms# 一个更复杂、更强大的组合增广策略
advanced_aug = transforms.Compose([transforms.RandomResizedCrop(224, scale=(0.8, 1.0)), # 随机缩放裁剪transforms.RandomHorizontalFlip(p=0.5), # 随机水平翻转transforms.RandomVerticalFlip(p=0.05), # 偶尔随机垂直翻转(慎用)transforms.RandomRotation(degrees=15), # 随机旋转 ±15度transforms.ColorJitter(brightness=0.3, contrast=0.3, saturation=0.3, hue=0.1), # 颜色抖动transforms.RandomApply([transforms.GaussianBlur(kernel_size=(5, 5), sigma=(0.1, 2.0))], p=0.2), # 20%概率模糊transforms.RandomGrayscale(p=0.1), # 10%概率转灰度transforms.ToTensor(),transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])# 或者直接使用研究证明有效的自动增广策略
auto_aug = transforms.Compose([transforms.AutoAugment(transforms.AutoAugmentPolicy.IMAGENET), # 或 CIFAR10, SVHNtransforms.ToTensor(),transforms.Normalize(...)
])
总结:如何选择?
- 从简单开始:对于新任务,先从
RandomHorizontalFlip
+RandomResizedCrop
+ColorJitter
开始。 - 引入几何变换:如果任务需要旋转不变性(如卫星图像、医学影像),加入
RandomRotation
。 - 防止过拟合:如果模型开始过拟合,加入
GaussianBlur
或RandomGrayscale
来“破坏”细节。 - 使用“大招”:当你想进一步提升模型性能时,不要手动调参,直接尝试
RandAugment
或AutoAugment
。这些自动增广策略是在大量数据上学习出来的最优组合,通常比手动设计的更有效。
始终记住:增广的最终目的是让训练数据分布更接近真实、不可见的测试数据分布。选择哪种增广方法,很大程度上取决于你的数据集和任务本身的特点。