Alex_McAvoy

想要成为渔夫的猎手

数据增强

【概述】

在神经网络中,对于图片数据集中的数据,不仅要求数据相关,而且要求有尽可能大的数据量,但要想得到大量的数据,不仅要收集各种情景下、各种角度、各个位置的照片,还要确保数据的多样性,只有这样才能确保神经网络学到的特征更加全面

在现实中,若想达到以上的目的要付出巨大的代价,并且还要对照片上出现的东西进行准确标注,另外对于一些稀有的物种信息收集更是十分困难

数据增强(Data Augmentation)是一种数据扩充技术,其通过剪切、旋转、反射、翻转变换、缩放变换、平移变换、尺度变换、对比度变换、噪声扰动、颜色变换等手段对原数据集进行变换,从而扩充数据集

对于数据增强,有两种方式:

  1. 离线增强(Offline Augmentation):在模型读入数据集前,进行所有转换,从根本上增加数据集的规模,该方式适用于较小的数据集
  2. 在线增强(Online Augmentation):在模型读入数据时,对即将输入模型的小批量数据进行相应的转换,这种方式适用于较大的数据集

【神经网络不变性】

在神经网络中,如果其能够对不同的位置的物品也能稳健的分类,就被称为具有不变性

对于卷积神经网络,其对移位(translation)、视角(viewpoint)、大小(size)、照明(illumination),或以上的组合具有不变性

在现实场景中,可能会有一批在有限场景中拍摄的数据集,但可能应用在不同的条件下,比如在不同的方向、位置、缩放比例、亮度等

此时,可以通过额外合成数据来实现数据增强,进而训练神经网络

【减小不相关特征】

假设有一车辆数据集,其中存在两类车,一类是品牌 $A$,另一类是品牌 $B$,同时,所有品牌 $A$ 的车朝向左边,所有品牌 $B$ 的车朝向右边

在通过神经网络完成训练后,输入一个朝向右边的品牌 $A$ 的图像,此时神经网络输出却认为其是品牌 $B$

这是因为网络会寻找区分一个类与另一个类的最明显的特征,在上述的例子中,这个特征就是所有品牌 $A$ 的车朝向左边,品牌 $B$ 的车朝向右边

对于这种问题,可以通过翻转、剪切等手段来进行数据增强,从而减少数据集中不相关特征的数量,进而防止神经网络学习到不相关的模型,从而提升神经网络的效果

【常用技术】

翻转

翻转(Flip)是对图片进行水平和垂直翻转

如下图,从左到右分别为原图、水平翻转、垂直翻转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import torch
import torch.nn
import torch.nn.functional as F
from torchvision import datasets, transforms

train_loader = torch.utils.data.DataLoader(
datasets.MNIST('./data', train=True, download=True,
transform=transforms.Compose([
transforms.RandomHorizontalFlip(),# 水平翻转
transforms.RandomVerticalFlip(), # 上下翻转
transforms.ToTensor(),
])),
batch_size=batch_size, shuffle=True
)

剪切

剪切(Crop)是从原始图像中随机抽样一个部分,然后将此部分的大小调整为原始图像大小

如下图,从左到右分别为原图、左上角剪切图像、右下角剪切图像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import torch
import torch.nn
import torch.nn.functional as F
from torchvision import datasets, transforms

train_loader = torch.utils.data.DataLoader(
datasets.MNIST('./data', train=True, download=True,
transform=transforms.Compose([
transforms.Resize([32,32]), # 重设图像大小
transforms.RandomCrop([28, 28]), # 随机剪切
transforms.ToTensor()
])),
batch_size=batch_size, shuffle=True
)

噪声

当神经网络视图学习无用的高频特征时,通常会出现过拟合,噪声(Noise)可以有效的扭曲高频特征,适量的增强学习能力

通常会采用高斯噪声来对原图进行处理,模糊半径越大,正态分布标准差越大,图像就越模糊

如下图,从左到右分别为原图、加入高斯噪声图片、加盐的噪声图片

1
2
3
4
5
6
7
8
9
10
11
12
13
import torch
import torch.nn
import torch.nn.functional as F
from torchvision import datasets, transforms

train_loader = torch.utils.data.DataLoader(
datasets.MNIST('./data', train=True, download=True,
transform=transforms.Compose([
transforms..GaussianBlur(20, 10), # 加入模糊半径为20,标准差为10的高斯噪声
transforms.ToTensor()
])),
batch_size=batch_size, shuffle=True
)

旋转

旋转(Rotation)是将图片进行一定的角度的旋转,要求旋转后的图像仍与原图像尺寸大小相同

如果图片是正方形的,那么以 $90°$ 旋转将会保持图像大小,但如果图片是长方形的,那么只有 $180°$ 的旋转将会保持原来的大小

如下图,从左到右分别为原图、顺时针旋转 $90°$ 图像、顺时针旋转 $180°$ 图像、顺时针旋转 $270°$ 图像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import torch
import torch.nn
import torch.nn.functional as F
from torchvision import datasets, transforms

train_loader = torch.utils.data.DataLoader(
datasets.MNIST('./data', train=True, download=True,
transform=transforms.Compose([
transforms.RandomRotation(15), # 随机旋转-15°~15°
transforms.RandomRotation([90, 180]), # 随机在90°、180°中选一个度数来旋转,如果想有一定概率不旋转,可以加一个0进去
transforms.ToTensor(),
])),
batch_size=batch_size, shuffle=True
)

当对图片旋转一定角度后,需要保留原始图像大小,但由于图像没有关于其边界之外的任何信息,此时通常假设边界外的每个点都是 $0$,即得到一个未定义图像的黑色区域

此时需要一定的算法,来对黑色区域进行填充,关于这部分的内容,详见 插值填充 部分

缩放

缩放(Scale)是将图像按比例向外或向内缩放

向外缩放时,最终图像尺寸将大于原始图像尺寸,因此需要从向外缩放的新图像中剪切出一个部分,使其大小等于原始图像

如下图,从左到右依次为:原图、向外缩放 $10\%$、向外缩放 $20\%$

1
2
3
4
5
6
7
8
9
10
11
12
13
import torch
import torch.nn
import torch.nn.functional as F
from torchvision import datasets, transforms

train_loader = torch.utils.data.DataLoader(
datasets.MNIST('../data', train=True, download=True,
transform=transforms.Compose([
transforms.Resize([32,32]), # 缩放
transforms.ToTensor()
])),
batch_size=batch_size, shuffle=True
)

向内缩放时,最终图像尺寸将小于原始图像尺寸,而为使新图像与原图像尺寸相同,此时通常假设边界外的每个点都是 $0$,即得到一个未定义图像的黑色区域

此时需要一定的算法,来对黑色区域进行填充,关于这部分的内容,详见 插值填充 部分

移位

移位(Translation)是将将原图像框固定,然后按 $x$ 轴、$y$ 轴或两者结合的方向移动图像

移动时,当新图像超出原图像框,通常假设边界外的每个点都是 $0$,即得到一个未定义图像的黑色区域

此时需要一定的算法,来对黑色区域进行填充,关于这部分的内容,详见 插值填充 部分

如下图,从左到右依次为:原图、向右移位、向上移位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import torch
import torch.nn
import torch.nn.functional as F
from torchvision import datasets, transforms

train_loader = torch.utils.data.DataLoader(
datasets.MNIST('./data', train=True, download=True,
transform=transforms.Compose([
# 在(-img_width*0.1,img_width*0.1)范围内随机水平平移
# 在(-img_height*0.2,img_height*0.2)范围内随机垂直平移
transforms.RandomAffine(0, (0.2, 0.2)),
transforms.ToTensor()
])),
batch_size=batch_size, shuffle=True
)

值得注意的是,对于 PyTorch 的 RandomAffine() 函数,其汇总了旋转、平移、缩放等图像变换方法,并且支持叠加

插值填充

对于旋转、向内缩放、移位造成的黑色区域,通常采用插值(Interpolation)的方法进行填充,常见的填充方式有:

  • 常数(Constant):使用常数值填充未知区域,这可能不适用于自然图像,但可以用于在单色背景下拍摄的图像
  • 边界(Edge):在边界上扩展图像的边缘值,该方法适用于移位
  • 反射(Reflect):图像像素值沿图像边界反射,该方法适用于包含树木,山脉等的连续或自然背景
  • 对称(Symmetric):该方法类似于反射,只是不在反射边界处制作边缘像素的副本,通常反射与对称可以互换使用
  • 包裹(Wrap):令图像只是重复超出其边界,就好像它正在平铺一样,该方法并不像普遍使用

如下图,从左到右依次为:常数插值、边缘插值、反射插值、对称插值、包裹插值

感谢您对我的支持,让我继续努力分享有用的技术与知识点!