損失函數

2021-03-02 算法美食屋

一般來說,監督學習的目標函數由損失函數和正則化項組成。(Objective = Loss + Regularization)

Pytorch中的損失函數一般在訓練模型時候指定。

注意Pytorch中內置的損失函數的參數和tensorflow不同,是y_pred在前,y_true在後,而Tensorflow是y_true在前,y_pred在後。

對於回歸模型,通常使用的內置損失函數是均方損失函數nn.MSELoss 。

對於二分類模型,通常使用的是二元交叉熵損失函數nn.BCELoss (輸入已經是sigmoid激活函數之後的結果)或者 nn.BCEWithLogitsLoss (輸入尚未經過nn.Sigmoid激活函數) 。

對於多分類模型,一般推薦使用交叉熵損失函數 nn.CrossEntropyLoss。(y_true需要是一維的,是類別編碼。y_pred未經過nn.Softmax激活。)

此外,如果多分類的y_pred經過了nn.LogSoftmax激活,可以使用nn.NLLLoss損失函數(The negative log likelihood loss)。這種方法和直接使用nn.CrossEntropyLoss等價。

如果有需要,也可以自定義損失函數,自定義損失函數需要接收兩個張量y_pred,y_true作為輸入參數,並輸出一個標量作為損失函數值。

Pytorch中的正則化項一般通過自定義的方式和損失函數一起添加作為目標函數。

如果僅僅使用L2正則化,也可以利用優化器的weight_decay參數來實現相同的效果。

一,內置損失函數
import numpy as np
import pandas as pd
import torch 
from torch import nn 
import torch.nn.functional as F 


y_pred = torch.tensor([[10.0,0.0,-10.0],[8.0,8.0,8.0]])
y_true = torch.tensor([0,2])

# 直接調用交叉熵損失
ce = nn.CrossEntropyLoss()(y_pred,y_true)
print(ce)

# 等價於先計算nn.LogSoftmax激活,再調用NLLLoss
y_pred_logsoftmax = nn.LogSoftmax(dim = 1)(y_pred)
nll = nn.NLLLoss()(y_pred_logsoftmax,y_true)
print(nll)

tensor(0.5493)
tensor(0.5493)

內置的損失函數一般有類的實現和函數的實現兩種形式。

如:nn.BCE 和 F.binary_cross_entropy 都是二元交叉熵損失函數,前者是類的實現形式,後者是函數的實現形式。

實際上類的實現形式通常是調用函數的實現形式並用nn.Module封裝後得到的。

一般我們常用的是類的實現形式。它們封裝在torch.nn模塊下,並且類名以Loss結尾。

常用的一些內置損失函數說明如下。

nn.MSELoss(均方誤差損失,也叫做L2損失,用於回歸)

nn.L1Loss (L1損失,也叫做絕對值誤差損失,用於回歸)

nn.SmoothL1Loss (平滑L1損失,當輸入在-1到1之間時,平滑為L2損失,用於回歸)

nn.BCELoss (二元交叉熵,用於二分類,輸入已經過nn.Sigmoid激活,對不平衡數據集可以用weigths參數調整類別權重)

nn.BCEWithLogitsLoss (二元交叉熵,用於二分類,輸入未經過nn.Sigmoid激活)

nn.CrossEntropyLoss (交叉熵,用於多分類,要求label為稀疏編碼,輸入未經過nn.Softmax激活,對不平衡數據集可以用weigths參數調整類別權重)

nn.NLLLoss (負對數似然損失,用於多分類,要求label為稀疏編碼,輸入經過nn.LogSoftmax激活)

nn.CosineSimilarity(餘弦相似度,可用於多分類)

nn.AdaptiveLogSoftmaxWithLoss (一種適合非常多類別且類別分布很不均衡的損失函數,會自適應地將多個小類別合成一個cluster)

更多損失函數的介紹參考如下知乎文章:

《PyTorch的十八個損失函數》

https://zhuanlan.zhihu.com/p/61379965

二,自定義損失函數

自定義損失函數接收兩個張量y_pred,y_true作為輸入參數,並輸出一個標量作為損失函數值。

也可以對nn.Module進行子類化,重寫forward方法實現損失的計算邏輯,從而得到損失函數的類的實現。

下面是一個Focal Loss的自定義實現示範。Focal Loss是一種對binary_crossentropy的改進損失函數形式。

它在樣本不均衡和存在較多易分類的樣本時相比binary_crossentropy具有明顯的優勢。

它有兩個可調參數,alpha參數和gamma參數。其中alpha參數主要用於衰減負樣本的權重,gamma參數主要用於衰減容易訓練樣本的權重。

從而讓模型更加聚焦在正樣本和困難樣本上。這就是為什麼這個損失函數叫做Focal Loss。

詳見《5分鐘理解Focal Loss與GHM——解決樣本不平衡利器》

https://zhuanlan.zhihu.com/p/80594704

class FocalLoss(nn.Module):
    
    def __init__(self,gamma=2.0,alpha=0.75):
        super().__init__()
        self.gamma = gamma
        self.alpha = alpha

    def forward(self,y_pred,y_true):
        bce = torch.nn.BCELoss(reduction = "none")(y_pred,y_true)
        p_t = (y_true * y_pred) + ((1 - y_true) * (1 - y_pred))
        alpha_factor = y_true * self.alpha + (1 - y_true) * (1 - self.alpha)
        modulating_factor = torch.pow(1.0 - p_t, self.gamma)
        loss = torch.mean(alpha_factor * modulating_factor * bce)
        return loss
    
    
    

#困難樣本
y_pred_hard = torch.tensor([[0.5],[0.5]])
y_true_hard = torch.tensor([[1.0],[0.0]])

#容易樣本
y_pred_easy = torch.tensor([[0.9],[0.1]])
y_true_easy = torch.tensor([[1.0],[0.0]])

focal_loss = FocalLoss()
bce_loss = nn.BCELoss()

print("focal_loss(hard samples):", focal_loss(y_pred_hard,y_true_hard))
print("bce_loss(hard samples):", bce_loss(y_pred_hard,y_true_hard))
print("focal_loss(easy samples):", focal_loss(y_pred_easy,y_true_easy))
print("bce_loss(easy samples):", bce_loss(y_pred_easy,y_true_easy))

#可見 focal_loss讓容易樣本的權重衰減到原來的 0.0005/0.1054 = 0.00474
#而讓困難樣本的權重只衰減到原來的 0.0866/0.6931=0.12496

# 因此相對而言,focal_loss可以衰減容易樣本的權重。



focal_loss(hard samples): tensor(0.0866)
bce_loss(hard samples): tensor(0.6931)
focal_loss(easy samples): tensor(0.0005)
bce_loss(easy samples): tensor(0.1054)

FocalLoss的使用完整範例可以參考下面中自定義L1和L2正則化項中的範例,該範例既演示了自定義正則化項的方法,也演示了FocalLoss的使用方法。

三,自定義L1和L2正則化項

通常認為L1 正則化可以產生稀疏權值矩陣,即產生一個稀疏模型,可以用於特徵選擇。

而L2 正則化可以防止模型過擬合(overfitting)。一定程度上,L1也可以防止過擬合。

下面以一個二分類問題為例,演示給模型的目標函數添加自定義L1和L2正則化項的方法。

這個範例同時演示了上一個部分的FocalLoss的使用。

1,準備數據

import numpy as np 
import pandas as pd 
from matplotlib import pyplot as plt
import torch
from torch import nn
import torch.nn.functional as F
from torch.utils.data import Dataset,DataLoader,TensorDataset
import torchkeras 
%matplotlib inline
%config InlineBackend.figure_format = 'svg'

#正負樣本數量
n_positive,n_negative = 200,6000

#生成正樣本, 小圓環分布
r_p = 5.0 + torch.normal(0.0,1.0,size = [n_positive,1]) 
theta_p = 2*np.pi*torch.rand([n_positive,1])
Xp = torch.cat([r_p*torch.cos(theta_p),r_p*torch.sin(theta_p)],axis = 1)
Yp = torch.ones_like(r_p)

#生成負樣本, 大圓環分布
r_n = 8.0 + torch.normal(0.0,1.0,size = [n_negative,1]) 
theta_n = 2*np.pi*torch.rand([n_negative,1])
Xn = torch.cat([r_n*torch.cos(theta_n),r_n*torch.sin(theta_n)],axis = 1)
Yn = torch.zeros_like(r_n)

#匯總樣本
X = torch.cat([Xp,Xn],axis = 0)
Y = torch.cat([Yp,Yn],axis = 0)


#可視化
plt.figure(figsize = (6,6))
plt.scatter(Xp[:,0],Xp[:,1],c = "r")
plt.scatter(Xn[:,0],Xn[:,1],c = "g")
plt.legend(["positive","negative"]);

ds = TensorDataset(X,Y)

ds_train,ds_valid = torch.utils.data.random_split(ds,[int(len(ds)*0.7),len(ds)-int(len(ds)*0.7)])
dl_train = DataLoader(ds_train,batch_size = 100,shuffle=True,num_workers=2)
dl_valid = DataLoader(ds_valid,batch_size = 100,num_workers=2)

2,定義模型

class DNNModel(torchkeras.Model):
    def __init__(self):
        super(DNNModel, self).__init__()
        self.fc1 = nn.Linear(2,4)
        self.fc2 = nn.Linear(4,8) 
        self.fc3 = nn.Linear(8,1)
        
    def forward(self,x):
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        y = nn.Sigmoid()(self.fc3(x))
        return y
        
model = DNNModel()

model.summary(input_shape =(2,))

----
        Layer (type)               Output Shape         Param #
================================================================
            Linear-1                    [-1, 4]              12
            Linear-2                    [-1, 8]              40
            Linear-3                    [-1, 1]               9
================================================================
Total params: 61
Trainable params: 61
Non-trainable params: 0
----
Input size (MB): 0.000008
Forward/backward pass size (MB): 0.000099
Params size (MB): 0.000233
Estimated Total Size (MB): 0.000340
----

3,訓練模型

# 準確率
def accuracy(y_pred,y_true):
    y_pred = torch.where(y_pred>0.5,torch.ones_like(y_pred,dtype = torch.float32),
                      torch.zeros_like(y_pred,dtype = torch.float32))
    acc = torch.mean(1-torch.abs(y_true-y_pred))
    return acc

# L2正則化
def L2Loss(model,alpha):
    l2_loss = torch.tensor(0.0, requires_grad=True)
    for name, param in model.named_parameters():
        if 'bias' not in name: #一般不對偏置項使用正則
            l2_loss = l2_loss + (0.5 * alpha * torch.sum(torch.pow(param, 2)))
    return l2_loss

# L1正則化
def L1Loss(model,beta):
    l1_loss = torch.tensor(0.0, requires_grad=True)
    for name, param in model.named_parameters():
        if 'bias' not in name:
            l1_loss = l1_loss +  beta * torch.sum(torch.abs(param))
    return l1_loss

# 將L2正則和L1正則添加到FocalLoss損失,一起作為目標函數
def focal_loss_with_regularization(y_pred,y_true):
    focal = FocalLoss()(y_pred,y_true) 
    l2_loss = L2Loss(model,0.001) #注意設置正則化項係數
    l1_loss = L1Loss(model,0.001)
    total_loss = focal + l2_loss + l1_loss
    return total_loss

model.compile(loss_func =focal_loss_with_regularization,
              optimizer= torch.optim.Adam(model.parameters(),lr = 0.01),
             metrics_dict={"accuracy":accuracy})

dfhistory = model.fit(30,dl_train = dl_train,dl_val = dl_valid,log_step_freq = 30)

Start Training ...

================================================================================2020-07-11 23:34:17
{'step': 30, 'loss': 0.021, 'accuracy': 0.972}

 +--+--+++----+
| epoch |  loss | accuracy | val_loss | val_accuracy |
+--+--+++----+
|   1   | 0.022 |  0.971   |  0.025   |     0.96     |
+--+--+++----+

================================================================================2020-07-11 23:34:27
{'step': 30, 'loss': 0.016, 'accuracy': 0.984}

 +--+--+++----+
| epoch |  loss | accuracy | val_loss | val_accuracy |
+--+--+++----+
|   30  | 0.016 |  0.981   |  0.017   |    0.983     |
+--+--+++----+

================================================================================2020-07-11 23:34:27
Finished Training...

# 結果可視化
fig, (ax1,ax2) = plt.subplots(nrows=1,ncols=2,figsize = (12,5))
ax1.scatter(Xp[:,0],Xp[:,1], c="r")
ax1.scatter(Xn[:,0],Xn[:,1],c = "g")
ax1.legend(["positive","negative"]);
ax1.set_title("y_true");

Xp_pred = X[torch.squeeze(model.forward(X)>=0.5)]
Xn_pred = X[torch.squeeze(model.forward(X)<0.5)]

ax2.scatter(Xp_pred[:,0],Xp_pred[:,1],c = "r")
ax2.scatter(Xn_pred[:,0],Xn_pred[:,1],c = "g")
ax2.legend(["positive","negative"]);
ax2.set_title("y_pred");

四,通過優化器實現L2正則化

如果僅僅需要使用L2正則化,那麼也可以利用優化器的weight_decay參數來實現。

weight_decay參數可以設置參數在訓練過程中的衰減,這和L2正則化的作用效果等價。

before L2 regularization:

gradient descent: w = w - lr * dloss_dw 

after L2 regularization:

gradient descent: w = w - lr * (dloss_dw+beta*w) = (1-lr*beta)*w - lr*dloss_dw

so (1-lr*beta)is the weight decay ratio.

Pytorch的優化器支持一種稱之為Per-parameter options的操作,就是對每一個參數進行特定的學習率,權重衰減率指定,以滿足更為細緻的要求。

weight_params = [param for name, param in model.named_parameters() if "bias" not in name]
bias_params = [param for name, param in model.named_parameters() if "bias" in name]

optimizer = torch.optim.SGD([{'params': weight_params, 'weight_decay':1e-5},
                             {'params': bias_params, 'weight_decay':0}],
                            lr=1e-2, momentum=0.9)

相關焦點

  • 【損失函數】常見的損失函數(loss function)總結
    不同的模型用的損失函數一般也不一樣。損失函數分為經驗風險損失函數和結構風險損失函數。經驗風險損失函數指預測結果和實際結果的差別,結構風險損失函數是指經驗風險損失函數加上正則項。常見的損失函數以及其優缺點如下:0-1損失函數(zero-one loss)0-1損失是指預測值和目標值不相等為1, 否則為0:
  • 機器學習-損失函數
    編輯:Gemini損失函數(loss function)是用來估量你模型的預測值f(x)與真實值Y的不一致程度,它是一個非負實值函數,通常使用L(Y, f(x))來表示,損失函數越小,模型的魯棒性就越好。
  • 多類SVM的損失函數
    我們本篇線性分類器教程主要關注評分函數的概念和它的用法。但是,為了真的「學會」輸入值和類別標籤的映射關係,我們需要討論下面兩個重要的概念:在本周和下周的文章中,我們會討論兩類常見的損失函數,它們在機器學習、神經網絡和深度學習算法中都被應用:接下來,我們就討論多類SVM損失。
  • GAN是一種特殊的損失函數?
    現在,存在很多種類型的損失函數,使用哪種損失函數則取決於手頭上的任務。並且,他們有一個共同的屬性,即這些損失函數必須能夠用精確的數學表達式來表示,如:1.L1損失函數(絕對誤差):用於回歸任務。2.L2損失函數(均方誤差):和L1損失函數類似,但對異常值更加敏感。3.交叉熵損失函數:通常用於分類任務。
  • 損失函數的原理及使用場景
    損失函數:通常是針對單個訓練樣本而言,給定一個模型輸出y_和一個真實y,損失函數輸出一個實值損失L=f(y, y_)。代價函數:通常針對整個訓練集的總損失。目標函數:表示任意希望被優化的函數。1 均方誤差(RMSE)L2 loss在模型輸出與真實值的誤差服從高斯分布的假設下,如果這個假設能被滿足(比如回歸),那麼均方差損失是一個很好的損失函數選擇;如果這個假設不能被滿足(比如分類),那麼均方差損失不是一個好的選擇。
  • 機器學習經典損失函數比較
    我們常常將最小化的函數稱為損失函數,它主要用于衡量模型的預測能力。在尋找最小值的過程中,我們最常用的方法是梯度下降法,這種方法很像從山頂下降到山谷最低點的過程。 雖然損失函數描述了模型的優劣為我們提供了優化的方向,但卻不存在一個放之四海皆準的損失函數。損失函數的選取依賴於參數的數量、局外點、機器學習算法、梯度下降的效率、導數求取的難易和預測的置信度等方面。
  • 機器學習常用損失函數小結
    由於損失函數和代價函數只是在針對樣本集上有區別,因此在本文中統一使用了損失函數這個術語,但下文的相關公式實際上採用的是代價函數 Cost Function 的形式,請讀者自行留意。也就是說在模型輸出與真實值的誤差服從高斯分布的假設下,最小化均方差損失函數與極大似然估計本質上是一致的,因此在這個假設能被滿足的場景中(比如回歸),均方差損失是一個很好的損失函數選擇;當這個假設沒能被滿足的場景中(比如分類),均方差損失不是一個好的選擇。
  • 常見的損失函數(loss function)總結
    不同的模型用的損失函數一般也不一樣。損失函數分為經驗風險損失函數和結構風險損失函數。經驗風險損失函數指預測結果和實際結果的差別,結構風險損失函數是指經驗風險損失函數加上正則項。常見的損失函數以及其優缺點如下:1. 0-1損失函數(zero-one loss)0-1損失是指預測值和目標值不相等為1, 否則為0:特點:(1)0-1損失函數直接對應分類判斷錯誤的個數,但是它是一個非凸函數,不太適用.
  • 深度學習中常見的損失函數
    在深度學習分類任務中,我們經常會使用到損失函數,今天我們就來總結一下深度學習中常見的損失函數。Log損失函數是0-1損失函數的一種替代函數,其形式如下: 為什麼一開始我們說log損失函數也是0-1損失函數的一種替代函數,因為log損失函數其實也等價於如下形式:
  • XGBoost 自定義損失函數
    損失函數:損失函數描述了預測值和真實標籤的差異
  • 最全的損失函數匯總
    在多分類任務中,經常採用 softmax 激活函數+交叉熵損失函數,因為交叉熵描述了兩個概率分布的差異,然而神經網絡輸出的是向量,並不是概率分布的形式。所以需要 softmax激活函數將一個向量進行「歸一化」成概率分布的形式,再採用交叉熵損失函數計算 loss。
  • 神經網絡中的各種損失函數介紹
    不同的損失函數可用於不同的目標。在這篇文章中,我將帶你通過一些示例介紹一些非常常用的損失函數。這篇文章提到的一些參數細節都屬於tensorflow或者keras的實現細節。損失函數的簡要介紹損失函數有助於優化神經網絡的參數。
  • 如何選擇合適的損失函數,請看......
    最小化的這組函數被稱為「損失函數」。損失函數是衡量預測模型預測期望結果表現的指標。尋找函數最小值的最常用方法是「梯度下降」。把損失函數想像成起伏的山脈,梯度下降就像從山頂滑下,目的是到達山脈的最低點。沒有一個損失函數可以適用於所有類型的數據。
  • 如何為模型選擇合適的損失函數?所有ML學習者應該知道的5種回歸損失函數
    損失函數的選擇取決於許多因素,包括是否有離群點,機器學習算法的選擇,運行梯度下降的時間效率,是否易於找到函數的導數,以及預測結果的置信度。這篇文章的目的就是希望幫助大家了解不同的損失函數。機器學習中的所有算法都依賴於最小化或最大化某一個函數,我們稱之為「目標函數」。最小化的這組函數被稱為「損失函數」。損失函數是衡量預測模型預測期望結果表現的指標。
  • 煉丹系列之回歸損失函數前篇
    上一篇介紹了目標檢測任務中基於IOU的各種回歸損失函數,但是由於篇幅問題,關於更經典的L-norm損失未做介紹,本文針對L1,L2以及Smooth L1等損失進行詳細的對比總結。同時當有多個離群點時,這些點可能佔據Loss的主要部分,需要犧牲很多有效的樣本去補償它,所以MSE損失函數受離群點的影響較大。
  • 總結 | 深度學習損失函數大全
    在多分類任務中,經常採用 softmax 激活函數+交叉熵損失函數,因為交叉熵描述了兩個概率分布的差異,然而神經網絡輸出的是向量,並不是概率分布的形式。所以需要 softmax激活函數將一個向量進行「歸一化」成概率分布的形式,再採用交叉熵損失函數計算 loss。
  • 機器學習常見的損失函數以及何時使用它們
    每一個機器學習工程師都應該知道機器學習中這些常見的損失函數以及何時使用它們。在數學優化和決策理論中,損失函數或成本函數將一個或多個變量的值映射為一個實數,該實數直觀地表示與該事件相關的一些「成本」。損失函數是機器學習算法中的一個重要部分,主要用於進行算法對特徵數據集建模效果的評估,衡量算法的性能。損失函數是每個樣本預測值和真實值的差值,而成本函數是所有損失函數的平均值。但是一般兩者語義沒有明顯的區分。 損失函數直接反映了機器學習模型的預測結果。一般而言,損失函數越低,所建立的模型所提供的結果就越好。
  • 如何在Keras中創建自定義損失函數?
    Karim MANJRA 發布在 Unsplash 上的照片keras 中常用的損失函數如上所述,我們可以創建一個我們自己的自定義損失函數;但是在這之前,討論現有的 Keras 損失函數是很好的。什麼是自定義損失函數?對於不同的損失函數,計算損失的公式有不同的定義。在某些情況下,我們可能需要使用 Keras 沒有提供的損失計算公式。在這種情況下,我們可以考慮定義和使用我們自己的損失函數。這種用戶定義的損失函數稱為自定義損失函數。
  • 從最優化的角度看待Softmax損失函數
    Softmax交叉熵損失函數應該是目前最常用的分類損失函數了,在大部分文章都是從概率角度來解釋的(請讀者自行搜索),本文將嘗試從最優化的角度來推導出Softmax交叉熵損失函數,希望能夠啟發出更多的研究思路。
  • 機器學習從業者必知的5種回歸損失函數
    損失函數也能衡量預測模型在預測期望結果方面的性能。找到函數的最小值點的最常用方法是「梯度下降」。如果把損失函數比作連綿起伏的山巒,那麼梯度下降就好比愚公一樣盡力削低山脈,讓山達到最低點。損失函數,並非只有一種。根據不同的因素,包括是否存在異常值,所選機器學習算法,梯度下降的的時效,找到預測的置信度和導數的難易度,我們可以選擇不同的損失函數。