损失函数与优化算法

欢迎访问我的博客首页


1. 损失函数


  Pytorch 损失函数的两个参数 size_average 和 reduce 已被废弃,采用 reduction 代替它们。reduction 的取值是 none、mean、sum 三个字符串,分别表示保留维度、取均值、取和,默认是 sum。

2. 二分类损失


  虽然多分类任务更常见,但简单的二分类损失函数也有特定的使用场景,比如在目标检测任务中,如果只需要区分背景与目标,使用二分类任务就可以了。

1. sigmoid 函数

  网络输出的值称为 logit,它是取值在 [ − ∞ , + ∞ ] [-\infty, +\infty] [,+] 的 tensor。这个取值范围太大,需要进行归一化,即把 logit 的值都归一化到 [0, 1] 区间。一般使用 sigmoid 函数和 softmax 函数实现归一化,它们分别用于二分类和多分类。

S i g m o i d ( x ) = e x p ( x ) 1 + e x p ( x ) . (2.1) Sigmoid(x) = \frac{exp(x)}{1 + exp(x)}. \tag{2.1} Sigmoid(x)=1+exp(x)exp(x).(2.1)

1. BCELoss

  BCELoss 全称 binary cross entropy loss,即二分类交叉熵损失。它的 input 和 target 的维度须相同,且 input 的每个值的取值区间须是 [0, 1]。因此,一般把网络的输出经过 Sigmoid 函数转换到 [0, 1] 区间,再作为 BCELoss 的 input。用 x 代表 input,y 代表 target,BCELoss 的计算方法是:

B C E L o s s ( x ) = − 1 × [ y ⋅ l n ( x ) + ( 1 − y ) ⋅ l n ( 1 − x ) ] = { − l n ( 1 − x )   y = 0 , − l n x y = 1. (2.2) BCELoss(x) = -1 \times [ y \cdot ln(x) + (1 - y) \cdot ln(1 - x)] = \begin{cases} -ln(1-x)\ &y=0, \\ -lnx &y=1. \end{cases} \tag{2.2} BCELoss(x)=1×[yln(x)+(1y)ln(1x)]={ln(1x) lnxy=0,y=1.(2.2)

  BCELoss 的定义和用法如下:

def self_BCELoss(logit, target):
    loss = -1 * (target * torch.log(logit) + (1 - target) * torch.log(1 - logit))
    return loss

if __name__ == '__main__':
    logit = torch.Tensor([0.9, 0.1])
    target = torch.Tensor([0, 1])
    BCELoss = torch.nn.BCELoss(reduction='none')
    loss1 = BCELoss(logit, target)
    loss2 = self_BCELoss(logit, target)
    print(loss1)
    print(loss2)

  logit 来自网络输出,它的取值必须在 [0, 1] 区间,所以它一般是对网络输出求 softmax 或 sigmoid 的结果。
  target 是取值为 0 或 1 的标签,表示负样本或正样本。由公式 2.2 可以看出:当标签为 0 时,损失函数是关于 logit 的单调递增函数,即让负样本的 logit 值减小;当标签为 1 时,损失函数是关于 logit 的单调递减函数,即让正样本的 logit 值增大。
  当 reduction=‘none’ 时,loss.shape = logit.shape = target.shape,而且 loss[i] 仅与 logit[i] 和 target[i] 有关。

3. 多分类损失


3.1 交叉熵


  交叉熵损失 CrossEntropyLoss 是常用的多分类损失函数。它可以分解为 Softmax、ln、NLLLoss 三个子操作。

1. softmax 函数

  softmax 函数定义如下:

S o f t m a x ( x ) = e x p ( x ) e x p ( x 1 ) + e x p ( x 2 ) + ⋯ + e x p ( x n ) . (3.1) Softmax(x) = \frac{exp(x)}{exp(x_1) + exp(x_2) + \cdots + exp(x_n)}. \tag{3.1} Softmax(x)=exp(x1)+exp(x2)++exp(xn)exp(x).(3.1)

  其中 Softmax 的输入是向量,Sigmoid 的输入是标量。下面是 Softmax 的定义和用法:

def self_Softmax(logit):
    return torch.exp(logit) / torch.exp(logit).sum()

if __name__ == '__main__':
    logit = torch.Tensor([1, 2, 3])
    Softmax = torch.nn.Softmax(dim=0)
    out1 = Softmax(logit)
    out2 = self_Softmax(logit)
    print(out1)
    print(out2)

2. LogSoftmax

  LogSoftmax 等价于对 Softmax 值取自然对数 log,使用方法如下:

def self_LogSoftmax(logit):
    Softmax = torch.nn.Softmax(dim=0)
    return torch.log(Softmax(logit))

if __name__ == '__main__':
    logit = torch.Tensor([1, 2, 3])
    LogSoftmax = torch.nn.LogSoftmax(dim=0)
    loss1 = LogSoftmax(logit)
    loss2 = self_LogSoftmax(logit)
    print(loss1)
    print(loss2)

3. NLLLoss

  NLLLoss 全称 negative log likelihood loss,即负对数似然损失。NLLLoss 只是根据 target 中的下标返回 input 中对应的值的相反数,所以 target 须是长整型,input 须是浮点型。用法如下:

if __name__ == '__main__':
    input = torch.tensor([[0.1, 0.2, 99.5], [1.3, 2.4, 0.4]], dtype=torch.float32)
    target = torch.tensor([2, 0], dtype=torch.long)
    NLLLoss = torch.nn.NLLLoss(reduction='none')
    output = NLLLoss(input, target)
    print(output)  # tensor([-99.5, -1.3]).

  可以看出,NLLLoss 只是根据 target 中的下标返回 input 中对应的值的相反数。

4. CrossEntropyLoss

  CrossEntropyLoss 即交叉熵损失,它等价于 LogSoftmax(Softmax + ln) + NLLLoss。交叉熵就是 softmax 值的负对数。假如类别总数是 n,网络的输出 l o g i t = [ x 1 , x 2 , . . . , x k , . . . , x n ] logit = [x_1, x_2, ..., x_k, ..., x_n] logit=[x1,x2,...,xk,...,xn],标签 t a r g e t = k target=k target=k。那么交叉熵损失就是

l o s s ( l o g i t ,   t a r g e t ) = − l n ( e x p ( x k ) ∑ i = 1 n e x p ( x i ) ) . (3.2) loss(logit,\ target) = - ln(\frac{exp(x_k)}{\sum_{i=1}^{n}exp(x_i)}).\tag{3.2} loss(logit, target)=ln(i=1nexp(xi)exp(xk)).(3.2)

  通常 logit 和 target 都是多维的,下面就以二维的 logit 和 target 为例介绍 PyTorch 中交叉熵损失函数的用法。

if __name__ == '__main__':
    logit = torch.tensor([[9, 5, 8, 67, 11], [6, 75, 8, 4, 7]], dtype=torch.float32)
    CrossEntropyLoss = torch.nn.CrossEntropyLoss(reduction='none')
    # 标签形式的target。
    label = torch.tensor([3, 1], dtype=torch.long)
    loss1 = CrossEntropyLoss(logit, label)
    print(loss1)
    # 独热编码形式的target。
    one_hot = torch.tensor([[0, 0, 0, 1, 0], [0, 1, 0, 0, 0]], dtype=torch.float32)
    loss2 = CrossEntropyLoss(logit, one_hot)
    print(loss2)
    # 等价。
    NLLLoss = torch.nn.NLLLoss(reduction="none")
    loss3 = torch.log(torch.softmax(logit, dim=1))
    loss3 = NLLLoss(loss3, label)
    print(loss3)

  PyTorch 1.10 及以上版本才支持独热编码形式的 target。损失函数还可以有其它参数:

  1. ignore_index=3:类别 3 的交叉熵置 0。因为 target[1]=3,所以让 z[1]=0。
  2. weight = torch.tensor([1, 2, 3, 4, 5], dtype=torch.float):类别 0 的交叉熵乘1,类别1的交叉熵乘 2,以此类推。因为 target[1]=3,所以 z[1] 要乘 weight[3]。

3.2 平滑标签的交叉熵


1. 平滑标签交叉熵的原理

  交叉熵对异常值敏感。为了解决这个问题,提出了标签平滑损失:

y w a r m = y h o t ( 1 − α ) + α / K . (3.3) y_{warm} =y_{hot}(1-\alpha) + \alpha / K. \tag{3.3} ywarm=yhot(1α)+α/K.(3.3)

其中 K 是类别总数, α \alpha α 是个较小的数,如 0.1, y h o t y_{hot} yhot 是标注值的独热编码。可以看到,平滑标签就是把标注值 y h o t y_{hot} yhot 转换为 y w a r m y_{warm} ywarm

2. 平滑标签交叉熵的实现

  PyTorch 中暂时没有实现平滑标签的交叉熵,我们先看看 TensorFlow 实现的平滑标签的交叉熵。

if __name__ == '__main__':
    logit = tf.convert_to_tensor([[9, 5, 8, 67, 11], [6, 75, 8, 4, 7]], dtype=tf.float32)
    target = tf.convert_to_tensor([[0, 0, 0, 1, 0], [0, 1, 0, 0, 0]])
    loss = tf.compat.v1.losses.softmax_cross_entropy(
        onehot_labels=target, logits=logit, label_smoothing=0.2, reduction=tf.compat.v1.losses.Reduction.NONE)
    print(loss)

  使用 PyTorch 实现平滑标签的交叉熵如下。

def CELoss(input, target, label_smooth=0.2, reduction="none"):
    assert (input.size(0) == target.size(0))
    num_classes = input.size(1)
    if input.size() != target.size():
        target = F.one_hot(target.long(), num_classes)
    if label_smooth is not 0:
        target = target * (1 - label_smooth) + torch.ones_like(target) * label_smooth / num_classes
        # target = target * (1 - label_smooth) + label_smooth / num_classes
    return F.cross_entropy(input, target.float(), reduction=reduction)

if __name__ == '__main__':
    logits = torch.tensor([[9, 5, 8, 67, 11], [6, 75, 8, 4, 7]], dtype=torch.float32)
    label = torch.tensor([3, 1], dtype=torch.long)
    loss1 = CELoss(logits, label)
    one_hot = torch.tensor([[0, 0, 0, 1, 0], [0, 1, 0, 0, 0]], dtype=torch.float32)
    loss2 = CELoss(logits, one_hot)
    print(loss1)
    print(loss2)

  这里实现的平滑标签的交叉熵函数可以接收两种类型的标签。第 7 行和第 8 行等价,它们是公式 3.3 的实现。

3.3 focal loss


  focal loss 用于缓解困难样本问题。

1. 二分类的 focal loss 的原理

  Focal loss 即可以解决正负样本不均衡问题,也可以解决困难样本问题。回顾公式 2.2

B C E L o s s ( x ) = { − l n ( 1 − x ) y = 0 , − l n x y = 1. (2.2) BCELoss(x) = \begin{cases} -ln(1-x) &y=0,\\ -lnx &y=1. \end{cases} \tag{2.2} BCELoss(x)={ln(1x)lnxy=0,y=1.(2.2)

可以看出,无论是正样本(y=1)还是负样本(y=0),减小损失函数的目的都是使 l n ( ⋅ ) ln(\cdot) ln() 为 0。我们可以把公式 2.2 写成下面的等价形式:

B C E L o s s ( x ) = − l n p t 其 中 p t = { 1 − x y = 0 , x y = 1. (3.4) BCELoss(x) = -lnp_t\quad 其中 p_t = \begin{cases} 1-x &y=0,\\ x &y=1. \end{cases} \tag{3.4} BCELoss(x)=lnptpt={1xxy=0,y=1.(3.4)

那么,减小损失函数的目的就是使 p t p_t pt 接近 1。 p t p_t pt 接近 0 则说明样本是困难样本, p t p_t pt 接近 1 则说明样本是容易样本。为了缓解困难样本问题,可以给困难样本的损失函数乘一个较大的权重,给容易样本的损失函数乘一个较小的权重:

B C E L o s s ( x ) = − α t ( 1 − p t ) γ l n ( p t ) = { − α ⋅ x γ ⋅ l n ( 1 − x ) y = 0 , − ( 1 − α ) ⋅ ( 1 − x ) γ ⋅ l n ( x ) y = 1. (3.5) BCELoss(x) = - \alpha_{t} (1-p_t)^\gamma ln(p_t) = \begin{cases} -\alpha \cdot x^\gamma \cdot ln(1-x) &y=0,\\ -(1 - \alpha) \cdot (1-x)^\gamma \cdot ln(x) &y=1. \end{cases} \tag{3.5} BCELoss(x)=αt(1pt)γln(pt)={αxγln(1x)(1α)(1x)γln(x)y=0,y=1.(3.5)

  如果 p t p_t pt 接近 1,则说明这个样本是易分的容易样本,于是损失函数乘以接近 0 的 ( 1 − p t ) γ (1-p_t)^\gamma (1pt)γ;如果 p t p_t pt 接近 0,则说明这个样本是难分的困难样本,于是损失函数乘以接近 1 的 ( 1 − p t ) γ (1-p_t)^\gamma (1pt)γ。经验表明 γ \gamma γ 取 2 效果最好。
  公式 3.5 还使用了系数 α \alpha α,它的取值为 0.25,作用是平衡正负样本。注意,是正样本乘 0.25,负样本乘 0.75。

2. 多分类的 focal loss

  对多分类来说,因为包括背景的每一类都有标签,所以每一类都是正样本,focal loss 的计算也就更简单:对网络预测的结果求 softmax 的结果就是 p t p_t pt,然后把 p t p_t pt 的负对数值与 ( 1 − p t ) γ (1 - p_t)^\gamma (1pt)γ 相乘就是 focal loss 损失。比如包含背景共有 n 类,网络的输出是 x = [ x 1 , x 2 , . . . , x k , . . . , x n ] x = [x_1, x_2, ..., x_k, ..., x_n] x=[x1,x2,...,xk,...,xn],标签 t a r g e t = [ k ] target =[k] target=[k]。那么 focal loss 的计算步骤如下:

{ p = s i g m o i d ( l o g i t ) p t = p ⋅ t a r g e t + ( 1 − p ) ⋅ ( 1 − t a r g e t ) α = α ⋅ t a r g e t + ( 1 − α ) ⋅ ( 1 − t a r g e t ) c e _ l o s s = − [ ( 1 − t a r g e t ) ⋅ l n ( 1 − p ) + t a r g e t ⋅ l n ( p ) ] l o s s = α ( 1 − p t ) γ ⋅ c e _ l o s s (3.6) \begin{cases} p &= sigmoid(logit) \\ p_t &= p \cdot target + (1 - p) \cdot (1 - target) \\ \alpha &= \alpha \cdot target + (1 - \alpha) \cdot (1 - target) \\ ce\_loss &= - [(1 - target) \cdot ln(1 - p) + target \cdot ln(p)] \\ loss &= \alpha (1 - p_t) ^ \gamma \cdot ce\_loss \end{cases} \tag{3.6} pptαce_lossloss=sigmoid(logit)=ptarget+(1p)(1target)=αtarget+(1α)(1target)=[(1target)ln(1p)+targetln(p)]=α(1pt)γce_loss(3.6)

  公式 3.6 并不复杂,因为 target 是独热编码,取值只有 0 和 1。focal loss 把 n 分类问题看成 n 个二分类问题,对每个二分类问题按公式 3.5 计算损失,返回 n 个二分类问题的 n 个损失值。再根据需要求和或求均值。

3. focal loss 的实现

  下面的 focal loss 来自 torchvision,它基于 sigmoid 函数,对每个类别进行二分类,所以称为 sigmoid_focal_loss。

def sigmoid_focal_loss(inputs, targets, alpha=0.25, gamma=2, reduction="none"):
    ce_loss = F.binary_cross_entropy_with_logits(inputs, targets, reduction="none")
    p = torch.sigmoid(inputs)
    p_t = p * targets + (1 - p) * (1 - targets)
    loss = ce_loss * ((1 - p_t) ** gamma)
    if alpha >= 0:
        alpha_t = alpha * targets + (1 - alpha) * (1 - targets)
        loss = alpha_t * loss
    if reduction == "mean":
        loss = loss.mean()
    elif reduction == "sum":
        loss = loss.sum()
    return loss

if __name__ == '__main__':
    logit = torch.tensor([[1, 2, 3], [4, 5, 6]], dtype=torch.float32)
    target = torch.tensor([[0, 0, 1], [0, 0, 1]], dtype=torch.float32)
    loss1 = torchvision.ops.focal_loss.sigmoid_focal_loss(logit, target)
    loss2 = sigmoid_focal_loss(logit, target)
    print(loss1)
    print(loss2)

  其中 binary_cross_entropy_with_logics 的计算过程基于 BCELoss:

t1 = (torch.ones_like(targets) - targets) * torch.log(torch.ones_like(p) - p)
t2 = targets * torch.log(p)
ce_loss = -1 * (t1 + t2)

4. 回归损失


  下面介绍L1、L2、平滑L1三种损失函数。平滑L1损失是L1损失和L2损失的结合,所以一般使用平滑L1损失。

1. L1损失

L 1 L o s s ( x , y ) = ∣ x i − y i ∣ . L1Loss(x, y) =|x_i - y_i|. L1Loss(x,y)=xiyi.

loss = torch.nn.L1Loss(reduction='sum')
x = torch.tensor([[1, 2], [3, 4.]])
y = torch.tensor([[5, 6], [7, 8.]])
z = loss(x, y)

2. L2损失

M S E L o s s ( x , y ) = ( x i − y i ) 2 . MSELoss(x, y) = (x_i - y_i)^2. MSELoss(x,y)=(xiyi)2.

loss = torch.nn.MSELoss(reduction='sum')

3. 平滑L1损失

S m o o t h L 1 L o s s ( x , y ) = { 0.5 ( x i − y i ) 2   ∣ x i − y i ∣ < 1 ∣ x i − y i ∣ − 0.5   ∣ x i − y i ∣ ≥ 1 SmoothL1Loss(x, y) = \begin{cases} 0.5(x_i - y_i)^2\ &|x_i - y_i|<1 \\ |x_i - y_i|-0.5\ &|x_i - y_i| \geq 1 \end{cases} SmoothL1Loss(x,y)={0.5(xiyi)2 xiyi0.5 xiyi<1xiyi1

loss = torch.nn.SmoothL1Loss(reduction='sum')

对这三种损失函数而言

L损失

  L1 损失没有解析解,求解速度慢。L2 损失在差较大时容易梯度爆炸,但它有解析解,计算速度较快。SmoothL1Loss 损失在差较小(小于1)时使用 L2Loss 损失,在差较大时使用 L1Loss 损失,兼具两个的优点,系数 0.5 是为了使该分段函数连续。

5. 优化算法


  Adam 和 SGD 是深度学习最常用的两个优化算法。

# 自适应矩估计
optimer1 = torch.optim.Adam(params, lr=1e-3, betas=(0.9, 0.999), eps=1e-8, weight_decay=0, amsgrad=False)
# 随机梯度下降
optimer2 = torch.optim.SGD(params, lr=required, momentum=0, dampening=0, weight_decay=0, nesterov=False)

optimer3=torch.optim.AdamW(params, lr=1e-3, betas=(0.9, 0.999), eps=1e-8, weight_decay=1e-2, amsgrad=False)

5.1 Adam


  自适应矩估计算法:鲁棒性强但可能找不到最优解。

  1. betas:梯度的运行均值系数和方差系数。
  2. eps:epsilon,防止除 0 而添加的小数。
  3. amsgrad:是否采用 AMSGrad 优化方法。AMSGrad 是 Adam 的改进,通过添加额外的约束,使学习率始终为正值。

5.2 SGD


  随机梯度下降算法:容易出现震荡导致下降速度慢。所以可以选择带动量的随机梯度下降算法 SGDM 和自适应梯度算法 AdaGrad。
  动量为了避免陷入局部最优解。动量是带有

  1. momentum:动量系数。
  2. dampening:动量抑制系数。
  3. nesterov:是否使用 Nesterov 动量。

6. 参考


  1. 优化算法与 Adam 的缺点
  2. PyTorch 的 Adam 算法详解
  3. SGD 算法详解
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值