【华为AI比赛】广告信息流跨域CTR预估(持续更新)

学习总结

  • 暂时只是简单提交baseline,提交catboost后的线上成绩为0.71左右,持续更新本贴。

一、赛题解读

1.1 CTR赛题

广告推荐主要基于用户对广告的历史曝光、点击等行为进行建模,如果只是使用广告域数据,用户行为数据稀疏,行为类型相对单一。而引入同一媒体的跨域数据,可以获得同一广告用户在其他域的行为数据,深度挖掘用户兴趣,丰富用户行为特征。引入其他媒体的广告用户行为数据,也能丰富用户和广告特征。

赛题任务:本赛题基于:

  • 广告日志数据
  • 用户基本信息
  • 跨域数据

目标:优化广告ctr预估准确率。
目标域为广告域,源域为信息流推荐域,通过获取用户在信息流域中曝光、点击信息流等行为数据,进行用户兴趣建模,帮助广告域ctr的精准预估。

在这里插入图片描述

1.2 比赛评估指标

使用GAUC和AUC的加权求和作为评估指标。xAUC越高,代表结果越优,排名越靠前。
x A U C = α ∗ G A U C + β ∗ A U C \mathrm{xAUC}=\alpha^* \mathrm{GAUC}+\beta^* \mathrm{AUC} xAUC=αGAUC+βAUC
其中AUC为全体样本的AUC统计,GAUC为分组AUC的加权求和,以用户为维度分组,组权值为分组内曝光量 / 总曝光:  GAUC  = ∑ k = i n  AUC  i ∗ ∗  Impression  i ∑ k = i n  Impression  i \text { GAUC }=\frac{\sum_{k=i}^n \text { AUC }_{i *} * \text { Impression }_i}{\sum_{k=i}^n \text { Impression }_i}  GAUC =k=in Impression ik=in AUC i Impression i
初赛: α \alpha α 0.7 , β 0.7, \beta 0.7,β 0.3 0.3 0.3

1.3 比赛数据

数据下载:https://xj15uxcopw.feishu.cn/docx/doxcnufyNTvUfpU57sRyydgyK6c

本赛题提供 7 天数据用于训练,1 天数据用于测试,数据包括目标域(广告域)用
户行为日志,用户基本信息,广告素材信息,源域(信息流域)用户行为数据,源域(信
息流域)物品基本信息等。

希望选手基于给出的数据,识别并生成源域能反映用户兴趣,并能应用于目标域的用户行为特征表示,基于用户行为序列信息,进行源域和目标域的联合建模,预测用户在广告域的点击率。

(1)目标域用户行为数据

在这里插入图片描述
在这里插入图片描述

(2)源域用户行为数据

在这里插入图片描述
在这里插入图片描述

二、baseline通关模型

2.1 Logistic baseline

需要内存:1G;时间:5min。

#安装相关依赖库 如果是windows系统,cmd命令框中输入pip安装,参考上述环境配置
#!pip install sklearn
#!pip install pandas

#---------------------------------------------------
#导入库
import pandas as pd

#----------------数据探索----------------
# 只使用目标域用户行为数据
train_ads = pd.read_csv('./train/train_data_ads.csv',
    usecols=['log_id', 'label', 'user_id', 'age', 'gender', 'residence', 'device_name',
            'device_size', 'net_type', 'task_id', 'adv_id', 'creat_type_cd'])

test_ads = pd.read_csv('./test/test_data_ads.csv',
    usecols=['log_id', 'user_id', 'age', 'gender', 'residence', 'device_name',
    'device_size', 'net_type', 'task_id', 'adv_id', 'creat_type_cd'])
    
# 数据集采样  
train_ads = pd.concat([
    train_ads[train_ads['label'] == 0].sample(70000),
    train_ads[train_ads['label'] == 1].sample(10000),
])


#----------------模型训练----------------
# 加载训练逻辑回归模型
from sklearn.linear_model import LogisticRegression
clf = LogisticRegression()
clf.fit(
    train_ads.drop(['log_id', 'label', 'user_id'], axis=1),
    train_ads['label']
)


#----------------结果输出----------------
# 模型预测与生成结果文件
test_ads['pctr'] = clf.predict_proba(
    test_ads.drop(['log_id', 'user_id'], axis=1),
)[:, 1]
test_ads[['log_id', 'pctr']].to_csv('submission.csv',index=None)

2.2 Catboost算法

  • Catboost自动采用特殊的方式处理类别型特征(categorical features)。首先对categorical features做一些统计,计算某个类别特征(category)出现的频率,之后加上超参数,生成新的数值型特征(numerical features)。这也是这个算法最大的motivtion,有了catboost,再也不用手动处理类别型特征了
  • catboost还使用了组合类别特征,可以利用到特征之间的联系,这极大的丰富了特征维度。
  • 采用ordered boost的方法避免梯度估计的偏差,进而解决预测偏移的问题。
  • catboost的基模型采用的是对称树,同时计算leaf-value方式和传统的boosting算法也不一样,传统的boosting算法计算的是平均数,而catboost在这方面做了优化采用了其他的算法,这些改进都能防止模型过拟合。

(1)导入模块和数据预处理

#安装相关依赖库 如果是windows系统,cmd命令框中输入pip安装,参考上述环境配置
#!pip install sklearn
#!pip install pandas
#!pip install catboost
#---------------------------------------------------
#导入库
#----------------数据探索----------------
import pandas as pd
import numpy as np
import os
import gc
import matplotlib.pyplot as plt
from tqdm import *
#----------------核心模型----------------
from catboost import CatBoostClassifier
from sklearn.linear_model import SGDRegressor, LinearRegression, Ridge
#----------------交叉验证----------------
from sklearn.model_selection import StratifiedKFold, KFold
#----------------评估指标----------------
from sklearn.metrics import accuracy_score, f1_score, roc_auc_score, log_loss
#----------------忽略报警----------------
import warnings
warnings.filterwarnings('ignore')

数据预处理:

# 读取训练数据和测试数据
train_data_ads = pd.read_csv('./2022_3_data/train/train_data_ads.csv')
train_data_feeds = pd.read_csv('./2022_3_data/train/train_data_feeds.csv')

test_data_ads = pd.read_csv('./2022_3_data/test/test_data_ads.csv')
test_data_feeds = pd.read_csv('./2022_3_data/test/test_data_feeds.csv')

# 合并数据
# 合并数据
train_data_ads['istest'] = 0
test_data_ads['istest'] = 1
data_ads = pd.concat([train_data_ads, test_data_ads], axis=0, ignore_index=True)

train_data_feeds['istest'] = 0
test_data_feeds['istest'] = 1
data_feeds = pd.concat([train_data_feeds, test_data_feeds], axis=0, ignore_index=True)

del train_data_ads, test_data_ads, train_data_feeds, test_data_feeds
gc.collect()

(2)特征工程

1)自然数编码

# 自然数编码
def label_encode(series, series2):
    unique = list(series.unique())
    return series2.map(dict(zip(
        unique, range(series.nunique())
    )))

for col in ['ad_click_list_v001','ad_click_list_v002','ad_click_list_v003','ad_close_list_v001','ad_close_list_v002','ad_close_list_v003','u_newsCatInterestsST']:
    data_ads[col] = label_encode(data_ads[col], data_ads[col])

2)特征提取

问:源域的信息为什么没有直接进行自然数编码,而且在之后把它变为nunique和mean数据再加进去?直接把feeds数据变为float然后和ads数据合一起再进行训练可行吗?

答:第一个问题是因为base只做了粗糙的特征提取和拼接,思路可以尝试。

  • 比如只有些具有数值意义的字段再去统计mean等相关属性,对类别型特征统计频次之类的属性,包括对源域数据一些特征的编码处理这样。
    • 目标域的每一条数据都是记录用户在特定时间针对某广告任务的一次行为;
    • 源域则是用户在特定时间在某信息流场景下的一次行为。
  • 直接拼接的一个难点在于,源域和目标域采用的时间戳记录方式是不同的(一个是12小时计时且没给上午和下午的标识符,另一个则是24小时计时的),因此想直接用表的原始主键进行特征拼接是很难的。
  • 从应用的角度出发:选取user作为外键连接两个表的话会有很多一对多的关系,因此需要进一步对特定特征做统计分析(相当于是构造用户相关的一些行为习惯特征)
# 特征构建部分,均使用训练集数据进行构建,避免测试集使用未来数据
train_feeds = data_feeds[data_feeds.istest==0]
cols = [f for f in train_feeds.columns if f not in ['label','istest','u_userId']]
for col in tqdm(cols):
    tmp = train_feeds.groupby(['u_userId'])[col].nunique().reset_index()
    tmp.columns = ['user_id', col+'_feeds_nuni']
    data_ads = data_ads.merge(tmp, on='user_id', how='left')

cols = [f for f in train_feeds.columns if f not in ['istest','u_userId','u_newsCatInterests','u_newsCatDislike','u_newsCatInterestsST','u_click_ca2_news','i_docId','i_s_sourceId','i_entities']]
for col in tqdm(cols):
    tmp = train_feeds.groupby(['u_userId'])[col].mean().reset_index()
    tmp.columns = ['user_id', col+'_feeds_mean']
    data_ads = data_ads.merge(tmp, on='user_id', how='left')

特征提取部分围绕着train_feeds进行构建(添加源域信息,测试集也是train_feeds构建,避免使用未来数据),主要是nunique属性数统计和mean均值统计。由于是baseline方案,所以整体的提取比较粗暴,大家还是有很多的优化空间。

3)内存压缩

def reduce_mem_usage(df, verbose=True):
    numerics = ['int16', 'int32', 'int64', 'float16', 'float32', 'float64']
    start_mem = df.memory_usage().sum() / 1024**2    
    for col in df.columns:
        col_type = df[col].dtypes
        if col_type in numerics:
            c_min = df[col].min()
            c_max = df[col].max()
            if str(col_type)[:3] == 'int':
                if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max:
                    df[col] = df[col].astype(np.int8)
                elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max:
                    df[col] = df[col].astype(np.int16)
                elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max:
                    df[col] = df[col].astype(np.int32)
                elif c_min > np.iinfo(np.int64).min and c_max < np.iinfo(np.int64).max:
                    df[col] = df[col].astype(np.int64)  
            else:
                if c_min > np.finfo(np.float16).min and c_max < np.finfo(np.float16).max:
                    df[col] = df[col].astype(np.float16)
                elif c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max:
                    df[col] = df[col].astype(np.float32)
                else:
                    df[col] = df[col].astype(np.float64)    
    end_mem = df.memory_usage().sum() / 1024**2
    if verbose: print('Mem. usage decreased to {:5.2f} Mb ({:.1f}% reduction)'.format(end_mem, 100 * (start_mem - end_mem) / start_mem))
    return df
    
# 压缩使用内存
data_ads = reduce_mem_usage(data_ads)
# Mem. usage decreased to 2351.47 Mb (69.3% reduction)

由于数据比较大,所以合理的压缩内存节省空间尤为的重要,使用reduce_mem_usage函数可以压缩近70%的内存占有。

4)训练集和测试集的划分

# 划分训练集和测试集
cols = [f for f in data_ads.columns if f not in ['label','istest']]
x_train = data_ads[data_ads.istest==0][cols]
x_test = data_ads[data_ads.istest==1][cols]

y_train = data_ads[data_ads.istest==0]['label']

del data_ads, data_feeds
gc.collect()

5)训练模型和保存提交结果

catboost针对类别特征不用处理,baseline提前做了编码,所以没有在cat_features单独指定,

def cv_model(clf, train_x, train_y, test_x, clf_name, seed=2022):
    
    kf = KFold(n_splits=5, shuffle=True, random_state=seed)

    train = np.zeros(train_x.shape[0])
    test = np.zeros(test_x.shape[0])

    cv_scores = []

    for i, (train_index, valid_index) in enumerate(kf.split(train_x, train_y)):
        print('************************************ {} {}************************************'.format(str(i+1), str(seed)))
        trn_x, trn_y, val_x, val_y = train_x.iloc[train_index], train_y[train_index], train_x.iloc[valid_index], train_y[valid_index]
               
        params = {
    
    'learning_rate': 0.3, 'depth': 5, 'l2_leaf_reg': 10, 'bootstrap_type':'Bernoulli','random_seed':seed,
                  'od_type': 'Iter', 'od_wait': 50, 'random_seed': 11, 'allow_writing_files': False}

        model = clf(iterations=20000, **params, eval_metric='AUC')
        model.fit(trn_x, trn_y, eval_set=(val_x, val_y),
                  metric_period=200,
                  cat_features=[], 
                  use_best_model=True, 
                  verbose=1)

        val_pred  = model.predict_proba(val_x)[:,1]
        test_pred = model.predict_proba(test_x)[:,1]
            
        train[valid_index] = val_pred
        test += test_pred / kf.n_splits
        cv_scores.append(roc_auc_score(val_y, val_pred))
        
        print(cv_scores)
       
    print("%s_score_list:" % clf_name, cv_scores)
    print("%s_score_mean:" % clf_name, np.mean(cv_scores))
    print("%s_score_std:" % clf_name, np.std(cv_scores))
    return train, test

cat_train, cat_test = cv_model(CatBoostClassifier, x_train, y_train, x_test, "cat")

结果保存:

x_test['pctr'] = cat_test
x_test[['log_id','pctr']].to_csv('submission.csv', index=False)

提交catboost后的线上成绩为0.71左右。

三、特征提取技巧

实践:本次比赛是一个经典点击率预估(CTR)的数据挖掘赛,任务是构建一种模型,根据用户的测试数据来预测这个用户是否点击广告。这是典型的二分类问题,模型的预测输出为 0 或 1 (点击:1,未点击:0)

机器学习中,关于分类任务我们一般会想到逻辑回归、决策树等算法,在本文实践代码中,我们尝试使用逻辑回归来构建我们的模型。我们在解决机器学习问题时,一般会遵循以下流程:
在这里插入图片描述

  • 统计相关特征:
    • 用户在各属性ID上,历史点击行为的target mean(平滑)
    • 用户在属性ID上信息熵、共现次数、比例偏好等统计特征
  • 商品统计特征:
    • 各属性ID,历史点击行为的target mean(平滑)
    • 各属性ID,曝光次数统计
  • 序列相关特征:
    • 由word2vec生成的用户、广告等ID的embedding(取平均)

在这里插入图片描述
在这里插入图片描述

问:在不同的时刻是广告点击不同的,训练样本包含了一天所有时刻的广告点击统计,如果用训练出来的模型去预测9点和12点的点击效果,按照直观的想法是,最好是分别训练两个模型,一个是用9点左右的数据,一个是用12点左右的数据,如果这些数据都混在一起学习的话,怎么做效果会更好一点?

答:如果这两部分数据分布差异很大的话,在9点数据集上效果好的模型在12点上应该就达不到预期效果,这种情况可以考虑把他们分开构建模型分别预测。如果二者分布差异不大的话其实混在一起也没太大影响。如果想尝试在混合的情况下考虑这种因素的话,可以考虑构建不同时刻粒度的特征(比如小时特征之类的),之后通过该特征字段,结合其他类别特征,就能够统计并构造针对特定小时情况下的一些数据特征,然后可以对比不同特征下的模型效果进行探索。

四、改进思路

  • 继续尝试不同的预测模型或特征工程来提升模型预测的准确度
  • 尝试模型融合等策略
  • 查阅广告信息流跨域ctr预估预测相关资料,获取其他模型构建方法

五、其他模型

待更新。

附:时间规划

任务 task内容 时间 完成情况
任务1: 报名并理解赛题任务(两天) 9月11、12号
任务2: 配置环境(一天) 9月13号
任务3: baseline实践(一天) 9月14号 周三
任务4: 数据处理、算法应用等技能学习(两天) 9月15、16号 周五
任务5: 相关知识参考学习(一天) 9月17号 周六

Reference

[1] https://xj15uxcopw.feishu.cn/docx/doxcnw5LGZfH5n1WSwcNV59VxTg
[2] 华为比赛官网: https://developer.huawei.com/consumer/cn/activity/starAI2022/algo/competition.html#/preliminary/info/006/data
[3] 爱奇艺-用户留存预测挑战赛
[4] 鱼佬的catboost的baseline:https://blog.csdn.net/Datawhale/article/details/126079911
[5] 学习参考:https://xj15uxcopw.feishu.cn/docx/doxcnw5LGZfH5n1WSwcNV59VxTg
[6] catboost算法原理

猜你喜欢

转载自blog.csdn.net/qq_35812205/article/details/126200365