如何使用可选输入创建Keras模型

har*_*nen 8 python keras tensorflow

我正在寻找一种使用可选输入创建Keras模型的方法。在原始TensorFlow中,您可以使用以下可选输入创建占位符:

import numpy as np
import tensorflow as tf


def main():
    required_input = tf.placeholder(
        tf.float32,
        shape=(None, 2),
        name='required_input')

    default_optional_input = tf.random_uniform(
        shape=(tf.shape(required_input)[0], 3))
    optional_input = tf.placeholder_with_default(
        default_optional_input,
        shape=(None, 3),
        name='optional_input')

    output = tf.concat((required_input, optional_input), axis=-1)

    with tf.Session() as session:
        with_optional_input_output_np = session.run(output, feed_dict={
            required_input: np.random.uniform(size=(4, 2)),
            optional_input: np.random.uniform(size=(4, 3)),
        })

        print(f"with optional input: {with_optional_input_output_np}")

        without_optional_input_output_np = session.run(output, feed_dict={
            required_input: np.random.uniform(size=(4, 2)),
        })

        print(f"without optional input: {without_optional_input_output_np}")


if __name__ == '__main__':
    main()
Run Code Online (Sandbox Code Playgroud)

以类似的方式,我希望能够为Keras模型提供可选输入。似乎keras.layers.Input .__ init__中tensor参数可能正是我想要的,但至少它没有按我的预期工作(即,与上面所示的方式相同)。这是一个中断的示例:tf.placeholder_with_default

import numpy as np
import tensorflow as tf
import tensorflow_probability as tfp


def create_model(output_size):
    required_input = tf.keras.layers.Input(
        shape=(13, ), dtype='float32', name='required_input')

    batch_size = tf.shape(required_input)[0]

    def sample_optional_input(inputs, batch_size=None):
        base_distribution = tfp.distributions.MultivariateNormalDiag(
            loc=tf.zeros(output_size),
            scale_diag=tf.ones(output_size),
            name='sample_optional_input')

        return base_distribution.sample(batch_size)

    default_optional_input = tf.keras.layers.Lambda(
        sample_optional_input,
        arguments={'batch_size': batch_size}
    )(None)

    optional_input = tf.keras.layers.Input(
        shape=(output_size, ),
        dtype='float32',
        name='optional_input',
        tensor=default_optional_input)

    concat = tf.keras.layers.Concatenate(axis=-1)(
        [required_input, optional_input])
    dense = tf.keras.layers.Dense(
        output_size, activation='relu')(concat)

    model = tf.keras.Model(
        inputs=[required_input, optional_input],
        outputs=[dense])

    return model


def main():
    model = create_model(output_size=3)

    required_input_np = np.random.normal(size=(4, 13))
    outputs_np = model.predict({'required_input': required_input_np})
    print(f"outputs_np: {outputs_np}")

    required_input = tf.random_normal(shape=(4, 13))
    outputs = model({'required_input': required_input})
    print(f"outputs: {outputs}")


if __name__ == '__main__':
    main()
Run Code Online (Sandbox Code Playgroud)

第一次调用model.predict似乎提供了正确的输出,但是由于某种原因,直接调用模型失败,并出现以下错误:

ValueError:图层模型需要2个输入,但收到1个输入张量。收到的输入:[]

Can the tensor argument in Input.__init__ be used to implement optional inputs for Keras model as in my example above? If yes, what should I change in my example to make it run correctly? If not, what is the expected way of creating optional inputs in Keras?

Dan*_*ler 5

我真的认为没有解决方法是不可能的。Keras 不是为了那个。

但是,注意到您session.run为每种情况使用两个不同的命令,似乎使用两个模型应该很容易。一种模型使用可选输入,另一种不使用。您选择要使用的方法与选择session.run()要调用的方法相同。

也就是说,您可以Input(tensor=...)Lambda图层内使用或简单地创建可选输入。两件事都很好。但是不要使用Input(shape=..., tensor=...),这些是多余的参数,有时 Keras 不能很好地处理这样的冗余。

理想情况下,将所有操作都保留在Lambda层内,甚至是tf.shape操作。

那说:

required_input = tf.keras.layers.Input(
    shape=(13, ), dtype='float32', name='required_input')

#needs the input for the case you want to pass it:
optional_input_when_used = tf.keras.layers.Input(shape=(output_size,))


#operations should be inside Lambda layers
batch_size = Lambda(lambda x: tf.shape(x)[0])(required_input)

#updated for using the batch size coming from lambda
#you didn't use "inputs" anywhere in this function
def sample_optional_input(batch_size):
    base_distribution = tfp.distributions.MultivariateNormalDiag(
        loc=tf.zeros(output_size),
        scale_diag=tf.ones(output_size),
        name='sample_optional_input')

    return base_distribution.sample(batch_size)

#updated for using the batch size as input
default_optional_input = tf.keras.layers.Lambda(sample_optional_input)(batch_size)

#let's skip the concat for now - notice I'm not "using" this layer yet
dense_layer = tf.keras.layers.Dense(output_size, activation='relu')

#you could create the rest of the model here if it's big, so you don't create it twice 
#(check the final section of this answer)
Run Code Online (Sandbox Code Playgroud)

使用传递输入的模型:

concat_when_used = tf.keras.layers.Concatenate(axis=-1)(
    [required_input, optional_input_when_used]
)

dense_when_used = dense_layer(concat_when_used)  
#or final_part_of_the_model(concat_when_used)     

model_when_used = Model([required_input, optional_input_when_used], dense_when_used)
Run Code Online (Sandbox Code Playgroud)

不使用可选输入的模型:

concat_not_used = tf.keras.layers.Concatenate(axis=-1)(
    [required_input, default_optional_input]
)

dense_not_used = dense_layer(concat_not_used) 
#or final_part_of_the_model(concat_not_used)

model_not_used = Model(required_input, dense_not_used)
Run Code Online (Sandbox Code Playgroud)

可以像这样创建两个模型并选择一个使用(两个模型共享最后一层,因此它们将始终一起训练)

现在,在您选择 which 时session.run,现在您将选择要使用的模型:

model_when_used.predict([x1, x2])
model_when_used.fit([x1,x2], y)

model_not_used.predict(x)
model_not_used.fit(x, y)
Run Code Online (Sandbox Code Playgroud)

如何创建共享的最终零件?

如果你的最后一部分很大,你不会想两次调用所有东西来创建两个模型。在这种情况下,首先创建一个最终模型:

input_for_final = Input(shape_after_concat)
out = Dense(....)(input_for_final)
out = Dense(....)(out)
out = Dense(....)(out)
.......
final_part_of_the_model = Model(input_for_final, out)
Run Code Online (Sandbox Code Playgroud)

然后在之前的答案中使用最后一部分。

dense_when_used = final_part_of_the_model(concat_when_used)
dense_not_used = final_part_of_the_model(concat_not_used)  
Run Code Online (Sandbox Code Playgroud)