本文给出使用 传统机器学习模型 进行时序预测的代码分析,包括三个不同的落地案例。与经典的统计模型(如 ARIMA)不同,传统机器学习模型(如线性回归、随机森林、XGBoost 等)并不直接内置时序依赖,需要通过特征工程将时间序列转换为可以被通用学习算法处理的「特征-标签」形式,再做训练和预测。以下内容将通过三个示例案例进行详细展示。
目录
- 传统机器学习模型在时序分析中的思路
- 环境准备
- 示例一:使用线性回归对单变量时间序列进行短期预测
- 示例二:使用随机森林结合外生因素进行销售预测
- 示例三:使用 XGBoost 进行多步滚动预测
- 总结与注意事项
1. 传统机器学习模型在时序分析中的思路
在统计模型(如 ARIMA)中,时序信息由模型的自回归结构内置地捕捉。而在传统机器学习(如回归、树模型、SVM 等)中,模型不具备内置时序结构。我们一般通过以下方式解决:
-
窗口特征(Lag Features):为当前时刻 t 的目标值 yt 预测,手动提取过去若干时刻的数据作为特征。例如:
其中
lag
表示时滞阶数。也可加入滚动平均、滚动标准差等统计特征增强模型表达。 -
外生变量(Exogenous Features):如节假日信息、价格、天气、宏观经济指标等,也可一起输入模型。
-
时间特征:如一年中的第几天、星期几、是否节假日等,往往可帮助模型捕捉周期和季节性。
-
训练和评估方式:与普通回归不同,时序分析要注意时间顺序,不应随机打乱数据或进行普通的
train_test_split
,而应使用时间切分或滚动预测的方式来保证评估的正确性。
通过上述思路,我们可以将时序数据组织为 (X, y) 对,然后使用任何回归或机器学习算法进行预测。
2. 环境准备
以下示例均基于 Python 生态:
pip install numpy pandas scikit-learn matplotlib xgboost
- pandas:进行时序数据的读取和处理。
- numpy:基础数值运算。
- scikit-learn:提供机器学习模型(线性回归、随机森林、SVM 等)及评估方法。
- matplotlib:可视化结果。
- xgboost:示例三中需要安装 xgboost 库。
3. 示例一:使用线性回归对单变量时间序列进行短期预测
3.1 场景介绍
假设我们有一条单变量时间序列,代表每天的某个度量(如流量、销量、传感器数据等)。为了简单,我们没有引入外生变量,仅对历史数值做若干时滞特征,利用线性回归预测下一天的数值。
3.2 数据准备与特征工程
下面的示例假设我们有一个 CSV 文件 time_series_data.csv
,包含两列:date
(日期)和 value
(当天数值)。演示如何构造滞后特征。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
from math import sqrt
# ============ 1) 读取数据 ============
df = pd.read_csv("time_series_data.csv", parse_dates=["date"])
df.sort_values("date", inplace=True) # 保证按时间升序
df.set_index("date", inplace=True)
# 查看前几行
print(df.head())
# 简单画一下序列走势
plt.figure(figsize=(10,4))
plt.plot(df.index, df['value'], label='Original Data')
plt.title("Original Time Series")
plt.legend()
plt.show()
3.3 构造时滞特征
设定一个滞后窗口大小(如 lag=3
),意味着我们用 t-1, t-2, t-3
三个过去值来预测 t
。创建特征列,丢弃前面不足的行。
def create_lag_features(df, target_col, lag=3):
"""
为 df 中的 target_col 创造 lag 个时滞特征,
返回新的 DataFrame,包含 y(t) 以及 y(t-1)...y(t-lag) 等。
"""
df_new = df.copy()
for i in range(1, lag+1):
df_new[f'lag_{i}'] = df_new[target_col].shift(i)
return df_new
lag = 3
df_feats = create_lag_features(df, 'value', lag=lag)
# 删除有缺失值的行(最开始的几行因 shift 会产生NaN)
df_feats.dropna(inplace=True)
df_feats.head()
现在 df_feats
包含了:
value
:当日值 (label)lag_1
:上一日的值lag_2
:上两日的值lag_3
:上三日的值
3.4 划分训练集与测试集(时间序)
通常要保证训练集在时间上早于测试集,比如将最后 30 天作为测试集:
train_size = len(df_feats) - 30
train_data = df_feats.iloc[:train_size]
test_data = df_feats.iloc[train_size:]
X_train = train_data[[f'lag_{i}' for i in range(1, lag+1)]]
y_train = train_data['value']
X_test = test_data[[f'lag_{i}' for i in range(1, lag+1)]]
y_test = test_data['value']
3.5 训练线性回归模型
model = LinearRegression()
model.fit(X_train, y_train)
# 在测试集上预测
y_pred = model.predict(X_test)
# 评估
rmse = sqrt(mean_squared_error(y_test, y_pred))
print("Test RMSE:", rmse)
# 可视化对比
plt.figure(figsize=(10,4))
plt.plot(test_data.index, y_test, label='Actual')
plt.plot(test_data.index, y_pred, label='Predicted')
plt.title("Linear Regression - Predicted vs Actual")
plt.legend()
plt.show()
代码解析:
- 使用
LinearRegression
对 (X_train, y_train) 进行拟合;其中 X_train 是前3天的值,y_train 是当天值。 - 对测试集做预测并计算 RMSE 评估误差。
- 通过绘图观察预测值与真实值的对比。
3.6 小结
- 这种方式为时序数据建模带来了简单可行的思路:将过去时刻值作为特征,就可以用任何回归模型进行预测。
- 如果时序具有周期性或趋势,可以再额外添加滚动平均、周/月等时间戳特征等来增强模型。
- 线性回归相对简单,对非线性关系可能不足,后面示例将引入更加灵活的树模型。
4. 示例二:使用随机森林结合外生因素进行销售预测
4.1 场景介绍
假设我们是一家零售店,除了记录每日销量 sales
,还记录了节假日 holiday
、营销活动 promo
、天气 temp
等外生变量。我们希望用一个更强大的机器学习模型(随机森林)来预测下一天的销量。与示例一不同,这里有更多的特征(包括自回归特征和外生变量)。
4.2 数据准备与探索
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
df_sales = pd.read_csv("daily_sales_with_exog.csv", parse_dates=["date"])
df_sales.sort_values("date", inplace=True)
df_sales.set_index("date", inplace=True)
print(df_sales.head())
假设表格列有:
sales
: 当天销量holiday
: 是否节假日 (0或1)promo
: 是否有促销 (0或1)temp
: 当日平均气温 (float)
4.3 构建特征
- 时滞特征:我们可以把过去2天或3天的
sales
作为特征。 - 外生特征:
holiday
,promo
,temp
等。 - 日历特征:如将日期转换为星期几、月份等,帮助模型捕捉潜在周期性。
示例:
def make_features(df, target_col, lag=2):
df_new = df.copy()
# 1) 构造滞后特征
for i in range(1, lag+1):
df_new[f'lag_{i}'] = df_new[target_col].shift(i)
# 2) 可选:构造滚动平均或滚动统计
df_new['rolling_mean_3'] = df_new[target_col].shift(1).rolling(window=3).mean()
# 3) 加入一些日期衍生特征
df_new['day_of_week'] = df_new.index.dayofweek
df_new['month'] = df_new.index.month
return df_new
df_feats2 = make_features(df_sales, target_col='sales', lag=2)
df_feats2.dropna(inplace=True)
注意:
- 这里
holiday
,promo
,temp
原本就属于外生变量,无需 shift(或如果要预测下一天的销量,需要确保下一天的holiday/promo/temp
是已知或可预测)。 - 对天气、促销等是否在预测时可获取,需要在实际业务中确认。
4.4 划分训练和测试集
train_size = len(df_feats2) - 30 # 后30天做测试
train_df = df_feats2.iloc[:train_size]
test_df = df_feats2.iloc[train_size:]
feature_cols = ['lag_1','lag_2','rolling_mean_3','holiday','promo','temp','day_of_week','month']
X_train = train_df[feature_cols]
y_train = train_df['sales']
X_test = test_df[feature_cols]
y_test = test_df['sales']
4.5 训练随机森林模型
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error
import math
rf_model = RandomForestRegressor(n_estimators=100, random_state=42)
rf_model.fit(X_train, y_train)
y_pred = rf_model.predict(X_test)
rmse = math.sqrt(mean_squared_error(y_test, y_pred))
print("RandomForest RMSE:", rmse)
# 可视化预测结果
plt.figure(figsize=(10,5))
plt.plot(test_df.index, y_test, label='Actual')
plt.plot(test_df.index, y_pred, label='Predicted (RF)')
plt.title("Random Forest - Predicted vs Actual Sales")
plt.legend()
plt.show()
代码解析:
RandomForestRegressor
可以自动捕捉部分非线性和特征间的交互,可能比线性回归更适合复杂时序。- 我们将外生变量
holiday
,promo
,temp
,以及手动构造的时滞特征合并到X_train
。 - 测试时,使用最后 30 天的数据进行评估,检查预测效果。
4.6 重要特征可视化
在树模型中,我们可以查看特征重要度以初步了解模型的侧重:
importances = rf_model.feature_importances_
for feat, imp in zip(feature_cols, importances):
print(f"{feat}: {imp:.4f}")
这有助于发现哪些特征对模型最关键,比如 promo
(促销信息) 可能比温度更关键等。
4.7 小结
- 通过随机森林,我们能够同时利用历史销量时滞特征和外生特征来预测未来销量。
- 适用于多变量、非线性、特征交互复杂的时序场景。
- 对节假日、促销等离散特征也更具鲁棒性,能明显提升预测精度。
- 需要注意实际预测时外生变量的可用性。
5. 示例三:使用 XGBoost 进行多步滚动预测
5.1 场景介绍
在很多应用中,需要一次性预测多个未来时刻(如未来7天、未来30天等),称为多步预测。传统机器学习方法可以通过「滚动预测」(Recursive Strategy) 或者「多输出策略」等实现。
此处展示常见的滚动预测策略:即先预测 t+1,将该预测值当作已知值,再预测 t+2,以此类推。
5.2 数据与思路
假设我们有一条时间序列 df['value']
,要预测接下来的 7 天。使用 XGBoost 模型做逐日滚动预测:
- 特征工程:仍然以滞后特征为主。
- 滚动预测:预测下一天后,将预测值填充回序列,用它来预测再下一天。
- 评估:如果有真实的 7 天标签,也可以在一个循环中依次预测并与真实值比较。
5.3 代码示例
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from xgboost import XGBRegressor
from sklearn.metrics import mean_squared_error
df_3 = pd.read_csv("time_series_data.csv", parse_dates=["date"])
df_3.sort_values("date", inplace=True)
df_3.set_index("date", inplace=True)
# ====== 1) 特征工程 ======
def create_lag_features(df, target='value', n_lags=3):
df_new = df.copy()
for i in range(1, n_lags+1):
df_new[f'lag_{i}'] = df_new[target].shift(i)
return df_new
df_xgb = create_lag_features(df_3, 'value', n_lags=3)
df_xgb.dropna(inplace=True) # 去除前面缺失行
# 选取最后 7 天作为测试集
n_test = 7
train_df = df_xgb.iloc[:-n_test]
test_df = df_xgb.iloc[-n_test:]
features = [col for col in df_xgb.columns if col != 'value']
X_train, y_train = train_df[features], train_df['value']
X_test, y_test = test_df[features], test_df['value']
# ====== 2) 训练 XGBoost ======
xgb_model = XGBRegressor(n_estimators=100, learning_rate=0.1, random_state=42)
xgb_model.fit(X_train, y_train)
# ====== 3) 滚动预测的实现 ======
# 先将测试集中的特征矩阵拷贝一下,因为我们要逐日更新
test_rolling = test_df.copy()
predictions = []
# 将 DataFrame 转为可更新的形式
current_data = df_xgb.copy() # 包含全部历史(含训练集)
for i in range(n_test):
# 构造当日的特征 X
X_i = test_rolling[features].iloc[i:i+1] # 第 i 行
# 用模型预测
pred_i = xgb_model.predict(X_i)[0]
predictions.append(pred_i)
# 将预测结果 "滚动" 回去, 更新下一日的 lag 特征
# i.e. 模拟在真实场景下: t+1 预测得到 y_hat, 作为下个时刻 lag_1
day_index = test_rolling.index[i] # 当前预测日
# 假如明天是 next_day(只要 i < n_test-1)
if i < n_test - 1:
next_day_idx = test_rolling.index[i+1]
# test_rolling 中, next_day_idx 的 lag_1 = 当日真实值? or 预测值?
# 这里演示将预测值作为 next_day 的 lag_1
test_rolling.loc[next_day_idx, 'lag_1'] = pred_i
# 对 lag_2, lag_3 进行相应更新(把原lag_1挪到lag_2, 原lag_2挪到lag_3)
if 'lag_2' in test_rolling.columns:
test_rolling.loc[next_day_idx, 'lag_2'] = test_rolling.loc[day_index, 'lag_1']
if 'lag_3' in test_rolling.columns:
test_rolling.loc[next_day_idx, 'lag_3'] = test_rolling.loc[day_index, 'lag_2']
# predictions 就是逐日滚动预测 7 天的结果
y_test_array = y_test.values
rmse_roll = np.sqrt(mean_squared_error(y_test_array, predictions))
print("Rolling Forecast RMSE:", rmse_roll)
# 可视化
plt.figure(figsize=(10,5))
plt.plot(test_df.index, y_test_array, label='Actual')
plt.plot(test_df.index, predictions, label='Predicted (XGBoost Rolling)')
plt.title("XGBoost Multi-step Rolling Forecast")
plt.legend()
plt.show()
代码解析:
- create_lag_features:创建 3 阶滞后特征。
- 最后 7 天作为测试期,不是一次性预测 7 天,而是逐日滚动:
- 第 1 天预测时,lag1, lag2, lag3 是真实的历史数据;
- 第 2 天预测时,lag1 = 前一天的预测值,而 lag2, lag3 = 原先 real/historical or 继续滚动?不同策略取决于是否能获取真实值(这里假设不能,只能用预测值)。
- 不断更新
test_rolling
里 next_day 的 lag 列,依赖前一天的预测值。 - 获取 7 个预测值后,与真实标签做 RMSE 评估。
5.4 小结
- 滚动预测适合需要多步预测的场景,但误差会逐步累积。
- XGBoost 通过树结构能捕捉多阶滞后的复杂关系,对非线性时序也有较好效果。
- 若对外生变量做多步预测,需同时滚动或已知该外生变量的未来值。
- 也可使用多输出策略(一次性预测整个未来窗口),需要在特征工程和标签构造时做相应的扩展。
6. 总结与注意事项
- 传统机器学习模型的核心:对时序数据进行特征工程(滞后、滚动统计、外生变量等),然后在特征-标签的通用机器学习框架下训练模型。
- 非线性处理能力:相比线性回归,树模型(随机森林、GBDT、XGBoost)或其他非线性模型(SVR)更能捕捉复杂关系。
- 外生变量:能显著提升预测效果,但需要注意其可得性(预测时是否知道未来外生变量?需要另行预测或先验信息?)。
- 评估方式:时序预测应按时间分割训练测试集,不要随机打乱;多步预测时需注意滚动预测策略或其他多输出方法。
- 可扩展性:在多变量、长序列场景中,传统机器学习方法依旧有效,但可能需要大量特征工程,也可能在大数据集下不如深度学习灵活。
- 滚动预测 vs. 一次性预测:滚动预测简单易实现,但易累积误差。一次性预测(Direct Strategy)需为每个预测步分别训练一个模型或输出多维度,取决于业务需求。
通过以上三个示例(线性回归、随机森林、XGBoost),可以看到传统机器学习模型在时序预测中的通用思路:借助特征工程手动提取时序依赖,模型则像普通回归一样学习特征与目标的映射。配合合适的外生变量与评估策略,传统机器学习方法仍能在许多实际业务场景中取得稳定且可解释的效果。
【哈佛博后带小白玩转机器学习】 哔哩哔哩_bilibili
总课时超400+,时长75+小时