具有两种不同输入样本大小的 Keras 多任务学习
Posted
技术标签:
【中文标题】具有两种不同输入样本大小的 Keras 多任务学习【英文标题】:Keras Multitask learning with two different input sample size 【发布时间】:2019-12-04 13:45:09 【问题描述】:我正在使用共享层部分下的 Keras API 中的代码实现多任务回归模型。
有两个数据集,我们称它们为data_1
和data_2
,如下所示。
data_1 : shape(1434, 185, 37)
data_2 : shape(283, 185, 37)
data_1
由 1434 个样本组成,每个样本的长度为 185 个字符,37 表示唯一字符的总数为 37,或者换句话说,vocab_size
。相比之下,data_2
包含 283 个字符。
我将data_1
和data_2
转换为二维numpy 数组,如下所示,然后将其提供给Embedding 层。
data_1=np.argmax(data_1, axis=2)
data_2=np.argmax(data_2, axis=2)
这使得数据的形状如下。
print(np.shape(data_1))
(1434, 185)
print(np.shape(data_2))
(283, 185)
矩阵中的每个数字代表索引整数。
多任务模型如下。
user_input = keras.layers.Input(shape=((185, )), name='Input_1')
products_input = keras.layers.Input(shape=((185, )), name='Input_2')
shared_embed=(keras.layers.Embedding(vocab_size, 50, input_length=185))
user_vec_1 = shared_embed(user_input )
user_vec_2 = shared_embed(products_input )
input_vecs = keras.layers.concatenate([user_vec_1, user_vec_2], name='concat')
input_vecs_1=keras.layers.Flatten()(input_vecs)
input_vecs_2=keras.layers.Flatten()(input_vecs)
# Task 1 FC layers
nn = keras.layers.Dense(90, activation='relu',name='layer_1')(input_vecs_1)
result_a = keras.layers.Dense(1, activation='linear', name='output_1')(nn)
# Task 2 FC layers
nn1 = keras.layers.Dense(90, activation='relu', name='layer_2')(input_vecs_2)
result_b = keras.layers.Dense(1, activation='linear',name='output_2')(nn1)
model = Model(inputs=[user_input , products_input], outputs=[result_a, result_b])
model.compile(optimizer='rmsprop',
loss='mse',
metrics=['accuracy'])
模型可视化如下。
然后我如下拟合模型。
model.fit([data_1, data_2], [Y_1,Y_2], epochs=10)
错误:
ValueError: All input arrays (x) should have the same number of samples. Got array shapes: [(1434, 185), (283, 185)]
在 Keras 中有什么方法可以让我使用两个不同的样本量输入或一些技巧来避免此错误以实现我的多任务回归目标。
这是用于测试的最低工作代码。
data_1=np.array([[25, 5, 11, 24, 6],
[25, 5, 11, 24, 6],
[25, 0, 11, 24, 6],
[25, 11, 28, 11, 24],
[25, 11, 6, 11, 11]])
data_2=np.array([[25, 11, 31, 6, 11],
[25, 11, 28, 11, 31],
[25, 11, 11, 11, 31]])
Y_1=np.array([[2.33],
[2.59],
[2.59],
[2.54],
[4.06]])
Y_2=np.array([[2.9],
[2.54],
[4.06]])
user_input = keras.layers.Input(shape=((5, )), name='Input_1')
products_input = keras.layers.Input(shape=((5, )), name='Input_2')
shared_embed=(keras.layers.Embedding(37, 3, input_length=5))
user_vec_1 = shared_embed(user_input )
user_vec_2 = shared_embed(products_input )
input_vecs = keras.layers.concatenate([user_vec_1, user_vec_2], name='concat')
input_vecs_1=keras.layers.Flatten()(input_vecs)
input_vecs_2=keras.layers.Flatten()(input_vecs)
nn = keras.layers.Dense(90, activation='relu',name='layer_1')(input_vecs_1)
result_a = keras.layers.Dense(1, activation='linear', name='output_1')(nn)
# Task 2 FC layers
nn1 = keras.layers.Dense(90, activation='relu', name='layer_2')(input_vecs_2)
result_b = keras.layers.Dense(1, activation='linear',name='output_2')(nn1)
model = Model(inputs=[user_input , products_input], outputs=[result_a, result_b])
model.compile(optimizer='rmsprop',
loss='mse',
metrics=['accuracy'])
model.fit([data_1, data_2], [Y_1,Y_2], epochs=10)
【问题讨论】:
【参考方案1】:新答案:
在这里,我正在使用 TensorFlow 2 编写解决方案。 所以,你需要的是:
定义一个从数据中获取形状的动态输入
使用平均池化,因此您的 dens 层维度独立于输入维度。
单独计算损失
这是您修改后的示例:
## Do this
#pip install tensorflow==2.0.0
import tensorflow.keras as keras
import numpy as np
from tensorflow.keras.models import Model
data_1=np.array([[25, 5, 11, 24, 6],
[25, 5, 11, 24, 6],
[25, 0, 11, 24, 6],
[25, 11, 28, 11, 24],
[25, 11, 6, 11, 11]])
data_2=np.array([[25, 11, 31, 6, 11],
[25, 11, 28, 11, 31],
[25, 11, 11, 11, 31]])
Y_1=np.array([[2.33],
[2.59],
[2.59],
[2.54],
[4.06]])
Y_2=np.array([[2.9],
[2.54],
[4.06]])
user_input = keras.layers.Input(shape=((None,)), name='Input_1')
products_input = keras.layers.Input(shape=((None,)), name='Input_2')
shared_embed=(keras.layers.Embedding(37, 3, input_length=5))
user_vec_1 = shared_embed(user_input )
user_vec_2 = shared_embed(products_input )
x = keras.layers.GlobalAveragePooling1D()(user_vec_1)
nn = keras.layers.Dense(90, activation='relu',name='layer_1')(x)
result_a = keras.layers.Dense(1, activation='linear', name='output_1')(nn)
# Task 2 FC layers
x = keras.layers.GlobalAveragePooling1D()(user_vec_2)
nn1 = keras.layers.Dense(90, activation='relu', name='layer_2')(x)
result_b = keras.layers.Dense(1, activation='linear',name='output_2')(nn1)
model = Model(inputs=[user_input , products_input], outputs=[result_a, result_b])
loss = tf.keras.losses.MeanSquaredError()
optimizer = tf.keras.optimizers.Adam()
loss_values = []
num_iter = 300
for i in range(num_iter):
with tf.GradientTape() as tape:
# Forward pass.
logits = model([data_1, data_2])
loss_value = loss(Y_1, logits[0]) + loss(Y_2, logits[1])
loss_values.append(loss_value)
gradients = tape.gradient(loss_value, model.trainable_weights)
optimizer.apply_gradients(zip(gradients, model.trainable_weights))
import matplotlib.pyplot as plt
plt.plot(range(num_iter), loss_values)
plt.xlabel("iterations")
plt.ylabel('loss value')
老答案:
看来您的问题不是编码问题,而是机器学习问题!你必须配对你的数据集:这意味着,你必须在每一轮的两个输入层上输入你的 Keras 模型。
解决方案是以两个数据集的大小相同的方式对较小的数据集进行上采样。您执行此操作的方式取决于数据集的语义。另一种选择是对更大的数据集进行下采样,不推荐这样做。
在一个非常基本的情况下,如果我们假设样本是 i.i.d.跨数据集,您可以使用以下代码:
random_indices = np.random.choice(data_2.shape[0],
data_1.shape[0], replace=True)
upsampled_data_2 = data_2[random_indices]
因此,您获得了较小数据集的新版本upsampled_data_2
,其中包含一些重复样本,但与较大数据集的大小相同。
【讨论】:
我可以澄清单独计算损失后采取的步骤吗? (for循环)..您会继续下游使用model.predict等吗?或者模型是否必须适合但在for循环中计算了一些因素?谢谢 @user9317212,如果您想了解这里的工作原理,请阅读本教程:colab.research.google.com/drive/… 谢谢我会的!我正在寻找一个简单的序列模型的类似工作,该模型具有两组具有不同样本大小但想合并一个公共层的数据...我仍然需要能够查看模型性能,在某些时期停止等.. 能否请您告诉我如何评估模型(如何使用模型来预测看不见的数据)例如data_2_train =np.array([[22, 44, 1, 6, 5], [4, 1, 2, 11, 31], [25, 6, 11, 11, 31]]) ev = model.predic(data_2_train)
@javac 只需使用model.predict
。这是一个 Keras 模型,所以评估和往常一样。【参考方案2】:
对于这个答案,我假设您“没有任何配对用户和产品的规则”,并且您只是想随机抽样。
虽然已经有一个答案,但这个答案是有偏见的,因为它会有固定的配对,而你会想要改变配对。为此,您将需要一个生成器:
def generator(data_1, data_2, y1, y2, batchSize):
l1 = len(data_1)
l2 = len(data_2)
#extending data 2 to the same size of data 1
sampleRelation = l1 // l2 #l1 must be bigger
if l1 % l2 > 0:
sampleRelation += 1
data_2 = np.concatenate([data_2] * sampleRelation, axis=0)
y2 = np.concatenate([y2] * sampleRelation, axis=0)
data_2 = data_2[:l1]
y2 = y2[:l1]
#batches per epoch = steps_per_epoch
batches = l1 // batchSize
if l1 % batchSize > 0:
batches += 1
while True: #keras generators must be infinite
#shuffle indices in every epoch
np.random.shuffle(data_1)
np.random.shuffle(data_2)
#batch loop
for b in range(batches):
start = b*batchSize
end = start + batchSize
yield [data_1[start:end], data_2[start:end]], [y1[start:end], y2[start:end]]
使用model.fit_generator(generator(data_1, data_2, Y_1, Y_2, batchSize), steps_per_epoch = same_number_of_batches_as_inside_generator, ...)
训练您的模型
如果我的猜测是正确的,您不希望用户和产品之间有任何关系,而只想同时训练两个模型。在这种情况下,我建议采用这种架构:
【讨论】:
【参考方案3】:如果您尝试这样做,您的问题不清楚:
构建一个模型,该模型采用 user
和 product
,并预测关于 (user, product)
对的两件事。如果user
和product
没有配对,那么不清楚这意味着什么(正如@matias-valdenegro 指出的那样)。如果你配对另一种类型的随机元素(如the first answer).. 希望每个输出都能学会忽略另一个输入。这相当于:
构建两个模型,它们共享一个嵌入层(在这种情况下,concat 没有任何意义)。如果Y1
与data1
具有相同的长度并且Y2
与data2
具有相同的形状,那么这可能就是您想要的。这样,如果您有user
,您可以运行user
模型,如果您有product
,您可以运行product
模型。
我认为你真的想要#2。要训练它,您可以执行以下操作:
for user_batch, product_batch in zip(user_data.shuffle().repeat(),
product_data.shuffle().repeat()):
user_model.train_on_batch(*user_batch)
product_model.train_on_batch(*product_batch)
step = 1
if step > STEPS:
break
或者,将它们包装在一个组合模型中:
user_result = user_model(user_input)
product_result = product_model(product_input)
model = Model(inputs=[user_input , products_input],
outputs=[user_result, product_result])
model.compile(optimizer='rmsprop',
loss='mse',
metrics=['accuracy'])
model.fit([data_1, data_2], [Y_1,Y_2], epochs=10)
无论您使用哪种训练程序,您都应该对输出范围进行归一化处理,以便两个模型的损失具有可比性。第一个过程将交替的时期或步骤。第二个对两个损失的加权和进行单个梯度步骤。您可能想检查哪种损失加权最适合您。
【讨论】:
@mdsoust 关于keras.layers.concatenate
,请参考(***.com/questions/57197895/…),其中提到使用keras.layers.concatenate
进行多任务处理。
我需要分别编译这两个模型吗?
分别编译两个模型?如果你使用交替训练:是的。
keras.layers.concatenate
用于多任务处理。是的,如果所有输出头都需要依赖于所有输入,这是有道理的。但我不认为这是你的情况。对于未配对的数据没有意义。请澄清。
在我的例子中,应该在考虑所有输入的同时预测所有输出头以上是关于具有两种不同输入样本大小的 Keras 多任务学习的主要内容,如果未能解决你的问题,请参考以下文章