【写在后面】
最近在看检索和匹配相干的工作,之前对这个工作不太理解,只晓得就是类似度比照,找出类似度最高的样本就能够了。然而理解之后,在模型训练过程中,有许多办法(损失函数)来拉近正样本的间隔,拉远负样本的间隔。
Triplet loss
先从最经典的三元组 loss 说起,三元组的形成:从训练数据集中随机选一个样本,该样本称为 Anchor,而后再随机选取一个和 Anchor 属于同一类的样本和不同类的样本, 这两个样本对应的称为 Positive 和 Negative,由此形成一个三元组。
通过学习,让正样本特色表白之间的间隔尽可能小,而负样本的特色表白之间的间隔尽可能大,并且要让正样本之间的间隔和负样本之间的间隔之间有一个最小的距离(margin)。损失函数如下所示:
$$
\sum_{i}^{N}\left[\left\|f\left(x_{i}^{a}\right)-f\left(x_{i}^{p}\right)\right\|_{2}^{2}-\left\|f\left(x_{i}^{a}\right)-f\left(x_{i}^{n}\right)\right\|_{2}^{2}+\alpha\right]_{+}
$$
$[]_+$ 相当于一个 ReLU 函数。
Sum Hinge Loss & Max Hinge Loss
接下来介绍一下和 Triplet loss 十分靠近的 loss Max Hinge loss,像是 Triplet loss 的升维操作。
Triplet loss 的输出是 (a, p, n),个别的做法是 b 个 (ai,pi) i∈[0,b] pair 对,咱们对 pi 旋转一下失去 (p1,p2,…,pb,p0) 作为负样本列表。最初失去一个一维的 loss 向量 (l1,l2…,lb)。
Triplet loss 实际上只思考了由 a 和 p 组成矩阵的局部状况产生的 loss,咱们实际上能够对 a、p 产生的类似度矩阵中所有非对角线的负样本进行计算损失,从而充分利用 batch 内的信息,通过这个思路咱们能够失去 Sum Hinge Loss 如下,Triplet loss 的计算中是用的 L2 间隔,这里改为了余弦类似度,所以之前的 ap – an + margin,改为了 an – ap + margin 了,指标是让 an 的类似度更小,ap 的类似度更大
- Sum Hinge Loss
$$
\ell_{S H}(i, c)=\sum_{\hat{c}}[\alpha-s(i, c)+s(i, \hat{c})]_{+}+\sum_{\hat{i}}[\alpha-s(i, c)+s(\hat{i}, c)]_{+}
$$
- Max Hinge Loss
VSE++ 提出了一个新的损失函数 max hinge loss,它主张在排序过程中应该更多地关注艰难负样例,艰难负样本是指与 anchor 靠得近的负样本,试验后果也显示 max hinge loss 性能比之前罕用的排序损失 sum hinge loss 好很多:
$$
\ell_{M H}(i, c)=\max _{c^{\prime}}\left[\alpha+s\left(i, c^{\prime}\right)-s(i, c)\right]_{+}+\max _{i^{\prime}}\left[\alpha+s\left(i^{\prime}, c\right)-s(i, c)\right]_{+}
$$
Max Hinge Loss pytorch 代码如下:
def cosine_sim(im, s):
"""Cosine similarity between all the image and sentence pairs"""
return im.mm(s.t())
class MaxHingLoss(nn.Module):
def __init__(self, margin=0.2, measure=False, max_violation=True):
super(MaxHingLoss, self).__init__()
self.margin = margin
self.sim = cosine_sim
self.max_violation = max_violation
def forward(self, im, s):
an = self.sim(im, s) # an
diagonal = scores.diag().view(im.size(0), 1)
ap1 = diagonal.expand_as(scores)
ap2 = diagonal.t().expand_as(scores)
# query2doc retrieval
cost_s = (self.margin + an - ap1).clamp(min=0)
# doc2query retrieval
cost_im = (self.margin + an - ap2).clamp(min=0)
# clear diagonals
mask = torch.eye(scores.size(0)) > .5
I = Variable(mask)
if torch.cuda.is_available():
I = I.cuda()
cost_s = cost_s.masked_fill_(I, 0)
cost_im = cost_im.masked_fill_(I, 0)
# keep the maximum violating negative for each query
if self.max_violation:
cost_s = cost_s.max(1)[0][:1]
cost_im = cost_im.max(0)[0][:1]
return cost_s.mean() + cost_im.mean()
# or # return cost_s.sum() + cost_im.sum()
NCE
NCE(noise contrastive estimation)核心思想是将多分类问题转化成二分类问题,一个类是数据类别 data sample,另一个类是噪声类别 noisy sample,通过学习数据样本和噪声样本之间的区别,将数据样本去和噪声样本做比照,也就是“噪声比照(noise contrastive)”,从而发现数据中的一些个性。然而,如果把整个数据集剩下的数据都当作负样本(即噪声样本),尽管解决了类别多的问题,计算复杂度还是没有降下来,解决办法就是做负样本采样来计算 loss,这就是 estimation 的含意,也就是说它只是预计和近似。一般来说,负样本选取的越多,就越靠近整个数据集,成果天然会更好。
NCE loss 函数如下,一个正样本的二分类和 k 个负样本的二分类:
$$
\begin{aligned} \mathcal{L}_{\mathrm{NCE}_{k}}^{\mathrm{MC}} &=\sum_{(w, c) \in \mathcal{D}}\left(\log p(D=1 \mid c, w)+k \times \sum_{i=1, \bar{w} \sim q}^{k} \frac{1}{k} \times \log p(D=0 \mid c, \bar{w})\right) \\ &=\sum_{(w, c) \in \mathcal{D}}\left(\log p(D=1 \mid c, w)+\sum_{i=1, \bar{w} \sim q}^{k} \log p(D=0 \mid c, \bar{w})\right) . \end{aligned}
$$
Info NCE
Info NCE loss 是 NCE 的一个简略变体,它认为如果你只把问题看作是一个二分类,只有数据样本和噪声样本的话,可能对模型学习不敌对,因为很多噪声样本可能本就不是一个类,因而还是把它看成一个多分类问题比拟正当(但这里的多分类 k 指代的是负采样之后负样本的数量),于是就有了 InfoNCE loss 函数如下:
$$
L_{q}=-\log \frac{\exp \left(q \cdot k_{+} / \tau\right)}{\left.\sum_{i=0}^{k} \exp \left(q \cdot k_{i} / \tau\right)\right)}
$$
其中 $q \cdot k$ 相当于是 logits,$\tau$ 是温度系数,整体和 cross entropy 是十分相近的。
温度系数的作用就是管制了模型对负样本的区分度。
OHEM(Online Hard Example Mining)
Hard Negatie Mining 与 Online Hard Example Mining(OHEM)都属于难例开掘,它是解决指标检测老大难问题的罕用方法,使用于 R -CNN,fast R-CNN,faster rcnn 等 two-stage 模型与 SSD 等(有 anchor 的)one-stage 模型训练时的训练方法。(集体了解就是只计算 Top K 的难例的 loss)
OHEM 和难负例开掘名字上的不同。
Hard Negative Mining 只留神难负例
OHEM 则留神所有难例,不管正负(Loss 大的例子)
难例开掘的思维能够解决很多样本不均衡 / 简略样本过多的问题,比如说分类网络,将 hard sample 补充到数据集里,从新丢进网络当中,就如同给网络筹备一个错题集,哪里不会点哪里。
def ohem_loss(batch_size, cls_pred, cls_target, loc_pred, loc_target, smooth_l1_sigma=1.0):
"""
Arguments:
batch_size (int): number of sampled rois for bbox head training
loc_pred (FloatTensor): [R, 4], location of positive rois
loc_target (FloatTensor): [R, 4], location of positive rois
pos_mask (FloatTensor): [R], binary mask for sampled positive rois
cls_pred (FloatTensor): [R, C]
cls_target (LongTensor): [R]
Returns:
cls_loss, loc_loss (FloatTensor)
"""ohem_cls_loss = F.cross_entropy(cls_pred, cls_target, reduction='none', ignore_index=-1)
ohem_loc_loss = smooth_l1_loss(loc_pred, loc_target, sigma=smooth_l1_sigma, reduce=False)
#这里先暂存下失常的分类 loss 和回归 loss
loss = ohem_cls_loss + ohem_loc_loss
#而后对分类和回归 loss 求和
sorted_ohem_loss, idx = torch.sort(loss, descending=True)
#再对 loss 进行降序排列
keep_num = min(sorted_ohem_loss.size()[0], batch_size)
#失去须要保留的 loss 数量
if keep_num < sorted_ohem_loss.size()[0]:
#这句的作用是如果保留数目小于现有 loss 总数,则进行筛选保留,否则全副保留
keep_idx_cuda = idx[:keep_num]
#保留到须要 keep 的数目
ohem_cls_loss = ohem_cls_loss[keep_idx_cuda]
ohem_loc_loss = ohem_loc_loss[keep_idx_cuda]
#分类和回归保留雷同的数目
cls_loss = ohem_cls_loss.sum() / keep_num
loc_loss = ohem_loc_loss.sum() / keep_num
#而后别离对分类和回归 loss 求均值
return cls_loss, loc_loss
一些不相干的内容
1. 为什么 LogSoftmax 比 Softmax 更好?
log\_softmax 可能解决函数 overflow 和 underflow,放慢运算速度,进步数据稳定性。
因为 softmax 会进行指数操作,当上一层的输入,也就是 softmax 的输出比拟大的时候,可能就会产生 overflow。比方上图中,z1、z2、z3 取值很大的时候,超出了 float 能示意的范畴。
同理当输出为正数且绝对值也很大的时候,会分子、分母会变得极小,有可能四舍五入为 0,导致下溢出。
只管在数学示意式上是对 softmax 在取对数的状况。然而在实操中是通过:
$$
\log \left[f\left(x_{i}\right)\right]=\log \left(\frac{e^{x_{i}}}{e^{x_{1}}+e^{x_{2}}+\ldots+e^{x_{n}}}\right)\\=\log \left(\frac{\frac{e^{x_{i}}}{e^{M}}}{\frac{e^{x_{1}}}{e^{M}}+\frac{e^{2}}{e^{M}}+\ldots+\frac{e^{x_{n}}}{e^{M}}}\right)=\log \left(\frac{e^{\left(x_{i}-M\right)}}{\sum_{j}^{n} e^{\left(x_{j}-M\right)}}\right)\\=\log \left(e^{\left(x_{i}-M\right)}\right)-\log \left(\sum_{j}^{n} e^{\left(x_{j}-M\right)}\right)=\left(x_{i}-M\right)-\log \left(\sum_{j}^{n} e^{\left(x_{j}-M\right)}\right)
$$
来实现,其中 $M=\max \left(x_{i}\right), i=1,2, \cdots, n$,即 M 为所有 $x_{i}$ 中最大的值。能够解决这个问题,在放慢运算速度的同时,能够放弃数值的稳定性。
2. 什么是 label smoothing?
label smoothing 是一种正则化的形式,全称为 Label Smoothing Regularization(LSR),即标签平滑正则化。
在传统的分类工作计算损失的过程中,是将实在的标签做成 one-hot 的模式,而后应用穿插熵来计算损失。而 label smoothing 是将实在的 one hot 标签做一个标签平滑解决,使得标签变成又概率值的 soft label. 其中,在实在 label 处的概率值最大,其余地位的概率值是个十分小的数。
在 label smoothing 中有个参数 epsilon,形容了将标签软化的水平,该值越大,通过 label smoothing 后的标签向量的标签概率值越小,标签越平滑,反之,标签越趋向于 hard label,在训练 ImageNet-1k 的试验里通常将该值设置为 0.1。
参考文献
https://zhuanlan.zhihu.com/p/514859125
https://www.zhihu.com/question/358069078/answer/912691444
已建设深度学习公众号——FightingCV,欢送大家关注!!!
ICCV、CVPR、NeurIPS、ICML 论文解析汇总:https://github.com/xmu-xiaoma…
面向小白的 Attention、重参数、MLP、卷积外围代码学习:https://github.com/xmu-xiaoma…
退出交换群,请增加小助手 wx:FightngCV666
本文由 mdnice 多平台公布