有关熵的一系列认知这个
1、目前分类损失函数为何多用交叉熵,而不是KL散度。首先损失函数的功能是通过样本来计算模型分布与目标分布间的差异,在分布差异计算中,KL散度是最合适的。但在实际中,某一事件的标签是已知不变的(例如我们设置猫的label为1,那么所有关于猫的样本都要标记为1),即目标分布的熵为常数。而根据下面KL公式可以看到,KL散度 - 目标分布熵 = 交叉熵(这里的“-”表示裁剪)。所以我们不用计算KL散度,只需要计算交叉熵就可以得到模型分布与目标分布的损失值。从上面介绍,知道了模型分布与目标分布差异可用交叉熵代替KL散度的条件是目标分布为常数。如果目标分布是有变化的(如同为猫的样本,不同的样本,其值也会有差异),那么就不能使用交叉熵,例如蒸馏模型的损失函数就是KL散度,因为蒸馏模型的目标分布也是一个模型,该模型针对同类别的不同样本,会给出不同的预测值(如两张猫的图片a和b,目标模型对a预测为猫的值是0.6,对b预测为猫的值是0.8)。
分割的损失函数
底下写的都很烂,直接看这个博客吧
从图可以看出大多negative 样本位于背景区,而极少的 negative 位于前景和背景的过渡区。位于背景区的样本分类相对容易,成为easy negative, 训练时对应的score很大,loss 相对很小,在计算反向梯度时,造成easy negative example对参数收敛作用有限。从而导致hard negative 样本很难得到更新,效果不佳
————————————————
版权声明:本文为优快云博主「Biyoner」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/Biyoner/article/details/84728417
BCELoss
最近做的matting工作,matting类似于语义分割,而我只需要大致分为两类-前景和背景,前期实验选择了这个,尽管还有过渡的unknow区域。
torch的BCELoss,二元交叉熵损失
可以看到输入,gt对应即可,也就是满足input.shape==target.shape, 都可以计算出结果,算是比较好用的损失函数了。以下自己实现了一个
from .base_loss import baseLoss
import torch
class BCELoss(baseLoss):
def __init__(self):
super(BCELoss, self).__init__()
def forward(self, out, gt):
loss = torch.mean(-(gt*torch.log(out) + (1-gt)*torch.log(1-out)))
return loss
- 注意上面计算二元交叉熵loss后,需要使用torch.mean(),否则抛出错误
RuntimeError: grad can be implicitly created only for scalar outputs
loss变为nan和inf
训练过程中使用这个loss,loss都变成了nan和inf,猜测可能是0带来的问题,使用的几个loss只有bceloss可能带来这个问题,其他都是类似于l1loss。
怎样解决log_softmax带来的问题呢?
softmax看起来似乎不会带来过大/小的问题
e
x
i
∑
e
x
j
\frac{e^{x_i}}{\sum e^{x_j}}
∑exjexi
上面公式只会输出(0,1)的概率值,他的意思是分类概率不会是0%和100%,只能无限接近?
o
u
t
∈
(
0
,
1
)
out\in(0,1)
out∈(0,1)
log函数不能为0,不论是
l
o
g
(
o
u
t
)
log(out)
log(out)还是
l
o
g
(
1
−
o
u
t
)
log(1-out)
log(1−out)值域都是(0,1)啊,理论上趋近于0的时候会出现趋近于-inf,所以给以上函数修改了下,加了个1e-10,这样即使是0,计算值也是-10
loss = torch.mean(-(gt*torch.log(out+1e-10) + (1-gt)*torch.log(1-out+1e-10)))
以上都是猜测,用torch实测一下吧
f = nn.BCELoss()
a = torch.tensor([0.99, min_num])
b = torch.tensor([1, 0.0])
c = f(a, b)
print(c)
d = torch.mean(-(b*torch.log(a) + (1-b)*torch.log(1-a)))
print(d)
'''
min_num=1e-45
out:
tensor(0.0050)
tensor(0.0050)
min_num=1e-46
out:
tensor(0.0050)
tensor(nan)
'''
所以torch内部应该有机制避免了这个问题,自己写的代码就不可避免了(if min_num<=1e-46),引以注意
但是美中不足,以上需要网络的最后输出激活函数是sigmoid
,如果网络没有用激活函数,是原始的输出,那么计算之前需要用torch.sigmoid()
预处理下
BCEWithLogitsLoss
torch BCEWithLogitsLoss这个函数帮你做好了逻辑回归
CrossEntropyLoss
但是如果我的最后一层是softmax层怎么办呢?没关系CrossEntropyLoss函数帮你解决这个问题
CrossEntropyLoss=nn.LogSoftmax()+nn.NLLLoss()
但是他的函数设计比较违和,从下面可以看出,input和target维度是不一样的,他的target相当于不是one_hot编码,按照文档,可以使用多维数组作为输入,但是总归没有BCE用着顺手
KL散度
先简单介绍下KL散度,一直都只知道KL散度是衡量两个分布之间的距离度量,但是没有什么涉猎,前段时间为了在知识蒸馏过程中衡量学生和教师网络中间的特征层之间的分布,直接使用torch.nn.KLDivLoss
,kl(feature_s, feature_t)
是负值,即使两个相同的特征层之间KL散度也是-1
.这令我非常困惑。当时采用了论文1的方法代替,暂且称之为pikd
损失,但是效果仅仅提升了一点点,现在从头来再看KL散度
参考博客1,需要更正下上一段第一句话我对KL散度的理解,KL散度又叫相对熵,是描述两个 *概率分布* p q之间差异的一种方法
,公式如下
D
(
P
∣
∣
Q
)
=
∑
p
i
l
o
g
p
i
q
i
D{\left({P||Q}\right)}=\sum{p_ilog{\frac{p_i}{q_i}}}
D(P∣∣Q)=∑pilogqipi
理论上PQ元素个数不用相等,只要分布的元素一致即可,如{1,2},{1, 2,2},因为公式计算的是元素的概率分布,即
p
i
p_i
pi和
q
i
q_i
qi
而torch这个函数是真的🐕,看他的定义
torch.nn.functional.kl_div(input, target, size_average=None, reduce=None, reduction='mean')
Tensor kl_div(const Tensor& input, const Tensor& target, int64_t reduction) {
auto zeros = at::zeros_like(target);
auto output_pos = target * (at::log(target) - input);
auto output = at::where(target > 0, output_pos, zeros);
return apply_loss_reduction(output, reduction);
}
我们看上面的定义,他默认input
是log
之后的结果,所以我们要计算input和target之间概率分布的差异,其实是不是也可以理解为在target的条件下,input和target之间的比值越小越好,即
l
o
g
p
i
q
i
log\frac{p_i}{q_i}
logqipi变小,那么乘以target特征层的数值,他本来的意思是乘以概率,可能是乘以softmax后的输出,我猜测feature的激活数值在一定程度上代表了特征的重要性,所以乘以target的激活值也有一定的意义,但是数值较大,可能归一化效果较好?
所以torch KL散度的正确用法应该将输入input求log后输入函数才能得到正确的结果
kl = nn.KLDivLoss()
loss = kl(net_output.log(), target)
#loss = kl(student_out.log(), teacher_out)
PIKD
pixel-wise distillation
像素级别的知识蒸馏损失,可以衡量两个特征层之间的分布差异?猜的
看公式和代码就知道了
特征公式:
1
C
∑
c
=
1
C
F
i
\frac{1}{C}\sum_{c=1}^{C}{F_i}
C1∑c=1CFi 输出是一个
N
×
1
×
H
×
W
N\times1\times H\times W
N×1×H×W的特征统计
让后求两者L2损失即可
代码
class pikdLoss(nn.Module):
def __init__(self):
super(pikdLoss, self).__init__()
def get_hidden_loss(self, s, t, e=1e-6):
s_mean = s.mean(dim=1)
t_mean = t.mean(dim=1)
loss = torch.mean(torch.sqrt((s_mean - t_mean)**2 + e**2))
return loss
def forward(self, h, h_t):
out = []
for s, t in zip(h, h_t):
out.append(self.get_hidden_loss(s, t))
return torch.mean(torch.tensor(out))
L0 loss - noise2noise
在2018年noisetonoise中指出不同的噪声使用不同的约束函数会有不同的效果,对于高斯噪声L2损失约束均值,对于泊松噪声L1损失约束中位数,那么对于多脉冲噪声近似的L0损失约束效果最佳,但是L0范数不好优化,对此没有意义?所以他经过一番推导近似了一个L0损失,如果网络去噪效果不好可以尝试下
公式:
(
∣
g
t
−
o
u
t
∣
+
1
e
−
8
)
γ
(|gt-out|+ 1e-8)^{\gamma}
(∣gt−out∣+1e−8)γ
上述
γ
\gamma
γ从2线性衰减到0,仿照公式写了一个不知道对不对
class L0Loss(nn.Module):
def __init__(self, epochs):
super(L0Loss, self).__init__()
self.epochs = epochs
def forward(self, gt, raw, epoch):
return torch.mean(torch.pow((torch.abs(gt - raw) + 1e-8), (1 - epoch / self.epochs) * 2))
基于知识蒸馏的超分辨率卷积神经网络压缩方法 ↩︎