摘要:
合集:AI案例-NLP-广告传媒业
赛题:腾讯2019广告算法大赛-广告曝光预估-初赛
数据集:腾讯广告对用户的历史曝光日志记录了用户与广告的交互行为(如曝光、点击、转化等)
数据集发布方:腾讯
数据集价值:推断用户长期/短期兴趣,分析广告在不同场景下的表现,优化投放策略。
解决方案:广告历史曝光特征提取、词嵌入等建模技术
一、初赛赛题描述
2019年腾讯算法大赛是一场面向全球算法爱好者的专业赛事,旨在通过解决腾讯广告业务中的真实问题,推动数据挖掘和机器学习技术的发展。
目的
大赛旨在通过实际业务问题的解决,提升参赛者的算法能力和数据分析技巧,同时促进学术界与工业界的交流与合作。
赛题
比赛题目为“广告曝光预估”,要求参赛者基于历史广告曝光数据,预测新广告设置下的日曝光量。广告曝光预估的目的是在广告主创建新广告和修改广告设置时,为广告主提供未来的广告曝光效果参考。通过这个预估参考,广告主能避免盲目的优化尝试,有效缩短广告的优化周期,降低试错成本, 使广告效果尽快达到广告主的预期范围。
参赛者
吸引了来自清华大学、北京大学、浙江大学等顶尖院校以及微软研究院、京东等工业界的10,571名选手参与。
评价指标
包括准确性指标SMAPE和出价单调性指标monoscore,旨在衡量预测的准确度和报价与曝光量的相关性。
解决方案
涉及数据清洗、特征工程、模型构建和融合等多个环节,最终通过复赛和答辩选出优秀参赛队伍。
二、初赛数据集内容
本次竞赛提供的数据为脱敏后的真实业务数据,为历史n天的曝光广告的数据(特定流量上采样), 包括对应每次曝光的流量特征(用户属性和广告位等时空信息)以及曝光广告的设置和竞争力分数;测试集是新的一批广告设置(有完全新的广告id, 也有老的广告id修改了设置), 要求预估这批广告的日曝光 。出于业务数据安全保证的考虑,所有数据均为脱敏处理后的数据。
原始数据
原始数据统一保存在data文件夹。对于原始日志文件,需要自己使用pandas.DataFrame.to_hdf()先转化为csv或者hdf5格式。
数据集主要包括一些日志文件,分别为:
- 用户信息数据/user_data:记录用户 id、年龄、性别、地域、行为兴趣等。
- 广告静态信息/ad_static_feature.out:记录广告的一些元属性,包括账户id,商品id,商品类别等。
- 广告操作信息/ad_operation.dat:记录广告的历史修改记录,在这里出现的广告均为按点击付费/cpc广告,且static文件中不是所有广告有历史修改记录。
- 历史日志数据:记录广告请求时间、用户 id、广告位 id、竞价广告信息等。totalExposureLog.out中提取标签,也就是日曝光,可以通过对(广告id,年,月,日)进行分组统计的方式来提取该广告在当日出现的次数,即为曝光量。
预测数据:待预估广告以及对应的设置数据。test_sample.dat文件中只有商品id等原始特征。在这里面每条广告id对应有不同的出价,需要分别预测曝光,且该曝光需要满足单调性。
数据条数统计
totalExposureLog.out 总记录数: 102,386,695
ad_static_feature.out 总记录数: 735,911
user_data 总记录数: 1,396,718
test_sample.dat 总记录数: 20,290
数据结构
如之前所述,训练数据包含广告 n 天的曝光历史数据、曝光用户的属性数据,广告设置和操 作数据 ,按具体的维度可以分为如下几部分提供。
历史曝光日志
为避免数据量过大 ,历史曝光数据选择了在用户维度( uv)上按 512 分之一进行均匀采样。各字段使用制表符(\t)分隔 ,每列的具体含义如下:
- 广告请求 id :唯一标识每次请求(每个请求对应一个用户某一时刻 ,可能多个广告位)
- 广告请求时间 :该字段为时间戳,即 1970 纪元后经过的浮点秒数
- 广告位 id :加密后无业务含义,只区分不同广告位 ,每个广告位只能曝光特定素材尺寸的广告
- 用户 id(即看广告的人):加密后无业务含义 ,只区分不同用户 ,可和后面的用户特征数据中 id 相关联
- 曝光广告id :加密后无业务含义 ,只区分不同广告,可以和广告特征文件中的广告 id 关联。
- 曝光广告素材尺寸 :枚举型取值,不同广告位对素材的尺寸要求不同,同一个广告位可能适配多个不同尺寸的素材。
- 曝光广告出价bid :这里只记录cpc 出价,非 cpc 广告此处记录折算后的cpc价格
- 曝光广告 pctr :预估的 pctr ,和 bid 相乘得到 basic_ecpm
- 曝光广告 quality_ecpm :将广告质量和用户体验等因素折算成 ecpm 的分数 ,主要影响因素有 pctr/pcvr/窄定向等
- 曝光广告 totalEcpm :广告排序的分数依据,由 basic_ecpm 和 quality_ecpm相加得到
用户特征属性
注:用户特征字段未知的均使用 0 表示。每列特征取值都用加密后的 id 表示,均 为随机映射。不同列的 id 取值区间会重复。各字段使用制表符(\t)分隔 ,每列的具 体业务含义如下 :
- 用户 id :此处和上面曝光日志文件中的用户 id 关联
- 年龄(Age):每个取值随机映射为[1-N]的唯一 id
- 性别(Gender) :男/女
- 地域(area) :每个省/市用唯一 id 标识,可能多标签,使用逗号分隔不同 id
- 婚恋状态(Status):单身/已婚等状态 ,可能去多值,使用逗号分隔
- 学历(Education) :博士/硕士/本科/高中/初中/小学
- 消费能力(ConsuptionAbility):高/低
- 设备(device):IOS/Android, 不区分版本号
- 工作状态(work):在校大学生/商旅人士/政府公职人员/科研教育者/ IT 互联
- 网工作者/医护工作者, 可能取多值,逗号分隔
- 连接类型(ConnectionType) :无线/2G/3G/4G
- 行为兴趣(behavior) :每个兴趣点一个 id ,可多值,逗号分隔
广告数据文件
广告是指广告主创建的广告创意(或称广告素材)及广告展示相关设置,包含广告的基本信息(广告名称、投放时间等)、广告的推广目标、投放平台、投放的广 告规格、所投放的广告创意、广告的受众(即广告的定向设置)以及广告出价等信息。这里将广告的数据分为两部分,一部分是广告静态数据(如所属账户等), 另一部分是动态数据 ,即修改操作数据,主要包括广告状态、出价、定向等可修改的设置,该部分数据对应一个时间戳 ,多次修改的同一条广告会对应多条记录。 a. 广告静态数据 该类广告属性一般从广告创建后无法修改。所有 id 类数据均为加密后随机映射。 各列用制表符分隔,含义如下:
- 广告 id :和曝光日志中的广告 id 相关联
- 创建时间 :广告创建时的时间戳
- 广告账户 id :广告所在账户的唯一标识 ,账户结构分为四级:账户——推广计划——广告——素材
- 商品 id :广告推广目标的唯一标识,若推广目标是落地页,则该字段为空
- 商品类型 :广告推广目标的类型 ,枚举型
- 广告行业id :广告所属的行业类别标识
- 素材尺寸 :不同广告位对素材的尺寸要求不同,同一个广告可能有多个不同尺寸的素材 ,用逗号分隔
b . 广告操作数据 记录广告操作的流水数据,以及操作后的属性值。各列使用制表符分隔,含义如下 :
- 广告 id(同上 )
- 创建/修改时间: 即广告创建或者修改设置的时间
- 操作类型 :1-修改 ,2-新建
- 修改字段 :1-广告状态,2-出价 ,3-人群定向 ,4-广告时段设置
- 操作后的字段值:
- 广告状态取值 :1- 正常 ,0-失效
- 出价 :整数(单位分)
- 投放时段 :字符串。包含 7 个 64 位无符号整型数字(逗号分隔),每个整数分别代表周一到周日的投放时段。该整数转为2 进制后从低到高 48 位 bit 代表全天各时段(半小时为一时间窗口 )是否投放 ,1-投放,0-不投。举例说明 ,17179865088= 1111111111111111111111000000000000 ,代表投放时段为 6 :00-17 :00 ;281474976710655 =111111111111111111111111111111111111111111111111 ,代表全天投放。
- 人群定向 :字符串。格式如下 :feature_name1:feature_value1,feature_value2|feature_name2:featu re_value3,feature_value4| … 此处 feature_name 取值同用户属性文件 中的各列属性名,feature_value 取值 id 同用户属性文件中的定义,不同 feature 用“ |”分隔,不同 feature 取值用逗号分隔。广告通过人群定向 的设置来召回对应的用户请求,对应的人群规则:不同 feature_name 是 求 交集 ,同 一 featurename 下 不 同 的 value 求并集 ,未 定 义 的 feature_name 则 表 示 该 维 度 不 限 。 举 例 如 : 定 向 设 置 为 age:51,62,73,84|gender:1|area:1,3,5 ; 则表示该广告能被 “(年龄 id 为 51 或 62 或 73 或 84) 且 (性别取值为 1) 且 (地域取值为 1 或 3 或 5)”的用户召回( 即在这些用户上有曝光机会)。
测试数据
第 n+1 天的待预估广告以及对应的设置。各列由制表符(\t)分隔 , 含义说明如下:
- 样本 id
- 广告 id
- 创建时间
- 素材尺寸
- 广告行业 id
- 商品类型
- 商品 id
- 广告账户 id
- 投放时段
- 人群定向
- 出价(单位分)
以上各字段的格式均和训练数据中的广告数据格式一致。由于要评估出价相关性 ,故测试数据中一条广告 id 会对应多条不同出价的样本。为简化问题,测试数据也要求预估指定广告样本在下一个自然日的曝光量。(此处的 曝光量是和训练数据一样 uv 采样后的结果)。为了更准确的评估,此处的测试广告 已经剔除掉下一个自然日期间再次修改的广告(如暂停、修改出价、定向等) ,即认 为在预估周期内该广告设置保持不变。
三、初赛解决方案样例
该项目是初赛第1名的解决方案。
工作原理
赛题分析
腾讯效果广告采用的是GSP(Generalized Second-Price)竞价机制。说明:GSP(Generalized Second-Price,广义第二价格)是互联网广告竞价(如搜索引擎广告、程序化广告)中最核心的拍卖机制之一,由Google首次大规模应用于AdWords广告系统。其设计目标是平衡广告主利益与平台收入,同时兼顾用户体验。广告主对广告位(如搜索结果的某个排名)出价(Bid),但实际付费≠ 自身出价,而是 下一个广告主的出价 + 微小增量(通常为0.01元)。广告的实际曝光取决于广告的流量覆盖大小和在竞争广告中的相对竞争力水平。其中广告的流量覆盖取决于广告的人群定向(匹配对应特征的用户数量)、广告素材尺寸(匹配的广告位)以及投放时段、预算等设置项。而影响广告竞争力的主要有出价、广告质量等因素(如pctr/pcvr等), 以及对用户体验的控制策略。 通常来说, 基本竞争力可以用
ecpm = 1000 * cpc_bid * pctr = 1000 * cpa_bid * pctr * pcvr
(cpc, cpa分别代表按点击付费模式和按转化付费模式)。
综上,前者决定广告能参与竞争的次数以及竞争对象,后者决定在每次竞争中的胜出概率。二者最终决定广告每天的曝光量。
此次问题有两个评判标准:1、准确性相关指标, 2、单调性指标。 准确性指标采用SMAPE,出价单调性指标指对待预估广告 ad, 除出价 bid 外其他设置不变,任意变化 n 个 bid 取值,得到对应的 n 个曝光预估值,满足单调性。为此可采用两步走,第一步预测保证单个广告的预估曝光量相同,也就是相同广告id的特征相同。第二步调整广告id预估曝光量的单调性,这里要尽量保证预估曝光量的稳定性,由于不知道真实出价,可对bid进行排名,此外由于要求保留四位小数,单调性调整可使用基准曝光量+0.0001*出价排名。
问题建模
由于此次比赛只提供了历史广告曝光日志,与广告静态属性以及广告操作属性,所以需要自己构造训练集与验证集。
广告操作数据处理:
由于广告的操作属性,例如状态取值、出价、投放时段,以及定向人群每天的值是不同的,所以可采用两种方案:第一种是直接取创建时的属性,第二种是利用请求时间以及修改创建时的时间进行匹配得到广告的操作属性,经试验, 线上差距不大。
训练集与验证集生成:
初赛提取方案:以请求时间,广告id为主键,对request-id求count,就得到了某个广告某一天的曝光量。
复赛提取方案: 对竞价队列进行展开,同样以请求时间,广告id为主键,对曝光量标志位求sum,就得到了某个广告某一天的曝光量。
广告id的选取:直观来看训练集样本数量越多,但是仔细读题后发现初赛测试集预测的主要是CPC类广告,所以对于无法在广告操作数据表中匹配到的数据都可以舍弃,这样清洗下来的数据量又明显小了一大截,只剩下十几万了。采用同样的方法训练,虽然数据量变小了,但线上成绩提高了。同时由于0曝光量数量太多,会导致模型学到很多负值,所以可过滤掉请求次数小于特定值,仍旧是0曝光的广告,留下很不容易的曝光广告。
安装开发包
- python 10
- gensim 4.32
- scikit-learn
- tqdm
- pandas 2.23
- numpy
- scipy
- tensorFlow
说明:使用不同的开发包版本,API调用方式有差异。
运行流程
1、数据预处理
首先在src/preprocess.py进行数据前处理,主要是以下几个函数:
- parse_rawdata():转化曝光日志为otalExposureLog.pkl、转化静态广告属性为ad_static_feature.pkl、转化用户信息为user_data.pkl、转化测试数据为test_sample.pkl。
- construct_log():构造曝光日志,统计每一天各个广告的曝光量,同时将最后一天的数据择选出来
- extract_setting():对广告操作日志进行日期纠正,缺失日期记录则copy填充
- construct_train_data():构造训练集,根据曝光日志统计广告当天平均出价和曝光,通过crowd_direction和delivery_periods两个属性过滤其中未在广告操作日志出项的广告,并剔除出价过高(1000以上)和曝光过高(3000以上)的广告。
- construct_dev_data():构造验证集,输入为曝光日志中最后一天([‘request_day’]==17974)的数据,根据广告操作日志剔除未出现操作记录、当天有操作记录和出价不唯一的广告的广告,奇怪的是源码里后面针对补全时间记录的广告操作日志又进行了一次未出现操作记录的过滤,这里有部分过滤重复,但没有错误,最终的dev_df也填补了缺失日期的记录。最后,构造了虚假广告用来测试单调性,这一点。
- 最后,对各个数据集merge广告静态特征。总共生成四个数据集:
- train_dev_df:从广告日志+广告操作文件+广告静态文件提取出的数据集中减去最后一天的数据
- train_df:训练集,从广告日志+广告操作文件+广告静态文件聚合提取出的最全数据集
- dev_df:验证集,最后一天的广告数据
- test_df:直接读取的test数据
这样切分的一个用处是,通过train_dev_df和dev_df进行有监督的模型训练,再通过train_df和test_df进行测试。
执行:python src/preprocess.py
parsing raw data ....
construct log ....
100%|█████████████████████████████████████████████████████████████████████████████████████████████████████| 102386695/102386695 [03:21<00:00, 508717.87it/s]
construct train data ....
(180880, 15) (169288, 15)
25.94888323750553 100.87425119764077
construct dev data ....
(28490, 6)
13.211583011583011 104.92748332748333
load test data ....
combine advertise features ....
save preprocess data ....
(169288, 21) (28490, 12)
(180880, 21) (20290, 11)
输出:test_sample.pkl、user_data.pkl、ad_static_feature.pkl、totalExposureLog.pkl
生成四个数据集:
- train_dev.pkl:从广告日志+广告操作文件+广告静态文件提取出的数据集中减去最后一天的数据
- train.pkl:训练集,从广告日志+广告操作文件+广告静态文件聚合提取出的最全数据集
- dev.pkl:验证集,最后一天的广告数据
- test.pkl:直接读取的test数据
2、提取特征
源码:src/extract_feature.py
原始数据集中没有训练集,需要自己构建特征来进行训练,这次比赛最大的感觉就是非常接近真实业务,平时业务中也需要花很多时间与精力进行数据预处理。而在这道题中,训练集需要从零开始构建,构建地越好(本质上是特征工程做的越好),训练的模型越好。本题是一个回归问题。
(1)特征维度:主要从历史角度和全局角度去构建特征,具体维度有前一天、最近一天、历史所有、前n天和五折交叉统计全局特征。 (2)基础特征:广告在当天的竞争胜率,广告在当天竞争次数,广告在当天竞争胜利次数,广告在当天竞争失败次数。然后可以扩展为商品id和账户id等。然后将基础特征按特征维度进行构造。对于新广告,可以将商品id和账户id与基础特征进行组合。
在初赛中,初始特征分为类别特征和数值特征:
# 类别特征
categorical_features = ['aid_size','goods_type','goods_id','industry_id','account_id','crowd', 'period','area', 'behavior']
# 数值特征
numerical_features = ['pctr','quality_ecpm','totalEcpm','ecmp']
源码解析:该源码是一个特征工程脚本,主要用于从广告曝光日志数据中提取多种特征,包括统计特征、Embedding特征(Word2Vec/DeepWalk)、历史行为特征等,用于广告点击率(CTR)预测等任务。
整体流程
- 输入数据:
train_dev.pkl
/dev.pkl
或train.pkl
/test.pkl
:训练集和测试集的静态特征。user_log_dev.pkl
/user_log_test.pkl
:用户行为日志(曝光、点击等)。ad_static_feature.pkl
:广告静态特征(如广告主、商品类别等)。
- 特征提取:从日志数据中生成统计特征、序列特征、图特征等。
- 输出:增强后的训练集和测试集(保存为
.pkl
文件)。Word2Vec和DeepWalk生成的Embedding特征(保存为.pkl
)。
核心功能模块
(1) Word2Vec特征(w2v
函数)
- 目的:生成用户(
uid
)对广告/商品/广告主的行为序列的Embedding。 - 实现:
- 按天和用户分组,构造用户的行为序列(如
[good_id1, good_id2, ...]
)。 - 使用
gensim.Word2Vec
训练Embedding,维度为L
(如64维)。 - 输出每个
good_id
/advertiser
/aid
的Embedding向量。
- 按天和用户分组,构造用户的行为序列(如
- 示例:w2v(log, ‘uid’, ‘good_id’, ‘dev’, 64) # 生成用户对商品的Embedding
(2) DeepWalk特征(deepwalk
函数)
- 目的:基于用户-广告交互图生成图嵌入(Graph Embedding)。
- 实现:
- 构建二分图:节点为用户(
user_xxx
)和广告/商品(item_xxx
),边为交互记录。 - 通过随机游走生成路径,用Word2Vec训练节点Embedding。
- 分别输出用户和广告的Embedding。
- 构建二分图:节点为用户(
- 示例:deepwalk(log, ‘uid’, ‘aid’, ‘dev’, 64) # 生成用户和广告的图嵌入
(3) 统计特征(get_agg_features
函数)
- 目的:按分组键(如
good_id
、advertiser
)统计聚合特征(均值、计数、唯一值等)。 - 支持聚合操作:
count
,mean
,unique
,sum
,std
,median
,skew
等。
- 示例:get_agg_features(train_df, test_df, [“good_id”], ‘advertiser’, “count”) # 统计每个商品的广告主数量
(4) 历史行为特征(history
函数)
- 目的:提取用户或广告的历史行为统计值(如最近一次曝光
imp
的均值)。 - 实现:按
pivot
(如aid
)和日期分组,计算历史记录的均值。填充缺失值为中位数。
(5) 人群定向特征(crowd_direction
函数)
- 目的:解析广告的定向人群标签(如年龄、性别、地域等)。
- 输出:多值特征(如
age=18-24,25-30
拆分为单独列)。
(6) 投放时段特征(predict_periods
函数)
- 目的:将广告的投放时段(48个半小时区间)编码为48维二进制特征。
- 实现:解析
delivery_periods
字段(如"1,3,5"
表示第1、3、5时段投放)。生成48维的0/1向量和时段总数。
(7) 五折统计特征(kfold_static
函数)
- 目的:防止数据泄露,通过5折交叉验证统计目标变量(如
imp
)的均值、标准差等。 - 实现:将训练集分为5折,用4折统计特征,应用到剩余1折和测试集。
步骤1:数据划分 将训练集(假设含样本量N)随机划分为5个大小相似的子集(Fold 1~5),每个子集约含N/5个样本。 步骤2:循环计算特征 对每一折(如Fold 1)执行: 训练子集:使用其他4折(Fold 2~5)计算统计量(如某类别的目标均值)。 应用子集:将统计量填充到当前折(Fold 1)的对应特征中。 重复:对所有5折循环此操作。 步骤3:合并结果 最终,每个样本的特征值均由未包含该样本的其余4折数据计算得到,确保无数据泄露。
执行:python src/extract_feature.py
运行时长约3个小时,运行完整日志输出到文件:exec-log-extract_feature.txt。
data/train_dev.pkl data/dev.pkl data/user_log_dev.pkl dev
(169288, 21) (28490, 12) (96554023, 16)
(169288, 21) (28490, 12) (96554023, 22)
crowd_direction features
predict_periods features
crowd_uid features good_id advertiser
100%|███████████████████████████████████████████████████████████████████████████████████████████████████████| 96554023/96554023 [01:49<00:00, 878762.89it/s]
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████| 25005/25005 [00:00<00:00, 98015.71it/s]
crowd_uid features good_id request_day
100%|███████████████████████████████████████████████████████████████████████████████████████████████████████| 96554023/96554023 [01:41<00:00, 953591.07it/s]
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████| 25005/25005 [00:00<00:00, 69099.62it/s]
crowd_uid features good_id position
100%|███████████████████████████████████████████████████████████████████████████████████████████████████████| 96554023/96554023 [01:40<00:00, 962509.75it/s]
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████| 25005/25005 [00:00<00:00, 67075.92it/s]
crowd_uid features good_id period_id
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████| 96554023/96554023 [01:31<00:00, 1051436.01it/s]
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████| 25005/25005 [00:00<00:00, 61713.53it/s]
crowd_uid features good_id wday
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████| 96554023/96554023 [01:33<00:00, 1032667.44it/s]
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████| 25005/25005 [00:00<00:00, 81622.65it/s]
crowd_uid features advertiser good_id
100%|███████████████████████████████████████████████████████████████████████████████████████████████████████| 96554023/96554023 [02:02<00:00, 791140.14it/s]
100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████| 22381/22381 [00:00<00:00, 101376.61it/s]
crowd_uid features advertiser request_day
100%|███████████████████████████████████████████████████████████████████████████████████████████████████████| 96554023/96554023 [02:06<00:00, 761905.17it/s]
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████| 22381/22381 [00:00<00:00, 62494.74it/s]
crowd_uid features advertiser position
100%|███████████████████████████████████████████████████████████████████████████████████████████████████████| 96554023/96554023 [02:12<00:00, 727899.02it/s]
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████| 22381/22381 [00:00<00:00, 43944.05it/s]
crowd_uid features advertiser period_id
100%|███████████████████████████████████████████████████████████████████████████████████████████████████████| 96554023/96554023 [02:01<00:00, 797844.90it/s]
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████| 22381/22381 [00:00<00:00, 50837.56it/s]
crowd_uid features advertiser wday
100%|███████████████████████████████████████████████████████████████████████████████████████████████████████| 96554023/96554023 [02:00<00:00, 800033.06it/s]
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████| 22381/22381 [00:00<00:00, 84322.37it/s]
crowd_uid features aid uid
100%|███████████████████████████████████████████████████████████████████████████████████████████████████████| 96554023/96554023 [03:30<00:00, 457648.85it/s]
100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████| 476263/476263 [00:26<00:00, 18224.23it/s]
history aid imp
96.21676078635225
56.00772200772201
history aid bid
97.76160315894086
110.04910763982762
history aid pctr
7.566338030832839
8.092801352691056
history aid quality_ecpm
149.72950908929565
158.06947520980145
history aid totalEcpm
893.0726865624272
1132.2439811372467
K-fold static: aid_imp
static done!
train done!
test done!
aid_imp_mean
aid_imp_median
aid_imp_std
aid_imp_min
aid_imp_max
avg of mean 24.642441760070845 11.867858410728353
avg of median 22.220231203629318 10.754054054054054
avg of std 13.205945447404128 6.595450306473299
avg of min 7.973571664855158 3.254440154440154
avg of max 50.32135768630972 26.042857142857144
K-fold static: good_id_imp
static done!
train done!
test done!
good_id_imp_mean
good_id_imp_median
good_id_imp_std
good_id_imp_min
good_id_imp_max
avg of mean 25.525185891344954 26.064342433284722
avg of median 8.602050942772081 9.433397683397683
avg of std 58.9108489296647 60.968982102439945
avg of min 1.7919580832663862 1.7312741312741313
avg of max 593.5971539624782 638.3189189189189
K-fold static: advertiser_imp
static done!
train done!
test done!
advertiser_imp_mean
advertiser_imp_median
advertiser_imp_std
advertiser_imp_min
advertiser_imp_max
avg of mean 25.51539534049128 18.586147294447017
avg of median 16.381521430934267 12.662355212355212
avg of std 26.433157592486047 18.451343731383744
avg of min 2.3274183639714567 1.5247104247104246
avg of max 133.2966778507632 88.23243243243243
good_id_advertiser_count
(169288, 124) (28490, 115)
good_id_aid_count
(169288, 124) (28490, 115)
good_id_ad_size_count
(169288, 124) (28490, 115)
good_id_ad_type_id_count
(169288, 124) (28490, 115)
good_id_good_id_size
(169288, 124) (28490, 115)
advertiser_good_id_count
(169288, 124) (28490, 115)
advertiser_aid_count
(169288, 124) (28490, 115)
advertiser_ad_size_count
(169288, 124) (28490, 115)
advertiser_ad_type_id_count
(169288, 124) (28490, 115)
advertiser_good_type_count
(169288, 124) (28490, 115)
advertiser_advertiser_size
(169288, 124) (28490, 115)
good_type_good_type_size
(169288, 124) (28490, 115)
aid_aid_size
(169288, 124) (28490, 115)
(169288, 124) (28490, 115) (96554023, 22)
['id', 'position', 'aid', 'imp_ad_size', 'pctr', 'quality_ecpm', 'totalEcpm', 'request_day', 'wday', 'bid_unique', 'imp', 'bid', 'crowd_direction', 'delivery_periods', 'is', 'create_timestamp', 'advertiser', 'good_id', 'good_type', 'ad_type_id', 'ad_size', 'age', 'gender', 'area', 'status', 'education', 'consuptionAbility', 'os', 'work', 'connectionType', 'behavior', 'periods_on_0', 'periods_on_1', 'periods_on_2', 'periods_on_3', 'periods_on_4', 'periods_on_5', 'periods_on_6', 'periods_on_7', 'periods_on_8', 'periods_on_9', 'periods_on_10', 'periods_on_11', 'periods_on_12', 'periods_on_13', 'periods_on_14', 'periods_on_15', 'periods_on_16', 'periods_on_17', 'periods_on_18', 'periods_on_19', 'periods_on_20', 'periods_on_21', 'periods_on_22', 'periods_on_23', 'periods_on_24', 'periods_on_25', 'periods_on_26', 'periods_on_27', 'periods_on_28', 'periods_on_29', 'periods_on_30', 'periods_on_31', 'periods_on_32', 'periods_on_33', 'periods_on_34', 'periods_on_35', 'periods_on_36', 'periods_on_37', 'periods_on_38', 'periods_on_39', 'periods_on_40', 'periods_on_41', 'periods_on_42', 'periods_on_43', 'periods_on_44', 'periods_on_45', 'periods_on_46', 'periods_on_47', 'periods_cont', 'history_aid_imp', 'history_aid_bid', 'history_aid_pctr', 'history_aid_quality_ecpm', 'history_aid_totalEcpm', 'aid_imp_mean', 'aid_imp_median', 'aid_imp_std', 'aid_imp_min', 'aid_imp_max', 'good_id_imp_mean', 'good_id_imp_median', 'good_id_imp_std', 'good_id_imp_min', 'good_id_imp_max', 'advertiser_imp_mean', 'advertiser_imp_median', 'advertiser_imp_std', 'advertiser_imp_min', 'advertiser_imp_max', 'good_id_advertisers', 'good_id_request_days', 'good_id_positions', 'good_id_period_ids', 'good_id_wdays', 'advertiser_good_ids', 'advertiser_request_days', 'advertiser_positions', 'advertiser_period_ids', 'advertiser_wdays', 'aid_uids', 'good_id_advertiser_count', 'good_id_aid_count', 'good_id_ad_size_count', 'good_id_ad_type_id_count', 'good_id_good_id_size', 'advertiser_good_id_count', 'advertiser_aid_count', 'advertiser_ad_size_count', 'advertiser_ad_type_id_count', 'advertiser_good_type_count', 'advertiser_advertiser_size', 'good_type_good_type_size', 'aid_aid_size']
********************************************************************************
save done!
w2v: uid good_id
12869549
training...
outputing...
总唯一值数: 25005, 存在于模型的词数: 25005
成功保存 25005 条特征到 uid_good_id_dev_w2v_64.pkl
w2v: uid advertiser
12869549
training...
outputing...
总唯一值数: 22381, 存在于模型的词数: 22381
成功保存 22381 条特征到 uid_advertiser_dev_w2v_64.pkl
w2v: uid aid
12869549
training...
outputing...
总唯一值数: 476263, 存在于模型的词数: 476263
成功保存 476263 条特征到 uid_aid_dev_w2v_64.pkl
deepwalk: uid aid
creating
100000
200000
300000
400000
500000
600000
700000
800000
900000
1000000
1100000
1200000
1300000
1400000
1500000
1600000
1700000
1800000
np.mean(length)=9.392208717697969
len(sentences)=1801783
training...
outputing...
总用户数: 1325520, 存在于模型的用户数: 1325520
uid uid_aid_uid_deepwalk_embedding_64_0 ... uid_aid_uid_deepwalk_embedding_64_62 uid_aid_uid_deepwalk_embedding_64_63
0 49834 0.091470 ... 0.038908 0.043628
1 59700 0.104850 ... 0.092650 0.005146
2 792422 0.541553 ... 0.405268 0.224636
3 760453 0.208635 ... -0.331279 -0.043797
4 816887 0.058720 ... -0.042519 0.024703
[5 rows x 65 columns]
aid uid_aid_aid_deepwalk_embedding_64_0 ... uid_aid_aid_deepwalk_embedding_64_62 uid_aid_aid_deepwalk_embedding_64_63
0 1 0.080659 ... -0.088329 0.032304
1 2 0.279591 ... -0.393883 0.129783
2 3 -0.016399 ... -0.023268 0.002961
3 4 0.330164 ... 0.711161 0.918560
4 5 0.022585 ... 0.019552 -0.008853
[5 rows x 65 columns]
deepwalk: uid good_id
creating
100000
200000
300000
400000
500000
600000
700000
800000
900000
1000000
1100000
1200000
1300000
np.mean(length)=8.646732799251163
len(sentences)=1367455
training...
outputing...
总用户数: 1341958, 存在于模型的用户数: 1341958
uid uid_good_id_uid_deepwalk_embedding_64_0 ... uid_good_id_uid_deepwalk_embedding_64_62 uid_good_id_uid_deepwalk_embedding_64_63
0 49834 0.120699 ... 0.001743 0.079942
1 59700 0.011453 ... -0.041451 0.007269
2 792422 0.093330 ... 0.035615 0.154115
3 760453 -0.027460 ... 0.070581 0.249192
4 816887 0.006679 ... 0.045785 0.040837
[5 rows x 65 columns]
good_id uid_good_id_good_id_deepwalk_embedding_64_0 ... uid_good_id_good_id_deepwalk_embedding_64_62 uid_good_id_good_id_deepwalk_embedding_64_63
0 2 0.202471 ... -0.125943 -0.394300
1 3 0.202687 ... 0.479792 -0.132403
2 5 0.264336 ... 0.146698 -0.121029
3 7 0.770270 ... 0.594474 -0.644727
4 12 0.390771 ... 0.508046 0.091341
[5 rows x 65 columns]
输出的特征数据文件和对应的特征数如下:
uid_advertiser_dev_w2v_64.pkl 22,381
uid_advertiser_test_w2v_64.pkl 22,972
uid_aid_dev_w2v_64.pkl
uid_aid_test_w2v_64.pkl
uid_good_id_dev_w2v_64.pkl
uid_good_id_good_id_dev_deepwalk_64.pkl
uid_good_id_good_id_test_deepwalk_64.pkl
uid_good_id_test_w2v_64.pkl
uid_good_id_uid_dev_deepwalk_64.pkl
uid_good_id_uid_test_deepwalk_64.pkl
uid_aid_aid_dev_deepwalk_64.pkl
uid_aid_aid_test_deepwalk_64.pkl
uid_aid_uid_dev_deepwalk_64.pkl
uid_aid_uid_test_deepwalk_64.pkl
3、转换数据格式
源码:src\convert_format.py
1)缺失值NA用0填充
2)将Word2Vec和DeepWalk得到的embedding拼接起来,并且覆盖到5%的广告
3)将需要用key-values的稠密特征正则化到[0,1]之间
data/train_dev.pkl data/dev.pkl
(169288, 124) (28490, 115)
history_aid_imp history_aid_bid history_aid_pctr ... good_type_good_type_size aid_aid_size create_timestamp
138980 0.916917 0.373373 0.673674 ... 1.000000 0.000000 0.941492
13478 0.916917 0.373373 0.673674 ... 1.000000 0.766266 0.256080
18219 0.000000 0.554054 0.143271 ... 1.000000 0.766266 0.256080
23339 0.589590 0.529029 0.390139 ... 1.000000 0.766266 0.256080
28631 0.480981 0.579079 0.358188 ... 1.000000 0.766266 0.256080
... ... ... ... ... ... ... ...
100976 0.335335 0.514014 0.939869 ... 0.135636 0.700200 0.540114
111822 0.335335 0.514014 0.994270 ... 0.135636 0.700200 0.540114
116715 0.246747 0.514014 0.896539 ... 0.135636 0.700200 0.540114
29450 0.916917 0.373373 0.673674 ... 0.135636 0.000000 0.537746
142229 0.916917 0.373373 0.673674 ... 1.000000 0.000000 0.949692
[169288 rows x 35 columns]
data/dev_NN.pkl (28490, 116)
w2v
(28490, 180)
(28490, 244)
(28490, 308)
deepwalk
(28490, 372)
(28490, 436)
data/train_dev_NN_0.pkl (169288, 124)
w2v
(169288, 189)
(169288, 253)
(169288, 317)
deepwalk
(169288, 381)
(169288, 445)
data/train.pkl data/test.pkl
(180880, 124) (20290, 114)
history_aid_imp history_aid_bid history_aid_pctr ... good_type_good_type_size aid_aid_size create_timestamp
138980 0.904404 0.377878 0.665165 ... 1.000000 0.000000 0.918190
13478 0.904404 0.377878 0.665165 ... 1.000000 0.805305 0.261158
18219 0.000000 0.570571 0.134571 ... 1.000000 0.805305 0.261158
23339 0.569570 0.542543 0.369358 ... 1.000000 0.805305 0.261158
28631 0.467968 0.599099 0.336954 ... 1.000000 0.805305 0.261158
... ... ... ... ... ... ... ...
100976 0.331331 0.530030 0.944492 ... 0.161161 0.697698 0.536189
111822 0.331331 0.530030 0.995672 ... 0.161161 0.697698 0.536189
116715 0.248749 0.530030 0.903287 ... 0.161161 0.697698 0.536189
29450 0.904404 0.377878 0.665165 ... 0.161161 0.000000 0.533650
142229 0.904404 0.377878 0.665165 ... 1.000000 0.000000 0.925855
[180880 rows x 35 columns]
data/test_NN.pkl (20290, 115)
w2v
(20290, 179)
(20290, 243)
(20290, 307)
deepwalk
(20290, 371)
(20290, 435)
data/train_NN_0.pkl (180880, 124)
w2v
(180880, 189)
(180880, 253)
(180880, 317)
deepwalk
(180880, 381)
(180880, 445)
输出文件
dev_NN.pkl
test_NN.pkl
train_dev_NN_0.pkl
train_NN_0.pkl
4、训练
python train-tf1.x.py
最后输出结果为submission
该代码实现了一个 CTR(点击率预估)模型,使用的是 TensorFlow 1.x 风格的静态计算图,主要包含 特征工程、模型构建、训练与评估 等模块。
1) 特征工程
代码定义了 4 类特征,用于模型输入:
特征类型 | 说明 | 示例 |
---|---|---|
single_features | 单值离散特征(直接 Embedding) | aid , gender , advertiser |
cross_features | 交叉特征(分解机提取交互信息) | aid × gender , advertiser × good_id |
multi_features | 多值离散特征(如用户历史行为序列) | aid_uids , behavior (空格分隔) |
dense_features | 稠密连续特征(直接输入 MLP) | uid_w2v_embedding_* (64 维向量) |
kv_features | Key-Value 特征(浮点数,归一化到 [0,1]) | history_aid_pctr , good_id_imp_median |
2) 模型架构(CIN 模型)
CIN(Compressed Interaction Network/压缩交互网络)是推荐系统领域中的一种模型结构,首次提出于xDeepFM(2018年)论文中,用于解决传统推荐模型(如FM、DNN)在特征交互学习上的局限性。它旨在显式地建模高阶特征交互,同时避免全连接DNN的冗余计算,特别适合处理高维稀疏特征(如用户行为、物品类别等)。关键组件:
- 输入层:接收 4 类特征(单值、交叉、多值、稠密)。
- Embedding 层:将离散特征映射为低维向量。
- 交叉层(CIN):通过压缩交互学习高阶特征组合。
- MLP 层:全连接网络处理稠密特征和交叉特征。
- 输出层:预测
imp
(曝光量,回归任务)或CTR
(分类任务)。
3) 训练流程
(1) 数据加载
train = pd.read_pickle('data/train_NN_0.pkl') # 训练集
dev = pd.read_pickle('data/dev_NN.pkl') # 验证集
test = pd.read_pickle('data/test_NN.pkl') # 测试集
- 数据格式为
.pkl
(Pandas DataFrame)。 - 对
imp
(曝光量)取对数归一化: train[‘imp’] = train[‘imp’].apply(lambda x: np.log(x + 1))
(2) 超参数配置
hparam = tf.contrib.training.HParams(
model='CIN',
hidden_size=[1024, 512], # MLP 层大小
cross_layer_sizes=[128, 128], # 交叉层大小
learning_rate=0.0002, # 学习率
batch_size=32, # 训练批次
...
)
(3) 模型训练(TF 1.x 风格)
# 构建计算图
tf.reset_default_graph()
model = CIN.Model(hparams)
sess = tf.Session(config=config_proto)
sess.run(tf.global_variables_initializer())
# 训练与验证
model.train(train_dev, dev) # 训练
dev_preds = model.infer(dev) # 预测
dev_preds = np.exp(dev_preds) - 1 # 还原对数变换
4) 交叉验证(5-Fold)
for i in range(5):
train_index, dev_index = split_data(K_fold, i)
model = ctrNet.build_model(hparam)
model.train(train.loc[train_index], train.loc[dev_index])
preds = model.infer(test) # 测试集预测
5)输出结果
- 训练/验证预测:保存为 CSV 文件:train_fea.to_csv(‘nn_pred_CIN_train.csv’)
dev_fea.to_csv(‘nn_pred_CIN_dev.csv’)
test_fea.to_csv(‘nn_pred_CIN_test.csv’) - 评估指标:默认使用
SMAPE
(对称平均绝对百分比误差)。
源码作者和开源协议
作者:团队-鱼遇雨欲语与余
成员:刘育源、郭达雅、王贺
四、参考资料
[2019腾讯广告算法大赛方案分享(初赛冠军) - 知乎](https://zhuanlan.zhihu.com/p/69351598)
作者:SYSU-MSRA/中山大学-微软亚洲研究院联合实验室 Daya Guo
五、获取案例套装
文件大小:约 3.8 GB
获取大容量AI案例套装:广告传媒业自然语言处理案例套装