Замените слои шага в приложении MobileNet в Keras

Я хотел бы применить в Keras MobileNetV2 изображения размера 39 x 39 для классификации 3 классов. Мои изображения представляют собой тепловые карты (например, какие клавиши были нажаты на клавиатуре). Я думаю, что MobileNet был разработан для работы с изображениями размером 224 x 224. Я не буду использовать трансферное обучение, а буду обучать модель с нуля.

Чтобы заставить MobileNet работать с моими изображениями, я хотел бы заменить первые три извилины шага 2 на шаг 1. У меня есть следующий код:

from tensorflow.keras.applications import MobileNetV2

base_model = MobileNetV2(weights=None, include_top=False, 
                         input_shape=[39,39,3])
x = base_model.output
x = GlobalAveragePooling2D()(x)
x = Dropout(0.5)(x)
output_tensor = Dense(3, activation='softmax')(x)
cnn_model = Model(inputs=base_model.input, outputs=output_tensor)

opt = Adam(lr=learning_rate)
cnn_model.compile(loss='categorical_crossentropy', 
             optimizer=opt, metrics=['accuracy', tf.keras.metrics.AUC()])

Как я могу заменить первые три извилины шага 2 шагом 1, не строя MobileNet самостоятельно?


person machinery    schedule 09.05.2021    source источник
comment
почему нельзя изменить размер изображений?   -  person SoheilStar    schedule 09.05.2021
comment
@SoheilStar При изменении размера изображений это менее эффективно (чем больше изображения, тем больше время выполнения и потребление памяти).   -  person machinery    schedule 09.05.2021


Ответы (1)


Вот один обходной путь для ваших нужд, но я думаю, что, возможно, можно использовать более общий подход. Однако в MobileNetV2 есть только один слой conv с strides 2. Если вы следуете исходному коду, здесь

 x = layers.Conv2D(
      first_block_filters,
      kernel_size=3,
      strides=(2, 2),
      padding='same',
      use_bias=False,
      name='Conv1')(img_input)
  x = layers.BatchNormalization(
      axis=channel_axis, epsilon=1e-3, momentum=0.999, name='bn_Conv1')(
          x)
  x = layers.ReLU(6., name='Conv1_relu')(x)

А остальные блоки определяются следующим образом

  x = _inverted_res_block(
      x, filters=16, alpha=alpha, stride=1, expansion=1, block_id=0)
  x = _inverted_res_block(
      x, filters=24, alpha=alpha, stride=2, expansion=6, block_id=1)
  x = _inverted_res_block(
      x, filters=24, alpha=alpha, stride=1, expansion=6, block_id=2)

Итак, здесь я разберусь с первым conv с stride=(2, 2). Идея проста, мы добавим новый слой в нужное место встроенной модели, а затем удалим нужный слой.

def _make_divisible(v, divisor, min_value=None):
    if min_value is None:
        min_value = divisor
    new_v = max(min_value, int(v + divisor / 2) // divisor * divisor)
    # Make sure that round down does not go down by more than 10%.
    if new_v < 0.9 * v:
        new_v += divisor
    return new_v
alpha = 1.0
first_block_filters = _make_divisible(32 * alpha, 8)

inputLayer = tf.keras.Input(shape=(39, 39, 3), name="inputLayer")
inputcOonv = tf.keras.layers.Conv2D(
                first_block_filters,
                kernel_size=3,
                strides=(1, 1),
                padding='same',
                use_bias=False,
                name='Conv1_'
        )(inputLayer)

Приведенная выше функция _make_divisible просто получена из исходного кода. Так или иначе, теперь мы приписываем этот слой MobileNetV2 прямо перед первым слоем conv следующим образом:

base_model = tf.keras.applications.MobileNetV2(weights=None, 
                            include_top=False, 
                            input_tensor = inputcOonv)
x = base_model.output
x = GlobalAveragePooling2D()(x)
x = Dropout(0.5)(x)
output_tensor = Dense(3, activation='softmax')(x)
cnn_model = Model(inputs=base_model.input, outputs=output_tensor)

Теперь, если мы наблюдаем

for i, l in enumerate(cnn_model.layers):
    print(l.name, l.output_shape)
    if i == 8: break

inputLayer [(None, 39, 39, 3)]
Conv1_ (None, 39, 39, 32)
Conv1 (None, 20, 20, 32)
bn_Conv1 (None, 20, 20, 32)
Conv1_relu (None, 20, 20, 32)
expanded_conv_depthwise (None, 20, 20, 32)
expanded_conv_depthwise_BN (None, 20, 20, 32)
expanded_conv_depthwise_relu (None, 20, 20, 32)
expanded_conv_project (None, 20, 20, 16)

Имя слоя Conv1_ и Conv1 — это новый слой (с strides = 1) и старый слой (с strides = 2) соответственно. И так как нам нужно, теперь мы удаляем слой Conv1 с strides = 2 следующим образом:

cnn_model._layers.pop(2) # remove Conv1

for i, l in enumerate(cnn_model.layers):
    print(l.name, l.output_shape)
    if i == 8: break

inputLayer [(None, 39, 39, 3)]
Conv1_ (None, 39, 39, 32)
bn_Conv1 (None, 20, 20, 32)
Conv1_relu (None, 20, 20, 32)
expanded_conv_depthwise (None, 20, 20, 32)
expanded_conv_depthwise_BN (None, 20, 20, 32)
expanded_conv_depthwise_relu (None, 20, 20, 32)
expanded_conv_project (None, 20, 20, 16)
expanded_conv_project_BN (None, 20, 20, 16)

Теперь у вас есть модель cnn_model с strides = 1 на первом слое conv. Однако, если вас интересует этот подход и возможная проблема, см. мой другой ответ, связанный с этим. Удалить первые N слоев из модели Keras?

person M.Innat    schedule 09.05.2021
comment
Большое спасибо за ответ. Я вижу пять шагов 2. Есть четыре перевернутых блока res с шагом 2, см. x = _inverted_res_block(x, filters=24, alpha=alpha, stride=2, expansion=6, block_id=1). Как я могу также изменить их на шаг 1? - person machinery; 09.05.2021
comment
Итак, вы хотите сделать stride = 2 to 1 для всех случаев? Приведенный выше обходной путь больше похож на решение в пространстве слоев (изменить слой), чем на пространство гиперпараметров (шаги, отступы и т. д.). Насколько я знаю, невозможно сделать это без достаточного раскрытия предварительно обученной модели. Поэтому вместо этого я бы просто рекомендовал получить соответствующий исходный код и изменить его в соответствии с вашими потребностями. - person M.Innat; 09.05.2021