2021SC@SDUSC
摘要
一些图像处理操作如旋转、透视失真、运动模糊、高斯噪声去训练一个文本识别器,这个过程简称为BDA(Base Data Augmentation,基础数据增强)。处理过的图像被随机添加到训练图像中。实验表明,BDA对方向分类器的训练有一定的帮助。除此之外,RandAugment在训练方向分类器方面也展现了很好的效果。
基础数据增强分析
神经网络可能会需要大量的数据来保证精度。如果一个欠训练的神经网络识别相同物体的摄影距离不同的三张图片,那么它可能就认为这是三张不同的图片。所以我们必须使用大量数据训练神经网络。为了获得更多数据,我们可以将原有的数据进行旋转、透视失真、运动模糊、高斯噪声等操作来获得更多的数据。一个卷积神经网络,如果能够对物体即使它放在不同的地方也能稳健的分类,就被称为具有不变性的属性。更具体的,CNN可以对移位(translation)、视角(viewpoint)、大小(size)、照明(illumination)(或者以上的组合)具有不变性。这本质上是数据增强的前提。
神经网络的好坏与训练所使用的数据集的好坏有关。就算是最先进的神经网络也会因为少量且片面的数据集而作出错误的判断。为了阻止这种事情发生,我们需要减少数据集中不相关的特征。此时我们就可以通过原始数据集,对此进行一些操作来获得更好用的数据集。这就是我们进行BDA的原因。
RandAugment
谷歌大脑曾提出自动数据增强方法(AutoAugment),它确实对图像分类和目标检测等任务带来了益处。但是它的缺点也是明显的,一是大规模采用这样的方法会增加训练复杂性、加大计算成本,二是无法根据模型或数据集大小调整正则化强度。于是谷歌大脑团队又提出了一种数据增强的方法——RandAugment。RandAugment是一种新的数据增强方法,比AutoAugment简单又好用。其主要思想是随机选择变换,调整它们的大小。
在RandAugment中,为了减少参数空间的同时保持数据(图像)的多样性,研究人员用无参数过程替代了学习的策略和概率。这些策略和概率适用于每次变换(transformation),该过程始终选择均匀概率为1/k的变换。也就是说,给定训练图像的N个变换,RandAugment就能表示KN个潜在策略。研究人员采用线性标度来表示每个转换的强度。简单来说,就是每次变换都在0到10的整数范围内,其中,10表示给定变换的最大范围。为了进一步缩小参数空间,团队观察到每个转换的学习幅度(learned magnitude)在训练期间遵循类似的表:
程序可以使用标准方法高效地进行超参数优化,但是考虑到极小的搜索空间,研究人员发现朴素网格搜索(naive grid search)是非常有效的。研究人员的实验和与其他算法的比较证明RandAugment在数据增强方面确实有优势。
PPOCR中的应用
由于BDA和RandAugment对方向训练有一定的帮助,所以PPOCR在方向分类的训练图像中加入了BDA和RandAugment。
应用代码(PaddleOCR-release-2.2>ppocr>data>imaug>randaugment.py):
# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from PIL import Image, ImageEnhance, ImageOps
import numpy as np
import random
import six
class RawRandAugment(object):
def __init__(self,
num_layers=2,
magnitude=5,
fillcolor=(128, 128, 128),
**kwargs):
self.num_layers = num_layers
self.magnitude = magnitude
self.max_level = 10
abso_level = self.magnitude / self.max_level
self.level_map = {
"shearX": 0.3 * abso_level,
"shearY": 0.3 * abso_level,
"translateX": 150.0 / 331 * abso_level,
"translateY": 150.0 / 331 * abso_level,
"rotate": 30 * abso_level,
"color": 0.9 * abso_level,
"posterize": int(4.0 * abso_level),
"solarize": 256.0 * abso_level,
"contrast": 0.9 * abso_level,
"sharpness": 0.9 * abso_level,
"brightness": 0.9 * abso_level,
"autocontrast": 0,
"equalize": 0,
"invert": 0
}
# from https://*.com/questions/5252170/
# specify-image-filling-color-when-rotating-in-python-with-pil-and-setting-expand
def rotate_with_fill(img, magnitude):
rot = img.convert("RGBA").rotate(magnitude)
return Image.composite(rot,
Image.new("RGBA", rot.size, (128, ) * 4),
rot).convert(img.mode)
rnd_ch_op = random.choice
self.func = {
"shearX": lambda img, magnitude: img.transform(
img.size,
Image.AFFINE,
(1, magnitude * rnd_ch_op([-1, 1]), 0, 0, 1, 0),
Image.BICUBIC,
fillcolor=fillcolor),
"shearY": lambda img, magnitude: img.transform(
img.size,
Image.AFFINE,
(1, 0, 0, magnitude * rnd_ch_op([-1, 1]), 1, 0),
Image.BICUBIC,
fillcolor=fillcolor),
"translateX": lambda img, magnitude: img.transform(
img.size,
Image.AFFINE,
(1, 0, magnitude * img.size[0] * rnd_ch_op([-1, 1]), 0, 1, 0),
fillcolor=fillcolor),
"translateY": lambda img, magnitude: img.transform(
img.size,
Image.AFFINE,
(1, 0, 0, 0, 1, magnitude * img.size[1] * rnd_ch_op([-1, 1])),
fillcolor=fillcolor),
"rotate": lambda img, magnitude: rotate_with_fill(img, magnitude),
"color": lambda img, magnitude: ImageEnhance.Color(img).enhance(
1 + magnitude * rnd_ch_op([-1, 1])),
"posterize": lambda img, magnitude:
ImageOps.posterize(img, magnitude),
"solarize": lambda img, magnitude:
ImageOps.solarize(img, magnitude),
"contrast": lambda img, magnitude:
ImageEnhance.Contrast(img).enhance(
1 + magnitude * rnd_ch_op([-1, 1])),
"sharpness": lambda img, magnitude:
ImageEnhance.Sharpness(img).enhance(
1 + magnitude * rnd_ch_op([-1, 1])),
"brightness": lambda img, magnitude:
ImageEnhance.Brightness(img).enhance(
1 + magnitude * rnd_ch_op([-1, 1])),
"autocontrast": lambda img, magnitude:
ImageOps.autocontrast(img),
"equalize": lambda img, magnitude: ImageOps.equalize(img),
"invert": lambda img, magnitude: ImageOps.invert(img)
}
def __call__(self, img):
avaiable_op_names = list(self.level_map.keys())
for layer_num in range(self.num_layers):
op_name = np.random.choice(avaiable_op_names)
img = self.func[op_name](img, self.level_map[op_name])
return img
class RandAugment(RawRandAugment):
""" RandAugment wrapper to auto fit different img types """
def __init__(self, prob=0.5, *args, **kwargs):
self.prob = prob
if six.PY2:
super(RandAugment, self).__init__(*args, **kwargs)
else:
super().__init__(*args, **kwargs)
def __call__(self, data):
if np.random.rand() > self.prob:
return data
img = data['image']
if not isinstance(img, Image.Image):
img = np.ascontiguousarray(img)
img = Image.fromarray(img)
if six.PY2:
img = super(RandAugment, self).__call__(img)
else:
img = super().__call__(img)
if isinstance(img, Image.Image):
img = np.asarray(img)
data['image'] = img
return data
评估数据集(PaddleOCR-release-2.2>configs>cls>cls_mv3.yml):
Train:
dataset:
name: SimpleDataSet
data_dir: ./train_data/cls
label_file_list:
- ./train_data/cls/train.txt
transforms:
- DecodeImage: # load image
img_mode: BGR
channel_first: False
- ClsLabelEncode: # Class handling label
- RecAug:
use_tia: False
- RandAugment:
- ClsResizeImg:
image_shape: [3, 48, 192]
- KeepKeys:
keep_keys: ['image', 'label'] # dataloader will return list in this order
loader:
shuffle: True
batch_size_per_card: 512
drop_last: True
num_workers: 8