在密集Keras层中绑定自动编码器权重



我正试图在Keras中创建一个自定义的密集层,以在自动编码器中绑定权重。我在这里尝试了在卷积层中执行此操作的示例,但其中一些步骤似乎不适用于密集层(此外,代码来自两年多前(。

通过绑定权重,我希望解码层使用编码层的转置权重矩阵。本文(第5页(也采用了这种方法。以下是文章中的相关引用:

这里,我们选择编码和解码激活函数都是sigmoid函数,并且只考虑绑权情况,其中W′=WT(其中WT

在上面的引号中,W是编码层中的权重矩阵,W'(等于W的转置(是解码层中的加权矩阵。

我并没有在致密层中改变太多。我在构造函数中添加了一个tied_to参数,它允许您传递要将其绑定到的层。唯一的其他更改是对build函数,其代码片段如下:

def build(self, input_shape):
assert len(input_shape) >= 2
input_dim = input_shape[-1]
if self.tied_to is not None:
self.kernel = K.transpose(self.tied_to.kernel)
self._non_trainable_weights.append(self.kernel)
else:
self.kernel = self.add_weight(shape=(input_dim, self.units),
initializer=self.kernel_initializer,
name='kernel',
regularizer=self.kernel_regularizer,
constraint=self.kernel_constraint)
if self.use_bias:
self.bias = self.add_weight(shape=(self.units,),
initializer=self.bias_initializer,
name='bias',
regularizer=self.bias_regularizer,
constraint=self.bias_constraint)
else:
self.bias = None
self.input_spec = InputSpec(min_ndim=2, axes={-1: input_dim})
self.built = True

以下是__init__方法,此处唯一的更改是添加了tied_to参数。

def __init__(self, units,
activation=None,
use_bias=True,
kernel_initializer='glorot_uniform',
bias_initializer='zeros',
kernel_regularizer=None,
bias_regularizer=None,
activity_regularizer=None,
kernel_constraint=None,
bias_constraint=None,
tied_to=None,
**kwargs):
if 'input_shape' not in kwargs and 'input_dim' in kwargs:
kwargs['input_shape'] = (kwargs.pop('input_dim'),)
super(Dense, self).__init__(**kwargs)
self.units = units
self.activation = activations.get(activation)
self.use_bias = use_bias
self.kernel_initializer = initializers.get(kernel_initializer)
self.bias_initializer = initializers.get(bias_initializer)
self.kernel_regularizer = regularizers.get(kernel_regularizer)
self.bias_regularizer = regularizers.get(bias_regularizer)
self.activity_regularizer = regularizers.get(activity_regularizer)
self.kernel_constraint = constraints.get(kernel_constraint)
self.bias_constraint = constraints.get(bias_constraint)
self.input_spec = InputSpec(min_ndim=2)
self.supports_masking = True
self.tied_to = tied_to

call函数未进行编辑,但下面提供了它以供参考。

def call(self, inputs):
output = K.dot(inputs, self.kernel)
if self.use_bias:
output = K.bias_add(output, self.bias, data_format='channels_last')
if self.activation is not None:
output = self.activation(output)
return output

上面,我添加了一个条件来检查是否设置了tied_to参数,如果设置了,则将层的内核设置为tied_to层内核的转置。

下面是用于实例化模型的代码。它是使用Keras的顺序API完成的,DenseTied是我的自定义层。

# encoder
#
encoded1 = Dense(2, activation="sigmoid")
decoded1 = DenseTied(4, activation="sigmoid", tied_to=encoded1)
# autoencoder
#
autoencoder = Sequential()
autoencoder.add(encoded1)
autoencoder.add(decoded1)

训练模型后,下面是模型摘要和权重。

autoencoder.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_7 (Dense)              (None, 2)                 10        
_________________________________________________________________
dense_tied_7 (DenseTied)     (None, 4)                 12        
=================================================================
Total params: 22
Trainable params: 14
Non-trainable params: 8
________________________________________________________________
autoencoder.layers[0].get_weights()[0]
array([[-2.122982  ,  0.43029135],
[-2.1772149 ,  0.16689162],
[-1.0465667 ,  0.9828905 ],
[-0.6830663 ,  0.0512633 ]], dtype=float32)

autoencoder.layers[-1].get_weights()[1]
array([[-0.6521988 , -0.7131109 ,  0.14814234,  0.26533198],
[ 0.04387903, -0.22077179,  0.517225  , -0.21583867]],
dtype=float32)

正如您所看到的,autoencoder.get_weights()报告的权重似乎没有绑定。

在展示了我的方法之后,我的问题是,这是在密集Keras层中绑定权重的有效方法吗?我能够运行代码,目前正在进行培训。损失函数似乎也在合理地减小。我担心的是,这只会在模型构建时使它们相等,而不会真正将它们联系起来。我希望后端transpose函数能够通过引擎盖下的引用将它们绑定在一起,但我确信我遗漏了一些东西。

感谢Mikhail Berlinkov,一句重要的话:这段代码在Keras下运行,但在TF2.0中不是在急切模式下运行。它运行,但训练很差。

关键点是,对象如何存储转置的权重。self.kernel=K.转置(self.tied_to.kernel(

在非急切模式下,这将以正确的方式创建一个图形。在急切模式下,这会失败,可能是因为转置变量的值存储在构建时(==第一次调用(,然后在后续调用中使用。

然而:解决方案是在构建时不更改地存储变量,并将转置操作放入调用方法中。

我花了几天时间来弄清楚这一点,如果这能帮助到任何人,我很高兴。

在展示了我的方法后,我的问题是,这是在密集Keras层中绑定权重的有效方法吗?

是的,它是有效的。

我担心的是,这只会在构建模型时使它们相等,而不会实际将它们绑定。我希望后端transpose函数能够通过引擎盖下的引用将它们绑定在一起,但我确信我遗漏了一些东西。

它实际上将它们联系在计算图中,您可以在打印model.summary()时检查这些可训练权重只有一个副本。此外,在训练模型后,您可以使用model.get_weights()检查相应层的权重。当模型建立时,实际上还没有权重,只有占位符。

random.seed(1)
class DenseTied(Layer):
def __init__(self, units,
activation=None,
use_bias=True,
kernel_initializer='glorot_uniform',
bias_initializer='zeros',
kernel_regularizer=None,
bias_regularizer=None,
activity_regularizer=None,
kernel_constraint=None,
bias_constraint=None,
tied_to=None,
**kwargs):
self.tied_to = tied_to
if 'input_shape' not in kwargs and 'input_dim' in kwargs:
kwargs['input_shape'] = (kwargs.pop('input_dim'),)
super().__init__(**kwargs)
self.units = units
self.activation = activations.get(activation)
self.use_bias = use_bias
self.kernel_initializer = initializers.get(kernel_initializer)
self.bias_initializer = initializers.get(bias_initializer)
self.kernel_regularizer = regularizers.get(kernel_regularizer)
self.bias_regularizer = regularizers.get(bias_regularizer)
self.activity_regularizer = regularizers.get(activity_regularizer)
self.kernel_constraint = constraints.get(kernel_constraint)
self.bias_constraint = constraints.get(bias_constraint)
self.input_spec = InputSpec(min_ndim=2)
self.supports_masking = True
def build(self, input_shape):
assert len(input_shape) >= 2
input_dim = input_shape[-1]
if self.tied_to is not None:
self.kernel = K.transpose(self.tied_to.kernel)
self._non_trainable_weights.append(self.kernel)
else:
self.kernel = self.add_weight(shape=(input_dim, self.units),
initializer=self.kernel_initializer,
name='kernel',
regularizer=self.kernel_regularizer,
constraint=self.kernel_constraint)
if self.use_bias:
self.bias = self.add_weight(shape=(self.units,),
initializer=self.bias_initializer,
name='bias',
regularizer=self.bias_regularizer,
constraint=self.bias_constraint)
else:
self.bias = None
self.built = True
def compute_output_shape(self, input_shape):
assert input_shape and len(input_shape) >= 2
assert input_shape[-1] == self.units
output_shape = list(input_shape)
output_shape[-1] = self.units
return tuple(output_shape)
def call(self, inputs):
output = K.dot(inputs, self.kernel)
if self.use_bias:
output = K.bias_add(output, self.bias, data_format='channels_last')
if self.activation is not None:
output = self.activation(output)
return output

# input_ = Input(shape=(16,), dtype=np.float32)
# encoder
#
encoded1 = Dense(4, activation="sigmoid", input_shape=(4,), use_bias=True)
decoded1 = DenseTied(4, activation="sigmoid", tied_to=encoded1, use_bias=False)
# autoencoder
#
autoencoder = Sequential()
# autoencoder.add(input_)
autoencoder.add(encoded1)
autoencoder.add(decoded1)
autoencoder.compile(optimizer="adam", loss="binary_crossentropy")
print(autoencoder.summary())
autoencoder.fit(x=np.random.rand(100, 4), y=np.random.randint(0, 1, size=(100, 4)))
print(autoencoder.layers[0].get_weights()[0])
print(autoencoder.layers[1].get_weights()[0])

最新更新