mirror of
https://github.com/serengil/deepface.git
synced 2025-06-07 12:05:22 +00:00
307 lines
9.3 KiB
Python
307 lines
9.3 KiB
Python
# 3rd party dependencies
|
|
import tensorflow as tf
|
|
|
|
# project dependencies
|
|
from deepface.commons import package_utils, weight_utils
|
|
from deepface.models.FacialRecognition import FacialRecognition
|
|
from deepface.commons.logger import Logger
|
|
|
|
logger = Logger()
|
|
|
|
tf_major = package_utils.get_tf_major_version()
|
|
if tf_major == 1:
|
|
import keras
|
|
from keras import backend as K
|
|
from keras.models import Model
|
|
from keras.layers import (
|
|
Activation,
|
|
Add,
|
|
BatchNormalization,
|
|
Concatenate,
|
|
Conv2D,
|
|
DepthwiseConv2D,
|
|
GlobalAveragePooling2D,
|
|
Input,
|
|
Reshape,
|
|
Multiply,
|
|
ReLU,
|
|
PReLU,
|
|
)
|
|
else:
|
|
from tensorflow import keras
|
|
from tensorflow.keras import backend as K
|
|
from tensorflow.keras.models import Model
|
|
from tensorflow.keras.layers import (
|
|
Activation,
|
|
Add,
|
|
BatchNormalization,
|
|
Concatenate,
|
|
Conv2D,
|
|
DepthwiseConv2D,
|
|
GlobalAveragePooling2D,
|
|
Input,
|
|
Reshape,
|
|
Multiply,
|
|
ReLU,
|
|
PReLU,
|
|
)
|
|
|
|
|
|
# pylint: disable=line-too-long, too-few-public-methods, no-else-return, unsubscriptable-object, comparison-with-callable
|
|
PRETRAINED_WEIGHTS = "https://github.com/HamadYA/GhostFaceNets/releases/download/v1.2/GhostFaceNet_W1.3_S1_ArcFace.h5"
|
|
|
|
|
|
class GhostFaceNetClient(FacialRecognition):
|
|
"""
|
|
GhostFaceNet model (GhostFaceNetV1 backbone)
|
|
Repo: https://github.com/HamadYA/GhostFaceNets
|
|
Pre-trained weights: https://github.com/HamadYA/GhostFaceNets/releases/tag/v1.2
|
|
GhostFaceNet_W1.3_S1_ArcFace.h5 ~ 16.5MB
|
|
Author declared that this backbone and pre-trained weights got 99.7667% accuracy on LFW
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.model_name = "GhostFaceNet"
|
|
self.input_shape = (112, 112)
|
|
self.output_shape = 512
|
|
self.model = load_model()
|
|
|
|
|
|
def load_model():
|
|
model = GhostFaceNetV1()
|
|
|
|
weight_file = weight_utils.download_weights_if_necessary(
|
|
file_name="ghostfacenet_v1.h5", source_url=PRETRAINED_WEIGHTS
|
|
)
|
|
|
|
model = weight_utils.load_model_weights(
|
|
model=model, weight_file=weight_file
|
|
)
|
|
|
|
return model
|
|
|
|
|
|
def GhostFaceNetV1() -> Model:
|
|
"""
|
|
Build GhostFaceNetV1 model. Refactored from
|
|
github.com/HamadYA/GhostFaceNets/blob/main/backbones/ghost_model.py
|
|
Returns:
|
|
model (Model)
|
|
"""
|
|
inputs = Input(shape=(112, 112, 3))
|
|
|
|
out_channel = 20
|
|
|
|
nn = Conv2D(
|
|
out_channel,
|
|
(3, 3),
|
|
strides=1,
|
|
padding="same",
|
|
use_bias=False,
|
|
kernel_initializer=keras.initializers.VarianceScaling(
|
|
scale=2.0, mode="fan_out", distribution="truncated_normal"
|
|
),
|
|
)(inputs)
|
|
|
|
nn = BatchNormalization(axis=-1)(nn)
|
|
nn = Activation("relu")(nn)
|
|
|
|
dwkernels = [3, 3, 3, 5, 5, 3, 3, 3, 3, 3, 3, 5, 5, 5, 5, 5]
|
|
exps = [20, 64, 92, 92, 156, 312, 260, 240, 240, 624, 872, 872, 1248, 1248, 1248, 664]
|
|
outs = [20, 32, 32, 52, 52, 104, 104, 104, 104, 144, 144, 208, 208, 208, 208, 208]
|
|
strides_set = [1, 2, 1, 2, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1]
|
|
reductions = [0, 0, 0, 24, 40, 0, 0, 0, 0, 156, 220, 220, 0, 312, 0, 168]
|
|
|
|
pre_out = out_channel
|
|
for dwk, stride, exp, out, reduction in zip(dwkernels, strides_set, exps, outs, reductions):
|
|
shortcut = not (out == pre_out and stride == 1)
|
|
nn = ghost_bottleneck(nn, dwk, stride, exp, out, reduction, shortcut)
|
|
pre_out = out
|
|
|
|
nn = Conv2D(
|
|
664,
|
|
(1, 1),
|
|
strides=(1, 1),
|
|
padding="valid",
|
|
use_bias=False,
|
|
kernel_initializer=keras.initializers.VarianceScaling(
|
|
scale=2.0, mode="fan_out", distribution="truncated_normal"
|
|
),
|
|
)(nn)
|
|
nn = BatchNormalization(axis=-1)(nn)
|
|
nn = Activation("relu")(nn)
|
|
|
|
xx = Model(inputs=inputs, outputs=nn, name="GhostFaceNetV1")
|
|
|
|
# post modelling
|
|
inputs = xx.inputs[0]
|
|
nn = xx.outputs[0]
|
|
|
|
nn = keras.layers.DepthwiseConv2D(nn.shape[1], use_bias=False, name="GDC_dw")(nn)
|
|
nn = keras.layers.BatchNormalization(momentum=0.99, epsilon=0.001, name="GDC_batchnorm")(nn)
|
|
nn = keras.layers.Conv2D(
|
|
512, 1, use_bias=True, kernel_initializer="glorot_normal", name="GDC_conv"
|
|
)(nn)
|
|
nn = keras.layers.Flatten(name="GDC_flatten")(nn)
|
|
|
|
embedding = keras.layers.BatchNormalization(
|
|
momentum=0.99, epsilon=0.001, scale=True, name="pre_embedding"
|
|
)(nn)
|
|
embedding_fp32 = keras.layers.Activation("linear", dtype="float32", name="embedding")(embedding)
|
|
|
|
model = keras.models.Model(inputs, embedding_fp32, name=xx.name)
|
|
model = replace_relu_with_prelu(model=model)
|
|
return model
|
|
|
|
|
|
def se_module(inputs, reduction):
|
|
"""
|
|
Refactored from github.com/HamadYA/GhostFaceNets/blob/main/backbones/ghost_model.py
|
|
"""
|
|
# get the channel axis
|
|
channel_axis = 1 if K.image_data_format() == "channels_first" else -1
|
|
# filters = channel axis shape
|
|
filters = inputs.shape[channel_axis]
|
|
|
|
# from None x H x W x C to None x C
|
|
se = GlobalAveragePooling2D()(inputs)
|
|
|
|
# Reshape None x C to None 1 x 1 x C
|
|
se = Reshape((1, 1, filters))(se)
|
|
|
|
# Squeeze by using C*se_ratio. The size will be 1 x 1 x C*se_ratio
|
|
se = Conv2D(
|
|
reduction,
|
|
kernel_size=1,
|
|
use_bias=True,
|
|
kernel_initializer=keras.initializers.VarianceScaling(
|
|
scale=2.0, mode="fan_out", distribution="truncated_normal"
|
|
),
|
|
)(se)
|
|
se = Activation("relu")(se)
|
|
|
|
# Excitation using C filters. The size will be 1 x 1 x C
|
|
se = Conv2D(
|
|
filters,
|
|
kernel_size=1,
|
|
use_bias=True,
|
|
kernel_initializer=keras.initializers.VarianceScaling(
|
|
scale=2.0, mode="fan_out", distribution="truncated_normal"
|
|
),
|
|
)(se)
|
|
se = Activation("hard_sigmoid")(se)
|
|
|
|
return Multiply()([inputs, se])
|
|
|
|
|
|
def ghost_module(inputs, out, convkernel=1, dwkernel=3, add_activation=True):
|
|
"""
|
|
Refactored from github.com/HamadYA/GhostFaceNets/blob/main/backbones/ghost_model.py
|
|
"""
|
|
conv_out_channel = out // 2
|
|
cc = Conv2D(
|
|
conv_out_channel,
|
|
convkernel,
|
|
use_bias=False,
|
|
strides=(1, 1),
|
|
padding="same",
|
|
kernel_initializer=keras.initializers.VarianceScaling(
|
|
scale=2.0, mode="fan_out", distribution="truncated_normal"
|
|
),
|
|
)(inputs)
|
|
cc = BatchNormalization(axis=-1)(cc)
|
|
if add_activation:
|
|
cc = Activation("relu")(cc)
|
|
|
|
nn = DepthwiseConv2D(
|
|
dwkernel,
|
|
1,
|
|
padding="same",
|
|
use_bias=False,
|
|
depthwise_initializer=keras.initializers.VarianceScaling(
|
|
scale=2.0, mode="fan_out", distribution="truncated_normal"
|
|
),
|
|
)(cc)
|
|
nn = BatchNormalization(axis=-1)(nn)
|
|
if add_activation:
|
|
nn = Activation("relu")(nn)
|
|
return Concatenate()([cc, nn])
|
|
|
|
|
|
def ghost_bottleneck(inputs, dwkernel, strides, exp, out, reduction, shortcut=True):
|
|
"""
|
|
Refactored from github.com/HamadYA/GhostFaceNets/blob/main/backbones/ghost_model.py
|
|
"""
|
|
nn = ghost_module(inputs, exp, add_activation=True)
|
|
if strides > 1:
|
|
# Extra depth conv if strides higher than 1
|
|
nn = DepthwiseConv2D(
|
|
dwkernel,
|
|
strides,
|
|
padding="same",
|
|
use_bias=False,
|
|
depthwise_initializer=keras.initializers.VarianceScaling(
|
|
scale=2.0, mode="fan_out", distribution="truncated_normal"
|
|
),
|
|
)(nn)
|
|
nn = BatchNormalization(axis=-1)(nn)
|
|
|
|
if reduction > 0:
|
|
# Squeeze and excite
|
|
nn = se_module(nn, reduction)
|
|
|
|
# Point-wise linear projection
|
|
nn = ghost_module(nn, out, add_activation=False) # ghost2 = GhostModule(exp, out, relu=False)
|
|
|
|
if shortcut:
|
|
xx = DepthwiseConv2D(
|
|
dwkernel,
|
|
strides,
|
|
padding="same",
|
|
use_bias=False,
|
|
depthwise_initializer=keras.initializers.VarianceScaling(
|
|
scale=2.0, mode="fan_out", distribution="truncated_normal"
|
|
),
|
|
)(inputs)
|
|
xx = BatchNormalization(axis=-1)(xx)
|
|
xx = Conv2D(
|
|
out,
|
|
(1, 1),
|
|
strides=(1, 1),
|
|
padding="valid",
|
|
use_bias=False,
|
|
kernel_initializer=keras.initializers.VarianceScaling(
|
|
scale=2.0, mode="fan_out", distribution="truncated_normal"
|
|
),
|
|
)(xx)
|
|
xx = BatchNormalization(axis=-1)(xx)
|
|
else:
|
|
xx = inputs
|
|
return Add()([xx, nn])
|
|
|
|
|
|
def replace_relu_with_prelu(model) -> Model:
|
|
"""
|
|
Replaces relu activation function in the built model with prelu.
|
|
Refactored from github.com/HamadYA/GhostFaceNets/blob/main/backbones/ghost_model.py
|
|
Args:
|
|
model (Model): built model with relu activation functions
|
|
Returns
|
|
model (Model): built model with prelu activation functions
|
|
"""
|
|
|
|
def convert_relu(layer):
|
|
if isinstance(layer, ReLU) or (
|
|
isinstance(layer, Activation) and layer.activation == keras.activations.relu
|
|
):
|
|
layer_name = layer.name.replace("_relu", "_prelu")
|
|
return PReLU(
|
|
shared_axes=[1, 2],
|
|
alpha_initializer=tf.initializers.Constant(0.25),
|
|
name=layer_name,
|
|
)
|
|
return layer
|
|
|
|
input_tensors = keras.layers.Input(model.input_shape[1:])
|
|
return keras.models.clone_model(model, input_tensors=input_tensors, clone_function=convert_relu)
|