背景
本项目是天池的一个比赛,由阿里妈妈和天池大数据众智平台举办的广告预测算法大赛,本次参赛人数5200多个队伍,而我们只取得了731的成绩,最遗憾的是当我们写好CNN预测结果准备上传,队伍却意外解散,哈哈。虽然没-能进入复赛,但是过程还是很有意思,特此记录一下。
目标
本次比赛以阿里电商广告为研究对象,提供了淘宝平台的海量真实交易数据,参赛选手通过人工智能技术构建预测模型预估用户的购买意向,即给定广告点击相关的用户(user)、广告商品(ad)、检索词(query)、上下文内容(context)、商店(shop)等信息的条件下预测广告产生购买行为的概率(pCVR),形式化定义为:pCVR=P(conversion=1/query, user, ad, context, shop)。
结合淘宝平台的业务场景和不同的流量特点,我们定义了以下两类挑战:
- (1)日常的转化率预估
- (2)特殊日期的转化率预估
评估指标, 公式如下:
通过logarithmic loss(记为logloss)评估模型效果(越小越好)
其中N表示测试集样本数量,yi表示测试集中第i个样本的真实标签,pi表示第i个样本的预估转化率。
数据说明
本次比赛为参赛选手提供了5类数据(基础数据、广告商品信息、用户信息、上下文信息和店铺信息)。基础数据表提供了搜索广告最基本的信息,以及“是否交易”的标记。广告商品信息、用户信息、上下文信息和店铺信息等4类数据,提供了对转化率预估可能有帮助的辅助信息。
1 . 基础数据
字段 | 解释 |
---|---|
instance_id | 样本编号,Long |
is_trade | 是否交易的标记位,Int类型;取值是0或者1,其中1 表示这条样本最终产生交易,0 表示没有交易 |
item_id | 广告商品编号,Long类型 |
user_id | 用户的编号,Long类型 |
context_id | 上下文信息的编号,Long类型 |
shop_id | 店铺的编号,Long类型 |
2. 广告商品信息
字段 | 解释 |
---|---|
item_id | 广告商品编号,Long类型 |
item_category_list | 广告商品的的类目列表,String类型;从根类目(最粗略的一级类目)向叶子类目(最精细的类目)依次排列,数据拼接格式为 “category_0;category_1;category_2”,其中 category_1 是 category_0 的子类目,category_2 是 category_1 的子类目 |
item_property_list | 广告商品的属性列表,String类型;数据拼接格式为 “property_0;property_1;property_2”,各个属性没有从属关系 |
item_brand_id | 广告商品的品牌编号,Long类型 |
item_city_id | 广告商品的城市编号,Long类型 |
item_price_level | 广告商品的价格等级,Int类型;取值从0开始,数值越大表示价格越高 |
item_sales_level | 广告商品的销量等级,Int类型;取值从0开始,数值越大表示销量越大 |
item_collected_level | 广告商品被收藏次数的等级,Int类型;取值从0开始,数值越大表示被收藏次数越大 |
item_pv_level | 广告商品被展示次数的等级,Int类型;取值从0开始,数值越大表示被展示次数越大 |
3. 用户信息
字段 | 解释 |
---|---|
user_id | 用户的编号,Long类型 |
user_gender_id | 用户的预测性别编号,Int类型;0表示女性用户,1表示男性用户,2表示家庭用户 |
user_age_level | 用户的预测年龄等级,Int类型;数值越大表示年龄越大 |
user_occupation_id | 用户的预测职业编号,Int类型 |
user_star_level | 用户的星级编号,Int类型;数值越大表示用户的星级越高 |
4. 上下文信息
字段 | 解释 |
---|---|
context_id | 上下文信息的编号,Long类型 |
context_timestamp | 广告商品的展示时间,Long类型;取值是以秒为单位的Unix时间戳,以1天为单位对时间戳进行了偏移 |
context_page_id | 广告商品的展示页面编号,Int类型;取值从1开始,依次增加;在一次搜索的展示结果中第一屏的编号为1,第二屏的编号为2 |
predict_category_property | 根据查询词预测的类目属性列表,String类型;数据拼接格式为 “category_A:property_A_1,property_A_2,property_A_3;category_B:-1;category_C:property_C_1,property_C_2” ,其中 category_A、category_B、category_C 是预测的三个类目;property_B 取值为-1,表示预测的第二个类目 category_B 没有对应的预测属性 |
5. 店铺信息
字段 | 解释 |
---|---|
shop_id | 店铺的编号,Long类型 |
shop_review_num_level | 店铺的评价数量等级,Int类型;取值从0开始,数值越大表示评价数量越多 |
shop_review_positive_rate | 店铺的好评率,Double类型;取值在0到1之间,数值越大表示好评率越高 |
shop_star_level | 店铺的星级编号,Int类型;取值从0开始,数值越大表示店铺的星级越高 |
shop_score_service | 店铺的服务态度评分,Double类型;取值在0到1之间,数值越大表示评分越高 |
shop_score_delivery | 店铺的物流服务评分,Double类型;取值在0到1之间,数值越大表示评分越高 |
shop_score_description | 店铺的描述相符评分,Double类型;取值在0到1之间,数值越大表示评分越高 |
思路
我们的实验思路如下:
统计分析 -> 数据预处理 -> 特征抽取 -> 特征表示 -> 模型拟合和预测 -> 模型选择
其实从实验思路我们可以明显看出特征工程在这次比赛尤为重要,只有刻画好特征,才能利用模型得到好的预测结果,接下来我将按照实验思路进行总结。
实验
1. 统计分析
目的: 看清数据分布,了解广告、商品、店铺、用户与购买概率的关系
基础数据的统计分析(饼图、柱状图和折线图结合) ,将数据按照is_trade属性分为两张子表,分别进行对比统计分析
购买的用户分析:
单变量:性别分布、年龄分布、职业分布、星级分布
交叉变量:(重点)性别-年龄、性别-星级、年龄-星级、职业-星级,(参考)年龄-职业,性别-职业购买的商品分布对比
(重点)标签分布、属性分布、品牌分布、价格分布、销量分布,展示次数(后四项需考虑粒度的粗细)
(参考)城市分布、收藏次数分布上下文信息对比
(重点)时间戳分布
(参考)页面分布(看能否精确到类别)、预测类目的准确度(?)购买的店铺分布对比
(重点)评论数分布,好评率分布、星级分布
(参考)服务评分、物流评分、描述评分
实施:利用R对数据分布进行了统计
结果如下
以在不同属性上is_trade=0/1为例, 简要分析
- 1.转化分布
从上图可以明显看出在给定情景下转化率很低,也就是说,我们的训练数据存在了极度平衡的现象,甚至是可以把购买理解成异常值,我们的算法要能够极好的检测出异常实例。
- 2.年龄,性别,星级 分布
从图1可以明显看出,年龄越大,转化率先增加后减少(-1表示未知年龄),这个结果与我们常识一致,中间年龄段更具有消费能力, 性别转化分布没有贴出来,结果跟我们常识也是一致的,女性转化率高于男性。从图2中可以看出,星级越高购买率相对要更高一些,但是差距不太明显。
- 3.价格,收藏,展示 分布
图1,可以看出价格越高转化率先增加后降低,这与我们对电商平台的认知有关,价格太低必然会让人觉得物品质量不佳,但是随着价格增加,购买会带来更高的风险,转化率自然会降低。图2收藏次数越高,购买的可能性越大,收藏在电商市场的本质,就是商品入选了用户的购买集,对相关商品综合排序后,收藏的商品更有可能转化。图3,总体趋势是展示次数(广告效应)越多,购买率越高。
- 4.商店星级,评论数量 分布
图1.商店星级差异不明显。图2.评论数量居中的购买率更高
- 5 城市 | 商品标签 分布
这两幅图是仅仅选择了高频的城市和商标分布,可以看出城市和商品图,都有集中表现类,而商品更为明显。
总结: 数据统计分析的目的是分析变量之间的关系,观察具体特征对转化率的影响,从而用于模型中初始化权重
2. 数据预处理
处理缺失值
主要处理缺失值,以及属性值为-1的值,因为后期特征表示时,我们调用的sklearn借口进行one-hot表征,而接口要求输入数据不包括负数特征映射
由于城市和商品的值字段太长,在表征时会出现错误,因此将他们分别映射,并更新原始数据,代码如下:
def map_field(train_data, test_data, path):
train_data = set(train_data.unique())
test_data = set(test_data.unique())
all_property = list(train_data.union(test_data))
print(len(all_property))
map_data = {}
for i in range(len(all_property)):
map_data[str(all_property[i])] = i
with open(path, "w", encoding="utf-8") as dump:
json.dump(map_data, dump)
return map_data
def update_data(raw_data, field_1, field_2, path_1, path_2):
with open(path_1, "r", encoding="utf-8") as dump:
update_field_1 = json.load(dump)
with open(path_2, "r", encoding="utf-8") as dump:
update_field_2 = json.load(dump)
raw_data[field_1] = raw_data[field_1].apply(lambda x: update_field_1[str(x)])
raw_data[field_2] = raw_data[field_2].apply(lambda x: update_field_2[str(x)])
return raw_data
3. 特征抽取
本小节主要涉及对特征的转化和抽取,比如在上下文中的时间轴数据,考虑到节假日流量问题(比赛提出的挑战解决方法),我节假日和周末前后时间戳进行映射, 代码见下:
def convert_interval_time(hour, size=3):
"""
方法:把一天24小时按照力粒度为size大小进行分割
:param hour:
:param size:
:return:
"""
interval_time = [list(range(i, i + size)) for i in range(0, 24, size)]
interval_time_factor = {}
for i in range(len(interval_time)):
interval_time_factor[i] = interval_time[i]
for factor, h in interval_time_factor.items():
if hour in h:
return factor
else:
pass
def time_to_factor(self):
"""
方法:将时间戳转为区间因子
tips: add two features, is_weekend and is_holiday
:param data: 输入带时间戳的数据,
:param size:将原始时间戳转化为粒度为size个小时一个因子,默认为size=3
:return: 对于时间戳转为化为区间因子
"""
data_time = self.raw_data[self.modify_features["time"]]
data_time["format_time"] = data_time["context_timestamp"].apply(lambda x: time.localtime(x))
convert_data_time = pd.DataFrame(columns=["interval_time", "is_weekends", "is_holidays"])
convert_data_time["is_weekends"] = \
data_time["format_time"].apply(lambda x: 1 if x[6] in self.weekends else 0)
conv