Как преобразовать плотный слой в эквивалентный сверточный слой в Keras?

Я хотел бы сделать что-то похожее на документ Fully Convolutional Networks (https://people.eecs.berkeley.edu/~jonlong/long_shelhamer_fcn.pdf) с помощью Keras. У меня есть сеть, которая заканчивает выравнивание карт функций и запускает их через несколько плотных слоев. Я хотел бы загрузить весы из такой сети в одну, где плотные слои заменяются эквивалентными свертками.

В качестве примера можно использовать сеть VGG16, которая поставляется вместе с Keras, где вывод 7x7x512 последнего MaxPooling2D() сглажен, а затем переходит в слой Dense (4096). В этом случае Dense (4096) будет заменен сверткой 7x7x4096.

Моя реальная сеть немного отличается, есть слой GlobalAveragePooling2D() вместо MaxPooling2D() и Flatten(). Вывод GlobalAveragePooling2D() является двумерным тензором, и нет необходимости дополнительно его сглаживать, поэтому все плотные слои, включая первый, будут заменены на 1x1 свертки.

Я видел этот вопрос: Python keras, как преобразовать плотный слой в сверточный слой, который кажется очень похожим, если не идентичным. Проблема в том, что я не могу заставить предлагаемое решение работать, потому что (а) я использую TensorFlow в качестве бэкэнд, поэтому перестановка/фильтрация весов "неправильная", и (б) я не могу понять как загрузить вес. Загрузка старого файла весов в новую сеть с помощью model.load_weights(by_name=True) не работает, потому что имена не совпадают (и даже если они отличаются друг от друга).

Какова должна быть перестановка при использовании TensorFlow?

Как загрузить вес? Создать одну из каждой модели, вызвать model.load_weights() для загрузки одинаковых весов, а затем скопировать некоторые дополнительные веса, которые нуждаются в перестановке?

Ответ 1

а. Не нужно делать сложное вращение. Просто измените работу

б. Используйте get_weights() и запустите новый слой

Итерации через model.layers, создайте один и тот же слой с конфигурацией и весом нагрузки, используя set_weights или как показано ниже.

Следующий фрагмент псевдокода работает для меня. (Keras 2.0)

Псевдокод:

# find input dimensions of Flatten layer
f_dim =  flatten_layer.input_shape

# Creating new Conv layer and putting dense layers weights 
m_layer = model.get_layer(layer.name)
input_shape = m_layer.input_shape
output_dim =  m_layer.get_weights()[1].shape[0]
W,b = layer.get_weights()
if first dense layer :
    shape = (f_dim[1],f_dim[2],f_dim[3],output_dim)
    new_W = W.reshape(shape)
    new_layer = Convolution2D(output_dim,(f_dim[1],f_dim[2]),strides=(1,1),activation='relu',padding='valid',weights=[new_W,b])

else: (not first dense layer)
    shape = (1,1,input_shape[1],output_dim)
    new_W = W.reshape(shape)
    new_layer = Convolution2D(output_dim,(1,1),strides=(1,1),activation='relu',padding='valid',weights=[new_W,b])

Ответ 2

Основываясь на ответе hars, я создал эту функцию для преобразования произвольного cnn в fcn:

from keras.models import Sequential
from keras.layers.convolutional import Convolution2D
from keras.engine import InputLayer
import keras

def to_fully_conv(model):

    new_model = Sequential()

    input_layer = InputLayer(input_shape=(None, None, 3), name="input_new")

    new_model.add(input_layer)

    for layer in model.layers:

        if "Flatten" in str(layer):
            flattened_ipt = True
            f_dim = layer.input_shape

        elif "Dense" in str(layer):

            input_shape = layer.input_shape
            output_dim =  layer.get_weights()[1].shape[0]
            W,b = layer.get_weights()

            if flattened_ipt:
                shape = (f_dim[1],f_dim[2],f_dim[3],output_dim)
                new_W = W.reshape(shape)
                new_layer = Convolution2D(output_dim,
                                          (f_dim[1],f_dim[2]),
                                          strides=(1,1),
                                          activation=layer.activation,
                                          padding='valid',
                                          weights=[new_W,b])
                flattened_ipt = False

            else:
                shape = (1,1,input_shape[1],output_dim)
                new_W = W.reshape(shape)
                new_layer = Convolution2D(output_dim,
                                          (1,1),
                                          strides=(1,1),
                                          activation=layer.activation,
                                          padding='valid',
                                          weights=[new_W,b])


        else:
            new_layer = layer

        new_model.add(new_layer)

    return new_model

вы можете протестировать эту функцию следующим образом:

model = keras.applications.vgg16.VGG16()
new_model = to_fully_conv(model)