> For the complete documentation index, see [llms.txt](https://kerasnoone.gitbook.io/garnet/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://kerasnoone.gitbook.io/garnet/shen-jing-wang-luo/sun-shi-han-shu/ghm-loss.md).

# GHM Loss

## GHM Loss

### Focal loss 的问题

Focal Loss的重点是关注难以分类的样本, 这些样本对模型的优化贡献最大. 但GHM(Gradient Harmonizing Mechanism) Loss认为**让模型过多关注那些特别难分的样本是存在问题的**. 样本中的离群点(Outliers)在模型已经收敛后仍然会被判别错误, 属于超难分类的样本. 让模型取关注这些样本, 会使得模型过度地去拟合噪音, 损伤了泛化性, 造成了过拟合现象.

![](https://1942165044-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI7KRyeBH5dlW-CkUtn%2Fsync%2F7a5fdb79c1bea11429916e051d6365fec6f88013.png?generation=1624809146592178\&alt=media)

### GHM Loss 的原理

Focal Loss是从模型输出的分类置信度p的角度入手衰减样本的loss, GHM则是从**梯度密度**的角度去衰减样本的loss.

定义梯度密度, 从定义**梯度模长**开始. 考虑二分类交叉熵损失函数, 定义梯度模长为:

$$
g=\left|p-p^{*}\right|=\left{\begin{array}{ll}
1-p & \text { if } p^{*}=1 \\
p & \text { if } p^{\*}=0
\end{array}\right.
$$

这样定义梯度模长是因为输出层的sigmoid函数与二分类交叉熵损失函数结合有:

$$
p = \text{sigmoid}(x)
$$

$$
L\_{C E}=\left{\begin{array}{rlr}
-\log (p), & \text { if } & p^{*}=1 \\
-\log (1-p), & \text { if } & p^{*}=0
\end{array}\right.
$$

对应的损失函数对输出层的输入的梯度如下, 这一步的推到可以参考[Softmax与交叉熵求导](broken://pages/-MdD9oK_Z4XDsgctKE8P):

$$
\frac{\partial L\_{C E}}{\partial x}=\left{\begin{array}{rl}
p-1, & \text { if } \quad p^{*}=1 \\
p, & \text { if } \quad p^{*}=0
\end{array}=p-p^{\*}\right.
$$

因此有:

$$
g=\left|\frac{\partial L\_{C E}}{\partial x}\right|
$$

对于易于分类的样本, 它们的输出值会非常接近真实标签, 所以对梯度的贡献很小, 梯度模长接近0; 而难以分类的样本, 则会产生大梯度, 梯度模长接近于1. 因此**梯度模长的大小可以衡量样本的分类难易程度**.

衡量**梯度模长**与样本数量的关系, 如下图, 可以看到除了大量易分样本, 分类困难的样本数量也非常多, 这些样本可能是标注错误的噪音离群点, 过多的关注这些十分困难的分类样本, 不仅不会提升模型的分类效果, 反而会对模型质量带来一定的损伤.

![](https://1942165044-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI7KRyeBH5dlW-CkUtn%2Fsync%2Fb59533dd10302d90ed6abbbee123777ac6b38427.png?generation=1624809146372625\&alt=media)

因此GHM Loss在控制每个样本对梯度贡献的权重时, 选择使用**样本梯度模长所在的一定范围内, 如果在这个范围内样本数量较多, 对其梯度加以抑制**. 定义一个变量, **衡量出一定梯度范围内的样本数量**, 这个变量的概念类似于**密度**. 定义**梯度密度**$$GD(g)$$:

$$
G D(g)=\frac{1}{l\_{\varepsilon}(g)} \sum\_{k=1}^{N} \delta\_{\varepsilon}\left(g\_{k}, g\right)
$$

* $$\delta\_{\varepsilon}\left(g\_{k}, g\right)$$: 所有样本中, 梯度模长分布在$$\left(g-\frac{\varepsilon}{2}, g+\frac{\varepsilon}{2}\right)$$范围内的样本个数
* $$l\_{\varepsilon}(g)$$: 代表了$$\left(g-\frac{\varepsilon}{2}, g+\frac{\varepsilon}{2}\right)$$区间的长度

GHM Loss定义, 对于每个样本, 使用**梯度密度的倒数**对样本的交叉熵损失进行缩放, 即:

$$
\begin{aligned}
L\_{G H M-C} &=\frac{1}{N} \sum\_{i=1}^{N} \beta\_{i} L\_{C E}\left(p\_{i}, p\_{i}^{*}\right) \\
&=\sum\_{i=1}^{N} \frac{L\_{C E}\left(p\_{i}, p\_{i}^{*}\right)}{G D\left(g\_{i}\right)}
\end{aligned}
$$

### 计算过程

关键是在于如何计算梯度密度. 首先, 把梯度模长范围划分成10个区域. 这里要求输入必须经过sigmoid计算, 这样梯度模长的范围就限制在`0~1`之间:

```python
class GHMC(nn.Module):
    def __init__(self, bins=10, ......):
        self.bins = bins
        edges = torch.arange(bins + 1).float() / bins
......

>>> edges = tensor([0.0000, 0.1000, 0.2000, 0.3000, 0.4000,
                  0.5000, 0.6000, 0.7000, 0.8000,0.9000, 1.0000])
```

edges是每个区域的边界, 有了边界就很容易计算出梯度模长落入哪个区间内. 然后根据网络输出pred和ground true计算loss, 再将梯度密度作为权重, 对计算得到的BCE Loss进行缩放:

```python
# 计算梯度模长
g = torch.abs(pred.sigmoid().detach() - target)
# n 用来统计有效的区间数。
# 假如某个区间没有落入任何梯度模长，密度为0，需要额外考虑，不然取个倒数就无穷了。
n = 0  # n valid bins
# 通过循环计算落入10个bins的梯度模长数量
for i in range(self.bins):
    inds = (g >= edges[i]) & (g < edges[i + 1]) & valid
    num_in_bin = inds.sum().item()
    if num_in_bin > 0:
        # 重点，所谓的梯度密度就是1/num_in_bin
        weights[inds] = num_labels / num_in_bin
        n += 1
if n > 0:
    weights = weights / n
# 把上面计算的weights填到binary_cross_entropy_with_logits里就行了
loss = torch.nn.functional.binary_cross_entropy_with_logits(
    pred, target, weights, reduction='sum') / num_labels
```

## 参考资料

* [5分钟理解Focal Loss与GHM——解决样本不平衡利器](https://zhuanlan.zhihu.com/p/80594704)
* [目标检测算法之AAAI2019 Oral论文GHM Loss](https://cloud.tencent.com/developer/article/1552618)
* [GHM\_Detection](https://github.com/libuyu/GHM_Detection)
