How to map string to a int (class number) as a part of tf.data.Dataset .map() for preprocessing in Tensorflow?

Viewed 512

I'm trying to create a parser function that reads an (image, label) pair from TFRecord. When the label is an int64, all works well, however when I try and save the label as a string and convert it to an int in the parser function, things break. I'm still new to the framework, so my implementation below uses the eager .numpy() which doesn't work when I apply .map to a tf.data.Dataset.

Some background: I have hierarchies in my dataset, much like in this example where I used "dog" and "labrador" to represent this. I would like to allow to train data that can classify categorical images (cat, dog) and also train models that can classify more specific types (bulldog, labrador) from the same labeled data, which usually has labels of the most specific type for that on the image.

This is what I tried to do:

#!/usr/bin/env python3
import tensorflow as tf
import functools

# example: this model classifies dogs and cats. this map translates a specific dog type to the "dog" label, 
# and the same for cats. all other labels would go to the negative class "neg".
example_label_map = {
    "bulldog": "dog",
    "labrador": "dog",
    "persian cat": "cat",
    "cow": "neg",
}

# map the N classes to an int of 0..N-1
labels_to_class_int = {
    "neg": 0,   # negative class
    "dog": 1,
    "cat": 2,
}

def parse_tfrec_function(example, labels_map):
    """
    labels is a map of labels to class numbers. e.g. "bulldog" -> 1, "labrador" -> 1, "persian cat" -> 2, ...
    """
    image_feature_description = {
        "image": tf.io.FixedLenFeature([], tf.string),
        "label": tf.io.FixedLenFeature([], tf.string)
    }

    features = tf.io.parse_single_example(example, image_feature_description)

    image = tf.io.parse_tensor(features["image"], tf.uint8)
    
    # *** the following fails. how can this be done efficiently (lazy)?
    label = features["label"].numpy().decode('utf-8')
    class_num = labels_map[label]

    return image, class_num


tfrec_file = ['/path/to/dataset.tfrec']
dataset = tf.data.TFRecordDataset(tfrec_file)

labels_map = {}
for k, v in example_label_map.items():
    labels_map[k] = labels_to_class_int[v]

print(labels_map)

parser_fn = functools.partial(parse_tfrec_function, labels_map=labels_map)
parset_dataset = dataset.map(parser_fn)

Fail reason, as it tries to run eagerly:

AttributeError: 'Tensor' object has no attribute 'numpy'

What is the right way to do it? Storing the classes numbers in the TFRecord would require to make different TFRecords with practically the same data, if I wanted to train a network that is more specific to differentiate between a specific subset of classes.

How can a label string be converted efficiently to a class number, given a mapping that is run as a preprocess step (.map for tf.data.Dataset)?

Thanks!

1 Answers

IIUC the question is really about remapping string to an int while in graph mode. I think it should be possible to perform this string->int remapping using tf.case.

An example:

import tensorflow.compat.v1 as tf                                               
tf.disable_eager_execution()                                                    
                                                                             
                                                                                
def remap_string_to_int(tensor: tf.Tensor) -> tf.Tensor:                        
    pred_fn_pairs = [                                                           
        (tf.equal(tensor, 'neg'), lambda: 0),                                   
        (tf.equal(tensor, 'dog'), lambda: 1),                                   
        (tf.equal(tensor, 'cat'), lambda: 2),                                   
    ]                                                                           
    return tf.case(pred_fn_pairs)                           
                                                                                                                       
with tf.Session() as sess:                                                      
    label_tensor = tf.placeholder(tf.string, [])                                
    label_int_tensor = remap_string_to_int(label_tensor)                        
    print("int-value is: ",                                                     
          sess.run(label_int_tensor, feed_dict={label_tensor: 'dog'}))

prints:

>>> int-value is:  1
Related