微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

带有 Keras Functional API 的多输入多输出模型

如何解决带有 Keras Functional API 的多输入多输出模型

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

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

enter image description here

并且推断是连续的:

enter image description here

由于python的多进程库,我尝试并行化这3个模型的调用,但它非常不稳定,不建议这样做。

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

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

如下图所示:

enter image description here

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

enter image description here

我在 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,name="gender_inputs")
    
emotion_inputs = layers.Input(shape=(IMG_SIZE,name="emotion_inputs")


inputs = layers.concatenate([age_inputs,gender_inputs,emotion_inputs])
inputs = layers.Conv2D(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,name="emotion_pred")(inputs)

model = keras.Model(inputs=[age_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)

解决方法

我们可以在 tf. keras 中使用其出色的 Functional API 轻松做到这一点。在这里,我们将引导您了解如何使用 Functional 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 的 Functional 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,activation="relu")(x)
x = layers.MaxPooling2D(3)(x)
x = layers.Conv2D(32,activation="relu")(x)
x = layers.Conv2D(16,activation="relu")(x)
x = layers.GlobalMaxPooling2D()(x)

out_a = keras.layers.Dense(10,activation='softmax',name='10cls')(x)
out_b = keras.layers.Dense(2,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
)

enter image description here

需要注意的一点是,在模型定义期间定义 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_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])

enter image description here

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

encoder.output

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

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

# we expand for a batch dimension: (1,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

更新

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

(xtrain,(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) (10000,28)
# (10000,10) (10000,2) (10000,)

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

input0 = keras.Input(shape=(28,name="img2")
input1 = keras.Input(shape=(28,name="img1")
concate_input = layers.Concatenate()([input0,input1])

x = layers.Conv2D(16,activation="relu")(concate_input)
...
...
...
# multi-input,multi-output
encoder = keras.Model( inputs = [input0,input1],name="encoder")

enter image description here

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

# multi-input,multi-output
encoder.fit([xtrain,xtest],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,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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。