带权重的损失函数nn.crossEntropyLoss中的weight使用

深度学习处理数据时,经常遇到这类情况:

某一个类别样本数据非常多,但是其他几个类别的样本数据却非常少, 这便是常见的类别间的样本不均衡。

一般情况下,假n u m m a x num_{max}n u m ma x ​ 表示数量最多类别的样本个数,n u m m i n num_{min}n u m min ​表示数量最少类别的样本个数,当n u m m a x n u m m i n ≤ 10 \frac{num_{max}}{num_{min}}\leq10 n u m min ​n u m ma x ​​≤10的时候,是不需要考虑样本不平衡问题的。

当它们的比值大于(或者远远)10的时候是要考虑样本不平衡问题,为什么要考虑样本不平衡问题呢?接下来我们来解释一下:

假设有三类,标签类别为0, 1, 2,所对应的样本数量为100000,100, 10。

此时就有一个问题,在网络学习的过程中,假设预测出来的标签都是0(100000个样本),它的准确率为100000 100110 ≈ 0.9989 \frac{100000}{100110}\approx0.9989 100110 100000 ​≈0.9989 ,将近100%,所以模型就会朝着拟合标签0的方向更新,导致对标签0的样本过拟合,对其它两个类别的样本欠拟合,泛化能力很差。

这在医学类的数据中尤为明显, normal 类别样本数目经常大于 abnormal;

torch.utils.data.WeightedRandomSampler(weights, num_samples, replacement=True, generator=None)

该函数的作用: 按照样本采样权重,取样本的位置索引。

1.1 参数介绍

weights: 样本的采样权重, 并非样本对应的类别权重; 通常是通过 样本的采样权重= 该样本对应类别权重的倒数;
num_sampler: 生成索引的个数, 这些索引代表了样本所在位置的索引;
replacement: 用于控制采样是否有放回的, 如果有放回的,即重复取设置为True, 默认为True;
generator (Generator) – Generator used in sampling. 采样时用的生成器

1.2 样本类别权重与样本采样权重

样本类别权重
sample_class_weight: 是指该样本所属的哪一个类别, 该类别占比所有类别的权重;

样本采样权重:sample_sampling_weight: 即上述函数中的 weights
这里是指给每个样本分配采样权重,采样权重越大,代表了该样本越有可能被取到;
即假设有一千个样本, 那么就应该有一千个对应的样本权重;

注意,样本采样权重是如何生成的?
通常是 样本的采样权重= 该样本对应的类别权重的倒数
请先自己思考出来,原因如下。

现实中,如果某个类别的样本数量很少,
那么该样本对应的类别权重则很小。
类别权重代表了:该类别的样本占比所有类别总样本的比率很小;

但是,正是因为该类别的样本少,所以我们就需要给类别下的样本分配较高的采样权重。
样本的采样权重高,从而抽取到该类别的样本的次数就会越多;

有种负负得正的思想。 即样本少的类别,类别权重小。
但是该类别下的样本我们需要分配高的样本采样权重。

1.3 输出样本索引

torch.utils.data.WeightedRandomSampler(weights, num_samples, replacement=True, generator=None)

1.4 代码示例

假设样本对应的类别权重,样本采样权重分别如下:


class_weights = [0.5, 0.3, 0.2, 0.1],

sample_weights = (1/ torch.Tensor(class_weights))
sample_weights:
Out[37]: tensor([ 2.0000,  3.3333,  5.0000, 10.0000])

sample_index =  list(torch.utils.data.sampler.WeightedRandomSampler( sample_weights, 3, replacement = True))
sample_index
Out[39]: [3, 3, 0]

从而注意到,输出的样本索引, 样本采样权重越大, 则该样本的索引就越容易取到

2.1 熵的基本概念

  • 自信息:I ( x ) = − log ⁡ [ p ( x ) ] \mathrm{I}(x)=-\log [p(x)]I (x )=−lo g [p (x )]
  • 信息熵就是求自信息的期望:H ( P ) = E x ∼ p [ I ( x ) ] = − ∑ i N P ( x i ) log ⁡ P ( x i ) \mathrm{H}(\mathrm{P})=E{x \sim p}[I(x)]=-\sum{i}^{N} P\left(x{i}\right) \log P\left(x{i}\right)H (P )=E x ∼p [I (x )]=−∑i N P (x i )lo g P (x i )
  • 相对熵,也被称为 KL 散度,用于衡量两个分布的相似性(距离):D K L ( P , Q ) = E x ∼ p [ log ⁡ P ( x ) Q ( x ) ] \boldsymbol{D}{K L}(\boldsymbol{P}, \boldsymbol{Q})=\boldsymbol{E}{\boldsymbol{x} \sim p}\left[\log \frac{\boldsymbol{P}(\boldsymbol{x})}{Q(\boldsymbol{x})}\right]D K L (P ,Q )=E x ∼p [lo g Q (x )P (x )​]。

其中P ( X ) P(X)P (X )是真实分布,Q ( X ) Q(X)Q (X )是拟合的分布

  • 交叉熵:H ( P , Q ) = − ∑ i = 1 N P ( x i ) log ⁡ Q ( x i ) \mathrm{H}(\boldsymbol{P}, \boldsymbol{Q})=-\sum{i=1}^{N} \boldsymbol{P}\left(\boldsymbol{x}{i}\right) \log \boldsymbol{Q}\left(\boldsymbol{x}_{i}\right)H (P ,Q )=−∑i =1 N P (x i )lo g Q (x i ​)
  • 相对熵展开可得:
    D K L ( P , Q ) = E x ∼ p [ log ⁡ P ( x ) Q ( x ) ] = E x ∼ p [ log ⁡ P ( x ) − log ⁡ Q ( x ) ] = ∑ i = 1 N P ( x i ) [ log ⁡ P ( x i ) − log ⁡ Q ( x i ) ] = ∑ i = 1 N P ( x i ) log ⁡ P ( x i ) − ∑ i = 1 N P ( x i ) log ⁡ Q ( x i ) = H ( P , Q ) − H ( P ) \begin{aligned} \boldsymbol{D}{K L}(\boldsymbol{P}, \boldsymbol{Q}) &=\boldsymbol{E}{\boldsymbol{x} \sim p}\left[\log \frac{P(x)}{Q(\boldsymbol{x})}\right] \ &=\boldsymbol{E}{\boldsymbol{x} \sim p}[\log P(\boldsymbol{x})-\log Q(\boldsymbol{x})] \ &=\sum{i=1}^{N} P\left(x{i}\right)\left[\log P\left(\boldsymbol{x}{i}\right)-\log Q\left(\boldsymbol{x}{i}\right)\right] \ &=\sum{i=1}^{N} P\left(\boldsymbol{x}{i}\right) \log P\left(\boldsymbol{x}{i}\right)-\sum{i=1}^{N} P\left(\boldsymbol{x}{i}\right) \log \boldsymbol{Q}\left(\boldsymbol{x}_{i}\right) \ &= H(P,Q) -H(P) \end{aligned}D K L (P ,Q )​=E x ∼p [lo g Q (x )P (x )​]​=E x ∼p [lo g P (x )−lo g Q (x )]​=∑i =1 N P (x i )[lo g P (x i )−lo g Q (x i )]​=∑i =1 N P (x i )lo g P (x i )−∑i =1 N P (x i )lo g Q (x i ​)​=H (P ,Q )−H (P )​
    所以交叉熵 = 信息熵 + 相对熵,即H ( P , Q ) = D K L ( P , Q ) + H ( P ) \mathrm{H}(\boldsymbol{P}, \boldsymbol{Q})=\boldsymbol{D}{K \boldsymbol{L}}(\boldsymbol{P}, \boldsymbol{Q})+\mathrm{H}(\boldsymbol{P})H (P ,Q )=D K L (P ,Q )+H (P ),

又由于信息熵H ( P ) H(P)H (P )是固定的,因此优化交叉熵H ( P , Q ) H(P,Q)H (P ,Q )等价于优化相对熵D K L ( P , Q ) D{KL}(P,Q)D K L (P ,Q )。

2.2 损失函数(Loss Function)

损失函数(Loss Function)
是计算一个样本的模型输出与真实标签的差异

损失函数的计算

对于每一个样本的 Loss 计算公式为:

H ( P , Q ) = − ∑ i = 1 N P ( x i ) log ⁡ Q ( x i ) = l o g Q ( x i ) \mathrm{H}(\boldsymbol{P}, \boldsymbol{Q})=-\sum{i=1}^{N} \boldsymbol{P}\left(\boldsymbol{x}{\boldsymbol{i}}\right) \log Q\left(\boldsymbol{x}{\boldsymbol{i}}\right) = logQ(x{i})H (P ,Q )=−∑i =1 N P (x i )lo g Q (x i )=l o g Q (x i ),因为N = 1 N=1 N =1,P ( x i ) = 1 P(x_{i})=1 P (x i ​)=1。

所以loss ⁡ ( x , class ) = − log ⁡ ( exp ⁡ ( x [ class ] ) ∑ j exp ⁡ ( x [ j ] ) ) = − x [ class ] + log ⁡ ( ∑ j exp ⁡ ( x [ j ] ) ) \operatorname{loss}(x, \text { class })=-\log \left(\frac{\exp (x[\text { class }])}{\sum{j} \exp (x[j])}\right)=-x[\text { class }]+\log \left(\sum{j} \exp (x[j])\right)loss (x ,class )=−lo g (∑j e x p (x [j ])e x p (x [class ])​)=−x [class ]+lo g (∑j exp (x [j ]))。

此外, 代价函数和目标函数

  • 代价函数(Cost Function)是计算整个样本集的模型输出与真实标签的差异,是所有样本损失函数的平均值
    cos ⁡ t = 1 N ∑ i N f ( y i ∧ , y i ) \cos t=\frac{1}{N} \sum{i}^{N} f\left(y{i}^{\wedge}, y_{i}\right)cos t =N 1 ​∑i N f (y i ∧,y i ​)
  • 目标函数(Objective Function)就是代价函数加上正则项

2.3 给定类别权重的损失函数

用于类别不均衡时, 给每个类别的loss 加上权重
带类别的权重,则loss ⁡ ( x , class ) = weight ⁡ [ class ] ( − x [ class ] + log ⁡ ( ∑ j exp ⁡ ( x [ j ] ) ) ) \operatorname{loss}(x, \text { class })=\operatorname{weight}[\text { class }]\left(-x[\text { class }]+\log \left(\sum_{j} \exp (x[j])\right)\right)loss (x ,class )=weight class

nn.CrossEntropyLoss()

nn.CrossEntropyLoss(weight=None, size_average=None, ignore_index=-100, reduce=None, reduction='mean')

作用:
nn.LogSoftmax()和nn.NLLLoss()结合,计算交叉熵。nn.LogSoftmax()的作用是把输出值归一化到了 [0,1] 之间。

  • weight:各类别的 loss 设置权值
  • ignore_index:忽略某个类别的 loss 计算
  • reduction:计算模式,none, sum, mean(default)

注意这里的reduction: none(逐个输出batch中,每个样本的loss), sum(对当前batch 中所有样本的loss求和,返回标量), mean(对一个batch加权平均,返回标量),  默认值是mean;
(mean, 如果给定了每个类别的权重,即上面的weight 参数给定了; 则使用给定的权重进行加权平均; 用于样本不均衡时使用)

weight – 为每个类手动重新缩放权重。如果给定,则必须是大小为”C”的张量
size_average – 已弃用(参见:attr:”reduction”)。默认情况下,损失是批次中每个损失元素的平均值。请注意,对于某些损失,每个样本有多个元素。如果字段 :attr:”size_average” 设置为”False”,则每个小批量的损失相加。当reduce为”False”时忽略。默认值:”真”
ignore_index – 指定一个被忽略且不对输入梯度产生影响的目标值。当 :attr:”size_average”为”True”时,损失是针对未忽略目标的平均值。
reduce – 已弃用(参见:attr:”reduction”)。默认情况下,损失是每个小批量的观测值的平均值或总和,具体取决于 :attr:”size_average”。当 :attr:’reduce’ 为 ”False’ 时,返回每个批处理元素的损失并忽略 :attr:’size_average’。默认值:”真”
reduction – 指定要应用于输出的缩减:”无”|”卑鄙” |”总和”。”无”:不应用缩减,”平均值”:取输出的加权平均值,”总和”:对输出求和。注意::attr:’size_average’ 和 :attr:’reduce’ 正在被弃用,同时,指定这两个参数中的任何一个都将覆盖 :attr:’reduction’。默认值:”平均”

3.1 普通损失函数

下面设有 3 个样本做 2 分类。inputs 的形状为 3 × 2 3 \times 2 3 ×2,表示每个样本有两个神经元输出两个分类。target 的形状为 3 × 1 3 \times 1 3 ×1,注意类别从 0 开始,类型为torch.long。

import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np

inputs = torch.tensor([[1, 2], [1, 3], [1, 3]], dtype=torch.float)
target = torch.tensor([0, 1, 1], dtype=torch.long)

loss_f_none = nn.CrossEntropyLoss(weight=None, reduction='none')
loss_f_sum = nn.CrossEntropyLoss(weight=None, reduction='sum')
loss_f_mean = nn.CrossEntropyLoss(weight=None, reduction='mean')

loss_none = loss_f_none(inputs, target)
loss_sum = loss_f_sum(inputs, target)
loss_mean = loss_f_mean(inputs, target)

print("Cross Entropy Loss:\n ", loss_none, loss_sum, loss_mean)

则三个样本的损失输出如下:

Cross Entropy Loss:
  tensor([1.3133, 0.1269, 0.1269]) tensor(1.5671) tensor(0.5224)

3.2 带权重的损失函数

看带有类别权重的损失计算,首先设置类别的权重向量weights = torch.tensor([1, 2], dtype=torch.float),向量的元素个数等于类别的数量,然后在定义损失函数时把weight参数传进去。
输出为:

weights:  tensor([1., 2.])
tensor([1.3133, 0.2539, 0.2539]) tensor(1.8210) tensor(0.3642)

注意下面的1, 2,2, 分别代表了三个样本的权重.

权值总和为:1 + 2 + 2 = 5 1+2+2=5 1 +2 +2 =5,所以加权平均的 loss 为:1.8210 ÷ 5 = 0.3642 1.8210\div5=0.3642 1.8210 ÷5 =0.3642,

3.3 按照公式计算

为了验证正确性, 我们根据交叉熵的公式进行计算;

单个样本的 loss 计算公式loss ⁡ ( x , class ) = − log ⁡ ( exp ⁡ ( x [ class ] ) ∑ j exp ⁡ ( x [ j ] ) ) = − x [ class ] + log ⁡ ( ∑ j exp ⁡ ( x [ j ] ) ) \operatorname{loss}(x, \text { class })=-\log \left(\frac{\exp (x[\text { class }])}{\sum{j} \exp (x[j])}\right)=-x[\text { class }]+\log \left(\sum{j} \exp (x[j])\right)loss (x ,class )=−lo g (∑j e x p (x [j ])e x p (x [class ])​)=−x [class ]+lo g (∑j exp (x [j ])),

可以使用以下代码来手动计算第一个样本的损失

idx = 0

input_1 = inputs.detach().numpy()[idx]
target_1 = target.numpy()[idx]

x_class = input_1[target_1]

sigma_exp_x = np.sum(list(map(np.exp, input_1)))
log_sigma_exp_x = np.log(sigma_exp_x)

loss_1 = -x_class + log_sigma_exp_x

print("第一个样本loss为: ", loss_1)

结果为:1.3132617

同样将带权重的带入计算加权loss

weights = torch.tensor([1, 2], dtype=torch.float)
weights_all = np.sum(list(map(lambda x: weights.numpy()[x], target.numpy())))
mean = 0
loss_f_none = nn.CrossEntropyLoss(reduction='none')
loss_none = loss_f_none(inputs, target)
loss_sep = loss_none.detach().numpy()
for i in range(target.shape[0]):

x_class = target.numpy()[i]
tmp = loss_sep[i] * (weights.numpy()[x_class] / weights_all)
mean += tmp

print(mean)

结果为 0.3641947731375694

3.4 nn.NLLLoss

nn.NLLLoss(weight=None, size_average=None, ignore_index=-100, reduce=None, reduction='mean')

功能:实现负对数似然函数中的符号功能
主要参数:

  • weight:各类别的 loss 权值设置
  • ignore_index:忽略某个类别
  • reduction:计算模式,,可以为 none(逐个元素计算),sum(所有元素求和,返回标量),mean(加权平均,返回标量)

每个样本的 loss 公式为:l n = − w y n x n , y n l{n}=-w{y{n}} x{n, y_{n}}l n =−w y n x n ,y n ​。还是使用上面的例子,第一个样本的输出为 [1,2],类别为 0,则第一个样本的 loss 为 -1;第一个样本的输出为 [1,3],类别为 1,则第一个样本的 loss 为 -3。

weights = torch.tensor([1, 1], dtype=torch.float)

loss_f_none_w = nn.NLLLoss(weight=weights, reduction='none')
loss_f_sum = nn.NLLLoss(weight=weights, reduction='sum')
loss_f_mean = nn.NLLLoss(weight=weights, reduction='mean')

loss_none_w = loss_f_none_w(inputs, target)
loss_sum = loss_f_sum(inputs, target)
loss_mean = loss_f_mean(inputs, target)

print("\nweights: ", weights)
print("NLL Loss", loss_none_w, loss_sum, loss_mean)

输出如下:

weights:  tensor([1., 1.])
NLL Loss tensor([-1., -3., -3.]) tensor(-7.) tensor(-2.3333)

Pytorch的 nn.CrossEntropyLoss()的weight 使用场景:

weight : 损失函数中的类别权重

现实工程中,经常出现以下情况:

label标注的0-3四类,0类的比重过大,1类其次,2,3类都很少,怎么使用loss的weight来减轻样本不平衡问题?

weight参数该如何设置?

带权重的损失函数nn.crossEntropyLoss中的weight使用

如何设置损失函数中各个类别所对应的权重?
才能提升分类的性能。

此时,在损失函数中的权重,

比如设置loss weight代码,(0.1、 0.8、 1、 1 )四个类别的比例是我自己设置的)

weight=torch.from_numpy(np.array([0.1, 0.8, 1.0, 1.0])).float()

criterion = nn.CrossEntropyLoss(weight=weight).to(device)

那 nn.CrossEntropyLoss()的weight如何解决样本不平衡问题的。

当类别中的样本数量不均衡的时候,

  • 对于训练图像数量较少的类,你给它更多的权重,这样如果网络在预测这些类的标签时出错,就会受到更多的惩罚。
  • 对于具有大量图像的类,您可以赋予它较小的权重。那我们如何选择weight的值呢?

一般有两种方式(建议第二种):

第一种,用样本数的倒数当做权重。即 1 C l a s s S i z e \frac{1}{ClassSize}Cl a ss S i ze 1 ​ 。
用上述的三分类表示就是, w e i g h t = [ 1 100000 , 1 100 , 1 10 ] weight=[\frac{1}{100000}, \frac{1}{100}, \frac{1}{10}]w e i g h t =[100000 1 ​,100 1 ​,10 1 ​] 。之前测试过几次,这种方式会造成分类准确率下降。

第二种,Max(Numberof occurrences in most common class) / (Number of occurrences in rare classes)。即用类别中最大样本数量除以当前类别样本的数量,作为权重系数。

用上述的三分类表示就是, w e i g h t = [ 100000 100000 , 100000 100 , 100000 10 ] = [ 1.0 , 1000 , 10000 ] weight=[\frac{100000}{100000}, \frac{100000}{100}, \frac{100000}{10}]=[1.0, 1000, 10000]w e i g h t =[100000 100000 ​,100 100000 ​,10 100000 ​]=[1.0 ,1000 ,10000 ]

代码表示如下:

weights = [1.0, 1000, 10000]
class_weights = torch.FloatTensor(weights).to(device)
criterion = nn.CrossEntropyLoss(weight=class_weights)

reference:

https://pytorch.zhangxiann.com/4-mo-xing-xun-lian/4.2-sun-shi-han-shu

Original: https://blog.csdn.net/chumingqian/article/details/126625183
Author: mingqian_chu
Title: 带权重的损失函数nn.crossEntropyLoss中的weight使用

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/731128/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球