做者|Samrat Saha
编译|VK
来源|Towards Datas Sciencepython
Supervised Contrastive Learning这篇论文在有监督学习、交叉熵损失与有监督对比损失之间进行了大量的讨论,以更好地实现图像表示和分类任务。让咱们深刻了解一下这篇论文的内容。git
论文指出能够在image net数据集有1%的改进。github
就架构而言,它是一个很是简单的网络resnet 50,具备128维的头部。若是你想,你也能够多加几层。网络
Code self.encoder = resnet50() self.head = nn.Linear(2048, 128) def forward(self, x): feat = self.encoder(x) #须要对128向量进行标准化 feat = F.normalize(self.head(feat), dim=1) return feat
如图所示,训练分两个阶段进行。架构
使用对比损失的训练集(两种变化)dom
冻结参数,而后使用softmax损失在线性层上学习分类器。(来自论文的作法)机器学习
以上是不言自明的。函数
本文的主要内容是了解自监督的对比损失和监督的对比损失。学习
从上面的SCL(监督对比损失)图中能够看出,猫与任何非猫进行对比。这意味着全部的猫都属于同一个标签,都是正数对,任何非猫都是负的。这与三元组数据以及triplet loss的工做原理很是类似。google
每一张猫的图片都会被放大,因此即便是从一张猫的图片中,咱们也会有不少猫。
监督对比损失的损失函数,虽然看起来很可怕,但其实很简单。
稍后咱们将看到一些代码,但首先是很是简单的解释。每一个z是标准化的128维向量。
也就是说||z||=1
重申一下线性代数中的事实,若是u和v两个向量正规化,意味着u.v=cos(u和v之间的夹角)
这意味着若是两个标准化向量相同,它们之间的点乘=1
#尝试理解下面的代码 import numpy as np v = np.random.randn(128) v = v/np.linalg.norm(v) print(np.dot(v,v)) print(np.linalg.norm(v))
损失函数假设每幅图像都有一个加强版本,每批有N幅图像,生成的batch大小= 2*N
在i!=j,yi=yj时,分子exp(zi.zj)/tau表示一批中全部的猫。将i个第128个dim向量zi与全部的j个第128个dim向量点积。
分母是i个猫的图像点乘其余不是猫的图像。取zi和zk的点,使i!=k表示它点乘除它本身之外的全部图像。
最后,咱们取对数几率,并将其与批处理中除自身外的全部猫图像相加,而后除以2*N-1
全部图像的总损失和
咱们使用一些torch代码能够理解上面的内容。
假设咱们的批量大小是4,让咱们看看如何计算单个批次的损失。
若是批量大小为4,你在网络上的输入将是8x3x224x224,在这里图像的宽度和高度为224。
8=4x2的缘由是咱们对每一个图像老是有一个对比度,所以须要相应地编写一个数据加载程序。
对比损失resnet将输出8x128维的矩阵,你能够分割这些维度以计算批量损失。
#batch大小 bs = 4
这个部分能够计算分子
temperature = 0.07 anchor_feature = contrast_feature anchor_dot_contrast = torch.div( torch.matmul(anchor_feature, contrast_feature.T), temperature)
咱们的特征形状是8x128,让咱们采起3x128矩阵和转置,下面是可视化后的图片。
anchor_feature=3x128和contrast_feature=128x3,结果为3x3,以下所示
若是你注意到全部的对角线元素都是点自己,这实际上咱们不想要,咱们将删除他们。
线性代数有个性质:若是u和v是两个向量,那么当u=v时,u.v是最大的。所以,在每一行中,若是咱们取锚点对比度的最大值,而且取相同值,则全部对角线将变为0。
让咱们把维度从128降到2
#bs 1 和 dim 2 意味着 2*1x2 features = torch.randn(2, 2) temperature = 0.07 contrast_feature = features anchor_feature = contrast_feature anchor_dot_contrast = torch.div( torch.matmul(anchor_feature, contrast_feature.T), temperature) print('anchor_dot_contrast=\n{}'.format(anchor_dot_contrast)) logits_max, _ = torch.max(anchor_dot_contrast, dim=1, keepdim=True) print('logits_max = {}'.format(logits_max)) logits = anchor_dot_contrast - logits_max.detach() print(' logits = {}'.format(logits)) #输出看看对角线发生了什么 anchor_dot_contrast= tensor([[128.8697, -12.0467], [-12.0467, 50.5816]]) logits_max = tensor([[128.8697], [ 50.5816]]) logits = tensor([[ 0.0000, -140.9164], [ -62.6283, 0.0000]])
建立人工标签和建立适当的掩码进行对比计算。这段代码有点复杂,因此要仔细检查输出。
bs = 4 print('batch size', bs) temperature = 0.07 labels = torch.randint(4, (1,4)) print('labels', labels) mask = torch.eq(labels, labels.T).float() print('mask = \n{}'.format(logits_mask)) #对它进行硬编码,以使其更容易理解 contrast_count = 2 anchor_count = contrast_count mask = mask.repeat(anchor_count, contrast_count) #屏蔽self-contrast的状况 logits_mask = torch.scatter( torch.ones_like(mask), 1, torch.arange(bs * anchor_count).view(-1, 1), 0 ) mask = mask * logits_mask print('mask * logits_mask = \n{}'.format(mask))
让咱们理解输出。
batch size 4 labels tensor([[3, 0, 2, 3]]) #以上的意思是在这批4个品种的葡萄中,咱们有3,0,2,3个标签。以防大家忘了咱们在这里只作了一次对比因此咱们会有3_c 0_c 2_c 3_c做为输入批处理中的对比。 mask = tensor([[0., 1., 1., 1., 1., 1., 1., 1.], [1., 0., 1., 1., 1., 1., 1., 1.], [1., 1., 0., 1., 1., 1., 1., 1.], [1., 1., 1., 0., 1., 1., 1., 1.], [1., 1., 1., 1., 0., 1., 1., 1.], [1., 1., 1., 1., 1., 0., 1., 1.], [1., 1., 1., 1., 1., 1., 0., 1.], [1., 1., 1., 1., 1., 1., 1., 0.]]) #这是很是重要的,因此咱们建立了mask = mask * logits_mask,它告诉咱们在第0个图像表示中,它应该与哪一个图像进行对比。 # 因此咱们的标签就是标签张量([[3,0,2,3]]) # 我从新命名它们是为了更好地理解张量([[3_1,0_1,2_1,3_2]]) mask * logits_mask = tensor([[0., 0., 0., 1., 1., 0., 0., 1.], [0., 0., 0., 0., 0., 1., 0., 0.], [0., 0., 0., 0., 0., 0., 1., 0.], [1., 0., 0., 0., 1., 0., 0., 1.], [1., 0., 0., 1., 0., 0., 0., 1.], [0., 1., 0., 0., 0., 0., 0., 0.], [0., 0., 1., 0., 0., 0., 0., 0.], [1., 0., 0., 1., 1., 0., 0., 0.]])
锚点对比代码
logits = anchor_dot_contrast — logits_max.detach()
损失函数
数学回顾
咱们已经有了第一部分的点积除以tau做为logits。
#上述等式的第二部分等于torch.log(exp_logits.sum(1, keepdim=True)) exp_logits = torch.exp(logits) * logits_mask log_prob = logits - torch.log(exp_logits.sum(1, keepdim=True)) # 计算对数似然的均值 mean_log_prob_pos = (mask * log_prob).sum(1) / mask.sum(1) # 损失 loss = - mean_log_prob_pos loss = loss.view(anchor_count, 4).mean() print('19. loss {}'.format(loss))
我认为这是监督下的对比损失。我认为如今很容易理解自监督的对比损失,由于它比这更简单。
根据本文的研究结果,contrast_count越大,模型越清晰。须要修改contrast_count为2以上,但愿你能在上述说明的帮助下尝试。
参考引用
欢迎关注磐创AI博客站:
http://panchuang.net/
sklearn机器学习中文官方文档:
http://sklearn123.com/
欢迎关注磐创博客资源汇总站:
http://docs.panchuang.net/