Pull to refresh

Albumentations: XYMasking

Level of difficultyEasy
Reading time7 min
Views981

Короткая версия

После длинного вступления, будет туториал по применению аугментации XYMasking к спектрограммам от ЭЭГ.

Кто экономит время - код с примерами можно найти по ссылке в документации библиотеки.

Длинная версия

Albumentations - это Open Source библиотека для аугментации изображений.

Аугментация - это умное слово, которое в переводе с русского на русский означает "преобразование".

Q: Зачем это надо?

A: Основное применение - тренировка нейронных сетей на картиночных данных, например ImageNet.

Чем больше разнообразных данных сеть видит при тренировке, тем выше шансы, что она выучит закономерности, а не просто запомнит их.

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

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

Пример из https://albumentations.ai/docs/examples/showcase/

Аугментации бывают очень разные, от простых отражений, до более замороченных.
На 19 февраля 2024, в Albumentations их больше 70.

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

from albumentations import (
    HorizontalFlip, ShiftScaleRotate, CLAHE, RandomRotate90,
    Transpose, ShiftScaleRotate, Blur, OpticalDistortion, GridDistortion, HueSaturationValue,
    GaussNoise, MotionBlur, MedianBlur,
    RandomBrightnessContrast, Flip, OneOf, Compose
)
import numpy as np

def strong_aug(p=0.5):
    return Compose([
        RandomRotate90(p=0.5),
        Flip(p=0.5),
        Transpose(p=0.5),
        GaussNoise(p=0.5),
        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)
        ], p=0.2),
        OneOf([
            CLAHE(clip_limit=2),
            RandomBrightnessContrast(p=0.5),
        ], 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)

augmentation = strong_aug(p=0.9)
data = {"image": image, "mask": mask}
augmented = augmentation(**data)
image, mask = augmented["image"], augmented["mask"]

В этой функции видно, что отражаем, меняем цвета, поворачиваем.

При этом если дважды применить функцию к одной и той же картинке, то результат будет разный, как минимум потому что p=<циферка> это вероятность того, что каждое конкретное преобразование будет применено в каждом заходе.

(Ссылка с большим числом примеров применения)

Q: Как правильно выбирать аугментационный pipeline, учитывая, что он улучшает модель, даже если непонятно насколько?

A: Пока эта задача считается открытой. Существуют два лагеря.

Первые, назовем их "нормальные люди" используют то, что:

  • увидели в туториалах

  • работало в похожих задачах

  • подрезано из какой-то статьи

  • не должно выбивать из исходного распределения. Например, применяют горизонтальные отражения для классифицируем животных или растений, а для спутниковых снимков отражения, повороты и сдвиги (тут тянет сказать давно забытые слова "Преобразования группы Пуанкаре").

Этот подход не требует значительных временных затрат или глубокой экспертизы в области аугментаций.

Второй лагерь – "аугментационные наркоманы" – создают сложные аугментационные пайплайны, которые действительно работают лучше, но требуют глубоких знаний и опыта. Часто это люди с опытом участия в соревнованиях по машинному обучению, например, на Kaggle, где стремление к улучшению результатов даже на малые доли процента не позволяет игнорировать аугментации.

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

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

Это было вступление. Теперь к делу.

В Albumentations есть более 70 преобразований, о многих из которых мало кто знает.

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

Жертва данного текста - XYMasking.

Оно появилось в версии 1.4.0 было создано на фоне соревнования по классификации вредной мозговой активности ( HMS - Harmful Brain Activity Classification), организованнованного Hardward Medical School на Kaggle.

Участникам предлагается анализировать сигналы электроэнцефалограмм (ЭЭГ), чтобы предсказать, чем страдает пациент.

ЭЭГ представляет собой не один временной ряд, а сразу множество — данные снимаются с 20 точек на голове, что означает 20 временных рядов.


C WikiPedia https://en.wikipedia.org/wiki/10–20_system_(EEG)
Так выглядит ЭЭГ. Не специалисту мало что понятно.
Так выглядит ЭЭГ. Не специалисту мало что понятно.

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

Q: Как с этим связаны картинки и аугментации на них?

A: Временные ряды можно визуализировать в виде спектрограммы, объединяя временное и частотное представление. Это делается путем деления временного ряда на интервалы и применения косинусного преобразования для каждого промежутка, с последующим комбинированием интервалов по времени.

Таким образом, из 1D ряда получается 2D представление, где время идет по горизонтали, а частоты — по вертикали.

Источник: https://www.kaggle.com/competitions/hms-harmful-brain-activity-classification/overview


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

В статье SpecAugment авторы описали преообразования TimeMasking и FrequencyMasking, которые также реализованы в библиотеке torchaudio.

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

Это преобразование из семейства Dropout.

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

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

В спектрограиммах похожая история, при условии, что сигнал в ЭЭГ размазан по разным частотам и временным промежуткам.

И преобразование XYMasking как раз и является обобщением TimeMasking и FrequencyMasking.

А теперь примеры применения с кодом и визуализациями:

import albumentations as A
import torchaudio
import torch
from matplotlib import pyplot as plt
import numpy as np
def visualize(image):
    plt.figure(figsize=(10, 5))
    plt.axis('off')
    plt.imshow(image)python
img = np.load('../images/spectrogram.npy')

visualize(img[:, :, 0])
В этой спектрограмме 4 канала, причем они не в обычных значениях пикселей [0-255], а float numbers
В этой спектрограмме 4 канала, причем они не в обычных значениях пикселей [0-255], а float numbers

Одна вертикальная маска (временная) фиксированной ширины, заполненная нулями

params1 = {
    "num_masks_x": 1,    
    "mask_x_length": 20,
    "fill_value": 0,    

}
transform1 = A.Compose([A.XYMasking(**params1, p=1)])
visualize(transform1(image=img[:, :, 0])["image"])

Одна вертикальная полоса (временная), причем ширина случайно выбрана из диапазона, заполненная нулями

Выше я писал, что аугментационный pipeline недетерминиован. Этот пример, когда выбор параметров аугментации при каждом применении имеют случайную составляющую.

Тут число полос фиксировано - одна вертикальная, и ни одной горизонтальной, но ширина выбирается случайным образом среди числел от 0 до 20.

У этого набора параметров есть прямой аналог из пакета torchaudio, который как раз и называется TimeMasking.

Пример с torchaudio:

spectrogram = torchaudio.transforms.Spectrogram()
masking = torchaudio.transforms.TimeMasking(time_mask_param=20)
masked = masking(torch.from_numpy(img[:, :, 0]))
visualize(masked.numpy())

А теперь на Albumentations:

params2 = {
    "num_masks_x": 1,    
    "mask_x_length": (0, 20), # This line changed from fixed  to a range
    "fill_value": 0,
}
transform2 = A.Compose([A.XYMasking(**params2, p=1)])
visualize(transform2(image=img[:, :, 0])["image"])

Одна горизонтальная (частотная) с шириной выбранной случайным образом, заполненная нулями

Имплементация с torhaudio под названием FrequencyMasking

spectrogram = torchaudio.transforms.Spectrogram()
masking = torchaudio.transforms.FrequencyMasking(freq_mask_param=20)
masked = masking(torch.from_numpy(img[:, :, 0]))
visualize(masked)

А теперь Albumentations:

params3 = {    
    "num_masks_y": 1,    
    "mask_y_length": (0, 20),
    "fill_value": 0,    

}
transform3 = A.Compose([A.XYMasking(**params3, p=1)])
visualize(transform3(image=img[:, :, 0])["image"])

Несколько вертикальных и горизонтальных полос

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

params4 = {    
    "num_masks_x": (2, 4),
    "num_masks_y": 5,    
    "mask_y_length": 8,
    "mask_x_length": (10, 20),
    "fill_value": 0,  

}
transform4 = A.Compose([A.XYMasking(**params4, p=1)])
visualize(transform4(image=img[:, :, 0])["image"])

Применение к изображениям с произвольным числом каналов

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

params5 = {    
    "num_masks_x": (2, 4),
    "num_masks_y": 5,    
    "mask_y_length": 8,
    "mask_x_length": (20, 30),
    "fill_value": (0, 1, 2, 3),  

}
transform5 = A.Compose([A.XYMasking(**params5, p=1)])
transformed = transform5(image=img)["image"]

fig, axs = plt.subplots(2, 2) 
vmin=0
vmax=3

axs[0, 0].imshow(transformed[:, :, 0], vmin=vmin, vmax=vmax)
axs[0, 0].set_title('Channel 0')
axs[0, 0].axis('off')  # Hide axes for cleaner visualization

axs[0, 1].imshow(transformed[:, :, 1], vmin=vmin, vmax=vmax)
axs[0, 1].set_title('Channel 1')
axs[0, 1].axis('off')

axs[1, 0].imshow(transformed[:, :, 2], vmin=vmin, vmax=vmax)
axs[1, 0].set_title('Channel 2')
axs[1, 0].axis('off')

axs[1, 1].imshow(transformed[:, :, 3], vmin=vmin, vmax=vmax)
axs[1, 1].set_title('Channel 3')
axs[1, 1].axis('off')

plt.tight_layout()

plt.show()

Q: А что еще можно?

A: Много всего. В самой библиотеке море интересного функицонала, который достаточно слабо освещен в примерах, и о нем в будущих частях.

Но даже если говорить про XYMasking, за рамками текста остались:

  • Применение одинакого набора занулений к набору картинок.

  • Применение к сегментационным маскам. Имплементация это позволяет, но сходу непонятно где это надо для спектрогра.

  • Применение к ключевым точкам (keypoints). Тут тоже имплементация позволяет, но зачем это может быть надо при работе со спектрограммаи непонятно.

Заключение


Если вы до сюда дочитали у меня к вам просьба - перейти по ссылке на GitHub библиотеки и поставить звездочку. Нам будет очень приятно.

А если есть вопросы, комментарии, предложения - пишите в комментариях к посту, в issues в репозитории или мне в личку.

P.S. Этот текст на английском.

Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
Total votes 6: ↑6 and ↓0+6
Comments0

Articles