摘要:
合集:AI案例-ML-零售业
赛题:商品销量智能预测挑战赛
主办方:科大讯飞股份有限公司
主页:http://challenge.xfyun.cn/topic/info?type=product-sales
AI问题:时间序列预测问题
数据集:商品历史销量数据和商品月订单数据。训练集83,788条数据,共53个时序特征和统计特征。测试集20,948条数据。
数据集价值:支持时间序列预测
解决方案:CatBoost、XGBoost和LightGBM三种梯度提升算法
一、赛题描述
随着企业持续产生的商品销量,其数据对于自身营销规划、市场分析、物流规划都有重要意义。但是销量预测的影响因素繁多,传统的基于统计的计量模型,比如时间序列模型等由于对现实的假设情况过多,导致预测结果较差。因此需要更加优秀的智能AI算法,以提高预测的准确性,从而助力企业降低库存成本、缩短交货周期、提高企业抗风险能力。本次大赛提供了商品销量历史数据作为训练样本,参赛选手需基于提供的样本构建模型,预测商品未来的销售量。
二、数据集说明
本次比赛为参赛选手提供了两类数据:商品历史销量数据和商品月订单数据。商品历史需求销量数据提供了商品编码、日期、是否促销、商品销售量。商品月订单数据提供了商品编码、商品类型、月份、订单数量、商品月初和月末库存量。(label空值的含义表示该商品当天无销量)。
商品历史销量数据
商品历史销量数据字段定义:字段名、数据类型、说明。
product_id, string, 商品编码
date, string, 日期
is_sale_day, int , 是否促销
label,float,商品销售量
数据样例:1.csv
商品id | 时间 | 总销量 | 浏览量 | 抖音转化率 | 视频个数 | 直播个数 | 直播销量 | 视频销量 | 视频达人 | 直播达人 |
---|---|---|---|---|---|---|---|---|---|---|
【七月妈妈】山东红薯粉条500g*3袋 | 2021/7/8 16:00 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
【七月妈妈】山东红薯粉条500g*3袋 | 2021/7/9 16:00 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
【七月妈妈】山东红薯粉条500g*3袋 | 2021/7/10 16:00 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
商品月订单数据
商品月订单表格字段定义: 字段名、数据类型、说明。
product_id, string, 商品编码
type, string, 商品类型
year,string, 年
month,string, 月
order, float, 商品月订单量
begin_stock, float, 商品月初库存
end stock, float, 商品月末库存
数据集版权许可协议
GPL-v3
三、解决方案样例
解决方案
源码:E_commerce_Sales_Forecast.ipynb
本案例的主要目标是基于历史销售数据构建模型,预测商品未来的销售量。系统使用了CatBoost、XGBoost和LightGBM三种梯度提升算法。
安装开发包
【本样例运行环境的关键版本信息】
python 3.12.3
lightgbm 3.3.0
catboost 1.2.7
xgboost 1.6.0
导入相关系统库
import pandas as pd
import numpy as np
import pickle as pkl
import os
from sklearn.model_selection import train_test_split
from catboost import CatBoostRegressor
from sklearn.model_selection import KFold
from bayes_opt import BayesianOptimization
from sklearn.metrics import mean_squared_error
import xgboost as xgb
import lightgbm as lgb
import matplotlib.pyplot as plt
主要工作流程
1 数据准备
- 从”./data”目录读取多个CSV文件
- 合并所有数据到一个DataFrame
- 处理时间字段,提取星期数
- 标记购物节日期(如双11、双12等促销时段)
path_list = os.listdir("./data")
csv_list = list()
for file_name in path_list:
df = pd.read_csv("./data/" + file_name)
df["是否为购物节"] = df_time["是否为购物节"].copy(deep=True)
df["前一周购物节天数"] = df_time["前一周购物节天数"].copy(deep=True)
df["未来一周购物节天数"] = df_time["未来一周购物节天数"].copy(deep=True)
generate_dataset(df)
csv_list.append(df)
dataset = pd.concat(csv_list, axis=0)
dataset_goods = list(dataset.商品id.unique())
2 特征工程
在时间序列分析中,滞后值(Lag Values) 是指将某个变量的历史值(过去时间点的观测值)作为当前时间点的特征。这种操作能够帮助模型捕捉数据的时序依赖关系,例如趋势、周期性和季节性。
滞后值是通过 shift()
方法生成的,表示将时间序列数据向后移动 n
个时间步。例如:
总销量_forward_1
= 昨天的销量(当前时间点前1天的值)总销量_forward_2
= 前天的销量(当前时间点前2天的值)- …
总销量_forward_14
= 14天前的销量(当前时间点前14天的值)
总销量backward_1 到 总销量backward_7为未来的数据,这些列用于计算 目标变量 “后七天平均销量”(即未来7天的日均销量)。
核心特征生成函数,主要功能:
- 生成时间序列特征:前1-14天的销量、浏览量等指标的滞后值。
- 计算统计特征:前一周/前两周的平均值、方差、最大值、最小值
- 计算比率特征:直播销量占比、视频销量占比等
- 添加购物节相关特征:前一周/未来一周的购物节天数
- 添加商品级别的聚合特征(总销量、总浏览量等)
- 生成销量变化趋势特征(平均值增量、方差值增量)
- 以上前1-14天的销量相关的特征生成后写入输入特征变量 train_dataset_x。生成未来7天平均销量(作为目标变量) 写入变量 train_dataset_y。
def generate_dataset(data_df: pd.DataFrame, cols=None):
if cols is None:
cols = ['总销量', '直播个数', '直播销量', '视频销量', '直播达人', '直播销量占比', '视频销量占比']
remove_cols = list()
data_df["时间"] = data_df["时间"].apply(lambda x: str(x).split(" ")[0])
data_df["时间"] = pd.to_datetime(data_df["时间"])
data_df["星期数"] = data_df["时间"].dt.isocalendar().week
data_df["星期数"] = data_df["星期数"].astype(int)
data_df["直播销量占比"] = data_df.apply(lambda x: 0.0 if x["总销量"] == 0 else x["直播销量"] / x["总销量"], axis=1)
data_df["视频销量占比"] = data_df.apply(lambda x: 0.0 if x["总销量"] == 0 else x["视频销量"] / x["总销量"], axis=1)
backward_list = list()
for name in cols:
forward_list = list()
for _idx in range(1, 15):
data_df[name + "_forward_" + str(_idx)] = data_df[name].shift(periods=_idx, fill_value=0)
remove_cols.append(name + "_forward_" + str(_idx))
forward_list.append(name + "_forward_" + str(_idx))
forward_list.append(name)
tmp_df_1 = data_df[forward_list[:7]]
tmp_df_2 = data_df[forward_list[7:]]
tmp_df = data_df[forward_list]
data_df[name + "_前第一周天平均值"] = tmp_df_1.mean(axis=1)
data_df[name + "_前第二周天平均值"] = tmp_df_2.mean(axis=1)
data_df[name + "_前第一周天方差值"] = tmp_df_1.mean(axis=1)
data_df[name + "_前第二周天方差值"] = tmp_df_2.mean(axis=1)
data_df[name + "_平均值增量"] = data_df[name + "_前第一周天平均值"] - data_df[name + "_前第二周天平均值"]
data_df[name + "_方差值增量"] = data_df[name + "_前第一周天方差值"] - data_df[name + "_前第二周天方差值"]
data_df[name + "_前两周最大值"] = tmp_df.max(axis=1)
data_df[name + "_前两周最小值"] = tmp_df.min(axis=1)
if name == "总销量":
for j in range(1, 8):
data_df[name + "_backward_" + str(j)] = data_df[name].shift(periods=j * (-1), fill_value=0)
backward_list.append(name + "_backward_" + str(j))
remove_cols.append(name + "_backward_" + str(j))
tmp_df = data_df[backward_list]
data_df["后七天平均销量"] = tmp_df.mean(axis=1)
data_df = data_df.copy() # 如果 DataFrame 已经高度碎片化,可以通过复制 DataFrame 来重新组织内存,消除碎片
for _idx, date_time in enumerate(["2021-12-26", "2021-12-25", "2021-12-24", "2021-12-23", "2021-12-22", "2021-12-21"]):
data_df.loc[data_df[data_df["时间"] == date_time].index, "后七天平均销量"] = data_df.loc[
data_df[data_df["时间"] == date_time].index, backward_list[:_idx + 1]].mean(axis=1)
data_df.drop(columns=remove_cols, inplace=True)
训练集、测试集和特征变量、输出/预测目标变量的定义如下。后七天平均销量为预测目标变量。
test_dataset = dataset_test.drop(columns=["后七天平均销量", "时间"])
dataset_train.drop(columns=["时间", "是否为0"], inplace=True)
train_dataset_x = dataset_train.drop(columns=["后七天平均销量"])
train_dataset_y = dataset_train["后七天平均销量"].copy(deep=True)
3 模型构建与训练
实现了三种模型:
- CatBoost:处理类别特征能力强的梯度提升算法
- XGBoost:高效的梯度提升决策树算法
- LightGBM:基于直方图的梯度提升算法,训练速度快
# 定义模型参数
params_catboost = {"iterations": 1000,
"learning_rate": 0.03,
"l2_leaf_reg": 3,
"bagging_temperature": 1,
"subsample": 0.66,
"random_strength": 1,
"depth": 6,
"rsm": 1,
"one_hot_max_size": 2,
"leaf_estimation_method": "Gradient",
"fold_len_multiplier": 2,
"border_count": 128,
"random_seed": 2022,
"loss_function": "RMSE",
"eval_metric": "RMSE",
"od_type": "Iter",
"od_wait": 50}
params_lightgbm = {"n_estimators": 300,
"learning_rate": 0.1,
"num_leaves": 500,
"max_depth": 6,
"min_data_in_leaf": 1000,
"lambda_l1": 1,
"lambda_l2": 1,
"min_gain_to_split": 1,
"bagging_fraction": 0.8,
"feature_fraction": 0.8,
"random_state": 2022,
"metric": "rmse"}
params_xgboost = {'booster': 'gbtree',
'objective': 'reg:squarederror',
'eval_metric': 'rmse',
'seed': 2022,
'learning_rate': 0.01,
'gamma': 0.1,
'min_child_weight': 1.1,
'max_depth': 5,
'lambda': 10,
'subsample': 0.7,
'colsample_bytree': 0.7,
'colsample_bylevel': 0.7,
'tree_method': 'exact'}
模型训练函数model_fun()
通用模型训练函数,支持:
- 数据分割(训练集/测试集)。训练数据:2021-07-08 到 2021-12-24。测试数据:2021-12-27。
- 不同模型的训练(CatBoost/XGBoost/LightGBM)
- 训练过程监控和评估
def model_fun(train_sample, train_label, params, model, model_name="catboost"):
print("------------------- 训练 {} 模型 -------------------".format(model_name))
x_train, x_test, y_train, y_test = train_test_split(train_sample, train_label, test_size=0.2, random_state=2022)
_model = model(**params)
print("x_train.shape: {}".format(x_train.shape))
print("y_train.shape: {}".format(y_train.shape))
print("x_test.shape: {}".format(x_test.shape))
print("y_test.shape: {}".format(y_test.shape))
_model.fit(x_train, y_train)
if model_name.lower() == "catboost":
_model.fit(x_train, y_train, eval_set=[(x_train, y_train), (x_test, y_test)], cat_features=["商品id"], verbose=50)
elif model_name.lower() == "lightgbm":
_model.fit(x_train, y_train, eval_set=[(x_train, y_train), (x_test, y_test)], eval_metric='rmse', verbose=50)
else:
_model.fit(x_train, y_train)
train_pred = _model.predict(x_train)
train_mse = mean_squared_error(y_train, train_pred)
test_pred = _model.predict(x_test)
test_mse = mean_squared_error(y_test, test_pred)
result_dic = {"train_mse": train_mse, "test_mse": test_mse}
return _model, result_dic
xgboost为例:
# xgboost
model_tree_xgb, model_result_xgb = model_fun(train_dataset_x, train_dataset_y, params_xgboost, xgb.XGBRegressor, "xgboost")
feature_importance_xgb = feature_importance_model(model_tree_xgb, "xgboost", train_columns)
feature_importance_xgb.head(20)
输出:
------------------- 训练 xgboost 模型 -------------------
x_train.shape : (83788, 53)
y_train.shape : (83788,)
x_test.shape : (20948, 53)
y_test.shape : (20948,)
输出前20种特征feature_importance_xgb.head(20)。其中总销量backward_1到总销量backward_7为预测后7天的销量值。
FeatureImportance | |
---|---|
总销量_前第二周天方差值 | 0.268957 |
总销量_前两周最小值 | 0.137244 |
总销量_前第二周天平均值 | 0.088891 |
总销量_前第一周天平均值 | 0.088605 |
总销量_backward_1 | 0.023013 |
总销量_backward_2 | 0.033763 |
总销量_backward_3 | 0.074188 |
总销量_backward_4 | 0.087193 |
总销量_backward_5 | 0.080564 |
总销量_backward_6 | 0.029972 |
总销量_backward_7 | 0.010891 |
总销量_前第一周天方差值 | 0.023166 |
总销量_前两周最大值 | 0.006882 |
总销量_forward_9 | 0.006779 |
总销量_forward_2 | 0.005585 |
总销量_forward_11 | 0.004291 |
直播达人 | 0.004564 |
浏览量_all | 0.004505 |
视频个数 | 0.003872 |
总销量 | 0.002667 |
4 模型评估与优化
函数 get_result() 执行完整的模型训练和预测流程:
- 10折交叉验证
- 模型训练
- 验证集评估
- 测试集预测
- 计算均方误差(MSE)作为评估指标
# 模型预测
def get_result(params_dic, model, model_name: str, x_data, y_data, test_data):
print("------------------------- get_result -------------------------")
ans = []
mean_score = 0
sk = KFold(n_splits=10, shuffle=True, random_state=2022)
_model = model(**params_dic)
for train_index, test_index in sk.split(x_data, y_data):
x_train = x_data.iloc[train_index]
y_train = y_data.iloc[train_index]
x_test = x_data.iloc[test_index]
y_test = y_data.iloc[test_index]
if model_name.lower() == "catboost":
regressor_model = _model.fit(x_train, y_train, eval_set=(x_test, y_test), verbose=500,
cat_features=["商品id"])
elif model_name.lower() == "lightgbm":
regressor_model = _model.fit(x_train, y_train, eval_set=(x_test, y_test), verbose=500)
else:
regressor_model = _model.fit(x_train, y_train)
y_pred = regressor_model.predict(x_test)
test_mse = mean_squared_error(y_test, y_pred)
print("model 验证MSE:{}".format(test_mse))
mean_score += test_mse / 10.
y_test_pred = regressor_model.predict(test_data)
ans.append(y_test_pred)
print("10折平均MSE:{}".format(mean_score))
model_pred = sum(ans) / 10.
return model_pred
5 预测与结果输出
- 对测试数据进行预测
- 将预测结果保存为CSV文件
result_fuse = pd.read_csv("./submission/_提交示例.csv")
result_fuse["未来一周天均销量"] = (result_cat["未来一周天均销量"] + result_lgbm["未来一周天均销量"] + result_xgb["未来一周天均销量"]) / 3.8
result_fuse.to_csv("./submission/result_fuse.csv", encoding="utf_8_sig", index=False)
result_xgboost.csv 数据样例:
商品id | 未来一周天均销量 |
---|---|
【七月妈妈】山东红薯粉条500g*3袋 | -0.554255605 |
【李老板】爆款 俄罗斯风味国产紫皮糖每包158g 拍1发5包 | 0.7535468339920044 |
45°帝王经典100ml五粮液股份有限公司出品 | 0.06480810791254044 |
【伦哥专享】21新米 十月稻田 长粒香米 2.5kg*4袋 | -0.504981279 |
【自嗨锅】粉丝福利酥麻牛肉粉自热锅 86g | -2.378837585 |
【池妈专属】池妈@鸭脖60g/根 原味or香辣79.9元/8根 | 2.0607380867004395 |
【宠粉款】大号葡式蛋挞皮90个 欣乐家庭烘培(1800g) | 0.22446861863136292 |
晟迪180g*2袋 香辣孜然三角骨 冷冻保存 顺丰包邮 | 0.06403211504220963 |
技术亮点
- 全面的特征工程:生成了丰富的时序特征和统计特征,考虑了购物节等特殊时段的影响。
- 多模型集成:同时使用三种主流梯度提升算法,可以利用不同算法的优势。
- 自动化调参:使用贝叶斯优化自动寻找最优超参数,减少人工调参工作量。
- 稳健的评估:采用10折交叉验证,确保模型评估结果的可靠性。
- 高效实现:利用CatBoost对类别特征的原生支持,减少了特征编码的工作量。
源码开源协议
GPL-v3