相关连接
本章对语义分割任务中常见的数据扩增方法进行介绍,并使用OpenCV和albumentations两个库完成具体的数据扩增操作。
2 数据扩增方法
本章主要内容为数据扩增方法、OpenCV数据扩增、albumentations数据扩增和Pytorch读取赛题数据四个部分组成。
2.1 学习目标
- 理解基础的数据扩增方法
- 学习OpenCV和albumentations完成数据扩增
- Pytorch完成赛题读取
2.2 常见的数据扩增方法
数据扩增是一种有效的正则化方法,可以防止模型过拟合,在深度学习模型的训练过程中应用广泛。数据扩增的目的是增加数据集中样本的数据量,同时也可以有效增加样本的语义空间。
需注意:
- 不同的数据,拥有不同的数据扩增方法;
- 数据扩增方法需要考虑合理性,不要随意使用;
- 数据扩增方法需要与具体任何相结合,同时要考虑到标签的变化;
对于图像分类,数据扩增方法可以分为两类:
- 标签不变的数据扩增方法:数据变换之后图像类别不变;
- 标签变化的数据扩增方法:数据变换之后图像类别变化;
而对于语义分割而言,常规的数据扩增方法都会改变图像的标签。如水平翻转、垂直翻转、旋转90%、旋转和随机裁剪,这些常见的数据扩增方法都会改变图像的标签,即会导致地标建筑物的像素发生改变。
2.3 OpenCV数据扩增
OpenCV是计算机视觉必备的库,可以很方便的完成数据读取、图像变化、边缘检测和模式识别等任务。为了加深各位对数据可做的影响,这里首先介绍OpenCV完成数据扩增的操作。
# 首先读取原始图片 img = cv2.imread(train_mask['name'].iloc[0]) mask = rle_decode(train_mask['mask'].iloc[0]) plt.figure(figsize=(16, 8)) plt.subplot(1, 2, 1) plt.imshow(img) plt.subplot(1, 2, 2) plt.imshow(mask)
# 垂直翻转 plt.figure(figsize=(16, 8)) plt.subplot(1, 2, 1) plt.imshow(cv2.flip(img, 0)) plt.subplot(1, 2, 2) plt.imshow(cv2.flip(mask, 0))
# 水平翻转 plt.figure(figsize=(16, 8)) plt.subplot(1, 2, 1) plt.imshow(cv2.flip(img, 0)) plt.subplot(1, 2, 2) plt.imshow(cv2.flip(mask, 0))
# 随机裁剪 x, y = np.random.randint(0, 256), np.random.randint(0, 256) plt.figure(figsize=(16, 8)) plt.subplot(1, 2, 1) plt.imshow(img[x:x+256, y:y+256]) plt.subplot(1, 2, 2) plt.imshow(mask[x:x+256, y:y+256])
2.4 albumentations数据扩增
albumentations是基于OpenCV的快速训练数据增强库,拥有非常简单且强大的可以用于多种任务(分割、检测)的接口,易于定制且添加其他框架非常方便。
albumentations也是计算机视觉数据竞赛中最常用的库:
- GitHub:https://github.com/albumentations-team/albumentations
- 示例:https://github.com/albumentations-team/albumentations_examples
与OpenCV相比albumentations具有以下优点:
- albumentations支持的操作更多,使用更加方便;
- albumentations可以与深度学习框架(Keras或Pytorch)配合使用;
- albumentations支持各种任务(图像分流)的数据扩增操作
albumentations它可以对数据集进行逐像素的转换,如模糊、下采样、高斯造点、高斯模糊、动态模糊、RGB转换、随机雾化等;也可以进行空间转换(同时也会对目标进行转换),如裁剪、翻转、随机裁剪等。
import albumentations as A # 水平翻转 augments = A.HorizontalFlip(p=1)(image=img, mask=mask) img_aug, mask_aug = augments['image'], augments['mask'] # 随机裁剪 augments = A.RandomCrop(p=1, height=256, width=256)(image=img, mask=mask) img_aug, mask_aug = augments['image'], augments['mask'] # 旋转 augments = A.ShiftScaleRotate(p=1)(image=img, mask=mask) img_aug, mask_aug = augments['image'], augments['mask']
albumentations还可以组合多个数据扩增操作得到更加复杂的数据扩增操作:
trfm = A.Compose([ A.Resize(256, 256), A.HorizontalFlip(p=0.5), A.VerticalFlip(p=0.5), A.RandomRotate90(), ]) augments = trfm(image=img, mask=mask) img_aug, mask_aug = augments['image'], augments['mask'] plt.figure(figsize=(16, 8)) plt.subplot(1, 2, 1) plt.imshow(augments['image']) plt.subplot(1, 2, 2) plt.imshow(augments['mask'])aug
2.5 Pytorch数据读取
由于本次赛题我们使用Pytorch框架讲解具体的解决方案,接下来将是解决赛题的第一步使用Pytorch读取赛题数据。在Pytorch中数据是通过Dataset进行封装,并通过DataLoder进行并行读取。所以我们只需要重载一下数据读取的逻辑就可以完成数据的读取。
- Dataset:数据集,对数据进行读取并进行数据扩增;
- DataLoder:数据读取器,对Dataset进行封装并进行批量读取;
定义Dataset:
import torch.utils.data as D class TianChiDataset(D.Dataset): def __init__(self, paths, rles, transform): self.paths = paths self.rles = rles self.transform = transform self.len = len(paths) self.as_tensor = T.Compose([ #要用torchvision.transform变换,先要转为PIL_img类型 T.ToPILImage(), T.Resize(IMAGE_SIZE), T.ToTensor(), T.Normalize([0.625, 0.448, 0.688], [0.131, 0.177, 0.101]), ]) def __getitem__(self, index): img = cv2.imread(self.paths[index]) mask = rle_decode(self.rles[index]) augments = self.transform(image=img, mask=mask) return self.as_tensor(augments['image']), augments['mask'][None] def __len__(self): return self.len
实例化Dataset:
trfm = A.Compose([ A.Resize(IMAGE_SIZE, IMAGE_SIZE), A.HorizontalFlip(p=0.5), A.VerticalFlip(p=0.5), A.RandomRotate90(), ]) dataset = TianChiDataset( train_mask['name'].values, train_mask['mask'].fillna('').values, trfm )
实例化DataLoder,批大小为10:
loader = D.DataLoader(dataset, batch_size=10, shuffle=True, num_workers=0)
2.6 本章小结
本章对数据扩增方法进行简单介绍,并介绍并完成OpenCV数据扩增、albumentations数据扩增和Pytorch读取赛题数据的具体操作。
2.7 课后作业
- 使用OpenCV完成图像加噪数据扩增;
- 使用OpenCV完成图像旋转数据扩增;
- 使用albumentations其他的的操作完成扩增操作;
- 使用Pytorch完成赛题数据读取;
总结
1)查看效果
使用baseline中的数据扩增
在baseline的数据扩增基础上再加一条缩放扩增。
可以看到,损失收敛的更加快了,说明这条扩增,对模型有了不少的提升。(控制变量法,只改变了扩增)
2)albumentations的例子
albumentations比较好用,可以直接对输入数据处理同时也对mask或者框进行变换。
from albumentations import ( HorizontalFlip, IAAPerspective, ShiftScaleRotate, CLAHE, RandomRotate90, Transpose, ShiftScaleRotate, Blur, OpticalDistortion, GridDistortion, HueSaturationValue, IAAAdditiveGaussianNoise, GaussNoise, MotionBlur, MedianBlur, IAAPiecewiseAffine, IAASharpen, IAAEmboss, RandomBrightnessContrast, Flip, OneOf, Compose ) import numpy as np def strong_aug(p=0.5): return Compose([ RandomRotate90(), Flip(), Transpose(), OneOf([ IAAAdditiveGaussianNoise(), GaussNoise(), ], p=0.2), OneOf([ MotionBlur(p=0.2), MedianBlur(blur_limit=3, p=0.1), Blur(blur_limit=3, p=0.1), ], p=0.2), ShiftScaleRotate(shift_limit=0.0625, scale_limit=0.2, rotate_limit=45, p=0.2), OneOf([ OpticalDistortion(p=0.3), GridDistortion(p=0.1), IAAPiecewiseAffine(p=0.3), ], p=0.2), OneOf([ CLAHE(clip_limit=2), IAASharpen(), IAAEmboss(), RandomBrightnessContrast(), ], p=0.3), HueSaturationValue(p=0.3), ], p=p) image = np.ones((300, 300, 3), dtype=np.uint8) mask = np.ones((300, 300), dtype=np.uint8) whatever_data = "my name" augmentation = strong_aug(p=0.9) data = {"image": image, "mask": mask, "whatever_data": whatever_data, "additional": "hello"} augmented = augmentation(**data) image, mask, whatever_data, additional = augmented["image"], augmented["mask"], augmented["whatever_data"], augmented["additional"]
如Oneof
,它会选择里面的其中之一增样操作,权重是按照,每个增样操作的概率。
OneOf([ OpticalDistortion(p=0.3), GridDistortion(p=0.1), IAAPiecewiseAffine(p=0.3), ], p=0.2),
如里面3个他们的权重为3:1:3,于是p=0.2的概率(事件A)会从里面抽一个增样,抽中OpticalDistortion(p=0.3)(事件B),的条件概率p(B|A)=3/7
3)为什么增强操作多了,反而效果更差了?
如下面这种变换,看上去做了很多操作,但是出现了一些问题。
比如概率为p=0.5的操作就有9个,如果要返回不进行操作的原图的概率为
P = ( 1 2 ) 9 P=\left( \frac{1}{2} \right) ^9P=(21)9
很多时候,得到,都不是原图。参加训练的原图也寥寥无几。
因此在训练的时候,很多图片都是增强过度了,跟原图的相似度相差很大,从而导致验证集损失无法收敛。
下图是导致,模型效果更差的一种案例
trfm = A.Compose([ #改变HSV A.ColorJitter(p=0.5), A.HueSaturationValue(p=0.5), #resize A.Resize(IMAGE_SIZE, IMAGE_SIZE), #旋转与缩放 A.HorizontalFlip(p=0.5), A.VerticalFlip(p=0.5), A.RandomRotate90(), A.ShiftScaleRotate(scale_limit=(-0.3,-0.05),p=0.5),#随机旋转(-45~45),随机缩小0.3~0.05 #透视变换 A.IAAPerspective(scale=(0.03,0.05),p=0.5),#透视变化,随机放大0.03~0.05 #加噪声 A.ISONoise(p=0.5), A.GaussNoise(p=0.5), # 平滑处理 A.GaussianBlur(blur_limit=3,p=0.5),#核大小给3,设置成更大,根本看不清了 ])
于是,可以运用A.Compose([op],p=0.5)在尾部加上概率,保证原图的数量。
或者通过减少增强操作,来保证原图数量,比如baseline中的增强操作步骤还是比较少的,因此能有不错的效果。
或者可以选用A.OneOf([op],p=0.5)来组合操作,也可以在Compose中套Compose,在有效保证鲁棒性的同时,也避免了过度偏离原始数据(原图)。
总结来说:在batch_size在比较小的情况下,采用较多增样会导致,学习的是特殊样例的分布特征,不容易学不到原始数据特征,从而导致参数更新时,偏向一些极端数据,导致不好损失收敛。
因此,在增样的同时,最好还能增加点批次,更有利于学习整体的特征。如果真的因为显存不够,建议减少增样类型,或者使用Compose或Oneof后额外增加概率p,来获得原图,避免小批次的数据,以免过度偏离原始数据分布的特征。