教你用Python解決非平衡數據問題
來源:數據分析1480
作者:劉順祥
本文約3153字,建議閱讀7分鐘。
本文為你分享數據挖掘中常見的非平衡數據的處理,內容涉及到非平衡數據的解決方案和原理,以及如何使用Python這個強大的工具實現平衡的轉換。
前言
好久沒有更新自己寫的文章了,相信很多讀者都會比較失望,甚至取關了吧,在此向各位網友道個歉。文章未及時更新的主要原因是目前在寫Python和R語言相關的書籍,激動的是基於Python的數據分析與挖掘的書已經編寫完畢,後期還繼續書寫R語言相關的內容。希望得到網友的理解,為晚來的新文章再次表示抱歉。
本次分享的主題是關於數據挖掘中常見的非平衡數據的處理,內容涉及到非平衡數據的解決方案和原理,以及如何使用Python這個強大的工具實現平衡的轉換。
SMOTE演算法的介紹
在實際應用中,讀者可能會碰到一種比較頭疼的問題,那就是分類問題中類別型的因變數可能存在嚴重的偏倚,即類別之間的比例嚴重失調。如欺詐問題中,欺詐類觀測在樣本集中畢竟佔少數;客戶流失問題中,非忠實的客戶往往也是占很少一部分;在某營銷活動的響應問題中,真正參與活動的客戶也同樣只是少部分。
如果數據存在嚴重的不平衡,預測得出的結論往往也是有偏的,即分類結果會偏向於較多觀測的類。對於這種問題該如何處理呢?最簡單粗暴的辦法就是構造1:1的數據,要麼將多的那一類砍掉一部分(即欠採樣),要麼將少的那一類進行Bootstrap抽樣(即過採樣)。但這樣做會存在問題,對於第一種方法,砍掉的數據會導致某些隱含信息的丟失;而第二種方法中,有放回的抽樣形成的簡單複製,又會使模型產生過擬合。
為了解決數據的非平衡問題,2002年Chawla提出了SMOTE演算法,即合成少數過採樣技術,它是基於隨機過採樣演算法的一種改進方案。該技術是目前處理非平衡數據的常用手段,並受到學術界和工業界的一致認同,接下來簡單描述一下該演算法的理論思想。
SMOTE演算法的基本思想就是對少數類別樣本進行分析和模擬,並將人工模擬的新樣本添加到數據集中,進而使原始數據中的類別不再嚴重失衡。該演算法的模擬過程採用了KNN技術,模擬生成新樣本的步驟如下:
採樣最鄰近演算法,計算出每個少數類樣本的K個近鄰;
從K個近鄰中隨機挑選N個樣本進行隨機線性插值;
構造新的少數類樣本;
將新樣本與原數據合成,產生新的訓練集;
為了使讀者理解SMOTE演算法實現新樣本的模擬過程,可以參考下圖和人工新樣本的生成過程:
如上圖所示,實心圓點代表的樣本數量要明顯多於五角星代表的樣本點,如果使用SMOTE演算法模擬增加少類別的樣本點,則需要經過如下幾個步驟:
利用KNN演算法,選擇離樣本點x1最近的K個同類樣本點(不妨最近鄰為5);
從最近的K個同類樣本點中,隨機挑選M個樣本點(不妨M為2),M的選擇依賴於最終所希望的平衡率;
對於每一個隨機選中的樣本點,構造新的樣本點;新樣本點的構造需要使用下方的公式:
其中,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),所以,由此得到的兩個新樣本點為:
重複步驟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)
ratio:用於指定重抽樣的比例,如果指定字元型的值,可以是』minority』,表示對少數類別的樣本進行抽樣、』majority』,表示對多數類別的樣本進行抽樣、』not minority』表示採用欠採樣方法、』all』表示採用過採樣方法,默認為』auto』,等同於』all』和』not minority』;如果指定字典型的值,其中鍵為各個類別標籤,值為類別下的樣本量;
random_state:用於指定隨機數生成器的種子,默認為None,表示使用默認的隨機數生成器;
k_neighbors:指定近鄰個數,默認為5個;
m_neighbors:指定從近鄰樣本中隨機挑選的樣本個數,默認為10個;
kind:用於指定SMOTE演算法在生成新樣本時所使用的選項,默認為』regular』,表示對少數類別的樣本進行隨機採樣,也可以是』borderline1』、』borderline2』和』svm』;
svm_estimator:用於指定SVM分類器,默認為sklearn.svm.SVC,該參數的目的是利用支持向量機分類器生成支持向量,然後再生成新的少數類別的樣本;
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:UsersAdministratorDesktopCustomer_Churn.xlsx")churn.head()
如上圖所示,流失用戶僅佔到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()
如上表所示,即為清洗後的乾淨數據,接下來對該數據集進行拆分,分別構建訓練數據集和測試數據集,並利用訓練數據集構建分類器,測試數據集檢驗分類器:
# 用於建模的所有自變數
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))
如上結果所示,決策樹的預測準確率超過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()
如上圖所示,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))
如上結果所示,對於訓練數據集本身,它的類別比例還是存在較大差異的,但經過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))
如上結果所示,利用平衡數據重新建模後,模型的準確率同樣很高,為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()
最終得到的AUC值為0.836,此時就可以認為模型相對比較合理了。
結語
OK,今天的內容就介紹到這邊,如果需要數據或代碼,可以關注「數據派THU」並回復「不平衡」即可。期待各位的交流~
※編程小白必看!Python到底能做什麼?
※開啟智能運維時代:Linux雲計算+Python運維開發
TAG:Python |