当前位置:   article > 正文

人脸识别(Arcface模型)改进_facenet的改进模型

facenet的改进模型

本文档主要记录对Arcface模型改进过程中所学习到的知识。

1.人脸识别发展历程

  • 2014年DeepFace和DeepID系列都是主要先训练Softmax多分类器人脸识别框架;然后抽取特征层,用特征再训练另一个神经网络、孪生网络或组合贝叶斯等人脸验证框架。想同时拥有人脸验证和人脸识别系统,需要分开训练两个神经网络。但线性变换矩阵W的大小随着身份数量n的增加而线性增大。
    • DeepFace、DeepID框架为 CNN+Softmax,网络在第一个FC层形成判别力很强的人脸特征,用于人脸识别。
    • DeepID2、DeepID2+、DeepID3都采用 CNN+Softmax+Contrastive Loss,使得同类特征的L2距离尽可能小,不同类特征的L2距离大于某个间隔。
  • 2015年FaceNet提出了一个绝大部分人脸问题的统一解决框架,直接学习嵌入特征,然后人脸识别、人脸验证和人脸聚类等都基于这个特征来做。FaceNet在 DeepID2 的基础上,抛弃了分类层,再将 Contrastive Loss 改进为 Triplet Loss,获得类内紧凑和类间差异。但人脸三元组的数量出现爆炸式增长,特别是对于大型数据集,导致迭代次数显著增加;样本挖掘策略造成很难有效的进行模型的训练。
    • 2017年《In Defense of the Triplet Loss for Person Re-Identification》提出了 Soft-Margin 损失公式替代原始的 Triplet Loss 表达式,并引进了 Batch Hard Sampling。
    • 2017年Wu, C等从导函数角度解释了为什么 Non-squared Distance 比 Squared-distance 好,并在这个 insight 基础上提出了 Margin Based Loss。此外,他们还提出了 Distance Weighted Sampling。文章认为 FaceNet 中的 Semi-hard Sampling,Deep Face Recognition 中的 Random Hard 和 Batch Hard 都不能轻易取到会产生大梯度(大 loss,即对模型训练有帮助的 triplets)。
  • 2015年VGGNet为了加速 Triplet Loss 的训练,先用传统的 softmax 训练人脸识别模型,因为 Classficiation 信号的强监督特性,模型会很快拟合;之后移除Classificiation Layer,用 Triplet Loss 对模型进行特征层 finetune。
  • 2016年Center Loss为每个类别学习一个中心,并将每个类别的所有特征向量拉向对应类别中心,根据每个特征向量与其类别中心之间的欧几里得距离,以获得类内紧度;而类间分散则由Softmax loss 的联合惩罚来保证。然而,在训练期间更新实际类别中心非常困难,因为可供训练的人脸类别数量最近急剧增加。
  • 2017年COCO Loss,归一化了权值c,归一化了特征f,并乘尺度因子,在LFW上达到99.86%;
  • 2017年SphereFace提出A-Softmax,是L-Softmax的改进,提出了角度间隔惩罚,又归一化了权值W,让训练更加集中在优化深度特征映射和特征向量角度上,降低样本数量不均衡问题。
    • 《Learning towards minimum hyperspherical energy》又说了它的损失函数需要一系列近似才能计算出来,从而导致网络训练不稳定。为了稳定训练,他们提出混合Softmax loss损失函数,经验上,softmax loss 在训练过程中占主导地位,因为基于整数的乘性角度间隔使得目标逻辑曲线非常陡峭,从而阻碍了收敛。
  • 2018年ArcFace提出加性角度间隔损失,θ+m,还归一化特征向量和权重,几何上有恒定的线性角度margen。直接优化弧度,为了性能的稳定,ArcFace不需要与其他loss函数实现联合监督。
  • 2018年MobileFaceNets,MobileNetV2+ArcFace Loss,轻量化模型。
  • 2019年AdaFace引入了自适应特征正则化的方法,根据样本的难度动态调整正则化强度,从而提高了对困难样本的处理能力,并提升了模型的鲁棒性,特别是在处理复杂背景和光照变化时表现出色。
  • PFE将人脸嵌入表示为概率分布而不是确定值,通过将不确定性纳入考虑,提升了模型在不同条件下的表现。
  • 2020年CurricularFace提出了一种课程学习策略,通过逐步增加训练样本的难度,提高了模型的学习效率和识别性能。
  • 2021年AFS (Attention-aware Face Super-Resolution)利用注意力机制对低分辨率人脸图像进行超分辨率重建,提升了低质量图像的人脸识别性能。
  • Partial FC (Partial Fully Connected Layer)通过减少全连接层的计算量,显著提升了训练速度和内存效率,同时保持了高识别准确率。提高了大规模人脸识别训练的效率,适用于大数据集和大模型的训练。
  • 2022年SAM (Self-Attention Mechanism)SAM将自注意力机制应用于人脸识别,通过捕捉全局上下文信息,提高了特征提取的准确性和鲁棒性。
  • FairFace++致力于解决人脸识别中的公平性问题,提出了改进的数据采集和训练方法,减少了算法对不同种族和性别的偏见。
  • 2023年EfficientFace采用了一系列模型压缩和加速技术,如量化、剪枝和蒸馏,使得人脸识别模型在移动设备和边缘设备上运行更加高效。提升了模型的运行效率和实时性,适用于资源受限的应用场景。

2.Arcface模型分析

2.1 网络结构

Arcface网络的输入是H*W*3的图片,输出为512维的特征向量,其网络结构主要模块为:

 主干网络通过多个上述模块堆叠,输出512维特征向量。

2.2 Arcface损失函数

在此之前常用的softmax损失函数:

其中x_{i}为第i个样本的输出特征,真实的分类为第y_{i}类,w_{yi}为最后一层全连接层的权重中第y_{i}列,b_{yi}为最后全连接层的偏置,w_{j}为最后一层全连接层的权重中第j列,m 为批量数,n为类别数。Softmax损失函数没有明确优化特征以使正对样本的相似性得分更高,而负对样本的相似性得分更低,导致该损失函数在人脸识别中性能存在明显瓶颈。

Arcface在softmax损失函数的基础上进行改进,根据向量乘法的定义:

通过L2归一化固定$||w_{j} || =1$,固定$||x_{i}||$并重新缩放到s,通过该方法简化计算,损失函数中仅取决于特征向量和权重之间的角度。在cos\theta中添加一个角度余量m,由于cos(\theta + m)小于cos\theta,当\theta\in[0,\pi-m]时,Arcface损失函数为:

 其中的约束条件为:

 实际上Arcface的损失函数遵循增大类间距离,减小类内距离的原则,角度余量m起到增大类间边际距离的作用,分类效果如下所示:

Softmgeax损失函数的目的是为了找出各类的分界,而Arcface的损失函数的优化目标是为了尽可能的增大类间距离,将不同类按照一定距离区分开来。

3.Arcface改进实验记录

3.1 替换Backbone

3.1.1 更改Backbone为fasternet

fasternet 论文:https://arxiv.org/abs/2303.03667

相关博客:【CVPR 2023】FasterNet论文详解_cvpr2023 fasternet-CSDN博客

针对特征提取中各通道的特征图具有高度相似性,存在计算冗余的问题,作者提出一种新型的卷积方式(PConv + PWConv),PConv 为在输入特征图的c_{p}个通道上进行卷积,其余通道上不变,但为了充分地利用各个通道的信息,在剩余通道上附加PWConv(逐点卷积),如图5(b)所示,形成T形Conv。

鉴于新型PConv和现成的PWConv作为主要的算子,进一步提出FasterNet,这是一个新的神经网络家族,运行速度非常快,对许多视觉任务非常有效。作者的目标是使体系结构尽可能简单,使其总体上对硬件友好。

在图4中展示了整体架构。它有4个层次级,每个层次级前面都有一个嵌入层(步长为4的常规4×4卷积)或一个合并层(步长为2的常规2×2卷积),用于空间下采样和通道数量扩展。每个阶段都有一堆FasterNet块。最后两个阶段中的块消耗更少的内存访问,并且倾向于具有更高的FLOPS,因此,放置了更多FasterNet块,并相应地将更多计算分配给最后两个阶段。每个FasterNet块有一个PConv层,后跟2个PWConv(或Conv 1×1)层。它们一起显示为倒置残差块,其中中间层具有扩展的通道数量,并且放置了Shorcut以重用输入特征。

除了上述算子,标准化和激活层对于高性能神经网络也是不可或缺的。然而,许多先前的工作在整个网络中过度使用这些层,这可能会限制特征多样性,从而损害性能。它还可以降低整体计算速度。相比之下,只将它们放在每个中间PWConv之后,以保持特征多样性并实现较低的延迟。

此外,使用批次归一化(BN)代替其他替代方法。BN的优点是,它可以合并到其相邻的Conv层中,以便更快地进行推断,同时与其他层一样有效。对于激活层,根据经验选择了GELU用于较小的FasterNet变体,而ReLU用于较大的FasterNet变体,同时考虑了运行时间和有效性。最后三个层,即全局平均池化、卷积1×1和全连接层,一起用于特征转换和分类。

Fasternet的代码实现,保存在backbone/mobilefacenet中,代码为:

  1. class FasterNet(nn.Module):
  2. def __init__(self,
  3. in_chans=3,
  4. num_classes=512,
  5. embed_dim=96,
  6. depths=(1, 2, 8, 2),
  7. mlp_ratio=2.,
  8. n_div=4,
  9. patch_size=4,
  10. patch_stride=4,
  11. patch_size2=2, # for subsequent layers
  12. patch_stride2=2,
  13. patch_norm=True,
  14. feature_dim=1280,
  15. drop_path_rate=0.1,
  16. layer_scale_init_value=0,
  17. norm_layer='BN',
  18. act_layer='RELU',
  19. fork_feat=False,
  20. init_cfg=None,
  21. pretrained=None,
  22. pconv_fw_type='split_cat',
  23. **kwargs):
  24. super().__init__()
  25. if norm_layer == 'BN':
  26. norm_layer = nn.BatchNorm2d
  27. else:
  28. raise NotImplementedError
  29. if act_layer == 'GELU':
  30. act_layer = nn.GELU
  31. elif act_layer == 'RELU':
  32. act_layer = partial(nn.ReLU, inplace=True)
  33. else:
  34. raise NotImplementedError
  35. if not fork_feat:
  36. self.num_classes = num_classes
  37. self.num_stages = len(depths)
  38. self.embed_dim = embed_dim
  39. self.patch_norm = patch_norm
  40. self.num_features = int(embed_dim * 2 ** (self.num_stages - 1))
  41. self.mlp_ratio = mlp_ratio
  42. self.depths = depths
  43. # split image into non-overlapping patches
  44. self.patch_embed = PatchEmbed(
  45. patch_size=patch_size,
  46. patch_stride=patch_stride,
  47. in_chans=in_chans,
  48. embed_dim=embed_dim,
  49. norm_layer=norm_layer if self.patch_norm else None
  50. )
  51. # stochastic depth decay rule
  52. dpr = [x.item()
  53. for x in torch.linspace(0, drop_path_rate, sum(depths))]
  54. # build layers
  55. stages_list = []
  56. for i_stage in range(self.num_stages):
  57. stage = BasicStage(dim=int(embed_dim * 2 ** i_stage),
  58. n_div=n_div,
  59. depth=depths[i_stage],
  60. mlp_ratio=self.mlp_ratio,
  61. drop_path=dpr[sum(depths[:i_stage]):sum(depths[:i_stage + 1])],
  62. layer_scale_init_value=layer_scale_init_value,
  63. norm_layer=norm_layer,
  64. act_layer=act_layer,
  65. pconv_fw_type=pconv_fw_type
  66. )
  67. stages_list.append(stage)
  68. # patch merging layer
  69. if i_stage < self.num_stages - 1:
  70. stages_list.append(
  71. PatchMerging(patch_size2=patch_size2,
  72. patch_stride2=patch_stride2,
  73. dim=int(embed_dim * 2 ** i_stage),
  74. norm_layer=norm_layer)
  75. )
  76. self.stages = nn.Sequential(*stages_list)
  77. self.fork_feat = fork_feat
  78. if self.fork_feat:
  79. self.forward = self.forward_det
  80. # add a norm layer for each output
  81. self.out_indices = [0, 2, 4, 6]
  82. for i_emb, i_layer in enumerate(self.out_indices):
  83. if i_emb == 0 and os.environ.get('FORK_LAST3', None):
  84. raise NotImplementedError
  85. else:
  86. layer = norm_layer(int(embed_dim * 2 ** i_emb))
  87. layer_name = f'norm{i_layer}'
  88. self.add_module(layer_name, layer)
  89. else:
  90. self.forward = self.forward_cls
  91. # Classifier head
  92. self.avgpool_pre_head = nn.Sequential(
  93. nn.AdaptiveAvgPool2d(1),
  94. nn.Conv2d(self.num_features, feature_dim, 1, bias=False),
  95. act_layer()
  96. )
  97. self.head = nn.Linear(feature_dim, num_classes) \
  98. if num_classes > 0 else nn.Identity()
  99. self.apply(self.cls_init_weights)
  100. self.init_cfg = copy.deepcopy(init_cfg)
  101. if self.fork_feat and (self.init_cfg is not None or pretrained is not None):
  102. self.init_weights()
  103. def cls_init_weights(self, m):
  104. if isinstance(m, nn.Linear):
  105. trunc_normal_(m.weight, std=.02)
  106. if isinstance(m, nn.Linear) and m.bias is not None:
  107. nn.init.constant_(m.bias, 0)
  108. elif isinstance(m, (nn.Conv1d, nn.Conv2d)):
  109. trunc_normal_(m.weight, std=.02)
  110. if m.bias is not None:
  111. nn.init.constant_(m.bias, 0)
  112. elif isinstance(m, (nn.LayerNorm, nn.GroupNorm)):
  113. nn.init.constant_(m.bias, 0)
  114. nn.init.constant_(m.weight, 1.0)
  115. # init for mmdetection by loading imagenet pre-trained weights
  116. def init_weights(self, pretrained=None):
  117. logger = get_root_logger()
  118. if self.init_cfg is None and pretrained is None:
  119. logger.warn(f'No pre-trained weights for '
  120. f'{self.__class__.__name__}, '
  121. f'training start from scratch')
  122. pass
  123. else:
  124. assert 'checkpoint' in self.init_cfg, f'Only support ' \
  125. f'specify `Pretrained` in ' \
  126. f'`init_cfg` in ' \
  127. f'{self.__class__.__name__} '
  128. if self.init_cfg is not None:
  129. ckpt_path = self.init_cfg['checkpoint']
  130. elif pretrained is not None:
  131. ckpt_path = pretrained
  132. ckpt = _load_checkpoint(
  133. ckpt_path, logger=logger, map_location='cpu')
  134. if 'state_dict' in ckpt:
  135. _state_dict = ckpt['state_dict']
  136. elif 'model' in ckpt:
  137. _state_dict = ckpt['model']
  138. else:
  139. _state_dict = ckpt
  140. state_dict = _state_dict
  141. missing_keys, unexpected_keys = \
  142. self.load_state_dict(state_dict, False)
  143. # show for debug
  144. print('missing_keys: ', missing_keys)
  145. print('unexpected_keys: ', unexpected_keys)
  146. def forward_cls(self, x):
  147. # output only the features of last layer for image classification
  148. x = self.patch_embed(x)
  149. x = self.stages(x)
  150. x = self.avgpool_pre_head(x) # B C 1 1
  151. x = torch.flatten(x, 1)
  152. x = self.head(x)
  153. return x
  154. def forward_det(self, x: Tensor) -> Tensor:
  155. # output the features of four stages for dense prediction
  156. x = self.patch_embed(x)
  157. outs = []
  158. for idx, stage in enumerate(self.stages):
  159. x = stage(x)
  160. if self.fork_feat and idx in self.out_indices:
  161. norm_layer = getattr(self, f'norm{idx}')
  162. x_out = norm_layer(x)
  163. outs.append(x_out)
  164. return outs

3.1.2 更改Backbone为GhostFaceNet

论文 : GhostFaceNets: Lightweight Face Recognition Model From Cheap Operations | IEEE Journals & Magazine | IEEE Xplore

 GhostFaceNet的主要思想是卷积过程中,各个通道的卷积特征图高度相似,因此提出对部分通道进行卷积,将卷积得到的特征图进行线性变换得到剩余通道的特征图,减少特征映射冗余,减少计算量。

GhostFacenets的网络模型 :

其中的G-Bneckv1结构如下:

Ghost Module 的主要结构:

 深度可分离卷积分为逐通道卷积和逐点卷积两部分:

 逐通道卷积的输出通道仅通过一个卷积核。

 逐点卷积的实现方式与普通卷积类似,区别在于逐点卷积的卷积核为1x1。

利用深度可分离卷积实现对部分通道卷积,剩余通道通过逐点卷积生成。

主要的创新点:

1.使用极简轻量化的GhostFaceNet网络作为人脸识别网络的主干。

2. 在主干网络输出特征的最后一层将GAP层更换为GDC(全局深度卷积模块),GAP层平等地对待输出特征映射的每个单元,这与在提取人脸特征向量时,不同类型的单元为理论带来不同数量的判别信息的假设相冲突。,所以在最终输出特征向量时,不采用全局平均池化。

3.将非线性激活函数ReLU改为PReLU,支持负激活,增强学习非线性函数的能力,提高网络性能。

该激活函数可以自适应地学习矫正线性单元的参数,并且能够在增加可忽略的额外计算成本下提高准确率。

 PRelu与LeakyRelu激活函数类似,不同点在于LeakyRelu的激活函数在负值方向的斜率为固定值,而PRelu的斜率为自适应斜率,在方向传播的过程中会更新负值方向的权重。当斜率为0时,则退化为Relu.

代码实现:

  1. import torch
  2. import torch.nn as nn
  3. import torch.nn.functional as F
  4. import math
  5. from torchviz import make_dot
  6. def _make_divisible(v, divisor, min_value=None):
  7. """
  8. This function is taken from the original tf repo.
  9. It ensures that all layers have a channel number that is divisible by 8
  10. It can be seen here:
  11. https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py
  12. """
  13. if min_value is None:
  14. min_value = divisor
  15. new_v = max(min_value, int(v + divisor / 2) // divisor * divisor)
  16. # Make sure that round down does not go down by more than 10%.
  17. if new_v < 0.9 * v:
  18. new_v += divisor
  19. return new_v
  20. def hard_sigmoid(x, inplace: bool = False):
  21. if inplace:
  22. return x.add_(3.).clamp_(0., 6.).div_(6.)
  23. else:
  24. return F.relu6(x + 3.) / 6.
  25. class SqueezeExcite(nn.Module):
  26. def __init__(self, in_chs, se_ratio=0.25, reduced_base_chs=None,
  27. act_layer=nn.PReLU, gate_fn=hard_sigmoid, divisor=4, **_):
  28. super(SqueezeExcite, self).__init__()
  29. self.gate_fn = gate_fn
  30. reduced_chs = _make_divisible((reduced_base_chs or in_chs) * se_ratio, divisor)
  31. self.avg_pool = nn.AdaptiveAvgPool2d(1)
  32. self.conv_reduce = nn.Conv2d(in_chs, reduced_chs, 1, bias=True)
  33. self.act1 = act_layer()
  34. self.conv_expand = nn.Conv2d(reduced_chs, in_chs, 1, bias=True)
  35. def forward(self, x):
  36. x_se = self.avg_pool(x)
  37. x_se = self.conv_reduce(x_se)
  38. x_se = self.act1(x_se)
  39. x_se = self.conv_expand(x_se)
  40. x = x * self.gate_fn(x_se)
  41. return x
  42. class ConvBnAct(nn.Module):
  43. def __init__(self, in_chs, out_chs, kernel_size,
  44. stride=1, act_layer=nn.PReLU):
  45. super(ConvBnAct, self).__init__()
  46. self.conv = nn.Conv2d(in_chs, out_chs, kernel_size, stride, kernel_size//2, bias=False)
  47. self.bn1 = nn.BatchNorm2d(out_chs)
  48. self.act1 = act_layer()
  49. def forward(self, x):
  50. x = self.conv(x)
  51. x = self.bn1(x)
  52. x = self.act1(x)
  53. return x
  54. class ModifiedGDC(nn.Module):
  55. def __init__(self, in_chs, image_size, num_classes, dropout, emb=512): #embedding = 512 from original code
  56. super(ModifiedGDC, self).__init__()
  57. image_kernel_size = 0
  58. self.dropout = dropout
  59. if image_size % 32 == 0:
  60. image_kernel_size = image_size//32
  61. else:
  62. image_kernel_size = (image_size//32) + 1
  63. self.conv_dw = nn.Conv2d(in_chs, in_chs, kernel_size=image_kernel_size, groups=in_chs, bias=False)
  64. self.bn1 = nn.BatchNorm2d(in_chs)
  65. self.conv = nn.Conv2d(in_chs, emb, kernel_size=1, bias=False)
  66. nn.init.xavier_normal_(self.conv.weight.data) #initialize weight
  67. self.bn2 = nn.BatchNorm1d(emb)
  68. self.linear = nn.Linear(emb, num_classes) if num_classes else nn.Identity()
  69. def forward(self, x):
  70. x = self.conv_dw(x)
  71. x = self.bn1(x)
  72. if self.dropout > 0. and self.dropout < 1.:
  73. x = F.dropout(x, p=self.dropout, training=self.training)
  74. x = self.conv(x)
  75. x = x.view(x.size(0), -1) #flatten
  76. x = self.bn2(x)
  77. x = self.linear(x)
  78. return x
  79. class GhostModuleV2(nn.Module):
  80. def __init__(self, inp, oup, kernel_size=1, ratio=2, dw_size=3, stride=1, prelu=True,mode=None,args=None):
  81. super(GhostModuleV2, self).__init__()
  82. self.mode=mode
  83. self.gate_fn=nn.Sigmoid()
  84. if self.mode in ['original']:
  85. self.oup = oup
  86. init_channels = math.ceil(oup / ratio)
  87. new_channels = init_channels*(ratio-1)
  88. self.primary_conv = nn.Sequential(
  89. nn.Conv2d(inp, init_channels, kernel_size, stride, kernel_size//2, bias=False),
  90. nn.BatchNorm2d(init_channels),
  91. nn.PReLU() if prelu else nn.Sequential(),
  92. )
  93. self.cheap_operation = nn.Sequential(
  94. nn.Conv2d(init_channels, new_channels, dw_size, 1, dw_size//2, groups=init_channels, bias=False),
  95. nn.BatchNorm2d(new_channels),
  96. nn.PReLU() if prelu else nn.Sequential(),
  97. )
  98. elif self.mode in ['attn']: #DFC
  99. self.oup = oup
  100. init_channels = math.ceil(oup / ratio)
  101. new_channels = init_channels*(ratio-1)
  102. self.primary_conv = nn.Sequential(
  103. nn.Conv2d(inp, init_channels, kernel_size, stride, kernel_size//2, bias=False),
  104. nn.BatchNorm2d(init_channels),
  105. nn.PReLU() if prelu else nn.Sequential(),
  106. )
  107. self.cheap_operation = nn.Sequential(
  108. nn.Conv2d(init_channels, new_channels, dw_size, 1, dw_size//2, groups=init_channels, bias=False),
  109. nn.BatchNorm2d(new_channels),
  110. nn.PReLU() if prelu else nn.Sequential(),
  111. )
  112. self.short_conv = nn.Sequential(
  113. nn.Conv2d(inp, oup, kernel_size, stride, kernel_size//2, bias=False),
  114. nn.BatchNorm2d(oup),
  115. nn.Conv2d(oup, oup, kernel_size=(1,5), stride=1, padding=(0,2), groups=oup,bias=False),
  116. nn.BatchNorm2d(oup),
  117. nn.Conv2d(oup, oup, kernel_size=(5,1), stride=1, padding=(2,0), groups=oup,bias=False),
  118. nn.BatchNorm2d(oup),
  119. )
  120. def forward(self, x):
  121. if self.mode in ['original']:
  122. x1 = self.primary_conv(x)
  123. x2 = self.cheap_operation(x1)
  124. out = torch.cat([x1,x2], dim=1)
  125. return out[:,:self.oup,:,:]
  126. elif self.mode in ['attn']:
  127. res=self.short_conv(F.avg_pool2d(x,kernel_size=2,stride=2))
  128. x1 = self.primary_conv(x)
  129. x2 = self.cheap_operation(x1)
  130. out = torch.cat([x1,x2], dim=1)
  131. return out[:,:self.oup,:,:]*F.interpolate(self.gate_fn(res),size=(out.shape[-2],out.shape[-1]),mode='nearest')
  132. class GhostBottleneckV2(nn.Module):
  133. def __init__(self, in_chs, mid_chs, out_chs, dw_kernel_size=3,
  134. stride=1, act_layer=nn.PReLU, se_ratio=0.,layer_id=None,args=None):
  135. super(GhostBottleneckV2, self).__init__()
  136. has_se = se_ratio is not None and se_ratio > 0.
  137. self.stride = stride
  138. # Point-wise expansion
  139. if layer_id<=1:
  140. self.ghost1 = GhostModuleV2(in_chs, mid_chs, prelu=True,mode='original',args=args)
  141. else:
  142. self.ghost1 = GhostModuleV2(in_chs, mid_chs, prelu=True,mode='attn',args=args)
  143. # Depth-wise convolution
  144. if self.stride > 1:
  145. self.conv_dw = nn.Conv2d(mid_chs, mid_chs, dw_kernel_size, stride=stride,
  146. padding=(dw_kernel_size-1)//2,groups=mid_chs, bias=False)
  147. self.bn_dw = nn.BatchNorm2d(mid_chs)
  148. # Squeeze-and-excitation
  149. if has_se:
  150. self.se = SqueezeExcite(mid_chs, se_ratio=se_ratio)
  151. else:
  152. self.se = None
  153. self.ghost2 = GhostModuleV2(mid_chs, out_chs, prelu=False,mode='original',args=args)
  154. # shortcut
  155. if (in_chs == out_chs and self.stride == 1):
  156. self.shortcut = nn.Sequential()
  157. else:
  158. self.shortcut = nn.Sequential(
  159. nn.Conv2d(in_chs, in_chs, dw_kernel_size, stride=stride,
  160. padding=(dw_kernel_size-1)//2, groups=in_chs, bias=False),
  161. nn.BatchNorm2d(in_chs),
  162. nn.Conv2d(in_chs, out_chs, 1, stride=1, padding=0, bias=False),
  163. nn.BatchNorm2d(out_chs),
  164. )
  165. def forward(self, x):
  166. residual = x
  167. x = self.ghost1(x)
  168. if self.stride > 1:
  169. x = self.conv_dw(x)
  170. x = self.bn_dw(x)
  171. if self.se is not None:
  172. x = self.se(x)
  173. x = self.ghost2(x)
  174. x += self.shortcut(residual)
  175. return x
  176. class GhostFaceNetV2(nn.Module):
  177. def __init__(self, cfgs, image_size=256, num_classes=1000, width=1.0, dropout=0.2, block=GhostBottleneckV2,
  178. add_pointwise_conv=False, args=None):
  179. super(GhostFaceNetV2, self).__init__()
  180. self.cfgs = cfgs
  181. # building first layer
  182. output_channel = _make_divisible(16 * width, 4)
  183. self.conv_stem = nn.Conv2d(3, output_channel, 3, 2, 1, bias=False)
  184. self.bn1 = nn.BatchNorm2d(output_channel)
  185. self.act1 = nn.PReLU()
  186. input_channel = output_channel
  187. # building inverted residual blocks
  188. stages = []
  189. layer_id=0
  190. for cfg in self.cfgs:
  191. layers = []
  192. for k, exp_size, c, se_ratio, s in cfg:
  193. output_channel = _make_divisible(c * width, 4)
  194. hidden_channel = _make_divisible(exp_size * width, 4)
  195. if block==GhostBottleneckV2:
  196. layers.append(block(input_channel, hidden_channel, output_channel, k, s,
  197. se_ratio=se_ratio,layer_id=layer_id,args=args))
  198. input_channel = output_channel
  199. layer_id+=1
  200. stages.append(nn.Sequential(*layers))
  201. output_channel = _make_divisible(exp_size * width, 4)
  202. stages.append(nn.Sequential(ConvBnAct(input_channel, output_channel, 1)))
  203. self.blocks = nn.Sequential(*stages)
  204. # building last several layers
  205. pointwise_conv = []
  206. if add_pointwise_conv:
  207. pointwise_conv.append(nn.Conv2d(input_channel, output_channel, 1, 1, 0, bias=True))
  208. pointwise_conv.append(nn.BatchNorm2d(output_channel))
  209. pointwise_conv.append(nn.PReLU())
  210. else:
  211. pointwise_conv.append(nn.Sequential())
  212. self.pointwise_conv = nn.Sequential(*pointwise_conv)
  213. self.classifier = ModifiedGDC(output_channel, image_size, num_classes, dropout)
  214. def forward(self, x):
  215. x = self.conv_stem(x)
  216. x = self.bn1(x)
  217. x = self.act1(x)
  218. x = self.blocks(x)
  219. x = self.pointwise_conv(x)
  220. x = self.classifier(x)
  221. return x
  222. def ghostfacenetv2(bn_momentum=0.9, bn_epsilon=1e-5, num_classes=None, **kwargs):
  223. cfgs = [
  224. # k, t, c, SE, s
  225. [[3, 16, 16, 0, 1]],
  226. [[3, 48, 24, 0, 2]],
  227. [[3, 72, 24, 0, 1]],
  228. [[5, 72, 40, 0.25, 2]],
  229. [[5, 120, 40, 0.25, 1]],
  230. [[3, 240, 80, 0, 2]],
  231. [[3, 200, 80, 0, 1],
  232. [3, 184, 80, 0, 1],
  233. [3, 184, 80, 0, 1],
  234. [3, 480, 112, 0.25, 1],
  235. [3, 672, 112, 0.25, 1]
  236. ],
  237. [[5, 672, 160, 0.25, 2]],
  238. [[5, 960, 160, 0, 1],
  239. [5, 960, 160, 0.25, 1],
  240. [5, 960, 160, 0, 1],
  241. [5, 960, 160, 0.25, 1]
  242. ]
  243. ]
  244. GhostFaceNet = GhostFaceNetV2(cfgs,
  245. num_classes=num_classes,
  246. image_size=kwargs['image_size'],
  247. width=kwargs['width'],
  248. dropout=kwargs['dropout'],
  249. args=kwargs['args'])
  250. for module in GhostFaceNet.modules():
  251. if isinstance(module, nn.BatchNorm2d):
  252. module.momentum, module.eps = bn_momentum, bn_epsilon
  253. return GhostFaceNet
  254. if __name__=='__main__':
  255. kwargs = {
  256. 'image_size': 112, # 示例图像大小
  257. 'width': 1.0, # 示例宽度
  258. 'dropout': 0.2, # 示例 dropout
  259. 'args': None # 示例额外参数
  260. }
  261. model = ghostfacenetv2(**kwargs)
  262. x = torch.randn(16,3,112,112)
  263. out = model(x)
  264. print(out.shape)
  265. graph = make_dot(out,params = dict(model.named_parameters()))
  266. graph.view()

 4.网络轻量化方法

4.1 模型剪枝

模型剪枝主要出现在全连接层中,这主要是因为全连接层中每一个节点都与上一个节点相连,冗余程度最高,其中部分神经元对输出结果影响较小,可以选择去除。

 在选择的过程中,将权重绝对值较小的神经元去除,并移除对应的连接,这种操作可以有效减小全连接层的复杂程度,保留对输出结果起主要影响的神经元,但也会造成一定的误差,误差累积后会对网络的准确率造成一定影响。

4.2 网络高效化的模块

ResNet:残差结构主要解决较深的神经网络中出现参数冗余的问题,当网络中部分层在整体结构中发挥作用较小甚至不发挥作用时,这种情况下我们希望网络不对输入X做处理,即直接输出F(x) = x,但令神经网络学习到该参数较为复杂,残差边的出现将主干路的优化条件变为了F(x)=0,降低了网络学习的难度的同时,也使得网络更快的收敛。

EfficientNet:提出复合缩放方法,对网络在不同的网络维度(深度、宽度、分辨率)上进行统一的缩放,以获得更好的性能。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/秋刀鱼在做梦/article/detail/932163
推荐阅读
  

闽ICP备14008679号