Keras自定义层不像普通层那样返回权重

3

我试图获取层的权重。当使用Keras层并将输入连接到它时,似乎正常工作。 但是,当将其封装在我的自定义层中时,就不再起作用了。这是一个bug还是我漏掉了什么?

编辑:考虑:

我读到可以在自定义层的build()函数中定义可训练变量。然而,由于自定义层包含Keras层Dense(以及之后可能有更多Keras层),它们应该已经定义了可训练变量和权重/偏差初始化器。 (我看不到一种方法可以通过TestLayer的init()函数来覆盖它们,并使用在TestLayer的build()函数中定义的变量)。

class TestLayer(layers.Layer):
    def __init__(self):
        super(TestLayer, self).__init__()
        self.test_nn = layers.Dense(3)

    def build(self, input_shape):
        super(TestLayer, self).build(input_shape)


    def call(self, inputs, **kwargs):
        test_out = test_nn(inputs) # which is test_in
        return test_out


test_in = layers.Input((2,))
test_nn = layers.Dense(3)
print(test_nn.get_weights()) # empty, since no connection to the layer
test_out = test_nn(test_in)
print(test_nn.get_weights()) # layer returns weights+biases

testLayer = TestLayer()
features = testLayer(test_in)
print(testLayer.get_weights()) # Problem: still empty, even though connected to input.
2个回答

3
文本翻译如下:

文档指出build()方法应该调用add_weight(),但你没有:

应该调用add_weight(),然后调用super的build()

如果你正在子类化layers.Layer,那么你不需要在类内部定义一个dense层。下面是正确的子类化方式:

import tensorflow as tf
from tensorflow.keras import layers

class TestLayer(layers.Layer):
    def __init__(self, outshape=3):
        super(TestLayer, self).__init__()
        self.outshape = outshape

    def build(self, input_shape):
        self.kernel = self.add_weight(name='kernel',
                                      shape=(int(input_shape[1]), self.outshape),
                                      trainable=True)

        super(TestLayer, self).build(input_shape)


    def call(self, inputs, **kwargs):
        return tf.matmul(inputs, self.kernel)

test_in = layers.Input((2,))

testLayer = TestLayer()
features = testLayer(test_in)
print(testLayer.get_weights())
#[array([[-0.68516827, -0.01990592,  0.88364804],
#       [-0.459718  ,  0.19161093,  0.39982545]], dtype=float32)]

这里有一些更多的子类化Layer类的例子。
然而,如果你坚持按照自己的方式实现,并且想使用get_weights(),你必须重写它(在这种情况下,你可以创建一个不需要子类化的类):
import tensorflow as tf
from tensorflow.keras import layers

class TestLayer(layers.Layer):
    def __init__(self, outshape=3):
        super(TestLayer, self).__init__()
        self.test_nn = layers.Dense(outshape)
        self.outshape = outshape

    def build(self, input_shape):
        super(TestLayer, self).build(input_shape)

    def call(self, inputs, **kwargs):
        return self.test_nn(inputs)

    def get_weights(self):
        with tf.Session() as sess:
            sess.run([x.initializer for x in self.test_nn.trainable_variables])
            return sess.run(self.test_nn.trainable_variables)

test_in = layers.Input((2,))

testLayer = TestLayer()
features = testLayer(test_in)
print(testLayer.get_weights())
#[array([[ 0.5692867 ,  0.726858  ,  0.37790012],
#       [ 0.2897135 , -0.7677493 , -0.58776844]], dtype=float32), #array([0., 0., 0.], dtype=float32)]

谢谢!我的一个问题是,如果我在Dense之后有layers.BatchNormalization,那么实现会是什么样子?我猜在call()中需要自己的实现,但这似乎并不简单。(我也想知道这是否是自定义层的预期方式。) - beginh
1
你写的方式不是使用自定义层的预期方式。预期的方式是我提供的方式,这也是官方文档所述的方式。但是,如果你没有在某个keras对象(例如Sequential())中使用它,那么就没问题了。否则,你将不得不覆盖所有必需的方法(get_weights()是其中之一)才能使其正常工作。我真的建议你去官方网站上查看教程。此外,如果你想要子类化BN,请确保你理解了实现方式。它并不像tf.matmul() + bias那样简单。 - Vlad
how would the implementation then look like if I had layers.BatchNormalization after Dense? - like this: bn = tf.keras.layers.BatchNormalization()(features) - Vlad

1

很遗憾,在Keras中不支持在其他图层内部使用图层。

我之前遇到过这个问题,并在此处提交了一个问题报告,但团队向我确认了这是故意的。

你可以在自定义图层中定义一个方法,比如:

def dense(X, f_in, f_out):
    W = self.add_weight(name='kernel',
                        shape=(f_in, f_out))
    b = self.add_weight(name='bias',
                        shape=(f_out, ))
    return K.dot(X, W) + b

或者继承Dense层并使用super().call()的输出。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接