Skip to content

Commit 9852362

Browse files
committed
Updated LFW evaluation code
1 parent 113966c commit 9852362

File tree

6 files changed

+438
-93
lines changed

6 files changed

+438
-93
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Learning rate schedule
2+
# Maps an epoch number to a learning rate
3+
0: 0.05
4+
100: 0.005
5+
200: 0.0005
6+
276: -1

src/facenet.py

Lines changed: 13 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -90,62 +90,18 @@ def shuffle_examples(image_paths, labels):
9090
image_paths_shuff, labels_shuff = zip(*shuffle_list)
9191
return image_paths_shuff, labels_shuff
9292

93-
def read_images_from_disk(input_queue):
94-
"""Consumes a single filename and label as a ' '-delimited string.
95-
Args:
96-
filename_and_label_tensor: A scalar string tensor.
97-
Returns:
98-
Two tensors: the decoded image, and the string label.
99-
"""
100-
label = input_queue[1]
101-
file_contents = tf.read_file(input_queue[0])
102-
example = tf.image.decode_image(file_contents, channels=3)
103-
return example, label
104-
10593
def random_rotate_image(image):
10694
angle = np.random.uniform(low=-10.0, high=10.0)
10795
return misc.imrotate(image, angle, 'bicubic')
10896

109-
# def read_and_augment_data(image_list, label_list, image_size, batch_size, max_nrof_epochs,
110-
# random_crop, random_flip, random_rotate, nrof_preprocess_threads, shuffle=True):
111-
#
112-
# images = ops.convert_to_tensor(image_list, dtype=tf.string)
113-
# labels = ops.convert_to_tensor(label_list, dtype=tf.int32)
114-
#
115-
# # Makes an input queue
116-
# input_queue = tf.train.slice_input_producer([images, labels],
117-
# num_epochs=max_nrof_epochs, shuffle=shuffle)
118-
#
119-
# images_and_labels = []
120-
# for _ in range(nrof_preprocess_threads):
121-
# image, label = read_images_from_disk(input_queue)
122-
# if random_rotate:
123-
# image = tf.py_func(random_rotate_image, [image], tf.uint8)
124-
# if random_crop:
125-
# image = tf.random_crop(image, [image_size, image_size, 3])
126-
# else:
127-
# image = tf.image.resize_image_with_crop_or_pad(image, image_size, image_size)
128-
# if random_flip:
129-
# image = tf.image.random_flip_left_right(image)
130-
# #pylint: disable=no-member
131-
# image.set_shape((image_size, image_size, 3))
132-
# image = tf.image.per_image_standardization(image)
133-
# images_and_labels.append([image, label])
134-
#
135-
# image_batch, label_batch = tf.train.batch_join(
136-
# images_and_labels, batch_size=batch_size,
137-
# capacity=4 * nrof_preprocess_threads * batch_size,
138-
# allow_smaller_final_batch=True)
139-
#
140-
# return image_batch, label_batch
141-
14297
# 1: Random rotate 2: Random crop 4: Random flip 8: Fixed image standardization 16: Flip
14398
RANDOM_ROTATE = 1
14499
RANDOM_CROP = 2
145100
RANDOM_FLIP = 4
146101
FIXED_STANDARDIZATION = 8
147102
FLIP = 16
148-
def create_input_pipeline(images_and_labels_list, input_queue, image_size, nrof_preprocess_threads):
103+
def create_input_pipeline(input_queue, image_size, nrof_preprocess_threads, batch_size_placeholder):
104+
images_and_labels_list = []
149105
for _ in range(nrof_preprocess_threads):
150106
filenames, label, control = input_queue.dequeue()
151107
images = []
@@ -171,7 +127,14 @@ def create_input_pipeline(images_and_labels_list, input_queue, image_size, nrof_
171127
image.set_shape(image_size + (3,))
172128
images.append(image)
173129
images_and_labels_list.append([images, label])
174-
return images_and_labels_list
130+
131+
image_batch, label_batch = tf.train.batch_join(
132+
images_and_labels_list, batch_size=batch_size_placeholder,
133+
shapes=[image_size + (3,), ()], enqueue_many=True,
134+
capacity=4 * nrof_preprocess_threads * 100,
135+
allow_smaller_final_batch=True)
136+
137+
return image_batch, label_batch
175138

176139
def get_control_flag(control, field):
177140
return tf.equal(tf.mod(tf.floor_div(control, field), 2), 1)
@@ -398,7 +361,7 @@ def split_dataset(dataset, split_ratio, min_nrof_images_per_class, mode):
398361
raise ValueError('Invalid train/test split mode "%s"' % mode)
399362
return train_set, test_set
400363

401-
def load_model(model):
364+
def load_model(model, input_map=None):
402365
# Check if the model is a model directory (containing a metagraph and a checkpoint file)
403366
# or if it is a protobuf file with a frozen graph
404367
model_exp = os.path.expanduser(model)
@@ -407,15 +370,15 @@ def load_model(model):
407370
with gfile.FastGFile(model_exp,'rb') as f:
408371
graph_def = tf.GraphDef()
409372
graph_def.ParseFromString(f.read())
410-
tf.import_graph_def(graph_def, name='')
373+
tf.import_graph_def(graph_def, input_map=input_map, name='')
411374
else:
412375
print('Model directory: %s' % model_exp)
413376
meta_file, ckpt_file = get_model_filenames(model_exp)
414377

415378
print('Metagraph file: %s' % meta_file)
416379
print('Checkpoint file: %s' % ckpt_file)
417380

418-
saver = tf.train.import_meta_graph(os.path.join(model_exp, meta_file))
381+
saver = tf.train.import_meta_graph(os.path.join(model_exp, meta_file), input_map=input_map)
419382
saver.restore(tf.get_default_session(), os.path.join(model_exp, ckpt_file))
420383

421384
def get_model_filenames(model_dir):

src/train_softmax.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -119,21 +119,14 @@ def main(args):
119119
labels_placeholder = tf.placeholder(tf.int32, shape=(None,1), name='labels')
120120
control_placeholder = tf.placeholder(tf.int32, shape=(None,1), name='control')
121121

122-
images_and_labels = []
123122
nrof_preprocess_threads = 4
124-
125123
input_queue = data_flow_ops.FIFOQueue(capacity=2000000,
126124
dtypes=[tf.string, tf.int32, tf.int32],
127125
shapes=[(1,), (1,), (1,)],
128126
shared_name=None, name=None)
129127
enqueue_op = input_queue.enqueue_many([image_paths_placeholder, labels_placeholder, control_placeholder], name='enqueue_op')
130-
images_and_labels = facenet.create_input_pipeline(images_and_labels, input_queue, image_size, nrof_preprocess_threads)
128+
image_batch, label_batch = facenet.create_input_pipeline(input_queue, image_size, nrof_preprocess_threads, batch_size_placeholder)
131129

132-
image_batch, label_batch = tf.train.batch_join(
133-
images_and_labels, batch_size=batch_size_placeholder,
134-
shapes=[image_size + (3,), ()], enqueue_many=True,
135-
capacity=4 * nrof_preprocess_threads * args.batch_size,
136-
allow_smaller_final_batch=True)
137130
image_batch = tf.identity(image_batch, 'image_batch')
138131
image_batch = tf.identity(image_batch, 'input')
139132
label_batch = tf.identity(label_batch, 'label_batch')

src/validate_on_lfw.py

Lines changed: 88 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
import lfw
3737
import os
3838
import sys
39-
import math
39+
from tensorflow.python.ops import data_flow_ops
4040
from sklearn import metrics
4141
from scipy.optimize import brentq
4242
from scipy import interpolate
@@ -52,44 +52,89 @@ def main(args):
5252

5353
# Get the paths for the corresponding images
5454
paths, actual_issame = lfw.get_paths(os.path.expanduser(args.lfw_dir), pairs)
55-
56-
# Load the model
57-
facenet.load_model(args.model)
5855

59-
# Get input and output tensors
60-
images_placeholder = tf.get_default_graph().get_tensor_by_name("input:0")
56+
image_paths_placeholder = tf.placeholder(tf.string, shape=(None,1), name='image_paths')
57+
labels_placeholder = tf.placeholder(tf.int32, shape=(None,1), name='labels')
58+
batch_size_placeholder = tf.placeholder(tf.int32, name='batch_size')
59+
control_placeholder = tf.placeholder(tf.int32, shape=(None,1), name='control')
60+
phase_train_placeholder = tf.placeholder(tf.bool, name='phase_train')
61+
62+
nrof_preprocess_threads = 4
63+
image_size = (args.image_size, args.image_size)
64+
eval_input_queue = data_flow_ops.FIFOQueue(capacity=2000000,
65+
dtypes=[tf.string, tf.int32, tf.int32],
66+
shapes=[(1,), (1,), (1,)],
67+
shared_name=None, name=None)
68+
eval_enqueue_op = eval_input_queue.enqueue_many([image_paths_placeholder, labels_placeholder, control_placeholder], name='eval_enqueue_op')
69+
image_batch, label_batch = facenet.create_input_pipeline(eval_input_queue, image_size, nrof_preprocess_threads, batch_size_placeholder)
70+
71+
# Load the model
72+
input_map = {'image_batch': image_batch, 'label_batch': label_batch, 'phase_train': phase_train_placeholder}
73+
facenet.load_model(args.model, input_map=input_map)
74+
75+
# Get output tensor
6176
embeddings = tf.get_default_graph().get_tensor_by_name("embeddings:0")
62-
phase_train_placeholder = tf.get_default_graph().get_tensor_by_name("phase_train:0")
63-
64-
#image_size = images_placeholder.get_shape()[1] # For some reason this doesn't work for frozen graphs
65-
image_size = args.image_size
66-
embedding_size = embeddings.get_shape()[1]
67-
68-
# Run forward pass to calculate embeddings
69-
print('Runnning forward pass on LFW images')
70-
batch_size = args.lfw_batch_size
71-
nrof_images = len(paths)
72-
nrof_batches = int(math.ceil(1.0*nrof_images / batch_size))
73-
emb_array = np.zeros((nrof_images, embedding_size))
74-
for i in range(nrof_batches):
75-
start_index = i*batch_size
76-
end_index = min((i+1)*batch_size, nrof_images)
77-
paths_batch = paths[start_index:end_index]
78-
images = facenet.load_data(paths_batch, False, False, image_size)
79-
feed_dict = { images_placeholder:images, phase_train_placeholder:False }
80-
emb_array[start_index:end_index,:] = sess.run(embeddings, feed_dict=feed_dict)
81-
82-
tpr, fpr, accuracy, val, val_std, far = lfw.evaluate(emb_array,
83-
actual_issame, nrof_folds=args.lfw_nrof_folds)
77+
#
78+
coord = tf.train.Coordinator()
79+
tf.train.start_queue_runners(coord=coord, sess=sess)
8480

85-
print('Accuracy: %1.3f+-%1.3f' % (np.mean(accuracy), np.std(accuracy)))
86-
print('Validation rate: %2.5f+-%2.5f @ FAR=%2.5f' % (val, val_std, far))
81+
evaluate(sess, eval_enqueue_op, image_paths_placeholder, labels_placeholder, phase_train_placeholder, batch_size_placeholder, control_placeholder,
82+
embeddings, label_batch, paths, actual_issame, args.lfw_batch_size, args.lfw_nrof_folds, args.distance_metric, args.subtract_mean,
83+
args.use_flipped_images, args.use_fixed_image_standardization)
8784

88-
auc = metrics.auc(fpr, tpr)
89-
print('Area Under Curve (AUC): %1.3f' % auc)
90-
eer = brentq(lambda x: 1. - x - interpolate.interp1d(fpr, tpr)(x), 0., 1.)
91-
print('Equal Error Rate (EER): %1.3f' % eer)
92-
85+
86+
def evaluate(sess, enqueue_op, image_paths_placeholder, labels_placeholder, phase_train_placeholder, batch_size_placeholder, control_placeholder,
87+
embeddings, labels, image_paths, actual_issame, batch_size, nrof_folds, distance_metric, subtract_mean, use_flipped_images, use_fixed_image_standardization):
88+
# Run forward pass to calculate embeddings
89+
print('Runnning forward pass on LFW images')
90+
91+
# Enqueue one epoch of image paths and labels
92+
nrof_embeddings = len(actual_issame)*2 # nrof_pairs * nrof_images_per_pair
93+
nrof_flips = 2 if use_flipped_images else 1
94+
nrof_images = nrof_embeddings * nrof_flips
95+
labels_array = np.expand_dims(np.arange(0,nrof_images),1)
96+
image_paths_array = np.expand_dims(np.repeat(np.array(image_paths),nrof_flips),1)
97+
control_array = np.zeros_like(labels_array, np.int32)
98+
if use_fixed_image_standardization:
99+
control_array += np.ones_like(labels_array)*facenet.FIXED_STANDARDIZATION
100+
if use_flipped_images:
101+
# Flip every second image
102+
control_array += (labels_array % 2)*facenet.FLIP
103+
sess.run(enqueue_op, {image_paths_placeholder: image_paths_array, labels_placeholder: labels_array, control_placeholder: control_array})
104+
105+
embedding_size = int(embeddings.get_shape()[1])
106+
assert nrof_images % batch_size == 0, 'The number of LFW images must be an integer multiple of the LFW batch size'
107+
nrof_batches = nrof_images // batch_size
108+
emb_array = np.zeros((nrof_images, embedding_size))
109+
lab_array = np.zeros((nrof_images,))
110+
for i in range(nrof_batches):
111+
feed_dict = {phase_train_placeholder:False, batch_size_placeholder:batch_size}
112+
emb, lab = sess.run([embeddings, labels], feed_dict=feed_dict)
113+
lab_array[lab] = lab
114+
emb_array[lab, :] = emb
115+
if i % 10 == 9:
116+
print('.', end='')
117+
sys.stdout.flush()
118+
print('')
119+
embeddings = np.zeros((nrof_embeddings, embedding_size*nrof_flips))
120+
if use_flipped_images:
121+
# Concatenate embeddings for flipped and non flipped iversion of the images
122+
embeddings[:,:embedding_size] = emb_array[0::2,:]
123+
embeddings[:,embedding_size:] = emb_array[1::2,:]
124+
else:
125+
embeddings = emb_array
126+
127+
assert np.array_equal(lab_array, np.arange(nrof_images))==True, 'Wrong labels used for evaluation, possibly caused by training examples left in the input pipeline'
128+
tpr, fpr, accuracy, val, val_std, far = lfw.evaluate(embeddings, actual_issame, nrof_folds=nrof_folds, distance_metric=distance_metric, subtract_mean=subtract_mean)
129+
130+
print('Accuracy: %2.5f+-%2.5f' % (np.mean(accuracy), np.std(accuracy)))
131+
print('Validation rate: %2.5f+-%2.5f @ FAR=%2.5f' % (val, val_std, far))
132+
133+
auc = metrics.auc(fpr, tpr)
134+
print('Area Under Curve (AUC): %1.3f' % auc)
135+
eer = brentq(lambda x: 1. - x - interpolate.interp1d(fpr, tpr)(x), 0., 1.)
136+
print('Equal Error Rate (EER): %1.3f' % eer)
137+
93138
def parse_arguments(argv):
94139
parser = argparse.ArgumentParser()
95140

@@ -105,6 +150,14 @@ def parse_arguments(argv):
105150
help='The file containing the pairs to use for validation.', default='data/pairs.txt')
106151
parser.add_argument('--lfw_nrof_folds', type=int,
107152
help='Number of folds to use for cross validation. Mainly used for testing.', default=10)
153+
parser.add_argument('--distance_metric', type=int,
154+
help='Distance metric 0:euclidian, 1:cosine similarity.', default=0)
155+
parser.add_argument('--use_flipped_images',
156+
help='Concatenates embeddings for the image and its horizontally flipped counterpart.', action='store_true')
157+
parser.add_argument('--subtract_mean',
158+
help='Subtract feature mean before calculating distance.', action='store_true')
159+
parser.add_argument('--use_fixed_image_standardization',
160+
help='Performs fixed standardization of images.', action='store_true')
108161
return parser.parse_args(argv)
109162

110163
if __name__ == '__main__':

0 commit comments

Comments
 (0)