“ 简单的介绍 ”

· 初见LSTM🤦‍♂️

2022年的美赛之际,巨弱第一次邂逅LSTM,当时巨弱的美赛题是关于时序预测、制定交易策略方面,赛前队长让巨弱去接触RNN(循环神经网络)和 Transformer,于是乎在寒假了解这两模型时,巨弱碰巧也接触到了LSTM(长短期记忆模型),当时的巨弱难以启齿,什么记忆门、遗忘门,实在是让一个低水平的人能以理解,好在当时的巨弱有些神经网络的基础并且硬着头皮在生吃RNN资料后,再去理解LSTM最后终于算是入了个门。不过可惜后来美赛并未使用到LSTM,因此巨弱与它的关系算是告一段落。

· 再见LSTM🤦‍♀️

上天注定,机缘巧合,前段时间巨弱遇到了时间序列方面的预测问题需要解决,巨弱搜遍脑海除了ARIMA等,就是LSTM了,于是乎这段封尘已久的故事又再次续写。这次搭建的LSTM确实没让人失望,拟合度直接接近完美,不得不说这让我对LSTM的感情好像上升了一个级别(/ω\)。

下面是一张巨弱使用python代码对 kaggle: ads 数据集搭建LSTM模型的训练拟合图,效果看上去是不是还可以😊:

LSTM拟合图

“ LSTM简介 ”

LSTM(Long short-term memory, 长短期记忆) 是一种特殊的 RNN,主要是为了解决长序列训练过程中的梯度消失和梯度爆炸问题。简单来说,相比普通的RNN,LSTM能够在更长的序列中有更好的表现。LSTM 通过门控状态来控制传输状态,从而记住需要长时间记忆的,同时忘记不重要的信息,对很多需要 “长期记忆” 的任务尤其好用。

LSTM原理图

这里是巨弱找的一篇关于LSTM原理的完整讲解:Understanding LSTM Networks

“ 代码说明 ”

只需要准备好index为时间且连续以及将需要拟合预测的时序变量单独作为1列的df_x数据集,即可运该模型代码,代码的末尾设置了look_backpredict_length参数,分别表示模型拟合预测的结果主要以过去 look_back = n天作为参考进行预测和使用训练得到的模型预测长度为 predict_length 的未来数据,默认为look_back = 1predict_length = 30 大家可以根据需求进行修改。

除此之外,大家也可以对lstm_model函数中的结构进行修改,以此根据需求优化并选择最优的LSTM模型。

  • 导入必要的包
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import statsmodels.api as sm
from keras.callbacks import ReduceLROnPlateau, EarlyStopping, ModelCheckpoint
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import mean_absolute_error , mean_squared_error
from tensorflow.keras.layers import LSTM, Dense, Dropout, BatchNormalization
from tensorflow.keras.models import Sequential
from pylab import *
import seaborn as sns
from matplotlib.font_manager import FontProperties
mpl.rcParams['font.sans-serif'] = ['SimHei']
  • 模型的搭建和训练
  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
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
def data_lstm(df):
    # 1. 时序数据的标准化处理及训练集和验证集划分
    scale = MinMaxScaler(feature_range = (0, 1))
    df = scale.fit_transform(df)
    train_size = int(len(df) * 0.80)
    test_size = len(df) - train_size

	# 2. 由于时序变量具有先后关系,因此划分数据集时一般先前作为训练集、后者作为验证集
    train, test = df[0:train_size, :], df[train_size:len(df), :]
    return(train_size, test_size, train, test, df, scale)

# 2. lookback 表示以过去的几个日期作为主要预测变量,这里我选择的默认为1
# 输入数据集 和 输出数据集 的的建立
def create_data_set(dataset, look_back=1):
    data_x, data_y = [], []
    for i in range(len(dataset)-look_back-1):
        a = dataset[i:(i+look_back), 0]
        data_x.append(a)
        data_y.append(dataset[i + look_back, 0])
    return np.array(data_x), np.array(data_y)

# 3. 训练集和验证集的数据转化
def lstm(train, test, look_back=1):

    X_train,Y_train,X_test,Y_test = [],[],[],[]
    X_train,Y_train = create_data_set(train, look_back)
    X_train = np.reshape(X_train, (X_train.shape[0], X_train.shape[1], 1))
    X_test, Y_test = create_data_set(test, look_back)
    X_test = np.reshape(X_test, (X_test.shape[0], X_test.shape[1], 1))
    return (X_train, Y_train, X_test, Y_test)

# 4. 定义LSTM模型结构, 内部的结构参数可以根模型的拟合结果进行修改
def lstm_model(X_train, Y_train, X_test, Y_test):

	# 第一层,256个神经元,以及0.3的概率dropout进行正则
    regressor = Sequential()
    regressor.add(LSTM(units = 256, return_sequences = True, input_shape = (X_train.shape[1], 1)))
    regressor.add(Dropout(0.3))

	# 第二层,128个神经元,以及0.3的概率dropout进行正则
    regressor.add(LSTM(units = 128, return_sequences = True))
    regressor.add(Dropout(0.3))

	# 第三层,128个神经元,以及0.3的概率dropout进行正则
    regressor.add(LSTM(units = 128))
    regressor.add(Dropout(0.3))

    regressor.add(Dense(units = 1))
    regressor.compile(optimizer = 'adam', loss = 'mean_squared_error') # 损失函数为均方误差
    reduce_lr = ReduceLROnPlateau(monitor='val_loss', patience=5)
	
	# 下面的参数都可以进行修改,一般而言batchsize越大会越好些,epochs表示迭代次数,大家根据结果,大概何时收敛即可定为多少
    history =regressor.fit(X_train, Y_train, epochs = 80, batch_size = 8,validation_data=(X_test, Y_test), callbacks=[reduce_lr],shuffle=False)
    return(regressor, history)

# 5. 模型训练
def loss_epoch(regressor, X_train, Y_train, X_test, Y_test, scale, history):

    train_predict = regressor.predict(X_train)
    test_predict = regressor.predict(X_test)

    # 将预测值进行反标准化,即还原
    train_predict = scale.inverse_transform(train_predict)
    Y_train = scale.inverse_transform([Y_train])
    test_predict = scale.inverse_transform(test_predict)
    Y_test = scale.inverse_transform([Y_test])

	# 输出训练集和验证集的绝对误差和均方误差
    print('Train Mean Absolute Error:', mean_absolute_error(Y_train[0], train_predict[:,0]))
    print('Train Mean Squared Error:',np.sqrt(mean_squared_error(Y_train[0], train_predict[:,0])))
    print('Test Mean Absolute Error:', mean_absolute_error(Y_test[0], test_predict[:,0]))
    print('Test Root Mean Squared Error:',np.sqrt(mean_squared_error(Y_test[0], test_predict[:,0])))
		
	# 损失值结果  可视化
    plt.figure(figsize=(16,8))
    plt.plot(history.history['loss'], label='Train Loss')
    plt.plot(history.history['val_loss'], label='Test Loss')
    plt.title('model loss')
    plt.ylabel('loss')
    plt.xlabel('epochs')
    plt.legend(loc='upper right')
    plt.show()
    return(train_predict, test_predict, Y_train, Y_test)

# 6. 绘制拟合图,对未来进行预测
def Y_pre(Y_train, Y_test, train_predict, test_predict):
    Y_real = np.vstack((Y_train.reshape(-1,1), Y_test.reshape(-1,1)))
    Y_pred = np.vstack((train_predict[:,0].reshape(-1,1), test_predict[:,0].reshape(-1,1)))
    return(Y_real, Y_pred)

def plot_compare(n, Y_real, Y_pred):
    aa=[x for x in range(n)]
    plt.figure(figsize=(14,6))
    plt.plot(aa, Y_real, marker='.', label="actual")
    plt.plot(aa, Y_pred, 'r', label="prediction")
    plt.tight_layout()
    sns.despine(top=True)
    plt.subplots_adjust(left=0.07)
    plt.xticks(size= 15)
    plt.yticks(size= 15)
    plt.xlabel('Time step', size=15)
    plt.legend(fontsize=15)
    plt.show()

# 7. 根据一个真实的值预测连续的长度
def predict_sequences_multiple(model, firstValue, length, look_back=1):
    prediction_seqs = []
    curr_frame = firstValue
    for i in range(length): 
        predicted = []
        predicted.append(model.predict(curr_frame[-look_back:])[0,0])
        curr_frame = np.insert(curr_frame, i+look_back, predicted[-1], axis=0)
        prediction_seqs.append(predicted[-1])
    return prediction_seqs
  • 运行模型
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
'''只需要准备好index为时间且连续,时序变量单独为1列的df_x数据框,即可运该模型代码'''

look_back = 1 # 再次说明 look_back = n 表示预测结果主要以过去 n 天作为参考进行预测,大家可以根据想法自行修改
predict_length = 30 # 预测未来30天的数据,个人需要根据自己对未来预测天数的需求进行长度改变,正常而言短期内的预测会较为准确

train_size, test_size, train, test, df_x, scale = data_lstm(df_x)
X_train, Y_train, X_test, Y_test = lstm(train[:, 0].reshape(train_size, 1),
                                        test[:, 0].reshape(test_size, 1),
                                        look_back = look_back)

regressor, history = lstm_model(X_train, Y_train, X_test, Y_test)
train_predict, test_predict, Y_train, Y_test = loss_epoch(regressor, X_train, Y_train, X_test, Y_test, scale, history)
Y_real, Y_pre = Y_pre(Y_train, Y_test, train_predict, test_predict)
plot_compare(len(Y_real), Y_real, Y_pre)

predictions = predict_sequences_multiple(regressor, X_test[-1,:],
                                         predict_length,
                                         look_back = look_back) 
'''预测未来30天的数据并保存至pre_30数据框'''
pre_30 = scale.inverse_transform(np.array(predictions).reshape(-1, 1))