前言
我们在刚开始学习训练网络的时候,经常会遇到因为数据集不够大导致模型欠拟合,为了解决这个问题我们有两种常见的方法,其中之一简单粗暴即增大数据集,但是在数据采集有困难的情况下也有一些比较取巧的办法。数据增强说白了就是将原有数据进行变换,增强数据的丰富性,减少模型对一些实际意义不重要的特征的依赖。
1. transforms随机性
pytorch中最常用的对数据进行增强的工具当属torchvision中的transforms模块。Transforms顾名思义就是对数据进行变换。
shape_aug = torchvision.transforms.RandomResizedCrop(
(200, 200), scale=(0.1, 1), ratio=(0.5, 2))
shape_aug(img)
上面的代码使用transforms的RandomResizedCrop随机裁剪对输入图片进行裁剪。这样的操作是随机的,连续调用两次shape_aug得到的裁剪结果可能不一致。
2. transforms组合
我么可以利用transforms.Compose来将不同的数据变换按顺序组合起来。
比如这样
transformer = transforms.Compose([
transforms.Resize((224, 224)),
# 随机旋转
transforms.RandomRotation(15, expand=False),
transforms.RandomHorizontalFlip(p=0.1),
transforms.RandomVerticalFlip(p=0.1),
# 中心裁剪
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.85223039, 0.8524969, 0.8526602],
std=[0.27402772, 0.2744828, 0.2744293])
])
上面的代码展示了使用transforms.Compose将不同的transforms组合,记住这样的变换是有顺序的。
有时候我们使用的图像变换并不具有随机性,比如CenterCrop,transforms搭配了以下方法来增强随机性
-
transforms.RandomChoice:从给定的一系列transforms中选一个进行操作
-
transforms.RandomApply:给一个transforms加上概率,以一定的概率执行该操作
-
transforms.RandomOrder:将transforms中的操作顺序随机打乱
transformer = transforms.Compose([
transforms.RandomChoice([transforms.RandomVerticalFlip(p=1), transforms.RandomHorizontalFlip(p=1)]),
transforms.RandomApply([transforms.RandomAffine(degrees=0, shear=45, fillcolor=(255, 0, 0)),
transforms.Grayscale(num_output_channels=3)], p=0.5),
transforms.RandomOrder([transforms.RandomRotation(15),
transforms.Pad(padding=32),
transforms.RandomAffine(degrees=0, translate=(0.01, 0.1), scale=(0.9, 1.1))]),
transforms.ToTensor(),
transforms.Normalize(mean=[0.85223039, 0.8524969, 0.8526602],
std=[0.27402772, 0.2744828, 0.2744293])
])
记得前面我们讲过的符号编程,使用torchscript来种从PyTorch代码创建可序列化和可优化模型的方法,如果你将要在代码中将transforms脚本化就不能使用transforms.Compose而应该使用torch.nn.Sequential。具体实现如下。
transforms = torch.nn.Sequential(
transforms.CenterCrop(10),
transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)),
)
scripted_transforms = torch.jit.script(transforms)
3. transforms特殊情况
当然还有各种各样的对数据进行变换的操作因为时间限制,也不一一展开讲了,但是有一点要注意的是,有的transforms操作只能适用于特定的数据类型。
大部分方法同时可以对PIL Image类型和Tensor类型的数据进行处理,部分方法只能对PIL Image类型的数据进行处理或者是只能对Tensor类型的数据进行处理。
方法 | PIL Image | Tensor |
---|---|---|
RandomChoice | √ | × |
RandomOrder | √ | × |
LinearTransformation | × | √ |
Normalize | × | √ |
RandomErasing | × | √ |
ConvertImageDtype | × | √ |
3. 适合懒人的transforms
涉及到两个类,第一个就是自动增强的方法类,第二个类描述自动增强策略,作为自动增强方法类的初始化参数用来指定自动增强策略。
Pytorch提供了现成的几个策略可供选择,包含有imagenet训练采取的数据增强策略
torchvision.transforms.AutoAugment
torchvision.transforms.AutoAugmentPolicy
具体使用方法
import torch
import torchvision.transforms as T
tensor_image = torch.randint(0, 256, size=(3, 256, 256), dtype=torch.uint8)
auto_augmentation = T.AutoAugment(T.AutoAugmentPolicy.CIFAR10)
out_image3 = auto_augmentation(tensor_image)
4. 函数式转换functional transforms
functional可以提供了一些更加精细的变换,用于搭建复杂的变换流水线。和前面的变换相反,函数变换的参数不包含随机数种子生成器。这意味着你必须指定所有参数的值,但是你可以自己引入随机数。
PyTorch在torchvision.transforms模块中,给定了很多官配transform,但是当你需要自定义Transforms的时候functional transforms就可以起作用了。
我们以官方的centerCrop为例,查看与源码发现调用了一个F.center_crop
[docs]class CenterCrop(torch.nn.Module):
"""Crops the given image at the center.
If the image is torch Tensor, it is expected
to have [..., H, W] shape, where ... means an arbitrary number of leading dimensions.
If image size is smaller than output size along any edge, image is padded with 0 and then center cropped.
Args:
size (sequence or int): Desired output size of the crop. If size is an
int instead of sequence like (h, w), a square crop (size, size) is
made. If provided a sequence of length 1, it will be interpreted as (size[0], size[0]).
"""
def __init__(self, size):
super().__init__()
self.size = _setup_size(size, error_msg="Please provide only two dimensions (h, w) for size.")
[docs] def forward(self, img):
"""
Args:
img (PIL Image or Tensor): Image to be cropped.
Returns:
PIL Image or Tensor: Cropped image.
"""
return F.center_crop(img, self.size)
往上翻可以发现这个F的出处
from . import functional as F
from .functional import InterpolationMode, _interpolation_modes_from_int
也就是说,我么使用functional一方面其实是调用更加底层的接口来实现我们自己的transforms,另一方面则在我们不需要随机性或者想要设置随机数种子让transforms变换操作能够被复现的时候我们可以采用这个方法。
函数式变换和之前讲的随机变换进行对比。
torchvision.transforms.RandomAffine(
degrees,
translate=None,
scale=None,
shear=None,
interpolation=<InterpolationMode.NEAREST: 'nearest'>,
fill=0,
fillcolor=None,
resample=None)
torchvision.transforms.functional.affine(
img: torch.Tensor,
angle: float,
translate: List[int],
scale: float,
shear: List[float],
interpolation: torchvision.transforms.functional.InterpolationMode = <InterpolationMode.NEAREST: 'nearest'>,
fill: Optional[List[float]] = None,
resample: Optional[int] = None,
fillcolor: Optional[List[float]] = None) → torch.Tensor
下面的例子展示了直接调用torchvision.transforms.functional底层逻辑来实现一些图像变换
import torchvision.transforms.functional as TF
import random
def my_segmentation_transforms(image, segmentation):
if random.random() > 0.5:
angle = random.randint(-30, 30)
image = TF.rotate(image, angle)
segmentation = TF.rotate(segmentation, angle)
# more transforms ...
return image, segmentation
我们也可以自己实现一个transforms类,
import torchvision.transforms.functional as TF
import random
class MyRotationTransform:
"""Rotate by one of the given angles."""
def __init__(self, angles):
self.angles = angles
def __call__(self, x):
angle = random.choice(self.angles)
return TF.rotate(x, angle)
rotation_transform = MyRotationTransform(angles=[-30, -15, 0, 15, 30])