损失函数

实际任务中使用到的损失函数记录

矩阵所有元素的平方和再开方,他是是向量二范式的拓展类比。

$$ ||A||_F = \sqrt{\sum a_{ij}^2} $$

如何作为距离度量呢?

可以利用如下方式,如果标签矩阵为$B$(通常为一个batch里的多个样本标签向量组成,如标签向量为4维,batch_size 为128)则$B$的形状为(128,4)),距离度量公式为 $$ \mathcal L_F = ||A-B|| $$

作为距离度量方式不详 $$ \begin{aligned} ||A||_*=\operatorname{tr}\left(\sqrt{A^{T} A}\right) &=\operatorname{tr}\left(\sqrt{\left(U \Sigma V^{T}\right)^{T} U \Sigma V^{T}}\right) \ &=\operatorname{tr}\left(\sqrt{V \Sigma^{T} U^{T} U \Sigma V^{T}}\right) \ &=\operatorname{tr}\left(\sqrt{V \Sigma^{2} V^{T}}\right)\left(\Sigma^{T}=\Sigma\right) \ &=\operatorname{tr}\left(\sqrt{V^{T} V \Sigma^{2}}\right) \ &=\operatorname{tr}(\Sigma) \end{aligned} $$

参考代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import torch
label = np.array([[1, 2, 3, 4], [1, 2, 3, 4]])
pred = np.array([[2, 3, 4, 5], [2, 3, 4, 5]])

print("F范数矩阵")
print(torch.norm(torch.from_numpy(pred - label).type(torch.cuda.FloatTensor), p="fro", dim=-1))
print("2范数矩阵")
print(torch.norm(torch.from_numpy(pred - label).type(torch.cuda.FloatTensor), p=2, dim=-1))

print("核范数矩阵")  # 核范数作为Loss使用方法不详
print(torch.norm(torch.from_numpy(pred - label).type(torch.cuda.FloatTensor), p="nuc"))

print("平均损失计算:每个样本的损失相加,再除以总样本数")
print(torch.norm(torch.from_numpy(pred - label).type(torch.cuda.FloatTensor), p="fro", dim=-1).sum() / label.shape[0])
print(torch.norm(torch.from_numpy(pred - label).type(torch.cuda.FloatTensor), p=2, dim=-1).sum() / label.shape[0])

输出
F范数矩阵
tensor([2., 2.], device='cuda:0')
2范数矩阵
tensor([2., 2.], device='cuda:0')
核范数矩阵
tensor(2.8284, device='cuda:0')
平均损失计算每个样本的损失相加再除以总样本数
tensor(2., device='cuda:0')
tensor(2., device='cuda:0')

Process finished with exit code 0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
>>> # 不同shape的欧距计算 作者:https://blog.csdn.net/sinat_24899403/article/details/119249822
>>> import torch
>>> from torch import nn

>>> a=torch.tensor([[1,1,1],
                [2,2,2]])
>>> b=torch.tensor([[2,2,2],
								[1,1,1],
                [2,2,2],
                [1,1,1],
                [2,2,2]])
>>> def pdist(a: torch.Tensor, b: torch.Tensor, p: int = 2) -> torch.Tensor:
    return (a-b).abs().pow(p).sum(-1).pow(1/p)

>>> a_=a.unsqueeze(1)
>>> b_=b.unsqueeze(0)
>>> print(pdist(a_,b_))

tensor([[1.7321, 0.0000, 1.7321, 0.0000, 1.7321],
        [0.0000, 1.7321, 0.0000, 1.7321, 0.0000]])

如果是多对多,且shape不一致,使用numpy计算是最快的,考虑原因可能是torch未封装int类型的不同shape数据的计算方式,如果shape相同可以使用pairwise的方法

形状相同(用于标签预测)

1
2
3
4
5
6
7
8
9
>>> b = torch.tensor([[0, 0, 1],
                  [0, 1, 0]
                  ])
>>> c = torch.tensor([[1, 0, 1],
                  [0, 1, 0]
                  ])
>>> metric = BinaryHammingDistance(multidim_average='samplewise')
>>> print(metric(b, c))
tensor([0.3333, 0.0000])

形状不同(用于检索)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

>>> from sklearn.neighbors import DistanceMetric
>>> import time
>>> a = np.array([[0, 0, 2],])
>>> b = np.array([[0, 0, 1],
              [0, 1, 0]
              ])
>>> s1 = time.time()
>>> print(DistanceMetric.get_metric("hamming").pairwise(a,b))
>>> print(f"耗时{time.time()-s1}")
[[0.33333333 0.66666667]]
耗时0.0003008842468261719

>>> a_tensor = torch.as_tensor(a, dtype=torch.float)
>>> b_tensor = torch.as_tensor(b, dtype=torch.float)
>>> s2 = time.time()
>>> print(torch.cdist(a_tensor, b_tensor, p=0))
>>> print(f"耗时{time.time()-s2}")
tensor([[1., 2.]])
耗时0.0003383159637451172

>>> a_tensor = torch.as_tensor(a, dtype=torch.float).cuda()
>>> b_tensor = torch.as_tensor(b, dtype=torch.float).cuda()
>>> s3 = time.time()
>>> print(torch.cdist(a_tensor, b_tensor, p=0))
>>> print(f"耗时{time.time()-s3}")
tensor([[1., 2.]], device='cuda:0')
耗时0.0015494823455810547

维基百科:KL散度(Kullback-Leibler divergence,简称KLD),在讯息系统中称为相对熵(relative entropy),在连续时间序列中称为随机性(randomness),在统计模型推断中称为讯息增益(information gain)。也称讯息散度(information divergence)。它是两个几率分布$P$和$Q$差别的非对称性的度量。 KL散度是用来度量使用基于$Q$的分布来编码服从$P$的分布的样本所需的额外的平均比特数,注意$P$,$Q$先后顺序。典型情况下,P表示数据的真实分布,Q表示数据的理论分布、估计的模型分布、或P的近似分布。

$$ \begin{equation} \begin{aligned} D_{KL}(P||Q) &= -\sum_iP(i)ln\frac{Q(i)}{P(i)} \\ &= \sum_iP(i)ln\frac{P(i)}{Q(i)} \end{aligned} \end{equation} $$

相对熵的值为非负数:

$$ D_{KL}(P||Q)\geq 0 $$ 另,概率都为零时取零。

维基百科:在信息论中,基于相同事件测度的两个概率分布$p$和$q$的交叉熵是指,当基于一个“非自然”(相对于“真实”分布$p$而言)的概率分布$q$进行编码时,在事件集合中唯一标识一个事件所需要的平均比特数(bit)。

给定两个概率分布$p$和$q$,$p$相对于$q$的交叉熵定义为:

$$ H(p,q) = E_p[-\log q] = H(p) + D_{KL}(p||q), $$

其中,$H(p)$是$p$的熵,$D_{KL}(p||q)$是从$p$与$q$的KL散度(也被称为$p$,相对于$q$的相对熵)。

对于离散分布$p$和$q$,交叉熵可以定义为:

$$ H(p,q)=-\sum_xp(x)\log q(x). $$

其中求和是指在样本空间进行计算求和。记住这个计算方法,因为后续介绍分类任务的交叉熵损失是基于此的。

假设在二分类任务时,真实标签$y$和预测标签$\hat{y}$取值空间为${0,1}$。根据交叉熵定义,可以将交叉熵损失函数定义如下。 $$ J=-\frac{1}{N}\sum_{i=1}^{N}[y\log\hat{y}+(1-y)\log(1-\hat{y})] $$ 假设真实分布为$p(i)$(真实标签$y$的分布),模型预测的分布为$q(i)$(预测标签$\hat{y}$的分布,推导如下,

$$ \begin{equation} \begin{aligned} H(p,q) &=-\sum_x p(x)\cdot \log({q(x)}) \\ &=\sum_x p(x)\cdot \log(\frac{1}{q(x)})\\ &=\sum_x p_{(y=0|x)} \cdot \log(\frac{1}{q_{(y=0|x)} }) + p_{(y=1|x)} \cdot \log(\frac{1}{q_{(y=1|x)}})\\ &=\sum_x y\log(\frac{1}{\hat{y}}) + (1-y)\log(\frac{1}{1-\hat{y}})\\ &=-\sum_x [y\log \hat{y} + (1-y)\log(1-\hat{y})] \end{aligned} \end{equation} $$

最后乘上$\frac{1}{N}$进行平均操作。

与二分类交叉熵类似,多分类交叉熵同样是计算标签分布的熵值,基于此,我们需要把多分类考虑在内,也就是多一步求和,即每个类别上的交叉熵求和并在样本空间上进行求和,基于二分类交叉熵的参数定义,定义分类的标签种类为$n$,则多分类交叉熵损失可以表示如下 $$ \begin{equation} \mathcal L = -\frac{1}{N}\sum_{i=1}^N\sum_{j=1}^ny_j^{(i)}\cdot \log\hat{y}_j^{(i)} \end{equation} $$

其中,$n$为分类个数,即标签向量维度。$N$为样本个数。

给定anchor $p$,以欧距为例,衡量相似度,其正样本为$q$,负样本为$r$。 $$ \begin{equation} \begin{aligned} \mathcal L = max(||\boldsymbol p-\boldsymbol q||^2-||\boldsymbol p-\boldsymbol r||^2+\epsilon,0) \end{aligned} \end{equation} $$

代码实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
"""
loss class
"""
class triplet_loss(nn.Module): 
	def __init__(self): 
  	super(triplet_loss, self).__init__() 
    self.margin = 0.2
	def forward(self, anchor, positive, negative): 
  	pos_dist = (anchor - positive).pow(2).sum(1) 
    neg_dist = (anchor - negative).pow(2).sum(1) 
    loss = F.relu(pos_dist - neg_dist + self.margin) 
    return loss.mean()#we can also use #torch.nn.functional.pairwise_distance(anchor,positive, keep_dims=True), which #computes the euclidean distance.
  

"""
training part
"""
loss_fun = triplet_loss() 
optimizer = Adam(custom_model.parameters(), lr = 0.001) 
for epoch in range(30): 
    total_loss = 0 
    for i, (anchor, positive, negative) in enumerate(custom_loader): 
        anchor = anchor['image'].to(device) 
        positive = positive['image'].to(device) 
        negative = negative['image'].to(device) 
 
        anchor_feature = custom_model(anchor) 
        positive_feature = custom_model(positive) 
        negative_feature = custom_model(negative) 
 
        optimizer.zero_grad() 
        loss = loss_fun(anchor_feature, positive_feature, negative_feature) 
        loss.backward() 
        optimizer.step()
  

https://zhuanlan.zhihu.com/p/414327252?ivk_sa=1024320u

更多对比损失 https://lilianweng.github.io/posts/2021-05-31-contrastive/