天天看点

腾讯2019广告算法大赛总结

写在前面

第一次参加腾讯的广告赛,这次比赛需要自己提取训练数据和标签,除了维度以外,数据在其他方面都很接近真实业务,我本身也是第一次接触这样的数据。最后初赛B榜成绩85.2,排名第215。接下来做一些主观和客观的总结。

关于数据

今年的数据和往年一样,需要自己先转化为csv或者hdf5格式pandas.DataFrame.to_hdf。

在totalExposureLog.out中提取标签,也就是日曝光,可以通过groupby(广告id,年,月,日)这样的方式来提取该广告在当日出现的次数,即为曝光量。

ad_static_feature里面记录了广告的一些元属性,包括账户id,商品id,商品类别等。

ad_operation文件中记录了一些广告的历史修改记录,在这里出现的广告均为cpc广告。且static文件中不是所有广告有历史修改记录。

userdata中包含了用户数据,在这次比赛中我没有用到,不过听别人说里面有可以使用的信息可以用来上分。

test为测试数据,只有商品id等原始特征。在这里面每条广告id对应有不同的出价,需要分别预测曝光,且该曝光需要满足下面的单调性。

没有训练集,需要自己构建特征来进行训练,这次比赛最大的感觉就是非常接近真实业务,因为据我所了解,平常业务中也需要花很多时间与精力进行数据预处理。而在这道题中,训练集需要从零开始构建,构建地越好(本质上是特征工程做的越好),训练的模型越好。本题是一个回归问题。

最后的评价指标有两条:

腾讯2019广告算法大赛总结

SAMPE要确定的是预测值和真实值的偏差。

腾讯2019广告算法大赛总结

单调性要预测的是,是否你的预测曝光和出价成正比,单调性在最终的得分里占比不小。最终的得分由SMAPE和单调性加权产生。

腾讯2019广告算法大赛总结

数据预处理

Log数据大概有一亿条,内存不够的情况下需要先将数据进行切分。然后需要进行去重,大概有100万条左右的重复数据。去重后可以按照前面所说的方法进行标签的建立。

训练数据选取只在ad_operation内部有过历史操作记录的广告id,因为如果不是cpc广告也没有训练价值。

所以通过static和operation两个数据,我们就可以构建初步的特征,从operation里面提取出现过的id,再从static里面找到该id对应的原始特征,同时在operation对每个Id也有创建这个操作,创建操作产生的特征(曝光时段等)在static里面是没有的。

同时需要对缺失值进行填充。对于缺失值太多的类似商品id这样的特征可以直接舍弃。

在此阶段大概可以生成十七万左右的原始特征数据,将开始生成的标签与其进行merge。得到初步的训练集。此时未用到bid(出价)这个特征。

模型训练

在生成初步的训练集后,内部包含的特征有,广告id,商品id,商品类别,账户id。。。这样的类别特征(离散特征),也有像素材大小这样的连续特征。我在本题中使用的是lightgbm。

有两种方案可以处理,主要是针对离散特征:

  1. 针对lightgbm这样的基于树的模型,可以直接将离散特征输入,但是需要提取标记,在lightgbm中
    lgb.Dataset(train_X, categorical_feature = categorical_list, label = train_y)
               
    将类别特征名单独创建一个list,作为categorical_feature这个形参的实参。连续特征不需要做其他操作。
  2. 可以将类别数据进行one-hot操作,由于广告数据每个离散特征可取值非常多,导致维度非常大,这个时候内存无法正常容纳,此时可以创建稀疏矩阵。
from sklearn.preprocessing import OneHotEncoder
from scipy import sparse
## onehot encoder
numerical_feature = ['material_size', 'old', 'ad_nums', 'ad_class_len', 'goods_id_len',
                    'bid_mean', 'time_mean']
one_hot_feature = ['ad_account_id', 'ad_class_id', 'goods_class',
        'build_time_category'] #'goods_id'
enc = OneHotEncoder()
# 先将数值特征放入
train_x = train_df[numerical_feature]
test_x = test_df2[numerical_feature]
train_y = train_df['expose_days']

train_X = train_df[one_hot_feature]
test_X = train_df[one_hot_feature]

data = pd.concat([train_X,test_X])

# 将类别特征one-hot之后放入
for feature in one_hot_feature:
    print(feature)
    enc.fit(data[feature].values.reshape(-1, 1))
    train_a=enc.transform(train_df[feature].values.reshape(-1, 1))
    test_a = enc.transform(test_df2[feature].values.reshape(-1, 1))
    train_x= sparse.hstack((train_x, train_a))
    test_x = sparse.hstack((test_x, test_a)) 
           

      这里先将数值特征放入后,再放入one-hot特征。最后生成一个稀疏矩阵。即可开始进行训练,并通过模型得到一个初步答案。验证集这里选的是最后一天也就是3月19日的数据进行验证。到目前为止没有涉及到规则,此时提交的话,使用A榜测试集应该已经可以拿到83-84左右的分数。

规则模型

规则模型需要有一定的业务理解(虽然博主自身对于业务理解程度也不够)。本题通过统计A测试集可以发现很多广告id是老广告id,有一部分(大概6000条)是新广告id,新广告id指的是在opetation中没有出现过的广告(无历史操作记录)。对于老广告id,可以采取统计该广告的历史曝光,然后求得平均值,将其作为我们的预测值(也需要进行单调性调整)。这就是该题中一种常用的规则。在将规则预测的曝光,与原始训练的曝光进行融合(替换)。

在初赛后期,官方更新了b榜数据,最后排名使用b榜数据得分,在b榜中,新广告id占了三分之二左右,此时前面的规则模型提分效果降低。同时由于新广告数量的增加,也舍弃了广告id这个特征。因为在训练集内这个特征内部全为老广告id,可以预见该特征对新广告id的泛化能力较弱。

调整单调性

在模型训练以及规则后,由于没有放入出价这个特征,所以预测时也未用到该特征,于是对于同样的广告id,虽然它有不同的出价,但此时我们预测的曝光量是相同的。前面没有放入出价的目的就在于为了方便后面调整单调性,此时可以针对不同的出价,对初始预测曝光+bid/10000 便得到了最后预测的曝光量,该预测值满足了“相同的广告id,在出价越高的情况下,根据业务直觉,曝光量应该越高”的单调性。此时的a榜测试集分数应该可以达到85左右。

到上述为止,就完成了一个初步的模型,再想继续上分就需要做一些更有意义的操作,对于lgb模型训练,可以加入一些更有意义的特征,比如对于人群定向的统计特征,时间统计特征等,也可以对于统计特征生成一些组合特征,还可以通过目标编码等构建新的特征等。对于规则模型,又可以加入一些更具有业务内涵的规则。比如

  1. 在多个不同属性文件中提取原始类别特征和统计特征,构建组合特征
  2. 提取用户的曝光记录,将其转化为文本序列,然后使用word2vec对广告id进行嵌入,可以得到广告id的embedding
  3. 将历史曝光数据中曝光量超过100的广告id标记为大广告id,预测新广告id是否为大广告id会出现样本不均衡现象,通过下采样结合多个分类模型组合的方式预测
  4. 对高维类别特征采用onehot编码,结合稀疏矩阵,节省内存

最后在A榜的得分可以达到86.5左右。由于B榜更新了大量的新广告id,选择去掉了广告id这个特征进行训练,以及新加入了部分特征,选择五折交叉验证。但是总体思路和上述相差不大,最后得分85.2。

总结

这次比赛,问题出在可能还是自己对于业务背景不太了解,真实业务数据由于格式,脏数据等问题需要自己一步一步清洗,最后得到干净的训练集,这可能需要花费很多时间与精力。个人对于CTR的理解仍然不够深入,还需要更多学习。