所有文章 > AI驱动 > 突破XGBoost!时间序列预测 !!

突破XGBoost!时间序列预测 !!

1. 问题定义

假设我们有一个零售公司,该公司的目标是预测未来7天的销售量

数据集包括以下几列:

  • 日期:具体的销售日期
  • 销售量(Sales):每天的销售量数据
  • 天气(Weather):当天的天气情况(如晴天、多云、雨天等)
  • 节假日(Holiday):是否为节假日(1表示是,0表示否)
  • 促销(Promotion):是否有促销活动(1表示有,0表示无)

目标是利用历史数据构建一个模型,能够通过学习销售量的历史规律,结合天气、促销、节假日等因素,预测未来一段时间内的销售量

可能会遇到的业务挑战

  • 非线性因素:如促销活动对销售量的影响具有显著的非线性关系,而天气等因素的影响可能更加复杂。
  • 时间依赖性:销售数据具有显著的时间依赖性,可能存在周期性、趋势性和季节性规律。
  • 多维特征:除了时间序列本身的特征外,还有如天气、促销、节假日等外部因素的影响,如何有效融合这些信息是模型设计中的一个关键问题。

2. XGBoost与时间序列模型结合

XGBoost 是一种基于梯度提升决策树(GBDT)的集成算法。它通过逐步训练一系列弱学习器(决策树),并对每个弱学习器进行加权求和来形成最终的预测结果。与其他回归模型相比,XGBoost在面对高维特征和非线性关系时表现较好。此外,XGBoost通过加权和正则化手段,能有效避免过拟合问题。

XGBoost的目标函数可以表示为:

其中:

每一轮训练,XGBoost都会构建一棵新的树来修正上一棵树的残差,最终模型是所有树预测结果的加权和。

XGBoost 结合时间序列模型

在时间序列问题中,传统的模型(如ARIMA)假设数据是平稳的,并基于过去的时序模式进行预测。而在XGBoost中,我们通过生成滞后特征将时间序列问题转化为一个标准的回归问题。

假设我们要预测第 t天的销售量yt ,我们可以用前几天的销售量以及其他因素(如天气、促销、节假日)作为特征:

其中:

通过这种方式,我们将时间序列问题转化为一个回归问题,并利用XGBoost的强大性能来进行预测。

时间序列中的窗口特征

在处理时间序列问题时,常用的特征生成方式包括滞后特征(Lag Features) 和 滚动窗口特征(Rolling Window Features)

3. 模型原理

为了进一步细化,假设我们定义的时间序列回归模型的目标是根据前  p天的销售数据以及其他特征预测第 t天的销售量:

其中:

XGBoost中,模型通过不断迭代构建弱学习器(决策树)来拟合销售数据和其他特征的关系,预测值是所有树预测结果的加权和:

其中:

4. 销售数据集

这里,我们生成一个虚拟的销售数据集来模拟真实的销售情况。

该数据集将包含以下特征:

  • 日期(Date)
  • 销售量(Sales)
  • 天气(Weather)
  • 节假日(Holiday)
  • 促销(Promotion)

我们假设天气、节假日和促销都会影响每天的销售量,并且销售量具有一定的周期性(如每月的波动)。

生成虚拟数据集:

import pandas as pd
import numpy as np
import random

# 生成日期范围
dates = pd.date_range(start='2022-01-01', periods=1000, freq='D')

# 模拟销售量数据,假设其具有周期性和随机波动
np.random.seed(42)
sales = 200 + 10 * np.sin(np.arange(len(dates)) / 30) + np.random.normal(0, 20, len(dates))

# 随机生成天气、节假日和促销数据
weather = np.random.choice(['Sunny', 'Rainy', 'Cloudy'], size=len(dates))
holiday = np.random.choice([0, 1], size=len(dates), p=[0.9, 0.1]) # 10% 是节假日
promotion = np.random.choice([0, 1], size=len(dates), p=[0.8, 0.2]) # 20% 有促销活动

# 创建DataFrame
df = pd.DataFrame({
'Date': dates,
'Sales': sales,
'Weather': weather,
'Holiday': holiday,
'Promotion': promotion
})

# 显示前几行数据
df.head()

在这个数据集中:

  • 销售量(Sales) 受月度周期性影响,同时带有随机噪声。
  • 天气(Weather) 为一个类别变量,随机生成三种可能的天气:晴天(Sunny)、雨天(Rainy)和多云(Cloudy)。
  • 节假日(Holiday) 和 促销(Promotion) 为二值变量,分别表示是否为节假日和是否有促销活动。

通过上述步骤,我们生成了一个完整的虚拟销售数据集,包含365天的销售记录。

5. 数据预处理与特征工程

在数据预处理阶段,我们需要对数据进行多项处理,包括:

  1. 日期特征处理:将日期信息拆解为年、月、日和星期几等特征。
  2. 类别特征编码:将天气、节假日、促销等类别特征转换为模型可以接受的数值格式。
  3. 时间序列滞后特征:生成销售量的滞后特征,以捕捉历史数据对当前销售的影响。

日期特征处理

将日期信息转换为年、月、日、星期几等特征,以便模型能够捕捉到时间的季节性规律。

# 日期特征处理
df['Year'] = df['Date'].dt.year
df['Month'] = df['Date'].dt.month
df['Day'] = df['Date'].dt.day
df['DayOfWeek'] = df['Date'].dt.dayofweek

类别特征编码

将类别变量转换为数值特征,通常使用独热编码(One-Hot Encoding)

# 使用独热编码将天气特征转换为数值特征
df = pd.get_dummies(df, columns=['Weather'], drop_first=True)

滞后特征生成

为了捕捉历史数据对未来销售量的影响,我们需要生成滞后特征。假设我们使用过去7天的销售量作为滞后特征。

# 生成滞后特征
for lag in range(1, 8):
df[f'Sales_lag_{lag}'] = df['Sales'].shift(lag)

# 删除缺失值(由于滞后特征的产生,前几行会产生缺失值)
df = df.dropna()

滞后特征生成后,我们的数据集将包含过去7天的销售量特征,这对于捕捉时间序列中的依赖关系非常重要。

滚动窗口特征生成

除了滞后特征,滚动窗口特征也是常用的手段之一。我们可以计算过去几天的销售量的均值、方差等统计量,以更好地捕捉销售趋势。

# 生成滚动窗口的均值和标准差特征
df['Rolling_mean_7'] = df['Sales'].rolling(window=7).mean().shift(1)
df['Rolling_std_7'] = df['Sales'].rolling(window=7).std().shift(1)

# 同样需要删除因滚动窗口导致的缺失值
df = df.dropna()

通过生成上述特征,我们完成了特征工程,数据集现在不仅包含原始的销售数据和外部特征,还增加了大量的时序特征。

6. 基于XGBoost的时间序列销售预测模型的构建

在数据预处理完成之后,我们可以开始构建基于XGBoost时间序列预测模型。这里我们将使用PyTorch来实现一个简单的神经网络,并XGBoost的行为。

数据准备

首先,我们将数据划分为训练集和测试集,并转换为PyTorch的张量格式。

from sklearn.model_selection import train_test_split
import torch

# 准备训练和测试集
X = df.drop(columns=['Date', 'Sales']).values
y = df['Sales'].values

# 确保没有 NaN
X = np.nan_to_num(X) # 将 NaN 转换为 0 或其他默认数值
y = np.nan_to_num(y)

# 确保数据类型都是数值型
X = X.astype(np.float32)
y = y.astype(np.float32)

# 数据集划分
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, shuffle=False)

# 转换为PyTorch张量
X_train = torch.tensor(X_train, dtype=torch.float32)
X_test = torch.tensor(X_test, dtype=torch.float32)
y_train = torch.tensor(y_train, dtype=torch.float32).view(-1, 1)
y_test = torch.tensor(y_test, dtype=torch.float32).view(-1, 1)

模型结构定义

在这里,我们定义一个简单的XGBoost的回归模型结构。虽然XGBoost本质上是树模型,但我们来体现其非线性拟合能力。

import torch.nn as nn

# 定义神经网络模型
class XGBoostTimeSeriesModel(nn.Module):
def __init__(self, input_dim):
super(XGBoostTimeSeriesModel, self).__init__()
self.fc1 = nn.Linear(input_dim, 128)
self.fc2 = nn.Linear(128, 64)
self.fc3 = nn.Linear(64, 1)

def forward(self, x):
x = torch.relu(self.fc1(x))
x = torch.relu(self.fc2(x))
x = self.fc3(x)
return x

# 初始化模型
input_dim = X_train.shape[1]
model = XGBoostTimeSeriesModel(input_dim)

损失函数与优化器

我们使用均方误差(MSE)作为损失函数,并使用Adam优化器进行模型优化。

import torch.optim as optim

# 定义损失函数和优化器
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=0.01)

模型训练

通过梯度下降法训练模型,进行500次迭代训练。

# 模型训练
epochs = 500
for epoch in range(epochs):
model.train()
optimizer.zero_grad()
outputs = model(X_train)
loss = criterion(outputs, y_train)
loss.backward()
optimizer.step()

if epoch % 50 == 0:
print(f'Epoch {epoch}/{epochs}, Loss: {loss.item()}')

模型测试

训练完成后,我们可以在测试集上进行预测,并计算模型的性能。

from sklearn.metrics import mean_squared_error

# 模型预测
model.eval()
predictions = model(X_test).detach().numpy()

# 计算均方误差
mse = mean_squared_error(y_test, predictions)
print(f'Test MSE: {mse}')

7. 结果可视化

为了更直观地展示模型的表现,我们将预测值与真实值进行对比绘图,并绘制其他有助于分析模型性能的图形。

预测值与真实值对比图

展示模型在测试集上的预测效果,通过对比可以看到模型是否准确地捕捉到销售趋势。

import matplotlib.pyplot as plt

# 绘制预测值与真实值的对比图
plt.figure(figsize=(12, 6))
plt.plot(df['Date'][-len(y_test):], y_test, label='True Sales', linewidth=2)
plt.plot(df['Date'][-len(y_test):], predictions, label='Predicted Sales', linestyle='--', linewidth=2)
plt.xlabel('Date')
plt.ylabel('Sales')
plt.title('Sales Prediction vs True Sales')
plt.legend()
plt.grid(True)
plt.show()

损失下降曲线

通过绘制训练过程中的损失下降曲线,可以帮助我们判断模型是否收敛。

# 绘制损失下降曲线
losses = []
for epoch in range(epochs):
model.train()
optimizer.zero_grad()
outputs = model(X_train)
loss = criterion(outputs, y_train)
loss.backward()
optimizer.step()

losses.append(loss.item())

plt.figure(figsize=(8, 4))
plt.plot(losses)
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training Loss Curve')
plt.show()

特征重要性图

虽然XGBoost具有内建的特征重要性评估机制,但我们可以通过分析模型的权重来了解哪些特征对预测影响最大。

importances = model.fc1.weight.abs().mean(dim=0).detach().numpy()
feature_names = df.drop(columns=['Date', 'Sales']).columns
plt.figure(figsize=(10, 6))
plt.barh(feature_names, importances)
plt.xlabel('Feature Importance')
plt.title('Feature Importance in Sales Prediction')
plt.show()

8. 模型优化与调参

在实际应用中,优化模型的性能是关键步骤。常见的调优手段包括:

  1. 特征选择与重要性分析:通过分析特征重要性,筛选对模型效果有贡献的特征,并去除不重要的特征。
  2. 超参数调优:使用网格搜索或随机搜索调优超参数,例如树的数量、学习率、正则化参数等。
  3. 交叉验证:使用时间序列分割的交叉验证方法评估模型,确保模型的泛化能力。

超参数调优

XGBoost模型的关键超参数包括:

  • 学习率(learning_rate):控制每棵树的贡献。
  • 树的最大深度(max_depth):控制每棵树的复杂度。
  • 正则化参数(lambda 和 alpha):防止过拟合。

可以使用GridSearchCV进行网格搜索:

from sklearn.model_selection import GridSearchCV
import xgboost as xgb

# 创建XGBoost模型
xgb_model = xgb.XGBRegressor()

# 定义超参数搜索空间
param_grid = {
'learning_rate': [0.01, 0.05, 0.1],
'max_depth': [3, 5, 7],
'n_estimators': [100, 200, 300],
'reg_alpha': [0, 0.1, 0.5],
'reg_lambda': [1, 1.5, 2]
}

# 进行网格搜索
grid_search = GridSearchCV(estimator=xgb_model, param_grid=param_grid, cv=5, scoring='neg_mean_squared_error', verbose=1)
grid_search.fit(X_train, y_train)

# 输出最佳参数
print("Best Parameters:", grid_search.best_params_)

模型验证与早停

在训练过程中可以引入早停机制(Early Stopping),即如果模型在验证集上的性能在连续几轮迭代中没有提升,则提前终止训练以防止过拟合。

# 使用早停机制训练XGBoost模型
xgb_model = xgb.XGBRegressor(learning_rate=0.1, max_depth=5, n_estimators=300)
xgb_model.fit(X_train, y_train, eval_set=[(X_test, y_test)], early_stopping_rounds=50, verbose=True)

通过这种方式,模型的训练将更加稳健,避免因过度训练导致的过拟合现象。

整个内容,通过结合XGBoost与时间序列模型,给大家分享了如何利用历史销售数据以及多维特征(如天气、促销、节假日等)进行销售预测。XGBoost的强大非线性拟合能力使其在处理复杂特征和多维数据时表现优异。通过合理的特征工程、模型训练、调参与优化,我们可以构建出一个精确且具备良好泛化能力的预测模型。

模型可继续改进的方向:

  • 进一步增强时序特征:可以加入更多的时间序列特征,如季节性成分和长短期记忆网络(LSTM)等方法进行对比。
  • 外部因素引入:可以考虑加入更多的外部因素,如竞争对手信息、经济指标等,以进一步提高预测的精度。
  • 优化超参数调优:通过更多的超参数搜索和交叉验证,进一步提升模型的泛化性能。

最终,该模型可以应用于库存管理、市场营销以及生产计划等多种业务场景。

文章转自微信公众号@深夜努力写Python