2019移動廣告反欺詐算法挑戰賽baseline

前言:

分享這個baseline之前,首先先感謝一下我的好朋友油菜花一朵給予的一些幫助。然後呢介紹一下最近比賽中碰到的幾個問題,以及解釋。如果有可能的話,明天分享一個94.47左右的baseline吧,初賽之前設置爲粉絲可見,初賽後在設置所有人可見吧。本來想分享47的baseline的,但是後來發現版本找不到了。就把自己的想法融合了一下,也不知道多少分。比賽名次不重要學到東西才重要。

 

第一:爲什麼使用kaggle上的方案效果不好

因爲科大訊飛的移動廣告反欺詐算法挑戰賽和之前kaggle上的‘TalkingData AdTracking Fraud Detection Challenge’比賽題目是一樣的,和kaggle上不同的是科大訊飛增加了一些屬性。所以有很多人都是仿照kaggle上的比賽進行的,但是發現效果不是很好。這個原因是由於kaggle上的廣告欺詐是最後的標籤是由TalkingData公司自己的算法生成的,最後公司也說了他們的算法中加上了時間信息。所以這也是爲什麼幾乎每個開源代碼都統計了很多關於時間的特徵。

 

第二:catboost的baseline抖動特別厲害

訓練集的數據100萬條,測試集僅僅只有10萬條。這可能是由於訓練集和測試集的數據都太少了導致的。所以同樣的模型,相同的隨機種子最後生成的結果差距也是有的。當跑的代碼不是很好的時候,可以嘗試一下再跑一次。

 

第三: 爲什麼數據清洗之後會更差

我們發現最後訓練的模型線上結果好的時候,往往模型中單個屬性是比較強的特徵。所以我想會不會科大訊飛官方的label也是由自己模型生成的,而且訓練的時候沒有對數據做過多的處理。如果我們要數據清洗的話,我們清理之後的屬性一定要比之前的強。

 

第四: 一些強特徵加上關於label的統計之後線下會很好,但是線上提分不是很高

這個我一直很好奇,在我的模型中得到一些強特徵之後我統計一下,我嘗試使用統計這些特徵關於label的均值和方差,最後顯示特徵的重要程度的時候,這些也都是不錯的特徵,但是最後線上提交的時候卻發現增加分數不是很明顯。這個可能是catboost的特徵對於label而言比較敏感,所以可以換成其他的模型例如lgb試一下。

 

第五: 加上時間數據之後線下很好但是線上不好

之前我統計同一個model前一次點擊時間,和之後的點擊時間差,但是同樣是線下有提升,但是線上效果不好,我之前是以爲數據泄露,但是按照網上的一些教程修改之後效果依舊存在同樣的問題。暫時沒有想到解釋的方案。

 

第六:介紹一下我這個baseline的特點吧

1、基本架構使用的是catboost的baseline

2、增加了一些關於強特徵關於label的統計特徵 例如均值 方差 出現次數

3、使用make的均值填充 h, w, ppi

4、對於一些強特徵進行組合,統計組合特徵的出現次數count(),以及累積計數cumcount()

5、爲了節約內存,優化訓練過程,刪除一些不必要的,輸出信息

6、刪除了catboost模型中一些不必要的特徵

7、使用相同的特徵,但是加上xgb, lgb, catboost進行stacking, 最後使用logist進行迴歸分析

 

代碼:

# -*- coding: utf-8 -*-
# @Time    : 2019/8/18 9:28
# @Author  : YYLin
# @Email   : [email protected]
# @File    : A_Simple_Stacking_Model.py
# 特徵部分選擇使用之前簡單的特徵 加上lgb catboost xgb進行stacking操作 分數大約46

import numpy as np
import pandas as pd
import gc
from tqdm import tqdm
from sklearn.model_selection import KFold
from sklearn.preprocessing import LabelEncoder
from datetime import timedelta
import catboost as cbt
import lightgbm as lgb
import xgboost as xgb
from sklearn.metrics import f1_score
from sklearn.linear_model import LogisticRegression
import warnings
warnings.filterwarnings('ignore')
from scipy import stats


test = pd.read_table("../A_Data/testdata.txt", nrows=10000)
train = pd.read_table("../A_Data/traindata.txt", nrows=10000)
all_data = pd.concat([train, test], ignore_index=True)

all_data['time'] = pd.to_datetime(all_data['nginxtime'] * 1e+6) + timedelta(hours=8)  # 轉換爲北京時間24小時格式
all_data['day'] = all_data['time'].dt.dayofyear  # 今年的第幾天
all_data['hour'] = all_data['time'].dt.hour  # 每天的第幾小時

all_data['model'].replace('PACM00', "OPPO R15", inplace=True)  #
all_data['model'].replace('PBAM00', "OPPO A5", inplace=True)
all_data['model'].replace('PBEM00', "OPPO R17", inplace=True)
all_data['model'].replace('PADM00', "OPPO A3", inplace=True)
all_data['model'].replace('PBBM00', "OPPO A7", inplace=True)
all_data['model'].replace('PAAM00', "OPPO R15_1", inplace=True)
all_data['model'].replace('PACT00', "OPPO R15_2", inplace=True)
all_data['model'].replace('PABT00', "OPPO A5_1", inplace=True)
all_data['model'].replace('PBCM10', "OPPO R15x", inplace=True)

# for fea in ['model', 'make', 'lan', 'new_make', 'new_model']:
for fea in ['model', 'make', 'lan']:
    all_data[fea] = all_data[fea].astype('str')
    all_data[fea] = all_data[fea].map(lambda x: x.upper())  # .upper()字符轉大寫

    from urllib.parse import unquote


    def url_clean(x):
        x = unquote(x, 'utf-8').replace('%2B', ' ').replace('%20', ' ').replace('%2F', '/').replace('%3F', '?').replace(
            '%25', '%').replace('%23', '#').replace(".", ' ').replace('??', ' '). \
            replace('%26', ' ').replace("%3D", '=').replace('%22', '').replace('_', ' ').replace('+', ' ').replace('-',
                                                                                                                   ' ').replace(
            '__', ' ').replace('  ', ' ').replace(',', ' ')

        if (x[0] == 'V') & (x[-1] == 'A'):
            return "VIVO {}".format(x)
        elif (x[0] == 'P') & (x[-1] == '0'):
            return "OPPO {}".format(x)
        elif (len(x) == 5) & (x[0] == 'O'):
            return "Smartisan {}".format(x)
        elif ('AL00' in x):
            return "HW {}".format(x)
        else:
            return x


    all_data[fea] = all_data[fea].map(url_clean)

all_data['big_model'] = all_data['model'].map(lambda x: x.split(' ')[0])
all_data['model_equal_make'] = (all_data['big_model'] == all_data['make']).astype(int)

# 處理 ntt 的數據特徵 但是不刪除之前的特徵 將其歸爲新的一列數據
all_data['new_ntt'] = all_data['ntt']
all_data.new_ntt[(all_data.new_ntt == 0) | (all_data.new_ntt == 7)] = 0
all_data.new_ntt[(all_data.new_ntt == 1) | (all_data.new_ntt == 2)] = 1
all_data.new_ntt[all_data.new_ntt == 3] = 2
all_data.new_ntt[(all_data.new_ntt >= 4) & (all_data.new_ntt <= 6)] = 3

# 使用make填充 h w ppi值爲0.0的數據
all_data['h'].replace(0.0, np.nan, inplace=True)
all_data['w'].replace(0.0, np.nan, inplace=True)
# all_data['ppi'].replace(0.0, np.nan, inplace=True)
# cols = ['h', 'w', 'ppi']
cols = ['h', 'w']
gp_col = 'make'
for col in tqdm(cols):
    na_series = all_data[col].isna()
    names = list(all_data.loc[na_series, gp_col])
    # 使用均值 或者衆數進行填充缺失值
    # df_fill = all_data.groupby(gp_col)[col].mean()
    df_fill = all_data.groupby(gp_col)[col].agg(lambda x: stats.mode(x)[0][0])
    t = df_fill.loc[names]
    t.index = all_data.loc[na_series, col].index
    # 相同的index進行賦值
    all_data.loc[na_series, col] = t
    all_data[col].fillna(0.0, inplace=True)
    del df_fill
    gc.collect()

# H, W, PPI
all_data['size'] = (np.sqrt(all_data['h'] ** 2 + all_data['w'] ** 2) / 2.54) / 1000
all_data['ratio'] = all_data['h'] / all_data['w']
all_data['px'] = all_data['ppi'] * all_data['size']
all_data['mj'] = all_data['h'] * all_data['w']

# 強特徵進行組合
Fusion_attributes = ['make_adunitshowid', 'adunitshowid_model', 'adunitshowid_ratio', 'make_model',
                     'make_osv', 'make_ratio', 'model_osv', 'model_ratio', 'model_h', 'ratio_osv']

for attribute in tqdm(Fusion_attributes):
    name = "Fusion_attr_" + attribute
    dummy = 'label'
    cols = attribute.split("_")
    cols_with_dummy = cols.copy()
    cols_with_dummy.append(dummy)
    gp = all_data[cols_with_dummy].groupby(by=cols)[[dummy]].count().reset_index().rename(index=str,
                                                                                          columns={dummy: name})
    all_data = all_data.merge(gp, on=cols, how='left')

# 對ip地址和reqrealip地址進行分割 定義一個machine的關鍵字
all_data['ip2'] = all_data['ip'].apply(lambda x: '.'.join(x.split('.')[0:2]))
all_data['ip3'] = all_data['ip'].apply(lambda x: '.'.join(x.split('.')[0:3]))
all_data['reqrealip2'] = all_data['reqrealip'].apply(lambda x: '.'.join(x.split('.')[0:2]))
all_data['reqrealip3'] = all_data['reqrealip'].apply(lambda x: '.'.join(x.split('.')[0:3]))
all_data['machine'] = 1000 * all_data['model'] + all_data['make']

var_mean_attributes = ['adunitshowid', 'make', 'model', 'ver']
for attr in tqdm(var_mean_attributes):
    # 統計關於ratio的方差和均值特徵
    var_label = 'ratio'
    var_name = 'var_' + attr + '_' + var_label
    gp = all_data[[attr, var_label]].groupby(attr)[var_label].var().reset_index().rename(index=str,
                                                                                         columns={var_label: var_name})
    all_data = all_data.merge(gp, on=attr, how='left')
    all_data[var_name] = all_data[var_name].fillna(0).astype(int)

    mean_label = 'ratio'
    mean_name = 'mean_' + attr + '_' + mean_label
    gp = all_data[[attr, mean_label]].groupby(attr)[mean_label].mean().reset_index().rename(index=str, columns={
        mean_label: mean_name})
    all_data = all_data.merge(gp, on=attr, how='left')
    all_data[mean_name] = all_data[mean_name].fillna(0).astype(int)

    # 統計關於h的方差和均值特徵
    var_label = 'h'
    var_name = 'var_' + attr + '_' + var_label
    gp = all_data[[attr, var_label]].groupby(attr)[var_label].var().reset_index().rename(index=str,
                                                                                         columns={var_label: var_name})
    all_data = all_data.merge(gp, on=attr, how='left')
    all_data[var_name] = all_data[var_name].fillna(0).astype(int)

    mean_label = 'h'
    mean_name = 'mean_' + attr + '_' + mean_label
    gp = all_data[[attr, mean_label]].groupby(attr)[mean_label].mean().reset_index().rename(index=str, columns={
        mean_label: mean_name})
    all_data = all_data.merge(gp, on=attr, how='left')
    all_data[mean_name] = all_data[mean_name].fillna(0).astype(int)

    # 統計關於h的方差和均值特徵
    var_label = 'w'
    var_name = 'var_' + attr + '_' + var_label
    gp = all_data[[attr, var_label]].groupby(attr)[var_label].var().reset_index().rename(index=str,
                                                                                         columns={var_label: var_name})
    all_data = all_data.merge(gp, on=attr, how='left')
    all_data[var_name] = all_data[var_name].fillna(0).astype(int)

    mean_label = 'w'
    mean_name = 'mean_' + attr + '_' + mean_label
    gp = all_data[[attr, mean_label]].groupby(attr)[mean_label].mean().reset_index().rename(index=str, columns={
        mean_label: mean_name})
    all_data = all_data.merge(gp, on=attr, how='left')
    all_data[mean_name] = all_data[mean_name].fillna(0).astype(int)

    del gp
    gc.collect()

cat_col = [i for i in all_data.select_dtypes(object).columns if i not in ['sid', 'label']]
for i in tqdm(cat_col):
    lbl = LabelEncoder()
    all_data['count_' + i] = all_data.groupby([i])[i].transform('count')
    all_data[i] = lbl.fit_transform(all_data[i].astype(str))

for i in tqdm(['h', 'w', 'ppi', 'ratio']):
    all_data['{}_count'.format(i)] = all_data.groupby(['{}'.format(i)])['sid'].transform('count')

feature_name = [i for i in all_data.columns if i not in ['sid', 'label', 'time']]
print(feature_name)
print('all_data.info:', all_data.info())

# cat_list = ['pkgname', 'ver', 'adunitshowid', 'mediashowid', 'apptype', 'ip', 'city', 'province', 'reqrealip',
#             'adidmd5',
#             'imeimd5', 'idfamd5', 'openudidmd5', 'macmd5', 'dvctype', 'model', 'make', 'ntt', 'carrier', 'os', 'osv',
#             'orientation', 'lan', 'h', 'w', 'ppi', 'ip2', 'new_make', 'new_model', 'country', 'new_province',
#             'new_city',
#             'ip3', 'reqrealip2', 'reqrealip3']
cat_list = ['pkgname', 'ver', 'adunitshowid', 'mediashowid', 'apptype', 'ip', 'city', 'province', 'reqrealip',
            'adidmd5',
            'imeimd5', 'idfamd5', 'openudidmd5', 'macmd5', 'dvctype', 'model', 'make', 'ntt', 'carrier', 'os', 'osv',
            'orientation', 'lan', 'h', 'w', 'ppi', 'ip2',
            'ip3', 'reqrealip2', 'reqrealip3']

tr_index = ~all_data['label'].isnull()
X_train = all_data[tr_index][list(set(feature_name))].reset_index(drop=True)
y = all_data[tr_index]['label'].reset_index(drop=True).astype(int)
X_test = all_data[~tr_index][list(set(feature_name))].reset_index(drop=True)
print(X_train.shape, X_test.shape)
# 節約一下內存
del all_data
gc.collect()


# 以下代碼是5折交叉驗證的結果 + lgb catboost xgb 最後使用logist進行迴歸預測
def get_stacking(clf, x_train, y_train, x_test, feature_name, n_folds=5):
    print('len_x_train:', len(x_train))

    train_num, test_num = x_train.shape[0], x_test.shape[0]
    second_level_train_set = np.zeros((train_num,))
    second_level_test_set = np.zeros((test_num,))
    test_nfolds_sets = np.zeros((test_num, n_folds))
    kf = KFold(n_splits=n_folds)

    for i, (train_index, test_index) in enumerate(kf.split(x_train)):
        x_tra, y_tra = x_train[feature_name].iloc[train_index], y_train[train_index]
        x_tst, y_tst = x_train[feature_name].iloc[test_index], y_train[test_index]

        clf.fit(x_tra[feature_name], y_tra, eval_set=[(x_tst[feature_name], y_tst)])

        second_level_train_set[test_index] = clf.predict(x_tst[feature_name])
        test_nfolds_sets[:, i] = clf.predict(x_test[feature_name])

    second_level_test_set[:] = test_nfolds_sets.mean(axis=1)
    return second_level_train_set, second_level_test_set


def lgb_f1(labels, preds):
    score = f1_score(labels, np.round(preds))
    return 'f1', score, True


lgb_model = lgb.LGBMClassifier(random_seed=2019, n_jobs=-1, objective='binary', learning_rate=0.05, n_estimators=3000,
                               num_leaves=31, max_depth=-1, min_child_samples=50, min_child_weight=9, subsample_freq=1,
                               subsample=0.7, colsample_bytree=0.7, reg_alpha=1, reg_lambda=5,
                               early_stopping_rounds=400)


xgb_model = xgb.XGBClassifier(objective='binary:logistic', eval_metric='auc', learning_rate=0.02, max_depth=6,
                              early_stopping_rounds=400, feval=lgb_f1)


cbt_model = cbt.CatBoostClassifier(iterations=3000, learning_rate=0.05, max_depth=11, l2_leaf_reg=1, verbose=10,
                                   early_stopping_rounds=400, task_type='GPU', eval_metric='F1')

train_sets = []
test_sets = []
for clf in [xgb_model, cbt_model, lgb_model]:
    print('begin train clf:', clf)
    train_set, test_set = get_stacking(clf, X_train, y, X_test, feature_name)
    train_sets.append(train_set)
    test_sets.append(test_set)

meta_train = np.concatenate([result_set.reshape(-1, 1) for result_set in train_sets], axis=1)
meta_test = np.concatenate([y_test_set.reshape(-1, 1) for y_test_set in test_sets], axis=1)

# 使用邏輯迴歸作爲第二層模型
bclf = LogisticRegression()
bclf.fit(meta_train, y)
test_pred = bclf.predict_proba(meta_test)[:, 1]

# 提交結果
submit = test[['sid']]
submit['label'] = (test_pred >= 0.5).astype(int)
print(submit['label'].value_counts())
submit.to_csv("A_Simple_Stacking_Model.csv", index=False)

# 打印預測地概率 方便以後使用融合模型
df_sub = pd.concat([test['sid'], pd.Series(test_pred)], axis=1)
df_sub.columns = ['sid', 'label']
df_sub.to_csv('A_Simple_Stacking_Model_proba.csv', sep=',', index=False)

 

XGB輸出結果:

 

lgb輸出的結果:

 

 

 catboost輸出的結果:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章