【问题标题】:Multi-input Multi-output Model with Keras Functional API使用 Keras 功能 API 的多输入多输出模型
【发布时间】:2021-06-24 23:50:20
【问题描述】:

如图 1 所述,我有 3 个模型,每个模型都适用于特定领域。

这 3 个模型分别使用不同的数据集进行训练。

推理是顺序的:

感谢python的Multiprocess库,我尝试并行化这3个模型的调用,但它非常不稳定,不建议这样做。

这是我必须确保一次完成所有这些的想法:

由于 3 个模型共享一个共同的预训练模型,我想制作一个具有多个输入和多个输出的单一模型。

如下图所示:

在推理过程中,我将调用一个模型,该模型将同时执行所有 3 个操作。

我看到使用 KERAS 的功能 API 是可能的,但我不知道如何做到这一点。 数据集的输入具有相同的维度。这些是 (200,200,3) 的图片。

如果有人有共享通用结构的多输入多输出模型的示例,我很好。

升级

这是我的代码示例,但由于layers. concatenate (...) 行传播了EfficientNet 模型未考虑的形状,因此它返回错误。

age_inputs = layers.Input(shape=(IMG_SIZE, IMG_SIZE, 3), name="age_inputs")
    
gender_inputs = layers.Input(shape=(IMG_SIZE, IMG_SIZE, 3)
                               , name="gender_inputs")
    
emotion_inputs = layers.Input(shape=(IMG_SIZE, IMG_SIZE, 3), 
                                name="emotion_inputs")


inputs = layers.concatenate([age_inputs, gender_inputs, emotion_inputs])
inputs = layers.Conv2D(3, (3, 3), activation="relu")(inputs)    
model = EfficientNetB0(include_top=False, 
                   input_tensor=inputs, weights="imagenet")
    

model.trainable = False

inputs = layers.GlobalAveragePooling2D(name="avg_pool")(model.output)
inputs = layers.BatchNormalization()(inputs)

top_dropout_rate = 0.2
inputs = layers.Dropout(top_dropout_rate, name="top_dropout")(inputs)

age_outputs = layers.Dense(1, activation="linear", 
                          name="age_pred")(inputs)
gender_outputs = layers.Dense(GENDER_NUM_CLASSES, 
                              activation="softmax", 
                              name="gender_pred")(inputs)
emotion_outputs = layers.Dense(EMOTION_NUM_CLASSES, activation="softmax", 
                             name="emotion_pred")(inputs)

model = keras.Model(inputs=[age_inputs, gender_inputs, emotion_inputs], 
              outputs =[age_outputs, gender_outputs, emotion_outputs], 
              name="EfficientNet")

optimizer = keras.optimizers.Adam(learning_rate=1e-2)
model.compile(loss={"age_pred" : "mse", 
                   "gender_pred":"categorical_crossentropy", 
                    "emotion_pred":"categorical_crossentropy"}, 
                   optimizer=optimizer, metrics=["accuracy"])

(age_train_images, age_train_labels), (age_test_images, age_test_labels) = reg_data_loader.load_data(...)
(gender_train_images, gender_train_labels), (gender_test_images, gender_test_labels) = cat_data_loader.load_data(...)
(emotion_train_images, emotion_train_labels), (emotion_test_images, emotion_test_labels) = cat_data_loader.load_data(...)

 model.fit({'age_inputs':age_train_images, 'gender_inputs':gender_train_images, 'emotion_inputs':emotion_train_images},
         {'age_pred':age_train_labels, 'gender_pred':gender_train_labels, 'emotion_pred':emotion_train_labels},
                 validation_split=0.2, 
                       epochs=5, 
                            batch_size=16)

【问题讨论】:

    标签: python machine-learning keras deep-learning functional-api


    【解决方案1】:

    我们可以在tf. keras 中使用其出色的函数式 API 轻松做到这一点。在这里,我们将向您介绍如何使用功能 API 构建具有不同类型(classificationregression)的多输出。

    根据上一张图,您需要一个输入模型和三个不同类型的输出。为了演示,我们将使用MNIST 这是一个手写数据集。它通常是一个 10 类分类问题数据集。从中,我们将另外创建 2 类分类器(数字是even 还是odd)以及 1 回归部分(用于预测一个数字,即对于 9 的图像输入,它应该给出近似的正方形)。


    数据集

    import numpy as np 
    import tensorflow as tf
    from tensorflow import keras
    from tensorflow.keras import layers
    
    (xtrain, ytrain), (_, _) = keras.datasets.mnist.load_data()
    
    # 10 class classifier 
    y_out_a = keras.utils.to_categorical(ytrain, num_classes=10) 
    
    # 2 class classifier, even or odd 
    y_out_b = keras.utils.to_categorical((ytrain % 2 == 0).astype(int), num_classes=2) 
    
    # regression, predict square of an input digit image
    y_out_c = tf.square(tf.cast(ytrain, tf.float32))
    

    因此,我们的训练对将是 xtrain[y_out_a, y_out_b, y_out_c],与您上一张图相同。


    模型构建

    让我们使用tf. keras 的功能API 相应地构建模型。请参阅下面的模型定义。 MNIST 样本是28 x 28 灰度图像。所以我们的输入就是这样设置的。我猜你的数据集可能是 RGB,所以相应地更改输入维度。

    input = keras.Input(shape=(28, 28, 1), name="original_img")
    x = layers.Conv2D(16, 3, activation="relu")(input)
    x = layers.Conv2D(32, 3, activation="relu")(x)
    x = layers.MaxPooling2D(3)(x)
    x = layers.Conv2D(32, 3, activation="relu")(x)
    x = layers.Conv2D(16, 3, activation="relu")(x)
    x = layers.GlobalMaxPooling2D()(x)
    
    out_a = keras.layers.Dense(10, activation='softmax', name='10cls')(x)
    out_b = keras.layers.Dense(2, activation='softmax', name='2cls')(x)
    out_c = keras.layers.Dense(1, activation='linear', name='1rg')(x)
    
    encoder = keras.Model( inputs = input, outputs = [out_a, out_b, out_c], name="encoder")
    
    # Let's plot 
    keras.utils.plot_model(
        encoder
    )
    

    需要注意的是,在模型定义期间定义out_aout_bout_c 时,我们设置了它们的name 变量,这非常重要。它们的名称分别设置为'10cls''2cls''1rg'。您也可以从上图中看到这一点(最后 3 个尾巴)。


    编译并运行

    现在,我们可以看到为什么 name 变量很重要。为了运行模型,我们首先需要使用正确的loss 函数、metricsoptimizer 编译它。现在,如果您知道,对于 classificationregression 问题,optimizer 可以相同,但对于 loss 函数和 metrics 应该更改。在我们的模型中,它具有多类型输出模型(2 个分类和 1 个回归),我们需要为每种类型设置正确的 lossmetrics。请看下面是如何完成的。

    encoder.compile(
        loss = {
            "10cls": tf.keras.losses.CategoricalCrossentropy(),
            "2cls": tf.keras.losses.CategoricalCrossentropy(),
            "1rg": tf.keras.losses.MeanSquaredError()
        },
    
        metrics = {
            "10cls": 'accuracy',
            "2cls": 'accuracy',
            "1rg": 'mse'
        },
    
        optimizer = tf.keras.optimizers.Adam(learning_rate=0.001)
    )
    

    看,我们上述模型的每个最后一个输出,在这里由它们的name 变量表示。我们为它们设置了适当的编译。希望你理解这部分。现在,是时候训练模型了。

    encoder.fit(xtrain, [y_out_a, y_out_b, y_out_c], epochs=30, verbose=2)
    
    Epoch 1/30
    1875/1875 - 6s - loss: 117.7318 - 10cls_loss: 3.2642 - 4cls_loss: 0.9040 - 1rg_loss: 113.5637 - 10cls_accuracy: 0.6057 - 4cls_accuracy: 0.8671 - 1rg_mse: 113.5637
    Epoch 2/30
    1875/1875 - 5s - loss: 62.1696 - 10cls_loss: 0.5151 - 4cls_loss: 0.2437 - 1rg_loss: 61.4109 - 10cls_accuracy: 0.8845 - 4cls_accuracy: 0.9480 - 1rg_mse: 61.4109
    Epoch 3/30
    1875/1875 - 5s - loss: 50.3159 - 10cls_loss: 0.2804 - 4cls_loss: 0.1371 - 1rg_loss: 49.8985 - 10cls_accuracy: 0.9295 - 4cls_accuracy: 0.9641 - 1rg_mse: 49.8985
    
    
    Epoch 28/30
    1875/1875 - 5s - loss: 15.5841 - 10cls_loss: 0.1066 - 4cls_loss: 0.0891 - 1rg_loss: 15.3884 - 10cls_accuracy: 0.9726 - 4cls_accuracy: 0.9715 - 1rg_mse: 15.3884
    Epoch 29/30
    1875/1875 - 5s - loss: 15.2199 - 10cls_loss: 0.1058 - 4cls_loss: 0.0859 - 1rg_loss: 15.0281 - 10cls_accuracy: 0.9736 - 4cls_accuracy: 0.9727 - 1rg_mse: 15.0281
    Epoch 30/30
    1875/1875 - 5s - loss: 15.2178 - 10cls_loss: 0.1136 - 4cls_loss: 0.0854 - 1rg_loss: 15.0188 - 10cls_accuracy: 0.9722 - 4cls_accuracy: 0.9736 - 1rg_mse: 15.0188
    <tensorflow.python.keras.callbacks.History at 0x7ff42c18e110>
    

    这就是最后一层的每个输出如何通过他们关注的loss 函数进行优化。仅供参考,有一点需要提一下,.compile 您可能需要的模型有一个基本参数:loss_weights - 加权不同模型输出的损失贡献。请参阅我的其他答案here


    预测/推理

    让我们看看一些输出。我们现在希望这个模型能够预测 3 件事:(1) 是什么数字,(2) 是偶数还是奇数,以及 (3) 它的平方值。

    import matplotlib.pyplot as plt
    plt.imshow(xtrain[0])
    

    如果我们想快速检查模型的输出层

    encoder.output
    
    [<KerasTensor: shape=(None, 10) dtype=float32 (created by layer '10cls')>,
     <KerasTensor: shape=(None, 2) dtype=float32 (created by layer '4cls')>,
     <KerasTensor: shape=(None, 1) dtype=float32 (created by layer '1rg')>]
    

    将此xtrain[0](我们知道5)传递给模型以进行预测。

    # we expand for a batch dimension: (1, 28, 28, 1)
    pred10, pred2, pred1 = encoder.predict(tf.expand_dims(xtrain[0], 0))
    
    # regression: square of the input dgit image 
    pred1 
    array([[22.098022]], dtype=float32)
    
    # even or odd, surely odd 
    pred2.argmax()
    0
    
    # which number, surely 5
    pred10.argmax()
    5
    

    更新

    根据您的评论,我们也可以扩展上述模型以采用多输入。我们需要改变一些事情。为了演示,我们将使用mnist数据集的xtrainxtest样本作为多输入模型。

    (xtrain, ytrain), (xtest, _) = keras.datasets.mnist.load_data()
    
    xtrain = xtrain[:10000] # both input sample should be same number 
    ytrain = ytrain[:10000] # both input sample should be same number
    
    y_out_a = keras.utils.to_categorical(ytrain, num_classes=10)
    y_out_b = keras.utils.to_categorical((ytrain % 2 == 0).astype(int), num_classes=2)
    y_out_c = tf.square(tf.cast(ytrain, tf.float32))
    
    print(xtrain.shape, xtest.shape) 
    print(y_out_a.shape, y_out_b.shape, y_out_c.shape)
    # (10000, 28, 28) (10000, 28, 28)
    # (10000, 10) (10000, 2) (10000,)
    

    接下来,我们需要修改上述模型的某些部分,使其采用多输入。接下来,如果您现在进行绘图,您将看到新的图表。

    input0 = keras.Input(shape=(28, 28, 1), name="img2")
    input1 = keras.Input(shape=(28, 28, 1), name="img1")
    concate_input = layers.Concatenate()([input0, input1])
    
    x = layers.Conv2D(16, 3, activation="relu")(concate_input)
    ...
    ...
    ...
    # multi-input , multi-output
    encoder = keras.Model( inputs = [input0, input1], 
                           outputs = [out_a, out_b, out_c], name="encoder")
    

    现在,我们可以如下训练模型

    # multi-input, multi-output
    encoder.fit([xtrain, xtest], [y_out_a, y_out_b, y_out_c], 
                 epochs=30, batch_size = 256, verbose=2)
    
    Epoch 1/30
    40/40 - 1s - loss: 66.9731 - 10cls_loss: 0.9619 - 2cls_loss: 0.4412 - 1rg_loss: 65.5699 - 10cls_accuracy: 0.7627 - 2cls_accuracy: 0.8815 - 1rg_mse: 65.5699
    Epoch 2/30
    40/40 - 0s - loss: 60.5408 - 10cls_loss: 0.8959 - 2cls_loss: 0.3850 - 1rg_loss: 59.2598 - 10cls_accuracy: 0.7794 - 2cls_accuracy: 0.8928 - 1rg_mse: 59.2598
    Epoch 3/30
    40/40 - 0s - loss: 57.3067 - 10cls_loss: 0.8586 - 2cls_loss: 0.3669 - 1rg_loss: 56.0813 - 10cls_accuracy: 0.7856 - 2cls_accuracy: 0.8951 - 1rg_mse: 56.0813
    ...
    ...
    Epoch 28/30
    40/40 - 0s - loss: 29.1198 - 10cls_loss: 0.4775 - 2cls_loss: 0.2573 - 1rg_loss: 28.3849 - 10cls_accuracy: 0.8616 - 2cls_accuracy: 0.9131 - 1rg_mse: 28.3849
    Epoch 29/30
    40/40 - 0s - loss: 27.5318 - 10cls_loss: 0.4696 - 2cls_loss: 0.2518 - 1rg_loss: 26.8104 - 10cls_accuracy: 0.8645 - 2cls_accuracy: 0.9142 - 1rg_mse: 26.8104
    Epoch 30/30
    40/40 - 0s - loss: 27.1581 - 10cls_loss: 0.4620 - 2cls_loss: 0.2446 - 1rg_loss: 26.4515 - 10cls_accuracy: 0.8664 - 2cls_accuracy: 0.9158 - 1rg_mse: 26.4515
    

    现在,我们可以测试多输入模型并从中获得多输出。

    pred10, pred2, pred1 = encoder.predict(
        [
             tf.expand_dims(xtrain[0], 0),
             tf.expand_dims(xtrain[0], 0)
        ]
    )
    
    # regression part 
    pred1
    array([[25.13295]], dtype=float32)
    
    # even or odd 
    pred2.argmax()
    0
    
    # what digit 
    pred10.argmax()
    5
    

    【讨论】:

    • 感谢您的回复。在我的情况下,数据 (x, y) 来自不同的数据集。我宁愿在 model.fit (..) 部分有这样的东西,我认为: model.fit ({'input1': x_input1, 'input2': x_input2, 'input3': x_input3}, {'output1': y_output1, 'output2': y_output2, 'output3': y_output3}, validation_split = 0.2, epochs = 5, batch_size = 16) 我最大的问题是如何混合 3 个输入并将它们引入同一个模型。
    • 我更新了我的帖子,添加了我的代码,让您了解我想要做什么。
    • 我在 Concatenate() 之后使用了一个预训练模型 (EfficienceNet)... 我得到了这个错误:ValueError: Cannot assign to variable conv2d_196/kernel:0 due to variable shape (3, 3, 6, 32) 和值形状 (32, 3, 3, 3) 不兼容
    • 在你的情况下,在concate 两次输入之后,它的形状得到height x width x 6。然后你将它传递给采用height 输入形状的图像网络模型x width x 3. 要解决这个问题,只需在连接之后和图像网络模型之前添加这一层 Conv2D(3, (3, 3), padding='same')
    • 试试this。您共享的链接使用了layer.Input,它作为input_tensor 传递给图像网络模型。 Burt 在你的情况下它来自layer.concate
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-28
    • 2020-03-13
    • 2018-12-23
    • 1970-01-01
    • 2020-02-29
    • 2019-05-17
    • 2017-06-02
    相关资源
    最近更新 更多