# 4.2 损失函数

> 本章代码：
>
> * <https://github.com/zhangxiann/PyTorch_Practice/blob/master/lesson4/loss_function_1.py>
> * <https://github.com/zhangxiann/PyTorch_Practice/blob/master/lesson4/loss_function_1.py>

这篇文章主要介绍了损失函数的概念，以及 PyTorch 中提供的常用损失函数。

## 损失函数

损失函数是衡量模型输出与真实标签之间的差异。我们还经常听到代价函数和目标函数，它们之间差异如下：

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

  Loss $=f\left(y^{\wedge}, y\right)$
* 代价函数(Cost Function)是计算整个样本集的模型输出与真实标签的差异，是所有样本损失函数的平均值

  $\cos t=\frac{1}{N} \sum\_{i}^{N} f\left(y\_{i}^{\wedge}, y\_{i}\right)$
* 目标函数(Objective Function)就是代价函数加上正则项

在 PyTorch 中的损失函数也是继承于`nn.Module`，所以损失函数也可以看作网络层。

在逻辑回归的实验中，我使用了交叉熵损失函数`loss_fn = nn.BCELoss()`，$BCELoss$的继承关系：`nn.BCELoss() -> _WeightedLoss -> _Loss -> Module`。在计算具体的损失时`loss = loss_fn(y_pred.squeeze(), train_y)`，这里实际上在 Loss 中进行一次前向传播，最终调用`BCELoss()`的`forward()`函数`F.binary_cross_entropy(input, target, weight=self.weight, reduction=self.reduction)`。

下面介绍 PyTorch 提供的损失函数。注意在所有的损失函数中，`size_average`和`reduce`参数都不再使用。

### 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(加权平均，返回标量)

下面介绍熵的一些基本概念

* 自信息：$\mathrm{I}(x)=-\log \[p(x)]$
* 信息熵就是求自信息的期望：$\mathrm{H}(\mathrm{P})=E\_{x \sim p}\[I(x)]=-\sum\_{i}^{N} P\left(x\_{i}\right) \log P\left(x\_{i}\right)$
* 相对熵，也被称为 KL 散度，用于衡量两个分布的相似性(距离)：$\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]$。其中$P(X)$是真实分布，$Q(X)$是拟合的分布
* 交叉熵：$\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)$

相对熵展开可得：

$\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}$

所以交叉熵 = 信息熵 + 相对熵，即$\mathrm{H}(\boldsymbol{P}, \boldsymbol{Q})=\boldsymbol{D}*{K \boldsymbol{L}}(\boldsymbol{P}, \boldsymbol{Q})+\mathrm{H}(\boldsymbol{P})$，又由于信息熵$H(P)$是固定的，因此优化交叉熵$H(P,Q)$等价于优化相对熵$D*{KL}(P,Q)$。

所以对于**每一个样本**的 Loss 计算公式为：

$\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})$，因为$N=1$，$P(x\_{i})=1$。

所以$\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)$。

如果了类别的权重，则$\operatorname{loss}(x, \text { class })=\operatorname{weight}\[\text { class }]\left(-x\[\text { class }]+\log \left(\sum\_{j} \exp (x\[j])\right)\right)$。

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

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

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

# def loss function
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')

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

# view
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)
```

我们根据单个样本的 loss 计算公式$\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)$，可以使用以下代码来手动计算第一个样本的损失

```
idx = 0

input_1 = inputs.detach().numpy()[idx]      # [1, 2]
target_1 = target.numpy()[idx]              # [0]

# 第一项
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
loss_1 = -x_class + log_sigma_exp_x

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

结果为：1.3132617

下面继续看带有类别权重的损失计算，首先设置类别的权重向量`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=5$，所以加权平均的 loss 为：$1.8210\div5=0.3642$，通过手动计算的方式代码如下：

```
weights = torch.tensor([1, 2], dtype=torch.float)
weights_all = np.sum(list(map(lambda x: weights.numpy()[x], target.numpy())))  # [0, 1, 1]  # [1 2 2]
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

### 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}}$。还是使用上面的例子，第一个样本的输出为 \[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')

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

# view
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)
```

### nn.BCELoss

```
nn.BCELoss(weight=None, size_average=None, reduce=None, reduction='mean')
```

功能：计算二分类的交叉熵。需要注意的是：输出值区间为 \[0,1]。

主要参数：

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

计算公式为：$l\_{n}=-w\_{n}\left\[y\_{n} \cdot \log x\_{n}+\left(1-y\_{n}\right) \cdot \log \left(1-x\_{n}\right)\right]$

使用这个函数有两个不同的地方：

* 预测的标签需要经过 sigmoid 变换到 \[0,1] 之间。
* 真实的标签需要转换为 one hot 向量，类型为`torch.float`。

代码如下：

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

target_bce = target

# itarget
inputs = torch.sigmoid(inputs)

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

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

# forward
loss_none_w = loss_f_none_w(inputs, target_bce)
loss_sum = loss_f_sum(inputs, target_bce)
loss_mean = loss_f_mean(inputs, target_bce)

# view
print("\nweights: ", weights)
print("BCE Loss", loss_none_w, loss_sum, loss_mean)
```

结果为：

```
BCE Loss tensor([[0.3133, 2.1269],
        [0.1269, 2.1269],
        [3.0486, 0.0181],
        [4.0181, 0.0067]]) tensor(11.7856) tensor(1.4732)
```

第一个 loss 为 0，3133，手动计算的代码如下：

```
x_i = inputs.detach().numpy()[idx, idx]
y_i = target.numpy()[idx, idx]              #

# loss
# l_i = -[ y_i * np.log(x_i) + (1-y_i) * np.log(1-y_i) ]      # np.log(0) = nan
l_i = -y_i * np.log(x_i) if y_i else -(1-y_i) * np.log(1-x_i)
```

### nn.BCEWithLogitsLoss

```
nn.BCEWithLogitsLoss(weight=None, size_average=None, reduce=None, reduction='mean', pos_weight=None)
```

功能：结合 sigmoid 与二分类交叉熵。需要注意的是，网络最后的输出不用经过 sigmoid 函数。这个 loss 出现的原因是有时网络模型最后一层输出不希望是归一化到 \[0,1] 之间，但是在计算 loss 时又需要归一化到 \[0,1] 之间。

主要参数：

* weight：各类别的 loss 权值设置
* pos\_weight：**设置样本类别对应的神经元的输出的 loss 权值**
* ignore\_index：忽略某个类别
* reduction：计算模式，，可以为 none(逐个元素计算)，sum(所有元素求和，返回标量)，mean(加权平均，返回标量)

代码如下：

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

target_bce = target

# itarget
# inputs = torch.sigmoid(inputs)

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

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

# forward
loss_none_w = loss_f_none_w(inputs, target_bce)
loss_sum = loss_f_sum(inputs, target_bce)
loss_mean = loss_f_mean(inputs, target_bce)

# view
print("\npos_weights: ", pos_w)
print(loss_none_w, loss_sum, loss_mean)
```

输出为

```
pos_weights:  tensor([3.])
tensor([[0.9398, 2.1269],
        [0.3808, 2.1269],
        [3.0486, 0.0544],
        [4.0181, 0.0201]]) tensor(12.7158) tensor(1.5895)
```

与 BCELoss 进行对比

```
BCE Loss tensor([[0.3133, 2.1269],
        [0.1269, 2.1269],
        [3.0486, 0.0181],
        [4.0181, 0.0067]]) tensor(11.7856) tensor(1.4732)
```

可以看到，样本类别对应的神经元的输出的 loss 都增加了 3 倍。

### nn.L1Loss

```
nn.L1Loss(size_average=None, reduce=None, reduction='mean')
```

功能：计算 inputs 与 target 之差的绝对值

主要参数：

* reduction：计算模式，，可以为 none(逐个元素计算)，sum(所有元素求和，返回标量)，mean(加权平均，返回标量)

公式：$l\_{n}=\left|x\_{n}-y\_{n}\right|$

### nn.MSELoss

功能：计算 inputs 与 target 之差的平方

公式：$l\_{n}=\left(x\_{n}-y\_{n}\right)^{2}$

主要参数：

* reduction：计算模式，，可以为 none(逐个元素计算)，sum(所有元素求和，返回标量)，mean(加权平均，返回标量)

代码如下：

```
inputs = torch.ones((2, 2))
target = torch.ones((2, 2)) * 3

loss_f = nn.L1Loss(reduction='none')
loss = loss_f(inputs, target)

print("input:{}\ntarget:{}\nL1 loss:{}".format(inputs, target, loss))

# ------------------------------------------------- 6 MSE loss ----------------------------------------------

loss_f_mse = nn.MSELoss(reduction='none')
loss_mse = loss_f_mse(inputs, target)

print("MSE loss:{}".format(loss_mse))
```

输出如下：

```
input:tensor([[1., 1.],
        [1., 1.]])
target:tensor([[3., 3.],
        [3., 3.]])
L1 loss:tensor([[2., 2.],
        [2., 2.]])
MSE loss:tensor([[4., 4.],
        [4., 4.]])
```

### nn.SmoothL1Loss

```
nn.SmoothL1Loss(size_average=None, reduce=None, reduction='mean')
```

功能：平滑的 L1Loss

公式：$z\_{i}=\left{\begin{array}{ll}0.5\left(x\_{i}-y\_{i}\right)^{2}, & \text { if }\left|x\_{i}-y\_{i}\right|<1 \ \left|x\_{i}-y\_{i}\right|-0.5, & \text { otherwise }\end{array}\right.$

下图中橙色曲线是 L1Loss，蓝色曲线是 Smooth L1Loss

![](https://image.zhangxiann.com/20200701101230.png)\
主要参数：

* reduction：计算模式，，可以为 none(逐个元素计算)，sum(所有元素求和，返回标量)，mean(加权平均，返回标量)

### nn.PoissonNLLLoss

```
nn.PoissonNLLLoss(log_input=True, full=False, size_average=None, eps=1e-08, reduce=None, reduction='mean')
```

功能：泊松分布的负对数似然损失函数

主要参数：

* log\_input：输入是否为对数形式，决定计算公式
  * 当 log\_input = True，表示输入数据已经是经过对数运算之后的，loss(input, target) = exp(input) - target \* input
  * 当 log\_input = False，，表示输入数据还没有取对数，loss(input, target) = input - target \* log(input+eps)
* full：计算所有 loss，默认为 loss
* eps：修正项，避免 log(input) 为 nan

代码如下：

```
inputs = torch.randn((2, 2))
target = torch.randn((2, 2))

loss_f = nn.PoissonNLLLoss(log_input=True, full=False, reduction='none')
loss = loss_f(inputs, target)
print("input:{}\ntarget:{}\nPoisson NLL loss:{}".format(inputs, target, loss))
```

输出如下：

```
input:tensor([[0.6614, 0.2669],
        [0.0617, 0.6213]])
target:tensor([[-0.4519, -0.1661],
        [-1.5228,  0.3817]])
Poisson NLL loss:tensor([[2.2363, 1.3503],
        [1.1575, 1.6242]])
```

手动计算第一个 loss 的代码如下：

```
idx = 0

loss_1 = torch.exp(inputs[idx, idx]) - target[idx, idx]*inputs[idx, idx]

print("第一个元素loss:", loss_1)
```

结果为：2.2363

### nn.KLDivLoss

```
nn.KLDivLoss(size_average=None, reduce=None, reduction='mean')
```

功能：计算 KLD(divergence)，KL 散度，相对熵

注意事项：需要提前将输入计算 log-probabilities，如通过`nn.logsoftmax()`

主要参数：

* reduction：计算模式，，可以为 none(逐个元素计算)，sum(所有元素求和，返回标量)，mean(加权平均，返回标量)，batchmean(batchsize 维度求平均值)

公式：$\begin{aligned} D\_{K L}(P | Q)=E\_{x-p}\left\[\log \frac{P(x)}{Q(x)}\right] &=E\_{x-p}\[\log P(x)-\log Q(x)] =\sum\_{i=1}^{N} P\left(x\_{i}\right)\left(\log P\left(x\_{i}\right)-\log Q\left(x\_{i}\right)\right) \end{aligned}$

对于每个样本来说，计算公式如下，其中$y\_{n}$是真实值$P(x)$，$x\_{n}$是经过对数运算之后的预测值$logQ(x)$。

$l\_{n}=y\_{n} \cdot\left(\log y\_{n}-x\_{n}\right)$

代码如下：

```
inputs = torch.tensor([[0.5, 0.3, 0.2], [0.2, 0.3, 0.5]])
inputs_log = torch.log(inputs)
target = torch.tensor([[0.9, 0.05, 0.05], [0.1, 0.7, 0.2]], dtype=torch.float)

loss_f_none = nn.KLDivLoss(reduction='none')
loss_f_mean = nn.KLDivLoss(reduction='mean')
loss_f_bs_mean = nn.KLDivLoss(reduction='batchmean')

loss_none = loss_f_none(inputs, target)
loss_mean = loss_f_mean(inputs, target)
loss_bs_mean = loss_f_bs_mean(inputs, target)

print("loss_none:\n{}\nloss_mean:\n{}\nloss_bs_mean:\n{}".format(loss_none, loss_mean, loss_bs_mean))
```

输出如下：

```
loss_none:
tensor([[-0.5448, -0.1648, -0.1598],
        [-0.2503, -0.4597, -0.4219]])
loss_mean:
-0.3335360586643219
loss_bs_mean:
-1.000608205795288
```

手动计算第一个 loss 的代码为：

```
idx = 0
loss_1 = target[idx, idx] * (torch.log(target[idx, idx]) - inputs[idx, idx])
print("第一个元素loss:", loss_1)
```

结果为：-0.5448。

### nn.MarginRankingLoss

```
nn.MarginRankingLoss(margin=0.0, size_average=None, reduce=None, reduction='mean')
```

功能：计算两个向量之间的相似度，用于排序任务

特别说明：该方法计算 两组数据之间的差异，返回一个$n \times n$ 的 loss 矩阵

主要参数：

* margin：边界值，$x\_{1}$与$x\_{2}$之间的差异值
* reduction：计算模式，，可以为 none(逐个元素计算)，sum(所有元素求和，返回标量)，mean(加权平均，返回标量)

计算公式：$\operatorname{loss}(x, y)=\max (0,-y \*(x 1-x 2)+\operatorname{margin})$，$y$的取值有 +1 和 -1。

* 当 $y=1$时，希望$x\_{1} > x\_{2}$，当$x\_{1} > x\_{2}$，不产生 loss
* 当 $y=-1$时，希望$x\_{1} < x\_{2}$，当$x\_{1} < x\_{2}$，不产生 loss

代码如下：

```
x1 = torch.tensor([[1], [2], [3]], dtype=torch.float)
x2 = torch.tensor([[2], [2], [2]], dtype=torch.float)

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

loss_f_none = nn.MarginRankingLoss(margin=0, reduction='none')

loss = loss_f_none(x1, x2, target)

print(loss)
```

输出为：

```
tensor([[1., 1., 0.],
        [0., 0., 0.],
        [0., 0., 1.]])
```

第一行表示$x\_{1}$中的第一个元素分别与$x\_{2}$中的 3 个元素计算 loss，以此类推。

### nn.MultiLabelMarginLoss

```
nn.MultiLabelMarginLoss(size_average=None, reduce=None, reduction='mean')
```

功能：多标签边界损失函数

举例：4 分类任务，样本 x 属于 0 类和 3 类，那么标签为 \[0, 3, -1, -1]，

主要参数：

* reduction：计算模式，，可以为 none(逐个元素计算)，sum(所有元素求和，返回标量)，mean(加权平均，返回标量)

计算公式：$\operatorname{loss}(x, y)=\sum\_{i j} \frac{\max (0,1-(x\[y\[j]]-x\[i]))}{x \cdot \operatorname{size}(0)}$，表示每个真实类别的神经元输出减去其他神经元的输出。

代码如下：

```
x = torch.tensor([[0.1, 0.2, 0.4, 0.8]])
y = torch.tensor([[0, 3, -1, -1]], dtype=torch.long)

loss_f = nn.MultiLabelMarginLoss(reduction='none')

loss = loss_f(x, y)

print(loss)
```

输出为：

```
0.8500
```

手动计算如下：

```
x = x[0]
item_1 = (1-(x[0] - x[1])) + (1 - (x[0] - x[2]))    # [0]
item_2 = (1-(x[3] - x[1])) + (1 - (x[3] - x[2]))    # [3]

loss_h = (item_1 + item_2) / x.shape[0]

print(loss_h)
```

### nn.SoftMarginLoss

```
nn.SoftMarginLoss(size_average=None, reduce=None, reduction='mean')
```

功能：计算二分类的 logistic 损失

主要参数：

* reduction：计算模式，，可以为 none(逐个元素计算)，sum(所有元素求和，返回标量)，mean(加权平均，返回标量)

计算公式：$\operatorname{loss}(x, y)=\sum\_{i} \frac{\log (1+\exp (-y\[i] \* x\[i]))}{\text { x.nelement } 0}$

代码如下：

```
inputs = torch.tensor([[0.3, 0.7], [0.5, 0.5]])
target = torch.tensor([[-1, 1], [1, -1]], dtype=torch.float)

loss_f = nn.SoftMarginLoss(reduction='none')

loss = loss_f(inputs, target)

print("SoftMargin: ", loss)
```

输出如下：

```
SoftMargin:  tensor([[0.8544, 0.4032],
        [0.4741, 0.9741]])
```

手动计算第一个 loss 的代码如下：

```
idx = 0

inputs_i = inputs[idx, idx]
target_i = target[idx, idx]

loss_h = np.log(1 + np.exp(-target_i * inputs_i))

print(loss_h)
```

结果为：0.8544

### nn.MultiLabelSoftMarginLoss

```
nn.MultiLabelSoftMarginLoss(weight=None, size_average=None, reduce=None, reduction='mean')
```

功能：SoftMarginLoss 的多标签版本

主要参数：

* weight：各类别的 loss 权值设置
* reduction：计算模式，，可以为 none(逐个元素计算)，sum(所有元素求和，返回标量)，mean(加权平均，返回标量)

计算公式：$\operatorname{loss}(x, y)=-\frac{1}{C} *\sum\_{i} y\[i]* \log \left((1+\exp (-x\[i]))^{-1}\right)+(1-y\[i]) \* \log \left(\frac{\exp (-x\[i])}{(1+\exp (-x\[i]))}\right)$

代码如下

```
inputs = torch.tensor([[0.3, 0.7, 0.8]])
target = torch.tensor([[0, 1, 1]], dtype=torch.float)

loss_f = nn.MultiLabelSoftMarginLoss(reduction='none')

loss = loss_f(inputs, target)

print("MultiLabel SoftMargin: ", loss)
```

输出为：

```
MultiLabel SoftMargin:  tensor([0.5429])
```

手动计算的代码如下：

```
x = torch.tensor([[0.1, 0.2, 0.7], [0.2, 0.5, 0.3]])
y = torch.tensor([1, 2], dtype=torch.long)

loss_f = nn.MultiMarginLoss(reduction='none')

loss = loss_f(x, y)

print("Multi Margin Loss: ", loss)
```

### nn.MultiMarginLoss

```
nn.MultiMarginLoss(p=1, margin=1.0, weight=None, size_average=None, reduce=None, reduction='mean')
```

功能：计算多分类的折页损失

主要参数：

* p：可以选择 1 或 2
* weight：各类别的 loss 权值设置
* margin：边界值
* reduction：计算模式，，可以为 none(逐个元素计算)，sum(所有元素求和，返回标量)，mean(加权平均，返回标量)

计算公式：$\operatorname{loss}(x, y)=\frac{\left.\sum\_{i} \max (0, \operatorname{margin}-x\[y]+x\[i])\right)^{p}}{\quad \text { x.size }(0)}$，其中 y 表示真实标签对应的神经元输出，x 表示其他神经元的输出。

代码如下：

```
x = torch.tensor([[0.1, 0.2, 0.7], [0.2, 0.5, 0.3]])
y = torch.tensor([1, 2], dtype=torch.long)

loss_f = nn.MultiMarginLoss(reduction='none')

loss = loss_f(x, y)

print("Multi Margin Loss: ", loss)
```

输出如下：

```
Multi Margin Loss:  tensor([0.8000, 0.7000])
```

手动计算第一个 loss 的代码如下：

```
x = x[0]
margin = 1

i_0 = margin - (x[1] - x[0])
# i_1 = margin - (x[1] - x[1])
i_2 = margin - (x[1] - x[2])

loss_h = (i_0 + i_2) / x.shape[0]

print(loss_h)
```

输出为：0.8000

### nn.TripletMarginLoss

```
nn.TripletMarginLoss(margin=1.0, p=2.0, eps=1e-06, swap=False, size_average=None, reduce=None, reduction='mean')
```

功能：计算三元组损失，人脸验证中常用

主要参数：

* p：范数的阶，默认为 2
* margin：边界值
* reduction：计算模式，，可以为 none(逐个元素计算)，sum(所有元素求和，返回标量)，mean(加权平均，返回标量)

计算公式：$L(a, p, n)=\max \left{d\left(a\_{i}, p\_{i}\right)-d\left(a\_{i}, n\_{i}\right)+\text { margin, } 0\right}$，$d\left(x\_{i}, y\_{i}\right)=\left|\mathbf{x}*{i}-\mathbf{y}*{i}\right|*{p}$，其中$d(a*{i}, p\_{i})$表示正样本对之间的距离(距离计算公式与 p 有关)，$d(a\_{i}, n\_{i})$表示负样本对之间的距离。表示正样本对之间的距离比负样本对之间的距离小 margin，就没有了 loss。

代码如下：

```
anchor = torch.tensor([[1.]])
pos = torch.tensor([[2.]])
neg = torch.tensor([[0.5]])

loss_f = nn.TripletMarginLoss(margin=1.0, p=1)

loss = loss_f(anchor, pos, neg)

print("Triplet Margin Loss", loss)
```

输出如下：

```
Triplet Margin Loss tensor(1.5000)
```

手动计算的代码如下：

```
margin = 1
a, p, n = anchor[0], pos[0], neg[0]

d_ap = torch.abs(a-p)
d_an = torch.abs(a-n)

loss = d_ap - d_an + margin

print(loss)
```

### nn.HingeEmbeddingLoss

```
nn.HingeEmbeddingLoss(margin=1.0, size_average=None, reduce=None, reduction='mean')
```

功能：计算两个输入的相似性，常用于非线性 embedding 和半监督学习

特别注意：输入 x 应该为两个输入之差的绝对值

主要参数：

* margin：边界值
* reduction：计算模式，，可以为 none(逐个元素计算)，sum(所有元素求和，返回标量)，mean(加权平均，返回标量)

计算公式：$l\_{n}=\left{\begin{array}{ll}x\_{n}, & \text { if } y\_{n}=1 \ \max \left{0, \Delta-x\_{n}\right}, & \text { if } y\_{n}=-1\end{array}\right.$

代码如下：

```
inputs = torch.tensor([[1., 0.8, 0.5]])
target = torch.tensor([[1, 1, -1]])

loss_f = nn.HingeEmbeddingLoss(margin=1, reduction='none')

loss = loss_f(inputs, target)

print("Hinge Embedding Loss", loss)
```

输出为：

```
Hinge Embedding Loss tensor([[1.0000, 0.8000, 0.5000]])
```

手动计算第三个 loss 的代码如下：

```
margin = 1.
loss = max(0, margin - inputs.numpy()[0, 2])

print(loss)
```

结果为 0.5

### nn.CosineEmbeddingLoss

```
torch.nn.CosineEmbeddingLoss(margin=0.0, size_average=None, reduce=None, reduction='mean')
```

功能：采用余弦相似度计算两个输入的相似性

主要参数：

* margin：边界值，可取值 \[-1, 1]，推荐为 \[0, 0.5]
* reduction：计算模式，，可以为 none(逐个元素计算)，sum(所有元素求和，返回标量)，mean(加权平均，返回标量)

计算公式：$\operatorname{loss}(x, y)=\left{\begin{array}{ll}1-\cos \left(x\_{1}, x\_{2}\right), & \text { if } y=1 \ \max \left(0, \cos \left(x\_{1}, x\_{2}\right)-\operatorname{margin}\right), & \text { if } y=-1\end{array}\right.$

其中$\cos (\theta)=\frac{A \cdot B}{|A||B|}=\frac{\sum\_{i=1}^{n} A\_{i} \times B\_{i}}{\sqrt{\sum\_{i=1}^{n}\left(A\_{i}\right)^{2}} \times \sqrt{\sum\_{i=1}^{n}\left(B\_{i}\right)^{2}}}$

代码如下：

```
x1 = torch.tensor([[0.3, 0.5, 0.7], [0.3, 0.5, 0.7]])
x2 = torch.tensor([[0.1, 0.3, 0.5], [0.1, 0.3, 0.5]])

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

loss_f = nn.CosineEmbeddingLoss(margin=0., reduction='none')

loss = loss_f(x1, x2, target)

print("Cosine Embedding Loss", loss)
```

输出如下：

```
Cosine Embedding Loss tensor([[0.0167, 0.9833]])
```

手动计算第一个样本的 loss 的代码为：

```
margin = 0.

def cosine(a, b):
numerator = torch.dot(a, b)
denominator = torch.norm(a, 2) * torch.norm(b, 2)
return float(numerator/denominator)

l_1 = 1 - (cosine(x1[0], x2[0]))

l_2 = max(0, cosine(x1[0], x2[0]))

print(l_1, l_2)
```

结果为：0.016662120819091797 0.9833378791809082

### nn.CTCLoss

```
nn.CTCLoss(blank=0, reduction='mean', zero_infinity=False)
```

功能：计算 CTC 损失，解决时序类数据的分类，全称为 Connectionist Temporal Classification

主要参数：

* blank：blank label
* zero\_infinity：无穷大的值或梯度置 0
* reduction：计算模式，，可以为 none(逐个元素计算)，sum(所有元素求和，返回标量)，mean(加权平均，返回标量)

对时序方面研究比较少，不展开讲了。

**参考资料**

* [深度之眼 PyTorch 框架班](https://ai.deepshare.net/detail/p_5df0ad9a09d37_qYqVmt85/6)

如果你觉得这篇文章对你有帮助，不妨点个赞，让我有更多动力写出好文章。

我的文章会首发在公众号上，欢迎扫码关注我的公众号**张贤同学**。

![](https://image.zhangxiann.com/QRcode_8cm.jpg)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://pytorch.zhangxiann.com/4-mo-xing-xun-lian/4.2-sun-shi-han-shu.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
