Get labels from dataset when using tensorflow image_dataset_from_directory

Question:

I wrote a simple CNN using tensorflow (v2.4) + keras in python (v3.8.3). I am trying to optimize the network, and I want more info on what it is failing to predict. I am trying to add a confusion matrix, and I need to feed tensorflow.math.confusion_matrix() the test labels.

My problem is that I cannot figure out how to access the labels from the dataset object created by tf.keras.preprocessing.image_dataset_from_directory()

My images are organized in directories having the label as the name. The documentation says the function returns a tf.data.Dataset object.

If label_mode is None, it yields float32 tensors of shape (batch_size, image_size[0], image_size[1], num_channels), encoding

images (see below for rules regarding num_channels).
Otherwise, it yields a tuple (images, labels), where images has shape (batch_size, image_size[0], image_size[1], num_channels), and
labels follows the format described below.

Here is the code:

import tensorflow as tf
from tensorflow.keras import layers
#import matplotlib.pyplot as plt
import numpy as np
import random

import PIL
import PIL.Image

import os
import pathlib

#load the IMAGES
dataDirectory = '/p/home/username/tensorflow/newBirds'

dataDirectory = pathlib.Path(dataDirectory)
imageCount = len(list(dataDirectory.glob('*/*.jpg')))
print('Image count: {0}n'.format(imageCount))

#test display an image
# osprey = list(dataDirectory.glob('OSPREY/*'))
# ospreyImage = PIL.Image.open(str(osprey[random.randint(1,100)]))
# ospreyImage.show()

# nFlicker = list(dataDirectory.glob('NORTHERN FLICKER/*'))
# nFlickerImage = PIL.Image.open(str(nFlicker[random.randint(1,100)]))
# nFlickerImage.show()

#set parameters
batchSize = 32
height=224
width=224

(trainData, trainLabels) = tf.keras.preprocessing.image_dataset_from_directory(
    dataDirectory,
    labels='inferred',
    label_mode='categorical',
    validation_split=0.2,
    subset='training',
    seed=324893,
    image_size=(height,width),
    batch_size=batchSize)

testData = tf.keras.preprocessing.image_dataset_from_directory(
    dataDirectory,
    labels='inferred',
    label_mode='categorical',
    validation_split=0.2,
    subset='validation',
    seed=324893,
    image_size=(height,width),
    batch_size=batchSize)

#class names and sampling a few images
classes = trainData.class_names
testClasses = testData.class_names
#plt.figure(figsize=(10,10))
# for images, labels in trainData.take(1):
#     for i in range(9):
#         ax = plt.subplot(3, 3, i+1)
#         plt.imshow(images[i].numpy().astype("uint8"))
#         plt.title(classes[labels[i]])
#         plt.axis("off")
# plt.show()

#buffer to hold the data in memory for faster performance
autotune = tf.data.experimental.AUTOTUNE
trainData = trainData.cache().shuffle(1000).prefetch(buffer_size=autotune)
testData = testData.cache().prefetch(buffer_size=autotune)

#augment the dataset with zoomed and rotated images
#use convolutional layers to maintain spatial information about the images
#use max pool layers to reduce
#flatten and then apply a dense layer to predict classes
model = tf.keras.Sequential([
    #layers.experimental.preprocessing.RandomFlip('horizontal', input_shape=(height, width, 3)),
    #layers.experimental.preprocessing.RandomRotation(0.1),
    #layers.experimental.preprocessing.RandomZoom(0.1),
    layers.experimental.preprocessing.Rescaling(1./255, input_shape=(height, width, 3)),
    layers.Conv2D(16, 3, padding='same', activation='relu'),
    layers.MaxPooling2D(),
    layers.Conv2D(32, 3, padding='same', activation='relu'),
    layers.MaxPooling2D(),
    layers.Conv2D(64, 3, padding='same', activation='relu'),
    layers.MaxPooling2D(),
    layers.Conv2D(128, 3, padding='same', activation='relu'),
    layers.MaxPooling2D(),
    layers.Conv2D(256, 3, padding='same', activation='relu'),
    layers.MaxPooling2D(),
    # layers.Conv2D(512, 3, padding='same', activation='relu'),
    # layers.MaxPooling2D(),
    #layers.Conv2D(1024, 3, padding='same', activation='relu'),
    #layers.MaxPooling2D(),
    #dropout prevents overtraining by not allowing each node to see each datapoint
    #layers.Dropout(0.5),
    layers.Flatten(),
    layers.Dense(512, activation='relu'),
    layers.Dense(len(classes))
    ])

model.compile(optimizer='adam',
              loss=tf.keras.losses.CategoricalCrossentropy(from_logits=True),
              metrics=['accuracy'])
model.summary()
    
epochs=2
history = model.fit(
    trainData,
    validation_data=testData,
    epochs=epochs
    )

#create confusion matrix
predictions = model.predict_classes(testData)
confusionMatrix = tf.math.confusion_matrix(labels=testClasses, predictions=predictions).numpy()

I have tried using (foo, foo1) = tf.keras.preprocessing.image_dataset_from_directory(dataDirectory, etc), but I get
(trainData, trainLabels) = tf.keras.preprocessing.image_dataset_from_directory(
ValueError: too many values to unpack (expected 2)

And if I try to return as one variable and then split it as so:

train = tf.keras.preprocessing.image_dataset_from_directory(
    dataDirectory,
    labels='inferred',
    label_mode='categorical',
    validation_split=0.2,
    subset='training',
    seed=324893,
    image_size=(height,width),
    batch_size=batchSize)
trainData = train[0]
trainLabels = train[1]

I get TypeError: ‘BatchDataset’ object is not subscriptable

I can access the labels via testClasses = testData.class_names, but I get:

2020-11-03 14:15:14.643300: W
tensorflow/core/framework/op_kernel.cc:1740] OP_REQUIRES failed at
cast_op.cc:121 : Unimplemented: Cast string to int64 is not supported
Traceback (most recent call last): File "birdFake.py", line 115, in

confusionMatrix = tf.math.confusion_matrix(labels=testClasses, predictions=predictions).numpy() File
"/p/home/username/miniconda3/lib/python3.8/site-packages/tensorflow/python/util/dispatch.py",
line 201, in wrapper
return target(*args, **kwargs) File "/p/home/username/miniconda3/lib/python3.8/site-packages/tensorflow/python/ops/confusion_matrix.py",
line 159, in confusion_matrix
labels = math_ops.cast(labels, dtypes.int64) File "/p/home/username/miniconda3/lib/python3.8/site-packages/tensorflow/python/util/dispatch.py",
line 201, in wrapper
return target(*args, **kwargs) File "/p/home/username/miniconda3/lib/python3.8/site-packages/tensorflow/python/ops/math_ops.py",
line 966, in cast
x = gen_math_ops.cast(x, base_type, name=name) File "/p/home/username/miniconda3/lib/python3.8/site-packages/tensorflow/python/ops/gen_math_ops.py",
line 1827, in cast
_ops.raise_from_not_ok_status(e, name) File "/p/home/username/miniconda3/lib/python3.8/site-packages/tensorflow/python/framework/ops.py",
line 6862, in raise_from_not_ok_status
six.raise_from(core._status_to_exception(e.code, message), None) File "", line 3, in raise_from
tensorflow.python.framework.errors_impl.UnimplementedError: Cast
string to int64 is not supported [Op:Cast]

I am open to any method to get those labels into the confusion matrix. Any ideas as to why what I am doing is not working would also be appreciated.

UPDATE: I tried the method proposed by Alexandre Catalano, and I get the following error

Traceback (most recent call last): File "./birdFake.py", line 118,
in
labels = np.concatenate([labels, np.argmax(y.numpy(), axis=-1)]) File "<array_function internals>", line 5, in concatenate
ValueError: all the input arrays must have same number of dimensions,
but the array at index 0 has 1 dimension(s) and the array at index 1
has 0 dimension(s)

I printed the first element of the labels array, and it is zero

Asked By: Jon R

||

Answers:

If I were you, I’ll iterate over the entire testData, I’ll save the predictions and labels along the way and I’ll build the confusion matrix at the end.

testData = tf.keras.preprocessing.image_dataset_from_directory(
    dataDirectory,
    labels='inferred',
    label_mode='categorical',
    seed=324893,
    image_size=(height,width),
    batch_size=32)


predictions = np.array([])
labels =  np.array([])
for x, y in testData:
  predictions = np.concatenate([predictions, model.predict_classes(x)])
  labels = np.concatenate([labels, np.argmax(y.numpy(), axis=-1)])

tf.math.confusion_matrix(labels=labels, predictions=predictions).numpy()

and the result is

Found 4 files belonging to 2 classes.
array([[2, 0],
       [2, 0]], dtype=int32)
Answered By: Alexandre Catalano

Modified from Alexandre Catalano’s post:

predictions = np.array([])
labels =  np.array([])
for x, y in test_ds:
  predictions = np.concatenate([predictions, **np.argmax**(model.predict(x), axis = -1)])
  labels = np.concatenate([labels, np.argmax(y.numpy(), axis=-1)])

You need to take the np.argmax for both sets

This works in 2021 now.

Answered By: Henry Yoshi
# Neah it does not, some debugging revealed:
# Credit: (with corrections and debugging)
# https://stackoverflow.com/questions/64687375/get-labels-from-dataset-when-using-tensorflow-image-dataset-from-directory
# predictions = np.array([])

test_labels =  np.array([])
# counter = 0
for x, y in test_unshuffled:
#   predictions = np.argmax(model.predict(x), axis = -1)  #np.concatenate([predictions, np.argmax(model.predict(x), axis = -1)])
#   print(f'prediction: {predictions}, size of {len(predictions)}')
#   print(f'y label:    {y.numpy()}, size of {len(y.numpy())}') #labels = np.concatenate([labels, np.argmax(y.numpy(), axis=-1)])
  test_labels = np.concatenate([test_labels, y.numpy()])
#   counter += 1
#   if counter > 1:
#     break

# with the final code:
test_predicted_labels = np.argmax(classes_predicted_unshuffled, axis=1)
test_predicted_labels.shape # sanity check

test_labels =  np.array([])
for x, y in test_unshuffled:
  test_labels = np.concatenate([test_labels, y.numpy()])
test_labels.shape # sanity check better match test_predicted_labels
Answered By: user6600615

the short solution for reading labels from image_dataset_from_directory function:

train_label = np.concatenate([y for x, y in train_data], axis=0)

test_label = np.concatenate([y for x, y in test_data], axis=0) 
Answered By: Ali karimi

The tf.data.Dataset object is batch-like object so you need to take a single and loop through it.
For the first batch, you do:

for image, label in test_ds.take(1):
    print (label)

I used test_ds from your code above because it has the data and labels all in one object.
So the take away is that tf.data.Dataset object is a batch-like object.

Answered By: Abraham Owodunni
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.