1. 程式人生 > >JData資料處理及高潛使用者購買意向預測

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%。