您当前的位置: 首页 >  ar

人脸识别0-05:insightFace-损失函数arcface-史上最全

发布时间:2019-08-23 09:56:56 ,浏览量:7

以下链接是个人关于insightFace所有见解,如有错误欢迎大家指出,我会第一时间纠正,如有兴趣可以加微信:17575010159 相互讨论技术。 人脸识别0-00:insightFace目录:https://blog.csdn.net/weixin_43013761/article/details/99646731: 这是本人项目的源码:https://github.com/944284742/1.FaceRecognition 其中script目录下的文件为本人编写,主要用于适应自己的项目,可以查看该目录下的redeme文件。

softmax到arcface

在讲解arcface loss之前,我们必须去看一下softmax家族发展的历史,这里就直接贴出链接了,大家去看即可:

softmax变种或增强1:Large-Margin Softmax Loss for Convolutional Neural Networks

softmax增强: A-SoftMax. SphereFace: Deep Hypersphere Embedding for Face Recognition

论文阅读之Arcface:https://blog.csdn.net/Wuzebiao2016/article/details/81839452

ArcFace算法笔记:https://blog.csdn.net/u014380165/article/details/80645489

人脸识别-arcface损失函数:看了前面的,再看这篇,通俗易解。

自我理解

大家看了上面的之后,应该思路也比较清晰了。在这里我也讲解一下自己的理解。如果我们要了解arcface,那么肯定需要了解一下该算法的发展,演变过程, 首先我们来看看

sofrmax loss

在这里插入图片描述

这是传统的的Softmax公式, W x T x i W_x^Tx_i WxTxi+ b j b_j bj代表的是全链接的输出,通过计算会得到每个类别的概率。而这种方式主要考虑是否能够正确的分类,缺乏类内和类间距的约束。在[A Discriminative Feature Learning Approach for Deep Face Recognition]这篇文章中,作者使用了一个比LeNet更深的网络结构,用Mnist做了一个小实验来证明Softmax学习到的特征与理想状态下的差距: 在这里插入图片描述 实验结果表明,传统的Softmax仍存在着很大的类内距离,也就是说,通过对损失函数增加类内距离的约束,能达到比更新现有网络结构更加事半功倍的效果。于是,[A Discriminative Feature Learning Approach for Deep Face Recognition]的作者提出了Center Loss,并从不同角度对结果的提升做了论证。 下面是直接的理解: 在这里插入图片描述 N是样本的数量,i代表第i个样本,j代表第j个类别,fyi代表着第i个样本所属的类别的分数

fyi是全连接层的输出,代表着每一个类别的分数,

每一个分数即为权重W和特征向量X的内积 在这里插入图片描述 每个样本的softmax值即为: 在这里插入图片描述

L-softmax loss

假设一个2分类问题,x属于类别1,那么原来的softmax肯定是希望: W 1 T x > W 2 T x W^T_1x > W^T_2x W1Tx>W2Tx

也就是属于类别1的概率大于类别2的概率,这个式子和下式是等效的: ∣ ∣ W 1 ∣ ∣ ∣ ∣ x ∣ ∣ c o s ( θ 1 ) > ∣ ∣ W 2 ∣ ∣ ∣ ∣ x ∣ ∣ c o s ( θ 2 ) ||W1|| ||x||cos(θ _1) > ||W2|| ||x||cos(θ _2) ∣∣W1∣∣∣∣x∣∣cos(θ1)>∣∣W2∣∣∣∣x∣∣cos(θ2)

large margin softmax就是将上面不等式替换为: ∣ ∣ W 1 ∣ ∣ ∣ ∣ x ∣ ∣ c o s ( m θ 1 ) > ∣ ∣ W 2 ∣ ∣ ∣ ∣ x ∣ ∣ c o s ( θ 2 ) ( 0 < θ 1 < p i = 3.14 m ) ||W1|| ||x||cos(mθ _1) > ||W2|| ||x||cos(θ _2)(0<θ _1<\frac{pi=3.14}{m}) ∣∣W1∣∣∣∣x∣∣cos(mθ1)>∣∣W2∣∣∣∣x∣∣cos(θ2)(0<θ1<mpi=3.14)

m是正整数,cos函数在0到π范围又是单调递减的,所以cos(mx)要小于cos(x)。通过这种方式定义损失会逼得模型学到类间距离更大的,类内距离更小的特征。 在这里插入图片描述 从几何的角度看两种损失的差别: 在这里插入图片描述 设置为cos(mx)后,使得学习到的W参数更加的扁平,可以加大样本的类间距离。

Large-Margin Softmax的实验效果:

在这里插入图片描述

Center Loss

在这里插入图片描述 Center Loss的整体思想是希望一个batch中每个样本的feature你feature的中心的距离的平方和要越小越好,也就是类内距离越小越好。作者提出,最终的损失函数包含softmax loss和center loss,用参数λ来控制二者的比重,如下面公式所示:

在这里插入图片描述 因而,加入了Softmax Loss对正确类别分类的考虑以及Center Loss对类内距离紧凑的考虑,总的损失函数在分类结果上有很好的表现力。以下是作者继上个实验后使用新的损失函数并调节不同的参数λ \lambdaλ得到的实验结果,可以看到,加入了Center Loss后增加了对类内距离的约束,使得同个类直接的样本的类内特征距离变得紧凑。 在这里插入图片描述

A-softmax loss

A-softmax loss简单讲就是在large margin softmax loss的基础上添加了两个限制条件||W||=1和b=0,使得预测仅取决于W和x之间的角度。 softmax的计算:在这里插入图片描述 W i T x + b i W^T_ix + b_i WiTx+bi可以改写成 ∣ ∣ W 1 ∣ ∣ ∣ ∣ x ∣ ∣ c o s ( θ i ) + b i ||W1|| ||x||cos(θ _i)+b_i ∣∣W1∣∣∣∣x∣∣cos(θi)+bi 若引入两个限制条件, ∣ ∣ W 1 ∣ ∣ = ∣ ∣ W 2 ∣ ∣ = 1 ||W1|| = ||W2|| = 1 ∣∣W1∣∣=∣∣W2∣∣=1 以及 b 1 = b 2 = 0 b_1=b_2=0 b1=b2=0 decision boundary变为: ∣ ∣ x ∣ ∣ ( c o s θ 1 − c o s θ 2 ) = 0 ||x||(cosθ_1 - cosθ_2) = 0 ∣∣x∣∣(cosθ1−cosθ2)=0,只取决于角度了 则损失函数变为: 在这里插入图片描述 在这两个限制条件的基础上,作者又添加了和large margin softmax loss一样的角度参数,使得公式变为: 在这里插入图片描述

AM-softmax

在A-softmax的基础上,修改Cos(mθ)为一个新函数: 在这里插入图片描述 与ASoftmax中定的的类似,可以达到减小对应标签项的概率,增大损失的效果,因此对同一类的聚合更有帮助 然后根据Normface,对f进行归一化,乘上缩放系数s,最终的损失函数变为: 在这里插入图片描述 这样做的好处在于A-Softmax的倍角计算是要通过倍角公式,反向传播时不方便求导,而只减m反向传播时导数不用变化

Asoftmax是用m乘以θ,而AMSoftmax是用cosθ减去m,这是两者的最大不同之处:一个是角度距离,一个是余弦距离。

之所以选择cosθ-m而不是cos(θ-m),这是因为我们从网络中得到的是W和f的内积,如果要优化cos(θ-m)那么会涉及到arccos操作,计算量过大。

arcface

在这里插入图片描述 分类正确label的值为 在这里插入图片描述, cos函数在(0,1)内是单调递减少的,加上m,会使该值变得更小,从而loss会变得很大。这样修改的原因:角度距离比余弦距离在对角度的影响更加直接

源码分析

现在已经对softmax到arcface的发展史,有了大致的了解。下面看代码recognition/train.py(注意:fc7输出的就是角度θ):

def get_symbol(args): # 获得一个特征向量 embedding = eval(config.net_name).get_symbol() # 定义一个标签的占位符,用来存放标签 all_label = mx.symbol.Variable('softmax_label') gt_label = all_label
    is_softmax = True # 如果损失函数为softmax if config.loss_name == 'softmax': # 定义一个全连接层的权重,使用全局池化代替全链接层 _weight = mx.symbol.Variable("fc7_weight", shape=(config.num_classes, config.emb_size), lr_mult=config.fc7_lr_mult, wd_mult=config.fc7_wd_mult, init=mx.init.Normal(0.01)) # 如果不设置bias,使用全局池化代替全链接层,得到每个id的概率值 if config.fc7_no_bias: fc7 = mx.sym.FullyConnected(data=embedding, weight=_weight, no_bias=True, num_hidden=config.num_classes, name='fc7') # 如果设置_bias,使用全局池化代替全链接层,得到每个id的cos_t else: _bias = mx.symbol.Variable('fc7_bias', lr_mult=2.0, wd_mult=0.0) fc7 = mx.sym.FullyConnected(data=embedding, weight=_weight, bias=_bias, num_hidden=config.num_classes, name='fc7') # 如果损失函数为margin_softmax elif config.loss_name == 'margin_softmax': # 定义一个全连接层的权重,使用全局池化代替全链接层 _weight = mx.symbol.Variable("fc7_weight", shape=(config.num_classes, config.emb_size), lr_mult=config.fc7_lr_mult, wd_mult=config.fc7_wd_mult, init=mx.init.Normal(0.01)) # 获得loss中m的缩放系数 s = config.loss_s # 先进行L2正则化,然后进行全链接 _weight = mx.symbol.L2Normalization(_weight, mode='instance') nembedding = mx.symbol.L2Normalization(embedding, mode='instance', name='fc1n') * s #使用全局池化代替全链接层,得到每个id的角度*64 fc7 = mx.sym.FullyConnected(data=nembedding, weight=_weight, no_bias=True, num_hidden=config.num_classes, name='fc7') in_shape,out_shape,uax_shape = fc7.infer_shape(data = (2,3,112,112)) print('fc7',out_shape) # 其存在m1,m2,m3是为了把算法整合在一起, # arcface cosface combined if config.loss_m1 != 1.0 or config.loss_m2 != 0.0 or config.loss_m3 != 0.0: # cosface loss if config.loss_m1 == 1.0 and config.loss_m2 == 0.0: s_m = s * config.loss_m3
                gt_one_hot = mx.sym.one_hot(gt_label, depth=config.num_classes, on_value=s_m, off_value=0.0) fc7 = fc7 - gt_one_hot # arcface combined else: # fc7每一行找出gt_label对应的值,即 角度*s zy = mx.sym.pick(fc7, gt_label, axis=1) in_shape,out_shape,uax_shape = zy.infer_shape(data = (2,3,112,112),softmax_label = (2,)) print('zy', out_shape) # 进行复原,前面乘以了s,cos_t为-1到1之间 cos_t = zy / s # t为0-3.14之间 # 该arccos是为了让后续的cos单调递增 t = mx.sym.arccos(cos_t) # m1  sphereface if config.loss_m1 != 1.0: t = t * config.loss_m1 # arcface或者combined if config.loss_m2 > 0.0: t = t + config.loss_m2 #  t为0-3.14之间,单调递增 body = mx.sym.cos(t) # combined 或者 arcface if config.loss_m3 > 0.0: body = body - config.loss_m3

                new_zy = body * s # 得到差值 diff = new_zy - zy # 扩展一个维度 diff = mx.sym.expand_dims(diff, 1) # 把标签转化为one_hot编码 gt_one_hot = mx.sym.one_hot(gt_label, depth=config.num_classes, on_value=1.0, off_value=0.0) # 进行更新 body = mx.sym.broadcast_mul(gt_one_hot, diff) fc7 = fc7 + body # 如果损失函数为triplet elif config.loss_name.find('triplet') >= 0: is_softmax = False nembedding = mx.symbol.L2Normalization(embedding, mode='instance', name='fc1n') anchor = mx.symbol.slice_axis(nembedding, axis=0, begin=0, end=args.per_batch_size // 3) positive = mx.symbol.slice_axis(nembedding, axis=0, begin=args.per_batch_size // 3, end=2 * args.per_batch_size // 3) negative = mx.symbol.slice_axis(nembedding, axis=0, begin=2 * args.per_batch_size // 3, end=args.per_batch_size) if config.loss_name == 'triplet': ap = anchor - positive
            an = anchor - negative
            ap = ap * ap
            an = an * an
            ap = mx.symbol.sum(ap, axis=1, keepdims=1) # (T,1) an = mx.symbol.sum(an, axis=1, keepdims=1) # (T,1) triplet_loss = mx.symbol.Activation(data=(ap - an + config.triplet_alpha), act_type='relu') triplet_loss = mx.symbol.mean(triplet_loss) else: ap = anchor * positive
            an = anchor * negative
            ap = mx.symbol.sum(ap, axis=1, keepdims=1) # (T,1) an = mx.symbol.sum(an, axis=1, keepdims=1) # (T,1) ap = mx.sym.arccos(ap) an = mx.sym.arccos(an) triplet_loss = mx.symbol.Activation(data=(ap - an + config.triplet_alpha), act_type='relu') triplet_loss = mx.symbol.mean(triplet_loss) triplet_loss = mx.symbol.MakeLoss(triplet_loss) out_list = [mx.symbol.BlockGrad(embedding)] # 如果使用了softmax if is_softmax: softmax = mx.symbol.SoftmaxOutput(data=fc7, label=gt_label, name='softmax', normalization='valid') out_list.append(softmax) if config.ce_loss: # ce_loss = mx.symbol.softmax_cross_entropy(data=fc7, label = gt_label, name='ce_loss')/args.per_batch_size body = mx.symbol.SoftmaxActivation(data=fc7) body = mx.symbol.log(body) _label = mx.sym.one_hot(gt_label, depth=config.num_classes, on_value=-1.0, off_value=0.0) body = body * _label
            ce_loss = mx.symbol.sum(body) / args.per_batch_size
            out_list.append(mx.symbol.BlockGrad(ce_loss)) # 如果是triplet else: out_list.append(mx.sym.BlockGrad(gt_label)) out_list.append(triplet_loss) # 聚集所有的符号 out = mx.symbol.Group(out_list) return out

好了,这次就分析到此为止了

关注
打赏
1688896170
查看更多评论

暂无认证

  • 7浏览

    0关注

    115984博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文
立即登录/注册

微信扫码登录

0.0910s