Могу ли я делиться весами между слоями keras, но имеют другие параметры?

В keras, можно ли делиться весами между двумя слоями, но иметь разные параметры? Рассмотрим следующий пример (предположительно, немного надуманный):

conv1 = Conv2D(64, 3, input_shape=input_shape, padding='same')
conv2 = Conv2D(64, 3, input_shape=input_shape, padding='valid')

Обратите внимание, что слои идентичны, за исключением padding. Могу ли я получить keras для использования одинаковых весов для обоих? (т.е. соответственно обучать сеть?)

Я посмотрел на keras doc, а раздел на общих слоях, по-видимому, подразумевает, что совместное использование работает только в том случае, если слои полностью идентичны.

Ответ 1

Насколько мне известно, это невозможно сделать с помощью общего "уровня API" использования Keras. Однако, если вы копаете немного глубже, есть некоторые (уродливые) способы распределения весов.

Прежде всего, веса слоев Conv2D создаются внутри функции build(), вызывая add_weight():

    self.kernel = self.add_weight(shape=kernel_shape,
                                  initializer=self.kernel_initializer,
                                  name='kernel',
                                  regularizer=self.kernel_regularizer,
                                  constraint=self.kernel_constraint)

Для вашего предоставленного использования (т.е. по умолчанию trainable/constraint/regularizer/initializer), add_weight() ничего не делает, но добавляет весовые переменные в _trainable_weights:

    weight = K.variable(initializer(shape), dtype=dtype, name=name)
    ...
        self._trainable_weights.append(weight)

Наконец, поскольку build() вызывается только внутри __call__(), если слой не был создан, общие веса между слоями могут быть созданы:

  • Вызовите conv1.build(), чтобы инициализировать переменные conv1.kernel и conv1.bias для совместного использования.
  • Вызовите conv2.build() для инициализации слоя.
  • Замените conv2.kernel и conv2.bias на conv1.kernel и conv1.bias.
  • Удалите conv2.kernel и conv2.bias из conv2._trainable_weights.
  • Добавить conv1.kernel и conv1.bias в conv2._trainable_weights.
  • Завершить определение модели. Здесь conv2.__call__() будем называть; однако, поскольку conv2 уже построен, веса не будут повторно инициализированы.

Может оказаться полезным следующий фрагмент кода:

def create_shared_weights(conv1, conv2, input_shape):
    with K.name_scope(conv1.name):
        conv1.build(input_shape)
    with K.name_scope(conv2.name):
        conv2.build(input_shape)
    conv2.kernel = conv1.kernel
    conv2.bias = conv1.bias
    conv2._trainable_weights = []
    conv2._trainable_weights.append(conv2.kernel)
    conv2._trainable_weights.append(conv2.bias)

# check if weights are successfully shared
input_img = Input(shape=(299, 299, 3))
conv1 = Conv2D(64, 3, padding='same')
conv2 = Conv2D(64, 3, padding='valid')
create_shared_weights(conv1, conv2, input_img._keras_shape)
print(conv2.weights == conv1.weights)  # True

# check if weights are equal after model fitting
left = conv1(input_img)
right = conv2(input_img)
left = GlobalAveragePooling2D()(left)
right = GlobalAveragePooling2D()(right)
merged = concatenate([left, right])
output = Dense(1)(merged)
model = Model(input_img, output)
model.compile(loss='binary_crossentropy', optimizer='adam')

X = np.random.rand(5, 299, 299, 3)
Y = np.random.randint(2, size=5)
model.fit(X, Y)
print([np.all(w1 == w2) for w1, w2 in zip(conv1.get_weights(), conv2.get_weights())])  # [True, True]

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