face.evoLVe: High-Performance Face Recognition Library based on PaddlePaddle & PyTorch

  • Evolve to be more comprehensive, effective and efficient for face related analytics & applications! (WeChat News)
  • About the name:
  • "face" means this repo is dedicated for face related analytics & applications.
  • "evolve" means unleash your greatness to be better and better. "LV" are capitalized to acknowledge the nurturing of Learning and Vision (LV) group, Nation University of Singapore (NUS).
  • This work was done during Jian Zhao served as a short-term "Texpert" Research Scientist at Tencent FiT DeepSea AI Lab, Shenzhen, China.
AuthorJian Zhao
Homepagehttps://zhaoj9014.github.io

face.evoLVe for High-Performance Face Recognition

Introduction

![tipping_hand_person](https://github.githubassets.com/images/icons/emoji/unicode/1f481.png =20x20)

![](https://github.com/ZhaoJ9014/face.evoLVe/raw/master/disp/Fig1.png =450pxx) ![](https://github.com/ZhaoJ9014/face.evoLVe/raw/master/disp/Fig17.png =400pxx)

  • This repo provides a comprehensive face recognition library for face related analytics & applications, including face alignment (detection, landmark localization, affine transformation, etc.), data processing (e.g., augmentation, data balancing, normalization, etc.), various backbones (e.g., ResNet, IR, IR-SE, ResNeXt, SE-ResNeXt, DenseNet, LightCNN, MobileNet, ShuffleNet, DPN, etc.), various losses (e.g., Softmax, Focal, Center, SphereFace, CosFace, AmSoftmax, ArcFace, Triplet, etc.) and bags of tricks for improving performance (e.g., training refinements, model tweaks, knowledge distillation, etc.).
  • The current distributed training schema with multi-GPUs under PyTorch and other mainstream platforms parallels the backbone across multi-GPUs while relying on a single master to compute the final bottleneck (fully-connected/softmax) layer. This is not an issue for conventional face recognition with moderate number of identities. However, it struggles with large-scale face recognition, which requires recognizing millions of identities in the real world. The master can hardly hold the oversized final layer while the slaves still have redundant computation resource, leading to small-batch training or even failed training. To address this problem, this repo provides a highly-elegant, effective and efficient distributed training schema with multi-GPUs under PyTorch, supporting not only the backbone, but also the head with the fully-connected (softmax) layer, to facilitate high-performance large-scale face recognition.
  • All data before & after alignment, source codes and trained models are provided.
  • This repo can help researchers/engineers develop high-performance deep face recognition models and algorithms quickly for practical use and deployment.

Pre-Requisites

![cake](https://github.githubassets.com/images/icons/emoji/unicode/1f370.png =20x20)

  • Linux or macOS
  • Python 3.7 (for training & validation) and Python 2.7 (for visualization w/ tensorboardX)
  • PyTorch 1.0 (for traininig & validation, install w/ pip install torch torchvision)
  • MXNet 1.3.1 (optional, for data processing, install w/ pip install mxnet-cu90)
  • TensorFlow 1.12 (optional, for visualization, install w/ pip install tensorflow-gpu)
  • tensorboardX 1.6 (optional, for visualization, install w/ pip install tensorboardX)
  • OpenCV 3.4.5 (install w/ pip install opencv-python)
  • bcolz 1.2.0 (install w/ pip install bcolz)

While not required, for optimal performance it is highly recommended to run the code using a CUDA enabled GPU. We used 4-8 NVIDIA Tesla P40 in parallel.


Usage

![orange_book](https://github.githubassets.com/images/icons/emoji/unicode/1f4d9.png =20x20)

Clone the repo: git clone https://github.com/ZhaoJ9014/face.evoLVe.PyTorch.git.

mkdir data checkpoint log at appropriate directory to store your train/val/test data, checkpoints and training logs.

Prepare your train/val/test data (refer to Sec. Data Zoo for publicly available face related databases), and ensure each database folder has the following structure:

./data/db_name/
        -> id1/
            -> 1.jpg
            -> ...
        -> id2/
            -> 1.jpg
            -> ...
        -> ...
            -> ...
            -> ...

Refer to the codes of corresponding sections for specific purposes.


Face Alignment

This section is based on the work of MTCNN.

Folder: ./align

Face detection, landmark localization APIs and visualization toy example with ipython notebook:

from PIL import Image
from detector import detect_faces
from visualization_utils import show_results

img = Image.open('some_img.jpg') # modify the image path to yours
bounding_boxes, landmarks = detect_faces(img) # detect bboxes and landmarks for all faces in the image
show_results(img, bounding_boxes, landmarks) # visualize the results

Face alignment API (perform face detection, landmark localization and alignment with affine transformations on a whole database folder source_root with the directory structure as demonstrated in Sec. Usage, and store the aligned results to a new folder dest_root with the same directory structure):

python face_align.py -source_root [source_root] -dest_root [dest_root] -crop_size [crop_size]

# python face_align.py -source_root './data/test' -dest_root './data/test_Aligned' -crop_size 112

For macOS users, there is no need to worry about *.DS_Store files which may ruin your data, since they will be automatically removed when you run the scripts.

Keynotes for customed use: 1) specify the arguments of source_root, dest_root and crop_size to your own values when you run face_align.py; 2) pass your customed min_face_size, thresholds and nms_thresholds values to the detect_faces function of detector.py to match your practical requirements; 3) if you find the speed using face alignment API is a bit slow, you can call face resize API to firstly resize the image whose smaller size is larger than a threshold (specify the arguments of source_root, dest_root and min_side to your own values) before calling the face alignment API:

python face_resize.py

Data Processing

Folder: ./balance

Remove low-shot data API (remove the low-shot classes with less than min_num samples in the training set root with the directory structure as demonstrated in Sec. Usage for data balance and effective model training):

python remove_lowshot.py -root [root] -min_num [min_num]

# python remove_lowshot.py -root './data/train' -min_num 10

Keynotes for customed use: specify the arguments of root and min_num to your own values when you run remove_lowshot.py.

We prefer to include other data processing tricks, e.g., augmentation (flip horizontally, scale hue/satuation/brightness with coefficients uniformly drawn from [0.6,1.4], add PCA noise with a coefficient sampled from a normal distribution N(0,0.1), etc.), weighted random sampling, normalization, etc. to the main training script in Sec. Training and Validation to be self-contained.


Training and Validation

Folder: ./

Configuration API (configurate your overall settings for training & validation) config.py:

import torch

configurations = {
    1: dict(
        SEED = 1337, # random seed for reproduce results

        DATA_ROOT = '/media/pc/6T/jasonjzhao/data/faces_emore', # the parent root where your train/val/test data are stored
        MODEL_ROOT = '/media/pc/6T/jasonjzhao/buffer/model', # the root to buffer your checkpoints
        LOG_ROOT = '/media/pc/6T/jasonjzhao/buffer/log', # the root to log your train/val status
        BACKBONE_RESUME_ROOT = './', # the root to resume training from a saved checkpoint
        HEAD_RESUME_ROOT = './', # the root to resume training from a saved checkpoint

        BACKBONE_NAME = 'IR_SE_50', # support: ['ResNet_50', 'ResNet_101', 'ResNet_152', 'IR_50', 'IR_101', 'IR_152', 'IR_SE_50', 'IR_SE_101', 'IR_SE_152']
        HEAD_NAME = 'ArcFace', # support:  ['Softmax', 'ArcFace', 'CosFace', 'SphereFace', 'Am_softmax']
        LOSS_NAME = 'Focal', # support: ['Focal', 'Softmax']

        INPUT_SIZE = [112, 112], # support: [112, 112] and [224, 224]
        RGB_MEAN = [0.5, 0.5, 0.5], # for normalize inputs to [-1, 1]
        RGB_STD = [0.5, 0.5, 0.5],
        EMBEDDING_SIZE = 512, # feature dimension
        BATCH_SIZE = 512,
        DROP_LAST = True, # whether drop the last batch to ensure consistent batch_norm statistics
        LR = 0.1, # initial LR
        NUM_EPOCH = 125, # total epoch number (use the firt 1/25 epochs to warm up)
        WEIGHT_DECAY = 5e-4, # do not apply to batch_norm parameters
        MOMENTUM = 0.9,
        STAGES = [35, 65, 95], # epoch stages to decay learning rate

        DEVICE = torch.device("cuda:0" if torch.cuda.is_available() else "cpu"),
        MULTI_GPU = True, # flag to use multiple GPUs; if you choose to train with single GPU, you should first run "export CUDA_VISILE_DEVICES=device_id" to specify the GPU card you want to use
        GPU_ID = [0, 1, 2, 3], # specify your GPU ids
        PIN_MEMORY = True,
        NUM_WORKERS = 0,
),
}

Train & validation API (all folks about training & validation, i.e., import package, hyperparameters & data loaders, model & loss & optimizer, train & validation & save checkpoint) train.py. Since MS-Celeb-1M serves as an ImageNet in the filed of face recognition, we pre-train the face.evoLVe models on MS-Celeb-1M and perform validation on LFW, CFP_FF, CFP_FP, AgeDB, CALFW, CPLFW and Vggface2_FP. Let's dive into details together step by step.

Import necessary packages:

import torch
import torch.nn as nn
import torch.optim as optim
import torchvision.transforms as transforms
import torchvision.datasets as datasets

from config import configurations
from backbone.model_resnet import ResNet_50, ResNet_101, ResNet_152
from backbone.model_irse import IR_50, IR_101, IR_152, IR_SE_50, IR_SE_101, IR_SE_152
from head.metrics import ArcFace, CosFace, SphereFace, Am_softmax
from loss.focal import FocalLoss
from util.utils import make_weights_for_balanced_classes, get_val_data, separate_irse_bn_paras, separate_resnet_bn_paras, warm_up_lr, schedule_lr, perform_val, get_time, buffer_val, AverageMeter, accuracy

from tensorboardX import SummaryWriter
from tqdm import tqdm
import os

Initialize hyperparameters:

cfg = configurations[1]

SEED = cfg['SEED'] # random seed for reproduce results
torch.manual_seed(SEED)

DATA_ROOT = cfg['DATA_ROOT'] # the parent root where your train/val/test data are stored
MODEL_ROOT = cfg['MODEL_ROOT'] # the root to buffer your checkpoints
LOG_ROOT = cfg['LOG_ROOT'] # the root to log your train/val status
BACKBONE_RESUME_ROOT = cfg['BACKBONE_RESUME_ROOT'] # the root to resume training from a saved checkpoint
HEAD_RESUME_ROOT = cfg['HEAD_RESUME_ROOT']  # the root to resume training from a saved checkpoint

BACKBONE_NAME = cfg['BACKBONE_NAME'] # support: ['ResNet_50', 'ResNet_101', 'ResNet_152', 'IR_50', 'IR_101', 'IR_152', 'IR_SE_50', 'IR_SE_101', 'IR_SE_152']
HEAD_NAME = cfg['HEAD_NAME'] # support:  ['Softmax', 'ArcFace', 'CosFace', 'SphereFace', 'Am_softmax']
LOSS_NAME = cfg['LOSS_NAME'] # support: ['Focal', 'Softmax']

INPUT_SIZE = cfg['INPUT_SIZE']
RGB_MEAN = cfg['RGB_MEAN'] # for normalize inputs
RGB_STD = cfg['RGB_STD']
EMBEDDING_SIZE = cfg['EMBEDDING_SIZE'] # feature dimension
BATCH_SIZE = cfg['BATCH_SIZE']
DROP_LAST = cfg['DROP_LAST'] # whether drop the last batch to ensure consistent batch_norm statistics
LR = cfg['LR'] # initial LR
NUM_EPOCH = cfg['NUM_EPOCH']
WEIGHT_DECAY = cfg['WEIGHT_DECAY']
MOMENTUM = cfg['MOMENTUM']
STAGES = cfg['STAGES'] # epoch stages to decay learning rate

DEVICE = cfg['DEVICE']
MULTI_GPU = cfg['MULTI_GPU'] # flag to use multiple GPUs
GPU_ID = cfg['GPU_ID'] # specify your GPU ids
PIN_MEMORY = cfg['PIN_MEMORY']
NUM_WORKERS = cfg['NUM_WORKERS']
print("=" * 60)
print("Overall Configurations:")
print(cfg)
print("=" * 60)

writer = SummaryWriter(LOG_ROOT) # writer for buffering intermedium results

Train & validation data loaders:

train_transform = transforms.Compose([ # refer to https://pytorch.org/docs/stable/torchvision/transforms.html for more build-in online data augmentation
    transforms.Resize([int(128 * INPUT_SIZE[0] / 112), int(128 * INPUT_SIZE[0] / 112)]), # smaller side resized
    transforms.RandomCrop([INPUT_SIZE[0], INPUT_SIZE[1]]),
    transforms.RandomHorizontalFlip(),
    transforms.ToTensor(),
    transforms.Normalize(mean = RGB_MEAN,
                         std = RGB_STD),
])

dataset_train = datasets.ImageFolder(os.path.join(DATA_ROOT, 'imgs'), train_transform)

# create a weighted random sampler to process imbalanced data
weights = make_weights_for_balanced_classes(dataset_train.imgs, len(dataset_train.classes))
weights = torch.DoubleTensor(weights)
sampler = torch.utils.data.sampler.WeightedRandomSampler(weights, len(weights))

train_loader = torch.utils.data.DataLoader(
    dataset_train, batch_size = BATCH_SIZE, sampler = sampler, pin_memory = PIN_MEMORY,
    num_workers = NUM_WORKERS, drop_last = DROP_LAST
)

NUM_CLASS = len(train_loader.dataset.classes)
print("Number of Training Classes: {}".format(NUM_CLASS))

lfw, cfp_ff, cfp_fp, agedb, calfw, cplfw, vgg2_fp, lfw_issame, cfp_ff_issame, cfp_fp_issame, agedb_issame, calfw_issame, cplfw_issame, vgg2_fp_issame = get_val_data(DATA_ROOT)

Define and initialize model (backbone & head):

BACKBONE_DICT = {'ResNet_50': ResNet_50(INPUT_SIZE), 
                 'ResNet_101': ResNet_101(INPUT_SIZE), 
                 'ResNet_152': ResNet_152(INPUT_SIZE),
                 'IR_50': IR_50(INPUT_SIZE), 
                 'IR_101': IR_101(INPUT_SIZE), 
                 'IR_152': IR_152(INPUT_SIZE),
                 'IR_SE_50': IR_SE_50(INPUT_SIZE), 
                 'IR_SE_101': IR_SE_101(INPUT_SIZE), 
                 'IR_SE_152': IR_SE_152(INPUT_SIZE)}
BACKBONE = BACKBONE_DICT[BACKBONE_NAME]
print("=" * 60)
print(BACKBONE)
print("{} Backbone Generated".format(BACKBONE_NAME))
print("=" * 60)

HEAD_DICT = {'ArcFace': ArcFace(in_features = EMBEDDING_SIZE, out_features = NUM_CLASS, device_id = GPU_ID),
             'CosFace': CosFace(in_features = EMBEDDING_SIZE, out_features = NUM_CLASS, device_id = GPU_ID),
             'SphereFace': SphereFace(in_features = EMBEDDING_SIZE, out_features = NUM_CLASS, device_id = GPU_ID),
             'Am_softmax': Am_softmax(in_features = EMBEDDING_SIZE, out_features = NUM_CLASS, device_id = GPU_ID)}
HEAD = HEAD_DICT[HEAD_NAME]
print("=" * 60)
print(HEAD)
print("{} Head Generated".format(HEAD_NAME))
print("=" * 60)

Define and initialize loss function:

LOSS_DICT = {'Focal': FocalLoss(), 
             'Softmax': nn.CrossEntropyLoss()}
LOSS = LOSS_DICT[LOSS_NAME]
print("=" * 60)
print(LOSS)
print("{} Loss Generated".format(LOSS_NAME))
print("=" * 60)

Define and initialize optimizer:

if BACKBONE_NAME.find("IR") >= 0:
    backbone_paras_only_bn, backbone_paras_wo_bn = separate_irse_bn_paras(BACKBONE) # separate batch_norm parameters from others; do not do weight decay for batch_norm parameters to improve the generalizability
    _, head_paras_wo_bn = separate_irse_bn_paras(HEAD)
else:
    backbone_paras_only_bn, backbone_paras_wo_bn = separate_resnet_bn_paras(BACKBONE) # separate batch_norm parameters from others; do not do weight decay for batch_norm parameters to improve the generalizability
    _, head_paras_wo_bn = separate_resnet_bn_paras(HEAD)
OPTIMIZER = optim.SGD([{'params': backbone_paras_wo_bn + head_paras_wo_bn, 'weight_decay': WEIGHT_DECAY}, {'params': backbone_paras_only_bn}], lr = LR, momentum = MOMENTUM)
print("=" * 60)
print(OPTIMIZER)
print("Optimizer Generated")
print("=" * 60)

Whether resume from a checkpoint or not:

if BACKBONE_RESUME_ROOT and HEAD_RESUME_ROOT:
    print("=" * 60)
    if os.path.isfile(BACKBONE_RESUME_ROOT) and os.path.isfile(HEAD_RESUME_ROOT):
        print("Loading Backbone Checkpoint '{}'".format(BACKBONE_RESUME_ROOT))
        BACKBONE.load_state_dict(torch.load(BACKBONE_RESUME_ROOT))
        print("Loading Head Checkpoint '{}'".format(HEAD_RESUME_ROOT))
        HEAD.load_state_dict(torch.load(HEAD_RESUME_ROOT))
    else:
        print("No Checkpoint Found at '{}' and '{}'. Please Have a Check or Continue to Train from Scratch".format(BACKBONE_RESUME_ROOT, HEAD_RESUME_ROOT))
    print("=" * 60)

Whether use multi-GPU or not:

if MULTI_GPU:
    # multi-GPU setting
    BACKBONE = nn.DataParallel(BACKBONE, device_ids = GPU_ID)
    BACKBONE = BACKBONE.to(DEVICE)
else:
    # single-GPU setting
    BACKBONE = BACKBONE.to(DEVICE)

Minor settings prior to training:

DISP_FREQ = len(train_loader) // 100 # frequency to display training loss & acc

NUM_EPOCH_WARM_UP = NUM_EPOCH // 25  # use the first 1/25 epochs to warm up
NUM_BATCH_WARM_UP = len(train_loader) * NUM_EPOCH_WARM_UP  # use the first 1/25 epochs to warm up
batch = 0  # batch index

Training & validation & save checkpoint (use the first 1/25 epochs to warm up -- gradually increase LR to the initial value to ensure stable convergence):

for epoch in range(NUM_EPOCH): # start training process
    
    if epoch == STAGES[0]: # adjust LR for each training stage after warm up, you can also choose to adjust LR manually (with slight modification) once plaueau observed
        schedule_lr(OPTIMIZER)
    if epoch == STAGES[1]:
        schedule_lr(OPTIMIZER)
    if epoch == STAGES[2]:
        schedule_lr(OPTIMIZER)

    BACKBONE.train()  # set to training mode
    HEAD.train()

    losses = AverageMeter()
    top1 = AverageMeter()
    top5 = AverageMeter()

    for inputs, labels in tqdm(iter(train_loader)):

        if (epoch + 1 <= NUM_EPOCH_WARM_UP) and (batch + 1 <= NUM_BATCH_WARM_UP): # adjust LR for each training batch during warm up
            warm_up_lr(batch + 1, NUM_BATCH_WARM_UP, LR, OPTIMIZER)

        # compute output
        inputs = inputs.to(DEVICE)
        labels = labels.to(DEVICE).long()
        features = BACKBONE(inputs)
        outputs = HEAD(features, labels)
        loss = LOSS(outputs, labels)

        # measure accuracy and record loss
        prec1, prec5 = accuracy(outputs.data, labels, topk = (1, 5))
        losses.update(loss.data.item(), inputs.size(0))
        top1.update(prec1.data.item(), inputs.size(0))
        top5.update(prec5.data.item(), inputs.size(0))

        # compute gradient and do SGD step
        OPTIMIZER.zero_grad()
        loss.backward()
        OPTIMIZER.step()
        
        # dispaly training loss & acc every DISP_FREQ
        if ((batch + 1) % DISP_FREQ == 0) and batch != 0:
            print("=" * 60)
            print('Epoch {}/{} Batch {}/{}\t'
                  'Training Loss {loss.val:.4f} ({loss.avg:.4f})\t'
                  'Training [email protected] {top1.val:.3f} ({top1.avg:.3f})\t'
                  'Training [email protected] {top5.val:.3f} ({top5.avg:.3f})'.format(
                epoch + 1, NUM_EPOCH, batch + 1, len(train_loader) * NUM_EPOCH, loss = losses, top1 = top1, top5 = top5))
            print("=" * 60)

        batch += 1 # batch index

    # training statistics per epoch (buffer for visualization)
    epoch_loss = losses.avg
    epoch_acc = top1.avg
    writer.add_scalar("Training_Loss", epoch_loss, epoch + 1)
    writer.add_scalar("Training_Accuracy", epoch_acc, epoch + 1)
    print("=" * 60)
    print('Epoch: {}/{}\t'
          'Training Loss {loss.val:.4f} ({loss.avg:.4f})\t'
          'Training [email protected] {top1.val:.3f} ({top1.avg:.3f})\t'
          'Training [email protected] {top5.val:.3f} ({top5.avg:.3f})'.format(
        epoch + 1, NUM_EPOCH, loss = losses, top1 = top1, top5 = top5))
    print("=" * 60)

    # perform validation & save checkpoints per epoch
    # validation statistics per epoch (buffer for visualization)
    print("=" * 60)
    print("Perform Evaluation on LFW, CFP_FF, CFP_FP, AgeDB, CALFW, CPLFW and VGG2_FP, and Save Checkpoints...")
    accuracy_lfw, best_threshold_lfw, roc_curve_lfw = perform_val(MULTI_GPU, DEVICE, EMBEDDING_SIZE, BATCH_SIZE, BACKBONE, lfw, lfw_issame)
    buffer_val(writer, "LFW", accuracy_lfw, best_threshold_lfw, roc_curve_lfw, epoch + 1)
    accuracy_cfp_ff, best_threshold_cfp_ff, roc_curve_cfp_ff = perform_val(MULTI_GPU, DEVICE, EMBEDDING_SIZE, BATCH_SIZE, BACKBONE, cfp_ff, cfp_ff_issame)
    buffer_val(writer, "CFP_FF", accuracy_cfp_ff, best_threshold_cfp_ff, roc_curve_cfp_ff, epoch + 1)
    accuracy_cfp_fp, best_threshold_cfp_fp, roc_curve_cfp_fp = perform_val(MULTI_GPU, DEVICE, EMBEDDING_SIZE, BATCH_SIZE, BACKBONE, cfp_fp, cfp_fp_issame)
    buffer_val(writer, "CFP_FP", accuracy_cfp_fp, best_threshold_cfp_fp, roc_curve_cfp_fp, epoch + 1)
    accuracy_agedb, best_threshold_agedb, roc_curve_agedb = perform_val(MULTI_GPU, DEVICE, EMBEDDING_SIZE, BATCH_SIZE, BACKBONE, agedb, agedb_issame)
    buffer_val(writer, "AgeDB", accuracy_agedb, best_threshold_agedb, roc_curve_agedb, epoch + 1)
    accuracy_calfw, best_threshold_calfw, roc_curve_calfw = perform_val(MULTI_GPU, DEVICE, EMBEDDING_SIZE, BATCH_SIZE, BACKBONE, calfw, calfw_issame)
    buffer_val(writer, "CALFW", accuracy_calfw, best_threshold_calfw, roc_curve_calfw, epoch + 1)
    accuracy_cplfw, best_threshold_cplfw, roc_curve_cplfw = perform_val(MULTI_GPU, DEVICE, EMBEDDING_SIZE, BATCH_SIZE, BACKBONE, cplfw, cplfw_issame)
    buffer_val(writer, "CPLFW", accuracy_cplfw, best_threshold_cplfw, roc_curve_cplfw, epoch + 1)
    accuracy_vgg2_fp, best_threshold_vgg2_fp, roc_curve_vgg2_fp = perform_val(MULTI_GPU, DEVICE, EMBEDDING_SIZE, BATCH_SIZE, BACKBONE, vgg2_fp, vgg2_fp_issame)
    buffer_val(writer, "VGGFace2_FP", accuracy_vgg2_fp, best_threshold_vgg2_fp, roc_curve_vgg2_fp, epoch + 1)
    print("Epoch {}/{}, Evaluation: LFW Acc: {}, CFP_FF Acc: {}, CFP_FP Acc: {}, AgeDB Acc: {}, CALFW Acc: {}, CPLFW Acc: {}, VGG2_FP Acc: {}".format(epoch + 1, NUM_EPOCH, accuracy_lfw, accuracy_cfp_ff, accuracy_cfp_fp, accuracy_agedb, accuracy_calfw, accuracy_cplfw, accuracy_vgg2_fp))
    print("=" * 60)

    # save checkpoints per epoch
    if MULTI_GPU:
        torch.save(BACKBONE.module.state_dict(), os.path.join(MODEL_ROOT, "Backbone_{}_Epoch_{}_Batch_{}_Time_{}_checkpoint.pth".format(BACKBONE_NAME, epoch + 1, batch, get_time())))
        torch.save(HEAD.state_dict(), os.path.join(MODEL_ROOT, "Head_{}_Epoch_{}_Batch_{}_Time_{}_checkpoint.pth".format(HEAD_NAME, epoch + 1, batch, get_time())))
    else:
        torch.save(BACKBONE.state_dict(), os.path.join(MODEL_ROOT, "Backbone_{}_Epoch_{}_Batch_{}_Time_{}_checkpoint.pth".format(BACKBONE_NAME, epoch + 1, batch, get_time())))
        torch.save(HEAD.state_dict(), os.path.join(MODEL_ROOT, "Head_{}_Epoch_{}_Batch_{}_Time_{}_checkpoint.pth".format(HEAD_NAME, epoch + 1, batch, get_time())))

Now, you can start to play with face.evoLVe and run train.py. User friendly information will popped out on your terminal:

About overall configuration:

About number of training classes:

About backbone details:

About head details:

About loss details:

About optimizer details:

About resume training:

About training status & statistics (when batch index reachs DISP_FREQ or at the end of each epoch):

About validation statistics & save checkpoints (at the end of each epoch):

Monitor on-the-fly GPU occupancy with watch -d -n 0.01 nvidia-smi.

Please refer to Sec. Model Zoo for specific model weights and corresponding performance.

Feature extraction API (extract features from pre-trained models) ./util/extract_feature_v1.py (implemented with PyTorch build-in functions) and ./util/extract_feature_v2.py (implemented with OpenCV).

Visualize training & validation statistics with tensorboardX (see Sec. Model Zoo):

tensorboard --logdir /media/pc/6T/jasonjzhao/buffer/log

Data Zoo

![tiger](https://github.githubassets.com/images/icons/emoji/unicode/1f42f.png =20x20)

Database Version #Identity #Image #Frame #Video Download Link
LFW Raw 5,749 13,233 - - Google Drive, Baidu Drive
LFW Align_250x250 5,749 13,233 - - Google Drive, Baidu Drive
LFW Align_112x112 5,749 13,233 - - Google Drive, Baidu Drive
CALFW Raw 4,025 12,174 - - Google Drive, Baidu Drive
CALFW Align_112x112 4,025 12,174 - - Google Drive, Baidu Drive
CPLFW Raw 3,884 11,652 - - Google Drive, Baidu Drive
CPLFW Align_112x112 3,884 11,652 - - Google Drive, Baidu Drive
CASIA-WebFace Raw_v1 10,575 494,414 - - Baidu Drive
CASIA-WebFace Raw_v2 10,575 494,414 - - Google Drive, Baidu Drive
CASIA-WebFace Clean 10,575 455,594 - - Google Drive, Baidu Drive
MS-Celeb-1M Clean 100,000 5,084,127 - - Google Drive
MS-Celeb-1M Align_112x112 85,742 5,822,653 - - Google Drive
Vggface2 Clean 8,631 3,086,894 - - Google Drive
Vggface2_FP Align_112x112 - - - - Google Drive, Baidu Drive
AgeDB Raw 570 16,488 - - Google Drive, Baidu Drive
AgeDB Align_112x112 570 16,488 - - Google Drive, Baidu Drive
IJB-A Clean 500 5,396 20,369 2,085 Google Drive, Baidu Drive
IJB-B Raw 1,845 21,798 55,026 7,011 Google Drive
CFP Raw 500 7,000 - - Google Drive, Baidu Drive
CFP Align_112x112 500 7,000 - - Google Drive, Baidu Drive
Umdfaces Align_112x112 8,277 367,888 - - Google Drive, Baidu Drive
CelebA Raw 10,177 202,599 - - Google Drive, Baidu Drive
CACD-VS Raw 2,000 163,446 - - Google Drive, Baidu Drive
YTF Align_344x344 1,595 - 3,425 621,127 Google Drive, Baidu Drive
DeepGlint Align_112x112 180,855 6,753,545 - - Google Drive
UTKFace Align_200x200 - 23,708 - - Google Drive, Baidu Drive
BUAA-VisNir Align_287x287 150 5,952 - - Baidu Drive, PW: xmbc
CASIA NIR-VIS 2.0 Align_128x128 725 17,580 - - Baidu Drive, PW: 883b
Oulu-CASIA Raw 80 65,000 - - Baidu Drive, PW: xxp5
NUAA-ImposterDB Raw 15 12,614 - - Baidu Drive, PW: if3n
CASIA-SURF Raw 1,000 - - 21,000 Baidu Drive, PW: izb3
CASIA-FASD Raw 50 - - 600 Baidu Drive, PW: h5un
CASIA-MFSD Raw 50 - - 600
Replay-Attack Raw 50 - - 1,200
WebFace260M Raw 24M 2M - https://www.face-benchmark.org/

Remark: unzip CASIA-WebFace clean version with

unzip casia-maxpy-clean.zip    
cd casia-maxpy-clean    
zip -F CASIA-maxpy-clean.zip --out CASIA-maxpy-clean_fix.zip    
unzip CASIA-maxpy-clean_fix.zip

Remark: after unzip, get image data & pair ground truths from AgeDB, CFP, LFW and VGGFace2_FP align_112x112 versions with

import numpy as np
import bcolz
import os

def get_pair(root, name):
    carray = bcolz.carray(rootdir = os.path.join(root, name), mode='r')
    issame = np.load('{}/{}_list.npy'.format(root, name))
    return carray, issame

def get_data(data_root):
    agedb_30, agedb_30_issame = get_pair(data_root, 'agedb_30')
    cfp_fp, cfp_fp_issame = get_pair(data_root, 'cfp_fp')
    lfw, lfw_issame = get_pair(data_root, 'lfw')
    vgg2_fp, vgg2_fp_issame = get_pair(data_root, 'vgg2_fp')
    return agedb_30, cfp_fp, lfw, vgg2_fp, agedb_30_issame, cfp_fp_issame, lfw_issame, vgg2_fp_issame

agedb_30, cfp_fp, lfw, vgg2_fp, agedb_30_issame, cfp_fp_issame, lfw_issame, vgg2_fp_issame = get_data(DATA_ROOT)

Remark: We share MS-Celeb-1M_Top1M_MID2Name.tsv (Google Drive, Baidu Drive), VGGface2_ID2Name.csv (Google Drive, Baidu Drive), VGGface2_FaceScrub_Overlap.txt (Google Drive, Baidu Drive), VGGface2_LFW_Overlap.txt (Google Drive, Baidu Drive), CASIA-WebFace_ID2Name.txt (Google Drive, Baidu Drive), CASIA-WebFace_FaceScrub_Overlap.txt (Google Drive, Baidu Drive), CASIA-WebFace_LFW_Overlap.txt (Google Drive, Baidu Drive), FaceScrub_Name.txt (Google Drive, Baidu Drive), LFW_Name.txt (Google Drive, Baidu Drive), LFW_Log.txt (Google Drive, Baidu Drive) to help researchers/engineers quickly remove the overlapping parts between their own private datasets and the public datasets.

Due to release license issue, for other face related databases, please make contact with us in person for more details.


Model Zoo

![monkey](https://github.githubassets.com/images/icons/emoji/unicode/1f412.png =20x20)

Model

Backbone Head Loss Training Data Download Link
IR-50 ArcFace Focal MS-Celeb-1M_Align_112x112 Google Drive, Baidu Drive

Setting

INPUT_SIZE: [112, 112]; RGB_MEAN: [0.5, 0.5, 0.5]; RGB_STD: [0.5, 0.5, 0.5]; BATCH_SIZE: 512 (drop the last batch to ensure consistent batch_norm statistics); Initial LR: 0.1; NUM_EPOCH: 120; WEIGHT_DECAY: 5e-4 (do not apply to batch_norm parameters); MOMENTUM: 0.9; STAGES: [30, 60, 90]; Augmentation: Random Crop + Horizontal Flip; Imbalanced Data Processing: Weighted Random Sampling; Solver: SGD; GPUs: 4 NVIDIA Tesla P40 in Parallel

Training & validation statistics

Performance

LFW CFP_FF CFP_FP AgeDB CALFW CPLFW Vggface2_FP
99.78 99.69 98.14 97.53 95.87 92.45 95.22

Model

Backbone Head Loss Training Data Download Link
IR-50 ArcFace Focal Private Asia Face Data Google Drive, Baidu Drive

Setting

INPUT_SIZE: [112, 112]; RGB_MEAN: [0.5, 0.5, 0.5]; RGB_STD: [0.5, 0.5, 0.5]; BATCH_SIZE: 1024 (drop the last batch to ensure consistent batch_norm statistics); Initial LR: 0.01 (initialize weights from the above model pre-trained on MS-Celeb-1M_Align_112x112); NUM_EPOCH: 80; WEIGHT_DECAY: 5e-4 (do not apply to batch_norm parameters); MOMENTUM: 0.9; STAGES: [20, 40, 60]; Augmentation: Random Crop + Horizontal Flip; Imbalanced Data Processing: Weighted Random Sampling; Solver: SGD; GPUs: 8 NVIDIA Tesla P40 in Parallel

Performance (please perform evaluation on your own Asia face benchmark dataset)

Model

Backbone Head Loss Training Data Download Link
IR-152 ArcFace Focal MS-Celeb-1M_Align_112x112 Baidu Drive, PW: b197

Setting

INPUT_SIZE: [112, 112]; RGB_MEAN: [0.5, 0.5, 0.5]; RGB_STD: [0.5, 0.5, 0.5]; BATCH_SIZE: 256 (drop the last batch to ensure consistent batch_norm statistics); Initial LR: 0.01; NUM_EPOCH: 120; WEIGHT_DECAY: 5e-4 (do not apply to batch_norm parameters); MOMENTUM: 0.9; STAGES: [30, 60, 90]; Augmentation: Random Crop + Horizontal Flip; Imbalanced Data Processing: Weighted Random Sampling; Solver: SGD; GPUs: 4 NVIDIA Geforce RTX 2080 Ti in Parallel

Training & validation statistics

Performance

LFW CFP_FF CFP_FP AgeDB CALFW CPLFW Vggface2_FP
99.82 99.83 98.37 98.07 96.03 93.05 95.50

Achievement


Acknowledgement


Citation

Please consult and consider citing the following papers:

@article{wang2021face,
title={Face. evoLVe: A High-Performance Face Recognition Library},
author={Wang, Qingzhong and Zhang, Pengfei and Xiong, Haoyi and Zhao, Jian},
journal={arXiv preprint arXiv:2107.08621},
year={2021}
}


@article{tu2021image,
title={Image-to-Video Generation via 3D Facial Dynamics},
author={Tu, Xiaoguang and Zou, Yingtian and Zhao, Jian and Ai, Wenjie and Dong, Jian and Yao, Yuan and Wang, Zhikang and Guo, Guodong and Li, Zhifeng and Liu, Wei and others},
journal={T-CSVT},
year={2021}
}


@article{tu2021joint,
title={Joint Face Image Restoration and Frontalization for Recognition},
author={Tu, Xiaoguang and Zhao, Jian and Liu, Qiankun and Ai, Wenjie and Guo, Guodong and Li, Zhifeng and Liu, Wei and Feng, Jiashi},
journal={T-CSVT},
year={2021}
}


@article{zhao2020towards,
title={Towards age-invariant face recognition},
author={Zhao, Jian and Yan, Shuicheng and Feng, Jiashi},
journal={T-PAMI},
year={2020}
}


@article{liang2020fine,
title={Fine-grained facial expression recognition in the wild},
author={Liang, Liqian and Lang, Congyan and Li, Yidong and Feng, Songhe and Zhao, Jian},
journal={T-IFS},
pages={482--494},
year={2020}
}


@article{tu2020learning,
title={Learning generalizable and identity-discriminative representations for face anti-spoofing},
author={Tu, Xiaoguang and Ma, Zheng and Zhao, Jian and Du, Guodong and Xie, Mei and Feng, Jiashi},
journal={T-IST},
pages={1--19},
year={2020}
}


@article{tu20203d,
title={3D face reconstruction from a single image assisted by 2D face images in the wild},
author={Tu, Xiaoguang and Zhao, Jian and Xie, Mei and Jiang, Zihang and Balamurugan, Akshaya and Luo, Yao and Zhao, Yang and He, Lingxiao and Ma, Zheng and Feng, Jiashi},
journal={T-MM},
year={2020}
}


@inproceedings{wang2020learning,
title={Learning to Detect Head Movement in Unconstrained Remote Gaze Estimation in the Wild},
author={Wang, Zhecan and Zhao, Jian and Lu, Cheng and Yang, Fan and Huang, Han and Guo, Yandong and others},
booktitle={WACV},
pages={3443--3452},
year={2020}
}


@article{zhao2019recognizing,
title={Recognizing Profile Faces by Imagining Frontal View},
author={Zhao, Jian and Xing, Junliang and Xiong, Lin and Yan, Shuicheng and Feng, Jiashi},
journal={IJCV},
pages={1--19},
year={2019}
}


@article{kong2019cross,
title={Cross-Resolution Face Recognition via Prior-Aided Face Hallucination and Residual Knowledge Distillation},
author={Kong, Hanyang and Zhao, Jian and Tu, Xiaoguang and Xing, Junliang and Shen, Shengmei and Feng, Jiashi},
journal={arXiv preprint arXiv:1905.10777},
year={2019}
}


@article{tu2019joint,
title={Joint 3D face reconstruction and dense face alignment from a single image with 2D-assisted self-supervised learning},
author={Tu, Xiaoguang and Zhao, Jian and Jiang, Zihang and Luo, Yao and Xie, Mei and Zhao, Yang and He, Linxiao and Ma, Zheng and Feng, Jiashi},
journal={arXiv preprint arXiv:1903.09359},
year={2019}
}     


@inproceedings{zhao2019multi,
title={Multi-Prototype Networks for Unconstrained Set-based Face Recognition},
author={Zhao, Jian and Li, Jianshu and Tu, Xiaoguang and Zhao, Fang and Xin, Yuan and Xing, Junliang and Liu, Hengzhu and Yan, Shuicheng and Feng, Jiashi},
booktitle={IJCAI},
year={2019}
}


@inproceedings{zhao2019look,
title={Look Across Elapse: Disentangled Representation Learning and Photorealistic Cross-Age Face Synthesis for Age-Invariant Face Recognition},
author={Zhao, Jian and Cheng, Yu and Cheng, Yi and Yang, Yang and Lan, Haochong and Zhao, Fang and Xiong, Lin and Xu, Yan and Li, Jianshu and Pranata, Sugiri and others},
booktitle={AAAI},
year={2019}
}


@article{tu2019joint,
title={Joint 3D Face Reconstruction and Dense Face Alignment from A Single Image with 2D-Assisted Self-Supervised Learning},
author={Tu, Xiaoguang and Zhao, Jian and Jiang, Zihang and Luo, Yao and Xie, Mei and Zhao, Yang and He, Linxiao and Ma, Zheng and Feng, Jiashi},
journal={arXiv preprint arXiv:1903.09359},
year={2019}
}


@article{tu2019learning,
title={Learning Generalizable and Identity-Discriminative Representations for Face Anti-Spoofing},
author={Tu, Xiaoguang and Zhao, Jian and Xie, Mei and Du, Guodong and Zhang, Hengsheng and Li, Jianshu and Ma, Zheng and Feng, Jiashi},
journal={arXiv preprint arXiv:1901.05602},
year={2019}
}


@article{zhao20183d,
title={3D-Aided Dual-Agent GANs for Unconstrained Face Recognition},
author={Zhao, Jian and Xiong, Lin and Li, Jianshu and Xing, Junliang and Yan, Shuicheng and Feng, Jiashi},
journal={T-PAMI},
year={2018}
}


@inproceedings{zhao2018towards,
title={Towards Pose Invariant Face Recognition in the Wild},
author={Zhao, Jian and Cheng, Yu and Xu, Yan and Xiong, Lin and Li, Jianshu and Zhao, Fang and Jayashree, Karlekar and Pranata,         Sugiri and Shen, Shengmei and Xing, Junliang and others},
booktitle={CVPR},
pages={2207--2216},
year={2018}
}


@inproceedings{zhao3d,
title={3D-Aided Deep Pose-Invariant Face Recognition},
author={Zhao, Jian and Xiong, Lin and Cheng, Yu and Cheng, Yi and Li, Jianshu and Zhou, Li and Xu, Yan and Karlekar, Jayashree and       Pranata, Sugiri and Shen, Shengmei and others},
booktitle={IJCAI},
pages={1184--1190},
year={2018}
}


@inproceedings{zhao2018dynamic,
title={Dynamic Conditional Networks for Few-Shot Learning},
author={Zhao, Fang and Zhao, Jian and Yan, Shuicheng and Feng, Jiashi},
booktitle={ECCV},
pages={19--35},
year={2018}
}


@inproceedings{zhao2017dual,
title={Dual-agent gans for photorealistic and identity preserving profile face synthesis},
author={Zhao, Jian and Xiong, Lin and Jayashree, Panasonic Karlekar and Li, Jianshu and Zhao, Fang and Wang, Zhecan and Pranata,           Panasonic Sugiri and Shen, Panasonic Shengmei and Yan, Shuicheng and Feng, Jiashi},
booktitle={NeurIPS},
pages={66--76},
year={2017}
}


@inproceedings{zhao122017marginalized,
title={Marginalized cnn: Learning deep invariant representations},
author={Zhao12, Jian and Li, Jianshu and Zhao, Fang and Yan13, Shuicheng and Feng, Jiashi},
booktitle={BMVC},
year={2017}
}


@inproceedings{cheng2017know,
title={Know you at one glance: A compact vector representation for low-shot learning},
author={Cheng, Yu and Zhao, Jian and Wang, Zhecan and Xu, Yan and Jayashree, Karlekar and Shen, Shengmei and Feng, Jiashi},
booktitle={ICCVW},
pages={1924--1932},
year={2017}
}


@inproceedings{xu2017high,
title={High performance large scale face recognition with multi-cognition softmax and feature retrieval},
author={Xu, Yan and Cheng, Yu and Zhao, Jian and Wang, Zhecan and Xiong, Lin and Jayashree, Karlekar and Tamura, Hajime and Kagaya, Tomoyuki and Shen, Shengmei and Pranata, Sugiri and others},
booktitle={ICCVW},
pages={1898--1906},
year={2017}
}


@inproceedings{wangconditional,
title={Conditional Dual-Agent GANs for Photorealistic and Annotation Preserving Image Synthesis},
author={Wang, Zhecan and Zhao, Jian and Cheng, Yu and Xiao, Shengtao and Li, Jianshu and Zhao, Fang and Feng, Jiashi and Kassim, Ashraf},
booktitle={BMVCW},
}


@inproceedings{li2017integrated,
title={Integrated face analytics networks through cross-dataset hybrid training},
author={Li, Jianshu and Xiao, Shengtao and Zhao, Fang and Zhao, Jian and Li, Jianan and Feng, Jiashi and Yan, Shuicheng and Sim, Terence},
booktitle={ACM MM},
pages={1531--1539},
year={2017}
}


@article{xiong2017good,
title={A good practice towards top performance of face recognition: Transferred deep feature fusion},
author={Xiong, Lin and Karlekar, Jayashree and Zhao, Jian and Cheng, Yi and Xu, Yan and Feng, Jiashi and Pranata, Sugiri and Shen, Shengmei},
journal={arXiv preprint arXiv:1704.00438},
year={2017}
}


@article{zhao2017robust,
title={Robust lstm-autoencoders for face de-occlusion in the wild},
author={Zhao, Fang and Feng, Jiashi and Zhao, Jian and Yang, Wenhan and Yan, Shuicheng},
journal={T-IP},
volume={27},
number={2},
pages={778--790},
year={2017}
}


@inproceedings{li2016robust,
title={Robust face recognition with deep multi-view representation learning},
author={Li, Jianshu and Zhao, Jian and Zhao, Fang and Liu, Hao and Li, Jing and Shen, Shengmei and Feng, Jiashi and Sim, Terence},
booktitle={ACM MM},
pages={1068--1072},
year={2016}
}
GitHub - ZhaoJ9014/face.evoLVe: ??High-Performance Face Recognition Library on PaddlePaddle & PyTorch??
??High-Performance Face Recognition Library on PaddlePaddle & PyTorch?? - GitHub - ZhaoJ9014/face.evoLVe: ??High-Performance Face Recognition Library on PaddlePaddle & PyTorch??