Машинное обучение, Программирование

Построение сложных конвейеров увеличения изображений с помощью Tensorflow

Использование модуля данных Tensorflow для построения сложного конвейера увеличения изображений.

Если вы хотите обучить свои модели с помощью Tensorflow наиболее эффективным способом, вам, вероятно, следует использовать TFRecords и Модуль данных Tensorflow для построения ваших конвейеров, но в зависимости от требований и ограничений ваших приложений их использование может быть необходимым, а не и, как вариант, хорошая новость заключается в том, что Tensorflow сделал их оба довольно чистыми и простыми в использовании.

В этой статье мы рассмотрим простой, но эффективный способ построения конвейеров со сложными комбинациями увеличения данных с использованием модуля данных Tensorflow.

Один из упомянутых мной вариантов, который может улучшить обучение ваших моделей, - это использовать TFRecords, TFRecord - это простой формат, предоставляемый Tensorflow для хранения данных, я не буду вдаваться в подробности о TFRecords, потому что это не является предметом внимания данной статьи. но если вы хотите узнать больше, ознакомьтесь с этим руководством от Tensorflow.

Информация, представленная здесь, может быть применена для обучения моделей с помощью Tensorflow на любом оборудовании, я собираюсь использовать TPU в качестве целевого оборудования, потому что, если вы используете TPU, вероятно, вы уже пытаетесь максимально использовать свои ресурсы, и вам понадобится в любом случае использовать модуль данных Tensorflow.

Увеличение данных с помощью Tensorflow

Во-первых, мы начнем с того, что посмотрим, как выполняется увеличение данных, в официальном руководстве по увеличению данных от Tensorflow.

# Data augmentation function
def augment(image, label):
  image = tf.image.random_crop(image, size=[IMG_SIZE, IMG_SIZE, 3])
  image = tf.image.random_brightness(image, max_delta=0.5)
  image = tf.clip_by_value(image, 0, 1)
  return image, label

# Tensorflow data pipeline
train_ds = (
    train_ds
    .shuffle(1000)
    .map(augment, num_parallel_calls=AUTOTUNE)
    .batch(batch_size)
    .prefetch(AUTOTUNE)
)

Как мы видим из функции увеличения, она применяет последовательность преобразований к изображениям: сначала выполняется произвольная обрезка, затем применяется случайная яркость и, наконец, обрезаются значения, чтобы они оставались между 0. и 1.

Следуя лучшим практикам Tensorflow, функция увеличения данных обычно применяется к конвейеру данных с помощью операции map.

Проблема с подходом выше заключается в том, как преобразования применяются к изображениям, вы в основном просто складываете их последовательно, как правило, вам нужно будет иметь некоторый контроль над тем, что и как применяется, позвольте мне описать несколько сценариев, чтобы сделать моя точка зрения.

Сценарий 1:

Для ваших данных могут быть полезны передовые методы увеличения данных, такие как Cutout, Mixup или CutMix. Если вы знакомы с тем, как они работают, вы знаете, что для каждого образца вы, вероятно, собираетесь применить только один из них.

Сценарий 2:

Возможно, вы захотите использовать множество дополнений «на уровне пикселей», под уровнем пикселей я имею в виду преобразования, такие как яркость, регулировка гаммы, контраст или насыщенность, обычно более легкие варианты этих преобразований можно безопасно использовать во многих различных наборах данных, но с использованием всех они сразу могут слишком сильно изменить ваши изображения и в конечном итоге помешать обучению модели.

Итак, что можно было сделать?

Если вы знакомы с увеличением данных для задач компьютерного зрения, возможно, вы слышали о таких библиотеках, как Imgaug или Albumentations, если нет, то вот два примера из библиотеки Albumentations того, как она может выполнять увеличение данных:

def augment(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),
        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)


augmented_image = augment(image=image)['image']

Мы ясно видим, что Albumentations предоставляет гораздо более эффективный способ применения различных преобразований к изображениям. Вы можете применять их последовательно, как в учебнике Tensorflow, но вы также можете использовать такие операции, как OneOf, и выбрать только одно из группы преобразований, которые будут применяться, и самая важная деталь заключается в том, что здесь вы может контролировать вероятность применения каждого преобразования.
Стоит отметить, что преобразования, которые используют эти библиотеки, сильно оптимизированы для максимально быстрого выполнения, у Albumentations даже есть эталонный тест.

Лучшее из обоих миров было бы, если бы мы могли использовать такую ​​библиотеку, как Albumentations, которая очень эффективна и уже реализует множество разных преобразований с нашим конвейером данных Tensorflow, но, к сожалению, это невозможно, так что мы можем сделать ?

Сложное дополнение данных с помощью Tensorflow

На самом деле, если мы проявим немного творчества, мы можем создать функции увеличения данных, которые довольно близки к функциям, предоставляемым Albumentation, и используя только код Tensorflow, поэтому он может работать на TPU, интегрированных с конвейерами Tensorflow, вот простой пример:

def augment(image):
    p_spatial = tf.random.uniform([], 0, 1.0, dtype=tf.float32)
    p_rotate = tf.random.uniform([], 0, 1.0, dtype=tf.float32)
    
    
    # Flips
    if p_spatial >= .2:
        image = tf.image.random_flip_left_right(image)
        image = tf.image.random_flip_up_down(image)
        
    # Rotates
    if p_rotate > .75:
        image = tf.image.rot90(image, k=3) # rotate 270º
    elif p_rotate > .5:
        image = tf.image.rot90(image, k=2) # rotate 180º
    elif p_rotate > .25:
        image = tf.image.rot90(image, k=1) # rotate 90º

    return image

Большой! в этой функции есть все, что нам понравилось в Albumentations, и она представляет собой чистый Tensorflow, давайте проверим:
- [x] Применить преобразование последовательно.
- [x] Тип «OneOf» преобразования (группировка).
- [x] Управляет вероятностью применения преобразования.

Давайте разберемся, что происходит на этой функции.

Сначала мы определяем две переменные p_spatial и p_rotate, затем назначаем им вероятности, эти вероятности выбираются из случайного равномерного распределения, это означает, что все числа в интервале [ 0, 1] имеют одинаковые шансы на выборку.
Затем у нас есть два разных типа преобразований, которые мы хотим применить: переворачивание и вращение, у них есть разная семантика, поэтому они принадлежат разным группам.
Для преобразований переворота, если p_spatial больше, чем .2, мы применим два случайных Другими словами, существует 80% -я вероятность применения этих двух случайных отражений.
При преобразованиях поворота мы используем больше контроля, это будет быть похожим на OneOf из Albumentations, потому что мы применяем только одно из этих преобразований, каждое из них имеет 25% шанс применения, а также есть 25% шанс вообще ничего не применять, нам нужен такой контроль, потому что нет смысла вращать изображение на 90 ° thee раза, затем еще 2 раза и так далее.

Используя эту идею, вы можете создавать функции увеличения данных, которые могут быть намного сложнее, чем эта, вот пример, который я использовал для конкурса Kaggle Классификация меланомы SIIM-ISIC:

def data_augment(image):
    p_rotation = tf.random.uniform([], 0, 1.0, dtype=tf.float32)
    p_rotate = tf.random.uniform([], 0, 1.0, dtype=tf.float32)
    p_cutout = tf.random.uniform([], 0, 1.0, dtype=tf.float32)
    p_shear = tf.random.uniform([], 0, 1.0, dtype=tf.float32)
    p_crop = tf.random.uniform([], 0, 1.0, dtype=tf.float32)
    
    
    if p_shear > .2:
        if p_shear > .6:
            image = transform_shear(image, config['HEIGHT'], shear=20.)
        else:
            image = transform_shear(image, config['HEIGHT'], shear=-20.)
    
    if p_rotation > .2:
        if p_rotation > .6:
            image = transform_rotation(image, config['HEIGHT'], rotation=45.)
        else:
            image = transform_rotation(image, config['HEIGHT'], rotation=-45.)

    if p_crop > .2:
        image = data_augment_crop(image)

    if p_rotate > .2:
        image = data_augment_rotate(image)
        
    image = data_augment_spatial(image)
    
    image = tf.image.random_saturation(image, 0.7, 1.3)
    image = tf.image.random_contrast(image, 0.8, 1.2)
    image = tf.image.random_brightness(image, 0.1)
    
    if p_cutout > .5:
        image = data_augment_cutout(image)
    
    return image

def data_augment_spatial(image):
    p_spatial = tf.random.uniform([], 0, 1.0, dtype=tf.float32)

    image = tf.image.random_flip_left_right(image)
    image = tf.image.random_flip_up_down(image)
    if p_spatial > .75:
        image = tf.image.transpose(image)

    return image

def data_augment_rotate(image):
    p_rotate = tf.random.uniform([], 0, 1.0, dtype=tf.float32)
    
    if p_rotate > .66:
        image = tf.image.rot90(image, k=3) # rotate 270º
    elif p_rotate > .33:
        image = tf.image.rot90(image, k=2) # rotate 180º
    else:
        image = tf.image.rot90(image, k=1) # rotate 90º

    return image

def data_augment_crop(image):
    p_crop = tf.random.uniform([], 0, 1.0, dtype=tf.float32)
    crop_size = tf.random.uniform([], int(config['HEIGHT']*.7), config['HEIGHT'], dtype=tf.int32)
    
    if p_crop > .5:
        image = tf.image.random_crop(image, size=[crop_size, crop_size, config['CHANNELS']])
    else:
        if p_crop > .4:
            image = tf.image.central_crop(image, central_fraction=.7)
        elif p_crop > .2:
            image = tf.image.central_crop(image, central_fraction=.8)
        else:
            image = tf.image.central_crop(image, central_fraction=.9)
    
    image = tf.image.resize(image, size=[config['HEIGHT'], config['WIDTH']])

    return image

Я также оставлю две ссылки на полные примеры кода, использующие аналогичный подход.
- Полный код для примера выше
- Вводная записная книжка для расширенного дополнения с помощью Tensorflow

Если вы хотите узнать, как создать полный конвейер Tensorflow для обучения моделей на TPU, вот классная статья, которую я написал Эффективное использование TPU для классификации изображений.

Чтобы узнать больше, просмотрите ссылки:
- Учебник Tensorflow TFRecords
- Документация по модулю данных Tensorflow
- Учебник по модулю данных Tensorflow
- Лучше производительность с tf.data API
- Учебник по расширению данных Tensorflow
- Эффективное использование TPU для классификации изображений
- Конвейеры данных со скоростью TPU