天天看點

教你用Python解決非平衡資料問題(附代碼)

好久沒有更新自己寫的文章了,相信很多讀者都會比較失望,甚至取關了吧,在此向各位網友道個歉。文章未及時更新的主要原因是目前在寫Python和R語言相關的書籍,激動的是基于Python的資料分析與挖掘的書已經編寫完畢,後期還繼續書寫R語言相關的内容。希望得到網友的了解,為晚來的新文章再次表示抱歉。

本次分享的主題是關于資料挖掘中常見的非平衡資料的處理,内容涉及到非平衡資料的解決方案和原理,以及如何使用Python這個強大的工具實作平衡的轉換。

SMOTE算法的介紹

在實際應用中,讀者可能會碰到一種比較頭疼的問題,那就是分類問題中類别型的因變量可能存在嚴重的偏倚,即類别之間的比例嚴重失調。如欺詐問題中,欺詐類觀測在樣本集中畢竟占少數;客戶流失問題中,非忠實的客戶往往也是占很少一部分;在某營銷活動的響應問題中,真正參與活動的客戶也同樣隻是少部分。

如果資料存在嚴重的不平衡,預測得出的結論往往也是有偏的,即分類結果會偏向于較多觀測的類。對于這種問題該如何處理呢?最簡單粗暴的辦法就是構造1:1的資料,要麼将多的那一類砍掉一部分(即欠采樣),要麼将少的那一類進行Bootstrap抽樣(即過采樣)。但這樣做會存在問題,對于第一種方法,砍掉的資料會導緻某些隐含資訊的丢失;而第二種方法中,有放回的抽樣形成的簡單複制,又會使模型産生過拟合。

為了解決資料的非平衡問題,2002年Chawla提出了SMOTE算法,即合成少數過采樣技術,它是基于随機過采樣算法的一種改進方案。該技術是目前處理非平衡資料的常用手段,并受到學術界和工業界的一緻認同,接下來簡單描述一下該算法的理論思想。

SMOTE算法的基本思想就是對少數類别樣本進行分析和模拟,并将人工模拟的新樣本添加到資料集中,進而使原始資料中的類别不再嚴重失衡。該算法的模拟過程采用了KNN技術,模拟生成新樣本的步驟如下:

教你用Python解決非平衡資料問題(附代碼)

采樣最鄰近算法,計算出每個少數類樣本的K個近鄰;

教你用Python解決非平衡資料問題(附代碼)

從K個近鄰中随機挑選N個樣本進行随機線性插值;

教你用Python解決非平衡資料問題(附代碼)

構造新的少數類樣本;

教你用Python解決非平衡資料問題(附代碼)

将新樣本與原資料合成,産生新的訓練集;

為了使讀者了解SMOTE算法實作新樣本的模拟過程,可以參考下圖和人工新樣本的生成過程:

教你用Python解決非平衡資料問題(附代碼)

如上圖所示,實心圓點代表的樣本數量要明顯多于五角星代表的樣本點,如果使用SMOTE算法模拟增加少類别的樣本點,則需要經過如下幾個步驟:

教你用Python解決非平衡資料問題(附代碼)

利用KNN算法,選擇離樣本點x1最近的K個同類樣本點(不妨最近鄰為5);

教你用Python解決非平衡資料問題(附代碼)

從最近的K個同類樣本點中,随機挑選M個樣本點(不妨M為2),M的選擇依賴于最終所希望的平衡率;

教你用Python解決非平衡資料問題(附代碼)

對于每一個随機選中的樣本點,構造新的樣本點;新樣本點的構造需要使用下方的公式:

教你用Python解決非平衡資料問題(附代碼)

其中,xi表示少數類别中的一個樣本點(如圖中五角星所代表的x1樣本);xj表示從K近鄰中随機挑選的樣本點j;rand(0,1)表示生成0~1之間的随機數。

假設圖中樣本點x1的觀測值為(2,3,10,7),從圖中的5個近鄰中随機挑選2個樣本點,它們的觀測值分别為(1,1,5,8)和(2,1,7,6),是以,由此得到的兩個新樣本點為:

教你用Python解決非平衡資料問題(附代碼)
教你用Python解決非平衡資料問題(附代碼)

重複步驟1)、2)和3),通過疊代少數類别中的每一個樣本xi,最終将原始的少數類别樣本量擴大為理想的比例;

通過SMOTE算法實作過采樣的技術并不是太難,讀者可以根據上面的步驟自定義一個抽樣函數。當然,讀者也可以借助于imblearn子產品,并利用其子子產品over_sampling中的SMOTE“類”實作新樣本的生成。有關該“類”的文法和參數含義如下:

SMOTE(ratio=’auto’, random_state=None, k_neighbors=5, m_neighbors=10,

out_step=0.5, kind=’regular’, svm_estimator=None, n_jobs=1)           
教你用Python解決非平衡資料問題(附代碼)

ratio:用于指定重抽樣的比例,如果指定字元型的值,可以是’minority’,表示對少數類别的樣本進行抽樣、’majority’,表示對多數類别的樣本進行抽樣、’not minority’表示采用欠采樣方法、’all’表示采用過采樣方法,預設為’auto’,等同于’all’和’not minority’;如果指定字典型的值,其中鍵為各個類别标簽,值為類别下的樣本量;

教你用Python解決非平衡資料問題(附代碼)

random_state:用于指定随機數生成器的種子,預設為None,表示使用預設的随機數生成器;

教你用Python解決非平衡資料問題(附代碼)

k_neighbors:指定近鄰個數,預設為5個;

教你用Python解決非平衡資料問題(附代碼)

m_neighbors:指定從近鄰樣本中随機挑選的樣本個數,預設為10個;

教你用Python解決非平衡資料問題(附代碼)

kind:用于指定SMOTE算法在生成新樣本時所使用的選項,預設為’regular’,表示對少數類别的樣本進行随機采樣,也可以是’borderline1’、’borderline2’和’svm’;

教你用Python解決非平衡資料問題(附代碼)

svm_estimator:用于指定SVM分類器,預設為sklearn.svm.SVC,該參數的目的是利用支援向量機分類器生成支援向量,然後再生成新的少數類别的樣本;

教你用Python解決非平衡資料問題(附代碼)

n_jobs:用于指定SMOTE算法在過采樣時所需的CPU數量,預設為1表示僅使用1個CPU運作算法,即不使用并行運算功能;

分類算法的應用實戰

本次分享的資料集來源于德國某電信行業的客戶曆史交易資料,該資料集一共包含條4,681記錄,19個變量,其中因變量churn為二進制變量,yes表示客戶流失,no表示客戶未流失;剩餘的自變量包含客戶的是否訂購國際長途套餐、語音套餐、短信條數、話費、通話次數等。接下來就利用該資料集,探究非平衡資料轉平衡後的效果。

# 導入第三方包

import pandas as pd

import numpy as np

import matplotlib.pyplot as plt

from sklearn import model_selection

from sklearn import tree

from sklearn import metrics

from imblearn.over_sampling import SMOTE


# 讀取資料churn = pd.read_excel(r'C:\Users\Administrator\Desktop\Customer_Churn.xlsx')
churn.head()           
教你用Python解決非平衡資料問題(附代碼)
# 中文亂碼的處理

plt.rcParams['font.sans-serif']=['Microsoft YaHei']



# 為確定繪制的餅圖為圓形,需執行如下代碼

plt.axes(aspect = 'equal')

# 統計交易是否為欺詐的頻數

counts = churn.churn.value_counts()



# 繪制餅圖

plt.pie(x = counts, # 繪圖資料

 labels=pd.Series(counts.index).map({'yes':'流失','no':'未流失'}), # 添加文字标簽

autopct='%.2f%%' # 設定百分比的格式,這裡保留一位小數
 )

# 顯示圖形

plt.show()           
教你用Python解決非平衡資料問題(附代碼)

如上圖所示,流失使用者僅占到8.3%,相比于未流失使用者,還是存在比較大的差異的。可以認為兩種類别的客戶是失衡的,如果直接對這樣的資料模組化,可能會導緻模型的結果不夠準确。不妨先對該資料建構随機森林模型,看看是否存在偏倚的現象。

原始資料表中的state變量和Area_code變量表示使用者所屬的“州”和地區編碼,直覺上可能不是影響使用者是否流失的重要原因,故将這兩個變量從表中删除。除此,使用者是否訂購國際長途業務international_plan和語音業務voice_mail_plan,屬于字元型的二進制值,它們是不能直接代入模型的,故需要轉換為0-1二進制值。

# 資料清洗

# 删除state變量和area_code變量

churn.drop(labels=['state','area_code'], axis = 1, inplace = True)



# 将二進制變量international_plan和voice_mail_plan轉換為0-1啞變量

churn.international_plan = churn.international_plan.map({'no':0,'yes':1})
churn.voice_mail_plan = churn.voice_mail_plan.map({'no':0,'yes':1})
churn.head()
           
教你用Python解決非平衡資料問題(附代碼)

如上表所示,即為清洗後的幹淨資料,接下來對該資料集進行拆分,分别建構訓練資料集和測試資料集,并利用訓練資料集建構分類器,測試資料集檢驗分類器:

# 用于模組化的所有自變量

predictors = churn.columns[:-1]

# 資料拆分為訓練集和測試集

X_train,X_test,y_train,y_test = model_selection.train_test_split(churn[predictors], churn.churn, random_state=12)



# 建構決策樹

dt = tree.DecisionTreeClassifier(n_estimators = 300)
dt.fit(X_train,y_train)

# 模型在測試集上的預測

pred = dt.predict(X_test)



# 模型的預測準确率

print(metrics.accuracy_score(y_test, pred))

# 模型評估報告

print(metrics.classification_report(y_test, pred))           
教你用Python解決非平衡資料問題(附代碼)

如上結果所示,決策樹的預測準确率超過93%,其中預測為no的覆寫率recall為97%,但是預測為yes的覆寫率recall卻為62%,兩者相差甚遠,說明分類器确實偏向了樣本量多的類别(no)。

# 繪制ROC曲線

# 計算流失使用者的機率值,用于生成ROC曲線的資料

y_score = dt.predict_proba(X_test)[:,1]
fpr,tpr,threshold = metrics.roc_curve(y_test.map({'no':0,'yes':1}), y_score)



# 計算AUC的值

roc_auc = metrics.auc(fpr,tpr)

# 繪制面積圖

plt.stackplot(fpr, tpr, color='steelblue', alpha = 0.5, edgecolor = 'black')

# 添加邊際線

plt.plot(fpr, tpr, color='black', lw = 1)

# 添加對角線

plt.plot([0,1],[0,1], color = 'red', linestyle = '--')

# 添加文本資訊

plt.text(0.5,0.3,'ROC curve (area = %0.3f)' % roc_auc)

# 添加x軸與y軸标簽

plt.xlabel('1-Specificity')
plt.ylabel('Sensitivity')

# 顯示圖形

plt.show()           
教你用Python解決非平衡資料問題(附代碼)

如上圖所示,ROC曲線下的面積為0.795,AUC的值小于0.8,故認為模型不太合理。(通常拿AUC與0.8比較,如果大于0.8,則認為模型合理)。接下來,利用SMOTE算法對資料進行處理:

# 對訓練資料集作平衡處理

over_samples = SMOTE(random_state=1234) 
over_samples_X,over_samples_y = over_samples.fit_sample(X_train, y_train)



# 重抽樣前的類别比例

print(y_train.value_counts()/len(y_train))

# 重抽樣後的類别比例

print(pd.Series(over_samples_y).value_counts()/len(over_samples_y))           
教你用Python解決非平衡資料問題(附代碼)

如上結果所示,對于訓練資料集本身,它的類别比例還是存在較大差異的,但經過SMOTE算法處理後,兩個類别就可以達到1:1的平衡狀态。下面就可以利用這個平衡資料,重新建構決策樹分類器了:

# 基于平衡資料重新建構決策樹模型

dt2 = ensemble.DecisionTreeClassifier(n_estimators = 300)
dt2.fit(over_samples_X,over_samples_y)

# 模型在測試集上的預測

pred2 =dt2.predict(np.array(X_test))



# 模型的預測準确率

print(metrics.accuracy_score(y_test, pred2))

# 模型評估報告

print(metrics.classification_report(y_test, pred2))           
教你用Python解決非平衡資料問題(附代碼)

如上結果所示,利用平衡資料重建立模後,模型的準确率同樣很高,為92.6%(相比于原始非平衡資料建構的模型,準确率僅下降1%),但是預測為yes的覆寫率提高了10%,達到72%,這就是平衡帶來的好處。

# 計算流失使用者的機率值,用于生成ROC曲線的資料

y_score = rf2.predict_proba(np.array(X_test))[:,1]
fpr,tpr,threshold = metrics.roc_curve(y_test.map({'no':0,'yes':1}), y_score)

# 計算AUC的值

roc_auc = metrics.auc(fpr,tpr)

# 繪制面積圖

plt.stackplot(fpr, tpr, color='steelblue', alpha = 0.5, edgecolor = 'black')

# 添加邊際線

plt.plot(fpr, tpr, color='black', lw = 1)

# 添加對角線

plt.plot([0,1],[0,1], color = 'red', linestyle = '--')

# 添加文本資訊

plt.text(0.5,0.3,'ROC curve (area = %0.3f)' % roc_auc)

# 添加x軸與y軸标簽

plt.xlabel('1-Specificity')
plt.ylabel('Sensitivity')



# 顯示圖形

plt.show()           
教你用Python解決非平衡資料問題(附代碼)

最終得到的AUC值為0.836,此時就可以認為模型相對比較合理了。

原文釋出時間為:2018-05-13

本文作者:劉順祥

本文來自雲栖社群合作夥伴“

資料派THU

”,了解相關資訊可以關注“

”。