Focal Loss for Dense Object Detection
首先,需要了解交叉熵是怎麼工作的: https://blog.csdn.net/tsyccnh/article/details/79163834
本文的核心公式如下:
借用上面博客中的概率表:
* 貓 青蛙 老鼠
Label 0 1 0
Pred 0.3 0.6 0.1
本文將該論文應用在多分類任務中的類別不平衡問題上.
其中alpha是控制類別不平衡問題的超參數,每個類別對應相應的alpha值,樣本多的對應的alpha小,樣本少的對應alpha大.
gamma是控制難易樣本的超參數,當這個是難樣本時,預測的pt會很低,(1-pt)值就會變高,使得這個損失加大,反之,樣本爲簡單樣本時,pt會很大,而(1-pt)會很小,使得這個損失變得更小.這樣就能使得模型更加關注與難樣本.由於類別不平衡時,少量樣本的類別預測結果更差,因此也能獲得更多的關注.這樣也能起到控制類別不平衡的效果.
例如上面計算結果爲-0.4log(0.6)*alpha.
當青蛙的樣本爲難樣本時,假設預測概率爲0.2.
則計算結果就爲-0.8*log(0.2)*alpha
很明顯結果比普通的交叉熵大.
該篇論文推薦的alpha爲0.25, gamma爲2
代碼如下,input爲預測結果,如果不是softmax的預測結果則使用第二個,否則使用第一行代碼.
target爲標籤
logpt = torch.log(input)
#logpt = F.log_softmax(input)
#pt = nn.Softmax()(input) # N*H*W,C
pt = pt.gather(1, target).view(-1)
logpt = logpt.gather(1, target)
logpt = logpt * alpha
loss = -1 * (1 - pt)**self.gamma * logpt
---------------------
作者:一條不愛讀書的鹹魚
來源:CSDN
原文:https://blog.csdn.net/a362682954/article/details/85207937
版權聲明:本文爲博主原創文章,轉載請附上博文鏈接!
https://www.aiuai.cn/aifarm636.html
題目: Focal Loss for Dense Object Detection - ICCV2017
作者: Tsung-Yi, Lin, Priya Goyal, Ross Girshick, Kaiming He, Piotr Dollar
團隊: FAIR
精度最高的目標檢測器往往基於 RCNN 的 two-stage 方法,對候選目標位置再採用分類器處理. 而,one-stage 目標檢測器是對所有可能的目標位置進行規則的(regular)、密集採樣,更快速簡單,但是精度還在追趕 two-stage 檢測器. <論文所關注的問題於此.>
論文發現,密集檢測器訓練過程中,所遇到的極端前景背景類別不均衡(extreme foreground-background class imbalance)是核心原因.
對此,提出了 Focal Loss,通過修改標準的交叉熵損失函數,降低對能夠很好分類樣本的權重(down-weights the loss assigned to well-classified examples),解決類別不均衡問題.
Focal Loss 關注於在 hard samples 的稀疏子集進行訓練,並避免在訓練過程中大量的簡單負樣本淹沒檢測器. <RetinaNet>
Focal Loss 是動態縮放的交叉熵損失函數,隨着對正確分類的置信增加,縮放因子(scaling factor) 衰退到 0. 如圖:
Focal Loss 的縮放因子能夠動態的調整訓練過程中簡單樣本的權重,並讓模型快速關注於困難樣本(hard samples).
基於 Focal Loss 的 RetinaNet 的目標檢測器表現.
1. Focal Loss
Focal Loss 旨在解決 one-stage 目標檢測器在訓練過程中出現的極端前景背景類不均衡的問題(如,前景:背景 = 1:1000).
首先基於二值分類的交叉熵(cross entropy, CE) 引入 Focal Loss:
其中,
爲 groundtruth 類別; 是模型對於類別
所得到的預測概率.
符號簡介起見,定義
:
則,
.
CE Loss 如圖 Figure 1 中的上面的藍色曲線所示. 其一個顯著特點是,對於簡單易分的樣本(
),其 loss 也是一致對待. 當累加了大量簡單樣本的 loss 後,具有很小 loss 值的可能淹沒稀少的類(rare class).
1.1 均衡交叉熵 Blanced CE
解決類別不均衡的一種常用方法是,對類別 +1 引入權重因子
,對於類別 -1 引入權重
.
符號簡介起見,定義
:
則,
-balanced CE loss 爲:
1.2 Focal Loss 定義
雖然
能夠平衡 positive/negative 樣本的重要性,但不能區分 easy/had 樣本.
對此,Focal Loss 提出將損失函數降低 easy 樣本的權重,並關注於對 hard negatives 樣本的訓練.
添加調製因子(modulating factor)
到 CE loss,其中
爲可調的 focusing 參數.
Focal Loss 定義爲:
如圖 Figure 1,給出了
中幾個值的可視化.
Focal Loss 的兩個屬性:
- [1] - 當樣本被誤分,且
值很小時,調製因子接近於 1,loss 不受影響. 隨着
- ,則調製因子接近於 0,則容易分類的樣本的損失函數被降低權重.
- [2] - focusing 參數
- 平滑地調整哪些 easy 樣本會被降低權重的比率(rate). 當 ,FL=CE;隨着 增加,調製因子的影響也會隨之增加(實驗中發現
- 效果最佳.)
直觀上,調製因子能夠減少 easy 樣本對於損失函數的貢獻,並延伸了loss 值比較地的樣本範圍.
例如,
時,被分類爲 的樣本,與 CE 相比,會減少 100x 倍;而且,被分類爲 的樣本,與 CE 相比,會有少於 1000x 倍的 loss 值. 這就自然增加了將難分類樣本的重要性(如 且時,難分類樣本的 loss 值會增加 4x 倍.)
實際上,論文采用了 Focal Loss 的
-balanced 變形:
1.3. Focal Loss 例示
Focal Loss 並不侷限於具體的形式. 這裏給出另一種例示.
假設
,
定義
爲(類似於前面對於的定義):
定義:
,其中,是 groundtruth 類別.
則:
當
時,樣本被正確分類,此時.
有:
對於交叉熵損失函數
,由,
對於 Focal Loss
,其中爲常數.
再者,假設
,則 ,其中爲常數.
則,
包含兩個參數 和,控制着 loss 曲線的陡度(steepness) 和移動(shift). 如 Figure 5.
1.4. Focal Loss 求導
的求導:
的求導:
的求導:
如圖 Figure 6. 三種 loss 函數,對於high-confidence 的預測結果,其導數都趨近於 -1 或 0.
但,與
不同的是, 和 的有效設置時,只要,二者的導數都是很小的.
2. SoftmaxFocalLoss 求導
Focal Loss 損失函數:
其中:
Softmax 函數:
其中,
爲類別數, 是網絡全連接層等的輸出向量, 是向量的第個元素值.
則
關於求導:
而,
Softmax 函數關於 x 的求導爲:
當
時,
當
時,
Softmax 的函數求導即爲:
故:
3. Pytorch 實現
import torch import torch.nn as nn import torch.nn.functional as F class FocalLoss(nn.Module): def __init__(self, alpha=0.25, gamma=2, size_average=True): super(FocalLoss, self).__init__() self.alpha = alpha self.gamma = torch.Tensor([gamma]) self.size_average = size_average if isinstance(alpha, (float, int, long)): if self.alpha > 1: raise ValueError('Not supported value, alpha should be small than 1.0') else: self.alpha = torch.Tensor([alpha, 1.0 - alpha]) if isinstance(alpha, list): self.alpha = torch.Tensor(alpha) self.alpha /= torch.sum(self.alpha) def forward(self, input, target): if input.dim() > 2: input = input.view(input.size(0), input.size(1), -1) # [N,C,H,W]->[N,C,H*W] ([N,C,D,H,W]->[N,C,D*H*W]) # target # [N,1,D,H,W] ->[N*D*H*W,1] if self.alpha.device != input.device: self.alpha = torch.tensor(self.alpha, device=input.device) target = target.view(-1, 1) logpt = torch.log(input + 1e-10) logpt = logpt.gather(1, target) logpt = logpt.view(-1, 1) pt = torch.exp(logpt) alpha = self.alpha.gather(0, target.view(-1)) gamma = self.gamma if not self.gamma.device == input.device: gamma = torch.tensor(self.gamma, device=input.device) loss = -1 * alpha * torch.pow((1 - pt), gamma) * logpt if self.size_average: loss = loss.mean() else: loss = loss.sum() return loss
(原)SphereFace及其pytorch代碼
轉載請註明出處:
http://www.cnblogs.com/darkknightzh/p/8524937.html
論文:
SphereFace: Deep Hypersphere Embedding for Face Recognition
https://arxiv.org/abs/1704.08063
http://wyliu.com/papers/LiuCVPR17v3.pdf
官方代碼:
https://github.com/wy1iu/sphereface
pytorch代碼:
https://github.com/clcarwin/sphereface_pytorch
說明:沒用過mxnet,下面的代碼註釋只是純粹從代碼的角度來分析並進行註釋,如有錯誤之處,敬請諒解,並歡迎指出。
傳統的交叉熵公式如下:
Li=−logeWTyixi+byi∑jeWTjxi+bj=−loge‖‖Wyi‖‖‖‖xi‖‖cos(θyi,i)+byi∑je‖‖Wj‖‖‖‖xi‖‖cos(θj,i)+bj
將W歸一化到1,且不考慮偏置項,即bj=0
,則上式變成:
Lmodified=1N∑i−log(e‖‖xi‖‖cos(θyi,i)∑je‖‖xi‖‖cos(θj,i))
其中θ爲w和x的夾角。
爲了進一步限制夾角的範圍,使用mθ,上式變成
Lang=1N∑i−log(e‖‖xi‖‖cos(mθyi,i)e‖‖xi‖‖cos(mθyi,i)+∑j≠yie‖‖xi‖‖cos(θj,i))
其中θ範圍爲[0,πm]
。
爲了使得上式單調,引入ψ(θyi,i)
:
Lang=1N∑i−log(e‖‖xi‖‖ψ(θyi,i)e‖‖xi‖‖ψ(θyi,i)+∑j≠yie‖‖xi‖‖cos(θj,i))
其中
ψ(θyi,i)=(−1)kcos(mθyi,i)−2k
,θyi,i∈[kπm,(k+1)πm],k∈[0,m−1],m≥1
代碼中引入了超參數λ,爲
λ=max(λmin,λmax1+0.1×iterator)
其中,λmin=5
,λmax=1500
爲程序中預先設定的值。
實際的ψ(θ)
爲
ψ(θyi)=(−1)kcos(mθyi)−2k+λcos(θyi)1+λ
對應下面代碼爲:
output = cos_theta * 1.0 output[index] -= cos_theta[index]*(1.0+0)/(1+self.lamb) output[index] += phi_theta[index]*(1.0+0)/(1+self.lamb)
對於yi處的計算,
output(yi)=cos(θyi)−cos(θyi)1+λ+ψ(θyi)1+λ=ψ(θyi)+λcos(θyi)1+λ=(−1)kcos(mθyi)−2k+λcos(θyi)1+λ
和上面的公式對應。
具體的代碼如下(完整的代碼見參考網址):
1 class AngleLinear(nn.Module): 2 def __init__(self, in_features, out_features, m = 4, phiflag=True): 3 super(AngleLinear, self).__init__() 4 self.in_features = in_features 5 self.out_features = out_features 6 self.weight = Parameter(torch.Tensor(in_features,out_features)) 7 self.weight.data.uniform_(-1, 1).renorm_(2,1,1e-5).mul_(1e5) 8 self.phiflag = phiflag 9 self.m = m 10 self.mlambda = [ 11 lambda x: x**0, # cos(0*theta)=1 12 lambda x: x**1, # cos(1*theta)=cos(theta) 13 lambda x: 2*x**2-1, # cos(2*theta)=2*cos(theta)**2-1 14 lambda x: 4*x**3-3*x, 15 lambda x: 8*x**4-8*x**2+1, 16 lambda x: 16*x**5-20*x**3+5*x 17 ] 18 19 def forward(self, input): # input爲輸入的特徵,(B, C),B爲batchsize,C爲圖像的類別總數 20 x = input # size=(B,F),F爲特徵長度,如512 21 w = self.weight # size=(F,C) 22 23 ww = w.renorm(2,1,1e-5).mul(1e5) #對w進行歸一化,renorm使用L2範數對第1維度進行歸一化,將大於1e-5的截斷,乘以1e5,使得最終歸一化到1.如果1e-5設置的過大,裁剪時某些很小的值最終可能小於1。注意,第0維度只對每一行進行歸一化(每行平方和爲1),第1維度指對每一列進行歸一化。由於w的每一列爲x的權重,因而此處需要對每一列進行歸一化。如果要對x歸一化,需要對每一行進行歸一化,此時第二個參數應爲0 24 xlen = x.pow(2).sum(1).pow(0.5) # 對輸入x求平方,而後對不同列求和,再開方,得到每行的模,最終大小爲第0維的,即B(由於對x不歸一化,但是計算餘弦時需要歸一化,因而可以先計算模。但是對於w,不太懂爲何不直接使用這種方式,而是使用renorm函數?) 25 wlen = ww.pow(2).sum(0).pow(0.5) # 對權重w求平方,而後對不同行求和,再開方,得到每列的模(理論上之前已經歸一化,此處應該是1,但第一次運行到此處時,並不是1,不太懂),最終大小爲第1維的,即C 26 27 cos_theta = x.mm(ww) # 矩陣相乘(B,F)*(F,C)=(B,C),得到cos值,由於此處只是乘加,故未歸一化 28 cos_theta = cos_theta / xlen.view(-1,1) / wlen.view(1,-1) # 對每個cos值均除以B和C,得到歸一化後的cos值 29 cos_theta = cos_theta.clamp(-1,1) #將cos值截斷到[-1,1]之間,理論上不截斷應該也沒有問題,畢竟w和x都歸一化後,cos值不可能超出該範圍 30 31 if self.phiflag: 32 cos_m_theta = self.mlambda[self.m](cos_theta) # 通過cos_theta計算cos_m_theta,mlambda爲cos_m_theta展開的結果 33 theta = Variable(cos_theta.data.acos()) # 通過反餘弦,計算角度theta,(B,C) 34 k = (self.m*theta/3.14159265).floor() # 通過公式,計算k,(B,C)。此處爲了保證theta大於k*pi/m,轉換過來就是m*theta/pi,再向上取整 35 n_one = k*0.0 - 1 # 通過k的大小,得到同樣大小的-1矩陣,(B,C) 36 phi_theta = (n_one**k) * cos_m_theta - 2*k # 通過論文中公式,得到phi_theta。(B,C) 37 else: 38 theta = cos_theta.acos() # 得到角度theta,(B, C),每一行爲當前特徵和w的每一列的夾角 39 phi_theta = myphi(theta,self.m) # 40 phi_theta = phi_theta.clamp(-1*self.m,1) 41 42 cos_theta = cos_theta * xlen.view(-1,1) # 由於實際上不對x進行歸一化,此處cos_theta需要乘以B。(B,C) 43 phi_theta = phi_theta * xlen.view(-1,1) # 由於實際上不對x進行歸一化,此處phi_theta需要乘以B。(B,C) 44 output = (cos_theta,phi_theta) 45 return output # size=(B,C,2) 46 47 48 class AngleLoss(nn.Module): 49 def __init__(self, gamma=0): 50 super(AngleLoss, self).__init__() 51 self.gamma = gamma 52 self.it = 0 53 self.LambdaMin = 5.0 54 self.LambdaMax = 1500.0 55 self.lamb = 1500.0 56 57 def forward(self, input, target): 58 self.it += 1 59 cos_theta,phi_theta = input # cos_theta,(B,C)。 phi_theta,(B,C) 60 target = target.view(-1,1) #size=(B,1) 61 62 index = cos_theta.data * 0.0 #得到和cos_theta相同大小的全0矩陣。(B,C) 63 index.scatter_(1,target.data.view(-1,1),1) # 得到一個one-hot矩陣,第i行只有target[i]的值爲1,其他均爲0 64 index = index.byte() # index爲float的,轉換成byte類型 65 index = Variable(index) 66 67 self.lamb = max(self.LambdaMin,self.LambdaMax/(1+0.1*self.it)) # 得到lamb 68 output = cos_theta * 1.0 #size=(B,C) # 如果直接使用output=cos_theta,可能不收斂(未測試,但其他程序中碰到過直接對輸入使用[index]無法收斂,加上*1.0可以收斂的情況) 69 output[index] -= cos_theta[index]*(1.0+0)/(1+self.lamb) # 此行及下一行將target[i]的值通過公式得到最終輸出 70 output[index] += phi_theta[index]*(1.0+0)/(1+self.lamb) 71 72 logpt = F.log_softmax(output) # 得到概率 73 logpt = logpt.gather(1,target) # 下面爲交叉熵的計算(和focal loss的計算有點類似,當gamma爲0時,爲交叉熵)。 74 logpt = logpt.view(-1) 75 pt = Variable(logpt.data.exp()) 76 77 loss = -1 * (1-pt)**self.gamma * logpt 78 loss = loss.mean() 79 80 # target = target.view(-1) # 若要簡化,理論上可直接使用這兩行計算交叉熵(此處未測試,在其他程序中使用後可以正常訓練) 81 # loss = F.cross_entropy(cos_theta, target) 82 83 return loss