Advertisement

LFD-A-Light-and-Fast-Detector

阅读量:

该文本描述了MobileFaceNet和Arcface的PyTorch实现,包括模型结构、各个模块的实现(如Convblock、DepthWise、Residual等)、模型的训练和推理过程,以及模型的保存和ONNX转换到MNN的过程。文本中还提到了模型的性能和实现细节,如使用了批处理归一化、PReLU激活函数等。

开源地址

https://github.com/YonghaoHe/LFD-A-Light-and-Fast-Detector

320*240 cpu 17 90ms,模型大小 6.71 m

LFD-A-Light-and-Fast-Detector

复制代码
 #!/usr/bin/env python3

    
 # -*- coding: utf-8 -*-
    
 """
    
 Created on Tue May 21 09:09:25 2019
    
 Pytorch mobilefacenet & arcface architecture
    
   7. @author: AIRocker
    
 """
    
 import os
    
 import time
    
  
    
 from torch.nn import Linear, Conv2d, BatchNorm1d, BatchNorm2d, PReLU, ReLU, Sigmoid, Dropout2d, Dropout, AvgPool2d, MaxPool2d, AdaptiveAvgPool2d, Sequential, Module, Parameter
    
 from torch import nn
    
 import torch
    
 import math
    
  
    
 from cv_tools.call_cmd import onnx2mnn
    
  
    
  
    
 class Flatten(Module):
    
     def forward(self, input):
    
     return input.view(input.size(0), -1)
    
  
    
 def l2_norm(input,axis=1):
    
     norm = torch.norm(input,2,axis,True)
    
     output = torch.div(input, norm)
    
     if torch.isnan(output[0][0]):
    
     print('isnans')
    
     return output
    
  
    
 ##################################  MobileFaceNet #############################################################
    
     
    
 class Conv_block(Module):
    
     def __init__(self, in_c, out_c, kernel=(1, 1), stride=(1, 1), padding=(0, 0), groups=1):
    
     super(Conv_block, self).__init__()
    
     self.conv = Conv2d(in_c, out_channels=out_c, kernel_size=kernel, groups=groups, stride=stride, padding=padding, bias=False)
    
     self.bn = BatchNorm2d(out_c)
    
     self.prelu = PReLU(out_c)
    
     def forward(self, x):
    
     x = self.conv(x)
    
     x = self.bn(x)
    
     x = self.prelu(x)
    
     return x
    
  
    
 class Linear_block(Module):
    
     def __init__(self, in_c, out_c, kernel=(1, 1), stride=(1, 1), padding=(0, 0), groups=1):
    
     super(Linear_block, self).__init__()
    
     self.conv = Conv2d(in_c, out_channels=out_c, kernel_size=kernel, groups=groups, stride=stride, padding=padding, bias=False)
    
     self.bn = BatchNorm2d(out_c)
    
     def forward(self, x):
    
     x = self.conv(x)
    
     x = self.bn(x)
    
     return x
    
  
    
 class Depth_Wise(Module):
    
      def __init__(self, in_c, out_c, residual = False, kernel=(3, 3), stride=(2, 2), padding=(1, 1), groups=1):
    
     super(Depth_Wise, self).__init__()
    
     self.conv = Conv_block(in_c, out_c=groups, kernel=(1, 1), padding=(0, 0), stride=(1, 1))
    
     self.conv_dw = Conv_block(groups, groups, groups=groups, kernel=kernel, padding=padding, stride=stride)
    
     self.project = Linear_block(groups, out_c, kernel=(1, 1), padding=(0, 0), stride=(1, 1))
    
     self.residual = residual
    
      def forward(self, x):
    
     if self.residual:
    
         short_cut = x
    
     x = self.conv(x)
    
     x = self.conv_dw(x)
    
     x = self.project(x)
    
     if self.residual:
    
         output = short_cut + x
    
     else:
    
         output = x
    
     return output
    
  
    
 class Residual(Module):
    
     def __init__(self, c, num_block, groups, kernel=(3, 3), stride=(1, 1), padding=(1, 1)):
    
     super(Residual, self).__init__()
    
     modules = []
    
     for _ in range(num_block):
    
         modules.append(Depth_Wise(c, c, residual=True, kernel=kernel, padding=padding, stride=stride, groups=groups))
    
     self.model = Sequential(*modules)
    
     def forward(self, x):
    
     return self.model(x)
    
  
    
 class MobileFaceNet(Module):
    
     def __init__(self, embedding_size):
    
     super(MobileFaceNet, self).__init__()
    
     self.conv1 = Conv_block(3, 64, kernel=(3, 3), stride=(2, 2), padding=(1, 1))
    
     self.conv2_dw = Conv_block(64, 64, kernel=(3, 3), stride=(1, 1), padding=(1, 1), groups=64)
    
     self.conv_23 = Depth_Wise(64, 64, kernel=(3, 3), stride=(2, 2), padding=(1, 1), groups=128)
    
     self.conv_3 = Residual(64, num_block=4, groups=128, kernel=(3, 3), stride=(1, 1), padding=(1, 1))
    
     self.conv_34 = Depth_Wise(64, 128, kernel=(3, 3), stride=(2, 2), padding=(1, 1), groups=256)
    
     self.conv_4 = Residual(128, num_block=6, groups=256, kernel=(3, 3), stride=(1, 1), padding=(1, 1))
    
     self.conv_45 = Depth_Wise(128, 128, kernel=(3, 3), stride=(2, 2), padding=(1, 1), groups=512)
    
     self.conv_5 = Residual(128, num_block=2, groups=256, kernel=(3, 3), stride=(1, 1), padding=(1, 1))
    
     self.conv_6_sep = Conv_block(128, 512, kernel=(1, 1), stride=(1, 1), padding=(0, 0))
    
     self.conv_6_dw = Linear_block(512, 512, groups=512, kernel=(7,7), stride=(1, 1), padding=(0, 0))
    
     self.conv_6_flatten = Flatten()
    
     self.linear = Linear(512, embedding_size, bias=False)
    
     self.bn = BatchNorm1d(embedding_size)
    
     
    
     # weight initialization
    
     for m in self.modules():
    
         if isinstance(m, nn.Conv2d):
    
             n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
    
             m.weight.data.normal_(0, math.sqrt(2. / n))
    
         elif isinstance(m, nn.BatchNorm2d):
    
             m.weight.data.fill_(1)
    
             m.bias.data.zero_()
    
     
    
     def forward(self, x):
    
     out = self.conv1(x)
    
  
    
     out = self.conv2_dw(out)
    
  
    
     out = self.conv_23(out)
    
  
    
     out = self.conv_3(out)
    
     
    
     out = self.conv_34(out)
    
  
    
     out = self.conv_4(out)
    
  
    
     out = self.conv_45(out)
    
  
    
     out = self.conv_5(out)
    
  
    
     out = self.conv_6_sep(out)
    
  
    
     out = self.conv_6_dw(out)
    
     if torch.isnan(out[0][0]):
    
         print('isnans')
    
     out = self.conv_6_flatten(out)
    
  
    
     out = self.linear(out)
    
     if torch.isnan(out[0][0]):
    
         print('isnans')
    
     out = self.bn(out)
    
  
    
     return l2_norm(out)
    
  
    
 ##################################  Arcface head #############################################################
    
  
    
 class Arcface(Module):
    
     # implementation of additive margin softmax loss in https://arxiv.org/abs/1801.05599    
    
     def __init__(self, embedding_size=512, classnum=51332,  s=64., m=0.5):
    
     super(Arcface, self).__init__()
    
     self.classnum = classnum
    
     self.kernel = Parameter(torch.Tensor(embedding_size,classnum))
    
     nn.init.xavier_uniform_(self.kernel)
    
     # initial kernel
    
     self.kernel.data.uniform_(-1, 1).renorm_(2,1,1e-5).mul_(1e5)
    
     self.m = m # the margin value, default is 0.5
    
     self.s = s # scalar value default is 64, see normface https://arxiv.org/abs/1704.06369
    
     self.cos_m = math.cos(m)
    
     self.sin_m = math.sin(m)
    
     self.mm = self.sin_m * m  # issue 1
    
     self.threshold = math.cos(math.pi - m)
    
     def forward(self, embbedings, label):
    
     # weights norm
    
     nB = len(embbedings)
    
     if torch.isnan(self.kernel[0][0]):
    
         print('isnans')
    
     kernel_norm = l2_norm(self.kernel,axis=0) # normalize for each column
    
     # cos(theta+m)
    
     cos_theta = torch.mm(embbedings,kernel_norm)
    
 #         output = torch.mm(embbedings,kernel_norm)
    
     cos_theta = cos_theta.clamp(-1,1) # for numerical stability
    
     cos_theta_2 = torch.pow(cos_theta, 2)
    
     sin_theta_2 = 1 - cos_theta_2
    
     sin_theta = torch.sqrt(sin_theta_2)
    
     cos_theta_m = (cos_theta * self.cos_m - sin_theta * self.sin_m)
    
     # this condition controls the theta+m should in range [0, pi]
    
     #      0<=theta+m<=pi
    
     #     -m<=theta<=pi-m
    
     cond_v = cos_theta - self.threshold
    
     cond_mask = cond_v <= 0
    
     keep_val = (cos_theta - self.mm) # when theta not in [0,pi], use cosface instead
    
     cos_theta_m[cond_mask] = keep_val[cond_mask]
    
     output = cos_theta * 1.0 # a little bit hacky way to prevent in_place operation on cos_theta
    
     idx_ = torch.arange(0, nB, dtype=torch.long)
    
     output[idx_, label] = cos_theta_m[idx_, label]
    
     output *= self.s # scale up in order to make softmax work, first introduced in normface
    
  
    
     # if torch.isnan(output[0][0]):
    
     #     print('isnans')
    
  
    
     return output
    
  
    
 if __name__ == "__main__":
    
  
    
     device ="cpu"# torch.device("cuda:1" if torch.cuda.is_available() else "cpu")
    
     input = torch.Tensor(1, 3, 112, 112).to(device)
    
     model = MobileFaceNet(512).to(device)
    
     model.eval()
    
     model_path = "mfv2.pth"
    
     torch.save(model.state_dict(), model_path)
    
  
    
     fsize = os.path.getsize(model_path)
    
     fsize = fsize / float(1024 * 1024)
    
  
    
     print(f"model size {round(fsize, 2)} m")
    
  
    
     for i in range(10):
    
     start=time.time()
    
  
    
     x = model(input)
    
  
    
     print('output.size ', time.time()-start,x.shape)
    
     torch.save(model.state_dict(), f'var5.pth')
    
  
    
     # input = torch.randn(1, 3, 112, 112)
    
     onnx_name = "mfv2.onnx"
    
     torch.onnx.export(model, input, onnx_name, verbose=False)
    
  
    
     mnn_name = onnx_name.replace(".onnx", ".mnn")
    
     onnx2mnn(onnx_name, mnn_name)

全部评论 (0)

还没有任何评论哟~