1. 程式人生 > >天貓重複買家預測比賽整理

天貓重複買家預測比賽整理

賽題介紹

商家有時會在特定日期(例如“Boxing-day”,“黑色星期五”或“雙11”)進行大促銷(例如折扣或現金券),以吸引大量新買家。許多吸引的買家都是一次性交易獵人,這些促銷可能對銷售產生很小的長期影響。為了緩解這個問題,商家必須確定誰可以轉換為重複買家。通過瞄準這些潛力忠誠的客戶,商家可以大大降低促銷成本,提高投資回報率(ROI)。

眾所周知,線上廣告領域,客戶定位極具挑戰性,特別是對於新買家而言。但是通過Tmall.com長期積累的使用者行為日誌,我們或許可以解決這個問題。在這個挑戰中,我們提供了一套商家及其在“雙11”日促銷期間獲得的相應新買家。你的任務是預測對於指定商家的新買家將來是否會成為忠實客戶。換句話說,您需要預測這些新買家在6個月內再次從同一商家購買商品的概率。一個包含大約20萬用戶的資料集用於訓練,還有一個類似大小的資料集用於測試。與其他比賽類似,您可以提取任何特徵,然後使用其他工具進行訓練。您只需提交預測結果進行評估。

1.資料處理

資料描述

該資料集包含在“雙十一”之前和之後的過去6個月中的匿名使用者的購物日誌,以及指示他們是否是重複購買者的標籤資訊。由於隱私問題,資料以偏移量進行取樣,因此該資料集的統計結果對於Tmall.com的實際資料有一些偏差。但它不會影響解決方案的適用性。可以在“data_format2.zip”中找到訓練和測試資料集的檔案。資料格式的詳細資訊可以在下表中找到。 官方給了兩種格式的資料,格式1包含四個檔案,分別是使用者行為日誌、使用者資訊、訓練集、測試集。使用者行為日誌包含使用者ID、商品ID、商品類別、商戶ID、商品品牌、時間和使用者行為類別7個特徵,使用者資訊包含使用者ID、使用者年齡段和使用者性別資訊,訓練集和測試集分別包含使用者ID、商戶ID和是否為重複買家標籤,其中訓練集標籤為0-1,測試集標籤為空,需要選手預測。具體資訊可以檢視官方的賽題與資料面板。

賽題分析

與廣告點選率問題類似,此賽題的目的是根據使用者與商戶在雙11之前6個月的互動記錄資料和雙11期間的互動記錄預測商戶的新買家在未來的6個月內再次從同一商家購買商品的概率。

資料格式化

參考這裡的處理方式對日誌檔案進行壓縮儲存

# 對資料按照格式進行壓縮重新儲存
def compressData(inputData):
    '''
    :parameters: inputData: pd.Dataframe
    :return: inputData: pd.Dataframe
    :Purpose: 
    壓縮csv中的資料,通過改變掃描每列的dtype,轉換成適合的大小
    例如: int64, 檢查最小值是否存在負數,是則宣告signed,否則宣告unsigned,並轉換更小的int size
    對於object型別,則會轉換成category型別,佔用記憶體率小
    參考來自:https://www.jiqizhixin.com/articles/2018-03-07-3
    '''
    for eachType in set(inputData.dtypes.values):
        ##檢查屬於什麼型別
        if 'int' in str(eachType):
            ## 對每列進行轉換
            for i in inputData.select_dtypes(eachType).columns.values:
                if inputData[i].min() < 0:
                    inputData[i] = pd.to_numeric(inputData[i],downcast='signed')
                else:
                    inputData[i] = pd.to_numeric(inputData[i],downcast='unsigned')      
        elif 'float' in str(eachType):
            for i in inputData.select_dtypes(eachType).columns.values:   
                inputData[i] = pd.to_numeric(inputData[i],downcast='float')
        elif 'object' in str(eachType):
            for i in inputData.select_dtypes(eachType).columns.values: 
                inputData[i] = trainData7[i].astype('category')
    return inputData
 
userInfo = pd.read_csv('d:/JulyCompetition/input/user_log_format1.csv')
print('Before compressed:\n',userInfo.info())
userInfo = compressData(userInfo)
print('After compressed:\n',userInfo.info())

資料清洗

檢視缺失值發現brand_id有91015條缺失資料

userInfo.isnull().sum()

使用所在商戶對應的品牌眾數填充

# brand_id使用所在seller_id對應的brand_id的眾數填充
def get_Logs():
    '''
    :parameters: None: None
    :return: userLog: pd.Dataframe
    :Purpose: 
    方便與其他函式調取原始的行為資料,同時已對缺失省進行調整
    使用pickle模組進行序列話,加快速度讀寫
    '''
    filePath = 'd:/JulyCompetition/features/Logs.pkl'
    if os.path.exists(filePath):
        userLog = pickle.load(open(filePath,'rb'))
    else:
        userLog = pd.read_csv('d:/JulyCompetition/input/user_log_format1.csv',dtype=column_types)
        print('Is null? \n',userLog.isnull().sum())
 
        ## 對brand_id缺失值進行處理
        missingIndex = userLog[userLog.brand_id.isnull()].index
        ## 思路:找到所有商店所擁有brand_id的眾數,並對所缺失的brand_id與其相對應的商店進行填充
        sellerMode = userLog.groupby(['seller_id']).apply(lambda x:x.brand_id.mode()[0]).reset_index()
        pickUP = userLog.loc[missingIndex]
        pickUP = pd.merge(pickUP,sellerMode,how='left',on=['seller_id'])[0].astype('float32')
        pickUP.index = missingIndex
        userLog.loc[missingIndex,'brand_id'] = pickUP
        del pickUP,sellerMode,missingIndex
        print('--------------------')
        print('Is null? \n',userLog.isnull().sum())
        pickle.dump(userLog,open(filePath,'wb'))
    return userLog
userLog = get_Logs()

2.特徵工程

2.1 使用者特徵

使用者基本資訊

# 使用者基本資訊:年齡,性別(類別型特徵)
userInfo = pd.read_csv('d:/JulyCompetition/input/user_info_format1.csv')
userInfo.age_range.fillna(userInfo.age_range.median(),inplace=True)#年齡用中位數填充
userInfo.gender.fillna(userInfo.gender.mode()[0],inplace=True)# 性別用眾數填充
print('Check any missing value?\n',userInfo.isnull().any())# 檢查預設值
df_age = pd.get_dummies(userInfo.age_range,prefix='age')# 對age進行啞編碼
df_sex = pd.get_dummies(userInfo.gender)# 對gender進行啞編碼並改變列名
df_sex.rename(columns={0:'female',1:'male',2:'unknown'},inplace=True)
userInfo = pd.concat([userInfo.user_id, df_age, df_sex], axis=1)# 整合user資訊
del df_age,df_sex
print(userInfo.info())

使用者行為資訊

# 提取全部的原始行為資料...
totalActions = userLog[["user_id","action_type"]]
totalActions.head()

統計使用者互動次數

  • 使用者互動的總次數(是否為活躍使用者)
  • 使用者點選商品的總次數
  • 使用者加入購物車的總次數
  • 使用者購買商品的總次數
  • 使用者收藏商品的總次數
# 對行為類別進行啞編碼,0 表示點選, 1 表示加入購物車, 2 表示購買,3 表示收藏.
df = pd.get_dummies(totalActions['action_type'],prefix='userTotalAction')

# 統計日誌行為中使用者點選、加購、購買、收藏的總次數
totalActions = pd.concat([totalActions.user_id, df], axis=1).groupby(['user_id'], as_index=False).sum()
totalActions['userTotalAction'] = totalActions['userTotalAction_0']+totalActions['userTotalAction_1']+totalActions['userTotalAction_2']+totalActions['userTotalAction_3']
del df
totalActions.info()

使用者互動次數在所有使用者中的地位

  • 使用者互動次數佔所有使用者互動次數的比例
  • 使用者互動次數與使用者平均互動次數的差值(絕對差值、相對差值)
  • 使用者互動次數在所有使用者互動次數中的分位數
  • 使用者互動次數在所有使用者互動次數中的排名
print('所有使用者互動次數:'+str(userLog.shape[0]))
print('所有使用者數:'+str(userLog['user_id'].nunique()))
print('所有使用者平均互動次數:'+str(userLog.shape[0]/userLog['user_id'].nunique()))
totalActions['userTotalActionRatio'] = totalActions['userTotalAction']/userLog.shape[0]
totalActions['userTotalActionDiff'] = totalActions['userTotalAction']-userLog.shape[0]/userLog['user_id'].nunique()

使用者點選次數在所有使用者點選次數中的地位

  • 使用者點選次數佔所有使用者點選次數的比例
  • 使用者點選次數與使用者平均點選次數的差值(絕對差值、相對差值)
  • 使用者點選次數在所有使用者點選次數中的分位數
  • 使用者點選次數在所有使用者點選次數中的排名
print('所有使用者點選次數:'+str(userLog[userLog.action_type==0].shape[0]))
totalActions['userClickRatio'] = totalActions['userTotalAction_0']/userLog[userLog.action_type==0].shape[0]
print('使用者平均點選次數:'+str(userLog[userLog.action_type==0].shape[0]/userLog['user_id'].nunique()))
totalActions['userClickDiff'] = totalActions['userTotalAction_0']-userLog[userLog.action_type==0].shape[0]/userLog['user_id'].nunique()

使用者加入購物車次數在所有使用者加入購物車次數中的地位

  • 使用者加入購物車次數佔所有使用者加入購物車次數的比例
  • 使用者加入購物車次數與使用者平均加入購物車次數的差值(絕對差值、相對差值)
  • 使用者加入購物車次數在所有使用者加入購物車次數中的分位數
  • 使用者加入購物車次數在所有使用者加入購物車次數中的排名
print('所有使用者加入購物車次數:'+str(userLog[userLog.action_type==1].shape[0]))
totalActions['userAddRatio'] = totalActions['userTotalAction_1']/userLog[userLog.action_type==1].shape[0]
print('使用者平均加入購物車次數:'+str(userLog[userLog.action_type==1].shape[0]/userLog['user_id'].nunique()))
totalActions['userAddDiff'] = totalActions['userTotalAction_1']-userLog[userLog.action_type==1].shape[0]/userLog['user_id'].nunique()

使用者購買次數在所有使用者購買次數中的地位

  • 使用者購買次數佔所有使用者購買次數的比例
  • 使用者購買次數與使用者平均購買次數的差值(絕對差值、相對差值)
  • 使用者購買次數在所有使用者購買次數中的分位數
  • 使用者購買次數在所有使用者購買次數中的排名
print('所有使用者購買次數:'+str(userLog[userLog.action_type==2].shape[0]))
totalActions['userBuyRatio'] = totalActions['userTotalAction_2']/userLog[userLog.action_type==2].shape[0]
print('使用者平均購買次數:'+str(userLog[userLog.action_type==2].shape[0]/userLog['user_id'].nunique()))
totalActions['userBuyDiff'] = totalActions['userTotalAction_2']-userLog[userLog.action_type==2].shape[0]/userLog['user_id'].nunique()

使用者收藏次數在所有使用者收藏次數中的地位

  • 使用者收藏次數佔所有使用者收藏次數的比例
  • 使用者收藏次數與使用者平均收藏次數的差值(絕對差值、相對差值)
  • 使用者收藏次數在所有使用者收藏次數中的分位數
  • 使用者收藏次數在所有使用者收藏次數中的排名
print('所有使用者收藏次數:'+str(userLog[userLog.action_type==3].shape[0]))
totalActions['userSaveRatio'] = totalActions['userTotalAction_3']/userLog[userLog.action_type==3].shape[0]
print('使用者平均收藏次數:'+str(userLog[userLog.action_type==3].shape[0]/userLog['user_id'].nunique()))
totalActions['userSaveDiff'] = totalActions['userTotalAction_3']-userLog[userLog.action_type==3].shape[0]/userLog['user_id'].nunique()

統計使用者不同行為的習慣(使用者內部)

  • 使用者點選次數佔用戶總互動次數的比例
  • 使用者加入購物車次數佔用戶總互動次數的比例
  • 使用者購買次數佔用戶總互動次數的比例
  • 使用者收藏次數佔用戶總互動次數的比例
# 統計使用者點選,加購,收藏,購買次數佔用戶總互動次數的比例
totalActions['userClick_ratio'] = totalActions['userTotalAction_0']/totalActions['userTotalAction']
totalActions['userAdd_ratio'] = totalActions['userTotalAction_1']/totalActions['userTotalAction']
totalActions['userBuy_ratio'] = totalActions['userTotalAction_2']/totalActions['userTotalAction']
totalActions['userSave_ratio'] = totalActions['userTotalAction_3']/totalActions['userTotalAction']

統計使用者的點選、加入購物車、收藏的購買轉化率

  • 使用者點選轉化率
  • 使用者點選轉化率與所有使用者平均的點選轉化率的差值
  • 使用者加入購物車購買轉化率
  • 使用者加入購物車購買轉化率與所有使用者加入購物車購買轉化率的差值
  • 使用者收藏轉化率
  • 使用者收藏轉化率與所有使用者收藏轉化率的差值
# 統計日誌行為中使用者的點選、加購、收藏的購買轉化率
totalActions['userTotalAction_0_ratio'] = np.log1p(totalActions['userTotalAction_2']) - np.log1p(totalActions['userTotalAction_0'])
totalActions['userTotalAction_0_ratio_diff'] = totalActions['userTotalAction_0_ratio'] - totalActions['userTotalAction_0_ratio'].mean()
totalActions['userTotalAction_1_ratio'] = np.log1p(totalActions['userTotalAction_2']) - np.log1p(totalActions['userTotalAction_1'])
totalActions['userTotalAction_1_ratio_diff'] = totalActions['userTotalAction_1_ratio'] - totalActions['userTotalAction_1_ratio'].mean()
totalActions['userTotalAction_3_ratio'] = np.log1p(totalActions['userTotalAction_2']) - np.log1p(totalActions['userTotalAction_3'])
totalActions['userTotalAction_3_ratio_diff'] = totalActions['userTotalAction_3_ratio'] - totalActions['userTotalAction_3_ratio'].mean()
totalActions.info()

使用者互動的時間資訊(按天)

  • 使用者互動的總天數,與所有使用者平均互動總天數的比較
  • 使用者每個月的互動天數,與所有使用者平均每個月的互動天數的比較
  • 使用者月互動天數的變化量,與所有使用者平均月互動天數變化量的比較
days_cnt = userLog.groupby(['user_id'])['time_stamp'].nunique()
days_cnt_diff = days_cnt - userLog.groupby(['user_id'])['time_stamp'].nunique().mean()

使用者互動的時間資訊(按次數)

  • 使用者互動的總天數,與所有使用者平均互動總天數的比較
  • 使用者每個月的互動天數,與所有使用者平均每個月的互動天數的比較
  • 使用者月互動天數的變化量,與所有使用者平均月互動天數變化量的比較
  • 使用者相鄰兩次互動行為的相隔天數最小值、最大值
  • 所有使用者相鄰兩次互動行為的相隔天數平均最小值、平均最大值
  • 使用者在雙11之前是否是重複購買者(購買過一家商戶的至少兩件商品)
  • 使用者在雙11之前重複購買過的商品數量
# 對數值型特徵手動標準化
numeric_cols = totalActions.columns[totalActions.dtypes == 'float64']
numeric_cols
numeric_col_means = totalActions.loc[:, numeric_cols].mean()
numeric_col_std = totalActions.loc[:, numeric_cols].std()
totalActions.loc[:, numeric_cols] = (totalActions.loc[:, numeric_cols] - numeric_col_means) / numeric_col_std
totalActions.head(5)
# 將統計好的數量和轉化率進行拼接
userInfo = pd.merge(userInfo,totalActions,how='left',on=['user_id'])
del totalActions
userInfo.info()
  • 使用者六個月中做出行為的商品數量(使用者互動行為的廣泛程度)
  • 使用者六個月中做出行為的種類數量
  • 使用者六個月中做出行為的店鋪數量
  • 使用者六個月中做出行為的品牌數量
  • 使用者六個月中做出行為的天數(使用者互動行為的長期性)
# 使用者六個月中做出行為的商品數量
item_cnt = userLog.groupby(['user_id'])['item_id'].nunique()
# 使用者六個月中做出行為的種類數量
cate_cnt = userLog.groupby(['user_id'])['cat_id'].nunique()
# 使用者六個月中做出行為的店鋪數量
seller_cnt = userLog.groupby(['user_id'])['seller_id'].nunique()
# 使用者六個月中做出行為的品牌數量
brand_cnt = userLog.groupby(['user_id'])['brand_id'].nunique()
# 使用者六個月中做出行為的天數
days_cnt = userLog.groupby(['user_id'])['time_stamp'].nunique()

typeCount_result = pd.concat([item_cnt,cate_cnt],axis=1)
typeCount_result = pd.concat([typeCount_result,seller_cnt],axis=1)
typeCount_result = pd.concat([typeCount_result,brand_cnt],axis=1)
typeCount_result = pd.concat([typeCount_result,days_cnt],axis=1)
typeCount_result.rename(columns={'item_id':'item_cnt','cat_id':'cat_cnt','seller_id':'seller_cnt','brand_id':'brand_counts','time_stamp':'active_days'},inplace=True)
typeCount_result.reset_index(inplace=True)
typeCount_result.info()
# 對數值型特徵手動標準化
numeric_cols = typeCount_result.columns[typeCount_result.dtypes == 'int64']
print(numeric_cols)
numeric_col_means = typeCount_result.loc[:, numeric_cols].mean()
numeric_col_std = typeCount_result.loc[:, numeric_cols].std()
typeCount_result.loc[:, numeric_cols] = (typeCount_result.loc[:, numeric_cols] - numeric_col_means) / numeric_col_std
typeCount_result.head(5)
## 將統計好的數量進行拼接
userInfo = pd.merge(userInfo,typeCount_result,how='left',on=['user_id'])
del typeCount_result
userInfo.info()
  • 使用者在雙11之前是否有重複購買記錄(使用者是否趨向於購買過的店鋪)
  • 使用者在雙11之前重複購買過的商家數量(使用者對於購買過店鋪的認同度)
## 統計雙十一之前,使用者重複購買過的商家數量
### --------------------------------------------------------------------------
repeatSellerCount = userLog[["user_id","seller_id","time_stamp","action_type"]]
repeatSellerCount = repeatSellerCount[(repeatSellerCount.action_type == 2) & (repeatSellerCount.time_stamp < 1111)]
repeatSellerCount.drop_duplicates(inplace=True)
repeatSellerCount = repeatSellerCount.groupby(['user_id','seller_id'])['time_stamp'].count().reset_index()
repeatSellerCount = repeatSellerCount[repeatSellerCount.time_stamp > 1]
repeatSellerCount = repeatSellerCount.groupby(['user_id'])['seller_id'].count().reset_index()
repeatSellerCount.rename(columns={'seller_id':'repeat_seller_count'},inplace=True)
# 對數值型特徵手動標準化
numeric_cols = repeatSellerCount.columns[repeatSellerCount.dtypes == 'int64']
print(numeric_cols)
numeric_col_means = repeatSellerCount.loc[:, numeric_cols].mean()
numeric_col_std = repeatSellerCount.loc[:, numeric_cols].std()
repeatSellerCount.loc[:, numeric_cols] = (repeatSellerCount.loc[:, numeric_cols] - numeric_col_means) / numeric_col_std
repeatSellerCount.head(5)
userInfo = pd.merge(userInfo,repeatSellerCount,how='left',on=['user_id'])
# 沒有重複購買的user用0填充?
userInfo.repeat_seller_count.fillna(0,inplace=True)
userInfo['repeat_seller'] = userInfo['repeat_seller_count'].map(lambda x: 1 if x != 0 else 0)
del repeatSellerCount

使用者的活躍程度的變化

  • 使用者每個月點選的次數
  • 使用者每個月加購的次數
  • 使用者每個月購買的次數
  • 使用者每個月收藏的次數
  • 使用者每個月互動的總次數
# 使用者總互動的次數、天數
# 使用者互動的間隔
# 統計每月的點選次數,每月的加入購物次數,每月的購買次數,每月的收藏次數
### --------------------------------------------------------------------------
monthActionsCount = userLog[["user_id","time_stamp","action_type"]]
result = list()
for i in range(5,12):
    start = int(str(i)+'00')
    end = int(str(i)+'30')
    # 獲取i月的資料
    example = monthActionsCount[(monthActionsCount.time_stamp >= start) & (monthActionsCount.time_stamp < end)]
    # 對i月的互動行為進行啞編碼
    df = pd.get_dummies(example['action_type'],prefix='%d_Action'%i)
    df[str(i)+'_Action'] = df[str(i)+'_Action_0']+df[str(i)+'_Action_1']+df[str(i)+'_Action_2']+df[str(i)+'_Action_3']
    # 將example的time_stamp設為月份值(5,6,。。。,11)
    example.loc[:,'time_stamp'] = example.time_stamp.apply(lambda x: int(str(x)[0]) if len(str(x)) == 3 else int(str(x)[:2]))
    result.append(pd.concat([example, df], axis=1).groupby(['user_id','time_stamp'],as_index=False).sum())

for i in range(0,7):
    userInfo = pd.merge(userInfo,result[i],how='left',on=['user_id'])
    userInfo.fillna(0,inplace=True)
for col in ['time_stamp_x','action_type_x','time_stamp_y','action_type_y','time_stamp','action_type']:
    del userInfo[col]
for i in range(5,12):
    userInfo[str(i)+'_Action'] = userInfo[str(i)+'_Action_0']+userInfo[str(i)+'_Action_1']+userInfo[str(i)+'_Action_2']+userInfo[str(i)+'_Action_3']

儲存使用者特徵

filePath='d:/JulyCompetition/features/userInfo_Features.pkl'
pickle.dump(userInfo, open(filePath, 'wb'))

讀取使用者特徵

# 讀取使用者特徵
filePath='d:/JulyCompetition/features/userInfo_Features.pkl'
if os.path.exists(filePath):
    userInfo = pickle.load(open(filePath,'rb'))
userInfo.info()

2.2商戶特徵

統計基於商戶的特徵主要目的是分析商戶在當前市場的受歡迎程度及商戶自身對忠實使用者的吸引力

  • 商戶商品,種類,品牌總數,佔總數的比例 (商戶自身實力)

  • 商戶被點選,被加入購物車,被購買,被收藏次數(商戶受歡迎程度)

  • 商戶被點選購買轉化率,被加入購物車購買轉化率,被收藏次數購買轉化率(商戶購買轉化率)

  • 商戶被點選的人數,被加入購物車的人數,被購買的人數(商戶使用者範圍)

  • 商戶被互動總次數,每月次數,平均每月次數,每月最多最少次數,每月變化量(商戶受歡迎程度)

  • 被收藏的人數, 商戶重複買家總數量

  • 統計商戶的商品數

  • 統計商戶的商品類別數

  • 統計商戶的品牌數量

# 統計每個商戶的商品,種類,品牌總數,並放入dataFrame[seller_id,xx_number]為列名,便於往後的拼接
# (表示商戶的規模大小)
itemNumber = userLog[['seller_id','item_id']].groupby(['seller_id'])['item_id'].nunique().reset_index()
catNumber = userLog[['seller_id','cat_id']].groupby(['seller_id'])['cat_id'].nunique().reset_index()
brandNumber = userLog[['seller_id','brand_id']].groupby(['seller_id'])['brand_id'].nunique().reset_index()
itemNumber.rename(columns={'item_id':'item_number'},inplace=True)
catNumber.rename(columns={'cat_id':'cat_number'},inplace=True)
brandNumber.rename(columns={'brand_id':'brand_number'},inplace=True)
  • 商戶雙11之前的重複買家數量
 # 統計商戶重複買家總數量(表示商戶對於新使用者的留存能力)
repeatPeoCount = userLog[(userLog.time_stamp < 1111) & (userLog.action_type == 2)]
repeatPeoCount = repeatPeoCount.groupby(['seller_id'])['user_id'].value_counts().to_frame()
repeatPeoCount.rename(columns={'user_id':'Buy_Number'},inplace=True)
repeatPeoCount.reset_index(inplace=True)
repeatPeoCount = repeatPeoCount[repeatPeoCount.Buy_Number > 1]
repeatPeoCount = repeatPeoCount.groupby(['seller_id']).apply(lambda x:len(x.user_id)).reset_index()
repeatPeoCount = pd.merge(pd.DataFrame({'seller_id':range(1, 4996 ,1)}),repeatPeoCount,how='left',on=['seller_id']).fillna(0)
repeatPeoCount.rename(columns={0:'repeatBuy_peopleNumber'},inplace=True)
  • 商戶被點選次數
  • 商戶商品被加入購物車次數
  • 商戶商品被購買次數
  • 商戶商品被收藏次數
  • 商戶點選轉化率
  • 商戶加入購物車轉化率
  • 商戶收藏轉化率
##統計被點選,被加入購物車,被購買,被收藏次數
###統計被點選購買轉化率,被加入購物車購買轉化率,被收藏次數購買轉化率
sellers = userLog[["seller_id","action_type"]]
df = pd.get_dummies(sellers['action_type'],prefix='seller')
sellers = pd.concat([sellers, df], axis=1).groupby(['seller_id'], as_index=False).sum()
sellers.drop("action_type", axis=1,inplace=True)
del df
# 構造轉化率欄位
sellers['seller_0_ratio'] = np.log1p(sellers['seller_2']) - np.log1p(sellers['seller_0'])
sellers['seller_1_ratio'] = np.log1p(sellers['seller_2']) - np.log1p(sellers['seller_1'])
sellers['seller_3_ratio'] = np.log1p(sellers['seller_2']) - np.log1p(sellers['seller_3'])
sellers.info()
  • 商戶被點選的使用者數
  • 商戶被加入購物車的使用者數
  • 商戶被購買的使用者數
  • 商戶被收藏的使用者數
###統計每個商戶被點選的人數,被加入購物車的人數,被購買的人數,被收藏的人數
peoCount = userLog[["user_id","seller_id","action_type"]]
df = pd.get_dummies(peoCount['action_type'],prefix='seller_peopleNumber')
peoCount = pd.concat([peoCount, df], axis=1)
peoCount.drop("action_type", axis=1,inplace=True)
peoCount.drop_duplicates(inplace=True)
df1 = peoCount.groupby(['seller_id']).apply(lambda x:x.seller_peopleNumber_0.sum())
df2 = peoCount.groupby(['seller_id']).apply(lambda x:x.seller_peopleNumber_1.sum())
df3 = peoCount.groupby(['seller_id']).apply(lambda x:x.seller_peopleNumber_2.sum())
df4 = peoCount.groupby(['seller_id']).apply(lambda x:x.seller_peopleNumber_3.sum())
peoCount = pd.concat([df1, df2,df3, df4], axis=1).reset_index()
del df1,df2,df3,df4
peoCount.rename(columns={0:'seller_peopleNum_0',1:'seller_peopleNum_1',2:'seller_peopleNum_2',3:'seller_peopleNum_3'},inplace=True)
peoCount.info()
###對各種統計表根據seller_id進行拼接
sellers = pd.merge(sellers,peoCount,on=['seller_id'])
sellers = pd.merge(sellers,itemNumber,on=['seller_id'])
sellers = pd.merge(sellers,catNumber,on=['seller_id'])
sellers = pd.merge(sellers,brandNumber,on=['seller_id'])
sellers = pd.merge(sellers,repeatPeoCount,on=['seller_id'])
del itemNumber,catNumber,brandNumber,peoCount,repeatPeoCount
sellers.info()
  • 商戶的商品數佔總商品數的比例
  • 商戶的商品類別佔總商品類別的比例
  • 商戶的商品品牌佔總商品品牌的比例
# 統計每個商戶的商品數,商品種類、品牌佔總量的比例(表示商戶的規模大小)
sellers['item_ratio'] = sellers['item_number']/userLog['item_id'].nunique()
sellers['cat_ratio'] = sellers['item_number']/userLog['cat_id'].nunique()
sellers['brand_ratio'] = sellers['item_number']/userLog['brand_id'].nunique()
  • 在此商戶有點選行為的使用者數佔所有有點選行為使用者數的比例
  • 在此商戶有加入購物車使用者數佔所有有加購物車行為使用者數的比例
  • 在此商戶有購買行為的使用者數佔所有有購買行為使用者數的比例
  • 在此商戶有收藏行為的使用者數佔所有有收藏行為使用者數的比例
# 統計每個商戶被點選、加購、購買、收藏的人數佔有點選、加購、購買、收藏行為人數的比例
sellers['click_people_ratio'] = sellers['seller_peopleNum_0']/userLog[userLog['action_type'] == 0]['user_id'].nunique()
sellers['add_people_ratio'] = sellers['seller_peopleNum_1']/userLog[userLog['action_type'] == 1]['user_id'].nunique()
sellers['buy_people_ratio'] = sellers['seller_peopleNum_2']/userLog[userLog['action_type'] == 2]['user_id'].nunique()
sellers['save_people_ratio'] = sellers['seller_peopleNum_3']/userLog[userLog['action_type'] == 3]['user_id'].nunique()
# 對數值型特徵手動標準化
numeric_cols = sellers.columns[sellers.dtypes != 'uint64']
print(numeric_cols)
numeric_col_means = sellers.loc[:, numeric_cols].mean()
numeric_col_std = sellers.loc[:, numeric_cols].std()
sellers.loc[:, numeric_cols] = (sellers.loc[:, numeric_cols] - numeric_col_means) / numeric_col_std
sellers.head(5)

儲存商戶特徵

filePath='d:/JulyCompetition/features/sellerInfo_Features.pkl'
pickle.dump(sellers,open(filePath,'wb'))

讀取商戶特徵

# 讀取商戶特徵
filePath='d:/JulyCompetition/features/sellerInfo_Features.pkl'
if os.path.exists(filePath):
    sellers = pickle.load(open(filePath,'rb'))

2.3使用者-商戶特徵

統計使用者 - 商戶之間的特徵主要目的是分析特定使用者與特定商戶之間所形成的關係

  • 點選,加入購物車,購買,收藏的總次數
  • 點選,加入購物車,收藏的轉化率
  • 點選,加入購物車,購買,收藏的總天數
  • 點選商品數量,佔商戶總數量的比例
  • 加入購物車商品數量,佔商戶商品總數量的比例
  • 購買商品的數量,佔商戶商品總數量的比例
  • 收藏商品的數量,佔商戶商品總數量的比例
  • 點選商品類別數量,佔商戶商品類別總數量的比例
  • 加入購物車商品類別數量,佔商戶商品類別總數量的比例
  • 購買商品類別數量,佔商戶商品類別總數量的比例
  • 收藏商品類別數量,佔商戶商品類別總數量的比例
  • 點選商品品牌數量,佔商戶商品品牌總數量的比例
  • 加入購物車商品品牌數量,佔商戶商品品牌總數量的比例
  • 購買商品品牌數量,佔商戶商品品牌總數量的比例
  • 收藏商品品牌數量,佔商戶商品品牌總數量的比例
## 提取預測目標的行為資料
trainData = pd.read_csv('d:/JulyCompetition/input/train_format1.csv')
trainData.rename(columns={'merchant_id':'seller_id'},inplace=True)
testData = pd.read_csv('d:/JulyCompetition/input/test_format1.csv')
testData.rename(columns={'merchant_id':'seller_id'},inplace=True)
targetIndex = pd.concat([trainData[['user_id', 'seller_id']],testData[['user_id', 'seller_id']]],ignore_index=True)
logs = pd.merge(targetIndex,userLog,on=['user_id', 'seller_id'])
del trainData,testData,targetIndex
logs.info()
  • 使用者-商戶的點選、加入購物車、購買、收藏次數
  • 使用者-商戶的點選、加入購物車、收藏轉化率
### 統計使用者對預測的商店的行為特徵,例如點選,加入購物車,購買,收藏的總次數,以及各種轉化率
df_result = logs[["user_id", "seller_id","action_type"]]
df = pd.get_dummies(df_result['action_type'],prefix='userSellerAction')
df_result = pd.concat([df_result, df], axis=1).groupby(['user_id', 'seller_id'], as_index=False).sum()
del df
df_result.drop("action_type", axis=1,inplace=True)
df_result['userSellerAction_0_ratio'] = np.log1p(df_result['userSellerAction_2']) - np.log1p(df_result['userSellerAction_0'])
df_result['userSellerAction_1_ratio'] = np.log1p(df_result['userSellerAction_2']) - np.log1p(df_result['userSellerAction_1'])
df_result['userSellerAction_3_ratio'] = np.log1p(df_result['userSellerAction_2']) - np.log1p(df_result['userSellerAction_3'])
df_result.info()
  • 使用者-商戶的點選,加入購物車,購買,收藏的總天數
###統計使用者對預測商店點選的總天數
clickDays = logs[logs.action_type == 0]
clickDays = clickDays[["user_id", "seller_id","time_stamp","action_type"]]
clickDays = clickDays.groupby(['user_id', 'seller_id']).apply(lambda x:x.time_stamp.nunique()).reset_index()
clickDays.rename(columns={0:'click_days'},inplace=True)
df_result = pd.merge(df_result,clickDays,how='left',on=['user_id', 'seller_id'])
df_result.click_days.fillna(0,inplace=True)
del clickDays
###統計使用者對預測商店加入購物車的總天數
addDays = logs[logs.action_type == 1]
addDays = addDays[["user_id", "seller_id","time_stamp","action_type"]]
addDays = addDays.groupby(['user_id', 'seller_id']).apply(lambda x:x.time_stamp.nunique()).reset_index()
addDays.rename(columns={0:'add_days'},inplace=True)
df_result = pd.merge(df_result,addDays,how='left',on=['user_id', 'seller_id'])
df_result.add_days.fillna(0,inplace=True)
del addDays
###統計使用者對預測商店購物的總天數
buyDays = logs[logs.action_type == 2]
buyDays = buyDays[["user_id", "seller_id","time_stamp","action_type"]]
buyDays = buyDays.groupby(['user_id', 'seller_id']).apply(lambda x:x.time_stamp.nunique()).reset_index()
buyDays.rename(columns={0:'buy_days'},inplace=True)
df_result = pd.merge(df_result,buyDays,how='left',on=['user_id', 'seller_id'])
df_result.buy_days.fillna(0,inplace=True)
del buyDays
###統計使用者對預測商店購物的總天數
saveDays = logs[logs.action_type == 3]
saveDays = saveDays[["user_id", "seller_id","time_stamp","action_type"]]
saveDays = saveDays.groupby(['user_id', 'seller_id']).apply(lambda x:x.time_stamp.nunique()).reset_index()
saveDays.rename(columns={0:'save_days'},inplace=True)
df_result = pd.merge(df_result,saveDays,how='left',on=['user_id', 'seller_id'])
df_result.save_days.fillna(0,inplace=True)
del saveDays
  • 點選商品數量,佔商戶總數量的比例
  • 加入購物車商品數量,佔商戶商品總數量的比例
  • 購買商品的數量,佔商戶商品總數量的比例
  • 收藏商品的數量,佔商戶商品總數量的比例
itemCount = logs[["user_id", "seller_id","item_id","action_type"]]
# 點選商品數量
itemCountClick = itemCount[itemCount.action_type == 0]
item_result = itemCountClick.groupby(['user_id', 'seller_id']).apply(lambda x:x.item_id.nunique()).reset_index()
item_result.rename(columns={0:'item_click_count'},inplace=True)
item_result.item_click_count.fillna(0,inplace=True)
df_result = pd.merge(df_result,item_result,how='left',on=['user_id', 'seller_id'])
del itemCountClick,item_result
# 加入購物車商品數量
itemCountAdd = itemCount[itemCount.action_type == 1]
item_result = itemCountAdd.groupby(['user_id', 'seller_id']).apply(lambda x:x.item_id.nunique()).reset_index()
item_result.rename(columns={0:'item_add_count'},inplace=True)
item_result.item_add_count.fillna(0,inplace=True)
df_result = pd.merge(df_result,item_result,how='left',on=['user_id', 'seller_id'])
del itemCountAdd,item_result
# 購買商品數量
itemCountBuy = itemCount[itemCount.action_type == 2]
item_result = itemCountBuy.groupby(['user_id', 'seller_id']).apply(lambda x:x.item_id.nunique()).reset_index()
item_result.rename(columns={0:'item_buy_count'},inplace=True)
item_result.item_buy_count.fillna(0,inplace=True)
df_result = pd.merge(df_result,item_result,how='left',on=['user_id', 'seller_id'])
del itemCountBuy,item_result
# 收藏商品數量
itemCountSave = itemCount[itemCount.action_type == 3]
item_result = itemCountSave.groupby(['user_id', 'seller_id']).apply(lambda x:x.item_id.nunique()).reset_index()
item_result.rename(columns={0:'item_save_count'},inplace=True)
item_result.item_save_count.fillna(0,inplace=True)
df_result = pd.merge(df_result,item_result,how='left',on=['user_id', 'seller_id'])
del itemCountSave,item_result
  • 點選商品類別數量,佔商戶商品類別總數量的比例
  • 加入購物車商品類別數量,佔商戶商品類別總數量的比例
  • 購買商品類別數量,佔商戶商品類別總數量的比例
  • 收藏商品類別數量,佔商戶商品類別總數量的比例
catCount = logs[["user_id", "seller_id","cat_id","action_type"]]
# 點選種類數量
catCountClick = catCount[catCount.action_type == 0]
cat_result = catCountClick.groupby(['user_id', 'seller_id']).apply(lambda x:x.cat_id.nunique()).reset_index()
cat_result.rename(columns={0:'cat_click_count'},inplace=True)
cat_result.cat_click_count.fillna(0,inplace=True)
df_result = pd.merge(df_result,cat_result,how='left',on=['user_id', 'seller_id'])
del catCountClick,cat_result
# 加入購物車種類數量
catCountAdd = catCount[catCount.action_type == 1]
cat_result = catCountAdd.groupby(['user_id', 'seller_id']).apply(lambda x:x.cat_id.nunique()).reset_index()
cat_result.rename(columns={0:'cat_add_count'},inplace=True)
cat_result.cat_add_count.fillna(0,inplace=True)
df_result = pd.merge(df_result,cat_result,how='left',on=['user_id', 'seller_id'])
del catCountAdd,cat_result
# 購買種類數量
catCountBuy = catCount[catCount.action_type == 2]
cat_result = catCountBuy.groupby(['user_id', 'seller_id']).apply(lambda x:x.cat_id.nunique()).reset_index()
cat_result.rename(columns={0:'cat_buy_count'},inplace=True)
cat_result.cat_buy_count.fillna(0,inplace=True)
df_result = pd.merge(df_result,cat_result,how='left',on=['user_id', 'seller_id'])
del catCountBuy,cat_result
# 收藏種類數量
catCountSave = catCount[catCount.action_type == 3]
cat_result = catCountSave.groupby(['user_id', 'seller_id']).apply(lambda x:x.cat_id.nunique()).reset_index()
cat_result.rename(columns={0:'cat_save_count'},inplace=True)
cat_result.cat_save_count.fillna(0,inplace=True)
df_result = pd.merge(df_result,cat_result,how='left',on=['user_id', 'seller_id'])
del catCountSave,cat_result
  • 點選商品品牌數量,佔商戶商品品牌總數量的比例
  • 加入購物車商品品牌數量,佔商戶商品品牌總數量的比例
  • 購買商品品牌數量,佔商戶商品品牌總數量的比例
  • 收藏商品品牌數量,佔商戶商品品牌總數量的比例
brandCount = logs[["user_id", "seller_id","brand_id","action_type"]]
# 點選品牌數量
brandCountClick = brandCount[brandCount.action_type == 0]
brand_result = brandCountClick.groupby(['user_id', 'seller_id']).apply(lambda x:x.brand_id.nunique()).reset_index()
brand_result.rename(columns={0:'brand_click_count'},inplace=True)
brand_result.brand_click_count.fillna(0,inplace=True)
df_result = pd.merge(df_result,brand_result,how='left',on=['user_id', 'seller_id'])
del brandCountClick,brand_result
# 加入購物車品牌數量
brandCountAdd = brandCount[brandCount.action_type == 1]
brand_result = brandCountAdd.groupby(['user_id', 'seller_id']).apply(lambda x:x.brand_id.nunique()).reset_index()
brand_result.rename(columns={0:'brand_add_count'},inplace=True)
brand_result.brand_add_count.fillna(0,inplace=True)
df_result = pd.merge(df_result,brand_result,how='left',on=['user_id', 'seller_id'])
del brandCountAdd,brand_result
# 購買品牌數量
brandCountBuy = brandCount[brandCount.action_type == 2]
brand_result = brandCountBuy.groupby(['user_id', 'seller_id']).apply(lambda x:x.brand_id.nunique()).reset_index()
brand_result.rename(columns={0:'brand_buy_count'},inplace=True)
brand_result.brand_buy_count.fillna(0,inplace=True)
df_result = pd.merge(df_result,brand_result,how='left',on=['user_id', 'seller_id'])
del brandCountBuy,brand_result
# 收藏品牌數量
brandCountSave = brandCount[brandCount.action_type == 3]
brand_result = brandCountSave.groupby(['user_id', 'seller_id']).apply(lambda x:x.brand_id.nunique()).reset_index()
brand_result.rename(columns={0:'brand_save_count'},inplace=True)
brand_result.brand_save_count.fillna(0,inplace=True)
df_result = pd.merge(df_result,brand_result,how='left',on=['user_id', 'seller_id'])
del brandCountSave,brand_result
df_result.fillna(0,inplace=True)
# 對數值型特徵手動標準化
for col in ['buy_days','item_buy_count','cat_buy_count','brand_buy_count']:
    df_result[col] = df_result[col].astype('float64')
# 對數值型特徵手動標準化
numeric_cols = df_result.columns[df_result.dtypes == 'float64']
print(numeric_cols)
numeric_col_means = df_result.loc[:, numeric_cols].mean()
numeric_col_std = df_result.loc[:, numeric_cols].std()
df_result.loc[:, numeric_cols] = (df_result.loc[:, numeric_cols] - numeric_col_means) / numeric_col_std
df_result.head(5)

儲存使用者-商戶特徵

filePath='d:/JulyCompetition/features/userSellerActions.pkl'
pickle.dump(df_result,open(filePath,'wb'))
# 讀取商戶特徵
filePath='d:/JulyCompetition/features/userSellerActions.pkl'
if os.path.exists(filePath):
    df_results = pickle.load(open(filePath,'rb'))

3.建立模型

3.1 構造訓練集、測試集

# 構造訓練集
def make_train_set():
    filePath = 'd:/JulyCompetition/features/trainSetWithFeatures.pkl'
    if os.path.exists(filePath):
        trainSet = pickle.load(open(filePath,'rb'))
    else:     
        trainSet = pd.read_csv('d:/JulyCompetition/input/train_format1.csv')
        trainSet.rename(columns={'merchant_id':'seller_id'},inplace=True)
        userInfo = pickle.load(open('d:/JulyCompetition/features/userInfo_Features.pkl','rb'))
        trainSet = pd.merge(trainSet,userInfo,how='left',on=['user_id'])
        sellerInfo = pickle.load(open('d:/JulyCompetition/features/sellerInfo_Features.pkl','rb'))
        trainSet = pd.merge(trainSet,sellerInfo,how='left',on=['seller_id'])
        userSellers = pickle.load(open('d:/JulyCompetition/features/userSellerActions.pkl','rb'))
        trainSet = pd.merge(trainSet,userSellers,how='left',on=['user_id','seller_id'])
        del userInfo,sellerInfo,userSellers
        pickle.dump(trainSet,open(filePath,'wb'))
    return trainSet
trainSet = make_train_set()
trainSet.info()
# 構造測試集
def make_test_set():
    filePath = 'd:/JulyCompetition/features/testSetWithFeatures.pkl'
    if os.path.exists(filePath):
        testSet = pickle.load(open(filePath,'rb'))
    else:     
        testSet = pd.read_csv('d:/JulyCompetition/input/test_format1.csv')
        testSet.rename(columns={'merchant_id':'seller_id'},inplace=True)
        userInfo = pickle.load(open('d:/JulyCompetition/features/userInfo_Features.pkl','rb'))
        testSet = pd.merge(testSet,userInfo,how='left',on=['user_id'])
        sellerInfo = pickle.load(open('d:/JulyCompetition/features/sellerInfo_Features.pkl','rb'))
        testSet = pd.merge(testSet,sellerInfo,how='left',on=['seller_id'])
        userSellers = pickle.load(open('d:/JulyCompetition/features/userSellerActions.pkl','rb'))
        testSet = pd.merge(testSet,userSellers,how='left',on=['user_id','seller_id'])
        del userInfo,sellerInfo,userSellers
        pickle.dump(testSet,open(filePath,'wb'))
    return testSet
testSet = make_test_set()
testSet.info()
## 提取訓練特徵集
from sklearn.model_selection import train_test_split
## 並按照0.85 : 0.15比例分割訓練集和測試集
## 並測試集中分一半給xgboost作驗證集,防止過擬合,影響模型泛化能力

# dataSet = pickle.load(open('features/trainSetWithFeatures.pkl','rb'))
###  把訓練集進行分隔成訓練集,驗證集,測試集
x = trainSet.loc[:,trainSet.columns != 'label']
y = trainSet.loc[:,trainSet.columns == 'label']
X_train, X_test, y_train, y_test = train_test_split(x,y,test_size = 0.2, random_state = 2018)

del X_train['user_id']
del X_train['seller_id']
del X_test['user_id']
del X_test['seller_id']
print(X_train.shape,y_train.shape,X_test.shape,y_test.shape)

3.2 模型訓練

LR

from sklearn.model_selection import train_test_split, GridSearchCV, KFold
from sklearn.linear_model import LogisticRegression
params=[
    {'penalty':['l1'],
    'C':[100,1000],
    'solver':['liblinear']},
    {'penalty':['l2'],
    'C':[100,1000],
    'solver':['lbfgs']}]
clf = LogisticRegression(random_state=2018, max_iter=1000,  verbose=2)
grid = GridSearchCV(clf, params, scoring='roc_auc',cv=10, verbose=2)
grid.fit(X_train, y_train)

print(grid.best_score_)    #檢視最佳分數(此處為neg_mean_absolute_error)
print(grid.best_params_)   #檢視最佳引數
print(grid.cv_results_)
print(grid.best_estimator_) 
lr=grid.best_estimator_

xgboost

## 提取訓練特徵集
## 並按照0.85 : 0.15比例分割訓練集和測試集
## 並測試集中分一半給xgboost作驗證集,防止過擬合,影響模型泛化能力
import pandas as pd
import numpy as np
import xgboost as xgb
import pickle
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score

# 構造訓練集
dataSet = pickle.load(open('d:/JulyCompetition/features/trainSetWithFeatures.pkl','rb'))
###  把訓練集進行分隔成訓練集,驗證集,測試集
x = dataSet.loc[:,dataSet.columns != 'label']
y = dataSet.loc[:,dataSet.columns == 'label']
x_train, x_test, y_train, y_test = train_test_split(x,y,test_size = 0.15, random_state = 0)
 
x_val = x_test.iloc[:int(x_test.shape[0]/2),:]
y_val = y_test.iloc[:int(y_test.shape[0]/2),:]
 
x_test = x_test.iloc[int(x_test.shape[0]/2):,:] 
y_test = y_test.iloc[int(y_test.shape[0]/2):,:]
 
del x_train['user_id'],x_train['seller_id'],x_val['user_id'],x_val['seller_id']
 
dtrain = xgb.DMatrix(x_train, label=y_train)
dtest = xgb.DMatrix(x_val, label=y_val)

## 快速訓練和測試:xgboost訓練
param = {'n_estimators': 500,
     'max_depth': 4, 
     'min_child_weight': 3,
     'gamma':0.3,
     'subsample': 0.8,
     'colsample_bytree': 0.8,  
     'eta': 0.125,
     'silent': 1, 
     'objective': 'binary:logistic',
     'eval_metric':'auc',
     'nthread':16
    }
plst = param.items()
evallist = [(dtrain, 'train'),(dtest,'eval')]
bst = xgb.train(plst, dtrain, 500, evallist, early_stopping_rounds=10)
 
## 將特徵重要性排序出來和列印並儲存
def create_feature_map(features):
    outfile = open(r'd:/JulyCompetition/output/featureMap/firstXGB.fmap', 'w')
    i = 0
    for feat in features:
        outfile.write('{0}\t{1}\tq\n'.format(i, feat))
        i = i + 1
    outfile.close()
def feature_importance(bst_xgb):
    importance = bst_xgb.get_fscore(fmap=r'd:/JulyCompetition/output/featureMap/firstXGB.fmap')
    importance = sorted(importance.items(), reverse=True)
 
    df = pd.DataFrame(importance, columns=['feature', 'fscore'])
    df['fscore'] = df['fscore'] / df['fscore'].sum()
    return df
 
## 建立特徵圖
create_feature_map(list(x_train.columns[:]))
## 根據特徵圖,計算特徵重要性,並排序和展示
feature_importance = feature_importance(bst)
feature_importance.sort_values("fscore", inplace=True, ascending=False)
feature_importance.head(20)
 
##使用測試集,評估模型
users = x_test[['user_id', 'seller_id']].copy()
del x_test['user_id']
del x_test['seller_id']
x_test_DMatrix = xgb.DMatrix(x_test)
y_pred = bst.predict(x_test_DMatrix)
 
## 呼叫ROC-AUC函式,計算其AUC值
roc_auc_score(y_test,y_pred)

多模型

import lightgbm as lgb
from sklearn.neighbors import KNeighborsClassifier
from sklearn.linear_model import  LogisticRegression
from sklearn.neural_network import MLPClassifier
from sklearn.ensemble import GradientBoostingClassifier, RandomForestClassifier

def get_models(SEED=2018):
    """
    :parameters: None: None
    :return: models: Dict
    :Purpose: 
    宣告各種基模型,並將他們放入字典中,便於呼叫
    """
    lgm = lgb.LGBMClassifier(num_leaves=50,learning_rate=0.05,n_estimators=250,class_weight='balanced',random_state=SEED)
    xgbMo = xgb.XGBClassifier(max_depth=4,min_child_weight=2,learning_rate=0.15,n_estimators=150,nthread=4,gamma=0.2,subsample=0.9,colsample_bytree=0.7, random_state=SEED)
    knn = KNeighborsClassifier(n_neighbors=1250,weights='distance',n_jobs=-1)## 使用了兩成的資料量,就花了大量時間訓練模型
    lr = LogisticRegression(C=150,class_weight='balanced',solver='liblinear', random_state=SEED)
    nn = MLPClassifier(solver='lbfgs', activation = 'logistic',early_stopping=False,alpha=1e-3,hidden_layer_sizes=(100,5), random_state=SEED)
    gb = GradientBoostingClassifier(learning_rate=0.01,n_estimators=600,min_samples_split=1000,min_samples_leaf=60,max_depth=10,subsample=0.85,max_features='sqrt',random_state=SEED)
    rf = RandomForestClassifier(min_samples_leaf=30,min_samples_split=120,max_depth=16,n_estimators=400,n_jobs=2,max_features='sqrt',class_weight='balanced',random_state=SEED)

    models = {
              'knn': knn, #分數太過低了,並且消耗時間長
              'xgb':xgbMo,
              'lgm':lgm,
              'mlp-nn': nn,
              'random forest': rf,
              'gbm': gb,
              'logistic': lr
              }

    return models
def train_predict(model_list):
    """
    :parameters: model_list: Dict
    :return: P: pd.DataFrame
    :Purpose: 
    根據提供的基模型字典,遍歷每個模型並進行訓練
    如果是lightgbm或xgboost,切入一些驗證集
    返回每個模型預測結果
    """
    Preds_stacker = np.zeros((y_test.shape[0], len(model_list)))
    Preds_stacker = pd.DataFrame(Preds_stacker)

    print("Fitting models.")
    cols = list()
    for i, (name, m) in enumerate(models.items()):
        print("%s..." % name, end=" ", flush=False)
        if name == 'xgb' or name == 'lgm':
            m.fit(x_train,y_train.values.ravel(),eval_metric='auc')
        else:
            m.fit(x_train, y_train.values.ravel())
        Preds_stacker.iloc[:, i] = m.predict_proba(x_test)[:, 1]
        cols.append(name)
        print("done")

    Preds_stacker.columns = cols
    print("Done.\n")
    return Preds_stacker
def score_models(Preds_stacker, true_preds):
    """
    :parameters: Preds_stacker: pd.DataFrame   true_preds: pd.Series
    :return: None
    :Purpose: 
    遍歷每個模型的預測結果,計算其與真實結果的AUC值
    """
    print("Scoring models.")
    for m in Preds_stacker.columns:
        score = roc_auc_score(true_preds, Preds_stacker.loc[:, m])
        print("%-26s: %.3f" % (m, score))
    print("Done.\n")
    
models = get_models()
Preds = train_predict(models)
score_models(Preds, y_test)

3.3 模型融合

def train_base_learners(base_learners, xTrain, yTrain, verbose=True):
    """
    :parameters: model_list: Dict, xTrain:pd.DataFrame, yTrain:pd.DataFrame
    :return: None
    :Purpose: 
    根據提供的基模型字典,和訓練資料,遍歷每個模型並進行訓練
    """
    if verbose: print("Fitting models.")
    for i, (name, m) in enumerate(base_learners.items()):
        if verbose: print("%s..." % name, end=" ", flush=False)
        if name == 'xgb' or name == 'lgm':
            m.fit(xTrain,yTrain.values.ravel(),eval_metric='auc')
        else:
            m.fit(xTrain, yTrain.values.ravel())
        if verbose: print("done")
 
def predict_base_learners(pred_base_learners, inp, verbose=True):
    """
    :parameters: model_list: Dict, inp
    :return: P:pd.DataFrame
    :Purpose: 
    根據提供的基模型字典,輸出預測結果
    """
    P = np.zeros((inp.shape[0], len(pred_base_learners)))
    if verbose: print("Generating base learner predictions.")
    for i, (name, m) in enumerate(pred_base_learners.items()):
        if verbose: print("%s..." % name, end=" ", flush=False)
        p = m.predict_proba(inp)
        # With two classes, need only predictions for one class
        P[:, i] = p[:, 1]
        if verbose: print("done")
    return P
  
def ensemble_predict(base_learners, meta_learner, inp, verbose=True):
    """
    :parameters: model_list: Dict, meta_learner, inp
    :return: P_pred, P
    :Purpose: 
    根據提供訓練好的基模型字典,還有訓練好的元模型,
    輸出預測值
    """
    P_pred = predict_base_learners(base_learners, inp, verbose=verbose)
    return P_pred, meta_learner.predict_proba(P_pred)[:, 1]
## 1.定義基模型
base_learners = get_models()
## 2.定義元模型(第二層架構)
meta_learner = GradientBoostingClassifier(
    n_estimators=5000,
    loss="exponential",
    max_features=3,
    max_depth=4,
    subsample=0.8,
    learning_rate=0.0025, 
    random_state=SEED
)
 
## 將每個模型的預測結果切分成兩半,一半作為元模型的訓練,另一半作為測試
xtrain_base, xpred_base, ytrain_base, ypred_base = train_test_split(
    x_train, y_train, test_size=0.5, random_state=SEED)
## 3.訓練基模型
train_base_learners(base_learners, xtrain_base, ytrain_base)
## 4.根據訓練好的基模型,輸出每個模型的測試值
P_base = predict_base_learners(base_learners, xpred_base)
## 5.根據剛剛的每個基模型的測試值,訓練元模型!
meta_learner.fit(P_base, ypred_base.values.ravel())
## 6.將元模型進行預測!
P_pred, p = ensemble_predict(base_learners, meta_learner, x_test)
print("\nEnsemble ROC-AUC score: %.3f" % roc_auc_score(y_test, p))