在此记录一下参考insightface
用tensorflow
实现人脸识别的过程。
难点1:
tf.keras不支持动态切换输出
训练的时候需要使用softmax
进行训练,但是验证的时候需要输出embedding
层的结果进行距离比较判断是否为同一个人。使用tf.keras
的方式无法做到方便的动态切换,因此我写了上面一篇博客“tf2.0
自定义Model高级用法”。但是发现这样还是没法让训练和测试时输出维度不同,后来在群里问了苏神,可以直接分成3个模型infer_model, val_model, train_model
,并删除train_model.fit()
时期的验证,自己重新定义一个callback
对val_model
进行验证,最后导出时使用infer_model
。
class FacerecValidation(k.callbacks.Callback): def __init__(self, validation_model: k.Model, validation_data: tf.data.Dataset, validation_steps: int, distance_fn: str, threshold: float): self.val_model = validation_model self.val_iter: Iterable[Tuple[Tuple[tf.Tensor, tf.Tensor], tf.Tensor]] = iter(validation_data) self.val_step = validation_steps self.distance_fn: l2distance = distance_register[distance_fn] self.threshold = threshold
def on_epoch_end(self, epoch: int, logs=None): logs = logs or {} acc: List[tf.Tensor] = [] for i in range(validation_steps): x, actual_issame = next(self.val_iter) y_pred: Tuple[tf.Tensor] = self.val_model.predict(x) dist = self.distance_fn(*y_pred) pred_issame = tf.less(dist, self.threshold)
tp = tf.reduce_sum(tf.logical_and(pred_issame, actual_issame)) fp = tf.reduce_sum(tf.logical_and(pred_issame, tf.logical_not(actual_issame))) tn = tf.reduce_sum(tf.logical_and(tf.logical_not(pred_issame), tf.logical_not(actual_issame))) fn = tf.reduce_sum(tf.logical_and(tf.logical_not(pred_issame), actual_issame))
tpr = tf.math.divide_no_nan(tf.cast(tp, tf.float32), tf.cast(tp + fn, tf.float32)) fpr = tf.math.divide_no_nan(tf.cast(fp, tf.float32), tf.cast(fp + tn, tf.float32)) acc.append[tf.cast(tp + tn, tf.float32) / tf.cast(dist.size, tf.float32)] acc = tf.reduce_mean(acc) logs['val_acc'] = acc.numpy() return super().on_epoch_end(epoch, logs=logs)
|
难点2:
tf.data难以输出triplet对数据
本来想用传统的索引+原始图片的方式输入数据,但是索引矩阵长就58
万了,我的电脑构建好数据管道之后直接爆炸。。。然后我就思考怎么用tfrecord
的方式构建triplet对
,对于训练softmax
来说比较简单,既可以把所有图片都做成一个tfrecord
,还可以把每个id
对应的图片做成一个tfrecord
,里面只要包含图像和标签数据即可。
但是所有图片做成一个tfrecord
肯定是不行的,因为咱们没办法在tfrecord
里面根据索引进行查找图像。这里我真的很想换到mxnet
,他的iamgerecorditer
是高度压缩且带索引的格式,tf
为何就是不能把tfrecord
里面的索引暴露出来,哪怕一个索引对应一块数据也可以啊。。但现在只能退而求其次,把每个id
对应一个tfrecord
,然后使用tf.data
里面的函数进行构建。
一番尝试之后,构建代码如下,首先h.train_list
包含了所有tfrecord
的路径,然后利用interleave
将所有的路径转换为dataset
对象,这里需要设置block_length=2
,这样就保证每个采样周期为2,接着再使用batch(4)
,这样我们可以从两个不同类别中分别采样两个样本,此时得到了4张图像,取前三张即可满足要求。
h = FcaeRecHelper('data/ms1m_img_ann.npy', [112, 112], 128, use_softmax=False) len(h.train_list) img_shape = list(h.in_hw) + [3]
is_augment = True is_normlize = False
def parser(stream: bytes): examples: dict = tf.io.parse_single_example( stream, {'img': tf.io.FixedLenFeature([], tf.string), 'label': tf.io.FixedLenFeature([], tf.int64)}) return tf.image.decode_jpeg(examples['img'], 3), examples['label']
def pair_parser(raw_imgs, labels): if is_augment: raw_imgs, _ = h.augment_img(raw_imgs, None) if is_normlize: imgs: tf.Tensor = h.normlize_img(raw_imgs) else: imgs = tf.cast(raw_imgs, tf.float32)
imgs.set_shape([4] + img_shape) labels.set_shape([4, ]) return (imgs[0], imgs[1], imgs[2]), (labels[:3])
batch_size = 1 ds = (tf.data.Dataset.from_tensor_slices(h.train_list) .interleave(lambda x: tf.data.TFRecordDataset(x) .shuffle(100) .repeat(), cycle_length=-1, block_length=2, num_parallel_calls=-1) .map(parser, -1) .batch(4, True) .map(pair_parser, -1) .batch(batch_size, True))
iters = iter(ds) for i in range(20): imgs, labels = next(iters) fig, axs = plt.subplots(1, 3) axs[0].imshow(imgs[0].numpy().astype('uint8')[0]) axs[1].imshow(imgs[1].numpy().astype('uint8')[0]) axs[2].imshow(imgs[2].numpy().astype('uint8')[0]) plt.show()
|