Ragged tensors are nested variable-length lists in TensorFlow. They enable many use cases, e.g. variable-length sentences or batches of images with different height and width dimensions.

In TensorFlow Datasets the most intuitive way is be to encode tensors with variables length like this:

features = tfds.features.FeaturesDict({
    'image': tfds.features.Tensor(shape=[None, None, 3], dtype=tf.uint8, encoding='zlib')
})

Update 2021-08-25: As of v4.4.0 ragged tensors are supported without the workarounds below.

Until v4.4.0 the following exception was thrown when the dataset is loaded:

NotImplementedError: Specification error for feature image (TensorInfo(shape=[None, None, 3], dtype=tf.uint8)): Tensor with a unknown dimension not at the first position not supported: TensorInfo(shape=[None, None, 3], dtype=tf.uint8)

There are two alternatives to implement ragged tensors.

Alternative 1: Using tfds.features.Image

Using tfds.features.Image works seamlessly:

features = tfds.features.FeaturesDict({
    'image': tfds.features.Image(shape=[None, None, 3], dtype=tf.uint8)
})

A disadvantage is that by default TFDS automatically decodes all images which may not be intended if you want to filter the dataset in advance. For performance reasons you need to apply tfds.decode.SkipDecoding to the image features when the dataset is loaded.

Alternative 2: Using tfds.features.Sequence

The alternative would be to use tfds.features.Sequence as mentioned in this issue:

Sequence = tfds.features.Sequence
features = tfds.features.FeaturesDict({
    'image': Sequence(Sequence(Sequence(tf.uint8, length=3)))
})

This is barely readable and also very slow. I tried this option with an example dataset and the generation was 20 times slower than with option 1.