初学LSTM时要解决的问题就是时间序列的预测。时间序列预测又分为多变量,单变量和多步预测
单变量预测:只有单变量进行预测,即只有时间的变化作为变量预测的标准。例如预测飞机乘客的实验
多变量预测:除了时间,还有其他变量作为输入一起预测。例如测试PM2.5的实验,除了时间,还有湿度等其他特征。
多步预测:即不仅仅预测下一步数据,而是预测未来的几步数据,例如从1,预测3步→2,3,4

步骤

  1. 整理数据:(包括稳定性改造(多利用差分),归一化,数据变为有监督的数据等)。时间序列其实有很多特征,例如周期性,季节性,趋势性等等。我觉得还有很多特征需要整理,例如可以在数据上加上合适的cos,sin函数来表示周期性等等。但我还没找到合适的代码,暂且不表……MinMaxScaler()的输入需要二维矩阵。差分就是后一个数据减前一个数据,可以消除数据的趋势性。
  2. 有监督的数据:(单步预测,那么就将t和(t+1)的数据进行合并,多步预测一个道理,就是将你要预测的数据与输入数据进行合并,将未来数据作为输入数据标签的意思)
    简单的单步预测拼接数据代码:
def timeseries_to_supervised(data, lag):  # lag表示的是当前的值只与历史lag个时间步长的值有关,也就是用lag个数据预测下一个
    df = DataFrame(data)
    colums = [df.shift(i) for i in range(lag, lag + 1)]  # 原始数据时间窗向后移动lag步长,最外层有[],所以生成的最终是列表,df.shift(i) 转换后仍是一个DataFrame,所以列表中会存入一个数据框,这个数据框
    # ,不会算上索引的列
    print(colums)
    print(np.asarray(colums).shape)#列表的形状只有在转换成数组后才能显示出形状
    colums.append(df)  # 拼接数据,将原始dataFrame存入列表中
    print(np.asarray(colums).shape)
    # print(type(colums))
    # colums=DataFrame(colums)
    # print(type(np.asarray(colums)))
    # print(type((colums)[1]))
    df = concat(colums, axis=1)  # 横向拼接重塑数据,格式:input putput,列表中的两个数据框进行拼接,这里要求的拼接的数据必须是panda的对象
    print(df)
    df.fillna(0, inplace=True)  # 由于数据整体向后滑动lag后,前面的lag个数据是Na形式,用0来填充
    return df
  1. 模型搭建:首先要划分数据集,额案后构造LSTM模型,这里注意几点:①输入的数据格式应该转换为 [samples, timesteps, features],表示样本数量,步长和特征数量,通常我们划分训练数据X时,都是二维的,即[samples, timesteps, features]格式,所以通常格式转换代码就是X = X.reshape(X.shape[0], 1, X.shape[1]),步长为1。②需要注意是否是多步预测,如果是三步预测,就需要设置最后一层的神经元个数为3,因为需要三个输出。通常代码为model.add(Dense(y.shape[1]))其中y是标签矩阵,所以shape[1]就是预测步数,以为我们用未来步数的数据作为标签。③将状态设为True。stateful=True意味着由每个batch计算出的状态都会被重用于初始化下一个batch的初始状态。否则每个bacth都会清理上一个Batch的状态。④for i in range(nb_epoch): model.fit(X, y, epochs=1, batch_size=n_batch, verbose=0, shuffle=False) model.reset_states() return model要手动进行epoch循环,这样才会在每个epoch结束的时候再reset_states,删除内部状态。如果直接使用epochs=nb_epoch,不手动循环,则网络会在每个batch结束的时候就reset_states,
    #充值状态只是消除内部状态,但并不是重置参数,所以每个epoch之间也不是相互独立的。
  2. 使用模型进行预测并计算损失值。注意这里的测试值也是必须是[samples, timesteps, features]格式。而且要对真实数据和预测结果进行逆转换。注意不同逆转换的顺序。之前是先进性差分,再进行归一化,那么这里就要先进行归一化。MinMaxScaler的逆转换方法需要注意输入的格式,很容易出错。如果想了解更多,可以看这个MinMaxScaler详解(inverse_transform)
  3. 画图,输出损失值

下面是一个多步预测的完整代码,预测的是洗发水的销量。源代码来自https://github.com/yangwohenmai/LSTM,根据自己的情况,做了一些注释

from pandas import DataFrame
from pandas import Series
from pandas import concat
from pandas import read_csv
from pandas import datetime
from sklearn.metrics import mean_squared_error
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import LSTM
from math import sqrt
from matplotlib import pyplot
from numpy import array
import numpy as np


# 加载数据集
def parser(x):
    return datetime.strptime(x, '%Y/%m/%d')


# 将时间序列转换为监督类型的数据序列
def series_to_supervised(data, n_in=1, n_out=1, dropnan=True):
    n_vars = 1 if type(data) is list else data.shape[1]
    df = DataFrame(data)
    cols, names = list(), list()
    # 这个for循环是用来输入列标题的 var1(t-1),var1(t),var1(t+1),var1(t+2)
    for i in range(n_in, 0, -1):
        cols.append(df.shift(i))
        names += [('var%d(t-%d)' % (j + 1, i)) for j in range(n_vars)]
    # 转换为监督型数据的预测序列 每四个一组,对应 var1(t-1),var1(t),var1(t+1),var1(t+2)
    for i in range(0, n_out):
        cols.append(df.shift(-i))
        if i == 0:
            names += [('var%d(t)' % (j + 1)) for j in range(n_vars)]
        else:
            names += [('var%d(t+%d)' % (j + 1, i)) for j in range(n_vars)]
    # 拼接数据
    agg = concat(cols, axis=1)
    agg.columns = names
    # 把null值转换为0
    if dropnan:
        agg.dropna(inplace=True)
    print(agg)
    return agg


# 对传入的数列做差分操作,相邻两值相减
def difference(dataset, interval=1):
    diff = list()
    for i in range(interval, len(dataset)):
        value = dataset[i] - dataset[i - interval]
        diff.append(value)
    return Series(diff)

# MinMaxScaler的方法:axis=0表示要在列上取值,注意这里需要输入的值是二维的
   # X_std = (X - X.min(axis=0)) / (X.max(axis=0) - X.min(axis=0))
    #     X_scaled = X_std * (max - min) + min
    #
    # where min, max = feature_range.

# 将序列转换为用于监督学习的训练和测试集,差分,缩放,转化有监督
def prepare_data(series, n_test, n_lag, n_seq):
    # 提取原始值
    raw_values = series.values
    # 将数据转换为静态的
    diff_series = difference(raw_values, 1)
    diff_values = diff_series.values
    diff_values = diff_values.reshape(len(diff_values), 1)#注意缩放需要输入的是二维数据
    print()
    # 重新调整数据为(-1,1)之间
    scaler = MinMaxScaler(feature_range=(-1, 1))
    scaled_values = scaler.fit_transform(diff_values)
    scaled_values = scaled_values.reshape(len(scaled_values), 1)
    # 转化为有监督的数据X,y
    supervised = series_to_supervised(scaled_values, n_lag, n_seq)
    supervised_values = supervised.values
    # 分割为测试数据和训练数据
    train, test = supervised_values[0:-n_test], supervised_values[-n_test:]
    return scaler, train, test


# 匹配LSTM网络训练数据
def fit_lstm(train, n_lag, n_seq, n_batch, nb_epoch, n_neurons):
    # 重塑训练数据格式 [samples, timesteps, features]
    X, y = train[:, 0:n_lag], train[:, n_lag:]
    X = X.reshape(X.shape[0], 1, X.shape[1])
    # 配置一个LSTM神经网络,添加网络参数
    model = Sequential()
    model.add(LSTM(n_neurons, batch_input_shape=(n_batch, X.shape[1], X.shape[2]), stateful=True))#意味着由每个batch计算出的状态都会被重用于初始化下一个batch的初始状态。状态RNN假设连续的两个batch之中,相同下标的元素有一一映射关系。
    model.add(Dense(y.shape[1]))#因为这里是多步预测,如果要预测三步,那么最后输出肯定是三个值,也就是说最后一层有三个神经元输出三个值
    model.compile(loss='mean_squared_error', optimizer='adam')
    # 调用网络,迭代数据对神经网络进行训练,最后输出训练好的网络模型
    for i in range(nb_epoch):
        model.fit(X, y, epochs=1, batch_size=n_batch, verbose=0, shuffle=False)
        model.reset_states()#要手动进行epoch循环,这样才会在每个epoch结束的时候再reset_states,删除内部状态。如果直接使用epochs=nb_epoch,不手动循环,则网络会在每个batch结束的时候就reset_states,
        #充值状态只是消除内部状态,但并不是重置参数,所以每个epoch之间也不是相互独立的。
    return model


# 用LSTM做预测
def forecast_lstm(model, X, n_batch):
    # 重构输入参数 [samples, timesteps, features]
    X = X.reshape(1, 1, len(X))#每次只测试一个,所以samples直接为1,特征为一步的特征长度,如果使用两步进行预测,就是使用前两个数据进行预测,因为是单变量,所以特征大小就是2
    # 开始预测
    forecast = model.predict(X, batch_size=n_batch)
    # 结果转换成数组
    return [x for x in forecast[0, :]]


# 利用训练好的网络模型,对测试数据进行预测
def make_forecasts(model, n_batch, train, test, n_lag, n_seq):
    forecasts = list()
    # 预测方式是用一个X值预测出后三步的Y值
    for i in range(len(test)):
        X, y = test[i, 0:n_lag], test[i, n_lag:]
        # 调用训练好的模型预测未来数据
        forecast = forecast_lstm(model, X, n_batch)
        # 将预测的数据保存
        forecasts.append(forecast)
    print("这里是最初的预测结构")
    print(np.asarray(forecast).shape)
    return forecasts


# 对预测后的缩放值(-1,1)进行逆变换
def inverse_difference(last_ob, forecast):
    # invert first forecast
    inverted = list()
    inverted.append(forecast[0] + last_ob)#先将起始值与差分的第一个值1进行相加,得到了原数据的2。,之后就是无限的将前一个数据与差分相加得到下一个数据的过程
    # propagate difference forecast using inverted first value
    for i in range(1, len(forecast)):
        print(forecast[i] + inverted[i - 1])
        inverted.append(forecast[i] + inverted[i - 1])
    return inverted



# 对预测完成的数据进行逆变换
def inverse_transform(series, forecasts, scaler, n_test):
    inverted = list()
    for i in range(len(forecasts)):
        # create array from forecast
        forecast = array(forecasts[i])
        forecast = forecast.reshape(1, len(forecast))#(1,3)
        print("预测")
        print(forecast)
        # 将预测后的数据缩放逆转换
        inv_scale = scaler.inverse_transform(forecast)
        inv_scale = inv_scale[0, :]
        print(inv_scale)
        # invert differencing
        index = len(series) - n_test + i - 1#这里取的是在开始取取差分的坐标,例如数组[1,2,5,7,,5],那么差分就是[1,3,2,-2]对应的是原来数据的去掉第一个,如果想要逆差分,就要获取1的索引,
        last_ob = series.values[index]#根据索引获取值为1.
        # 将预测后的数据差值逆转换
        inv_diff = inverse_difference(last_ob, inv_scale)#将起始值为1,和之后的差分列表传入方法,看上面方法继续
        # 保存数据
        print(inv_diff)
        inverted.append(inv_diff)
    return inverted


# 评估每个预测时间步的RMSE
def evaluate_forecasts(test, forecasts, n_lag, n_seq):
    print("这里是现在的测试结构")
    print(np.asarray(test).shape)
    print(test)
    print("这里是现在的预测结构")
    print(np.asarray(forecasts).shape)
    for i in range(n_seq):
        actual = [row[i] for row in test]
        predicted = [forecast[i] for forecast in forecasts]
        rmse = sqrt(mean_squared_error(actual, predicted))
        print('t+%d RMSE: %f' % ((i + 1), rmse))


# 在原始数据集的上下文中绘制预测图
def plot_forecasts(series, forecasts, n_test):
    # plot the entire dataset in blue
    pyplot.plot(series.values)
    # plot the forecasts in red
    for i in range(len(forecasts)):
        off_s = len(series) - n_test + i - 1
        off_e = off_s + len(forecasts[i]) + 1
        xaxis = [x for x in range(off_s, off_e)]
        yaxis = [series.values[off_s]] + forecasts[i]#将前一天的观察数据与预测数据进行连接,形成长度为4的向量
        pyplot.plot(xaxis, yaxis, color='red')
    # show the plot
    pyplot.show()


# 加载数据
series = read_csv('data_set/shampoo-sales.csv', header=0, parse_dates=[0], index_col=0, squeeze=True, date_parser=parser)
# 配置网络信息
n_lag = 1
n_seq = 3
n_test = 10
n_epochs = 15
n_batch = 1
n_neurons = 4
# 准备数据
scaler, train, test = prepare_data(series, n_test, n_lag, n_seq)
print(test)
# 准备预测模型
model = fit_lstm(train, n_lag, n_seq, n_batch, n_epochs, n_neurons)
# 开始预测
forecasts = make_forecasts(model, n_batch, train, test, n_lag, n_seq)
# 逆转换训练数据和预测数据
forecasts = inverse_transform(series, forecasts, scaler, n_test+2 )
print(forecasts)
# 逆转换测试数据
actual = [row[n_lag:] for row in test]
print("真实测试值")
actual = inverse_transform(series, actual, scaler, n_test + 2)
print(actual)
# 比较预测数据和测试数据,计算两者之间的损失值
evaluate_forecasts(actual, forecasts, n_lag, n_seq)
# 画图
plot_forecasts(series, forecasts, n_test + 2)

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐