据我所知,这不能通过 Keras 使用的常见“API 级别”来完成。
但是,如果你再深入一点,就会发现有一些(丑陋的)方法可以共享权重。
首先,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._trainable_weights 中删除conv2.kernel 和conv2.bias。
- 将
conv1.kernel 和conv1.bias 附加到conv2._trainable_weights。
- 完成模型定义。这里会调用
conv2.__call__();但是,由于 conv2 已经构建,因此不会重新初始化权重。
以下代码 sn-p 可能会有所帮助:
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]
这种 hacky 权重共享的一个缺点是,在模型保存/加载后权重不会保持共享。这不会影响预测,但如果您想加载训练好的模型进行进一步微调,则可能会出现问题。