首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >2018腾讯广告算法大赛总结/0.772229/Rank11

2018腾讯广告算法大赛总结/0.772229/Rank11

作者头像
Coggle数据科学
发布2019-09-12 17:20:49
1.2K0
发布2019-09-12 17:20:49
举报
文章被收录于专栏:Coggle数据科学Coggle数据科学

github链接:

liupengsay/2018-Tencent-social-advertising-algorithm-contest​github.com

首先感谢

BRYAN

@郭大、@Jachin(渣大

队伍:儿须成名酒须醉

比赛打的有点着急,打完IJCAI-18 阿里妈妈搜索广告转化预测才过来的,队友已经开始做了好久,数据居然比ijcai的还大,初赛也没有服务器,到复赛才租了台,老样子比赛遇到了各式各样的问题,思维,实践还有配置都有很大的不足,当然,遇到困难才能很好的进步。能取到如此成绩少不了大佬们的分享,以及时不时奶我的队友。

接下来奉上此次比赛完全方案,干货满满!!

本文内容:

  1. 赛题分析
  2. 特征构造
  3. 特征选择
  4. 模型选择与评估
  5. 最终融合
  6. 不足与总结

1.赛题分析

相似人群拓展(Lookalike)的工作机制是基于种子用户画像和社交关系链寻找出相似用户。

Lookalike 技术,设计基于种子用户画像和关系链寻找相似人群,即根据种子人群的共有属性进行自动化扩展,以扩大潜在用户覆盖面,提升广告效果。具体来讲,种子包里包含有广告主提交的一批已知种子用户,可以当作机器学习问题中的正样本。广告平台中有海量的非种子用户,也有大量的广告投放历史数据,可以帮助生成负样本。有了正负样本后,相似人群拓展就变为了一个机器学习问题中的二分类问题。在线上使用中,广告平台可以依据二分类模型算出的后验概率

来判定候选用户与种子包里用户的相似程度,最后将相似度最高的一批候选用户作为最终的结果。由于Lookalike相似人群扩展和广告CTR有些类似,所以我们沿用了很多阿里妈妈搜索广告转化预测的特征和模型。

1.1评估指标

对于扩展后的相似用户,如果在广告投放上有相关的效果行为(点击或者转化), 则认为是正例;如果不产生效果行为,则认为是负例。 每个待评估的种子包会提供如下信息:种子包对应的广告aid及其特征,以及对 应的候选用户集合(uid及其特征)。选手需要为每个种子包计算测试集中用户的 得分,比赛会据此计算每个种子包的AUC指标,AUCi表示第i个包的AUC值, 并以所有待评估的m个种子包的平均AUC作为最终的评估指标:

1.2数据问题

本次比赛初赛4个多G的数据,复赛16个多G的数据,由于数据太大,初始的数据格式也不能采用pandas直接读取,所以需要做格式转换,用dict的方式来初始化DataFrame。

2.特征构造

五大类特征,投放量(click)、投放比例(ratio)、转化率(cvr)、特殊转化率(CV_cvr)、多值长度(length),每类特征基本都做了一维字段和二维组合字段的统计。值得注意的是转化率利用预处理所得的分块标签独立出一个分块验证集不加入统计,其余分块做dropout交叉统计,测试集则用全部训练集数据进行统计。此外,我们发现一些多值字段的重要性很高,所以利用了lightgbm特征重要性对ct\marriage\interest字段的稀疏编码矩阵进行了提取,提取出排名前20的编码特征与其他单值特征进行类似上述cvr的统计生成CV_cvr的统计,这组特征和cvr的效果几乎相当。

此次比赛在构造特征时,首先参看了Baryan提供的baseline,然后结合以往比赛主要构造了统计特征,比例特征和转化率特征。和以往不同的是,构造这样特征时不仅考虑单个特征的统计度量,还考虑了所有可能的组合特征。也因此发现了很多不易想到的强特,如uid相关特征,uid点击次数,uid转化率。

label_feature=['aid','uid','advertiserId', 'campaignId', 'creativeId',
       'creativeSize', 'adCategoryId', 'productId', 'productType', 'age',
       'gender','education', 'consumptionAbility', 'LBS',
       'os', 'carrier', 'house']
#对于label_feature进行了两两组合构造统计特征
for feature in label_feature:
    s = time.time()
    try:
        data[feature] = LabelEncoder().fit_transform(data[feature].apply(int))
    except:
        data[feature] = LabelEncoder().fit_transform(data[feature])
    print(feature,int(time.time()-s),'s')

data = data[label_feature]
data['cnt']=1
col_type = label_feature.copy()
n = len(col_type)

num = 0
df_feature = pd.DataFrame()
# 但特征点击次数
for i in range(n):
    col_name = "cnt_click_of_"+col_type[i]
    s = time.time()
    se = (data[col_type[i]].map(data[col_type[i]].value_counts())).astype(int)
    semax = se.max()
    semin = se.min()
    df_feature[col_name] = ((se-se.min())/(se.max()-se.min())*100).astype(int).values
    num+=1
    print(num,col_name,int(time.time()-s),'s')
# 组合特征点击次数
for i in range(n):
    for j in range(n-i-1):
        col_name = "cnt_click_of_"+col_type[i+j+1]+"_and_"+col_type[i]
        s = time.time()
        se = data.groupby([col_type[i],col_type[i+j+1]])['cnt'].sum()
        dt = data[[col_type[i],col_type[i+j+1]]]
        se = (pd.merge(dt,se.reset_index(),how='left',
                        on=[col_type[i],col_type[j+i+1]]).sort_index()['cnt'].fillna(value=0)).astype(int)
        semax = se.max()
        semin = se.min()
        df_feature[col_name] = ((se-se.min())/(se.max()-se.min())*100).fillna(value=0).astype(int).values

特征类别分类

2.1基础特征

官方提供的原始特征(用户特征,广告特征).

2.2sparse特征

sparse特征主要参考开源baseline来做的,

2.3统计特征

主要使用了unqiue和click两类特征

2.4长度特征

vec_feature = ['creativeSize','ct','marriageStatus','interest1', 'interest2', 'interest3', 
               'interest4','interest5', 'kw1', 'kw2','kw3', 'topic1', 'topic2', 
               'topic3','appIdAction', 'appIdInstall']
# 对vector feature构造长度特征
for co in vec_feature[1:]:
    value = []
    lis = list(data[co].values)
    for i in range(len(lis)):
        value.append(len(lis[i].split(' ')))
    col_name = co+'_length'
    df_feature[col_name]=value

2.5比例特征

比例特征用来刻画某类用户的偏好,根据各类偏好更好的区分用户。

2.6转化率特征

由于本次比赛没有给出任何时间特征,所以构造转化率特征时很容易产生数据穿越,从而过拟合。为了解决数据穿越,可以采用分块求转化率,或者贝叶斯平滑等方法。值得注意的是转化率利用预处理所得的分块标签独立出一个分块验证集不加入统计,其余分块做dropout交叉统计,测试集则用全部训练集数据进行统计,很好的解决的数据穿越问题。

3特征选择

首先推荐一篇有关特征选择的文章

https://cloud.tencent.com/developer/article/1505647

可以想象经过上述构造特征的过程能够上到数百个特征,但是我们又不可能对所有特征进行训练,因为里面可能包含很多冗余特征,同时我们需要在少特征的情况下达到多特征的效果(奥卡姆剃刀原理)。最常用的方法是相关系数法以及模型输出特征重要性的方法。由于数据量问题,并没用采取比较复杂的方法。

# 特征重要性结合前向搜索进行特征选择
# 返回特征重要性排序,返回最佳迭代次数
clf = LGBMClassifier(boosting_type='gbdt',
                     num_leaves=31, max_depth=-1, 
                     learning_rate=0.1, n_estimators=10000, 
                     subsample_for_bin=200000, objective=None,
                     class_weight=None, min_split_gain=0.0, 
                     min_child_weight=0.001,
                     min_child_samples=20, subsample=1.0, subsample_freq=1,
                     colsample_bytree=1.0,
                     reg_alpha=0.0, reg_lambda=0.0, random_state=None,
                     n_jobs=-1, silent=True)
print('Fiting...')
clf.fit(train_part_x, train_part_y, eval_set=[(train_part_x, train_part_y),(evals_x, evals_y)], 
        eval_names =['train','valid'],
        eval_metric='auc',early_stopping_rounds=100)
se = pd.Series(clf.feature_importances_)
se = se[se>0]
col =list(se.sort_values(ascending=False).index)
pd.Series(col).to_csv('data_preprocessing/col_sort_one.csv',index=False)
print('特征重要性不为零的编码特征有',len(se),'个')
n = clf.best_iteration_
baseloss = clf.best_score_['valid']['auc']
print('baseloss',baseloss)
clf = LGBMClassifier(boosting_type='gbdt',
                     num_leaves=31, max_depth=-1, 
                     learning_rate=0.1, n_estimators=n, 
                     subsample_for_bin=200000, objective=None,
                     class_weight=None, min_split_gain=0.0, 
                     min_child_weight=0.001,
                     min_child_samples=20, subsample=1.0, subsample_freq=1,
                     colsample_bytree=1.0,
                     reg_alpha=0.0, reg_lambda=0.0, random_state=None,
                     n_jobs=-1, silent=True)
def evalsLoss(cols):
    print('Runing...')
    s = time.time()
    clf.fit(train_part_x[:,cols],train_part_y)
    ypre = clf.predict_proba(evals_x[:,cols])[:,1]
    print(time.time()-s,"s")
    return roc_auc_score(evals_y[0].values,ypre)
print('开始进行特征选择计算...')
all_num = int(len(se)/100)*100
print('共有',all_num,'个待计算特征')
loss = []
break_num = 0
for i in range(100,all_num,100):
    loss.append(evalsLoss(col[:i]))
    if loss[-1]>baseloss:
        best_num = i
        baseloss = loss[-1]
        break_num+=1
    print('前',i,'个特征的得分为',loss[-1],'而全量得分',baseloss)
    print('\n')
    if break_num==2:
        break
print('筛选出来最佳特征个数为',best_num,'这下子训练速度终于可以大大提升了')

内存原因,没办法一次性加载所有特征,故对每组特征进行组内筛选,对组内特征进行重要性排序,排序完筛选办法有两种,一种是按照排名进行前向搜索,另一种就是直接测试前n*5(n位正整数)个特征的效果,由于多数统计特征之间相关性很高,大概每组30+个特征的信息就能代表整组特征所包含的信息。第一种精度会好一些但速度慢,第二种办法就相对快很多。

4模型训练与评估

4.1模型选择

lightgbm,ffm,nffm

由于数据量过大,lgb根据分块数据与分组特征跑了很多个子模型,最后根据验证集的多组预测值进行auc排序后,依次百分比(list(range(0,101))*0.01)遍历加权以获得最佳权值,再将同样的权值应用到测试集的预测结果上,这样每多加权一个子模型,验证集的auc只会大于等于加权这个子模型之前的auc。整个加权过程其实就类似于是一种线性拟合,也可以利用各个子模型的验证集和测试集的预测结果作为特征,利用验证集的标签作为真实标签,采xgboost等模型进行训练,这样效果与之前的遍历加权差不多。

4.1切分验证集

根据aid,切分出来20%的训练集作为验证集,为了评分线上线下一直,构造特征时将训练集,验证集,测试集区分构造。

##插入字段n_parts数据集进行分块,训练集分成五块1、2、3、4、5,测试集1为6、测试集2为7
##也就是test字段与n_parts字段都是为了区分数据块,n_parts对训练集进行了分块
print('N parts...')
train = data[data['test']==0][['aid','label']]
test1_index  = data[data['test']==1].index
test2_index  = data[data['test']==2].index
n_parts = 5
index = []
for i in range(n_parts):
    index.append([])
aid = list(train['aid'].drop_duplicates().values)
for adid in aid:
    dt = train[train['aid']==adid]
    for k in range(2):
        lis = list(dt[dt['label']==k].sample(frac=1,random_state=2018).index)
        cut = [0]
        for i in range(n_parts):
            cut.append(int((i+1)*len(lis)/n_parts)+1)
        for j in range(n_parts):
            index[j].extend(lis[cut[j]:cut[j+1]])
se = pd.Series()
for r in range(n_parts):
    se = se.append(pd.Series(r+1,index=index[r]))
se = se.append(pd.Series(6,index=test1_index)) 
se = se.append(pd.Series(7,index=test2_index)) 
data.insert(0,'n_parts',list(pd.Series(data.index).map(se).values))

5模型融合

根据不同的特征组构造了十多个lgb模型,并且根据数据分布构造了6个nffm模型.

模型结构:
使用了lightgbm和nffm
lgb方面,由于数据量的原因,复赛均提取20%的训练集数据来训练lgb模型,通过不同的特征组合构造多个lgb模型进行融合。
nffm方面,模型结构主要是复赛数全量据构造的nffm,初赛和复赛全量数据构造的nffm两种,两种nffm所使用的特征也是不用的。同时为了模型的鲁棒性,每种nffm通过不同的数据分布各训练出三个模型。
融合结构,一个融合后的lgb模型结合六个nffm模型进行加权融合。
(
  ( 
    (
      result['nffm_75866_1']*0.5 + result['nffm_75866_2']*0.5
    ) 
    *0.5 + result['nffm_75866_0']*0.5
  ) 
  *0.2 + result['nffm_7688']*0.4 + (result['nffm_763']*0.4 + result['nffm_765']*0.6)*0.4 
) 
*0.7 + result['lgb']*0.3

6不足与总结

首先还是nn方面的欠缺,如果没有郭大的nffm,光靠lgb也只能到0.763+的成绩。这次比赛也是蛮可惜的,设备方面也是很大的问题,对于相同的代码,别人需要10小时,我们得20小时跑完。最后因为时间原因,nffm没有跑完全部的epoch。回想比赛经历,一路磕磕绊绊,收获很多,同时也发现自己的很多不足,模型尝试的少,理解不够深。接下来呢,准备沉淀一段时间,为了更好的下一次。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.赛题分析
  • 2.特征构造
  • 3特征选择
  • 4模型训练与评估
  • 5模型融合
  • 6不足与总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档