文本分類(六):不平衡文本分類,Focal Loss理論及PyTorch實現


轉載於:https://zhuanlan.zhihu.com/p/361152151

轉載於:https://www.jianshu.com/p/30043bcc90b6

摘要:本篇主要從理論到實踐解決文本分類中的樣本不均衡問題。首先講了下什么是樣本不均衡現象以及可能帶來的問題;然后重點從數據層面和模型層面講解樣本不均衡問題的解決策略。數據層面主要通過欠采樣和過采樣的方式來人為調節正負樣本比例,模型層面主要是通過加權Loss,包括基於類別Loss、Focal Loss和GHM Loss三種加權Loss函數;最后講了下其他解決樣本不均衡的策略,可以通過調節閾值修改正負樣本比例和利用半監督或自監督學習解決樣本不均衡問題。需要說明下上面解決樣本不均衡問題的策略不僅僅適用於文本分類任務,還可以擴展到其他的機器學習任務中。對於希望解決樣本不均衡問題的小伙伴可能有所幫助。

一、機器學習中的樣本不均衡問題

1.1 什么是樣本不均衡現象

機器學習領域中樣本不均衡現象隨處可見。咱們舉一些例子說明,圖片分類任務中假如我們要做貓狗圖片的識別任務,因為貓狗在日常生活中隨處可見,所以對應的樣本貓和狗的圖片很好找,樣本比較均衡,咱們能很容易的得到1W張貓的圖片和1W張狗的圖片。但是如果我們現在做狗和狼圖片識別任務,那情況就有些變化了。我們還是能方便的得到1W張狗的圖片,但是狼因為在生活中不怎么常見,所以在同樣的數據采集成本下我們可能只得到100張甚至更少的狼的圖片。

 

還有比如CTR任務中我們需要預測用戶是否會對廣告進行點擊,通常情況下曝光一個廣告用戶點擊的比率非常低,這里我們假如給101個用戶曝光廣告可能只有一個人點擊,那么得到的正負樣本比例就為1:100。如果是更高層級的廣告轉化目標比如下載、付費等正負樣本的比例就更低了。

 

同樣的例子會出現在文本分類任務中,假如我們要做一個識別是否對傳奇游戲標簽感興趣的文本二分類器,用戶搜索中這部分的比例非常少,也許1W條用戶搜索query中只有50條甚至更少的樣本屬於正例。這種現象就是樣本不均衡,因為樣本會呈現一個長尾分布,頭部的標簽包含了大量的樣本,而尾部的標簽擁有很少的樣本,就像下面這張圖片中表現的那樣出現一個長長的尾巴,所以這種現場也稱為長尾現象。

圖1 長尾現象

1.2 樣本不均衡可能帶來的問題

上面講了樣本不均衡的現象在機器學習場景中經常出現,那么樣本不均衡會帶來什么問題呢?眾所周知模型訓練的本質是最小化損失函數,當某個類別的樣本數量非常龐大,損失函數的值大部分被樣本數量較大的類別所影響,導致的結果就是模型分類會傾向於樣本量較大的類別。咱們拿上面文本分類的例子來說明,現在有1W條用戶搜索的樣本,其中50條和傳奇游戲標簽有關,9950條和傳奇游戲標簽無關,那么模型全部將樣本預測為負例,也能得到99.5%的准確率,會讓人有一種模型效果還不錯的假象,但是實際的情況是這個模型根本沒什么卵用,因為我們的目標是識別出這些數量較少的類別。這也是我們在實際業務場景中遇到的問題。

總體來看,解決樣本不均衡的問題主要從數據層面和模型層面來解決,下面會分別從理論到實踐的角度分享樣本不均衡問題的解決策略。

二、從數據層面解決樣本不均衡問題

現在我們遇到樣本不均衡的問題,假如我們的正樣本只有100條,而負樣本可能有1W條。如果不采取任何策略,那么我們就是使用這1.01W條樣本去訓練模型。從數據層面解決樣本不均衡的問題核心是通過人為控制正負樣本的比例,分成欠采樣和過采樣兩種。

2.1 欠采樣

欠采樣的基本做法是這樣的,現在我們的正負樣本比例為1:100。如果我們想讓正負樣本比例不超過1:10,那么模型訓練的時候數量比較少的正樣本也就是100條全部使用,而負樣本隨機挑選1000條,這樣通過人為的方式我們把樣本的正負比例強行控制在了1:10。這種方式存在一個問題,為了強行控制樣本比例我們生生的舍去了那9000條負樣本,這對於模型來說是莫大的損失。

相比於簡單的對負樣本隨機采樣的欠采樣方法,實際工作中我們會使用迭代預分類的方式來采樣負樣本。具體流程如下圖所示:

圖2 迭代預分類方式的欠采樣

首先我們會使用全部的正樣本和從負例候選集中隨機采樣一部分負樣本(這里假如是100條)去訓練第一輪分類器;然后用第一輪分類器去預測負例候選集剩余的9900條數據,把9900條負例中預測為正例的樣本(也就是預測錯誤的樣本)再隨機采樣100條和第一輪訓練的數據放到一起去訓練第二輪分類器;同樣的方法用第二輪分類器去預測負例候選集剩余的9800條數據,直到訓練的第N輪分類器可以全部識別負例候選集,這就是使用迭代預分類的方式進行欠采樣。

相比於隨機欠采樣來說迭代預分類的欠采樣方式能最大限度的利用負樣本中差異性較大的負樣本,從而在控制正負樣本比例的基礎上采樣出了最有代表意義的負樣本。

欠采樣的方式整體來說或多或少的會損失一些樣本,對於那些需要控制樣本量級的場景下比較合適。如果沒有嚴格控制樣本量級的要求那么下面的過采樣可能會更加適合你。

2.2 過采樣

過采樣和上面的欠采樣比較類似,都是人工干預控制樣本的比例,不同的是過采樣不會損失樣本。還拿上面的例子,現在有正樣本100條,負樣本1W條,最簡單的過采樣方式是我們會使用全部的負樣本1W條,但是為了維持正負樣本比例,我們會從正樣本中有放回的重復采樣,直到獲取了1000條正樣本,也就是說有些正樣本可能會被重復采樣到,這樣就能保持1:10的正負樣本比例了。這是最簡單的過采樣方式。

之前組里的小伙伴分享了基於SMOTE算法的過采樣方式,感興趣的小伙伴們可以關注下。在文本分類場景中我們主要通過樣本增強技術來實現過采樣。之前分享過一篇關於樣本增強技術的文章《廣告行業中那些趣事系列13:NLP中超實用的樣本增強技術》,里面包含了回譯技術、替換技術、隨機噪聲引入技術等方法可以實現樣本增強,通過這種方式可以增加正樣本,並且使得增加的正樣本不僅僅是簡單的重復樣本,而是有細微差異的正樣本,這里不再贅述。因為之前還沒接觸過文本生成,所以介紹的方法里通過文本生成來增強樣本的部分比較少。最近參加了公司的比賽,了解了一些文本生成的技術,增加點這段時間學到的通過文本生成的角度來實現文本增強的知識和大家分享下。

從文本生成的角度來增加正樣本從而間接的使用過采樣的方式來控制正負樣本比例主要嘗試過基於BERT的有條件生成任務和基於SIMBERT來生成相似文本任務:

(1) 基於BERT的有條件生成文本

基於BERT的有條件生成任務主要是利用微軟提供的UNILM來將BERT改造成可以處理Seq2Seq的任務,從而完成文本生成任務。下面是我用廣告文案語料微調BERT從而生成的部分標簽的文案的結果數據:

圖3 基於BERT的有條件生成任務部分結果

從上圖中可以發現生成的文本其實和我們使用的文案語料很相似,但是又不完全相同。模型通過微調訓練語料學習到了各個標簽的知識,然后運用這些知識生成了相似的文案,這些文案雖然只有部分修改,但是語義是比較合理的,所以生成的結果也比較合理。這塊使用的是蘇劍林的bert4keras中的例子,有興趣的小伙伴可以自己跑來玩一下:

(2) 基於SIMBERT生成相似文本

基於SIMBERT生成相似文本的方法是另外一種文本生成的方式,主要原理是生成和當前query比較相似的文本從而達到樣本增強的目的。下面是我們使用SIMBERT來生成相似文本的結果:

圖4 使用SIMBERT生成相似文本

上圖中我們輸入的初始文本是“想開一家奶茶店,需要多少的預算?”,然后下面就是自動生成的相似的文本。可以發現生成的結果還不賴,整體語義一致,而文本的形式會有些許不同,從而達到了樣本增強的目的。這塊使用的也是蘇劍林的bert4keras中的例子,感興趣的小伙伴可以去試試:

三、從模型層面解決樣本不均衡問題

上面主要從數據的層面來解決樣本不均衡的問題,本節主要從模型層面解決樣本不均衡的問題。相比於控制正負樣本的比例,我們還可以通過控制Loss損失函數來解決樣本不均衡的問題。拿二分類任務來舉例,通常使用交叉熵來計算損失,下面是交叉熵的公式:

上面的公式中y是樣本的標簽,p是樣本預測為正例的概率。

3.1 類別加權Loss

為了解決樣本不均衡的問題,最簡單的是基於類別的加權Loss,具體公式如下:

基於類別加權的Loss其實就是添加了一個參數a,這個a主要用來控制正負樣本對Loss帶來不同的縮放效果,一般和樣本數量成反比。還拿上面的例子舉例,有100條正樣本和1W條負樣本,那么我們設置a的值為10000/10100,那么正樣本對Loss的貢獻值會乘以一個系數10000/10100,而負樣本對Loss的貢獻值則會乘以一個比較小的系數100/10100,這樣相當於控制模型更加關注正樣本對損失函數的影響。通過這種基於類別的加權的方式可以從不同類別的樣本數量角度來控制Loss值,從而一定程度上解決了樣本不均衡的問題。

3.2 Focal Loss

上面基於類別加權Loss雖然在一定程度上解決了樣本不均衡的問題,但是實際的情況是不僅樣本不均衡會影響Loss,而且樣本的難易區分程度也會影響Loss。基於這個問題2017年何愷明大神在論文《Focal Loss for Dense Object Detection》中提出了非常火的Focal Loss,下面是Focal Loss的計算公式:

相比於公式2來說,Focal Loss添加了參數γ從置信的角度來加權Loss值。假如γ設置為0,那么公式3蛻變成了基於類別的加權也就是公式2;下面重點看看如何通過設置參數r來使得簡單和困難樣本對Loss的影響。當γ設置為2時,對於模型預測為正例的樣本也就是p>0.5的樣本來說,如果樣本越容易區分那么(1-p)的部分就會越小,相當於乘了一個系數很小的值使得Loss被縮小,也就是說對於那些比較容易區分的樣本Loss會被抑制,同理對於那些比較難區分的樣本Loss會被放大,這就是Focal Loss的核心:通過一個合適的函數來度量簡單樣本和困難樣本對總的損失函數的貢獻。

關於參數γ的設置問題,Focal Loss的作者建議設置為2。下面是不同的參數值γ樣本難易程度對Loss的影響對比圖:

圖5 不同的參數值γ樣本難易程度對Loss的影響對比圖

下面是一個Focal Loss的實現,感興趣的小伙伴可以試試,看能不能對下游任務有積極效果:

圖6 Focal Loss的代碼實現

3.3 GHM Loss

Focal Loss主要結合樣本的難易區分程度來解決樣本不均衡的問題,使得整個Loss的曲線平滑穩定的下降,但是對於一些特別難區分的樣本比如離群點會存在問題。可能一個模型已經收斂訓練的很好了,但是因為一些比如標注錯誤的離群點使得模型去關注這些樣本,反而降低了模型的效果。比如下面的離群點圖:

圖7 離群點圖

針對Focal Loss存在的問題,2019年論文《Gradient Harmonized Single-stage Detector》中提出了GHM(gradient harmonizing mechanism) Loss。相比於Focal Loss從置信度的角度去調整Loss,GHM Loss則是從一定范圍置信度p的樣本數量(論文中稱為梯度密度)去調整Loss。

理解GHM Loss的第一步是先理解梯度模長的概念,梯度模長g的計算公式如下:

公式4中p代表模型預測為1的概率值,p*是標簽值。也就是說如果樣本越難區分,那么g的值就越大。下面看看梯度模長g和樣本數量的關系圖:

圖8 梯度模長g和樣本數量的關系

從上圖中可以看出樣本中有很大一部分是容易區分的樣本,也就是梯度模長g趨於0的部分。但是還存在一些十分困難區分的樣本,也就是上圖中右邊紅圈中的樣本。GHM Loss認為不僅僅要多關注容易區分的樣本,這點和Focal Loss一致,同時還認為需要關注那些十分困難區分的樣本,因為這部分樣本可能是標注錯誤的離群點,過多的關注這部分樣本不僅不會提升模型的效果,反而還會有一定的逆向效果。那么問題來了,怎么同時抑制容易區分的樣本和十分困難區分的樣本呢?

針對這個問題,從上圖中可以發現容易區分的樣本和十分困難區分的樣本都存在一個共同點:數量多。那么只要我們抑制一定梯度范圍內數量多的樣本就可以達到這個效果,GHM Loss通過梯度密度GD(g)來表示一定梯度范圍內的樣本數量。這個其實有點像物理學中的密度,一定體積的物體的質量。梯度密度GD(G)的公式如下:

公式5中 [公式] 代表樣本中梯度模長g分布在 [公式] 范圍里面的樣本的個數, [公式] 代表了[公式]區間的長度。公式里面的細節小伙伴們可以去論文里面詳細了解。

說完了梯度密度GD(g)的計算公式,下面就是GHM Loss的計算公式:

公式6中的Lce其實就是交叉熵損失函數,也就是公式1。

下面看看交叉熵損失函數、Focal Loss和GHM Loss三種損失函數對不同梯度模長樣本的抑制效果圖:

圖9 三種損失函數對樣本的抑制效果圖

從上圖中可以看出交叉熵損失函數基本沒有抑制效果,Focal Loss可以有效的抑制容易區分的樣本,而GHM Loss不僅可以很好的抑制簡單樣本,還能抑制十分困難的樣本。

下面是復現了GHM Loss的一個github上工程,有興趣的小伙伴可以試試:

四、其他解決樣本不均衡問題的策略

上面主要是從數據層面和模型損失函數來解決樣本不均衡的問題,下面是一些其他的樣本不均衡策略:

4.1 調節閾值修改正負樣本比例

通常情況下Sigmoid函數會將大於0.5的閾值預測為正樣本。這時候我們可以通過調節閾值來調整正負樣本比例,比如設置0.3分作為閾值,將大於0.3的樣本都判定為正樣本,這樣相當於增加了正樣本的比例。

4.2 利用半監督或自監督學習解決樣本不均衡

之前領導分享過一篇知乎高贊的文章,主要是通過半監督或自監督學習來解決樣本不均衡的問題,因為篇幅有限,這里就不詳細介紹。后面可能會專門出一篇文章來詳細講解這種策略。這里先把鏈接放在這里,有興趣的小伙伴也可以學習下:

NeurIPS 2020 | 數據類別不平衡/長尾分布?不妨利用半監督或自監督學習

總結

本篇主要從理論到實踐解決文本分類中的樣本不均衡問題。首先講了下什么是樣本不均衡現象以及可能帶來的問題;然后重點從數據層面和模型層面講解樣本不均衡問題的解決策略。數據層面主要通過欠采樣和過采樣的方式來人為調節正負樣本比例,模型層面主要是通過加權Loss,包括基於類別Loss、Focal Loss和GHM Loss三種加權Loss函數;最后講了下其他解決樣本不均衡的策略,可以通過調節閾值修改正負樣本比例和利用半監督或自監督學習解決樣本不均衡問題。需要說明下上面解決樣本不均衡問題的策略不僅僅適用於文本分類任務,還可以擴展到其他的機器學習任務中。對於希望解決樣本不均衡問題的小伙伴可能有所幫助。

參考資料

[1] 《Focal Loss forDense Object Detection》

[2]《GradientHarmonized Single-stage Detector》

五、實戰

一、基本理論

  1. 采用soft - gamma: 在訓練的過程中階段性的增大gamma 可能會有更好的性能提升。
  2. alpha 與每個類別在訓練數據中的頻率有關。
  3. F.nll_loss(torch.log(F.softmax(inputs, dim=1),target)的函數功能與F.cross_entropy相同。
    F.nll_loss中實現了對於target的one-hot encoding,將其編碼成與input shape相同的tensor,然后與前面那一項(即F.nll_loss輸入的第一項)進行 element-wise production。
 
基於alpha=1采用不同的gamma值進行實驗的結果
  1. focal loss解決了什么問題?
    (1)不同類別不均衡
    (2)難易樣本不均衡

  2. 在retinanet中,除了使用呢focal loss外,還對初始化做了特殊處理,具體是怎么做的?

在retinanet中,對 classification subnet 的最后一層conv設置它的偏置b為:

b=−log((1−π)/π)

π代表先驗概率,就是類別不平衡中個數少的那個類別占總數的百分比,在檢測中就是代表object的anchor占所有anchor的比重,論文中設置的為0.01。

二、公式

標准的Cross Entropy 為:[圖片上傳失敗...(image-286df1-1571884440851)]

Focal Loss 為:[圖片上傳失敗...(image-460db1-1571884440851)]

其中,[圖片上傳失敗...(image-d6c655-1571884440851)]

關於Focal Loss的前向與后向推導見:知乎:Focal Loss 的前向與后向公式推導

三、代碼實現

一、來自Kaggle的實現(基於二分類交叉熵實現)

class FocalLoss(nn.Module): def __init__(self, alpha=1, gamma=2, logits=False, reduce=True): super(FocalLoss, self).__init__() self.alpha = alpha self.gamma = gamma self.logits = logits self.reduce = reduce def forward(self, inputs, targets): if self.logits: BCE_loss = F.binary_cross_entropy_with_logits(inputs, targets, reduce=False) else: BCE_loss = F.binary_cross_entropy(inputs, targets, reduce=False) pt = torch.exp(-BCE_loss) F_loss = self.alpha * (1-pt)**self.gamma * BCE_loss if self.reduce: return torch.mean(F_loss) else: return F_loss 

二、來自知乎大佬的實現:

import torch import torch.nn as nn import torch.nn.functional as F from torch.autograd import Variable class FocalLoss(nn.Module): r""" This criterion is a implemenation of Focal Loss, which is proposed in Focal Loss for Dense Object Detection. Loss(x, class) = - \alpha (1-softmax(x)[class])^gamma \log(softmax(x)[class]) The losses are averaged across observations for each minibatch. Args: alpha(1D Tensor, Variable) : the scalar factor for this criterion gamma(float, double) : gamma > 0; reduces the relative loss for well-classified examples (p > .5), putting more focus on hard, misclassified examples size_average(bool): By default, the losses are averaged over observations for each minibatch. However, if the field size_average is set to False, the losses are instead summed for each minibatch. """ def __init__(self, class_num, alpha=None, gamma=2, size_average=True): super(FocalLoss, self).__init__() if alpha is None: self.alpha = Variable(torch.ones(class_num, 1)) else: if isinstance(alpha, Variable): self.alpha = alpha else: self.alpha = Variable(alpha) self.gamma = gamma self.class_num = class_num self.size_average = size_average def forward(self, inputs, targets): N = inputs.size(0) C = inputs.size(1) P = F.softmax(inputs) class_mask = inputs.data.new(N, C).fill_(0) class_mask = Variable(class_mask) ids = targets.view(-1, 1) class_mask.scatter_(1, ids.data, 1.) #print(class_mask) if inputs.is_cuda and not self.alpha.is_cuda: self.alpha = self.alpha.cuda() alpha = self.alpha[ids.data.view(-1)] probs = (P*class_mask).sum(1).view(-1,1) log_p = probs.log() #print('probs size= {}'.format(probs.size())) #print(probs) batch_loss = -alpha*(torch.pow((1-probs), self.gamma))*log_p #print('-----bacth_loss------') #print(batch_loss) if self.size_average: loss = batch_loss.mean() else: loss = batch_loss.sum() return loss 

參考

  1. 知乎:Focal Loss 的Pytorch 實現以及實驗
  2. Kaggle:A Pytorch implementation of Focal Loss
  3. GitHub:CoinCheung/pytorch-loss
  4. GitHub:Hsuxu/Loss_ToolBox-PyTorch
  5. 知乎:focal loss理解與初始化偏置b設置解釋
  6. 個人博客:對focal loss中bias init的解釋很好
 The highest accuracy object detectors to date are based on a two-stage approach popularized by R-CNN, where a classifier is applied to a sparse set of candidate object locations. In contrast, one-stage detectors that are applied over a regular, dense sampling of possible object locations have the potential to be faster and simpler, but have trailed the accuracy of two-stage detectors thus far. In this paper, we investigate why this is the case. We discover that the extreme foreground-background class imbalance encountered during training of dense detectors is the central cause. We propose to address this class imbalance by reshaping the standard cross entropy loss such that it down-weights the loss assigned to well-classified examples. Our novel Focal Loss focuses training on a sparse set of hard examples and prevents the vast number of easy negatives from overwhelming the detector during training. To evaluate the effectiveness of our loss, we design and train a simple dense detector we call RetinaNet. Our results show that when trained with the focal loss, RetinaNet is able to match the speed of previous one-stage detectors while surpassing the accuracy of all existing state-of-the-art two-stage detectors. Code is at: https://github.com/facebookresearch/Detectron.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM