文章來自公衆號【機器學習煉丹術】python
焦點損失函數 Focal Loss(2017年何凱明大佬的論文)被提出用於密集物體檢測任務。機器學習
固然,在目標檢測中,可能待檢測物體有1000個類別,然而你想要識別出來的物體,只是其中的某一個類別,這樣其實就是一個樣本很是不均衡的一個分類問題。函數
而Focal Loss簡單的說,就是解決樣本數量極度不平衡的問題的。學習
說到樣本不平衡的解決方案,相比你們是知道一個混淆矩陣的f1-score的,可是這個好像不能用在訓練中當成損失。而Focal loss能夠在訓練中,讓小數量的目標類別增長權重,讓分類錯誤的樣本增長權重。spa
先來看一下簡單的二值交叉熵的損失:
.net
【而後看focal loss的改進】:
這個增長了一個$(1-y')^\gamma$的權重值,怎麼理解呢?就是若是給出的正確類別的機率越大,那麼$(1-y')^\gamma$就會越小,說明分類正確的樣本的損失權重小,反之,分類錯誤的樣本的損權重大。3d
【focal loss的進一步改進】:
這裏增長了一個$\alpha$,這個alpha在論文中給出的是0.25,這個就是單純的下降正樣本或者負樣本的權重,來解決樣本不均衡的問題。code
二者結合起來,就是一個能夠解決樣本不平衡問題的損失focal loss。orm
【總結】:blog
這個GHM是爲了解決Focal loss存在的一些問題。
【Focal Loss的弊端1】
讓模型過多的關注特別難分類的樣本是會有問題的。樣本中有一些異常點、離羣點(outliers)。因此模型爲了擬合這些很是難擬合的離羣點,就會存在過擬合的風險。
Focal Loss是從置信度p的角度入手衰減loss的。而GHM是必定範圍內置信度p的樣本數量來衰減loss的。
首先定義了一個變量g,叫作梯度模長(gradient norm):
能夠看出這個梯度模長,其實就是模型給出的置信度$p^*$與這個樣本真實的標籤之間的差值(距離)。g越小,說明預測越準,說明樣本越容易分類。
下圖中展現了g與樣本數量的關係:
【從圖中能夠看到】
GHM是這樣想的,對於梯度模長小的易分類樣本,咱們忽視他們;可是focal loss過於關注難分類樣本了。關鍵是難分類樣本其實也有不少!,若是模型一直學習難分類樣本,那麼可能模型的精確度就會降低。因此GHM對於難分類樣本也有一個衰減。
那麼,GHM對易分類樣本和難分類樣本都衰減,那麼真正被關注的樣本,就是那些不難不易的樣本。而抑制的程度,能夠根據樣本的數量來決定。
這裏定義一個GD,梯度密度:
$$GD(g)=\frac{1}{l(g)}\sum_{k=1}^N{\delta(g_k,g)}$$
總之,$GD(g)$就是梯度模長在$[g-\frac{\epsilon}{2},g+\frac{\epsilon}{2}]$內的樣本總數除以$\epsilon$.
而後把每個樣本的交叉熵損失除以他們對應的梯度密度就好了。
$$L_{GHM}=\sum^N_{i=1}{\frac{CE(p_i,p_i^*)}{GD(g_i)}}$$
論文中呢,是把梯度模長劃分紅了10個區域,由於置信度p是從0~1的,因此梯度密度的區域長度就是0.1,好比是0~0.1爲一個區域。
下圖是論文中給出的對比圖:
【從圖中能夠獲得】
固然能夠想到的是,GHM看起來是須要整個樣本的模型估計值,才能計算出梯度密度,才能進行更新。也就是說mini-batch看起來彷佛不能用GHM。
在GHM原文中也提到了這個問題,若是光使用mini-batch的話,那麼極可能出現不均衡的狀況。
【我我的以爲的處理方法】
上面講述的關鍵在於focal loss實現的功能:
在CenterNet中預測中心點位置的時候,也是使用了Focal Loss,可是稍有改動。
下面經過代碼來理解:
class FocalLoss(nn.Module): def __init__(self): super().__init__() self.neg_loss = _neg_loss def forward(self, output, target, mask): output = torch.sigmoid(output) loss = self.neg_loss(output, target, mask) return loss
這裏面的output能夠理解爲是一個1通道的特徵圖,每個pixel的值都是模型給出的置信度,而後經過sigmoid函數轉換成0~1區間的置信度。
而target是CenterNet的熱力圖,這一點可能比較難理解。打個比方,一個10*10的全都是0的特徵圖,而後這個特徵圖中只有一個pixel是1,那麼這個pixel的位置就是一個目標檢測物體的中心點。有幾個1就說明這個圖中有幾個要檢測的目標物體。
而後,若是一個特徵圖上,全都是0,只有幾個孤零零的1,未免顯得過於稀疏了,直觀上也很是的不平滑。因此CenterNet的熱力圖還須要對這些1爲中心作一個高斯
能夠看做是一種平滑:
能夠看到,數字1的四周是一樣的數字。這是一個以1爲中心的高斯平滑。
這裏咱們回到上面說到的$(1-Y)^\beta$:
對於數字1來講,咱們計算loss天然是用第一行來計算,可是對於1附近的其餘點來講,就要考慮$(1-Y)^\beta$了。越靠近1的點的$Y$越大,那麼$(1-Y)^\beta$就會越小,這樣從而下降1附近的權重值。其實這裏我也講不太明白,就是根據距離1的距離下降負樣本的權重值,從而能夠實現樣本過多的類別的權重較小。
咱們回到主題,對output進行sigmoid以後,與output一塊兒放到了neg_loss中。咱們來看什麼是neg_loss:
def _neg_loss(pred, gt, mask): pos_inds = gt.eq(1).float() * mask neg_inds = gt.lt(1).float() * mask neg_weights = torch.pow(1 - gt, 4) loss = 0 pos_loss = torch.log(pred) * torch.pow(1 - pred, 2) * pos_inds neg_loss = torch.log(1 - pred) * torch.pow(pred, 2) * \ neg_weights * neg_inds num_pos = pos_inds.float().sum() pos_loss = pos_loss.sum() neg_loss = neg_loss.sum() if num_pos == 0: loss = loss - neg_loss else: loss = loss - (pos_loss + neg_loss) / num_pos return loss
先說一下,這裏面的mask是根據特定任務中加上的一個小功能,就是在該任務中,一張圖片中有一部分是不須要計算loss的,因此先用過mask把那個部分過濾掉。這裏直接忽視mask就行了。
從neg_weights = torch.pow(1 - gt, 4)
能夠得知$\beta=4$,從下面的代碼中也不難推出,$\alpha=2$,剩下的內容就都同樣了。
把每個pixel的損失都加起來,除以目標物體的數量便可。