資料預處理:樣本非平衡處理
轉載:https://zhuanlan.zhihu.com/p/37311047
非平衡資料會影響最後的評判效果,嚴重的會帶來過擬合的效果,即模型總是把樣本劃分到樣本量較多的那一種。為了讓模型的評判更準確,我們需要對非平衡資料進行一定的處理,主要有以下幾種方式:
- 欠取樣
- 過取樣
- 人工合成
- 調權重
在開始介紹不同的處理方式之前,我們先引入一組非平衡資料。
-
#匯入一些相關庫
-
from
sklearn.model_selection
import train_test_split
-
from sklearn.linear_model
import LogisticRegression
-
from sklearn.metrics
import classification_report
-
from
sklearn.metrics
import roc_curve, auc
-
from sklearn.preprocessing
import scale
-
#匯入資料
-
df=pd.read_excel(
r"C:\Users\zhangjunhong\Desktop\Unbanlanced-data.xlsx"
).fillna(
0)
看一下正負樣本的具體資料量情況。
-
x=df.iloc[:,
1:
-1]
-
y=df[
"label"]
-
print(y.value_counts())
-
print(
"-------------------------")
-
print(y.value_counts(normalize=
True))
該資料量的正負樣本比例接近7:3,我們看一下不做任何處理的情況下,模型的預測效果如何。
-
#將模型進行封裝,方便呼叫
-
def get_result_data(x,y):
-
x_=scale(x,with_mean=
True,with_std=
True)
-
x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=
0.4,random_state=
0)
-
model=LogisticRegression()
-
clf=model.fit(x_train,y_train)
-
print(
"LR模型測試成績:{:.2f}".format(clf.score(x_test,y_test)))
-
y_pred=clf.predict(x_test)
-
target_names = [
'class 0',
'class 1']
-
print(classification_report(y_test, y_pred, target_names=target_names))
-
y_pred1=clf.decision_function(x_test)
-
fpr,tpr,threshold=roc_curve(y_test,y_pred1)
-
rocauc=auc(fpr,tpr)
#計算AUC
-
print(
"ROC分數:{:.2f}".format(rocauc))
-
-
if __name__==
"__main__":
-
get_result_data(x,y)
模型的準確率是0.75,ROC分數也就是AUC值為0.76,看著還不錯,但是class1的召回率要明顯高於class0的召回率,這是因為原樣本量中,class1的量要明顯高於class0的原因。
欠取樣
下采樣(under-sampling),是對非平衡資料中樣本數較多的那一類進行取樣,取樣使其約等於樣本量較少那一類的樣本量。
-
df1=df[df[
"label"]==
1]
#正樣本部分
-
df0=df[df[
"label"]==
0]
#負樣本部分
-
-
#對正樣本按0.5的比例進行下采樣
-
df2=df1.sample(frac=
0.5)
-
-
#將下采樣後的正樣本與負樣本進行組合
-
df_new=pd.concat([df0,df2])
-
-
x=df_new.iloc[:,
1:
-1]
-
y=df_new[
"label"]
-
-
#下采樣以後正負樣本量情況
-
print(y.value_counts())
-
print(
"-------------------------")
-
print(y.value_counts(normalize=
True))
對模型進行下采樣以後,正負樣本的樣本量基本接近1:1,符合我們目的,接下來看看下采樣後的模型表現。
-
if __name__==
"__main__":
-
get_result_data(x,y)
模型的準確率略有下降,但是ROC分數沒發生什麼變化,class0和class1的召回率也接近相等。
過取樣
過取樣(over-sampling),是對非平衡資料中樣本數較少的那一類進行取樣,常規的做法就是將其複製幾遍來達到正負樣本平衡,因為是同樣的資料複製多份,很容易發生過擬合,一般比較少用。具體的實現方式就比較簡單啦,這裡不羅列。
人工合成
人工合成就是人為地去合成一些樣本量較少的資料,來達到正負樣本平衡,人工合成數據能夠很好地避免過取樣帶來的模型過擬合。比較常用的方法就是SMOTE。
SMOTE的演算法原理如下:
- 根據正負樣本比例,確認取樣的比例,即要合成樣本的數量(k值)
- 對於少數樣本中的每個x,利用KNN演算法,選取k個待取樣的值x_n
- 然後對x_n進行如下運算得到對應的x_new:
x_new=x+rand(1)*|x-x_n|
(rand(1)表示生成0-1之間的一個隨機數)
關於SMOTE演算法的實現也由現成的庫,我們直接pip安裝就可以使用。
-
from collections
import Counter
-
from imblearn.over_sampling
import SMOTE
-
print(
'Original dataset shape {}'.format(Counter(y)))
-
sm = SMOTE(random_state=
42)
-
X_res, y_res = sm.fit_sample(x, y)
-
print(
'Resampled dataset shape {}'.format(Counter(y_res)))
原本正負樣本絕對量分別為12193:5617,人工合成部分樣本量以後,正負樣本的絕對量變為了12193:12193,完全平衡。
人工合成以後模型預測效果
-
if __name__==
"__main__":
-
get_result_data(X_res, y_res)
模型的準確率和ROC分數較欠取樣都有略微的上漲,其中class0的召回上漲,class1略降。
調權重
調權重就是調整模型中正負樣本的在模型表現中的表決權重,以此來平衡樣本絕對量的不平衡。比如正負樣本絕對量的比值為1:10,為了抵消這種量級上的不平衡,我們在模型中可以給與模型正負樣本10:1的表決權重,也就是10個正樣本的表決相當於1個負樣本的表決。
這個過程我們也可以直接設定模型引數class_weight="balanced"
進行實現。
-
x_=scale(x,with_mean=
True,with_std=
True)
-
x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=
0.4,random_state=
0)
-
-
model=LogisticRegression(class_weight=
"balanced")
-
clf=model.fit(x_train,y_train)
-
print(
"LR模型測試成績:{:.2f}".format(clf.score(x_test,y_test)))
-
y_pred=clf.predict(x_test)
-
-
target_names = [
'class 0',
'class 1']
-
print(classification_report(y_test, y_pred, target_names=target_names))
-
-
y_pred1=clf.decision_function(x_test)
-
fpr,tpr,threshold=roc_curve(y_test,y_pred1)
-
rocauc=auc(fpr,tpr)
#計算AUC
-
print(
"ROC分數:{:.2f}".format(rocauc))
調權重的結果和人工合成數據的結果接近一致。
最後
通過上面幾種方法的模型結果可以看出:
- 用任意一種方式處理或者不處理,ROC基本是一致的,這也驗證了我們在前面的推文中說到的,ROC是和樣本是否平衡沒關係的。
- 如果不做任何處理,模型的準確率會高,但是會發生嚴重的過擬合。
- 在做處理的這幾種方式中,欠取樣的效果要差於其他三種。
- 綜合來看,直接在模型引數中調權重是效果最好,也是最快捷的一種方式,不用事先去做什麼處理。
本文最後的結論是針對本次資料得出的結論,不代表在任何資料上效果都是如此,可能會限於資料本身的原因,結果會有所不同,本文重點講述非平衡資料不同的處理方式以及實現方式。