'multiple image input training using dataset object

How to use a dataset object as input in the model.fit() training loop, for a model with multiple inputs? Trying to pass the dataset itself gives me the following error:

Failed to find data adapter that can handle input: (<class 'list'> containing values of types {"<class 'tensorflow.python.data.ops.dataset_ops.MapDataset'>"}), <class 'NoneType'>

My case here:

I have a multiple input model built with keras The inputs are named 'First', 'Second' and 'Third'

I have an image dataset in keras-style:

main_directory/
...class_a/
......a_image_1.jpg
......a_image_2.jpg
...class_b/
......b_image_1.jpg
......b_image_2.jpg

I create the dataset object using tf.keras.utils.image_dataset_from_directory:

train_dataset = image_dataset_from_directory(train_dir,
                                             shuffle=False,
                                             label_mode='categorical',
                                             batch_size=hyperparameters["BATCH_SIZE"],
                                             image_size=IMG_SIZE)

Now, each image is divided in 3 parts, each part serving as input to each of the inputs of the model. I take care of that using some map functions. This is not relevant tot he problem and I will not include it. I cannot use the cropping layers included in TF because of unrelated reasons.

I then try to start the training loop:

 history = model.fit([train_dataset1, 
                      train_dataset2, 
                      train_dataset3,
                      ],
                      epochs=epochs, 
                      callbacks=callbacks,
                      validation_data=validation_dataset
                      validation_steps=steps
                      )

And here is where I get the error. I have tried some other approaches, like using a dict instead of a list. The problem seems to be that when training a model with multiple inputs, the fit() loop expects data to come as a list for x-values and a list for y-values, but I haven't been able to split the dataset object into the required formats

I have read many topics on this, but all use datasets that are created using the tf.data.Dataset.from_tensor_slices() method, which is not applicable in my case

Additionally, there is no indication of how the validation dataset has to be structured (at least according to the model.fit() documentation)

I have found some guidance saying that the validation dataset must have the same number of input/outputs as the training datasets (makes sense), but again, no indication on how to build or feed the validation dataset for a multiple input model



Solution 1:[1]

As I stated in a comment above, I managed to solve this issue, but there seems to be a bug in the way the Keras fit() training loops handles the input from a zipped dataset, so if you need to do this, you'll have to wait until it's fixed or write your own training loop.

How to approach this

I ended up solving this issue in the following way:

I created 3 separate dataset objects. Then to feed it to the model.fit() training loop, I used the tf.data.Dataset.zip() (as Almog David pointed out in the comments) method to create a single dataset containing the 3 separate datasets:

train_dataset = tf.keras.preprocessing.image_dataset_from_directory(train_dir,
                                             shuffle=False,
                                             label_mode='categorical',
                                             batch_size=32,
                                             image_size=IMG_SIZE)
validation_dataset = tf.keras.preprocessing.image_dataset_from_directory(validation_dir,
                                             shuffle=False,
                                             label_mode='categorical',
                                             batch_size=32,
                                             image_size=IMG_SIZE)

def resize_data1(images, classes):
    return (tfimgcrop(images,
                        offset_height=0,
                        offset_width=0,
                        target_height=64,
                        target_width=64),
                    classes)
def resize_data2(images, classes):
    return (tfimgcrop(images,
                        offset_height=0,
                        offset_width=64,
                        target_height=64,
                        target_width=64),
                    classes)
def resize_data3(images, classes):
    return (tfimgcrop(images,
                        offset_height=0,
                    offset_width=128,
                    target_height=64,
                    target_width=64),
                classes)

train_dataset_unb = train_dataset.unbatch()
train_dataset1 = train_dataset_unb.map(resize_data1)
train_dataset2 = train_dataset_unb.map(resize_data2)
train_dataset3 = train_dataset_unb.map(resize_data3)
train_dataset_zip = tf.data.Dataset.zip((train_dataset1, train_dataset2, train_dataset3))

validation_dataset_unb = validation_dataset.unbatch()
validation_dataset1 = validation_dataset_unb.map(resize_data1)
validation_dataset2 = validation_dataset_unb.map(resize_data2)
validation_dataset3 = validation_dataset_unb.map(resize_data3)
validation_dataset_zip = tf.data.Dataset.zip((validation_dataset1, validation_dataset2, validation_dataset3))

Validating the approach by testing what the zipped datasets return in each call:

Printing elements in a for() loop using the tf.data.Dataset.as_numpy_iterator() method:

for element in train_dataset_zip.as_numpy_iterator():
print("element", element)

Outputs:

element [[[x1, y1, z1], [c1,]], [[x2, y2, z2], [c2,]], [[x3, y3, z3], [c3,]]] 
element [[[x1, y1, z1], [c1,]], [[x2, y2, z2], [c2,]], [[x3, y3, z3], [c3,]]] 
[...]
element [[[x1, y1, z1], [c1,]], [[x2, y2, z2], [c2,]], [[x3, y3, z3], [c3,]]] 

Printing elements in a for() loop using the python intrinsic enumerate() function:

for idx, (ds1, ds2, ds3) in enumerate(train_dataset_zip):
    print("ds1: ", ds1)
    print("ds2: ", ds2)
    print("ds3: ", ds3)

Outputs:

ds1:  (<tf.Tensor: shape=(64, 64, 3), dtype=float32, numpy=[(large array with raw pixel values)], , dtype=float32)>, <tf.Tensor: shape=(1,), dtype=float32, numpy=array([0.], dtype=float32)>
ds2:  (<tf.Tensor: shape=(64, 64, 3), dtype=float32, numpy=[(large array with raw pixel values)], , dtype=float32)>, <tf.Tensor: shape=(1,), dtype=float32, numpy=array([0.], dtype=float32)>
ds3:  (<tf.Tensor: shape=(64, 64, 3), dtype=float32, numpy=[(large array with raw pixel values)], , dtype=float32)>, <tf.Tensor: shape=(1,), dtype=float32, numpy=array([0.], dtype=float32)>
ds1:  (<tf.Tensor: shape=(64, 64, 3), dtype=float32, numpy=[(large array with raw pixel values)], , dtype=float32)>, <tf.Tensor: shape=(1,), dtype=float32, numpy=array([0.], dtype=float32)>
ds2:  (<tf.Tensor: shape=(64, 64, 3), dtype=float32, numpy=[(large array with raw pixel values)], , dtype=float32)>, <tf.Tensor: shape=(1,), dtype=float32, numpy=array([0.], dtype=float32)>
ds3:  (<tf.Tensor: shape=(64, 64, 3), dtype=float32, numpy=[(large array with raw pixel values)], , dtype=float32)>, <tf.Tensor: shape=(1,), dtype=float32, numpy=array([0.], dtype=float32)>
[...]
ds1:  (<tf.Tensor: shape=(64, 64, 3), dtype=float32, numpy=[(large array with raw pixel values)], , dtype=float32)>, <tf.Tensor: shape=(1,), dtype=float32, numpy=array([0.], dtype=float32)>
ds2:  (<tf.Tensor: shape=(64, 64, 3), dtype=float32, numpy=[(large array with raw pixel values)], , dtype=float32)>, <tf.Tensor: shape=(1,), dtype=float32, numpy=array([0.], dtype=float32)>
ds3:  (<tf.Tensor: shape=(64, 64, 3), dtype=float32, numpy=[(large array with raw pixel values)], , dtype=float32)>, <tf.Tensor: shape=(1,), dtype=float32, numpy=array([0.], dtype=float32)>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1