Непонимание потока данных в UNET -подобных архитектурах и проблемы с выводом слоев Conv2DTranspose - PullRequest
1 голос
/ 04 февраля 2020

У меня проблема или две с входными размерами модифицированной архитектуры U- Net. Чтобы сэкономить ваше время и лучше понять / воспроизвести мои результаты, я опубликую код и выходные параметры. Модифицированная архитектура U- Net - это архитектура MultiRes UNet от https://github.com/nibtehaz/MultiResUNet/blob/master/MultiResUNet.py. и основан на этом документе https://arxiv.org/abs/1902.04049 Пожалуйста, не отключайтесь по длине этого кода. Вы можете просто скопировать и вставить его, и воспроизведение моих результатов займет не более 10 секунд. Также вам не нужен набор данных для этого. Протестировано с TF.v1.9 Keras v.2.20.

import tensorflow as tf
from tensorflow.keras.layers import Input, Conv2D, MaxPooling2D, Conv2DTranspose, concatenate, BatchNormalization, Activation, add
from tensorflow.keras.models import Model
from tensorflow.keras.activations import relu 

###{ 2D Convolutional layers

   # Arguments: ######################################################################
   #     x {keras layer} -- input layer                                   #
   #     filters {int} -- number of filters                                        #
   #     num_row {int} -- number of rows in filters                               #
   #     num_col {int} -- number of columns in filters                           #

    # Keyword Arguments:
   #     padding {str} -- mode of padding (default: {'same'})
  #      strides {tuple} -- stride of convolution operation (default: {(1, 1)})
 #       activation {str} -- activation function (default: {'relu'})
#        name {str} -- name of the layer (default: {None})

  #  Returns:
  #          [keras layer] -- [output layer]}

      # # ############################################################################


def conv2d_bn(x, filters ,num_row,num_col, padding = "same", strides = (1,1), activation = 'relu', name = None):

    x = Conv2D(filters,(num_row, num_col), strides=strides, padding=padding, use_bias=False)(x)
    x = BatchNormalization(axis=3, scale=False)(x)
    if(activation == None):
        return x
    x = Activation(activation, name=name)(x)

    return x

# our 2D transposed Convolution with batch normalization

 # 2D Transposed Convolutional layers

 #   Arguments:      #############################################################
 #       x {keras layer} -- input layer                                         #
 #       filters {int} -- number of filters                                    #
 #       num_row {int} -- number of rows in filters                           #
 #       num_col {int} -- number of columns in filters

 #   Keyword Arguments:
 #       padding {str} -- mode of padding (default: {'same'})
 #       strides {tuple} -- stride of convolution operation (default: {(2, 2)}) 
 #       name {str} -- name of the layer (default: {None})

  #  Returns:
  #      [keras layer] -- [output layer] ###################################

def trans_conv2d_bn(x, filters, num_row, num_col, padding='same', strides=(2, 2), name=None): 

    x = Conv2DTranspose(filters, (num_row, num_col), strides=strides, padding=padding)(x)
    x = BatchNormalization(axis=3, scale=False)(x)

    return x

# Our Multi-Res Block 

# Arguments: ############################################################
#        U {int} -- Number of filters in a corrsponding UNet stage     #
#        inp {keras layer} -- input layer                             #

#    Returns:                                                       #
#        [keras layer] -- [output layer]                           #
###################################################################

def MultiResBlock(U, inp, alpha = 1.67):

    W = alpha * U

    shortcut = inp

    shortcut = conv2d_bn(shortcut, int(W*0.167) + int(W*0.333) +
                         int(W*0.5), 1, 1, activation=None, padding='same')

    conv3x3 = conv2d_bn(inp, int(W*0.167), 3, 3,
                        activation='relu', padding='same')

    conv5x5 = conv2d_bn(conv3x3, int(W*0.333), 3, 3,
                        activation='relu', padding='same')

    conv7x7 = conv2d_bn(conv5x5, int(W*0.5), 3, 3,
                        activation='relu', padding='same')

    out = concatenate([conv3x3, conv5x5, conv7x7], axis=3)
    out = BatchNormalization(axis=3)(out)

    out = add([shortcut, out])
    out = Activation('relu')(out)
    out = BatchNormalization(axis=3)(out)

    return out

# Our ResPath:
# ResPath

#    Arguments:#######################################
#        filters {int} -- [description]
#        length {int} -- length of ResPath
#        inp {keras layer} -- input layer 

#    Returns:
#        [keras layer] -- [output layer]#############



def ResPath(filters, length, inp):
    shortcut = inp
    shortcut = conv2d_bn(shortcut, filters, 1, 1,
                         activation=None, padding='same')

    out = conv2d_bn(inp, filters, 3, 3, activation='relu', padding='same')

    out = add([shortcut, out])
    out = Activation('relu')(out)
    out = BatchNormalization(axis=3)(out)

    for i in range(length-1):

        shortcut = out
        shortcut = conv2d_bn(shortcut, filters, 1, 1,
                             activation=None, padding='same')

        out = conv2d_bn(out, filters, 3, 3, activation='relu', padding='same')

        out = add([shortcut, out])
        out = Activation('relu')(out)
        out = BatchNormalization(axis=3)(out)

    return out



#    MultiResUNet

#    Arguments: ############################################
#        height {int} -- height of image 
#        width {int} -- width of image 
#        n_channels {int} -- number of channels in image

#    Returns:
#        [keras model] -- MultiResUNet model###############




def MultiResUnet(height, width, n_channels):



    inputs = Input((height, width, n_channels))

    # downsampling part begins here 

    mresblock1 = MultiResBlock(32, inputs)
    pool1 = MaxPooling2D(pool_size=(2, 2))(mresblock1)
    mresblock1 = ResPath(32, 4, mresblock1)

    mresblock2 = MultiResBlock(32*2, pool1)
    pool2 = MaxPooling2D(pool_size=(2, 2))(mresblock2)
    mresblock2 = ResPath(32*2, 3, mresblock2)

    mresblock3 = MultiResBlock(32*4, pool2)
    pool3 = MaxPooling2D(pool_size=(2, 2))(mresblock3)
    mresblock3 = ResPath(32*4, 2, mresblock3)

    mresblock4 = MultiResBlock(32*8, pool3)


    # Upsampling part 

    up5 = concatenate([Conv2DTranspose(
        32*4, (2, 2), strides=(2, 2), padding='same')(mresblock4), mresblock3], axis=3)
    mresblock5 = MultiResBlock(32*8, up5)

    up6 = concatenate([Conv2DTranspose(
        32*4, (2, 2), strides=(2, 2), padding='same')(mresblock5), mresblock2], axis=3)
    mresblock6 = MultiResBlock(32*4, up6)

    up7 = concatenate([Conv2DTranspose(
        32*2, (2, 2), strides=(2, 2), padding='same')(mresblock6), mresblock1], axis=3)
    mresblock7 = MultiResBlock(32*2, up7)


    conv8 = conv2d_bn(mresblock7, 1, 1, 1, activation='sigmoid')

    model = Model(inputs=[inputs], outputs=[conv8])

    return model

Итак, вернемся к моей проблеме с несовпадающими размерами ввода / вывода в архитектуре UNet.

Если я выберу высоту / ширину фильтра (128,128) или (256,256) или (512,512) и сделаю:

 model = MultiResUnet(128, 128,3)
 display(model.summary()) 

Tensorflow дает мне прекрасный результат того, как выглядит вся архитектура. Теперь, если я сделаю это

     model = MultiResUnet(36, 36,3)
     display(model.summary()) 

, я получу эту ошибку:

---------------------- -------------------------------------------------- --- ValueError Traceback (последний вызов был последним) в ----> 1 модель = MultiRes Unet (36, 36,3) 2 дисплея (model.summary ())

в MultiRes Unet (высота, ширина, n_channels) 25 26 up5 = сцепление ([Conv2DTranspose (---> 27 32 * 4, (2, 2), шаги = (2, 2), заполнение = 'same') (mresblock4), mresblock3], axis = 3) 28 mresblock5 = MultiResBlock (32 * 8, up5) 29

~ / miniconda3 / envs / MastersThenv / lib / python3 .6 / site-пакеты / tenorflow / python / keras /layers/merge.py в сцеплении (входы, ось, ** кваргс) 682 Тензор, конкатенация входов вдоль оси axis. 683 "" "-> 684 return Конкатенация (ось = ось, ** кваргс) (входные данные) 685 686

~ / miniconda3 / envs / MastersThenv / lib / python3 .6 / site-packages / tenorflow / python / keras / engine / base_layer.py в вызов (self, input, * args, ** kwargs) 694 если все (hasattr (x, 'get_shape') для x в input_list): 695 input_shapes = nest.map_structure (лямбда-x: x.get_shape (), input) -> 696 self.build (input_shapes) 697 698 # Проверка входных предположений, установленных после построения слоя, например, формы ввода.

~ / miniconda3 / envs / MastersThenv / lib / python3 .6 / site-packages / tenorflow / python / keras / utils / tf_utils.py в оболочке (instance, input_shape) 146 else: 147 input_shape = tuple (tenor_shape.TensorShape (input_shape) .as_list ()) -> 148 output_shape = fn (instance, input_shape) 149, если output_shape не равен None: 150, если isinstance (output_shape, list):

~ / miniconda3 / envs / MastersThenv / lib / python3 .6 / site-packages / tenorflow / python / keras / слои / merge.py в сборке (self, input_shape) 388 «входов с соответствующими формами» 3 89 'кроме конкатной оси. '-> 390' Получил входные фигуры:% s '% (input_shape)) 391 392 def _merge_function (self, input):

ValueError: Для слоя Concatenate требуются входные данные с соответствующими формами, за исключением concat ось. Получил входные формы: [(Нет, 8, 8, 128), (Нет, 9, 9, 128)]

Почему Conv2DTranspose дает мне неправильный размер

(Нет, 8, 8, 128)

вместо

(Нет, 9, 9, 128)

и почему функция Concat не жалуется, когда я выбираю размеры фильтров, такие как (128,128), (256,256) и т. д. c. (кратно 32) Итак, чтобы обобщить этот вопрос, как я могу заставить эту архитектуру UNet работать с любым размером фильтра и как я могу иметь дело со слоем Conv2DTranspose, создающим вывод, который имеет на одно измерение меньше (ширина / высота), чем на самом деле необходимое измерение (если размер фильтра не кратен 32 или не симметричен c) и почему этого не происходит с фильтрами других размеров, кратными 32. И что, если у меня переменная Размеры ввода ??

Любая помощь будет принята с благодарностью.

ура, H

1 Ответ

2 голосов
/ 05 февраля 2020
Семейство моделей

U- Net (например, модель MultiRes UNet выше) соответствует архитектуре кодер-декодер. Encoder - это путь понижающей дискретизации с извлечением признаков, тогда как декодер - с повышающей дискретизацией. Карты функций из кодера объединены в декодере через skip-соединений . Эти карты объектов объединяются на последней оси, оси 'channel' (учитывая, что объекты имеют размеры [batch_size, height, width, channel]). Теперь для объединения элементов по любой оси (в нашем случае - по оси каналов) размеры по всем другим осям должны совпадать.

В приведенной выше архитектуре модели: в тракте энкодера выполняется 3 понижающей дискретизации / макс-пула операций (через MaxPooling2D). На пути декодера 3 выполняются операции повышения дискретизации / транспонирования , направленные на восстановление изображения в полном размере. Однако для того, чтобы конкатенации (через пропускаемые соединения) происходили, размеры элементов с пониженной или повышенной частотой выборки height, width & batch_size должны оставаться идентичными на каждом "уровне" модели. Я проиллюстрирую это на примерах, которые вы упомянули в вопросе:

1-й случай : входные размеры (128,128,3) : 128 -> 64 -> 32 -> 16 -> 32 -> 64 -> 128

2-й регистр : входные размеры (36,36,3) : 36 -> 18 -> 9 -> 4 -> 8 -> 16 -> 32

Во 2-м случае, когда достигаются высота и ширина карты объектов 9 в тракте кодера, дальнейшая понижающая дискретизация приводит к изменению размера (потере), что не может быть восстановлено в декодере при повышающей дискретизации. Следовательно, он выдает ошибку из-за невозможности объединить карты объектов измерений [(Нет, 8, 8, 128)] & [(Нет, 9, 9, 128)],

Как правило, для простой модели кодера-декодера (с пропускаемыми соединениями), имеющей слои понижающей дискретизации ' n ' (MaxPooling2D), входное измерение должно быть кратно 2 ^ n , чтобы иметь возможность объединить функции кодировщика модели в декодере. В этом случае n = 3 , следовательно, входное значение должно быть кратным 8 , чтобы не столкнуться с этими ошибками несоответствия размеров.

Надеюсь, это поможет! :)

...