|
|
|
@ -1,19 +1,30 @@
|
|
|
|
|
""" AutoAugment and RandAugment
|
|
|
|
|
Implementation adapted from:
|
|
|
|
|
""" AutoAugment, RandAugment, and AugMix for PyTorch
|
|
|
|
|
|
|
|
|
|
This code implements the searched ImageNet policies with various tweaks and improvements and
|
|
|
|
|
does not include any of the search code.
|
|
|
|
|
|
|
|
|
|
AA and RA Implementation adapted from:
|
|
|
|
|
https://github.com/tensorflow/tpu/blob/master/models/official/efficientnet/autoaugment.py
|
|
|
|
|
Papers: https://arxiv.org/abs/1805.09501, https://arxiv.org/abs/1906.11172, and https://arxiv.org/abs/1909.13719
|
|
|
|
|
|
|
|
|
|
AugMix adapted from:
|
|
|
|
|
https://github.com/google-research/augmix
|
|
|
|
|
|
|
|
|
|
Papers:
|
|
|
|
|
AutoAugment: Learning Augmentation Policies from Data - https://arxiv.org/abs/1805.09501
|
|
|
|
|
Learning Data Augmentation Strategies for Object Detection - https://arxiv.org/abs/1906.11172
|
|
|
|
|
RandAugment: Practical automated data augmentation... - https://arxiv.org/abs/1909.13719
|
|
|
|
|
AugMix: A Simple Data Processing Method to Improve Robustness and Uncertainty - https://arxiv.org/abs/1912.02781
|
|
|
|
|
|
|
|
|
|
Hacked together by Ross Wightman
|
|
|
|
|
"""
|
|
|
|
|
import random
|
|
|
|
|
import math
|
|
|
|
|
import re
|
|
|
|
|
from PIL import Image, ImageOps, ImageEnhance
|
|
|
|
|
from PIL import Image, ImageOps, ImageEnhance, ImageChops
|
|
|
|
|
import PIL
|
|
|
|
|
import numpy as np
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_PIL_VER = tuple([int(x) for x in PIL.__version__.split('.')[:2]])
|
|
|
|
|
|
|
|
|
|
_FILL = (128, 128, 128)
|
|
|
|
@ -178,6 +189,14 @@ def _enhance_level_to_arg(level, _hparams):
|
|
|
|
|
return (level / _MAX_LEVEL) * 1.8 + 0.1,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _enhance_increasing_level_to_arg(level, _hparams):
|
|
|
|
|
# the 'no change' level is 1.0, moving away from that towards 0. or 2.0 increases the enhancement blend
|
|
|
|
|
# range [0.1, 1.9]
|
|
|
|
|
level = (level / _MAX_LEVEL) * .9
|
|
|
|
|
level = 1.0 + _randomly_negate(level)
|
|
|
|
|
return level,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _shear_level_to_arg(level, _hparams):
|
|
|
|
|
# range [-0.3, 0.3]
|
|
|
|
|
level = (level / _MAX_LEVEL) * 0.3
|
|
|
|
@ -192,36 +211,47 @@ def _translate_abs_level_to_arg(level, hparams):
|
|
|
|
|
return level,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _translate_rel_level_to_arg(level, _hparams):
|
|
|
|
|
# range [-0.45, 0.45]
|
|
|
|
|
level = (level / _MAX_LEVEL) * 0.45
|
|
|
|
|
def _translate_rel_level_to_arg(level, hparams):
|
|
|
|
|
# default range [-0.45, 0.45]
|
|
|
|
|
translate_pct = hparams.get('translate_pct', 0.45)
|
|
|
|
|
level = (level / _MAX_LEVEL) * translate_pct
|
|
|
|
|
level = _randomly_negate(level)
|
|
|
|
|
return level,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _posterize_original_level_to_arg(level, _hparams):
|
|
|
|
|
# As per original AutoAugment paper description
|
|
|
|
|
# range [4, 8], 'keep 4 up to 8 MSB of image'
|
|
|
|
|
return int((level / _MAX_LEVEL) * 4) + 4,
|
|
|
|
|
def _posterize_level_to_arg(level, _hparams):
|
|
|
|
|
# As per Tensorflow TPU EfficientNet impl
|
|
|
|
|
# range [0, 4], 'keep 0 up to 4 MSB of original image'
|
|
|
|
|
# intensity/severity of augmentation decreases with level
|
|
|
|
|
return int((level / _MAX_LEVEL) * 4),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _posterize_research_level_to_arg(level, _hparams):
|
|
|
|
|
def _posterize_increasing_level_to_arg(level, hparams):
|
|
|
|
|
# As per Tensorflow models research and UDA impl
|
|
|
|
|
# range [4, 0], 'keep 4 down to 0 MSB of original image'
|
|
|
|
|
return 4 - int((level / _MAX_LEVEL) * 4),
|
|
|
|
|
# range [4, 0], 'keep 4 down to 0 MSB of original image',
|
|
|
|
|
# intensity/severity of augmentation increases with level
|
|
|
|
|
return 4 - _posterize_level_to_arg(level, hparams)[0],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _posterize_tpu_level_to_arg(level, _hparams):
|
|
|
|
|
# As per Tensorflow TPU EfficientNet impl
|
|
|
|
|
# range [0, 4], 'keep 0 up to 4 MSB of original image'
|
|
|
|
|
return int((level / _MAX_LEVEL) * 4),
|
|
|
|
|
def _posterize_original_level_to_arg(level, _hparams):
|
|
|
|
|
# As per original AutoAugment paper description
|
|
|
|
|
# range [4, 8], 'keep 4 up to 8 MSB of image'
|
|
|
|
|
# intensity/severity of augmentation decreases with level
|
|
|
|
|
return int((level / _MAX_LEVEL) * 4) + 4,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _solarize_level_to_arg(level, _hparams):
|
|
|
|
|
# range [0, 256]
|
|
|
|
|
# intensity/severity of augmentation decreases with level
|
|
|
|
|
return int((level / _MAX_LEVEL) * 256),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _solarize_increasing_level_to_arg(level, _hparams):
|
|
|
|
|
# range [0, 256]
|
|
|
|
|
# intensity/severity of augmentation increases with level
|
|
|
|
|
return 256 - _solarize_level_to_arg(level, _hparams)[0],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _solarize_add_level_to_arg(level, _hparams):
|
|
|
|
|
# range [0, 110]
|
|
|
|
|
return int((level / _MAX_LEVEL) * 110),
|
|
|
|
@ -233,15 +263,20 @@ LEVEL_TO_ARG = {
|
|
|
|
|
'Invert': None,
|
|
|
|
|
'Rotate': _rotate_level_to_arg,
|
|
|
|
|
# There are several variations of the posterize level scaling in various Tensorflow/Google repositories/papers
|
|
|
|
|
'Posterize': _posterize_level_to_arg,
|
|
|
|
|
'PosterizeIncreasing': _posterize_increasing_level_to_arg,
|
|
|
|
|
'PosterizeOriginal': _posterize_original_level_to_arg,
|
|
|
|
|
'PosterizeResearch': _posterize_research_level_to_arg,
|
|
|
|
|
'PosterizeTpu': _posterize_tpu_level_to_arg,
|
|
|
|
|
'Solarize': _solarize_level_to_arg,
|
|
|
|
|
'SolarizeIncreasing': _solarize_increasing_level_to_arg,
|
|
|
|
|
'SolarizeAdd': _solarize_add_level_to_arg,
|
|
|
|
|
'Color': _enhance_level_to_arg,
|
|
|
|
|
'ColorIncreasing': _enhance_increasing_level_to_arg,
|
|
|
|
|
'Contrast': _enhance_level_to_arg,
|
|
|
|
|
'ContrastIncreasing': _enhance_increasing_level_to_arg,
|
|
|
|
|
'Brightness': _enhance_level_to_arg,
|
|
|
|
|
'BrightnessIncreasing': _enhance_increasing_level_to_arg,
|
|
|
|
|
'Sharpness': _enhance_level_to_arg,
|
|
|
|
|
'SharpnessIncreasing': _enhance_increasing_level_to_arg,
|
|
|
|
|
'ShearX': _shear_level_to_arg,
|
|
|
|
|
'ShearY': _shear_level_to_arg,
|
|
|
|
|
'TranslateX': _translate_abs_level_to_arg,
|
|
|
|
@ -256,15 +291,20 @@ NAME_TO_OP = {
|
|
|
|
|
'Equalize': equalize,
|
|
|
|
|
'Invert': invert,
|
|
|
|
|
'Rotate': rotate,
|
|
|
|
|
'Posterize': posterize,
|
|
|
|
|
'PosterizeIncreasing': posterize,
|
|
|
|
|
'PosterizeOriginal': posterize,
|
|
|
|
|
'PosterizeResearch': posterize,
|
|
|
|
|
'PosterizeTpu': posterize,
|
|
|
|
|
'Solarize': solarize,
|
|
|
|
|
'SolarizeIncreasing': solarize,
|
|
|
|
|
'SolarizeAdd': solarize_add,
|
|
|
|
|
'Color': color,
|
|
|
|
|
'ColorIncreasing': color,
|
|
|
|
|
'Contrast': contrast,
|
|
|
|
|
'ContrastIncreasing': contrast,
|
|
|
|
|
'Brightness': brightness,
|
|
|
|
|
'BrightnessIncreasing': brightness,
|
|
|
|
|
'Sharpness': sharpness,
|
|
|
|
|
'SharpnessIncreasing': sharpness,
|
|
|
|
|
'ShearX': shear_x,
|
|
|
|
|
'ShearY': shear_y,
|
|
|
|
|
'TranslateX': translate_x_abs,
|
|
|
|
@ -274,7 +314,7 @@ NAME_TO_OP = {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AutoAugmentOp:
|
|
|
|
|
class AugmentOp:
|
|
|
|
|
|
|
|
|
|
def __init__(self, name, prob=0.5, magnitude=10, hparams=None):
|
|
|
|
|
hparams = hparams or _HPARAMS_DEFAULT
|
|
|
|
@ -295,12 +335,12 @@ class AutoAugmentOp:
|
|
|
|
|
self.magnitude_std = self.hparams.get('magnitude_std', 0)
|
|
|
|
|
|
|
|
|
|
def __call__(self, img):
|
|
|
|
|
if random.random() > self.prob:
|
|
|
|
|
if self.prob < 1.0 and random.random() > self.prob:
|
|
|
|
|
return img
|
|
|
|
|
magnitude = self.magnitude
|
|
|
|
|
if self.magnitude_std and self.magnitude_std > 0:
|
|
|
|
|
magnitude = random.gauss(magnitude, self.magnitude_std)
|
|
|
|
|
magnitude = min(_MAX_LEVEL, max(0, magnitude)) # clip to valid range
|
|
|
|
|
magnitude = min(_MAX_LEVEL, max(0, magnitude)) # clip to valid range
|
|
|
|
|
level_args = self.level_fn(magnitude, self.hparams) if self.level_fn is not None else tuple()
|
|
|
|
|
return self.aug_fn(img, *level_args, **self.kwargs)
|
|
|
|
|
|
|
|
|
@ -320,7 +360,7 @@ def auto_augment_policy_v0(hparams):
|
|
|
|
|
[('Invert', 0.4, 9), ('Rotate', 0.6, 0)],
|
|
|
|
|
[('Equalize', 1.0, 9), ('ShearY', 0.6, 3)],
|
|
|
|
|
[('Color', 0.4, 7), ('Equalize', 0.6, 0)],
|
|
|
|
|
[('PosterizeTpu', 0.4, 6), ('AutoContrast', 0.4, 7)],
|
|
|
|
|
[('Posterize', 0.4, 6), ('AutoContrast', 0.4, 7)],
|
|
|
|
|
[('Solarize', 0.6, 8), ('Color', 0.6, 9)],
|
|
|
|
|
[('Solarize', 0.2, 4), ('Rotate', 0.8, 9)],
|
|
|
|
|
[('Rotate', 1.0, 7), ('TranslateYRel', 0.8, 9)],
|
|
|
|
@ -330,16 +370,17 @@ def auto_augment_policy_v0(hparams):
|
|
|
|
|
[('Equalize', 0.8, 4), ('Equalize', 0.0, 8)],
|
|
|
|
|
[('Equalize', 1.0, 4), ('AutoContrast', 0.6, 2)],
|
|
|
|
|
[('ShearY', 0.4, 7), ('SolarizeAdd', 0.6, 7)],
|
|
|
|
|
[('PosterizeTpu', 0.8, 2), ('Solarize', 0.6, 10)], # This results in black image with Tpu posterize
|
|
|
|
|
[('Posterize', 0.8, 2), ('Solarize', 0.6, 10)], # This results in black image with Tpu posterize
|
|
|
|
|
[('Solarize', 0.6, 8), ('Equalize', 0.6, 1)],
|
|
|
|
|
[('Color', 0.8, 6), ('Rotate', 0.4, 5)],
|
|
|
|
|
]
|
|
|
|
|
pc = [[AutoAugmentOp(*a, hparams=hparams) for a in sp] for sp in policy]
|
|
|
|
|
pc = [[AugmentOp(*a, hparams=hparams) for a in sp] for sp in policy]
|
|
|
|
|
return pc
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def auto_augment_policy_v0r(hparams):
|
|
|
|
|
# ImageNet v0 policy from TPU EfficientNet impl, with research variation of Posterize
|
|
|
|
|
# ImageNet v0 policy from TPU EfficientNet impl, with variation of Posterize used
|
|
|
|
|
# in Google research implementation (number of bits discarded increases with magnitude)
|
|
|
|
|
policy = [
|
|
|
|
|
[('Equalize', 0.8, 1), ('ShearY', 0.8, 4)],
|
|
|
|
|
[('Color', 0.4, 9), ('Equalize', 0.6, 3)],
|
|
|
|
@ -353,7 +394,7 @@ def auto_augment_policy_v0r(hparams):
|
|
|
|
|
[('Invert', 0.4, 9), ('Rotate', 0.6, 0)],
|
|
|
|
|
[('Equalize', 1.0, 9), ('ShearY', 0.6, 3)],
|
|
|
|
|
[('Color', 0.4, 7), ('Equalize', 0.6, 0)],
|
|
|
|
|
[('PosterizeResearch', 0.4, 6), ('AutoContrast', 0.4, 7)],
|
|
|
|
|
[('PosterizeIncreasing', 0.4, 6), ('AutoContrast', 0.4, 7)],
|
|
|
|
|
[('Solarize', 0.6, 8), ('Color', 0.6, 9)],
|
|
|
|
|
[('Solarize', 0.2, 4), ('Rotate', 0.8, 9)],
|
|
|
|
|
[('Rotate', 1.0, 7), ('TranslateYRel', 0.8, 9)],
|
|
|
|
@ -363,11 +404,11 @@ def auto_augment_policy_v0r(hparams):
|
|
|
|
|
[('Equalize', 0.8, 4), ('Equalize', 0.0, 8)],
|
|
|
|
|
[('Equalize', 1.0, 4), ('AutoContrast', 0.6, 2)],
|
|
|
|
|
[('ShearY', 0.4, 7), ('SolarizeAdd', 0.6, 7)],
|
|
|
|
|
[('PosterizeResearch', 0.8, 2), ('Solarize', 0.6, 10)],
|
|
|
|
|
[('PosterizeIncreasing', 0.8, 2), ('Solarize', 0.6, 10)],
|
|
|
|
|
[('Solarize', 0.6, 8), ('Equalize', 0.6, 1)],
|
|
|
|
|
[('Color', 0.8, 6), ('Rotate', 0.4, 5)],
|
|
|
|
|
]
|
|
|
|
|
pc = [[AutoAugmentOp(*a, hparams=hparams) for a in sp] for sp in policy]
|
|
|
|
|
pc = [[AugmentOp(*a, hparams=hparams) for a in sp] for sp in policy]
|
|
|
|
|
return pc
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -400,23 +441,23 @@ def auto_augment_policy_original(hparams):
|
|
|
|
|
[('Color', 0.6, 4), ('Contrast', 1.0, 8)],
|
|
|
|
|
[('Equalize', 0.8, 8), ('Equalize', 0.6, 3)],
|
|
|
|
|
]
|
|
|
|
|
pc = [[AutoAugmentOp(*a, hparams=hparams) for a in sp] for sp in policy]
|
|
|
|
|
pc = [[AugmentOp(*a, hparams=hparams) for a in sp] for sp in policy]
|
|
|
|
|
return pc
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def auto_augment_policy_originalr(hparams):
|
|
|
|
|
# ImageNet policy from https://arxiv.org/abs/1805.09501 with research posterize variation
|
|
|
|
|
policy = [
|
|
|
|
|
[('PosterizeResearch', 0.4, 8), ('Rotate', 0.6, 9)],
|
|
|
|
|
[('PosterizeIncreasing', 0.4, 8), ('Rotate', 0.6, 9)],
|
|
|
|
|
[('Solarize', 0.6, 5), ('AutoContrast', 0.6, 5)],
|
|
|
|
|
[('Equalize', 0.8, 8), ('Equalize', 0.6, 3)],
|
|
|
|
|
[('PosterizeResearch', 0.6, 7), ('PosterizeResearch', 0.6, 6)],
|
|
|
|
|
[('PosterizeIncreasing', 0.6, 7), ('PosterizeIncreasing', 0.6, 6)],
|
|
|
|
|
[('Equalize', 0.4, 7), ('Solarize', 0.2, 4)],
|
|
|
|
|
[('Equalize', 0.4, 4), ('Rotate', 0.8, 8)],
|
|
|
|
|
[('Solarize', 0.6, 3), ('Equalize', 0.6, 7)],
|
|
|
|
|
[('PosterizeResearch', 0.8, 5), ('Equalize', 1.0, 2)],
|
|
|
|
|
[('PosterizeIncreasing', 0.8, 5), ('Equalize', 1.0, 2)],
|
|
|
|
|
[('Rotate', 0.2, 3), ('Solarize', 0.6, 8)],
|
|
|
|
|
[('Equalize', 0.6, 8), ('PosterizeResearch', 0.4, 6)],
|
|
|
|
|
[('Equalize', 0.6, 8), ('PosterizeIncreasing', 0.4, 6)],
|
|
|
|
|
[('Rotate', 0.8, 8), ('Color', 0.4, 0)],
|
|
|
|
|
[('Rotate', 0.4, 9), ('Equalize', 0.6, 2)],
|
|
|
|
|
[('Equalize', 0.0, 7), ('Equalize', 0.8, 8)],
|
|
|
|
@ -433,7 +474,7 @@ def auto_augment_policy_originalr(hparams):
|
|
|
|
|
[('Color', 0.6, 4), ('Contrast', 1.0, 8)],
|
|
|
|
|
[('Equalize', 0.8, 8), ('Equalize', 0.6, 3)],
|
|
|
|
|
]
|
|
|
|
|
pc = [[AutoAugmentOp(*a, hparams=hparams) for a in sp] for sp in policy]
|
|
|
|
|
pc = [[AugmentOp(*a, hparams=hparams) for a in sp] for sp in policy]
|
|
|
|
|
return pc
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -499,7 +540,7 @@ _RAND_TRANSFORMS = [
|
|
|
|
|
'Equalize',
|
|
|
|
|
'Invert',
|
|
|
|
|
'Rotate',
|
|
|
|
|
'PosterizeTpu',
|
|
|
|
|
'Posterize',
|
|
|
|
|
'Solarize',
|
|
|
|
|
'SolarizeAdd',
|
|
|
|
|
'Color',
|
|
|
|
@ -510,10 +551,31 @@ _RAND_TRANSFORMS = [
|
|
|
|
|
'ShearY',
|
|
|
|
|
'TranslateXRel',
|
|
|
|
|
'TranslateYRel',
|
|
|
|
|
#'Cutout' # FIXME I implement this as random erasing separately
|
|
|
|
|
#'Cutout' # NOTE I've implement this as random erasing separately
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_RAND_INCREASING_TRANSFORMS = [
|
|
|
|
|
'AutoContrast',
|
|
|
|
|
'Equalize',
|
|
|
|
|
'Invert',
|
|
|
|
|
'Rotate',
|
|
|
|
|
'PosterizeIncreasing',
|
|
|
|
|
'SolarizeIncreasing',
|
|
|
|
|
'SolarizeAdd',
|
|
|
|
|
'ColorIncreasing',
|
|
|
|
|
'ContrastIncreasing',
|
|
|
|
|
'BrightnessIncreasing',
|
|
|
|
|
'SharpnessIncreasing',
|
|
|
|
|
'ShearX',
|
|
|
|
|
'ShearY',
|
|
|
|
|
'TranslateXRel',
|
|
|
|
|
'TranslateYRel',
|
|
|
|
|
#'Cutout' # NOTE I've implement this as random erasing separately
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# These experimental weights are based loosely on the relative improvements mentioned in paper.
|
|
|
|
|
# They may not result in increased performance, but could likely be tuned to so.
|
|
|
|
|
_RAND_CHOICE_WEIGHTS_0 = {
|
|
|
|
@ -530,7 +592,7 @@ _RAND_CHOICE_WEIGHTS_0 = {
|
|
|
|
|
'Contrast': .005,
|
|
|
|
|
'Brightness': .005,
|
|
|
|
|
'Equalize': .005,
|
|
|
|
|
'PosterizeTpu': 0,
|
|
|
|
|
'Posterize': 0,
|
|
|
|
|
'Invert': 0,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -547,7 +609,7 @@ def _select_rand_weights(weight_idx=0, transforms=None):
|
|
|
|
|
def rand_augment_ops(magnitude=10, hparams=None, transforms=None):
|
|
|
|
|
hparams = hparams or _HPARAMS_DEFAULT
|
|
|
|
|
transforms = transforms or _RAND_TRANSFORMS
|
|
|
|
|
return [AutoAugmentOp(
|
|
|
|
|
return [AugmentOp(
|
|
|
|
|
name, prob=0.5, magnitude=magnitude, hparams=hparams) for name in transforms]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -577,6 +639,7 @@ def rand_augment_transform(config_str, hparams):
|
|
|
|
|
'n' - integer num layers (number of transform ops selected per image)
|
|
|
|
|
'w' - integer probabiliy weight index (index of a set of weights to influence choice of op)
|
|
|
|
|
'mstd' - float std deviation of magnitude noise applied
|
|
|
|
|
'inc' - integer (bool), use augmentations that increase in severity with magnitude (default: 0)
|
|
|
|
|
Ex 'rand-m9-n3-mstd0.5' results in RandAugment with magnitude 9, num_layers 3, magnitude_std 0.5
|
|
|
|
|
'rand-mstd1-w0' results in magnitude_std 1.0, weights 0, default magnitude of 10 and num_layers 2
|
|
|
|
|
|
|
|
|
@ -587,6 +650,7 @@ def rand_augment_transform(config_str, hparams):
|
|
|
|
|
magnitude = _MAX_LEVEL # default to _MAX_LEVEL for magnitude (currently 10)
|
|
|
|
|
num_layers = 2 # default to 2 ops per image
|
|
|
|
|
weight_idx = None # default to no probability weights for op choice
|
|
|
|
|
transforms = _RAND_TRANSFORMS
|
|
|
|
|
config = config_str.split('-')
|
|
|
|
|
assert config[0] == 'rand'
|
|
|
|
|
config = config[1:]
|
|
|
|
@ -598,6 +662,9 @@ def rand_augment_transform(config_str, hparams):
|
|
|
|
|
if key == 'mstd':
|
|
|
|
|
# noise param injected via hparams for now
|
|
|
|
|
hparams.setdefault('magnitude_std', float(val))
|
|
|
|
|
elif key == 'inc':
|
|
|
|
|
if bool(val):
|
|
|
|
|
transforms = _RAND_INCREASING_TRANSFORMS
|
|
|
|
|
elif key == 'm':
|
|
|
|
|
magnitude = int(val)
|
|
|
|
|
elif key == 'n':
|
|
|
|
@ -606,6 +673,145 @@ def rand_augment_transform(config_str, hparams):
|
|
|
|
|
weight_idx = int(val)
|
|
|
|
|
else:
|
|
|
|
|
assert False, 'Unknown RandAugment config section'
|
|
|
|
|
ra_ops = rand_augment_ops(magnitude=magnitude, hparams=hparams)
|
|
|
|
|
ra_ops = rand_augment_ops(magnitude=magnitude, hparams=hparams, transforms=transforms)
|
|
|
|
|
choice_weights = None if weight_idx is None else _select_rand_weights(weight_idx)
|
|
|
|
|
return RandAugment(ra_ops, num_layers, choice_weights=choice_weights)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_AUGMIX_TRANSFORMS = [
|
|
|
|
|
'AutoContrast',
|
|
|
|
|
'ColorIncreasing', # not in paper
|
|
|
|
|
'ContrastIncreasing', # not in paper
|
|
|
|
|
'BrightnessIncreasing', # not in paper
|
|
|
|
|
'SharpnessIncreasing', # not in paper
|
|
|
|
|
'Equalize',
|
|
|
|
|
'Rotate',
|
|
|
|
|
'PosterizeIncreasing',
|
|
|
|
|
'SolarizeIncreasing',
|
|
|
|
|
'ShearX',
|
|
|
|
|
'ShearY',
|
|
|
|
|
'TranslateXRel',
|
|
|
|
|
'TranslateYRel',
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def augmix_ops(magnitude=10, hparams=None, transforms=None):
|
|
|
|
|
hparams = hparams or _HPARAMS_DEFAULT
|
|
|
|
|
transforms = transforms or _AUGMIX_TRANSFORMS
|
|
|
|
|
return [AugmentOp(
|
|
|
|
|
name, prob=1.0, magnitude=magnitude, hparams=hparams) for name in transforms]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AugMixAugment:
|
|
|
|
|
""" AugMix Transform
|
|
|
|
|
Adapted and improved from impl here: https://github.com/google-research/augmix/blob/master/imagenet.py
|
|
|
|
|
From paper: 'AugMix: A Simple Data Processing Method to Improve Robustness and Uncertainty -
|
|
|
|
|
https://arxiv.org/abs/1912.02781
|
|
|
|
|
"""
|
|
|
|
|
def __init__(self, ops, alpha=1., width=3, depth=-1, blended=False):
|
|
|
|
|
self.ops = ops
|
|
|
|
|
self.alpha = alpha
|
|
|
|
|
self.width = width
|
|
|
|
|
self.depth = depth
|
|
|
|
|
self.blended = blended # blended mode is faster but not well tested
|
|
|
|
|
|
|
|
|
|
def _calc_blended_weights(self, ws, m):
|
|
|
|
|
ws = ws * m
|
|
|
|
|
cump = 1.
|
|
|
|
|
rws = []
|
|
|
|
|
for w in ws[::-1]:
|
|
|
|
|
alpha = w / cump
|
|
|
|
|
cump *= (1 - alpha)
|
|
|
|
|
rws.append(alpha)
|
|
|
|
|
return np.array(rws[::-1], dtype=np.float32)
|
|
|
|
|
|
|
|
|
|
def _apply_blended(self, img, mixing_weights, m):
|
|
|
|
|
# This is my first crack and implementing a slightly faster mixed augmentation. Instead
|
|
|
|
|
# of accumulating the mix for each chain in a Numpy array and then blending with original,
|
|
|
|
|
# it recomputes the blending coefficients and applies one PIL image blend per chain.
|
|
|
|
|
# TODO the results appear in the right ballpark but they differ by more than rounding.
|
|
|
|
|
img_orig = img.copy()
|
|
|
|
|
ws = self._calc_blended_weights(mixing_weights, m)
|
|
|
|
|
for w in ws:
|
|
|
|
|
depth = self.depth if self.depth > 0 else np.random.randint(1, 4)
|
|
|
|
|
ops = np.random.choice(self.ops, depth, replace=True)
|
|
|
|
|
img_aug = img_orig # no ops are in-place, deep copy not necessary
|
|
|
|
|
for op in ops:
|
|
|
|
|
img_aug = op(img_aug)
|
|
|
|
|
img = Image.blend(img, img_aug, w)
|
|
|
|
|
return img
|
|
|
|
|
|
|
|
|
|
def _apply_basic(self, img, mixing_weights, m):
|
|
|
|
|
# This is a literal adaptation of the paper/official implementation without normalizations and
|
|
|
|
|
# PIL <-> Numpy conversions between every op. It is still quite CPU compute heavy compared to the
|
|
|
|
|
# typical augmentation transforms, could use a GPU / Kornia implementation.
|
|
|
|
|
img_shape = img.size[0], img.size[1], len(img.getbands())
|
|
|
|
|
mixed = np.zeros(img_shape, dtype=np.float32)
|
|
|
|
|
for mw in mixing_weights:
|
|
|
|
|
depth = self.depth if self.depth > 0 else np.random.randint(1, 4)
|
|
|
|
|
ops = np.random.choice(self.ops, depth, replace=True)
|
|
|
|
|
img_aug = img # no ops are in-place, deep copy not necessary
|
|
|
|
|
for op in ops:
|
|
|
|
|
img_aug = op(img_aug)
|
|
|
|
|
mixed += mw * np.asarray(img_aug, dtype=np.float32)
|
|
|
|
|
np.clip(mixed, 0, 255., out=mixed)
|
|
|
|
|
mixed = Image.fromarray(mixed.astype(np.uint8))
|
|
|
|
|
return Image.blend(img, mixed, m)
|
|
|
|
|
|
|
|
|
|
def __call__(self, img):
|
|
|
|
|
mixing_weights = np.float32(np.random.dirichlet([self.alpha] * self.width))
|
|
|
|
|
m = np.float32(np.random.beta(self.alpha, self.alpha))
|
|
|
|
|
if self.blended:
|
|
|
|
|
mixed = self._apply_blended(img, mixing_weights, m)
|
|
|
|
|
else:
|
|
|
|
|
mixed = self._apply_basic(img, mixing_weights, m)
|
|
|
|
|
return mixed
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def augment_and_mix_transform(config_str, hparams):
|
|
|
|
|
""" Create AugMix PyTorch transform
|
|
|
|
|
|
|
|
|
|
:param config_str: String defining configuration of random augmentation. Consists of multiple sections separated by
|
|
|
|
|
dashes ('-'). The first section defines the specific variant of rand augment (currently only 'rand'). The remaining
|
|
|
|
|
sections, not order sepecific determine
|
|
|
|
|
'm' - integer magnitude (severity) of augmentation mix (default: 3)
|
|
|
|
|
'w' - integer width of augmentation chain (default: 3)
|
|
|
|
|
'd' - integer depth of augmentation chain (-1 is random [1, 3], default: -1)
|
|
|
|
|
'b' - integer (bool), blend each branch of chain into end result without a final blend, less CPU (default: 0)
|
|
|
|
|
'mstd' - float std deviation of magnitude noise applied (default: 0)
|
|
|
|
|
Ex 'augmix-m5-w4-d2' results in AugMix with severity 5, chain width 4, chain depth 2
|
|
|
|
|
|
|
|
|
|
:param hparams: Other hparams (kwargs) for the Augmentation transforms
|
|
|
|
|
|
|
|
|
|
:return: A PyTorch compatible Transform
|
|
|
|
|
"""
|
|
|
|
|
magnitude = 3
|
|
|
|
|
width = 3
|
|
|
|
|
depth = -1
|
|
|
|
|
alpha = 1.
|
|
|
|
|
blended = False
|
|
|
|
|
config = config_str.split('-')
|
|
|
|
|
assert config[0] == 'augmix'
|
|
|
|
|
config = config[1:]
|
|
|
|
|
for c in config:
|
|
|
|
|
cs = re.split(r'(\d.*)', c)
|
|
|
|
|
if len(cs) < 2:
|
|
|
|
|
continue
|
|
|
|
|
key, val = cs[:2]
|
|
|
|
|
if key == 'mstd':
|
|
|
|
|
# noise param injected via hparams for now
|
|
|
|
|
hparams.setdefault('magnitude_std', float(val))
|
|
|
|
|
elif key == 'm':
|
|
|
|
|
magnitude = int(val)
|
|
|
|
|
elif key == 'w':
|
|
|
|
|
width = int(val)
|
|
|
|
|
elif key == 'd':
|
|
|
|
|
depth = int(val)
|
|
|
|
|
elif key == 'a':
|
|
|
|
|
alpha = float(val)
|
|
|
|
|
elif key == 'b':
|
|
|
|
|
blended = bool(val)
|
|
|
|
|
else:
|
|
|
|
|
assert False, 'Unknown AugMix config section'
|
|
|
|
|
ops = augmix_ops(magnitude=magnitude, hparams=hparams)
|
|
|
|
|
return AugMixAugment(ops, alpha=alpha, width=width, depth=depth, blended=blended)
|
|
|
|
|