聊聊损失函数1. 噪声鲁棒损失函数简析 & 代码实现

今日来聊聊非惯例的丢失函数。在常用的分类穿插熵,以及回归均方差错之外,针对训练样本或许存在的数据长尾,标签噪声,数据不均衡等问题,咱们来聊聊适用不同场景有针对性的丢失函数。第一章咱们介绍,当标示标签存在噪声时能够尝试的丢失函数,这儿的标签噪声主要指独立于特征散布的标签噪声。代码详见pytorch, Tensorflow

Symmetric Loss Function

paper: Making Risk Minimization Tolerant to Label Noise

这儿咱们用最根底的二分类问题,和一个简化的假设”标示噪声和标签独立且均匀散布”,来解说下什么是对标示噪声鲁棒的丢失函数。假设全体误标示的样本占比为\eta,则在实在标签y=0和y=1中均有\eta份额的误标示,1被标成0,0被标称1。带噪声的丢失函数如下

L(f(x),ynoise)=(1−)∗L(f(x),y)+∗L(f(x),1−y)=(1−2)∗L(f(x),y)+∗[L(f(x),y)+L(f(x),1−y)]=(1−2)∗L(f(x),y)+K\begin{aligned}
L(f(x), y_{noise})& = (1-\eta)*L(f(x), y) + \eta * L(f(x), 1-y) \\
&= (1-2\eta)*L(f(x),y) + \eta*[L(f(x),y)+L(f(x),1-y)] \\
&= (1-2\eta)*L(f(x),y) + \eta K \\
\end{aligned}

因而假如丢失函数满意L(f(x),y)+L(f(x),1−y)=constantL(f(x),y)+L(f(x),1-y)=constant,则带噪声的丢失函数会和不带噪声的L(f(x),y)L(f(x),y)收敛到相同的解。作者以为这样的丢失函数便是symmetric的。

那有哪些常见的丢失函数是symmetric loss呢?

MAE便是!对于二分类的softmax的输出层L(f(x),y)+L(f(x),1−y)=∣y−f(x)∣+∣1−y−f(x)∣=1L(f(x),y)+L(f(x),1-y)=|y-f(x)| + |1-y-f(x)| = 1

敲黑板!记住这一点,因为后面的GCE和SCE其实都和MAE有着脱不开的联系。这儿对symmetric loss的证明做了简化,细节详见论文~

Generalized Cross Entropy(GCE)

paper:Generalized Cross Entropy Loss for Training Deep Neural Networks with Noisy Labels

话接上文,MAE虽然是一种noise robust的丢失函数,但是在深度学习中,因为MAE的梯度不是1便是-1,所有样本梯度scale都相同,缺乏对样本难易程度和模型相信度的描写,因而MAE很难收敛。

作者提出了一种交融MAE和Cross Entropy的方案,话不多说直接上Loss

Lq(f(x),yj)=1−fj(x)qqL_{q}(f(x),y_j) = \frac{1-f_j(x)^q}{q}

作者运用了negative box-cox来作为丢失函数,乍看和MAE没啥联系。不过改动q的取值,就会发现奥妙所在

  • q->1: L=1−fj(x)L=1-f_j(x), 便是MAE Loss
  • q->0: 根据洛必达法则,对分子分母一起求导,就会得到L=−log(fj(x))L=-log(f_j(x)), 便是Cross Entropy

所以GCE丢失函数通过控制q的取值,在MAE和CrossEntropy中寻找折中点。这个和Huber Loss的规划有些相似,只不过Huber是显式的用alpha权重来交融RMSE和MAE,而GCE是隐式的交融。q->1, 对噪声的鲁棒性更好,但更难收敛。作者还提出了切断GCE,对过大的loss进行切断,这儿就不细说了~

pytorch完成如下,TF完成见文首链接

class GeneralizeCrossEntropy(nn.Module):
    def __init__(self, q=0.7):
        super(GeneralizeCrossEntropy, self).__init__()
        self.q = q
    def forward(self, logits, labels):
        # Negative box cox: (1-f(x)^q)/q
        labels = torch.nn.functional.one_hot(labels, num_classes=logits.shape[-1])
        probs = F.softmax(logits, dim=-1)
        loss = (1 - torch.pow(torch.sum(labels * probs, dim=-1), self.q)) / self.q
        loss = torch.mean(loss)
        return loss

Symmetric Cross Entropy(SCE)

Symmetric Cross Entropy for Robust Learning with Noisy Labels

作者是从穿插熵的另一个含义动身, 最小化穿插熵实际是为了最小化猜测散布和实在散布的KL散度, 二者相关如下,其中H(y)是实在标签的信息熵是个常数

KL(y∣∣f(x))=∑ylog(f(x))−∑ylog(y)=H(y,f(x))−H(y)=CrossEntropy(y,f(x))−H(y)\begin{aligned}
KL(y||f(x)) &= \sum ylog(f(x)) – \sum ylog(y) \\
& = H(y, f(x)) – H(y) = CrossEntropy(y, f(x)) – H(y)
\end{aligned}

考虑KL散度对错对称的,KL(y||f(x))!=KL(f(x)||y), 前者衡量的是运用猜测散布对数据进行编码导致的信息丢失。但是当y本身存在噪声时,y或许不是正确标签,f(x)才是,这时就需要考虑另一个方向KL散度KL(f(x)||y)。于是作者运用对称KL对应的对称穿插熵(SCE)作为丢失函数

SCE=CE+RCE=H(y,f(x))+H(f(x),y)=∑jyjlog(fj(x))+∑jfj(x)log(yj)SCE =CE + RCE = H(y,f(x)) + H(f(x),y) \\
= \sum_j y_jlog(f_j(x)) + \sum_j f_j(x)log(y_j)

看到这儿多少会有一种作者又拍脑袋了的感觉>.<.不过只需要对RCE的部分做下改换就豁然开朗了。以二分类为例,log(0)无法核算用常数A替代

RCE=H(f(x),y)=f1(x)∗log(1)+(1−f1(x))∗log(0)=A(1−f1(x))RCE= H(f(x),y) = f_1(x) * log(1) + (1-f_1(x)) *log(0) = A(1-f_1(x))

RCE的部分便是一个MAE!所以SCE本质上是显式的交融穿插熵和MAE!pytorch完成如下,TF完成见文首链接

class SymmetricCrossEntropy(nn.Module):
    def __init__(self, alpha=0.1, beta=1):
        super(SymmetricCrossEntropy, self).__init__()
        self.alpha = alpha
        self.beta = beta
        self.epsilon = 1e-10
    def forward(self, logits, labels):
        # KL(p|q) + KL(q|p)
        labels = torch.nn.functional.one_hot(labels, num_classes=logits.shape[-1])
        probs = F.softmax(logits, dim=-1)
        # KL
        y_true = torch.clip(labels, self.eps, 1.0 - self.eps)
        y_pred = probs
        ce = -torch.mean(torch.sum(y_true * torch.log(y_pred), dim=-1))
        # reverse KL
        y_true = probs
        y_pred = torch.clip(labels, self.eps, 1.0 - self.eps)
        rce = -torch.mean(torch.sum(y_true * torch.log(y_pred), dim=-1))
        return self.alpha * ce + self.beta * rce

Peer Loss

  • Peer Loss Functions:Learning from Noisy Labels without Knowning Noise Rates
  • NLNL: Negative Learning for Noisy Labels

Peer Loss比较GCE和SCE只适用于Cross Entropy, 它的规划更加灵活。每个样本的丢失函数由惯例loss和随机label的loss加权得到,权重为alpha,这儿的loss支撑恣意的分类丢失函数。随机label作者通过打乱一个batch里面的label次序得到~

原理上感觉Peer Loss和NLNL很是相似都是negative learning的思路。对比下二者的丢失函数,PL是最小化带噪标签y的丢失的一起,最大化模型在随机标签上的丢失。NL是直接最大化模型在非实在标签y上的丢失。本质上都是negative learning,模型学习的不是x是什么,而是x不是什么,通过推进所有不正确分类的p->0,来得到正确的标签。从这个逻辑上说感觉Peer Loss和NLNL在高维的多分类场景下应该有更好的体现~

PL(f(x),y)=L(f(x),y)−L(f(x),y~)PL(f(x),y) = L(f(x),y) – \alpha L(f(x),\tilde{y})
NL(f(x),y)=L(1−f(x),y~)NL(f(x),y) = L(1-f(x), \tilde{y})

pytorch完成如下,TF完成见文首链接

class PeerLoss(nn.Module):
    def __init__(self, alpha=0.5, loss):
        super(PeerLoss, self).__init__()
        self.alpha = alpha
        self.loss = loss
    def forward(self, preds, labels):
        index = list(range(labels.shape[0]))
        rand_index = random.shuffle(index)
        rand_labels = labels[rand_index]
        loss_true = self.loss(preds, labels)
        loss_rand = self.loss(preds, rand_labels)
        loss = loss_true - self.alpha * loss_rand
        return loss

Bootstrap Loss

Training Deep Neural Networks on Noisy Labels with Bootstrapping

Bootstrap Loss是从猜测一致性的视点来降低噪声标签对模型的影响,作者给了soft和hard两种丢失函数。

soft Bootstrap是在Cross Entropy的根底上加上猜测熵值,在最小化猜测差错的一起最小化概率熵值,推进概率趋近于0/1,得到更相信的猜测。这儿其实用到了之前在半监督时说到的最小熵准则(小样本利器3. 半监督最小熵正则)也便是推进分类鸿沟远离高密度区。

对噪声标签,模型初始预估的熵值会较大(p->0.5), 因为加入了熵正则项,模型即便不去拟合噪声标签,而是向正确标签移动(进步猜测相信度降低熵值),也会降低丢失函数.不过这儿感觉熵正则的引入也有或许使得模型猜测相信度过高而导致过拟合

Lsoft=∑(yi+(1−)pi)log(pi)L_{soft} = \sum (\beta y_i + (1-\beta) p_i) log(p_i)

而Hard Bootstrap是把以上的猜测概率值替换为猜测概率最大的分类,Hard比较Soft更加类似label smoothing。举个栗子:当实在标签为y=0,噪声标签y=1,猜测概率为[0.7,0.3]时,=0.9\beta=0.9时Bootstrap拟合的y实际为[0.1,0.9], 会降低错误标签的相信度,给模型学习其他标签的时机。而当模型猜测和标签一致时y值不变,所以不会对正确有样本有太多影响,作用上作者评估也是Hard Bootstrap的作用要明显更好~

Lhard=∑(yi+(1−)argmx(pi))log(pi)L_{hard} = \sum (\beta y_i + (1-\beta) argmx(p_i)) log(p_i)

pytorch完成如下,TF完成见文首链接

class BootstrapCrossEntropy(nn.Module):
    def __init__(self, beta=0.95, is_hard=0):
        super(BootstrapCrossEntropy, self).__init__()
        self.beta = beta
        self.is_hard = is_hard
    def forward(self, logits, labels):
        # (beta * y + (1-beta) * p) * log(p)
        labels = F.one_hot(labels, num_classes=logits.shape[-1])
        probs = F.softmax(logits, dim=-1)
        probs = torch.clip(probs, self.eps, 1 - self.eps)
        if self.is_hard:
            pred_label = F.one_hot(torch.argmax(probs, dim=-1), num_classes=logits.shape[-1])
        else:
            pred_label = probs
        loss = torch.sum((self.beta * labels + (1 - self.beta) * pred_label) * torch.log(probs), dim=-1)
        loss = torch.mean(- loss)
        return loss

对更多降噪loss感兴趣的朋友望过来github.com/subeeshvasu…

又到年末填坑时间,争夺把今年写了一半的草稿都补完,冲鸭!


Reference

  1. zhuanlan.zhihu.com/p/147371861
  2. blog.csdn.net/suredied/ar…
  3. zhuanlan.zhihu.com/p/370775044
  4. zhuanlan.zhihu.com/p/569526954
  5. zhuanlan.zhihu.com/p/299404214