是否可以使用自定义生成器通过 keras tensorflow 2.0.0 训练多输入架构?

Is it possible to use a custom generator to train multi input architecture with keras tensorflow 2.0.0?

使用 TF 2.0.0,我可以用一个输入训练一个架构,我可以使用自定义生成器用一个输入训练一个架构,我可以用两个输入训练一个架构。但是我无法使用自定义生成器训练具有两个输入的架构。

为了保持简约,这里有一个简单的示例,没有生成器,也没有多个输入:

from tensorflow.keras import layers, models, Model, Input, losses
from numpy import random, array, zeros

input1 = Input(shape=2)
dense1 = layers.Dense(5)(input1)
fullModel = Model(inputs=input1, outputs=dense1)
fullModel.summary()

# Generate random examples:
nbSamples = 21
X_train = random.rand(nbSamples, 2)
Y_train = random.rand(nbSamples, 5)

batchSize = 4
fullModel.compile(loss=losses.LogCosh())
fullModel.fit(X_train, Y_train, epochs=10, batch_size=batchSize)

这是一个简单的密集层,它接受大小为 2 的输入向量。随机生成的数据集包含 21 个示例,批量大小为 4。我们没有加载所有数据并将它们提供给 model.fit(),而是还可以在输入中提供自定义生成器。这样做的主要优点(对于 RAM 消耗)是逐批加载而不是加载整个数据集。这是一个使用先前架构和自定义生成器的简单示例:

import json
# Save the last dataset in a file:
with open("./dataset1input.txt", 'w') as file:
    for i in range(nbSamples):
        example = {"x": X_train[i].tolist(), "y": Y_train[i].tolist()}
        file.write(json.dumps(example) + "\n")

def generator1input(datasetPath, batch_size, inputSize, outputSize):
    X_batch = zeros((batch_size, inputSize))
    Y_batch = zeros((batch_size, outputSize))
    i=0
    while True:
        with open(datasetPath, 'r') as file:
            for line in file:
                example = json.loads(line)
                X_batch[i] = array(example["x"])
                Y_batch[i] = array(example["y"])
                i+=1
                if i % batch_size == 0:
                    yield (X_batch, Y_batch)
                    i=0

fullModel.compile(loss=losses.LogCosh())
my_generator = generator1input("./dataset1input.txt", batchSize, 2, 5)
fullModel.fit(my_generator, epochs=10, steps_per_epoch=int(nbSamples/batchSize))

此处,生成器打开数据集文件,但每次调用时仅加载 batch_size 个示例(而非 nbSamples 示例),并在循环时滑入文件。

现在,我可以构建一个具有 2 个输入且没有生成器的简单功能架构:

input1 = Input(shape=2)
dense1 = layers.Dense(5)(input1)
subModel1 = Model(inputs=input1, outputs=dense1)
input2 = Input(shape=3)
dense2 = layers.Dense(5)(input2)
subModel2 = Model(inputs=input2, outputs=dense2)
averageLayer = layers.average([subModel1.output, subModel2.output])
fullModel = Model(inputs=[input1, input2], outputs=averageLayer)
fullModel.summary()

# Generate random examples:
nbSamples = 21
X1 = random.rand(nbSamples, 2)
X2 = random.rand(nbSamples, 3)
Y = random.rand(nbSamples, 5)

fullModel.compile(loss=losses.LogCosh())
fullModel.fit([X1, X2], Y, epochs=10, batch_size=batchSize)

到这里为止,所有模型都可以编译 运行,但是我无法使用具有最后一个架构及其 2 个输入的生成器...通过尝试以下代码(逻辑上应该可以在我的意见):

# Save data in a file:
with open("./dataset.txt", 'w') as file:
    for i in range(nbSamples):
        example = {"x1": X1[i].tolist(), "x2": X2[i].tolist(), "y": Y[i].tolist()}
        file.write(json.dumps(example) + "\n")

def generator(datasetPath, batch_size, inputSize1, inputSize2, outputSize):
    X1_batch = zeros((batch_size, inputSize1))
    X2_batch = zeros((batch_size, inputSize2))
    Y_batch = zeros((batch_size, outputSize))
    i=0
    while True:
        with open(datasetPath, 'r') as file:
            for line in file:
                example = json.loads(line)
                X1_batch[i] = array(example["x1"])
                X2_batch[i] = array(example["x2"])
                Y_batch[i] = array(example["y"])
                i+=1
                if i % batch_size == 0:
                    yield ([X1_batch, X2_batch], Y_batch)
                    i=0

fullModel.compile(loss=losses.LogCosh())
my_generator = generator("./dataset.txt", batchSize, 2, 3, 5)
fullModel.fit(my_generator, epochs=10, steps_per_epoch=(nbSamples//batchSize))

我得到以下错误:

File "C:\Anaconda\lib\site-packages\tensorflow_core\python\keras\engine\training.py", line 729, in fit
    use_multiprocessing=use_multiprocessing)
File "C:\Anaconda\lib\site-packages\tensorflow_core\python\keras\engine\training_v2.py", line 224, in fit
    distribution_strategy=strategy)
File "C:\Anaconda\lib\site-packages\tensorflow_core\python\keras\engine\training_v2.py", line 547, in _process_training_inputs
    use_multiprocessing=use_multiprocessing)
File "C:\Anaconda\lib\site-packages\tensorflow_core\python\keras\engine\training_v2.py", line 606, in _process_inputs
    use_multiprocessing=use_multiprocessing)
File "C:\Anaconda\lib\site-packages\tensorflow_core\python\keras\engine\data_adapter.py", line 566, in __init__
    reassemble, nested_dtypes, output_shapes=nested_shape)
File "C:\Anaconda\lib\site-packages\tensorflow_core\python\data\ops\dataset_ops.py", line 540, in from_generator
    output_types, tensor_shape.as_shape, output_shapes)
File "C:\Anaconda\lib\site-packages\tensorflow_core\python\data\util\nest.py", line 471, in map_structure_up_to
    results = [func(*tensors) for tensors in zip(*all_flattened_up_to)]
File "C:\Anaconda\lib\site-packages\tensorflow_core\python\data\util\nest.py", line 471, in <listcomp>
    results = [func(*tensors) for tensors in zip(*all_flattened_up_to)]
File "C:\Anaconda\lib\site-packages\tensorflow_core\python\framework\tensor_shape.py", line 1216, in as_shape
    return TensorShape(shape)
File "C:\Anaconda\lib\site-packages\tensorflow_core\python\framework\tensor_shape.py", line 776, in __init__
    self._dims = [as_dimension(d) for d in dims_iter]
File "C:\Anaconda\lib\site-packages\tensorflow_core\python\framework\tensor_shape.py", line 776, in <listcomp>
    self._dims = [as_dimension(d) for d in dims_iter]
File "C:\Anaconda\lib\site-packages\tensorflow_core\python\framework\tensor_shape.py", line 718, in as_dimension
    return Dimension(value)
File "C:\Anaconda\lib\site-packages\tensorflow_core\python\framework\tensor_shape.py", line 193, in __init__
    self._value = int(value)
TypeError: int() argument must be a string, a bytes-like object or a number, not 'tuple'

doc 中所述,model.fit()x 参数可以是 A generator or keras.utils.Sequence returning (inputs, targets)The iterator should return a tuple of length 1, 2, or 3, where the optional second and third elements will be used for y and sample_weight respectively。因此,我认为它不能接受超过一个生成器的输入。自定义生成器可能无法实现多个输入。拜托,你能解释一下吗?解决方案?

(否则,似乎可以使用较少自定义的方法来完成 tf.data.Dataset.from_generator(),但我很难理解 output_signature 参数中要指示的内容)



[编辑]感谢@Francis Tang 的回复。事实上,可以使用自定义生成器,但它让我明白我只需要更改行:

yield ([X1_batch, X2_batch], Y_batch)

收件人:

yield (X1_batch, X2_batch), Y_batch

不过,使用tf.keras.utils.Sequence确实可能更好。但我觉得它有点限制。 特别是,我在给出的示例(以及我能找到的关于 Sequence 的大多数示例)中了解到,__init__() 首先用于加载完整数据集,这有悖于发电机。 但也许这是一个关于 Sequence() 的特定示例,并且没有必要像那样使用 __init__():您可以直接读取文件并将所需的批次加载到 __getitem__() 中。 在这种情况下,似乎每次都推送浏览数据文件,否则有必要预先为每个批次创建一个文件(不是最理想的)。

from tensorflow.python.keras.utils.data_utils import Sequence

class generator(Sequence):
    def __init__(self,filename,batch_size):
        data = pickle.load(open(filename,'rb'))
        self.X1 = data['X1']
        self.X2 = data['X2']
        self.y = data['y']
        self.bs = batch_size
    
    def __len__(self):
        return (len(self.y) - 1) // self.bs + 1
    
    def __getitem__(self,idx):
        start, end = idx * self.bs, (idx+1) * self.bs
        return (self.X1[start:end], self.X2[start:end]), self.y[start:end]

您需要使用序列编写 class:https://www.tensorflow.org/api_docs/python/tf/keras/utils/Sequence