TensorFlow2 реализует пространственную адаптивную нормализацию (Spatial Adaptive Normalization, SPADE)

искусственный интеллект глубокое обучение
TensorFlow2 реализует пространственную адаптивную нормализацию (Spatial Adaptive Normalization, SPADE)

"Это 6-й день моего участия в ноябрьском испытании обновлений, ознакомьтесь с подробностями события:Вызов последнего обновления 2021 г."

Пространственная адаптивная нормализация (SPADE)

Пространственная адаптивная нормализация (Spatial Adaptive Normalization, SPADE)ДаGauGANОсновное новшество в , которое используется для послойной нормализации графов семантической сегментации, для лучшей интерпретацииSPADE, надо сначала понятьGauGANСетевой вход -语义分割图.

Маски сегментации меток с использованием одноразового кодирования

Рассмотрите возможность обученияGauGANиспользовалFacades数据集. Среди них карта сегментации находится вRGBИзображения кодируются разными цветами, как показано на изображении ниже. Например, стена отображается синим цветом, а колонна — красным. Это представление визуально просто для понимания, но бесполезно для обучения нейронной сети, потому что дляGANС точки зрения цветов семантики нет.

语义分割图

Тот факт, что цвета ближе в цветовом пространстве, не означает, что они близки и семантически. Например, мы можем изобразить траву светло-зеленым цветом, а самолеты — темно-зеленым, хотя карты сегментации имеют похожий оттенок, их семантика не имеет значения.

Поэтому вместо того, чтобы использовать цвета для маркировки пикселей, мы должны использовать метки классов. Однако это все еще не решает проблему, потому что метки классов являются случайными числами и не имеют семантики. Таким образом, лучший подход — использовать маску сегментации с меткой 1, если в этом пикселе есть объект, и маску сегментации с меткой 0 в противном случае. Другими словами, мы сразу кодируем метки на карте сегментации в виде форм.(H, W, number of classes)маска сегментации.

существуетJPEGПри кодировании некоторая визуальная информация, не очень важная для визуального эффекта, удаляется в процессе сжатия. Несмотря на то, что результирующие пиксели должны принадлежать к одному классу и иметь один и тот же цвет, они могут иметь разные значения. Поэтому мы не можемJPEGЦвета на изображении сопоставлены с классами. Чтобы решить эту проблему, нам нужно использовать несжатый формат изображенияBMP. При загрузке и предварительной обработке изображений мы будем загружать файлы и конвертировать их изBMPПреобразуйте в маску сегментации с горячим кодированием.

иногда,TensorFlowбазовая предварительная обработка изображенийAPIНекоторые сложные задачи не могут быть выполнены, поэтому нам нужно использовать другиеPythonбиблиотека,tf.py_functionпозвольте намTensorFlowОбщий бег в тренировочном процессеPythonфункция:

def load(image_file):
    def load_data(image_file):
        jpg_file = image_file.numpy().decode('utf-8')
        bmp_file = jpg_file.replace('.jpg', '.bmp')
        png_file = jpg_file.replace('.jpg', '.png')
        image = np.array(Image.open(jpg_file))/127.5-1
        map = np.array(Image.open(png_file))/127.5-1
        labels = np.array(Image.open(bmp_file), dtype=np.uint8)
        h,w,_ = image.shape
        n_class = 12
        mask = np.zeros((h,w,n_class),dtype=np.float32)
        for i in range(n_class):
            one_hot[labels==i,i] = 1
        return map, image, mask
    [mask, image, label] = tf.py_function(load_data, [image_file], [tf.float32, tf.float32, tf.float32])

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

Реализовать ЛОПАТУ

实例归一化Он стал очень популярен при создании изображений, но имеет тенденцию ослаблять семантику масок сегментации: предположим, что входное изображение содержит только одну метку сегментации; например, если предположить, что все изображение представляет собой небо, поскольку входные данные имеют одинаковые значения, выходные Слои позже также будут иметь единые значения.

Нормализация экземпляра вычисляет среднее значение по измерениям (H, W) для каждого канала. Следовательно, среднее значение для этого канала будет одинаковым, а нормализованная активация после вычитания среднего станет равной нулю. Очевидно, что семантика была потеряна, что является довольно крайним примером, но логика похожа, мы видим, что маска сегментации теряет свое семантическое значение по мере увеличения ее площади.

Для решения этой проблемы,SPADEНормализует локальную область, ограниченную маской сегментации, а не всей маской. На рисунке ниже показаноSPADEАрхитектура:

SPADE体系结构

При пакетной нормализации вычисления по измерениям(N, H, W)Среднее значение и стандартное отклонение каналов, дляSPADEто же самое. Разница в том, что каждый каналγиβбольше не скалярное значение, а двумерный вектор формы(H, W). Другими словами, для каждой активации, полученной из карты семантической сегментации, существуетγиβценность. Поэтому нормализация применяется по-разному к разным сегментированным областям. Эти два параметра изучаются с помощью двух сверточных слоев, как показано на следующем рисунке:

SPADE

SPADEПрименяется не только к входному каскаду сети, но и к внутренним слоям. Теперь мы можем использоватьTensorFlow2Реализация пользовательского слояSPADE.

сначала будет__init__Сверточный слой определяется в конструкторе следующим образом:

class SPADE(layers.Layer):
    def __init__(self, filters, epsilon=1e-5):
        super(SPADE, self).__init__()
        self.epsilon = epsilon
        self.conv = layers.Conv2D(128, 3, padding='same', activation='relu')
        self.conv_gamma = layers.Conv2D(filters, 3, padding='same')
        self.conv_beta = layers.Conv2D(filters, 3, padding='same')

Затем получите размеры карты активации, чтобы использовать их при изменении размера позже:

    def build(self, input_shape):
        self.resize_shape = input_shape[1:3]

Наконец, вcall()Соедините слои и операции вместе следующим образом:

    def call(self, input_tensor, raw_mask):
        mask = tf.image.resize(raw_mask, self.resize_shape, method='nearest')
        x = self.conv(mask)
        gamma = self.conv_gamma(x)
        beta = self.conv_beta(x)
        mean, var = tf.nn.moments(input_tensor, axes=(0,1,2), keepdims=True)
        std = tf.sqrt(var+self.epsilon)
        normalized = (input_tensor - mean) / std
        output = gamma * normalized + beta
        return output

Далее мы рассмотрим, как использоватьSPADE.

Применение SPADE в остаточных сетях

Наконец, мы будем изучать, какSPADEВставьте в остаточный блок:

应用SPADE

SPADEОсновными строительными блоками остаточного блока являютсяSPADE-ReLU-ConvЭтаж. каждыйSPADEОба принимают два входа — активации предыдущего слоя и карту семантической сегментации.

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

class Resblock(layers.Layer):
    def __init__(self, filters):
        super(Resblock, self).__init__()
        self.filters = filters
    
    def build(self, input_shape):
        input_filter = input_shape[-1]
        self.spade_1 = SPADE(input_filter)
        self.spade_2 = SPADE(self.filters)
        self.conv_1 = layers.Conv2D(self.filters, 3, padding='same')
        self.conv_2 = layers.Conv2D(self.filters, 3, padding='same')
        self.learned_skip = False
        if self.filters != input_filter:
            self.learned_skip = True
            self.spade_3 = SPADE(input_filter)
            self.conv_3 = layers.Conv2D(self.filters, 3, padding='same')

Наконец, вcall()Соедините слои в:

    def call(self, input_tensor, mask):
        x = self.spade_1(input_tensor, mask)
        x = self.conv_1(tf.nn.leaky_relu(x, 0.2))
        x = self.spade_2(x, mask)
        x = self.conv_2(tf.nn.leaky_relu(x, 0.2))
        if self.learned_skip:
            skip = self.spade_3(input_tensor, mask)
            skip = self.conv_3(tf.nn.leaky_relu(skip, 0.2))
        else:
            skip = input_tensor
        output = skip + x
        return output