Associating class_names with Dataset

Viewed 92

When using image_dataset_from_directory I have noticed the output BatchDataset has a class_names attribute that is a list of labels. This is nice.

I have a tensorflow MapDataset that I have hand-created via Dataset.from_tensor_slices + Dataset.map. I can manually set the class_names attribute into that MapDataset, but when I call my_dataset.batch(32) the class_names attribute is not carried over to the new BatchDataset.

How can I add class_names to a hand-created Dataset? I see tf.features.ClassLabel, but I can't figure out how to connect it to a Dataset.

I am using tensorflow~=2.4 with Python 3.8.

1 Answers

Sorry that I can only test on tensorflow2.9, and I havn't used tf.keras.utils.image_dataset_from_directory since its high integration. Do you mean the lables that class_a, class_b, etc. in the image_dataset_from_directory's sample ?

If so, in tf2.9, you can use attr element_spec to check, which will be determined by from_tensor_slices and maintained for ever in next procedures.

Additionally, attr class_names can be set arbitrarily since it is not internally maintained by tf.data.dataset.

I have checked the source codes in tf2.4.0 and find it had implemented attr element_spec already. So I think you can use it.

Here is sample code:

import tensorflow as tf

dataset = {
    "class_a": [tf.constant(v) for v in range(10)],
    "class_b": [tf.constant(v) for v in range(10,20)]
}
a = tf.data.Dataset.from_tensor_slices(dataset)
print(a.element_spec)
# {'class_a': TensorSpec(shape=(), dtype=tf.int32, name=None), 'class_b': TensorSpec(shape=(), dtype=tf.int32, name=None)}

a.class_names = "alielie"
print(a.class_names,a.element_spec)
# alielie {'class_a': TensorSpec(shape=(), dtype=tf.int32, name=None), 'class_b': TensorSpec(shape=(), dtype=tf.int32, name=None)}

a = a.map(lambda x:x)
a.class_names = "alielie1"
print(a.class_names,a.element_spec)
# alielie1 {'class_a': TensorSpec(shape=(), dtype=tf.int32, name=None), 'class_b': TensorSpec(shape=(), dtype=tf.int32, name=None)}

a = a.batch(5)
a.class_names = "alielie2"
print(a.class_names,a.element_spec)
# alielie2 {'class_a': TensorSpec(shape=(), dtype=tf.int32, name=None), 'class_b': TensorSpec(shape=(), dtype=tf.int32, name=None)}

And the following is my test for image_dataset_from_directory's class_names, where class_names just works when building the dataset, and dynamic modification is not supported originally. More precisely, dynamic modification on class_names is not forbidden but does not work as we expected.

import tensorflow as tf
import pathlib
dataset_url = "https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz"
data_dir = tf.keras.utils.get_file('flower_photos', origin=dataset_url, untar=True)
data_dir = pathlib.Path(data_dir)

batch_size = 1
img_height = 180
img_width = 180

#1 the labele will be [0]
train_ds = tf.keras.utils.image_dataset_from_directory(
  data_dir,
  labels='inferred',
  class_names = ['daisy','dandelion', 'sunflowers', 'tulips','roses'],
  image_size=(img_height, img_width),
  shuffle=False,
  seed=None,
  batch_size=batch_size)
for item in train_ds.take(5):
    print(tf.reduce_mean(item[0]),item[1])

#2 the labele will be [1]
train_ds = tf.keras.utils.image_dataset_from_directory(
  data_dir,
  labels='inferred',
  class_names = ['dandelion','daisy','sunflowers', 'tulips','roses'],
  image_size=(img_height, img_width),
  shuffle=False,
  seed=None,
  batch_size=batch_size)
for item in train_ds.take(5):
    print(tf.reduce_mean(item[0]),item[1])

#3 the labele will still be [0],  dynamic modification does not work
train_ds = tf.keras.utils.image_dataset_from_directory(
  data_dir,
  labels='inferred',
  class_names = ['daisy','dandelion','sunflowers','tulips','roses'],
  image_size=(img_height, img_width),
  shuffle=False,
  seed=None,
  batch_size=batch_size)
train_ds.class_names = ['dandelion','daisy','sunflowers', 'tulips','roses']
for item in train_ds.take(5):
    print(tf.reduce_mean(item[0]),item[1])

So, if you just want to show such like class_names in Dataset.from_tensor_slices, element_spec is the supported attr, as long as giving Dataset.from_tensor_slices the data in Typing dict[str,list[tf.Tensor]]. This attr will not be cut off by calling .batch or .map.

If you want to modify class_names dinamicly, unfortunately, dynamic modification does not work in image_dataset_from_directory. Meanwhile, Dataset.from_tensor_slices does not support this feature too.

Related