作者|PROCRASTINATOR
編譯|VK
來源|Analytics Vidhya
概述
-
了解類權重優化是如何工作的,以及如何在logistic回歸或任何其他算法中使用sklearn實現相同的方法
-
了解如何在不使用任何采樣方法的情況下,通過修改類權重可以克服類不平衡數據的問題
介紹
機器學習中的分類問題是我們給出了一些輸入(獨立變量),並且我們必須預測一個離散目標。離散值的分布極有可能是非常不同的。由於每個類的差異,算法往往偏向於現有的大多數值,而對少數值的處理效果不好。
類頻率的這種差異影響模型的整體可預測性。
在這些問題上獲得良好的准確度並不難,但並不意味着模型是良好的。我們需要檢查這些模型的性能是否具有任何商業意義或有任何價值。這就是為什么理解問題和數據是非常必要的,這樣你就可以使用正確的度量並使用適當的方法優化它。
目錄
-
什么是類別失衡?
-
為什么要處理類別不平衡?
-
什么是類別權重?
-
logistic回歸中的類權重
-
Python實現
- 簡單logistic回歸
- 加權logistic回歸('平衡')
- 加權logistic回歸(手動權重)
-
進一步提高分數的技巧
什么是類別失衡?
類不平衡是機器學習分類問題中出現的一個問題。它只說明目標類的頻率高度不平衡,即其中一個類的頻率與現有的其他類相比非常高。換句話說,對目標中的大多數類存在偏見。
假設我們考慮一個二分類,其中大多數目標類有10000個,而少數目標類只有100個。在這種情況下,比率為100:1,即每100個多數類,就只有一個少數類。這個問題就是我們所說的類別失衡。我們可以找到這些數據的一般領域有欺詐檢測、流失預測、醫療診斷、電子郵件分類等。
我們將在醫學領域中處理一個數據集,以正確理解類不平衡。在這里,我們必須根據給定的屬性(獨立變量)來預測一個人是否會患上心臟病。為了跳過數據的清理和預處理,我們使用的是數據的已清理版本。
在下面的圖像中,你可以看到目標變量的分布。
#繪制目標的條形圖
plt.figure(figsize=(10,6))
g = sns.barplot(data['stroke'], data['stroke'], palette='Set1', estimator=lambda x: len(x) / len(data) )
#圖的統計
for p in g.patches:
width, height = p.get_width(), p.get_height()
x, y = p.get_xy()
g.text(x+width/2,
y+height,
'{:.0%}'.format(height),
horizontalalignment='center',fontsize=15)
#設置標簽
plt.xlabel('Heart Stroke', fontsize=14)
plt.ylabel('Precentage', fontsize=14)
plt.title('Percentage of patients will/will not have heart stroke', fontsize=16)
在這里,
0:表示患者沒有心臟病。
1: 表示病人患了心臟病。
從分布上可以看出,只有2%的患者患有心臟病。所以,這是一個經典的類別失衡問題。
為什么要處理類別不平衡?
到目前為止,我們已經對類別失衡有了直覺。但是為什么需要克服這個問題,在使用這些數據建模時會產生什么問題?
大多數機器學習算法都假定數據在類中分布均勻。在類不平衡問題中,廣泛的問題是算法將更偏向於預測大多數類別(在我們的情況下沒有心臟病)。該算法沒有足夠的數據來學習少數類(心臟病)中的模式。
讓我們以一個現實生活的例子來更好地理解這一點。
假設你已經從你的家鄉搬到了一個新的城市,你在這里住了一個月。當你來到你的家鄉,你會非常熟悉所有的地方,如你的家,路線,重要的商店,旅游景點等等,因為你在那里度過了你的整個童年。
但是到了新城市,你不會對每個地方的具體位置有太多的想法,走錯路線迷路的幾率會非常高。在這里,你的家鄉是你的多數類,新城是少數類。
同樣,這種情況也會發生在類別不平衡中。少數類關於你類的信息不充分,這就是為什么少數類會有很高的誤分類錯誤。
注:為了檢查模型的性能,我們將使用f1分數作為衡量標准,而不是准確度。
原因是如果我們建立一個愚蠢的模型,預測每一個新的訓練數據為0(沒有心臟病),即使這樣,我們也會得到非常高的准確率,因為模型偏向大多數類。
在這里,模型非常精確,但對我們的問題陳述沒有任何價值。這就是為什么我們將使用f1分數作為評估指標。F1分數只不過是精確度和召回率的調和平均值。但是,評估指標是根據業務問題和我們希望減少的錯誤類型來選擇的。但是,f1分數是衡量類別不平衡問題的關鍵。
以下是f1分數公式:
f1 score = 2*(precision*recall)/(precision+recall)
讓我們通過訓練一個基於目標變量模式的模型來確認這一點,並檢查我們得到的分數:
#利用目標模式訓練模型
from sklearn.metrics import f1_score, accuracy_score, confusion_matrix
pred_test = []
for i in range (0, 13020):
pred_test.append(y_train.mode()[0])
#打印f1和准確度分數
print('The accuracy for mode model is:', accuracy_score(y_test, pred_test))
print('The f1 score for the model model is:',f1_score(y_test, pred_test))
#繪制混淆矩陣
conf_matrix(y_test, pred_test)
模式模型的准確度為:0.9819508448540707
模式模式的f1分數為:0.0
在這里,模型對測試數據的准確度為0.98,這是一個很好的分數。但另一方面,f1的分數為零,這表明該模型在少數類群體中表現不佳。我們可以通過查看混淆矩陣來確認這一點。
模式模型預測每個病人為0(無心臟病)。根據這個模型,無論病人有什么樣的症狀,他/她永遠不會犯心臟病。使用這個模型有意義嗎?
現在我們已經了解了什么是類不平衡以及它如何影響我們的模型性能,我們將把重點轉移到類權重是什么以及類權重如何幫助改進模型性能。
類別權重是多少?
大多數機器學習算法對有偏差的類數據不是很有用。但是,我們可以對現有的訓練算法進行修改,以考慮到類的傾斜分布。這可以通過給予多數類別和少數類別不同的權重來實現。在訓練階段,權重的差異會影響類別的分類。其整體目的是通過設置更高的類權重,同時為多數類降低權重,以懲罰少數類的錯誤分類。
為了更清楚地說明這一點,我們將恢復我們之前考慮過的城市例子。
請這樣想一想,上個月你在這個新城市度過,而不是在需要的時候出去,而是花了整整一個月的時間探索這個城市。整個月你花了更多的時間了解城市的路線和地點。給你更多的時間去研究將有助於你更好地了解這個新城市,並且減少迷路的機會。這正是類權重的工作原理。
在訓練過程中,我們在算法的代價函數中賦予少數類更大的權重,使其能夠對少數類提供更高的懲罰,使算法能夠專注於減少少數類的誤差。
注意:有一個閾值,你應該分別增加和減少少數類和多數類的類權重。如果給少數類賦予非常高的類權重,算法很可能會偏向少數類,並且會增加多數類中的錯誤。
大多數sklearn分類器建模庫,甚至一些基於boosting的庫,如LightGBM和catboost,都有一個內置的參數“class_weight”,這有助於我們優化少數類的得分,就像我們目前所學的那樣。
默認情況下,class_weights 的值為“None”,即兩個類的權重相等。除此之外,我們可以給它“balanced”或者傳遞一個包含兩個類的人工設計權重的字典。
當類權重=‘平衡’時,模型會自動分配與其各自頻率成反比的類權重。
更精確地說,計算公式為:
wj=n_samples / (n_classes * n_samplesj)
在這里,
-
wj是每個類的權重(j表示類)
-
n_samples是數據集中的樣本或行總數
-
n_classes是目標中唯一類的總數
-
n_samplesj是相應類的總行數
對於我們的心臟案例:
n_samples= 43400, n_classes= 2(0&1), n_sample0= 42617, n_samples1= 783
0類的權重:
w0= 43400/(2*42617) = 0.509
1類的權重:
w1= 43400/(2*783) = 27.713
我希望這能讓事情更清楚地表明,類別權重=‘balanced’有助於我們給予少數類別更高的權重,給多數類別較低的權重。
雖然在大多數情況下,將值作為“balanced”傳遞會產生很好的結果,但有時對於極端的類不平衡,我們可以嘗試設計權重。稍后我們將了解如何在Python中找到類權重的最佳值。
Logistic回歸中的類權重
我們可以通過在算法的代價函數中添加不同的類權重來修改每種機器學習算法,但這里我們將特別關注logistic回歸。
對於logistic回歸,我們使用對數損失作為成本函數。我們沒有使用均方誤差作為logistic回歸的成本函數,因為我們使用sigmoid曲線作為預測函數,而不是擬合直線。
將sigmoid函數展平會導致一條非凸曲線,這使得代價函數具有大量的局部極小值,而用梯度下降法收斂到全局極小值是非常困難的。但是對數損失是一個凸函數,我們只有一個極小值可以收斂。
log損失公式:
在這里,
-
N是值的數目
-
yi是目標類的實際值
-
yi是目標類的預測概率
讓我們形成一個偽表,其中包含實際預測、預測概率和使用log損失公式計算的成本:
在這個表格中,我們有10個觀察值,其中9個來自0類,9個來自1類。在下一篇專欄文章中,我們將給出每一次觀察的預測概率。最后,利用對數損失公式,我們得到了成本懲罰。
將權重加入成本函數后,修改后的對數損失函數為:
這里
w0是類0的類權重
w1是類1的類權重
現在,我們將添加權重,看看它會對成本懲罰產生什么影響。
對於權重值,我們將使用class_weights='balanced'公式。
w0= 10/(2*1) = 5
w1= 10/(2*9) = 0.55
計算表中第一個值的成本:
Cost = -(5(0*log(0.32) + 0.55(1-0)*log(1-0.32))
= -(0 + 0.55*log(.68))
= -(0.55*(-0.385))
= 0.211
同樣,我們可以計算每個觀測值的加權成本,更新后的表為:
通過該表,我們可以確定對大多數類的成本函數應用了較小的權重,從而導致較小的誤差值,進而減少了對模型系數的更新。一個更大的權重值應用到少數類的成本函數中,這會導致更大的誤差計算,進而對模型系數進行更多的更新。這樣,我們就可以改變模型的偏差,從而減少少數類的誤差。
結論:
較小的權重會導致較小的懲罰和對模型系數的小更新
較大的權重會導致較大的懲罰和對模型系數的大量更新
Python實現
在這里,我們將使用相同的心臟病數據來預測。首先,我們將訓練一個簡單的logistic回歸,然后我們將實現加權logistic回歸,類權重為“平衡”。最后,我們將嘗試使用網格搜索來找到類權重的最佳值。我們試圖優化的指標將是f1分數。
1簡單邏輯回歸:
這里,我們使用sklearn庫來訓練我們的模型,我們使用默認的logistic回歸。默認情況下,算法將為兩個類賦予相等的權重。
#導入和訓練模型
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression(solver='newton-cg')
lr.fit(x_train, y_train)
#測試數據預測
pred_test = lr.predict(x_test)
#計算並打印f1分數
f1_test = f1_score(y_test, pred_test)
print('The f1 score for the testing data:', f1_test)
# 創建混淆矩陣的函數
def conf_matrix(y_test, pred_test):
# 創建混淆矩陣
con_mat = confusion_matrix(y_test, pred_test)
con_mat = pd.DataFrame(con_mat, range(2), range(2))
plt.figure(figsize=(6,6))
sns.set(font_scale=1.5)
sns.heatmap(con_mat, annot=True, annot_kws={"size": 16}, fmt='g', cmap='Blues', cbar=False)
#調用函數
conf_matrix(y_test, pred_test)
測試數據f1得分:0.0
在簡單的logistic回歸模型中,f1得分為0。通過觀察混淆矩陣,我們可以確認我們的模型預測了每一個觀察結果,因為不會發生心臟病。這個模型並不比我們前面創建的模式模型好。讓我們試着給少數類增加一些權重,看看這是否有幫助。
2邏輯回歸(class_weight='balanced'):
我們在logistic回歸算法中加入了類權重參數,傳遞的值是“balanced”。
#導入和訓練模型
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression(solver='newton-cg', class_weight='balanced')
lr.fit(x_train, y_train)
# 測試數據預測
pred_test = lr.predict(x_test)
# 計算並打印f1分數
f1_test = f1_score(y_test, pred_test)
print('The f1 score for the testing data:', f1_test)
#繪制混淆矩陣
conf_matrix(y_test, pred_test)
測試數據f1得分:0.10098851188885921
通過在logistic回歸函數中添加一個單類權重參數,我們將f1分數提高了10%。我們可以在混淆矩陣中看到,盡管類0(無心臟病)的錯誤分類增加了,但模型可以很好地捕捉到類1(心臟病)。
我們可以通過改變類權重來進一步改進度量嗎?
3邏輯回歸(人工設置類權重):
最后,我們嘗試使用網格搜索來尋找得分最高的最優權重。我們將搜索0到1之間的權重。我們的想法是,如果我們給少數類別n作為權重,多數類別將得到1-n作為權重。
在這里,權重的大小並不是很大,但是多數類別和少數類別之間的權重比例將非常高。
例如:
w1 = 0.95
w0 = 1 – 0.95 = 0.05
w1:w0 = 19:1
因此,少數類別的權重將是多數類別的19倍。
from sklearn.model_selection import GridSearchCV, StratifiedKFold
lr = LogisticRegression(solver='newton-cg')
#設置類權重的范圍
weights = np.linspace(0.0,0.99,200)
#為網格搜索創建字典網格
param_grid = {'class_weight': [{0:x, 1:1.0-x} for x in weights]}
##用5倍網格搜索法擬合訓練數據
gridsearch = GridSearchCV(estimator= lr,
param_grid= param_grid,
cv=StratifiedKFold(),
n_jobs=-1,
scoring='f1',
verbose=2).fit(x_train, y_train)
#繪制不同權重值的分數
sns.set_style('whitegrid')
plt.figure(figsize=(12,8))
weigh_data = pd.DataFrame({ 'score': gridsearch.cv_results_['mean_test_score'], 'weight': (1- weights)})
sns.lineplot(weigh_data['weight'], weigh_data['score'])
plt.xlabel('Weight for class 1')
plt.ylabel('F1 score')
plt.xticks([round(i/10,1) for i in range(0,11,1)])
plt.title('Scoring for different class weights', fontsize=24)
從圖中我們可以看到少數類的最高值在0.93處達到峰值。
通過網格搜索,我們得到了最佳的類權重,0類(多數類)為0.06467,1類(少數類)為1:0.93532。
現在我們已經使用分層交叉驗證和網格搜索獲得了最佳類權重,我們將看到測試數據的性能。
#導入和訓練模型
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression(solver='newton-cg', class_weight={0: 0.06467336683417085, 1: 0.9353266331658292})
lr.fit(x_train, y_train)
# 測試數據預測
pred_test = lr.predict(x_test)
# 計算並打印f1分數
f1_test = f1_score(y_test, pred_test)
print('The f1 score for the testing data:', f1_test)
# 繪制混淆矩陣
conf_matrix(y_test, pred_test)
f1分數:0.15714644
通過手動改變權重值,我們可以進一步提高f1分數約6%。混淆矩陣還表明,從之前的模型來看,我們能夠更好地預測0類,但代價是我們的1類錯誤分類。這完全取決於業務問題或你希望減少的錯誤類型。在這里,我們的重點是提高f1分數,我們可以通過調整類別權重來做到這一點。
進一步提高得分的技巧
特征工程:為了簡單起見,我們只使用了給定的自變量。你可以嘗試創建新的特征
調整閾值:默認情況下,所有算法的閾值都是0.5。你可以嘗試不同的閾值值,並可以通過使用網格搜索或隨機化搜索來找到最佳值
使用高級算法:對於這個解釋,我們只使用了logistic回歸。你可以嘗試不同的bagging 和boosting 算法。最后還可以嘗試混合多種算法
結尾
我希望這篇文章能讓你了解類權重如何幫助處理類不平衡問題,以及在python中實現它有多容易。
雖然我們已經討論了類權重如何僅適用於logistic回歸,但其他算法的思想都是相同的;只是每種算法用於最小化誤差和優化少數類結果的代價函數的變化
原文鏈接:https://www.analyticsvidhya.com/blog/2020/10/improve-class-imbalance-class-weights/
歡迎關注磐創AI博客站:
http://panchuang.net/
sklearn機器學習中文官方文檔:
http://sklearn123.com/
歡迎關注磐創博客資源匯總站:
http://docs.panchuang.net/