paddle 裏面沒有 focal loss 的API,不過這個loss函數比較簡單,所以決定自己實現嘗試一下。在 paddle 裏面實現類似這樣的功能有兩種選擇:
- 使用 paddle 現有的 op 去組合出來所需要的能力
- 自己實現 op
- python 端實現 op
- C++ 端實現 op
兩種思路都可以實現,但是難度相差很多,前者比較簡單,熟悉 paddle 的數學操作,理解公式含義即可。後者又分兩種方式,python 端實現相對簡單,C++端實現比較複雜。這次嘗試用 paddle 的 op 組合實現。
理解 focal loss
最早出現在單階段目標檢測當中。單階段訓練時正負樣本比例難以控制,loss 被大量的負樣本充斥,導致反向傳播時向着調整負樣本方向前進,而正樣本的訓練不佳。爲了解決這個問題,何凱明大神提出了 focal loss 損失函數調節正負樣本權重,同時還能進一步調節難易樣本的權重。進一步的理解可上網查看,資料很多。原版 focal loss 的公式如下:
loss = -at·(1 - pt)^r · log(pt)
at和r 是超參數,分別用於控制正負樣本的權重和難易樣本的權重。對於多分類而言。at 不太好處理,因此這次實現暫時忽略,後續想法是根據各個類別佔總樣本中的比例設置。
# 調試打印函數,通過 py_func 實現打印參數值的 op,方便調試
def print_func(var):
logger.info("in py func type: {0}".format(type(var)))
print(np.array(var))
# 不帶類別平衡的 focal loss,僅僅區分類別難易;猜測此時算出來的梯度有一個 gama 倍,所以學習率可以比以往更小一點
def focal_loss(pred, label, gama):
# 使用打印函數查看當前 Tensor,
# fluid.layers.py_func(func=print_func, x=pred, out=None)
one_hot = paddle.fluid.layers.one_hot(label, train_parameters['class_dim'])
prob = one_hot * pred
cross_entropy = one_hot * fluid.layers.log(pred)
cross_entropy = fluid.layers.reduce_sum(cross_entropy, dim=-1)
sum = paddle.fluid.layers.sum(cross_entropy)
weight = -1.0 * one_hot * paddle.fluid.layers.pow((1.0 - pred), gama)
weight = fluid.layers.reduce_sum(weight, dim=-1)
return weight * cross_entropy