Tensorflow Tutorial 5, Using TensorFlow dataset APIs

Posted by Taolee on January 26, 2019

This artical shows how to use tf.data.DataSet apis to do preprecossing for images in DL on facenet

The oringinl facenet implementation using tensorflow.python.ops.dataflow_ops.FIFOQueue, which is depracated in new version of tensorflow. So this artical is to replace the orginal FIFOQueue based implementation to tensorflow.data.Dataset APIs which is recommended by Tensorflow.

A Useful TIPS of using TensorFlow

  1. There are two envs to control the log level. TF_CPP_MIN_LOG_LEVEL and TF_CPP_MIN_VLOG_LEVEL, and there usage are defined in //tensorflow/core/platform/default/logging.cc. By default, these two envs are considered as 0. And they have different usages.
  • The TF_CPP_MIN_LOG_LEVEL are used to control the LOG(severity) macro, where the severity can be one of the four constant int INFO=0, WARNING=1, ERROR=2, FATAL=3. Only when severity >= TF_CPP_MIN_LOG_LEVEL stands, the message of stagement LOG(severity) will be output. Summary, ** TF_CPP_MIN_LOG_LEVEL bigger, the less messages are output, 0 has most mesages, 2 has less messeges **

  • The TF_CPP_MIN_VLOG_LEVEL are used to control the VLOG(lvl) macro, where the lvl can be any integer. Only when lvl <= TF_CPP_MIN_VLOG_LEVEL, the messages of statement VLOG(lvl) will be considered as a INFO level log, and then the TF_CPP_MIN_LOG_LEVEL controls it. To summary, ** TF_CPP_MIN_VLOG_LEVEL bigger, the more messages are output, 10 has more log than 0.

  1. Examples
  • Q: How to show the log of VLOG(3) statement?
    A: TF_CPP_MIN_LOG_LEVEL=0 (or just don’t set) and TF_CPP_MIN_VLOG_LEVEL>=3 And this will also show all the logs of LOG statement, and VLOG(1), VLOG(2), VLOG(3).

  • Q: How to only show the log of WARNING ERROR and FATAL, without all INFO?
    A: TF_CPP_MIN_LOG_LEVEL=1, and TF_CPP_MIN_VLOF_LEVEL to anything (or just don’t set)

  • Q: If INFO are not printed, all VLOG won’t be printted out.

Create Dataset object from Tensor (filenames/labels)

Suppose we have a list of file names (in the format of numpy.ndarray) need to be used in traning/evulation, and the a list of labels (also in the format of numpy.array, and a list of control flags, which is a bit mask to represent serveral preprocss action to an image.

    labels_array = np.arange(0,nrof_images)
    image_paths_array = np.repeat(image_paths, nrof_flips)
     control_array = np.zeros_like(labels_array, np.int32)
     if use_fixed_image_standardization:
         control_array += np.ones_like(labels_array)*facenet.FIXED_STANDARDIZATION
     if use_flipped_images:
         # Flip every second image
         control_array += (labels_array % 2)*facenet.FLIP

Given the labels_array, image_paths_array and control_array of same length, we could have the following code to create a dataset. The main idea is using tf.data.Dataset.from_tensor_slices function to create a tf.data.Dataset object.

    image_size = (image_size, image_size)
    ##Create a dataset using tf.data APIs
    dataset = tf.data.Dataset.from_tensor_slices(
                    (image_paths_array, labels_array, control_array)) 
    dataset = dataset.map(lambda image_path, label, control :
                              facenet.preprocess_func(image_path, label, control, image_size), 
                              num_parallel_calls=4)
    dataset = dataset.batch(tf.cast(batch_size_placeholder, tf.int64))

Notes:

  • when using dataset.map, and dataset.batch option, please note that these function are pretty like other tensorflow function, which creates opeartions in graph, so you need to save the returned tensors to new dataset as the returned value. For example dataste = dataset.batch(N) of you just call dataset.batch(N), then you will lose the batched tensor, and the dataset still not batched.

  • num_parallel_calls parameter in dataset.map is to specify the parallel threads used to apply the function, normally you should set it to the number of cpu cores you have in your system.

Feed the dataset as model input

After the above snippets, a dataset is created. Following code shows how to use the dataset APIs. First of all, you should create an Iterator object of the dataset. And then use the get_next() function of the Iterator to iterate the batches of the data. After create the Itearator and feed the Tensors generated by get_next() function. The Iteartor’s initializer need to run once before the session can use the dataset batches.

    iterator =  dataset.make_initializable_iterator()
    #image_batch, and label_batch are the Tensors you could feed into the model.
    image_batch, label_batch = iterator.get_next()

    input_map = {'image_batch': image_batch, 'label_batch': label_batch, 
                  'phase_train': phase_train_placeholder,
                  'batch_size':  batch_size_placeholder}
    facenet.load_model(args.model, input_map=input_map, use_trt=args.use_trt)

    feed_dict = {phase_train_placeholder:False, batch_size_placeholder:batch_size}
    sess.run(iterator.initializer, feed_dict=feed_dict)

The input_map is a way to connect the batched images and labels to the model as the models’ input. Following the code of facenet.load_model function. Whether to use the freezed_graph or use the saved metagraph/checkpoint, there is way to specify a input_map parameter, which is to replace specified tensor as given, in the newly created graph.

def load_model(model, input_map=None, use_trt=False):
    # Check if the model is a model directory (containing a metagraph and a checkpoint file)
    #  or if it is a protobuf file with a frozen graph
    model_exp = os.path.expanduser(model)
    if (os.path.isfile(model_exp)):
        print('Model filename: %s' % model_exp)
        with gfile.FastGFile(model_exp,'rb') as f:
            graph_def = tf.GraphDef()
            graph_def.ParseFromString(f.read())
            if input_map is not None:
                nodes_names = [node.name for node in graph_def.node]
                for i in input_map:
                    assert i in nodes_names, "%s is not in graph" % i
            tf.import_graph_def(graph_def, input_map=input_map, name="")
    else:
        print('Model directory: %s' % model_exp)
        meta_file, ckpt_file = get_model_filenames(model_exp)
        
        print('Metagraph file: %s' % meta_file)
        print('Checkpoint file: %s' % ckpt_file)
      
        saver = tf.train.import_meta_graph(os.path.join(model_exp, meta_file), input_map=input_map)
        saver.restore(tf.get_default_session(), os.path.join(model_exp, ckpt_file))
 

Run the session

When the dataset is connected to the model input, you don’t need to feed the input images/labels to feed_dict para of session.run function. And when the whole dataset is iteraterated to the end, the sess.run will throw out an tf.errors.OutOfRangeError, which you could use to end the epoch. When you want another epoch (normally in traning stage), you could run the iterator.initializer once again, the match the iterator to the new begining of the dataset.

    while True:
        start = time.process_time()
        try:
            emb, lab = sess.run([embeddings, label_batch], feed_dict=feed_dict)
        except tf.errors.OutOfRangeError:
            break
        session_runtimes.append(time.process_time() - start)

Preprocess function

In the above paragraph, we haved passed an preprocess function to dataset.map function.

    facenet.preprocess_func(image_path, label, control, image_size)

The preprocess_func has following definitaion.

RANDOM_ROTATE = 1
RANDOM_CROP = 2
RANDOM_FLIP = 4
FIXED_STANDARDIZATION = 8
FLIP = 16
def preprocess_func(image_path, label, control, image_size):
    file_contents = tf.read_file(image_path)
    image = tf.image.decode_image(file_contents, 3)
    image = tf.cond(get_control_flag(control, RANDOM_ROTATE),
                    lambda:tf.py_func(random_rotate_image, [image], tf.uint8), 
                    lambda:tf.identity(image))
    image = tf.cond(get_control_flag(control, RANDOM_CROP), 
                    lambda:tf.random_crop(image, image_size + (3,)), 
                    lambda:tf.image.resize_image_with_crop_or_pad(image, image_size[0], image_size[1]))
    image = tf.cond(get_control_flag(control, RANDOM_FLIP),
                    lambda:tf.image.random_flip_left_right(image),
                    lambda:tf.identity(image))
    image = tf.cond(get_control_flag(control, FIXED_STANDARDIZATION),
                    lambda:(tf.cast(image, tf.float32) - 127.5)/128.0,
                    lambda:tf.image.per_image_standardization(image))
    image = tf.cond(get_control_flag(control, FLIP),
                    lambda:tf.image.flip_left_right(image),
                    lambda:tf.identity(image))
    return image, label

Resources and docs of dataset APIs

https://www.tensorflow.org/guide/datasets?hl=zh-cn