乘用车细分市场销量预测 竞赛 - DataFountain
本赛题需要参赛队伍根据给出的60款车型在22个细分市场(省份)的销量连续24个月(从2016年1月至2018年12月)的销量数据,建立销量预测模型;基于该模型预测同一款车型和相同细分市场在接下来一个季度连续4个月份的销量;除销量数据外,还提供同时期的用户互联网行为统计数据,包括:各细分市场每个车型的互联网搜索量数据等。
赛题理解
这是一个监督回归任务:
- 监督 :赛题给出过去2年(2016.1-2017.12)各车型、省份、车身的销量和搜索量数据,目标是预测未来4个月(2018.1~2018.4)的销量。
- 回归 :销量是一个连续变量,范围是1-15317。
赛题数据
数据集包含细分市场、时间和销量搜索量几个方面的信息。
pre_datatrain_sales_data.csv :初赛60车型训练数据。
datatrain_sales_data.csv: 复赛82车型训练数据。
dataevaluation_public.csv:测试集数据。
datatrain_search_data.csv:各细分市场的搜索量数据。
评价指标
采用归一化均方根误差的均值。首先单独计算每个车型在每个细分市场(省份)的NRMSE,再计算所有NRMSE的均值。
Score为最终评价指标,值为0-1之间,越接近1模型越准确。计算方式为:
由于该比赛已经结束,虽然无法提交测试集进行评分,但是在网上可以找到大牛们分享的代码和思想进行学习(在这里感谢大牛们的开源代码,让我受益匪浅)。
鉴于此,本次学习分享采用第过去4个月(第21-24月)的数据作为验证集进行评估,用整体预测和分月预测两种方式进行对比。
整体预测结果:初赛(60款车型)验证达分数为0.714,复赛(82款车型)验证达分数为0.713。
分月预测结果:初赛(60款车型)验证达分数达0.814,复赛(82款车型)验证达分数达0.806。
离散数据
月份:2016.01-2017.12,预测2018.1-2018.4
时间:1-28个月
省份:22个
车型:初赛60/复赛82
车身:4种
连续数据
销量:1-15317
搜索量:25-1552536
相关分析
在构造特征之前,对特征进行分析,发现时间特征和销量相关性最强,并绘制各月销量波动变化折线图。
参考:CCF BDCI 乘用车销量预测 冠军方案 - 知乎 (zhihu.com)
由于是时序问题,历史销量和销量的变化趋势是考虑的重点,可以围绕销量、搜索量、时间、细分市场这几个关键点进行特征构建。
因此,构建了历史月平移特征,差值特征,同比环比特征,趋势特征(增量、占比与涨幅)。
def get_stat_feature(df): ### 1.历史月特征:历史月和春节月特征 ''' 历史N月平移特征 ''' stat_feat = [] data = df.copy() '历史月销量' for i in range(1,17): tmp = data[['time_id','pro_id','model_id','body_id','salesVolume']] shifted = tmp.copy() shifted = shifted.rename(columns={'salesVolume':'last_{0}_sale'.format(i)}) shifted['time_id'] += i+2 data= pd.merge(data, shifted, on=['time_id','pro_id','model_id','body_id'], how='left') if i <= 6: stat_feat.append('last_{0}_sale'.format(i)) ... ... return data,stat_feat
提取出173个特征,通过lightGBM的特征重要性筛选出107个特征,还可以考虑特征和目标之间的相关性。
参考:2019CCF乘用车销量预测 (qq.com)
首先单独计算每个车型每个省份的NRMSE,再计算所有NRMSE的均值。
def score(data): pred = data.groupby(['pro_id', 'model_id'])['forecastVolum'].agg(lambda x: list(x)) label = data.groupby(['pro_id', 'model_id'])['salesVolume'].agg(lambda x: list(x)) label_mean = data.groupby(['pro_id', 'model_id'])['salesVolume'].agg(lambda x: np.mean(x)) data_agg = pd.Dataframe() data_agg['forecastVolum'] = pred data_agg['salesVolume'] = label data_agg['label_mean'] = label_mean nrmse_score = [] for raw in data_agg.values: nrmse_score.append(MSE(raw[0], raw[1]) ** 0.5 / raw[2]) return 1 - np.mean(nrmse_score)2.整体预测
在测试集上,用过去2年的数据作为训练集来预测未来连续4个月的销量。
在验证集上,用过去20个月的数据作为训练集预测过去连续4个月的销量。
62车型和82车型的验证分数都为0.71。
# 数据集划分 train_idx = df['time_id']<= 20 #1-20月 valid_idx = df['time_id'].between(21, 24) test_idx = df['time_id']> 24 #25-28月 train_x = df[train_idx][features] train_y = df[train_idx]['salesVolume'] valid_x = df[valid_idx][features] valid_y = df[valid_idx]['salesVolume'] x_test = df[test_idx][features]3.分月预测
在测试集上,首先得到1月份的结果,然后将1月份合并到训练集,预测2月份结果,然后是3月,4月。
在验证集上,首先得到第21月的结果,然后将第21月合并到训练集,预测第22月结果,然后是第23月,第24月。
初赛和复赛的验证分数都达到了0.8。
def LGB(input_data,is_get_82_model): if is_get_82_model == 0: input_data = input_data[input_data['new_model']==0] # 提取特征 df = input_data.copy() cate_feat = ['pro_id','body_id','model_id','month_id','jidu_id'] for i in cate_feat: df[i] = df[i].astype('category') features = [col for col in list(df.columns) if col not in ['salesVolume','new_model','id','time_id','forecastVolum']] print('features:',len(features)) # 1 划分数据集,验证 for month in [21,22,23,24]: train_idx = df['time_id'].between(1 , month-1) #20-23 valid_idx = df['time_id'].between(month, month) #21-24 train_x = df[train_idx][features] train_y = df[train_idx]['salesVolume'] valid_x = df[valid_idx][features] valid_y = df[valid_idx]['salesVolume'] model = lgb.LGBMRegressor( num_leaves=2**5-1, reg_alpha=0.55, reg_lambda=0.6, objective='mse', max_depth=-1, learning_rate=0.05, min_child_samples=20, random_state=2021, n_estimators=700, subsample=0.8, colsample_bytree=0.8 ) model.fit(df[train_idx][features], df[train_idx]['salesVolume'],eval_set=[(valid_x, valid_y)], categorical_feature=cate_feat,verbose=100) #注意缩进,含month的语句要在这个for循环里,才能实现分月预测 df['forecastVolum'] = model.predict(df[features]) df.loc[(df.time_id==month), 'forecastVolum'] = df[valid_idx]['forecastVolum'].apply(lambda x: 0 if x < 0 else x) df['forecastVolum'] = list(map(lambda x : x if x==np.NAN else (lg**(x))-1, df['forecastVolum'])) df['salesVolume'] = list(map(lambda x : x if x==np.NAN else (lg**(x))-1, df['salesVolume'])) print('NRMSE的均值:',score(data = df[df.time_id.between(21, 24)])) sub_cv=df[df.time_id.between(21, 24)][['salesVolume','forecastVolum']] # 2 预测测试集1-4月 df = input_data.copy() for month in [25,26,27,28]: all_idx = df['time_id'].between(1 , month-1) test_idx = df['time_id'].between(month, month) model.fit(df[all_idx][features], df[all_idx]['salesVolume'], categorical_feature=cate_feat) df['forecastVolum'] = model.predict(df[features]) df.loc[(df.time_id==month), 'salesVolume'] = df[test_idx]['forecastVolum'].apply(lambda x: 0 if x < 0 else x) df['salesVolume'] = list(map(lambda x : x if x==np.NAN else (lg**(x))-1, df['salesVolume'])) sub = df[df.time_id > 24][['id','salesVolume']] sub.columns = ['id','forecastVolum'] sub['id'] = sub['id'].map(int) sub['forecastVolum'] = sub['forecastVolum'].map(round) return sub_cv,sub
完整代码文件见Github:
YuQian629/CCF-Car_sales
最后,欢迎大家批评指正,您的建议或支持将是我持续学习分享的动力。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)