JData資料處理及高潛使用者購買意向預測
競賽概述:
本次大賽以京東商城真實的使用者、商品和行為資料(脫敏後)為基礎,參賽隊伍需要通過資料探勘的技術和機器學習的演算法,構建使用者購買商品的預測模型,輸出高潛使用者和目標商品的匹配結果,為精準營銷提供高質量的目標群體。同時,希望參賽隊伍能通過本次比賽,挖掘資料背後潛在的意義,為電商使用者提供更簡單、快捷、省心的購物體驗。
資料介紹:
符號定義:
S:提供的商品全集;
P:候選的商品子集(JData_Product.csv),P是S的子集;
U:使用者集合;
A:使用者對S的行為資料集合;
C:S的評價資料。
訓練資料部分:
提供2016-02-01到2016-04-15日使用者集合U中的使用者,對商品集合S中部分商品的行為、評價、使用者資料;提供部分候選商品的資料P。 選手從資料中自行組成特徵和資料格式,自由組合訓練測試資料比例。
預測資料部分:
2016-04-16到2016-04-20使用者是否下單P中的商品,每個使用者只會下單一個商品;抽取部分下單使用者資料,A榜使用50%的測試資料來計算分數;B榜使用另外50%的資料計算分數(計算準確率時剔除使用者提交結果中user_Id與A榜的交集部分)。
1、使用者資料
user_id | 使用者ID | 脫敏 |
age | 年齡段 | -1表示為知 |
sex | 性別 | 0表示男 1表示女 2表示保密 |
user_lv_cd | 使用者等級 | 有順序的級別列舉,越高級別數字越大 |
user_rg_tm | 使用者註冊日期 | 粒度到天 |
2、商品資料
sku_id | 商品編號 | 脫敏 |
a1 | 屬性1 | 列舉 ,-1表示為知 |
a2 | 屬性2 | 列舉 ,-1表示為知 |
a3 | 屬性3 | 列舉 ,-1表示為知 |
cate | 品類ID | 脫敏 |
brand | 品牌ID | 脫敏 |
3、評價資料
dt | 截止到時間 | 粒度到天 |
sku_id | 商品編號 | 脫敏 |
comment_num | 累計評論數分段 |
0表示無評論,1表示有一條評論 2表示有2-10條評論 3表示有11-50條評論 4表示大於50條評論 |
has_bad_comment | 是否有差評 | 0表示無 ,1表示有 |
bad_comment_rate | 差評率 | 查評論佔總評論數的比重 |
4、行為資料
user_id | 使用者編號 | 脫敏 |
sku_id | 商品編號 | 脫敏 |
time | 行為時間 | |
model_id | 點選模組編號,如果是點選 | 脫敏 |
type |
1.瀏覽(值瀏覽商品詳情頁); 2.加入購物車; 3.購物車刪除; 4.下單; 5.關注; 6.點選 |
|
cate | 品類ID | 脫敏 |
brand | 品牌ID | 脫敏 |
任務描述:
參賽者需要使用京東多個品類下商品的歷史銷售資料,構建演算法模型,預測使用者在未來5天內,對某個目標品類下商品的購買意向。對於訓練集中出現的每一個使用者,參賽者的模型需要預測該使用者在未來5天內是否購買目標品類下的商品以及所購買商品的SKU_ID。評測演算法將針對參賽者提交的預測結果,計算加權得分。
評分標準:
參賽者提交的結果檔案中包含對所有使用者購買意向的預測結果。對每一個使用者的預測結果包括兩方面:
1、該使用者2016-04-16到2016-04-20是否下單P中的商品,提交的結果檔案中僅包含預測為下單的使用者,預測為未下單的使用者,無須在結果中出現。若預測正確,則評測演算法中置label=1,不正確label=0;
2、如果下單,下單的sku_id (只需提交一個sku_id),若sku_id預測正確,則評測演算法中置pred=1,不正確pred=0。 對於參賽者提交的結果檔案,按如下公式計算得分:
Score=0.4*F11 + 0.6*F12
此處的F1值定義為:
F11=6*Recall*Precise/(5*Recall+Precise)
F12=5*Recall*Precise/(2*Recall+3*Precise)
其中,Precise為準確率,Recall為召回率.
F11是label=1或0的F1值,F12是pred=1或0的F1值.
資料清洗:
比賽的題目是高潛使用者的購買意向的預測,從機器學習的角度來講,可以認為這是一個二分類的任務。那麼就是要構建正負樣本. 由於拿到的是原始資料,裡面存在很多噪聲,因而第一步先要對資料清洗,
比如說: 去掉只有購買記錄的使用者(沒有可用的歷史瀏覽等記錄來預測使用者將來的購買意向)
去掉瀏覽量很大而購買量很少的使用者(惰性使用者或爬蟲使用者)
去掉最後5(7)天沒有記錄(互動)的商品和使用者
......
為了能夠進行上述清洗,在此首先構造了簡單的使用者(user)行為特徵和商品(item)行為行為特徵,對應於兩張表user_table和item_table
user_table特徵包括:
user_id(使用者id),age(年齡),sex(性別),
user_lv_cd(使用者級別),browse_num(瀏覽數),
addcart_num(加購數),delcart_num(刪購數),
buy_num(購買數),favor_num(收藏數),
click_num(點選數),buy_addcart_ratio(購買加購轉化率), buy_browse_ratio(購買瀏覽轉化率),
buy_click_ratio(購買點選轉化率), buy_favor_ratio(購買收藏轉化率)
item_table特徵包括:
sku_id(商品id),attr1,attr2,
attr3,cate,brand,browse_num,
addcart_num,delcart_num,
buy_num,favor_num,click_num,
buy_addcart_ratio,buy_browse_ratio,
buy_click_ratio,buy_favor_ratio,
comment_num(評論數),
has_bad_comment(是否有差評),
bad_comment_rate(差評率)
探索高潛使用者的行為:
比賽的題目是高潛使用者購買意向預測, 那麼理解清楚什麼是高潛使用者對於資料分析,特徵抽取,以及之後的建立模型有著至關重要的作用. 簡單來講,作為訓練集的高潛使用者應該具有以下特徵:
必須有購買行為
對一個商品購買和其他互動行為(瀏覽,點選,收藏等)時間差應該多於一天
因為根據賽題,我們需要預測未來5天的購買情況,那麼如果使用者對某商品在同一天完成所有的互動行為(包括購買),
無法從這種交易中指導未來的預測.
特徵工程:
使用者相關特徵:
主要根據使用者資料集,對使用者原本的年齡、性別、使用者等級,採用獨熱編碼。
def convert_age(age_str):
if age_str == u'-1':
return 0
elif age_str == u'15歲以下':
return 1
elif age_str == u'16-25歲':
return 2
elif age_str == u'26-35歲':
return 3
elif age_str == u'36-45歲':
return 4
elif age_str == u'46-55歲':
return 5
elif age_str == u'56歲以上':
return 6
else:
return -1
user = pd.read_csv(user_path, encoding='gbk')
user['age'] = user['age'].map(convert_age)
age_df = pd.get_dummies(user["age"], prefix="age")
sex_df = pd.get_dummies(user["sex"], prefix="sex")
user_lv_df = pd.get_dummies(user["user_lv_cd"], prefix="user_lv_cd")
user = pd.concat([user['user_id'], age_df, sex_df, user_lv_df], axis=1)
商品相關特徵:
根據商品資料集和評論資料集,對商品屬性特徵a1、a2、a3,和評論數量comment_num,進行獨熱編碼
product = pd.read_csv(product_path)
attr1_df = pd.get_dummies(product["a1"], prefix="a1")
attr2_df = pd.get_dummies(product["a2"], prefix="a2")
attr3_df = pd.get_dummies(product["a3"], prefix="a3")
product = pd.concat([product[['sku_id', 'cate', 'brand']], attr1_df, attr2_df, attr3_df], axis=1)
comments = comments[(comments.dt >= comment_date_begin) & (comments.dt < comment_date_end)]
df = pd.get_dummies(comments['comment_num'], prefix='comment_num')
comments = pd.concat([comments, df], axis=1) # type: pd.DataFrame
comments = comments[['sku_id', 'has_bad_comment', 'bad_comment_rate', 'comment_num_1', 'comment_num_2', 'comment_num_3', 'comment_num_4']]
提取商品在某段時間內的瀏覽購買轉化率,加入購物車購買轉化率,收藏購買轉化率,點選購買轉化率,可展現該商品在近期的人氣與熱門程度,方便預測該商品是否為使用者可能購買的高潛商品。
actions = get_actions(start_date, end_date)
df = pd.get_dummies(actions['type'], prefix='action')
actions = pd.concat([actions['sku_id'], df], axis=1)
actions = actions.groupby(['sku_id'], as_index=False).sum()
actions['product_action_1_ratio'] = actions['action_4'] / actions['action_1']
actions['product_action_2_ratio'] = actions['action_4'] / actions['action_2']
actions['product_action_3_ratio'] = actions['action_4'] / actions['action_3']
actions['product_action_5_ratio'] = actions['action_4'] / actions['action_5']
actions['product_action_6_ratio'] = actions['action_4'] / actions['action_6']
使用者行為相關特徵:
提取從起始日期start_date到截止日期end_date內的行為資料集,對使用者行為型別type做one-hot編碼,然後使用聚合函式groupby,對user_id,sku_id進行聚合分組,對組內的其他特徵進行相加,即可統計出在此期間使用者對商品的各種行為型別的累計互動次數。
這個函式方便後面採用劃窗方式,多次提取不同時間段的使用者行為累計特徵。
actions = actions[(actions.time >= start_date) & (actions.time < end_date)]
actions = actions[['user_id', 'sku_id', 'type']]
df = pd.get_dummies(actions['type'], prefix='%s-%s-action' % (start_date, end_date))
actions = pd.concat([actions, df], axis=1) # type: pd.DataFrame
actions = actions.groupby(['user_id', 'sku_id'], as_index=False).sum()
提取按時間衰減的累計行為特徵,使用匿名函式lambda提取出每條行為資料發生時間與截止日期的相隔天數,然後對相隔天數取反,作為對數函式的指數。這樣若發生互動行為的資料離要預測的日期離得越遠,那麼它對預測所佔的權重指數就越小。
actions = actions[(actions.time >= start_date) & (actions.time < end_date)]
df = pd.get_dummies(actions['type'], prefix='action')
actions = pd.concat([actions, df], axis=1) # type: pd.DataFrame
#近期行為按時間衰減
actions['weights'] = actions['time'].map(lambda x: datetime.strptime(end_date, '%Y-%m-%d') - datetime.strptime(x, '%Y-%m-%d %H:%M:%S'))
#actions['weights'] = time.strptime(end_date, '%Y-%m-%d') - actions['datetime']
actions['weights'] = actions['weights'].map(lambda x: math.exp(-x.days))
actions['action_1'] = actions['action_1'] * actions['weights']
actions['action_2'] = actions['action_2'] * actions['weights']
actions['action_3'] = actions['action_3'] * actions['weights']
actions['action_4'] = actions['action_4'] * actions['weights']
actions['action_5'] = actions['action_5'] * actions['weights']
actions['action_6'] = actions['action_6'] * actions['weights']
actions = actions.groupby(['user_id', 'sku_id', 'cate', 'brand'], as_index=False).sum()
通過提取使用者的點選購買轉化率,加入購物車後購買轉化率,以及瀏覽購買轉換率,可以更深刻的刻畫該使用者是否是要尋找的高潛購買使用者物件。
actions = get_actions(start_date, end_date)
df = pd.get_dummies(actions['type'], prefix='action')
actions = pd.concat([actions['user_id'], df], axis=1)
actions = actions.groupby(['user_id'], as_index=False).sum()
actions['user_action_1_ratio'] = actions['action_4'] / actions['action_1']
actions['user_action_2_ratio'] = actions['action_4'] / actions['action_2']
actions['user_action_3_ratio'] = actions['action_4'] / actions['action_3']
actions['user_action_5_ratio'] = actions['action_4'] / actions['action_5']
actions['user_action_6_ratio'] = actions['action_4'] / actions['action_6']
完成了特徵工程部分,接下來主要就是從特徵工程生成的許多特徵中選出有用的特徵,然後對模型引數進行調優。 使用xgboost模型對使用者模型進行建模,然後由於xgboost是基於樹模型的分類器,那麼在建樹的過程中也就自動完成了對特徵的選擇。
模型調優:
我們使用xgboost自帶的交叉驗證函式,先大約確定最優迭代次數。
param = {'eta' : 0.1, 'max_depth': 3, 'seed':27,
'min_child_weight': 1, 'gamma': 0, 'subsample': 0.8, 'colsample_bytree': 0.8,
'scale_pos_weight': 1, 'objective': 'binary:logistic','eval_metric':'auc'}
bst=xgb.cv( param, dtrain, 500,nfold=5,early_stopping_rounds=100)
bst
然後再使用sklearn的格子搜尋,類似以下程式碼,然後更改param_grid的值,即可確定每個引數的最優值。
param_test1 = {
'max_depth':[1,3,5,7]
}
gsearch1 = GridSearchCV(estimator = XGBClassifier( learning_rate =0.1, n_estimators=178, max_depth=5,
min_child_weight=1, gamma=0, subsample=0.8, colsample_bytree=0.8,
objective= 'binary:logistic', nthread=4, scale_pos_weight=1, seed=27),
param_grid = param_test1, scoring='roc_auc', n_jobs=4, iid=False, cv=StratifiedKFold(training_data.label,n_folds=5))
gsearch1.fit(dtrain_x,training_data.label)
gsearch1.grid_scores_, gsearch1.best_params_, gsearch1.best_score_
當確定好所有超引數時,用這些引數作為xgboost模型的引數,然後訓練出預測模型,此時可以呼叫xgboost自帶的得分函式檢視各個特徵的重要性,方便確定哪些特徵比較重要,哪些特徵被選做分裂節點的次數少。然後可以試著重新選擇特徵集,不斷重複上面的調優步驟,直到結果比較符合期望。
feature_score = bst.get_score()
feature_score = sorted(feature_score.items(), key=lambda x:x[1],reverse=True)
feature_score
程式設計過程中遇到的其他問題和解決方法:
1、MemoryError
因為資料集總共有5個多G,最初沒做記憶體方面的考慮,執行程式碼出現了“MemoryError”的問題,用以下方法成功解決: 資料量非常大時,比如一份銀行一個月的流水賬單,可能有高達幾千萬的record。對於一般效能的計算機,有或者是讀入到特殊的資料結構中,記憶體的儲存可能就非常吃力了。考慮到使用資料的實際情況,並不需要將所有的資料提取出記憶體。當然讀入資料庫是件比較明智的做法。若不用資料庫呢?可將大檔案拆分成小塊按塊讀入後,這樣可減少記憶體的儲存與計算資源。Python裡面可用chunker做分塊處理。
簡易使用方法:
chunker = pd.read_csv(PATH_LOAD, chunksize = CHUNK_SIZE)
讀取需要的列:
columns = ("date_time", "user_id")
chunks_train = pd.read_csv(filename, usecols = columns, chunksize = 100000)
分塊讀取chunk中的每一行:
for rawPiece in chunker_rawData:
current_chunk_size = len(rawPiece.index) #rawPiece 是dataframe
for i in range(current_chunk_size ):
timeFlag = timeShape(rawPiece.ix[i]) #獲取第i行的資料
2、特徵變數的表示 特徵變數的選擇和提取對於最後的目標函式(預測)非常關鍵,一開始用時間衰減來做效果很差,改用統計方法,把數值特徵(如年齡、評論數等)表示成啞變數,並處理成onehot編碼,預測結果準確率提升了20%。