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
- There are two envs to control the log level.
TF_CPP_MIN_LOG_LEVEL
andTF_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 theLOG(severity)
macro, where theseverity
can be one of the four constant intINFO=0
,WARNING=1
,ERROR=2
,FATAL=3
. Only whenseverity
>=TF_CPP_MIN_LOG_LEVEL
stands, the message of stagementLOG(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 theVLOG(lvl)
macro, where thelvl
can be any integer. Only whenlvl
<=TF_CPP_MIN_VLOG_LEVEL
, the messages of statementVLOG(lvl)
will be considered as aINFO
level log, and then theTF_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.
- 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 ofLOG
statement, andVLOG(1)
,VLOG(2)
,VLOG(3)
. -
Q: How to only show the log of
WARNING
ERROR
andFATAL
, without allINFO
?
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, allVLOG
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
, anddataset.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 newdataset
as the returned value. For exampledataste = dataset.batch(N)
of you just calldataset.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