前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >广告行业中那些趣事系列51:超牛的kaggle比赛Favorita Grocery Sales Forecasting冠军方案

广告行业中那些趣事系列51:超牛的kaggle比赛Favorita Grocery Sales Forecasting冠军方案

作者头像
数据拾光者
发布2022-05-05 14:02:39
4610
发布2022-05-05 14:02:39
举报
文章被收录于专栏:数据拾光者数据拾光者

导读:本文是“数据拾光者”专栏的第五十一篇文章,这个系列将介绍在广告行业中自然语言处理和推荐系统实践。本篇分享了kaggle比赛《Corporación Favorita Grocery Sales Forecasting》冠军方案,对商品销量预测相关问题感兴趣的小伙伴可以一起沟通交流。

摘要:本篇分享了kaggle比赛《Corporación Favorita Grocery Sales Forecasting》冠军方案。因为业务需要所以调研了商品销量预测比赛,重点学习了冠军方案的特征工程和模型构建,其中关于时间滑动窗口特征的构建非常巧妙,受益匪浅。对商品销量预测相关问题感兴趣的小伙伴可以一起沟通交流。

下面主要按照如下思维导图进行学习分享:

01

比赛介绍及数据理解

最近因为工作原因需要调研下kaggle比赛《Corporación Favorita Grocery Sales Forecasting》top方案的特征和模型工作,可以借鉴并应用到实际业务中。很多时候我们的任务可能与kaggle中某个比赛是类似的,想又快又好的完成目标其中一条有效的方法就是参考大牛分享的方案。因为很多大牛在比赛打完之后会分享自己的源码,这样相比于我们自己去从0到1构建模型效率会提升很多。不仅如此,参考大牛的方案可以让我们了解当前业界对于此类问题优秀的方案,快速得到很好的baseline,然后快速迭代更新更容易出成果。

该比赛kaggle地址如下:

https://www.kaggle.com/c/favorita-grocery-sales-forecasting/overview

整体来看该比赛就是预测商品的销量,官方提供了2013-2017年各商店商品的销量,参赛队伍需要根据已有数据预测未来一段时间商店商品的销量,下面是每年的训练样本量:

图1 每年的训练样本量

每年各月的训练样本分布如下:

图2 每年各月的训练样本

对官方提供的数据进行整理,下面是数据说明和示例:

图3 数据说明和示例

其中id代表唯一key值,实际无用;date代表日期,store_nbr代表商店id,item_nbr代表商品id,预测的粒度也就是某个商店中的某个商品在某一天的销量;onpromotion代表当天商店的该商品是否在促销;紫色的四个字段都是商店的特征,其中city代表市,state代表州,tpye代表商店层级,一共有A-E五个等级,cluster代表相似商店分组,一共有17个分组;红色的三个字段是商品的特征,其中family是商品分类,总共有33个分类,class代表商品小类,有337个小类别,perishable代表商品是否容易变质;oil_price代表油价,day_type代表假日类型。

02

详解冠军方案

冠军方案介绍地址如下:

https://www.kaggle.com/c/favorita-grocery-sales-forecasting/discussion/47582

2.1 样本选择

虽然官方提供了2013-2017的训练样本,但是作者仅使用了2017年的训练样本。详细如下:

训练样本:20170531 - 20170719 or 20170614 - 20170719

验证样本:20170726 - 20170810

测试集:20170816 - 20170820

2.2 特征工程

整体来看特征包括两大块,第一块是基本特征,第二块是时间滑动窗口特征。

(1)基本特征

基本特征主要包括item_nbr、family、class、perishable、store_nbr、city、state、type、cluster。

(2) 时间滑动窗口特征

这里重点研究时间滑动窗口特征。作者使用num_day个滑动窗口,分别统计item-store粒度、item粒度和store-class粒度的时间滑动窗口特征,关于时间滑动窗口特征介绍如下:

图4 时间滑动窗口特征介绍如下

时间滑动窗口具体特征如下:

  • 时间窗口内(最近14/60/140天):促销天数汇总;
  • 时间窗口内(后3/7/14天):促销天数汇总。这里需要介绍下为什么可以使用之后的促销天数数据,因为在测试集中官方已经给出了未来一段时间某商店某商品是否会进行促销,所以我们可以用未来几天促销的数据;
  • 时间窗口内(最近3/7/14/30/60/140天):
    • 销量差值的均值,以时间窗口最近3天为例,用第二天的销量减去第一天的销量,再用第三天的销量减去第二天的销量,将两者取均值就可以得到销量差值的均值,这个特征可以理解为想查看每天的销量增长率;
    • 销量每天按0.9衰减之后汇总,以时间窗口3天为例,最近一天销量不变,最近第二天的销量乘以衰减系数0.9,最近第三天的销量乘以衰减系数0.81,然后将三天衰减之后的销量相加;
    • 均值、中位数、最小值、最大值和标准差;
  • 时间窗口内(上一周最近3/7/14/30/60/140天):和前一天销量差值的均值、销量每天按0.9衰减之后汇总、均值、中位数、最小值、最大值和标准偏差。这个特征和上一个特征是一样的,只不过计算的是上一周各个特征值,作者想查看前一周的销量各个特征;
  • 时间窗口内(最近7/14/30/60/140天):
    • 有销量/促销的天数,分别查看时间窗口内有销量和促销的天数,以时间窗口3天为例,如果这三天都有销量,那么为3;
    • 距离上次有销量/促销的天数,以时间窗口3天为例,上一次有销量是昨天,那么该值为1。这个特征主要是查看上一次有销量或者促销对未来商品销量的影响,以促销为例,有些商品近期才做过促销,可能未来几天的销量就会受影响;
    • 距离最早有销量/促销的天数,以时间窗口3天为例,最早有销量是最近第三天,那么该值为3;
  • 时间窗口内(后15天)促销的天数、距离上次促销的天数、距离最早促销的天数,这个特征和上一个特征类似,只不过查看未来15天各个特征情况;
  • 时间窗口内(最近15天)当天的销量;
  • 最近4周时间窗口为(每周1-每周日)的销量均值,比如最近4周每周1的销量均值;
  • 最近20周时间窗口为(每周1-每周日)的销量均值,比如最近20周每周1的销量均值;
  • 时间窗口内(前16到后15天)每天是否促销。

特征加工代码如下:

代码语言:javascript
复制
# 计算不同时间窗口的特征
def get_timespan(df, dt, minus, periods,freq='D'):
   df_result = df[pd.date_range(dt - timedelta(days=minus),periods=periods, freq=freq)]
   return df_result
 
def prepare_dataset(df, promo_df, t2017,is_train=True, name_prefix=None):
    X= {
       # 以t2017为起点,最近14/60/140天促销汇总
       "promo_14_2017": get_timespan(promo_df, t2017, 14,14).sum(axis=1).values,
       "promo_60_2017": get_timespan(promo_df, t2017, 60,60).sum(axis=1).values,
       "promo_140_2017": get_timespan(promo_df, t2017, 140,140).sum(axis=1).values,
       # 以t2017为起点,后3/7/14天促销汇总
       "promo_3_2017_aft": get_timespan(promo_df, t2017 +timedelta(days=16), 15, 3).sum(axis=1).values,
       "promo_7_2017_aft": get_timespan(promo_df, t2017 +timedelta(days=16), 15, 7).sum(axis=1).values,
       "promo_14_2017_aft": get_timespan(promo_df, t2017 +timedelta(days=16), 15, 14).sum(axis=1).values,
    }
   
    #t2017为起点
   for i in [3, 7, 14, 30, 60, 140]:
       tmp = get_timespan(df, t2017, i, i)
       # 最近i天里和前一天销量差值的均值
       X['diff_%s_mean' % i] = tmp.diff(axis=1).mean(axis=1).values
       # 最近i天里销量每天按0.9衰减之后汇总       *******************************
       X['mean_%s_decay' % i] = (tmp * np.power(0.9,np.arange(i)[::-1])).sum(axis=1).values
       # 最近i天里均值、中位数、最小值、最大值和标准偏差
       X['mean_%s' % i] = tmp.mean(axis=1).values
       X['median_%s' % i] = tmp.median(axis=1).values
       X['min_%s' % i] = tmp.min(axis=1).values
       X['max_%s' % i] = tmp.max(axis=1).values
       X['std_%s' % i] = tmp.std(axis=1).values
   
    #t2017上一周,前i天各指标值,和上面是一样的
   for i in [3, 7, 14, 30, 60, 140]:
       tmp = get_timespan(df, t2017 + timedelta(days=-7), i, i)
       X['diff_%s_mean_2' % i] = tmp.diff(axis=1).mean(axis=1).values
       X['mean_%s_decay_2' % i] = (tmp * np.power(0.9,np.arange(i)[::-1])).sum(axis=1).values
       X['mean_%s_2' % i] = tmp.mean(axis=1).values
       X['median_%s_2' % i] =tmp.median(axis=1).values
       X['min_%s_2' % i] = tmp.min(axis=1).values
       X['max_%s_2' % i] = tmp.max(axis=1).values
       X['std_%s_2' % i] = tmp.std(axis=1).values
   
    #t2017为起点,最近i天内有销量/促销的天数、距离上次有销量的天数、距离最早有销量的天数
   for i in [7, 14, 30, 60, 140]:
       tmp = get_timespan(df, t2017, i, i)
       # 最近i天内有销量的天数
       X['has_sales_days_in_last_%s' % i] = (tmp > 0).sum(axis=1).values
       # 最近i天内距离上次有销量的天数,如果都没有销量则为i
       X['last_has_sales_day_in_last_%s' % i] = i - ((tmp > 0) *np.arange(i)).max(axis=1).values
       # 最近i天内距离最早有销量的天数
       X['first_has_sales_day_in_last_%s' % i] = ((tmp > 0) * np.arange(i,0, -1)).max(axis=1).values
       tmp = get_timespan(promo_df, t2017, i, i)
       X['has_promo_days_in_last_%s' % i] = (tmp > 0).sum(axis=1).values
       X['last_has_promo_day_in_last_%s' % i] = i - ((tmp > 0) *np.arange(i)).max(axis=1).values
       X['first_has_promo_day_in_last_%s' % i] = ((tmp > 0) * np.arange(i,0, -1)).max(axis=1).values
   
    #t2017为起点,未来15天内有促销的天数、距离上次有促销的天数、距离最早有促销的天数
   tmp = get_timespan(promo_df, t2017 + timedelta(days=16), 15, 15)
   X['has_promo_days_in_after_15_days'] = (tmp > 0).sum(axis=1).values
   X['last_has_promo_day_in_after_15_days'] = i - ((tmp > 0) *np.arange(15)).max(axis=1).values
   X['first_has_promo_day_in_after_15_days'] = ((tmp > 0) *np.arange(15, 0, -1)).max(axis=1).values
   
    #t2017为起点,前i天当天的销量
   for i in range(1, 16):
       X['day_%s_2017' % i] = get_timespan(df, t2017, i, 1).values.ravel()
   
    #t2017为起点,最近4/20周时间窗口为(每周1-每周日)的销量均值,比如最近4周每周周1的均值;
   for i in range(7):
       X['mean_4_dow{}_2017'.format(i)] = get_timespan(df, t2017, 28-i, 4,freq='7D').mean(axis=1).values
       X['mean_20_dow{}_2017'.format(i)] = get_timespan(df, t2017, 140-i, 20,freq='7D').mean(axis=1).values
   
    #t2017为起点,前后16天当天促销
   for i in range(-16, 16):
       # 需要把t2017 + timedelta(days=i) 转化成str格式,否则会报错
       X["promo_{}".format(i)] = promo_df[str(t2017 +timedelta(days=i))].values.astype(np.uint8)
 
    X= pd.DataFrame(X)
    #y是未来16天当天销量
   if is_train:
       y = df[pd.date_range(t2017, periods=16)].values
       return X, y
   if name_prefix is not None:
        X.columns = ['%s_%s' % (name_prefix, c) forc in X.columns]
   return X
 
print("Preparing dataset...")
#num_days = 8
num_days = 2
t2017 = date(2017, 5, 31)
X_l, y_l = [], []
for i in range(num_days):
   delta = timedelta(days=7 * i)
    #store_nbr-item_nbr粒度
   X_tmp, y_tmp = prepare_dataset(df_2017, promo_2017, t2017 + delta)
    #item_nbr粒度
   X_tmp2 = prepare_dataset(df_2017_item, promo_2017_item, t2017 + delta,is_train=False, name_prefix='item')
   X_tmp2.index = df_2017_item.index
   X_tmp2 =X_tmp2.reindex(df_2017.index.get_level_values(1)).reset_index(drop=True)
    #store-class粒度
   X_tmp3 = prepare_dataset(df_2017_store_class, df_2017_promo_store_class,t2017 + delta, is_train=False, name_prefix='store_class')
    X_tmp3.index = df_2017_store_class.index
   
    #构建多重索引必须从这里pd.MultiIndex.from_frame,源代码会报错
   X_tmp3 =X_tmp3.reindex(pd.MultiIndex.from_frame(df_2017_store_class_index)).reset_index(drop=True)
   X_tmp3
   
    #将不同粒度的训练数据合并
   X_tmp = pd.concat([X_tmp, X_tmp2, X_tmp3, items.reset_index(),stores.reset_index()], axis=1)
   
   X_l.append(X_tmp)
y_l.append(y_tmp)

2.3 模型构建

作者分别使用lgb和nn构建模型,最后通过加权求和的方式得到最终结果。

(1) 单模型效果

  • model_1 : 0.506 / 0.511 , 16 lgb modelstrained for each day source code;
  • model_2 : 0.507 / 0.513 , 16 nn modelstrained for each day source code;
  • model3 : 0.512 / 0.515,1 lgb model for 16 days with almost same features as model1;
  • model_4 : 0.517 / 0.519,1 nn model based on @sjv's code

其中mode1和model3使用的是传统lgb模型,model2和model4使用的是神经网络模型,下面是神经网络模型结构:

图5 神经网络模型结构

作者使用LSTM作为特征抽取器,后面再加全连接层。因为当时比赛时间比较早,Transformer还没被使用,如果现在要应用到实际业务中,将LSTM替换为Transformer可能会提升模型效果。

神经网络模型构建源码如下:

代码语言:javascript
复制
def build_model():
   model = Sequential()
   model.add(LSTM(512, input_shape=(X_train.shape[1],X_train.shape[2])))
   model.add(BatchNormalization())
   model.add(Dropout(.2))
 
   model.add(Dense(256))
   model.add(PReLU())
   model.add(BatchNormalization())
   model.add(Dropout(.1))
 
   model.add(Dense(256))
   model.add(PReLU())
   model.add(BatchNormalization())
   model.add(Dropout(.1))
 
   model.add(Dense(128))
   model.add(PReLU())
   model.add(BatchNormalization())
   model.add(Dropout(.05))
 
   model.add(Dense(64))
   model.add(PReLU())
   model.add(BatchNormalization())
   model.add(Dropout(.05))
 
   model.add(Dense(32))
   model.add(PReLU())
   model.add(BatchNormalization())
   model.add(Dropout(.05))
 
   model.add(Dense(16))
   model.add(PReLU())
   model.add(BatchNormalization())
   model.add(Dropout(.05))
   model.add(Dense(1))
   return model

(2) 模型融合

作者通过加权求和的方式将多模型结果进行融合,这也是kaggle比赛提分的套路了,最终提交的结果是:

finalmodel=0.42*model1 + 0.28 * model2 +0.18 * model3 + 0.12 * model4

03

其他top方案

整理了该比赛其他top2-top6的方案,感兴趣的小伙伴可以好好学习下:

  • 2st方案:https://www.kaggle.com/c/favorita-grocery-sales-forecasting/discussion/47568#latest-278474
  • 3st方案:https://www.kaggle.com/c/favorita-grocery-sales-forecasting/discussion/47560#latest-302253
  • 4st方案:https://www.kaggle.com/c/favorita-grocery-sales-forecasting/discussion/47529#latest-271077
  • 5st 方案:https://github.com/LenzDu/Kaggle-Competition-Favorita
  • 6st方案:https://www.kaggle.com/c/favorita-grocery-sales-forecasting/discussion/47575#latest-269568

04

总结及反思

本篇分享了kaggle比赛《Corporación Favorita Grocery Sales Forecasting》冠军方案。因为业务需要所以调研了商品销量预测比赛,重点学习了冠军方案的特征工程和模型构建,其中关于时间滑动窗口特征的构建非常巧妙,受益匪浅。对商品销量预测相关问题感兴趣的小伙伴可以一起沟通交流。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-03-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 数据拾光者 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
NLP 服务
NLP 服务(Natural Language Process,NLP)深度整合了腾讯内部的 NLP 技术,提供多项智能文本处理和文本生成能力,包括词法分析、相似词召回、词相似度、句子相似度、文本润色、句子纠错、文本补全、句子生成等。满足各行业的文本智能需求。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档