python資料分析:會員資料執行(下)——基於AdaBoost的營銷響應預測
何為AdaBoost
Adaboost是一種迭代演算法,其核心思想是針對同一個訓練集訓練不同的分類器(弱分類器),然後把這些弱分類器集合起來,構成一個更強的最終分類器(強分類器)。其演算法本身是通過改變資料分佈來實現的,它根據每次訓練集之中每個樣本的分類是否正確,以及上次的總體分類的準確率,來確定每個樣本的權值。將修改過權值的新資料集送給下層分類器進行訓練,最後將每次訓練得到的分類器最後融合起來,作為最後的決策分類器。使用adaboost分類器可以排除一些不必要的訓練資料特徵,並放在關鍵的訓練資料上面
應用
對adaBoost演算法的研究以及應用大多集中於分類問題,同時也出現了一些在迴歸問題上的應用。就其應用adaBoost系列主要解決了: 兩類問題、多類單標籤問題、多類多標籤問題、大類單標籤問題、迴歸問題。它用全部的訓練樣本進行學習。
過程分析
該演算法其實是一個簡單的弱分類演算法提升過程,這個過程通過不斷的訓練,可以提高對資料的分類能
力。整個過程如下所示:
- 先通過對N個訓練樣本的學習得到第一個弱分類器;
- 將分錯的樣本和其他的新資料一起構成一個新的N個的訓練樣本,通過對這個樣本的學習得到第二個弱分類器 ;
- 將1和2都分錯了的樣本加上其他的新樣本構成另一個新的N個的訓練樣本,通過對這個樣本的學習得到第三個弱分類器;
- 最終經過提升的強分類器。即某個資料被分為哪一類要由各分類器權值決定。
python案例
背景
該案例介紹了有關會員營銷預測的實際應用。會員部門在做會員營銷時,希望能通過資料預測在下一次營銷活動時,響應活動會員的具體名單和響應概率,以此來制定針對性的營銷策略。
本案例是一個常規性應用模型,業務部門希望通過建立好的模型週期性自動執行,也滿足營銷需要。同時,業務部門還希望基於現有的輔助決策平臺將會員資料篩選和檢視功能跟該模型結合起來應用。
資料集
案例資料位於order.xlsx中,包括2個sheet:sheet1為本次案例的訓練集,sheet2為本次案例的預測集。以下是資料概況:
以下是本資料集的13個特徵變數,包括:
- age:年齡,整數型變數
- total_pageviews:總頁面瀏覽量,整數型變數
- edu:教育程度,分型別變數,值域[1,10]
- edu_ages:受教育年限,整數型變數
- user_level:使用者等級,分型別變數,值域[1,7]
- industry:使用者行業劃分,分型別變數,值域[1,15]
- value_level:使用者價值度分類,分型別變數,值域[1,6]
- act_level:使用者活躍度分類,分型別變數,值域[1,5]
- sex:性別,值域為1或0
- blue_money:歷史訂單的藍券用券訂單金額(優惠券的一種),整數型變數
- red_money:歷史訂單的紅券用券訂單金額(優惠券的一種),整數型變數
- work_hours:工作時間長度,整數型變數
- region:地區,分型別變數,值域[1,41]
目標變數response,1代表使用者有響應,0代表使用者未響應。
import time # 匯入自帶時間庫
import numpy as np # numpy庫
import pandas as pd # pandas庫
from sklearn.preprocessing import OneHotEncoder # 匯入OneHotEncoder庫
from sklearn.model_selection import StratifiedKFold, cross_val_score # 匯入交叉檢驗演算法
from sklearn.feature_selection import SelectPercentile, f_classif # 匯入特徵選擇方法庫
from sklearn.ensemble import AdaBoostClassifier # 匯入整合演算法
from sklearn.pipeline import Pipeline # 匯入Pipeline庫
from sklearn.metrics import accuracy_score # 準確率指標
import warnings
# 設定不顯示提醒
warnings.filterwarnings('ignore')
# 讀取要預測的資料集
df = pd.read_excel('order.xlsx', sheetname=0)
# 獲取目標變數
y = df.response
# 獲取訓練資料集
df_X = df.drop('response', axis=1)
# shape
df_X.shape
# (39999, 13)
# 資料集基本情況
df_X.describe().T
# 個特徵資料型別
df_X.dtypes
這裡重點說下Dtypes輸出,從輸出結果可以看到,要做分類的特徵包括edu、user_level、industry、act_level、sex、region都會識別為浮點型,而後面要做的二值化標誌轉換需要轉換為int型,這裡的結果為下面做轉換提供了參考依據。
#缺失值
df_X.isnull().sum()
#缺失值
df_X.isnull().values.sum()
# 12
從NA Cols的結果可以看到除了value_level、blue_money和work_hours外,其他特徵都有缺失值;從valid records for each Cols結果中分析發現,這些列的缺失值不多,完整資料記錄是39999條而資料記錄缺失最多的特徵也只有2條而已;從Total number of NA lines is得到總的具有缺失值的記錄是12條,由此我們判斷缺失值情況不嚴重
# 樣本均衡性審查
y.value_counts()
0 30415
1 9584
Name: response, dtype: int64
還算均衡沒有出現相差太多
# 缺失值處理
# 字典:定義各個列資料轉換方法,分型別的用中位數或眾數,不然會出現小數
na_rules = {'age': df['age'].mean(),
'total_pageviews': df['total_pageviews'].mean(),
'edu': df['edu'].median(),
'edu_ages': df['edu_ages'].median(),
'user_level': df['user_level'].median(),
'industry': df['user_level'].median(),
'act_level': df['act_level'].median(),
'sex': df['sex'].median(),
'red_money': df['red_money'].mean(),
'region': df['region'].median()
}
X = df_X.fillna(na_rules) # 使用指定方法填充缺失值
# 做型別轉換,把分類特徵的型別給為整形,之後做one-hot
def type_con(df):
'''
轉換目標列的資料為特定資料型別
:param df: 資料框
:return: 型別轉換後的資料框
'''
var_list = {'edu': 'int32',
'user_level': 'int32',
'industry': 'int32',
'value_level': 'int32',
'act_level': 'int32',
'sex': 'int32',
'region': 'int32'
} # 字典:定義要轉換的列及其資料型別
for var in var_list: # 迴圈讀出列名和對應的資料型別
df[var] = df[var].astype(var_list[var]) # 資料型別轉換
return df
X = type_con(X)
X.dtypes
# 數值轉化,把型別變數做one-hot處理
def symbol_con(df, enc_object=None, train=True):
'''
將分類和順序變數轉換為二值化的標誌變數
:param df: 資料框
:param enc_transform: sklearn的標誌轉換物件,訓練階段設定預設值為None;預測階段使用從訓練階段獲得的轉換物件
:param train: 是否為訓練階段的判斷狀態,訓練階段為True,預測階段為False
:return: 標誌轉換後的資料框、標誌轉換物件(如果是訓練階段)
'''
convert_cols = ['edu', 'user_level', 'industry', 'value_level', 'act_level', 'sex', 'region'] # 選擇要做標誌轉換的列名
df_con = df[convert_cols] # 選擇要做標誌轉換的資料
df_org = df[['age', 'total_pageviews', 'edu_ages', 'blue_money', 'red_money', 'work_hours']].values # 設定不作標誌轉換的列
if train == True: # 如果處於訓練階段
enc = OneHotEncoder() # 建立標誌轉換模型物件
enc.fit(df_con) # 訓練模型
df_con_new = enc.transform(df_con).toarray() # 轉換資料並輸出為陣列格式
new_matrix = np.hstack((df_con_new, df_org)) # 將未轉換的資料與轉換後的資料合併
return new_matrix, enc
else:
df_con_new = enc_object.transform(df_con).toarray() # 使用訓練階段獲得的轉換物件轉換資料並輸出為陣列格式
new_matrix = np.hstack((df_con_new, df_org)) # 將未轉換的資料與轉換後的資料合併
return new_matrix
X, enc = symbol_con(X, None, True)
X.shape
# (39999, 92)
def get_best_model(X, y):
'''
結合交叉檢驗得到不同引數下的分類模型結果
:param X: 輸入X(特徵變數)
:param y: 預測y(目標變數)
:return: 特徵選擇模型物件
'''
transform = SelectPercentile(f_classif, percentile=50) # 使用f_classif方法選擇特徵最明顯的50%數量的特徵
model_adaboost = AdaBoostClassifier() # 建立AdaBoostClassifier模型物件
model_pipe = Pipeline(steps=[('ANOVA', transform), ('model_adaboost', model_adaboost)]) # 建立由特徵選擇和分類模型構成的“管道”物件
cv = StratifiedKFold(5) # 設定交叉檢驗次數
n_estimators = [20, 50, 80, 100] # 設定模型引數列表
score_methods = ['accuracy', 'f1', 'precision', 'recall', 'roc_auc'] # 設定交叉檢驗指標
mean_list = list() # 建立空列表用於存放不同引數方法、交叉檢驗評估指標的均值列表
std_list = list() # 建立空列表用於存放不同引數方法、交叉檢驗評估指標的標準差列表
for parameter in n_estimators: # 迴圈讀出每個引數值
t1 = time.time() # 記錄訓練開始的時間
score_list = list() # 建立空列表用於存放不同交叉檢驗下各個評估指標的詳細資料
print ('set parameters: %s' % parameter) # 列印當前模型使用的引數
for score_method in score_methods: # 迴圈讀出每個交叉檢驗指標
model_pipe.set_params(model_adaboost__n_estimators=parameter) # 通過“管道”設定分類模型引數
score_tmp = cross_val_score(model_pipe, X, y, scoring=score_method, cv=cv) # 使用交叉檢驗計算指定指標的得分
score_list.append(score_tmp) # 將交叉檢驗得分儲存到列表
score_matrix = pd.DataFrame(np.array(score_list), index=score_methods) # 將交叉檢驗詳細資料轉換為矩陣
score_mean = score_matrix.mean(axis=1).rename('mean') # 計算每個評估指標的均值
score_std = score_matrix.std(axis=1).rename('std') # 計算每個評估指標的標準差
score_pd = pd.concat([score_matrix, score_mean, score_std], axis=1) # 將原始詳細資料和均值、標準差合併
mean_list.append(score_mean) # 將每個引數得到的各指標均值追加到列表
std_list.append(score_std) # 將每個引數得到的各指標標準差追加到列表
print (score_pd.round(2)) # 列印每個引數得到的交叉檢驗指標資料,只保留2位小數
print ('-' * 60)
t2 = time.time() # 計算每個引數下演算法用時
tt = t2 - t1 # 計算時間間隔
print ('time: %s' % str(tt)) # 列印時間間隔
mean_matrix = np.array(mean_list).T # 建立所有引數得到的交叉檢驗的均值矩陣
std_matrix = np.array(std_list).T # 建立所有引數得到的交叉檢驗的標準差矩陣
mean_pd = pd.DataFrame(mean_matrix, index=score_methods, columns=n_estimators) # 將均值矩陣轉換為資料框
std_pd = pd.DataFrame(std_matrix, index=score_methods, columns=n_estimators) # 將均值標準差轉換為資料框
print ('Mean values for each parameter:')
print (mean_pd) # 列印輸出均值矩陣
print ('Std values for each parameter:')
print (std_pd) # 列印輸出標準差矩陣
print ('-' * 60)
return transform
# 分類模型訓練
transform = get_best_model(X, y) # 獲得最佳分類模型引數資訊
但是對比各方面的評估指標,我們發現引數設定為80時的結果相對於100基本一致(其實小數點後面還有更多的位數能夠體現出更多小模型器的結果會更好,但這種效果提升已經不明顯)。
然後使用tranform物件對資料集X做特徵選擇得到最終建模特徵資料X_final,最後使用AdaBoostClassifier模型以及最佳引數80做演算法訓練。
transform.fit(X, y) # 應用特徵選擇物件選擇要參與建模的特徵變數
X_final = transform.transform(X) # 獲得具有顯著性特徵的特徵變數
final_model = AdaBoostClassifier(n_estimators=100) # 從列印的引數均值和標準差資訊中確定引數並建立分類模型物件
final_model.fit(X_final, y) # 訓練模型
####新資料集做預測 ####
# 讀取要預測的資料集
test_data = pd.read_excel('order.xlsx', sheetname=1)
# 獲取最終的目標變數值
final_reponse = test_data['final_response']
# 獲得預測的輸入變數X
test_data = test_data.drop('final_response', axis=1)
# 檢視缺失值
test_data.isnull().sum()
# 填充缺失值
X_test = test_data.fillna(na_rules)
X_test = type_con(X_test) # 資料型別轉換
X_test = symbol_con(X_test, enc_object=enc, train=False) # 將分類和順序資料轉換為標誌
X_test_final = transform.transform(X_test)
# 獲取預測值標籤
predict_labels = pd.DataFrame(final_model.predict(X_test_final), columns=['labels'])
# 獲取預測概率
predict_labels_pro = pd.DataFrame(final_model.predict_proba(X_test_final), columns=['pro_0','pro_1'])
# 將預測標籤、預測資料和原始資料合併
predict_pd = pd.concat((test_data, predict_labels, predict_labels_pro), axis=1)
predict_pd.head()
# 看一下準確率
accuracy_score(final_reponse, predict_labels)
0.86249010516792946
不高,還可以看
####將預測結果寫入Excel####
writer = pd.ExcelWriter('order_result.xlsx') # 建立寫入檔案物件
predict_pd.to_excel(writer, 'data') # 將資料寫入sheet1
writer.save() # 儲存檔案
由於本案例是一個預測性質的應用,其結果直接給到業務部門,因此沒有分析型的資料結論。從最終的預測結果與真實應用結果的對照來看,業務部門對86.2%的準確率表示基本滿意,能符合預期。
參考:
《python資料分析與資料化運營》 宋天龍