目录:
- center loss理论
- 理论补充
- 代码分析
- Center Loss的缺点
本文理论部分参考一篇很好的文章,加上自己的见解。自己加的内容用【】括起来。另外优化了公式格式。
【 在人脸识别中,因为我们不可能实现收集所有人的人脸图像,因此在使用中总会遇到不在训练集中的人脸,这种情况下就要求CNN提取的deep features不仅是可分的,而且要具有较高的判别度。 】
最近几年网络效果的提升除了改变网络结构外,还有一群人在研究损失层的改进,这篇博文要介绍的就是较为新颖的center loss。center loss来自ECCV2016的一篇论文:A Discriminative
论文连接: http://ydwen.github.io/papers/WenECCV16.pdf
代码链接:https://github.com/pangyupo/mxnet_center_loss
1.center loss理论
对于常见的图像分类问题,我们常常用softmax loss来求损失,关于softmax loss你可以参考这篇博文:『损失函数』softmax loss。如果你的损失采用softmax loss,那么最后各个类别学出来的特征分布大概如下Fig2。这个图是以MNIST数据集做的实验,一共10个类别,用不同的颜色表示。从Fig2可以看出不管是训练数据集还是测试数据集,都能看出比较清晰的类别界限。
如果你是采用softmax loss【区分类间距,如人脸识别中的轮廓区分】加上本文提出的center loss【区分类内距,如人脸识别中的细节区分】的损失,那么最后各个类别的特征分布大概如下图Fig3。和Fig2相比,类间距离变大了,类内距离减少了(主要变化在于类内距离:intra-class),这就是直观的结果。
【center loss辅助softmax loss使用。优化时,两个Loss函数需同时优化。】
接下来详细介绍center loss。下面公式1中
函数的输入就是softmax的结果(是概率),而
表示的是softmax loss的结果(是损失)。
是全连接层的输出,因此
的输入就表示
属于类别
的概率。
那么center loss到底是什么呢?先看看center loss的公式 。 表示第 个类别的特征中心【标签中心; 表示样本i所对应的类别的所有样本特征的平均特征,或者说同类别样本特征的中心点 】, 表示全连接层之前的特征【 表示属于第 个类的样本的特征 】。后面会讲到实际使用的时候, 表示mini-batch的大小【样本数】。因此这个公式就是希望一个batch中的每个样本的feature离feature 的中心的距离的平方和要越小越好,也就是类内距离要越小越好。这就是center loss【 随着提取的特征变化,计算出的特征中心也会变化,反向传播时还会对计算出的中心进行修正,因为CNN提取的特征不好会导致特征中心不能代表该类。 】。
【Center loss 有一个难点,如何计算 。】 通过计算同一类别所有样本的特征,然后求平均值,这种方法是不切实际的,因为我们的训练样本非常庞大。作者另辟蹊径,使用mini-batch中的每个类别的平均特征近似不同类别所有样本的平均特征。这有点像BN中求feature map的均值和方差的思想。】
关于 的梯度和 的更新公式如下:
这个公式里面有个条件表达式如下式,这里当condition满足的时候,下面这个式子等于1,当不满足的时候,下面这个式子等于0。
因此,上面关于 的更新的公式中,当 (表示 类别)和 的类别 不一样的时候, 是不需要更新的,只有当 和 一样才需要更新。
作者文中用的损失 的包含softmax loss和center loss,用参数 控制二者的比重,【先训练好softmax loss,再加上center loss得到圆圈形分布,如Fig3所示;当同时训练两个损失函数时,得到了种类分布可能是随机分布的。无论怎样,只要类间分开就可以了】,如下式所示。这里的 表示mini-batch的包含的样本数量, 表示类别数。
具体的算法描述可以看下面的Algorithm1:
【
(1)计算softmax loss与center loss
(2)反向传播,求梯度
(3)更新权重W
(4)更新特征中心
(5)更新卷积层的初始化参数
】
原文链接: https://blog.csdn.net/u014380165/article/details/76946339
2.理论补充:
1.链接: https://www.cnblogs.com/carlber/p/10811396.html
- 中心损失它仅仅用来减少类内(比如说同一表情)的差异,而不能有效增大类间(比如说不同表情)的差异性
- softmax loss + center loss 能把同一表情的样本之间的距离拉近一些,使其相似性变大,尽量的往样本中心靠拢,但没有把不同表情之间的样本距离拉大。
- 定义如下:
代表求导约分;
代表下降类内部损失。
类中心c:
每一个样本的特征需要通过一个好的网络到达特征层获得,这样计算完后所有样本的特征的平均值为类中心c,而好的网络需要是在有类中心加入的情况下才能得到…
**没法直接获得c,所以将其放到网络里自己生成,在每一个batch里更新center.即随机初始化center,而后每一个每个batch里计算当前数据与center的距离(在每个batch size中个更新c),而后将这个梯度形式的距离加到center上.类似于参数修正.同样的类似于梯度下降法,这里再增加一个scale度量a,使得center不会抖动.**
【
- a经验值0.5。a较大时,来不及抖动,抖动小。
- 在训练时,softmax loss和center loss使用两个优化器,并且设置a参数。一起训练的时候不能给0.5。
- a越大,分的越开;越小,分的越不好。
】
2.链接:https://blog.csdn.net/wxb1553725576/article/details/80602786
通常在用CNN做人脸识别等分类问题时,我们通常采用softmax交叉熵作为损失函数,在close-set测试中模型性能良好,但在遇到unseen数据情况下,模型性能会急剧下降。在做分类时,模型的最后一层通常是全连接(很难不用全连接而使得最后一层的维度刚好和类别数相等),可以将最后一层看做线性分类器。一个直观的感觉是:如果模型学到的特征判别度更高,那么再遇到unseen数据时,泛化性能会比较好。为了使得模型学到的特征判别度更高,论文提出了一种新的辅助损失函数,之说以说是辅助损失函数是因为新提出的损失函数需要结合softmax交叉熵一起使用,而非替代后者。
在结合使用这两种损失函数时,可以认为softmax交叉熵负责增加inter-class距离,center-loss负责减小intra-class距离,这样学习到的特征判别度会更高。
虽然新提出的方法取得了良好的结果,但是也有一些不足之处,最麻烦的地方在于如何选择训练样本对。在论文中,作者也提到了,选取合适的样本对对于模型的性能至关重要,论文中采用的方法是每次选择比较难以分类的样本对重新训练,类似于hard-mining。同时,合适的训练样本还可以加快收敛速度。
3.链接: https://blog.csdn.net/LeeWanzhi/article/details/80301480
-
center loss 有效增强CNN对学到的深度特征的辨别力。
-
center loss为每一个类学习一个特征中心,让属于这个类的样本特征靠近特征中心。 训练时,同时更新特征中心并最小化深度特征与所属类特征中心的距离。
-
softmax 可以让不同类的深度特征分开。center loss可以将同一类的特征吸引到类中心。 这样,不仅可以扩大不同类的特征区别,还可以减小同一类的特征的区别。
-
理论上特征中心应该将所有的训练集考虑,计算特征中心。但每一次迭代都这样做不现实。
为了解决这个问题,我们: -
计算mini-batch的特征中心
-
为了避免由错误标签引起的巨大扰动,设置一个权重来控制特征中心的学习率。
-
lambda用于平衡两个损失,lambda的值对特征的辨别很重要 。
-
联合监督的必要性
-
如果只选择softmax loss,将会有很大的类内变化
-
如果只选择center loss,所有类的特征中心都会非常小,接近0,导致类间的区分就不大
4.链接: https://blog.csdn.net/qq_24548569/article/details/89676753
在深度学习里,深度学习网络通常被看作是特征提取器(从第1层网络层到最后一层隐藏层),提取出来的特征x随后经过线性变换(
)得到类别分数,通过softmax层计算损失。通常,我们没有对提取出来的特征x提出太大的约束,只要它能被最后一层分类器正确分类就可以了。特征x与其类别要有什么关系,需要特征x具有什么约束,这些都没有被明确地要求或规定。Softmax损失要求特征x能够被最后一层分类器正确分类,这个对特征x的约束不是很强。当给网络传入一些对抗样本(容易被网络误识别的样本)时,网络难以分辨出这些样本属于哪类。如果从提取出来的特征来说,是因为网络从对抗样本提取出来的特征处于最后一层分类器的分类边界,或者说,网络提取出来的特征没有很强的区分度。
Softmax损失能够要求特征具有类间的可分离性,但不能约束特征的类内紧凑性。
Center loss 添加的约束是,特征与同类别的平均特征的距离要足够小,这要求同类特征要接近它们的中心点。
Center loss 有一个难点,如何计算 。 通过计算同一类别所有样本的特征,然后求平均值,这种方法是不切实际的,因为我们的训练样本非常庞大。作者另辟蹊径,使用mini-batch中的每个类别的平均特征近似不同类别所有样本的平均特征。这有点像BN中求feature map的均值和方差的思想。在梯度下降的每一次迭代过程中, 的更新向量是:
其中 是指示函数,当 是类别时,函数返回1,否则返回0。分母的1是防止mini-batch中没有类别j的样本而导致分母为0。论文中设置了一个 的更新速率参数α,控制 的更新速度。
训练的总损失函数是:
论文结论:λ=0.003和α=0.5对Face verification任务效果最好。
5.原论文网络结构
6.数据特征提取
网络分析:
- X_0–>1–>2–>3–>4(经过softmax输出10分类结果)
- 输入–>784–512–>128(二维平面上有2个特征维度)–>10
- 100个批次输出特征结构:(100,2)
- (100,2)(2,10)=(100,10)softmax 要用
- (100,2)center 要用
- fc5(center,(100,2))+label(softmax)–>center loss
- fc5(center,(100,2))+fc6(2,10)–>softmax loss
- **(100,784)–>(782,512)–(512,128)–>(128,2)【输出:(100,2)。100个点,每个点两个维度,作为中心点。】–>(2,10)【输出:100,10。10个类别】**和 做损失。
- (100,2)中的2是为了在二维展示。在做人脸分类,特征维度越高(至少128个维度),效果越好。
3.代码分析
(1).例子:
center的计算公式如下:
- 计算过程为:
- 对数据与类中心做差的平方并开方,记做:A;
- 对A做和并平均。
- 由分析上述公式可知,上述计算过程所需数据为:1)类中心数据;2)每个种类的数量。接下来使用模拟数据计算centerloss:
1)类中心数据的计算过程:
import torch
def center_loss():
#5个数据、5个标签、2个中心。
data=torch.tenser([1,2],[4,5],[6,4],[7,9],[5,8],dtype=torch.float32)
label=torch.tensor([0,0,1,0,1],dtype=torch.float32)
center=torch.tensor([1,1],[2,2],dtype=torch.float32)
#类中心数据(曾广)
center_exp=center.index_select(dim=0,index=label.long())
print(center_exp)
- 每个种类的数量:
import torch
def center_loss():
#5个数据、5个标签、2个中心。
data=torch.tenser([1,2],[4,5],[6,4],[7,9],[5,8],dtype=torch.float32)
label=torch.tensor([0,0,1,0,1],dtype=torch.float32)
center=torch.tensor([1,1],[2,2],dtype=torch.float32)
#类中心数据(曾广)
center_exp=center.index_select(dim=0,index=label.long())
print(center_exp)
#计算每个种类的数量
count=torch.histc(label,bins=int(max(label).item()+1),min=int(min(label).item()),max=int(max(label).item()))
print(count)
#曾广每个类别的数据
count_exp=count.index_select(dim=0,index=label.long())
print(count_exp)
- 最终代码:
import torch
def center_loss():
data = torch.tensor([[3, 4], [5, 6], [7, 8], [9, 8], [6, 5]], dtype=torch.float32)#5个数据点
label = torch.tensor([0, 0, 1, 0, 1], dtype=torch.float32)#生成5个对应的标签
center = torch.tensor([[1, 1], [2, 2]], dtype=torch.float32)#2个中心点
print(data,label,center)
"""中心点变形"""
#[[1, 1], [2, 2]]-->[[1., 1.],[1., 1.],[2., 2.],[1., 1.],[2., 2.]]
#根据label找对应的center
print(label.long())
center_exp = center.index_select(dim=0, index=label.long())
print(center_exp)
"""统计"""
# histc:统计label中每个不重复元素出现的次数。
#0出现3次;
#1出现2次。
print(max(label).item())
count = torch.histc(label, bins=int(max(label).item() + 1), min=int(min(label).item()), max=int(max(label).item()))
print(count)
"""变形"""
#3,2-->[3., 3., 2., 3., 2.]
count_exp = count.index_select(dim=0, index=label.long())
print(count_exp)
"""center loss"""
loss = torch.mean(torch.div(torch.sum(torch.sqrt((torch.pow(data - center_exp, 2))), dim=1), count_exp))
"""center loss分解理解"""
#数据-中心
# print(data - center_exp)
#(数据-中心)的值做平方
# print(torch.pow(data - center_exp, 2))
#对平方值进行开方
# print(torch.sqrt((torch.pow(data - center_exp, 2))))
#在第一轴求和
# print(torch.sum(torch.sqrt((torch.pow(data - center_exp, 2))), dim=1))
#做除法
# print(torch.div(torch.sum(torch.sqrt((torch.pow(data - center_exp, 2))), dim=1), count_exp))
#加起来求平均
# print(loss)
return loss
center_loss()
(2).练习index_select:
import torch
input_tensor = torch.tensor([1,2,3,4,5])
print(input_tensor.index_select(0,torch.tensor([0,2,4])))#[1,3,5]
input_tensor = torch.tensor([[1,2,3,4,5],[6,7,8,9,10]])
print(input_tensor.index_select(0,torch.tensor([1])))#[[6,7,8,9,10]]
print(input_tensor.index_select(1,torch.tensor([1])))#[[2][7]]
import torch
import torch.nn as nn
def center_loss(feature,label,lambdas):
#center随机指定
label = label.unsqueeze(0)
center = nn.Parameter(torch.randn(label.shape[1], feature.shape[1]),requires_grad=True).cuda()#5个标签,每个标签里有两个值
print(center.shape)
print(label.shape[1], feature.shape[1])
label=label.squeeze()
center_exp = center.index_select(dim=0, index=label.long())
print(center_exp)
count = torch.histc(label, bins=int(max(label).item() + 1), min=0, max=int(max(label).item()))
# print(count)
count_exp = count.index_select(dim=0, index=label.long())
print(count_exp)
# center loss
#lambdas:做人脸时给小;自己做显示时给大
loss = lambdas/2*torch.mean(torch.div(torch.sum(torch.sqrt(torch.pow(feature - center_exp, 2)),dim=1),count_exp))
print(loss)
return loss
data = torch.tensor([[3, 4], [5, 6], [7, 8], [9, 8], [6, 5]], dtype=torch.float32).cuda()#加了cuda
label = torch.tensor([0, 0, 1, 0, 1], dtype=torch.float32).cuda()
center_loss(data,label,2)#二维中显示:2;三维中显示:3。128维:不规则形状。
(3)实践
特征数据来自模型倒数第二层。
- 网络模型
import torch
import torch.nn as nn
import matplotlib.pyplot as plt
class Net(nn.Module):
def __init__(self):
super(Net,self).__init__()
self.conv_layer = nn.Sequential(
nn.Conv2d(1,32,5,1,2),#28*28#卷积核为5,是因为MNIST数据集中数字图片上特征较少。
nn.PReLU(),
nn.Conv2d(32,32,5,1,2),#28*28
nn.PReLU(),
nn.MaxPool2d(2,2),#14*14#缩小一半
nn.Conv2d(32,64,5,1,2),#14*14
nn.PReLU(),
nn.Conv2d(64,64,5,1,2),#14*14
nn.PReLU(),
nn.MaxPool2d(2, 2),#7*7
nn.Conv2d(64,128,5,1,2),#7*7
nn.PReLU(),
nn.Conv2d(128,128,5,1,2),#7*7
nn.PReLU(),
nn.MaxPool2d(2, 2)#3*3
)
self.feature = nn.Linear(128*3*3,2)
self.output = nn.Linear(2,10)
def forward(self,x):
y_conv = self.conv_layer(x)
y_conv = torch.reshape(y_conv,[-1,128*3*3])
y_feature = self.feature(y_conv)
y_output = torch.log_softmax(self.output(y_feature),dim=1)#softmax激活+交叉熵损失函数;log_softmax激活+NLLLOSS
return y_feature,y_output
def visualize(self,feat, labels, epoch):
plt.ion()
# 给了10颜色
color = ['#ff0000', '#ffff00', '#00ff00', '#00ffff', '#0000ff',
'#ff00ff', '#990000', '#999900', '#009900', '#009999']
plt.clf()
# 在循环中画出
for i in range(10):
plt.plot(feat[labels == i, 0], feat[labels == i, 1], '.', c=color[i])
plt.legend(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], loc='upper right')
plt.xlim(xmin=-5,xmax=5)
plt.ylim(ymin=-5,ymax=5)
plt.title("epoch=%d" % epoch)
plt.savefig('./images/epoch=%d.jpg' % epoch)
plt.draw()
plt.pause(0.001)
- 训练
- CrossEntropyLoss()=torch.log(torch.softmax(None))+nn.NLLLoss()
- CrossEntropyLoss()=log_softmax() + NLLLoss()
- nn.CrossEntropyLoss()是nn.logSoftmax()和nn.NLLLoss()的整合
- softmax激活+交叉熵损失函数==log_softmax激活+NLLLOSS损失函数
- 实际中,使用两个损失函数。Adam+交叉熵;SGD+center loss
- 只使用Adam()训练结果:
- 只是用softmax训练结果:
- 优化器:
第一种思想:
推荐。
先用Adam()优化十几轮次,将类型快速分开;再用SGD()优化训练一轮就可以出来。
实际中,使用两个损失函数。
Adam+交叉熵;SGD+center loss
optimzer = torch.optim.Adam(net.parameters())
optimzer = torch.optim.SGD(net.parameters(),lr=1e-4, momentum=0.9)
第二种思想:
(1) 使用简单
如果不怕浪费时间,直接使用SGD训练,给小的lr.
optimzer = torch.optim.SGD(net.parameters(),lr=1e-5)
(2)复杂使用
刚开始下降慢时,加上动量(momentum)(会抖动),最后去掉;加上L2正则化(weight_decay)。
optimzer = torch.optim.SGD(net.parameters(),lr=1e-3, momentum=0.9, weight_decay=0.0005)
- 学习率
对不同的优化器使用学习率衰减。
heduler = lr_scheduler.StepLR(optimzer, 20, gamma=0.9)#学习率、衰减到原来的0.9倍
- 代码:
先用Adam()优化十几轮次,将类型快速分开;再用SGD()优化训练一轮就可以出来。
import torch
import torch.nn as nn
import torch.utils.data as data
import torchvision
import torchvision.transforms as transforms
import torch.optim.lr_scheduler as lr_scheduler
from my_centerloss.Net_Model import Net
from my_centerloss.loss import center_loss
import os
import numpy as np
if __name__ == '__main__':
save_path = "models/net_center3.pth"
train_data = torchvision.datasets.MNIST(root="./MNIST", download=True, train=True,
transform=transforms.Compose([transforms.ToTensor(),transforms.Normalize(mean=[0.5,],std=[0.5,])]))
train_loader = data.DataLoader(dataset=train_data, shuffle=True, batch_size=100,num_workers=4)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
net = Net().to(device)
if os.path.exists(save_path):
net.load_state_dict(torch.load(save_path))
else:
print("NO Param")
'CrossEntropyLoss()=torch.log(torch.softmax(None))+nn.NLLLoss()'
'CrossEntropyLoss()=log_softmax() + NLLLoss() '
'nn.CrossEntropyLoss()是nn.logSoftmax()和nn.NLLLoss()的整合'
#损失函数
lossfn_cls = nn.CrossEntropyLoss()
# lossfn_cls = nn.NLLLoss()
"""优化器"""
# 优化器(第一种思想)推荐
# 实际中,使用两个损失函数。Adam+交叉熵;SGD+center loss
# optimzer = torch.optim.Adam(net.parameters())
optimzer = torch.optim.SGD(net.parameters(),lr=1e-4, momentum=0.9)
# 优化器(第二种思想)
# (1)简单使用
# 不怕浪费时间,直接使用SGD训练,给小的lr.
# optimzer = torch.optim.SGD(net.parameters(),lr=1e-5)
# (2)复杂使用
# 刚开始下降慢时,加上动量(momentum)(会抖动),最后去掉;加上L2正则化(weight_decay)。
# optimzer = torch.optim.SGD(net.parameters(),lr=1e-3, momentum=0.9, weight_decay=0.0005)
"""学习率"""
# 对不同的优化器使用学习率
# scheduler = lr_scheduler.StepLR(optimzer, 20, gamma=0.9)
epoch = 0
while True:
feat_loader = []
label_loader = []
#导入数据
for i, (x, y) in enumerate(train_loader):
x = x.to(device)
y = y.to(device)
feature,output = net.forward(x)#输出两个值:特征(N,2)、分类结果(N,10)
# print(feature.shape)#[N,2]
# print(output.shape)#[N,10]
# center = nn.Parameter(torch.randn(output.shape[1], feature.shape[1]))
# print(center.shape)#[10,2]
loss_cls = lossfn_cls(output, y)
y = y.float()
# loss_center = center_loss(feature, y,center)
loss_center = center_loss(feature,y,2)#2:lanmda
loss = loss_cls+loss_center#可加权重
optimzer.zero_grad()
loss.backward()
optimzer.step()
# feature.shape=[100,2]
#y.shape=[100]
feat_loader.append(feature)
label_loader.append(y)
if i % 10 == 0:
# feat = torch.cat(feat_loader, 0)
# labels = torch.cat(label_loader, 0)
# print(feat.shape,labels.shape)
# net.visualize(feat.data.cpu().numpy(), labels.data.cpu().numpy(), epoch)
print("epoch:",epoch,"i:",i,"total:",loss.item(),"softmax_loss:",loss_cls.item(),"center_loss:",loss_center.item())
feat = torch.cat(feat_loader, 0)
labels = torch.cat(label_loader, 0)
'---------------'
# print(np.shape(feat_loader))#feat_loader.shape=[600,]=[[100,2],[100,2],...]600
# print(feat.shape)#feat.shape=[60000,2]
![epoch=0](E:\2020-02-13_centerloss\my_centerloss\images\epoch=0.jpg) # print(np.shape(label_loader))#feat_loader.shape=[600,]=[[100],[100],...]600
# print(labels.shape)#feat.shape=[60000]
'-------------------'
net.visualize(feat.data.cpu().numpy(), labels.data.cpu().numpy(), epoch)
epoch+=1
torch.save(net.state_dict(), save_path)
if epoch==30:
break
- 结果:
4.Center Loss的缺点
(1)类别较多时,对硬件的要求较高
- 计算量上:每个类别都有一个中心,当类别高达1万中,对内存、硬件的消耗,硬件要求非常高。
- 样本量上:实际人脸识别中,50万个类别需要500万的样本(每个类10张)。
(2)L2范数的离群值点对loss的影像较大
- 当某个类别的离群点较远时,导致该类别损失难以下降。
- 由于损失计算的是均值(所有点的共同努力结果),当损失下降了,离群点还是回不到中心。
(3)类内距太大
- 理想状态下,希望每个类的所有点重合在一个点上。
- 当类别有10万个时,由于类内距太大,导致一张图上放不下显示结果。
(4)只适合同类样本差异不大的数据
- MNIST数据集中,同一个数字的书写差距不大。如:很多种样式的0的书写方式。
- 当同类之间样本差距较大时,不能使用center loss。如:不同种类的狗、不同种类的大象、等做够、象等类别分类。