• 2022 CCF BDCI 返乡发展人群预测 [0.9117+]


    一、赛题介绍

    比赛地址:返乡发展人群预测

    1.1 任务介绍

    基于中国联通的大数据能力,通过使用对联通的信令数据、通话数据、互联网行为等数据进行建模,对个人是否会返乡工作进行判断

    1.2 数据简介

    train.csv:包含全量数据集的70%(dataNoLabel是训练集的一部分,选手可以自己决定是否使用)
    test.csv:包含全量数据集的30%
    位置类特特征:基于联通基站产生的用户信令数据;
    互联网类特征:基于联通用户上网产生的上网行为数据;
    通话类特征:基于联通用户日常通话、短信产生的数据

    1.3 评估指标

    使用ROC曲线下面积AUC(Area Under Curve)作为评价指标,AUC越大,预测越准确

    二、解题思路

    这是一个二分类任务,主要是从数据清洗、特征构造和筛选以及模型融合的思路

    • 数据清洗
    • 特征构造和筛选
    • 模型融合

    最终提交结果:

    榜单分数
    A榜0.91171720

    1.1 库导入

    # -*- coding: utf-8 -*-
    import numpy as np
    import pandas as pd
    from sklearn.preprocessing import LabelEncoder
    from sklearn.linear_model import LogisticRegression
    from xgboost import XGBClassifier
    from lightgbm import LGBMClassifier
    from catboost import CatBoostClassifier
    from sklearn.ensemble import GradientBoostingClassifier
    from sklearn.ensemble import HistGradientBoostingClassifier
    from sklearn.ensemble import StackingClassifier
    from sklearn.model_selection import StratifiedKFold
    from sklearn.metrics import roc_auc_score
    from sklearn.model_selection import train_test_split
    import warnings
    from tqdm import tqdm
    warnings.filterwarnings('ignore')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    1.2 数据读取

    train_data = pd.read_csv('data/dataTrain.csv')
    test_data = pd.read_csv('data/dataA.csv')
    submission = pd.read_csv('data/submit_example_A.csv')
    data_nolabel = pd.read_csv('data/dataNoLabel.csv')
    print(f'train_data.shape = {train_data.shape}')
    print(f'test_data.shape  = {test_data.shape}')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    1.3 特征构造

    自己DIY的特征

    train_data['f47'] = train_data['f1'] * 10 + train_data['f2']
    test_data['f47'] = test_data['f1'] * 10 + test_data['f2']
    
    • 1
    • 2

    来自网上的特征(来源:https://github.com/librauee/CCF2022/blob/main/FX/FX_baseline.py)

    # 暴力Feature 位置
    loc_f = ['f1', 'f2', 'f4', 'f5', 'f6']
    for df in [train_data, test_data]:
        for i in range(len(loc_f)):
            for j in range(i + 1, len(loc_f)):
                df[f'{loc_f[i]}+{loc_f[j]}'] = df[loc_f[i]] + df[loc_f[j]]
                df[f'{loc_f[i]}-{loc_f[j]}'] = df[loc_f[i]] - df[loc_f[j]]
                df[f'{loc_f[i]}*{loc_f[j]}'] = df[loc_f[i]] * df[loc_f[j]]
                df[f'{loc_f[i]}/{loc_f[j]}'] = df[loc_f[i]] / (df[loc_f[j]]+1)
    
    # 暴力Feature 通话
    com_f = ['f43', 'f44', 'f45', 'f46']
    for df in [train_data, test_data]:
        for i in range(len(com_f)):
            for j in range(i + 1, len(com_f)):
                df[f'{com_f[i]}+{com_f[j]}'] = df[com_f[i]] + df[com_f[j]]
                df[f'{com_f[i]}-{com_f[j]}'] = df[com_f[i]] - df[com_f[j]]
                df[f'{com_f[i]}*{com_f[j]}'] = df[com_f[i]] * df[com_f[j]]
                df[f'{com_f[i]}/{com_f[j]}'] = df[com_f[i]] / (df[com_f[j]]+1)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    ID类特征数值化

    cat_columns = ['f3']
    data = pd.concat([train_data, test_data])
    
    for col in cat_columns:
        lb = LabelEncoder()
        lb.fit(data[col])
        train_data[col] = lb.transform(train_data[col])
        test_data[col] = lb.transform(test_data[col])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    最后构造出训练集和测试集

    num_columns = [ col for col in train_data.columns if col not in ['id', 'label', 'f3']]
    feature_columns = num_columns + cat_columns
    target = 'label'
    
    train = train_data[feature_columns]
    label = train_data[target]
    test = test_data[feature_columns]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    1.4 模型训练代码

    常用的交叉验证模型框架

    def model_train(model, model_name, kfold=5):
        oof_preds = np.zeros((train.shape[0]))
        test_preds = np.zeros(test.shape[0])
        skf = StratifiedKFold(n_splits=kfold)
        print(f"Model = {model_name}")
        for k, (train_index, test_index) in enumerate(skf.split(train, label)):
            x_train, x_test = train.iloc[train_index, :], train.iloc[test_index, :]
            y_train, y_test = label.iloc[train_index], label.iloc[test_index]
    
            model.fit(x_train,y_train)
    
            y_pred = model.predict_proba(x_test)[:,1]
            oof_preds[test_index] = y_pred.ravel()
            auc = roc_auc_score(y_test,y_pred)
            print("- KFold = %d, val_auc = %.4f" % (k, auc))
            test_fold_preds = model.predict_proba(test)[:, 1]
            test_preds += test_fold_preds.ravel()
        print("Overall Model = %s, AUC = %.4f" % (model_name, roc_auc_score(label, oof_preds)))
        return test_preds / kfold
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    1.5 数据清洗

    在训练数据中存在很大一部分的干扰数据,导致模型训练存在很多噪声数据,所以需要对噪声数据进行清洗

    这里我使用一个简易的模型,将数据切割成60份,对每一份数据单独作为验证集,如果验证集的AUC几乎接近于0.5,则验证集中的数据大多数为干扰数据。

    gbc = GradientBoostingClassifier()
    gbc_test_preds = model_train(gbc, "GradientBoostingClassifier", 60)
    
    • 1
    • 2

    测试结果为

    KFlodAUC
    00.9034
    10.9112
    20.9045
    30.9006
    40.9013
    50.8986
    60.9009
    70.9116
    80.9245
    90.8902
    100.8995
    110.9047
    120.9291
    130.8980
    140.9279
    150.8995
    160.9080
    170.8942
    180.9179
    190.9044
    200.8937
    210.9138
    220.9024
    230.9091
    240.8937
    250.9173
    260.9047
    270.9010
    280.9047
    290.9144
    300.8984
    310.9079
    320.9240
    330.8899
    340.8780
    350.8942
    360.9112
    370.9221
    380.9273
    390.9137
    400.9206
    410.9053
    420.9033
    430.9193
    440.9141
    450.9087
    460.8957
    470.9142
    480.9179
    490.9128
    500.5608
    510.5328
    520.5020
    530.5067
    540.4888
    550.5065
    560.5163
    570.5019
    580.5235
    590.4842

    很明显可以看出,50~59的数据为干扰数据
    所以剔除干扰数据

    train = train[:50000]
    label = label[:50000]
    
    • 1
    • 2

    1.6 模型融合

    选取了

    • GradientBoostingClassifier
    • HistGradientBoostingClassifier
    • XGBClassifier
    • LGBMClassifier
    • CatBoostClassifier
      这6个树模型作为基础模型
    gbc = GradientBoostingClassifier(
        n_estimators=50, 
        learning_rate=0.1,
        max_depth=5
    )
    hgbc = HistGradientBoostingClassifier(
        max_iter=100,
        max_depth=5
    )
    xgbc = XGBClassifier(
        objective='binary:logistic',
        eval_metric='auc',
        n_estimators=100, 
        max_depth=6, 
        learning_rate=0.1
    )
    gbm = LGBMClassifier(
        objective='binary',
        boosting_type='gbdt',
        num_leaves=2 ** 6, 
        max_depth=8,
        colsample_bytree=0.8,
        subsample_freq=1,
        max_bin=255,
        learning_rate=0.05, 
        n_estimators=100, 
        metrics='auc'
    )
    cbc = CatBoostClassifier(
        iterations=210, 
        depth=6, 
        learning_rate=0.03, 
        l2_leaf_reg=1, 
        loss_function='Logloss', 
        verbose=0
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    通过StackingClassifier将6个模型进行Stack,Stack模型用LogisticRegression

    estimators = [
        ('gbc', gbc),
        ('hgbc', hgbc),
        ('xgbc', xgbc),
        ('gbm', gbm),
        ('cbc', cbc)
    ]
    clf = StackingClassifier(
        estimators=estimators, 
        final_estimator=LogisticRegression()
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    1.7 特征筛选

    特征筛选思路:先将模型训练好,然后对验证集进行测试得到基础AUC,之后循环遍历所有特征,在验证集上对单个特征进行mask后,得到mask后的AUC,评估两个AUC的差值,差值越大,则说明特征重要性越高。

    先将训练数据划分成训练集和验证集

    X_train, X_test, y_train, y_test = train_test_split(
        train, label, stratify=label, random_state=2022)
    
    • 1
    • 2

    然后用组合模型进行训练和验证

    clf.fit(X_train, y_train)
    y_pred = clf.predict_proba(X_test)[:, 1]
    auc = roc_auc_score(y_test, y_pred)
    print('auc = %.8f' % auc)
    
    • 1
    • 2
    • 3
    • 4

    循环遍历特征,对验证集中的特征进行mask

    ff = []
    for col in feature_columns:
        x_test = X_test.copy()
        x_test[col] = 0
        auc1 = roc_auc_score(y_test, clf.predict_proba(x_test)[:, 1])
        if auc1 < auc:
            ff.append(col)
        print('%5s | %.8f | %.8f' % (col, auc1, auc1 - auc))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    特征重要性TOP20

    特征AUCdiff
    f460.91137151-0.00207482
    f2/f40.91193439-0.00151194
    f2/f60.9121758-0.00127053
    f4/f50.91224485-0.00120148
    f470.91252212-0.00092421
    f5-f60.91257545-0.00087088
    f4-f50.91269045-0.00075589
    f44/f450.91274186-0.00070447
    f30.9127596-0.00068673
    f190.91277603-0.00067031
    f4-f60.91288475-0.00056158
    f2-f40.91292365-0.00052268
    f44/f460.91296255-0.00048378
    f45/f460.91296478-0.00048156
    f2/f50.91303568-0.00041066
    f5/f60.91307167-0.00037466
    f1/f60.91309919-0.00034714
    f80.91310869-0.00033764
    f1/f50.91315365-0.00029269
    f1-f40.91318568-0.00026066

    这里选取所有差值为负的特征,对比特征筛选后的特征提升

    clf.fit(X_train[ff], y_train)
    y_pred = clf.predict_proba(X_test[ff])[:, 1]
    auc = roc_auc_score(y_test, y_pred)
    print('auc = %.8f' % auc)
    
    • 1
    • 2
    • 3
    • 4
    特征筛选前特征筛选后
    0.913446330.91385538

    1.8 模型训练

    train = train[ff]
    test = test[ff]
    
    clf_test_preds = model_train(clf, "StackingClassifier", 10)
    
    submission['label'] = clf_test_preds
    submission.to_csv('submission.csv', index=False)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    最后线下训练结果为

    KFoldAUC
    00.9069
    10.9091
    20.9156
    30.9059
    40.9075
    50.9083
    60.9007
    70.9173
    80.9160
    90.9146
    overall0.9100

    三、总结

    最后线上A榜的结果为0.91171720

    后续优化思路:

    • 半监督学习, 使用pseudo_labeling
    • 特征工程,使用PolynomialFeatures进行多项式特征组合
  • 相关阅读:
    【Linux】 ps命令使用
    EasyExcel 读取数据为null
    Vue3+Typescript封装Pinia插件,优雅管理所有请求的loading效果
    AppData文件夹下Local,Locallow和Roaming
    【面试】Js面试题(一)
    时序分解 | Matlab实现CEEMDAN完全自适应噪声集合经验模态分解时间序列信号分解
    盘点JAVA中基于CAS实现的原子类, 你知道哪些?
    【微信小程序】父子组件的创建、通信与事件触发;组件生命周期
    MySQL数据库 —— 常用语句
    Debian下Hadoop集群安装
  • 原文地址:https://blog.csdn.net/JinbaoSite/article/details/126876073