标签平滑
标签平滑
目录
前言
Label Smoothing(标签平滑)是一种正则化的方法,用于减轻过拟合的影响。其应用场景必须具备以下几个要素:
- 标签必须是one-hot向量
- 损失函数是交叉熵损失函数
交叉熵损失
交叉熵损失是做分类任务里常用的一种损失,公式如下:
这里\(x_{i}\)表示的是模型输出的logits后经softmax的结果,shape为(\(N,M\)),\(y_{i}\)表示的是真实标签,通常用one-hot编码表示。
将以上公式拆解,可分为如下几部分
-
\(log \left(x_i\right)\) (log softmax)
即softmax后取对数,公式如下
\[\sum_{i=1}^n \log \left(\frac{\exp \left(x_i\right)}{\sum_{i=1}^n\left(\exp \left(x_i\right)\right)}\right) \] -
NLL Loss
全称为Negative log-likelihood(负对数似然损失),即
\[L(\theta)=-\sum_i^n \log \left(P\left(x_i ; \theta\right)\right) \]
由于求loss的时候,采用了one-hot编码,除去当前类别为1其余都为0,所以有:
这个形式和CrossEntropyLoss一致。
最终在训练网络时,为了达到最好的拟合效果,最优的预测概率分布为:
也就是说,网络会驱使自己往正确标签和错误标签差值最大的方向学习,在训练数据不足以表征所有样本特征的情况下,就会导致过拟合。
Label Smoothing
由于softmax的过拟合问题,即模型对于弱项的照顾很小。标签平滑就是为了降低softmax所带来的高Confiidence的影响,让模型略微关注到低概率分布的权重。这样做也有些影响,即最终的输出置信度会稍微低一些,需要细致的阈值过滤。
在Label Smoothing中,标签的编码不再单纯使用one-hot编码,而是用以下形式来表示:
即用\(\frac{\varepsilon}{n - 1}\)来代替0,用\(1-\varepsilon\)来代替1。其中\(\varepsilon\)是预设的一个超参数,一般取0.1,\(n\)是该分类问题的类别个数。
代码实现
class LSR(nn.Module):
def __init__(self, e=0.01,reduction='mean'):
super().__init__()
self.log_softmax = nn.LogSoftmax(dim=1)
self.e = e
self.reduction = reduction
def _one_hot(self, labels, classes, value=1):
"""
Convert labels to one hot vectors
Args:
labels: torch tensor in format [label1, label2, label3, ...]
classes: int, number of classes
value: label value in one hot vector, default to 1
Returns:
return one hot format labels in shape [batchsize, classes]
"""
#print("classes", classes)
one_hot = torch.zeros(labels.size(0), classes)
# labels and value_added size must match
labels = labels.view(labels.size(0), -1)
value_added = torch.Tensor(labels.size(0), 1).fill_(value)
value_added = value_added.to(labels.device)
one_hot = one_hot.to(labels.device)
one_hot.scatter_add_(1, labels, value_added)
return one_hot
def _smooth_label(self, target, length, smooth_factor):
"""convert targets to one-hot format, and smooth
them.
Args:
target: target in form with [label1, label2, label_batchsize]
length: length of one-hot format(number of classes)
smooth_factor: smooth factor for label smooth
Returns:
smoothed labels in one hot format
"""
#print("length", length)
#print("smooth_fact", smooth_factor)
one_hot = self._one_hot(target, length, value=1 - smooth_factor)
one_hot += smooth_factor / length
return one_hot.to(target.device)
def forward(self, x, target):
if x.size(0) != target.size(0):
raise ValueError('Expected input batchsize ({}) to match target batch_size({})'
.format(x.size(0), target.size(0)))
if x.dim() < 2:
raise ValueError('Expected input tensor to have least 2 dimensions(got {})'
.format(x.size(0)))
if x.dim() != 2:
raise ValueError('Only 2 dimension tensor are implemented, (got {})'
.format(x.size()))
#print("x: ", x)
#print("target", target)
smoothed_target = self._smooth_label(target, x.size(1), self.e)
x = self.log_softmax(x)
loss = torch.sum(- x * smoothed_target, dim=1)
if self.reduction == 'none':
return loss
elif self.reduction == 'sum':
return torch.sum(loss)
elif self.reduction == 'mean':
return torch.mean(loss)
else:
raise ValueError('unrecognized option, expect reduction to be one of none, mean, sum')
可以看到这个代码和公式有些出入,上述代码公式可写为