Label Smoothing标签平滑详解+Pytorch保姆级实际操作

目录

    • 简介
    • 从提出Label Smoothing的论文出发
      • 不使用LS时的情况
      • 不使用LS时可能带来的问题
      • LS作为正则化的一种方式
        • 具体操作
      • 为何LS可以避免偏激的输出
      • 对LS的另一个角度理解
    • Pytorch实际操作
      • 保姆级代码详解
      • 参考文献

简介

Label Smoothing是一个帮助多分类模型进行正则化的操作。

从提出Label Smoothing的论文出发

"When Does Label Smoothing Help? "这篇文章指出Szegedy et al.提出了Label Smoothing. 因此我们就从Szegedy et al.的文章入手。在这里我们简称Label Smoothing为LS。
标签平滑也可以被简称为LSR(Label-Smoothing Regularization)。

不使用LS时的情况

假设我们有一个K分类问题,我们有标签1到K。
假设 z k z_k zk是类 k k k未经softmax的log概率值,
设x是一个训练input
那么我们的模型给我们的每一个类 k k k的概率就是:
p ( k ∣ x ) = e x p ( z k ) ∑ i = 1 k e x p ( z i ) p(k|x) = \frac{exp(z_k)}{\sum_{i=1}^{k}exp(z_i)} p(kx)=i=1kexp(zi)exp(zk)
假设 q ( k ∣ x ) q(k|x) q(kx)是类 k k k的真实概率标签。

那么此处的Cross Entropy Loss就是(此处省去了条件概率后面的 “ ∣ x |x x”):
l = − ∑ k = 1 K log ⁡ ( p ( k ) ) q ( k ) l = - \sum_{k=1}^{K}\log(p(k)) q(k) l=k=1Klog(p(k))q(k)
这个loss关于 z k z_k zk求导的话:
∂ l ∂ z k = p ( k ) − q ( k ) \frac{\partial l}{\partial z_k} = p(k)-q(k) zkl=p(k)q(k)
这个值是肯定在-1和1之间的


假设K个类别中,只有类别y是正确的。也就是说,只有q(y)=1,
对于其他的q(k),只要 k ≠ y k\neq y k=y, q(k)=0。
此时,使得loss l l l最小,就是要让正确label y的log-likelihood最大。因为把所有的q(k)=0的情况都消掉之后,我们就有
l = − log ⁡ ( p ( y ) ) q ( y ) = − log ⁡ ( p ( y ) ) \begin{aligned} l &= - \log(p(y)) q(y)\\ &= -\log(p(y)) \end{aligned} l=log(p(y))q(y)=log(p(y))
所以当某训练input x的标签是y时,如果模型的输出能够做到
z y ≫ z k z_y \gg z_k zyzk
(for all k ≠ y k\neq y k=y)
那么此时我们就可以让loss达到尽可能小。

不使用LS时可能带来的问题

  1. 可能带来过拟合。模型可能会过度拟合它见过的数据,而运用到其他数据上可能无法泛化。
  2. 会导致 z y z_y zy z k z_k zk的差异太大,模型的输出可能过于偏激。

LS作为正则化的一种方式

LS则是为了避免这种偏激的情况。
既然是正则化,当然在最大化当前训练数据的log-likelihood上面就不会有什么帮助了,
不过可以让模型不那么偏激,且在别的数据上更好地泛化。

具体操作

本来我们的模型标签分布是这样的:
q ( k ∣ x ) = δ k , y q(k|x) = \delta_{k,y} q(kx)=δk,y
此处的 δ k , y \delta_{k,y} δk,y是一个狄拉克delta,意思是除了当k=y时这个值为1之外,其他情况值都为0。

而现在,我们不再用这个分布,而是使用:
q ′ ( k ∣ x ) = ( 1 − ϵ ) δ k , y + ϵ u ( k ) q'(k|x) = (1-\epsilon)\delta_{k,y} + \epsilon u(k) q(kx)=(1ϵ)δk,y+ϵu(k)
此处:
u(k)是一个关于具体类别k的分布,并且是固定的(与x取值无关)
ϵ \epsilon ϵ是一个平滑参数

这个新的 q ′ ( k ∣ x ) q'(k|x) q(kx)其实是再把原来的分布 q ( k ∣ x ) q(k|x) q(kx)和分布 u ( k ) u(k) u(k)做了一个结合。
可以这样理解:
1 − ϵ 1-\epsilon 1ϵ的比例下我们还是使用原来的绝对标签(0或1)
ϵ \epsilon ϵ的比例下我们使用我们的新分布 u ( k ) u(k) u(k)

此处 u ( k ) u(k) u(k)如何定义:
既然是标签的概率分布,那可以从数据出发,看一下每个标签出现概率的分布,把这些值用于 u ( k ) u(k) u(k)这个分布。
原作者在论文中的实验中使用的是 u ( k ) = 1 / K u(k)=1/K u(k)=1/K,那么就有(此处再次省略条件概率后面的 “ ∣ x |x x”):
q ′ ( k ) = ( 1 − ϵ ) δ k , y + ϵ K q'(k) = (1-\epsilon)\delta_{k,y} + \frac{\epsilon}{K} q(k)=(1ϵ)δk,y+Kϵ

为何LS可以避免偏激的输出

一开始我们提到,如果不用LS, l = − log ⁡ ( p ( y ) ) l = -\log(p(y)) l=log(p(y))这个loss会让类y的q(y)尽量接近1,而其它类的q(k)则接近0.
但现在我们的loss − ∑ k = 1 K log ⁡ ( p ( k ) ) q ′ ( k ) -\sum_{k=1}^{K}\log(p(k)) q'(k) k=1Klog(p(k))q(k) 中每个q’都是一个正数,不再是原来的0了。让p(k)太小的话,是不利于 l l l的减小的。

对LS的另一个角度理解

当我们用 q ′ ( k ∣ x ) q'(k|x) q(kx)替换 q ( k ∣ x ) q(k|x) q(kx),设此时的Cross Entropy为 H ( q ′ , p ) H(q',p) H(q,p),原来的是 H ( q , p ) H(q,p) H(q,p),我们做一些推导:
H ( q , p ) = − ∑ k = 1 K log ⁡ ( p ( k ) ) q ( k ) = − ∑ k = 1 K log ⁡ ( p ( k ) ) δ k , y \begin{aligned} H(q,p) &= -\sum_{k=1}^{K}\log(p(k)) q(k)\\ &=-\sum_{k=1}^{K}\log(p(k)) \delta_{k,y} \end{aligned} H(q,p)=k=1Klog(p(k))q(k)=k=1Klog(p(k))δk,y

H ( q ′ , p ) = − ∑ k = 1 K log ⁡ ( p ( k ) ) q ′ ( k ) = − ∑ k = 1 K log ⁡ ( p ( k ) ) [ ( 1 − ϵ ) δ k , y + ϵ u ( k ) ] = ( 1 − ϵ ) ( − ∑ k = 1 K log ⁡ ( p ( k ) ) δ k , y ) + ϵ ( − ∑ k = 1 K log ⁡ ( p ( k ) ) u ( k ) ) = ( 1 − ϵ ) H ( q , p ) + ϵ H ( u , p ) \begin{aligned} H(q',p) &= -\sum_{k=1}^{K}\log(p(k)) q'(k)\\ &=-\sum_{k=1}^{K}\log(p(k))[(1-\epsilon)\delta_{k,y} + \epsilon u(k)] \\ &=(1-\epsilon)(-\sum_{k=1}^{K}\log(p(k)) \delta_{k,y})+\epsilon(-\sum_{k=1}^{K}\log(p(k)) u(k))\\ &=(1-\epsilon)H(q,p) + \epsilon H(u,p) \end{aligned} H(q,p)=k=1Klog(p(k))q(k)=k=1Klog(p(k))[(1ϵ)δk,y+ϵu(k)]=(1ϵ)(k=1Klog(p(k))δk,y)+ϵ(k=1Klog(p(k))u(k))=(1ϵ)H(q,p)+ϵH(u,p)
从这个角度理解的话,实际上LS就是把原来的loss H ( q , p ) H(q,p) H(q,p)变成了两个loss H ( q ′ , p ) H(q',p) H(q,p) H ( u , p ) H(u,p) H(u,p)的结合。
其中, H ( u , p ) H(u,p) H(u,p)这个loss的作用就是,当p偏离u这个分布时,要对模型进行惩罚。这个惩罚的相对权重则是 ϵ 1 − ϵ \frac{\epsilon}{1-\epsilon} 1ϵϵ

注: H ( u , p ) H(u,p) H(u,p)这个东西换成KL散度也是ok的,因为
H ( u , p ) = D K L ( u ∣ ∣ p ) + H ( u ) H(u,p) = D_{KL}(u||p)+H(u) H(u,p)=DKL(up)+H(u)
而H(u)又是不变的。

Pytorch实际操作

不像TF,Pytorch内目前应该是没有现成的LS implementation的。但仍然有很多可用的实现方式,此处我们以NVIDIA/DeepLearningExamples的版本为例。

class LabelSmoothing(nn.Module):
    """NLL loss with label smoothing.
    """
    def __init__(self, smoothing=0.0):
        """Constructor for the LabelSmoothing module.
        :param smoothing: label smoothing factor
        """
        super(LabelSmoothing, self).__init__()
        self.confidence = 1.0 - smoothing
        self.smoothing = smoothing
        # 此处的self.smoothing即我们的epsilon平滑参数。

    def forward(self, x, target):
        logprobs = torch.nn.functional.log_softmax(x, dim=-1)
        nll_loss = -logprobs.gather(dim=-1, index=target.unsqueeze(1))
        nll_loss = nll_loss.squeeze(1)
        smooth_loss = -logprobs.mean(dim=-1)
        loss = self.confidence * nll_loss + self.smoothing * smooth_loss
        return loss.mean()

保姆级代码详解

这里我先把H(q’,p)化成一个跟这里代码最接近的形式。
H ( q ′ , p ) = − ∑ k = 1 K log ⁡ ( p ( k ) ) q ′ ( k ) = − ∑ k = 1 K log ⁡ ( p ( k ) ) [ ( 1 − ϵ ) δ k , y + ϵ u ( k ) ] = − ∑ k = 1 K [ ( 1 − ϵ ) log ⁡ ( p ( k ) ) δ k , y + ϵ log ⁡ ( p ( k ) ) u ( k ) ] \begin{aligned} H(q',p) &= -\sum_{k=1}^{K}\log(p(k)) q'(k)\\ &=-\sum_{k=1}^{K}\log(p(k))[(1-\epsilon)\delta_{k,y} + \epsilon u(k)] \\ &=-\sum_{k=1}^{K}[(1-\epsilon)\log(p(k))\delta_{k,y}+\epsilon\log(p(k)) u(k)]\\ \end{aligned} H(q,p)=k=1Klog(p(k))q(k)=k=1Klog(p(k))[(1ϵ)δk,y+ϵu(k)]=k=1K[(1ϵ)log(p(k))δk,y+ϵlog(p(k))u(k)]

  1. log ⁡ ( p ( k ) ) \log(p(k)) log(p(k))
logprobs = torch.nn.functional.log_softmax(x, dim=-1)

此处x的shape应该是(batch size * class数量),所以这里在class数量那个维度做了logsoftmax。

Pytorch中的Logsoftmax的具体实现:
L o g S o f t m a x ( a i ) = l o g ( exp ⁡ ( a i ) ∑ j exp ⁡ ( a j ) ) LogSoftmax(a_i) = log(\frac{\exp(a_i)}{\sum_{j}\exp(a_j)}) LogSoftmax(ai)=log(jexp(aj)exp(ai))

2. log ⁡ ( p ( k ) ) δ k , y \log(p(k))\delta_{k,y} log(p(k))δk,y

nll_loss = -logprobs.gather(dim=-1, index=target.unsqueeze(1))

此处的target的shape是(batch size), 应该就是每个training data的数字标签。
这里unsqueeze之后,就是(batch size * 1 )

这里的gather函数的具体理解请参考我的另一篇文章:Pytorch中的torch.gather函数详解,从抽象到具体。
总之,这里会输出一个形状也是(batch size * 1)的张量,第二个维度只取了那些target维度的log-prob值。
举个例子,假设我们的x是[[0.3,0.1,0.6],[0.2,0.3,0.5]],一共两个数据。
标签是[2,2]的话
输出会给到[[0.6],[0.5]]

 nll_loss = nll_loss.squeeze(1)

把输出的shape变回(batch size)

  1. log ⁡ ( p ( k ) ) u ( k ) \log(p(k)) u(k) log(p(k))u(k)
smooth_loss = -logprobs.mean(dim=-1)

这里的logprobs的shape是(batch size * class数量)

在第二个维度取均值的话,应该就是对每个x,所有类的logprobs取了平均值。

那不就相当于:

log ⁡ ( p ( k ) ) ∗ 1 K = log ⁡ ( p ( k ) ) u ( k ) \log(p(k)) * \frac{1}{K} = \log(p(k)) u(k) log(p(k))K1=log(p(k))u(k)

和论文里实验的实现方式是一样的

  1. − ∑ k = 1 K [ ( 1 − ϵ ) log ⁡ ( p ( k ) ) δ k , y + ϵ log ⁡ ( p ( k ) ) u ( k ) ] -\sum_{k=1}^{K}[(1-\epsilon)\log(p(k))\delta_{k,y}+\epsilon\log(p(k)) u(k)] k=1K[(1ϵ)log(p(k))δk,y+ϵlog(p(k))u(k)]
loss = self.confidence * nll_loss + self.smoothing * smooth_loss

OK了

参考文献

  1. When Does Label Smoothing Help?
  2. Rethinking the Inception Architecture for Computer Vision
  3. Label Smoothing in PyTorch
  4. NVIDIA/DeepLearningExamples

你可能感兴趣的:(Pytorch实战,算法,深度学习,机器学习,人工智能,python,算法)