LightGBM的时间序列预测


介绍

有多种统计模型,例如Box-Jenkins方法和用于时间序列预测的状态空间模型,但是我决定尝试使用LightGBM,它在表数据中很强大。
需要将时间序列数据输入到LightGBM的数据转换。我将不追求准确性而写有关数据处理过程的文章。

数据使用航空乘客。

  • 资料说明
  • 数据链接

环境

  • Google Colab

程序

数据准备

我已经在

之后的部分中尝试了一些转换方法,但是假定已读取了以下库和数据。

1
2
3
4
5
6
7
8
9
10
11
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import lightgbm as lgb
from sklearn.model_selection import *


# !wget "https://www.analyticsvidhya.com/wp-content/uploads/2016/02/AirPassengers.csv"

dateparse = lambda dates: pd.datetime.strptime(dates, '%Y-%m')
df = pd.read_csv('AirPassengers.csv', index_col='Month', date_parser=dateparse, dtype='float')

第一学期的预测

这是近乎实时的预测。这意味着在训练预测变量后,我们将每个时期的观测值用作测试数据。实际上,预测→结果是否正确,最新的观察值将用于下一个预测。

值历史

我们将创建功能。
假设可以某种方式估计数据中存在的12个月周期。
假设您输入了过去12个月的历史记录并预测了第13个月。使用pandas.Series.shift在时间方向上移动数据。

1
2
for i in range(1, 13):
    df['shift%s'%i] = df['#Passengers'].shift(i)

让我们看一下数据的开头和结尾。

1
pd.concat([df.head(13), df.tail(3)], axis=0, sort=False)

image.png

数据将向下移动,并且数据将从右到左排列。由于已向下移动,因此没有数据的部分为NaN,数据类型也为float。 NaN部分将在以后删除。
参见第1950-01行。以下是用于培训和测试的数据。用shift1到shift12预测目标变量#Passengers作为解释变量。

衍生历史记录

在时间序列数据中,我们希望捕获随时间变化的变量的特征,因此除历史值外,我们还将差异作为特征。假设最多二阶导数是可以的。
由于数据是离散的,因此导数就是差异。因此,创建一个差异列。使用pandas.Series.diff。关键是要为shift1操作。

1
df['deriv1'] = df['shift1'].diff(1)

让我们看一下数据。

1
df[['#Passengers', 'deriv1']].head(5)

image.png

例如,当预测1949-03时,只有直到1949-02的数据,因此1949-03行是1949-02减去1949-01的值118?112 = 6

学习

我会学习的。由于是实验,所以参数等是适当的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
df = df.dropna()

X = df.drop('#Passengers', axis=1)
y = df['#Passengers']

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, shuffle=False)
X_train, X_validation, y_train, y_validation = train_test_split(X_train, y_train, test_size=0.2, shuffle=False)


lgb_train = lgb.Dataset(X_train, y_train)
lgb_eval = lgb.Dataset(X_validation, y_validation, reference=lgb_train)

# LightGBM parameters
params = {
        'task' : 'train',
        'boosting':'gbdt',
        'objective' : 'regression',
        'metric' : {'mse'},
        'num_leaves':78,
        'drop_rate':0.05,
        'learning_rate':0.01,
        'seed':0,
        'verbose':0,
        'device': 'cpu'
}

evaluation_results = {}
gbm = lgb.train(params,
                lgb_train,
                num_boost_round=100000,
                valid_sets=[lgb_train, lgb_eval],
                valid_names=['Train', 'Valid'],
                evals_result=evaluation_results,
                early_stopping_rounds=1000,
                verbose_eval=100)

预测

让我们绘制测试数据的预测。

1
2
3
4
5
6
7
8
9
y_pred = gbm.predict(X_test, num_iteration=gbm.best_iteration)

y_ = np.concatenate([np.array([None for i in range(len(y_train)+len(y_validation))]) , y_pred])
y_ = pd.DataFrame(y_, index=X.index)

plt.figure(figsize=(10,5))
plt.plot(y, label='original')
plt.plot(y_, '--', label='predict')
plt.legend()

image.png

。 .. ..这是一个微妙的结果。

动态预测

在一次预测中,将观测值用于测试数据,但是在动态预测中,将由第一测试数据预测的值用作下一个说明变量,然后说明预测值。我们将递归地预测变量。

在上一节中的预测之前,其过程是相同的。

预测

由于将预测值递归输入到预测变量,因此有必要根据预测值再次创建特征。
首先,让测试数据的第一行为data
在测试数据的第一行中,y的历史记录部分为val
为了计算微分特征,令来自测试数据的1个周期和2个周期之前的y为y_backward1y_backward2

1
2
3
4
5
6
data = X_test.iloc[0, :]
data_index = data.index
val = X_test[['shift1', 'shift2', 'shift3', 'shift4', 'shift5', 'shift6', 'shift7', 'shift8', 'shift9', 'shift10', 'shift11', 'shift12']].iloc[0].values

y_backward1 = y_validation[-1]
y_backward2 = y_validation[-2]

在预测变量中输入

data以获取预测值。将预测值存储在列表中。
通过移动val并用预测值填充NaN,y的历史将移动一个周期。
从val计算统计量。
使用y_backward1y_backward2计算微分特征。
以上总结为新数据。在下一个循环中输入预测变量。重复这个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from scipy import ndimage

pred_dynamic = []
for i in range(len(X_test)):

    y_d = gbm.predict(data, num_iteration=gbm.best_iteration)
    pred_dynamic.append(y_d[0])

    val = ndimage.interpolation.shift(val, 1, cval=y_d)

    mean_val = np.mean(val)
    median_val = np.median(val)
    max_val = np.max(val)
    min_val = np.min(val)
    deriv1 = (y_d - y_backward1)[0]
    deriv2 = (y_d - 2*y_backward1 + y_backward2)[0]

    feature = np.array([deriv1, deriv2, mean_val, median_val, max_val, min_val])
    data = np.hstack([val, feature])
    data = pd.Series(data, index=data_index)

    y_backward2 = y_backward1
    y_backward1 = y_d

绘制预测值。

1
2
3
4
5
6
7
8
9
y__ = np.concatenate([np.array([None for i in range(len(y_train)+len(y_validation))]) , np.array(pred_dynamic)])
y__ = pd.DataFrame(y__, index=X.index)


plt.figure(figsize=(10,5))
plt.plot(y, label='original')
plt.plot(y_, '--', label='predict')
plt.plot(y__, '--', label='predict_dynamical')
plt.legend()

image.png

结论

由于

树型预测器只能在训练数据范围内进行预测,因此认为它不适合预测趋势数据。用于表格系统(例如LightGBM)的预测器可能能够通过删除具有大量数据的趋势分量和训练模式来预测时间序列数据。