在深度学习中,并非所有的网络结构都是顺序神经网络,还存在非顺序神经网络,比如有多个输入或者输出的网络,比较典型的是“Wide&Deep”网络(Heng-Tze Cheng et al.,Wide & Deep Learning for Recommender Systems), 此时就需要使用函数式API来构建复杂的网络了。
如下图Wide&Deep网络,将所有或者部分输入直接连接到输出层,这种架构能使神经网络学习深度模式(使用深度路径)和简单规则(通过短路径),而常规的MLP使所有的数据流经整个层。
使用Keras,该网络可以表述为:
input_ = keras.layers.Input(shape=X_train.shape[1:])
hidden1 = keras.layers.Dense(30, activation="relu")(input_)
hidden2 = keras.layers.Dense(30, activation="relu")(hidden1)
concat = keras.layers.concatenate([input_, hidden2])
output = keras.layers.Dense(1)(concat)
model = keras.models.Model(inputs=[input_], outputs=[output])
我们来看没一行代码:
一旦构建了Keras模型,一切都和之前一样,编译模型、训练、评估和使用它进行预测。
model.compile(loss="mean_squared_error", optimizer=keras.optimizers.SGD(lr=1e-3))
history = model.fit(X_train, y_train, epochs=20,
validation_data=(X_valid, y_valid))
mse_test = model.evaluate(X_test, y_test)
y_pred = model.predict(X_new)
如果想通过宽路径输入特征的子集,而通过深路径送入特征的另一个子集,在这种情况下,需要使用多输入。比如假设我吗要通过宽路径输入5个特征(特征0到4),通过深路径送入6个特征(特征2到7)。
模型网络层定义代码如下:
input_A = keras.layers.Input(shape=[5], name="wide_input")
input_B = keras.layers.Input(shape=[6], name="deep_input")
hidden1 = keras.layers.Dense(30, activation="relu")(input_B)
hidden2 = keras.layers.Dense(30, activation="relu")(hidden1)
concat = keras.layers.concatenate([input_A, hidden2])
output = keras.layers.Dense(1, name="output")(concat)
model = keras.models.Model(inputs=[input_A, input_B], outputs=[output])
此时,我们在调用fit()方法时,必须要传入一对矩阵(X_train_A, X_train_B),而不是传入单个矩阵X_train,调用evaluate()或predict()方法时也一样。
model.compile(loss="mse", optimizer=keras.optimizers.SGD(lr=1e-3))
X_train_A, X_train_B = X_train[:, :5], X_train[:, 2:] # train_A 0到4列,train_B 2到6列
X_valid_A, X_valid_B = X_valid[:, :5], X_valid[:, 2:]
X_test_A, X_test_B = X_test[:, :5], X_test[:, 2:]
X_new_A, X_new_B = X_test_A[:3], X_test_B[:3] # 只选取3个样本查看结果
history = model.fit((X_train_A, X_train_B), y_train, epochs=20,
validation_data=((X_valid_A, X_valid_B), y_valid))
mse_test = model.evaluate((X_test_A, X_test_B), y_test)
y_pred = model.predict((X_new_A, X_new_B))
在许多场景中,可能需要多少个输出
input_A = keras.layers.Input(shape=[5], name="wide_input")
input_B = keras.layers.Input(shape=[6], name="deep_input")
hidden1 = keras.layers.Dense(30, activation="relu")(input_B)
hidden2 = keras.layers.Dense(30, activation="relu")(hidden1)
concat = keras.layers.concatenate([input_A, hidden2])
output = keras.layers.Dense(1, name="main_output")(concat)
# 添加一个辅助输出,在训练的时候,也可以计入Loss
aux_output = keras.layers.Dense(1, name="aux_output")(hidden2)
model = keras.models.Model(inputs=[input_A, input_B],
outputs=[output, aux_output])
这种情况下,每个输出都需要有自己的损失函数,因此,当我们在编译模型的时候, 应该传递一系列损失。默认情况下,Keras将计算所有的这些损失,并将他们简单累加得到用于训练的最终损失。由于我们更加关心主要输出而不是辅助输出(因为它仅仅用于正则化),因此我们需要给主要输出更大的权重。
model.compile(loss=["mse", "mse"], loss_weights=[0.9, 0.1], optimizer=keras.optimizers.SGD(lr=1e-3))
history = model.fit([X_train_A, X_train_B], [y_train, y_train], epochs=20,
validation_data=([X_valid_A, X_valid_B], [y_valid, y_valid]))
当评估模型时,Keras将返回总损失以及所有单个损失,同样当预测时,Keras 将为每个输出返回预测值。
# 评估时返回总损失,以及单个任务损失
total_loss, main_loss, aux_loss = model.evaluate(
[X_test_A, X_test_B], [y_test, y_test])
# 预测时,为每个任务输出预测值
y_pred_main, y_pred_aux = model.predict([X_new_A, X_new_B])
注:X_new_A只有3个样本,因此输入结果为一个长度为3的数组。