Machine-Learning-Mastery-深度学习时间序列教程-十六-

71 阅读1小时+

Machine Learning Mastery 深度学习时间序列教程(十六)

原文:Machine Learning Mastery

协议:CC BY-NC-SA 4.0

如何用 Keras 为时间序列预测调整 LSTM 超参数

原文: machinelearningmastery.com/tune-lstm-hyperparameters-keras-time-series-forecasting/

配置神经网络很困难,因为没有关于如何做到这一点的好理论。

您必须系统地从探索的动态和客观结果点探索不同的配置,以尝试理解给定预测性建模问题的发生情况。

在本教程中,您将了解如何在时间序列预测问题上探索如何配置 LSTM 网络。

完成本教程后,您将了解:

  • 如何调整和解释训练时期数的结果。
  • 如何调整和解释训练批次大小的结果。
  • 如何调整和解释神经元数量的结果。

让我们开始吧。

How to Tune LSTM Hyperparameters with Keras for Time Series Forecasting

如何用 Keras 调整 LSTM 超参数用于时间序列预测 照片由 David Saddler 保留,保留一些权利。

教程概述

本教程分为 6 个部分;他们是:

  1. 洗发水销售数据集
  2. 实验测试线束
  3. 调整时代数量
  4. 调整批量大小
  5. 调整神经元数量
  6. 结果摘要

环境

本教程假定您已安装 Python SciPy 环境。您可以在此示例中使用 Python 2 或 3。

本教程假设您安装了 TensorFlow 或 Theano 后端的 Keras v2.0 或更高版本。

本教程还假设您安装了 scikit-learn,Pandas,NumPy 和 Matplotlib。

如果您在设置 Python 环境时需要帮助,请参阅以下帖子:

洗发水销售数据集

该数据集描述了 3 年期间每月洗发水的销售数量。

单位是销售计数,有 36 个观察。原始数据集归功于 Makridakis,Wheelwright 和 Hyndman(1998)。

您可以在此处下载并了解有关数据集的更多信息

下面的示例加载并创建已加载数据集的图。

# load and plot dataset
from pandas import read_csv
from pandas import datetime
from matplotlib import pyplot
# load dataset
def parser(x):
	return datetime.strptime('190'+x, '%Y-%m')
series = read_csv('shampoo-sales.csv', header=0, parse_dates=[0], index_col=0, squeeze=True, date_parser=parser)
# summarize first few rows
print(series.head())
# line plot
series.plot()
pyplot.show()

运行该示例将数据集作为 Pandas Series 加载并打印前 5 行。

Month
1901-01-01 266.0
1901-02-01 145.9
1901-03-01 183.1
1901-04-01 119.3
1901-05-01 180.3
Name: Sales, dtype: float64

然后创建该系列的线图,显示明显的增加趋势。

Line Plot of Shampoo Sales Dataset

洗发水销售数据集的线图

接下来,我们将了解实验中使用的 LSTM 配置和测试工具。

实验测试线束

本节介绍本教程中使用的测试工具。

数据拆分

我们将 Shampoo Sales 数据集分为两部分:训练和测试集。

前两年的数据将用于训练数据集,剩余的一年数据将用于测试集。

将使用训练数据集开发模型,并对测试数据集做出预测。

测试数据集的持久性预测(朴素预测)实现了每月洗发水销售 136.761 的错误。这在测试集上提供了较低的可接受表现限制。

模型评估

将使用滚动预测场景,也称为前进模型验证。

测试数据集的每个时间步骤将一次一个地走。将使用模型对时间步长做出预测,然后将获取测试集的实际预期值,并使其可用于下一时间步的预测模型。

这模仿了一个真实世界的场景,每个月都会有新的洗发水销售观察结果,并用于下个月的预测。

这将通过训练和测试数据集的结构进行模拟。我们将以一次性方法进行所有预测。

将收集关于测试数据集的所有预测,并计算错误分数以总结模型的技能。将使用均方根误差(RMSE),因为它会对大错误进行处罚,并产生与预测数据相同的分数,即每月洗发水销售额。

数据准备

在我们将 LSTM 模型拟合到数据集之前,我们必须转换数据。

在拟合模型和做出预测之前,对数据集执行以下三个数据变换。

  1. 转换时间序列数据,使其静止不动。具体而言,滞后= 1 差分以消除数据中的增加趋势。
  2. 将时间序列转换为监督学习问题。具体而言,将数据组织成输入和输出模式,其中前一时间步的观察被用作预测当前时间步的观察的输入
  3. 将观察结果转换为具有特定比例。具体而言,要将数据重缩放为-1 到 1 之间的值,以满足 LSTM 模型的默认双曲正切激活函数。

这些变换在预测时反转,在计算和误差分数之前将它们恢复到原始比例。

实验运行

每个实验场景将运行 10 次。

其原因在于,每次训练给定配置时,LSTM 网络的随机初始条件可能导致非常不同的结果。

诊断方法将用于研究模型配置。这是创建和研究模型技能随时间变化的线图(称为时期的训练迭代),以深入了解给定配置如何执行以及如何调整以获得更好的表现。

在每个时期结束时,将在训练和测试数据集上评估模型,并保存 RMSE 分数。

打印每个方案结束时的训练和测试 RMSE 分数,以指示进度。

一系列训练和测试 RMSE 得分在运行结束时绘制为线图。训练得分为蓝色,考试成绩为橙色。

让我们深入研究结果。

调整时代数量

我们将看调整的第一个 LSTM 参数是训练时期的数量。

该模型将使用批量大小为 4 和单个神经元。我们将探讨针对不同数量的训练时期训练此配置的效果。

500 个时代的诊断

下面列出了此诊断的完整代码清单。

代码的评论相当好,应该很容易理解。此代码将成为本教程中所有未来实验的基础,并且仅列出每个后续实验中所做的更改。

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
import matplotlib
# be able to save images on server
matplotlib.use('Agg')
from matplotlib import pyplot
import numpy

# date-time parsing function for loading the dataset
def parser(x):
	return datetime.strptime('190'+x, '%Y-%m')

# frame a sequence as a supervised learning problem
def timeseries_to_supervised(data, lag=1):
	df = DataFrame(data)
	columns = [df.shift(i) for i in range(1, lag+1)]
	columns.append(df)
	df = concat(columns, axis=1)
	df = df.drop(0)
	return df

# create a differenced series
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)

# scale train and test data to [-1, 1]
def scale(train, test):
	# fit scaler
	scaler = MinMaxScaler(feature_range=(-1, 1))
	scaler = scaler.fit(train)
	# transform train
	train = train.reshape(train.shape[0], train.shape[1])
	train_scaled = scaler.transform(train)
	# transform test
	test = test.reshape(test.shape[0], test.shape[1])
	test_scaled = scaler.transform(test)
	return scaler, train_scaled, test_scaled

# inverse scaling for a forecasted value
def invert_scale(scaler, X, yhat):
	new_row = [x for x in X] + [yhat]
	array = numpy.array(new_row)
	array = array.reshape(1, len(array))
	inverted = scaler.inverse_transform(array)
	return inverted[0, -1]

# evaluate the model on a dataset, returns RMSE in transformed units
def evaluate(model, raw_data, scaled_dataset, scaler, offset, batch_size):
	# separate
	X, y = scaled_dataset[:,0:-1], scaled_dataset[:,-1]
	# reshape
	reshaped = X.reshape(len(X), 1, 1)
	# forecast dataset
	output = model.predict(reshaped, batch_size=batch_size)
	# invert data transforms on forecast
	predictions = list()
	for i in range(len(output)):
		yhat = output[i,0]
		# invert scaling
		yhat = invert_scale(scaler, X[i], yhat)
		# invert differencing
		yhat = yhat + raw_data[i]
		# store forecast
		predictions.append(yhat)
	# report performance
	rmse = sqrt(mean_squared_error(raw_data[1:], predictions))
	return rmse

# fit an LSTM network to training data
def fit_lstm(train, test, raw, scaler, batch_size, nb_epoch, neurons):
	X, y = train[:, 0:-1], train[:, -1]
	X = X.reshape(X.shape[0], 1, X.shape[1])
	# prepare model
	model = Sequential()
	model.add(LSTM(neurons, batch_input_shape=(batch_size, X.shape[1], X.shape[2]), stateful=True))
	model.add(Dense(1))
	model.compile(loss='mean_squared_error', optimizer='adam')
	# fit model
	train_rmse, test_rmse = list(), list()
	for i in range(nb_epoch):
		model.fit(X, y, epochs=1, batch_size=batch_size, verbose=0, shuffle=False)
		model.reset_states()
		# evaluate model on train data
		raw_train = raw[-(len(train)+len(test)+1):-len(test)]
		train_rmse.append(evaluate(model, raw_train, train, scaler, 0, batch_size))
		model.reset_states()
		# evaluate model on test data
		raw_test = raw[-(len(test)+1):]
		test_rmse.append(evaluate(model, raw_test, test, scaler, 0, batch_size))
		model.reset_states()
	history = DataFrame()
	history['train'], history['test'] = train_rmse, test_rmse
	return history

# run diagnostic experiments
def run():
	# load dataset
	series = read_csv('shampoo-sales.csv', header=0, parse_dates=[0], index_col=0, squeeze=True, date_parser=parser)
	# transform data to be stationary
	raw_values = series.values
	diff_values = difference(raw_values, 1)
	# transform data to be supervised learning
	supervised = timeseries_to_supervised(diff_values, 1)
	supervised_values = supervised.values
	# split data into train and test-sets
	train, test = supervised_values[0:-12], supervised_values[-12:]
	# transform the scale of the data
	scaler, train_scaled, test_scaled = scale(train, test)
	# fit and evaluate model
	train_trimmed = train_scaled[2:, :]
	# config
	repeats = 10
	n_batch = 4
	n_epochs = 500
	n_neurons = 1
	# run diagnostic tests
	for i in range(repeats):
		history = fit_lstm(train_trimmed, test_scaled, raw_values, scaler, n_batch, n_epochs, n_neurons)
		pyplot.plot(history['train'], color='blue')
		pyplot.plot(history['test'], color='orange')
		print('%d) TrainRMSE=%f, TestRMSE=%f' % (i, history['train'].iloc[-1], history['test'].iloc[-1]))
	pyplot.savefig('epochs_diagnostic.png')

# entry point
run()

运行实验在 10 次实验运行的每一次结束时打印训练的 RMSE 和测试集。

0) TrainRMSE=63.495594, TestRMSE=113.472643
1) TrainRMSE=60.446307, TestRMSE=100.147470
2) TrainRMSE=59.879681, TestRMSE=95.112331
3) TrainRMSE=66.115269, TestRMSE=106.444401
4) TrainRMSE=61.878702, TestRMSE=86.572920
5) TrainRMSE=73.519382, TestRMSE=103.551694
6) TrainRMSE=64.407033, TestRMSE=98.849227
7) TrainRMSE=72.684834, TestRMSE=98.499976
8) TrainRMSE=77.593773, TestRMSE=124.404747
9) TrainRMSE=71.749335, TestRMSE=126.396615

还创建了在每个训练时期之后训练和测试集上的一系列 RMSE 得分的线图。

Diagnostic Results with 500 Epochs

500 个时期的诊断结果

结果清楚地表明 RMSE 在几乎所有实验运行的训练时期都呈下降趋势。

这是一个好兆头,因为它表明模型正在学习问题并具有一些预测技巧。实际上,所有最终测试分数都低于简单持久性模型(朴素预测)的误差,该模型在此问题上达到了 136.761 的 RMSE。

结果表明,更多的训练时期将导致更熟练的模型。

让我们尝试将时期数从 500 增加到 1000。

1000 个时期的诊断

在本节中,我们使用相同的实验设置,并使模型适合 1000 个训练时期。

具体地,n_epochs参数在run()函数中被设置为1000

n_epochs = 1000

运行该示例为最后一个时期的训练和测试集打印 RMSE。

0) TrainRMSE=69.242394, TestRMSE=90.832025
1) TrainRMSE=65.445810, TestRMSE=113.013681
2) TrainRMSE=57.949335, TestRMSE=103.727228
3) TrainRMSE=61.808586, TestRMSE=89.071392
4) TrainRMSE=68.127167, TestRMSE=88.122807
5) TrainRMSE=61.030678, TestRMSE=93.526607
6) TrainRMSE=61.144466, TestRMSE=97.963895
7) TrainRMSE=59.922150, TestRMSE=94.291120
8) TrainRMSE=60.170052, TestRMSE=90.076229
9) TrainRMSE=62.232470, TestRMSE=98.174839

还创建了每个时期的测试和训练 RMSE 得分的线图。

Diagnostic Results with 1000 Epochs

1000 个时期的诊断结果

我们可以看到模型误差的下降趋势确实继续并且似乎变慢。

训练和测试案例的线条变得更加横向,但仍然普遍呈下降趋势,尽管变化率较低。测试误差的一些示例显示可能的拐点大约 600 个时期并且可能显示出上升趋势。

值得进一步延长时代。我们对测试集中的平均表现持续改进感兴趣,这可能会持续下去。

让我们尝试将时期数从 1000 增加到 2000。

2000 年的诊断

在本节中,我们使用相同的实验设置,并使模型适合 2000 个训练时期。

具体地,在run()函数中将n_epochs参数设置为 2000。

n_epochs = 2000

运行该示例为最后一个时期的训练和测试集打印 RMSE。

0) TrainRMSE=67.292970, TestRMSE=83.096856
1) TrainRMSE=55.098951, TestRMSE=104.211509
2) TrainRMSE=69.237206, TestRMSE=117.392007
3) TrainRMSE=61.319941, TestRMSE=115.868142
4) TrainRMSE=60.147575, TestRMSE=87.793270
5) TrainRMSE=59.424241, TestRMSE=99.000790
6) TrainRMSE=66.990082, TestRMSE=80.490660
7) TrainRMSE=56.467012, TestRMSE=97.799062
8) TrainRMSE=60.386380, TestRMSE=103.810569
9) TrainRMSE=58.250862, TestRMSE=86.212094

还创建了每个时期的测试和训练 RMSE 得分的线图。

Diagnostic Results with 2000 Epochs

2000 年的诊断结果

正如人们可能已经猜到的那样,在训练和测试数据集的额外 1000 个时期内,误差的下降趋势仍在继续。

值得注意的是,大约一半的案例一直持续减少到运行结束,而其余案件则显示出增长趋势的迹象。

增长的趋势是过拟合的迹象。这是模型过拟合训练数据集的代价,代价是测试数据集的表现更差。通过对训练数据集的持续改进以及随后的测试数据集中的拐点和恶化技能的改进来举例说明。不到一半的运行显示了测试数据集中此类模式的开始。

然而,测试数据集的最终时期结果非常好。如果有机会我们可以通过更长时间的训练获得进一步的收益,我们必须探索它。

让我们尝试将 2000 年到 4000 年的时期数量加倍。

4000 个时代的诊断

在本节中,我们使用相同的实验设置,并使模型适合超过 4000 个训练时期。

具体地,在run()函数中将n_epochs参数设置为 4000。

n_epochs = 4000

运行该示例为最后一个时期的训练和测试集打印 RMSE。

0) TrainRMSE=58.889277, TestRMSE=99.121765
1) TrainRMSE=56.839065, TestRMSE=95.144846
2) TrainRMSE=58.522271, TestRMSE=87.671309
3) TrainRMSE=53.873962, TestRMSE=113.920076
4) TrainRMSE=66.386299, TestRMSE=77.523432
5) TrainRMSE=58.996230, TestRMSE=136.367014
6) TrainRMSE=55.725800, TestRMSE=113.206607
7) TrainRMSE=57.334604, TestRMSE=90.814642
8) TrainRMSE=54.593069, TestRMSE=105.724825
9) TrainRMSE=56.678498, TestRMSE=83.082262

还创建了每个时期的测试和训练 RMSE 得分的线图。

Diagnostic Results with 4000 Epochs

4000 个时期的诊断结果

类似的模式仍在继续。

即使在 4000 个时代,也存在改善表现的总趋势。有一种严重过拟合的情况,其中测试误差急剧上升。

同样,大多数运行以“良好”(优于持久性)最终测试错误结束。

结果摘要

上面的诊断运行有助于探索模型的动态行为,但缺乏客观和可比较的平均表现。

我们可以通过重复相同的实验并计算和比较每个配置的摘要统计量来解决这个问题。在这种情况下,30 个运行完成了迭代值 500,1000,2000,4000 和 6000。

我们的想法是在大量运行中使用汇总统计量比较配置,并确切地了解哪些配置可能在平均情况下表现更好。

完整的代码示例如下所示。

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
import matplotlib
# be able to save images on server
matplotlib.use('Agg')
from matplotlib import pyplot
import numpy

# date-time parsing function for loading the dataset
def parser(x):
	return datetime.strptime('190'+x, '%Y-%m')

# frame a sequence as a supervised learning problem
def timeseries_to_supervised(data, lag=1):
	df = DataFrame(data)
	columns = [df.shift(i) for i in range(1, lag+1)]
	columns.append(df)
	df = concat(columns, axis=1)
	df = df.drop(0)
	return df

# create a differenced series
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)

# invert differenced value
def inverse_difference(history, yhat, interval=1):
	return yhat + history[-interval]

# scale train and test data to [-1, 1]
def scale(train, test):
	# fit scaler
	scaler = MinMaxScaler(feature_range=(-1, 1))
	scaler = scaler.fit(train)
	# transform train
	train = train.reshape(train.shape[0], train.shape[1])
	train_scaled = scaler.transform(train)
	# transform test
	test = test.reshape(test.shape[0], test.shape[1])
	test_scaled = scaler.transform(test)
	return scaler, train_scaled, test_scaled

# inverse scaling for a forecasted value
def invert_scale(scaler, X, yhat):
	new_row = [x for x in X] + [yhat]
	array = numpy.array(new_row)
	array = array.reshape(1, len(array))
	inverted = scaler.inverse_transform(array)
	return inverted[0, -1]

# fit an LSTM network to training data
def fit_lstm(train, batch_size, nb_epoch, neurons):
	X, y = train[:, 0:-1], train[:, -1]
	X = X.reshape(X.shape[0], 1, X.shape[1])
	model = Sequential()
	model.add(LSTM(neurons, batch_input_shape=(batch_size, X.shape[1], X.shape[2]), stateful=True))
	model.add(Dense(1))
	model.compile(loss='mean_squared_error', optimizer='adam')
	for i in range(nb_epoch):
		model.fit(X, y, epochs=1, batch_size=batch_size, verbose=0, shuffle=False)
		model.reset_states()
	return model

# run a repeated experiment
def experiment(repeats, series, epochs):
	# transform data to be stationary
	raw_values = series.values
	diff_values = difference(raw_values, 1)
	# transform data to be supervised learning
	supervised = timeseries_to_supervised(diff_values, 1)
	supervised_values = supervised.values
	# split data into train and test-sets
	train, test = supervised_values[0:-12], supervised_values[-12:]
	# transform the scale of the data
	scaler, train_scaled, test_scaled = scale(train, test)
	# run experiment
	error_scores = list()
	for r in range(repeats):
		# fit the model
		batch_size = 4
		train_trimmed = train_scaled[2:, :]
		lstm_model = fit_lstm(train_trimmed, batch_size, epochs, 1)
		# forecast the entire training dataset to build up state for forecasting
		train_reshaped = train_trimmed[:, 0].reshape(len(train_trimmed), 1, 1)
		lstm_model.predict(train_reshaped, batch_size=batch_size)
		# forecast test dataset
		test_reshaped = test_scaled[:,0:-1]
		test_reshaped = test_reshaped.reshape(len(test_reshaped), 1, 1)
		output = lstm_model.predict(test_reshaped, batch_size=batch_size)
		predictions = list()
		for i in range(len(output)):
			yhat = output[i,0]
			X = test_scaled[i, 0:-1]
			# invert scaling
			yhat = invert_scale(scaler, X, yhat)
			# invert differencing
			yhat = inverse_difference(raw_values, yhat, len(test_scaled)+1-i)
			# store forecast
			predictions.append(yhat)
		# report performance
		rmse = sqrt(mean_squared_error(raw_values[-12:], predictions))
		print('%d) Test RMSE: %.3f' % (r+1, rmse))
		error_scores.append(rmse)
	return error_scores

# load dataset
series = read_csv('shampoo-sales.csv', header=0, parse_dates=[0], index_col=0, squeeze=True, date_parser=parser)
# experiment
repeats = 30
results = DataFrame()
# vary training epochs
epochs = [500, 1000, 2000, 4000, 6000]
for e in epochs:
	results[str(e)] = experiment(repeats, series, e)
# summarize results
print(results.describe())
# save boxplot
results.boxplot()
pyplot.savefig('boxplot_epochs.png')

首先运行代码打印 5 个配置中每个配置的摘要统计量。值得注意的是,这包括每个结果群体的 RMSE 得分的平均值和标准差。

均值给出了配置的平均预期表现的概念,而标准偏差给出了方差的概念。最小和最大 RMSE 分数还可以了解可能预期的最佳和最差情况示例的范围。

仅查看平均 RMSE 分数,结果表明配置为 1000 的迭代可能更好。结果还表明可能需要进一步调查 1000 至 2000 年的时代价值。

              500        1000        2000        4000        6000
count   30.000000   30.000000   30.000000   30.000000   30.000000
mean   109.439203  104.566259  107.882390  116.339792  127.618305
std     14.874031   19.097098   22.083335   21.590424   24.866763
min     87.747708   81.621783   75.327883   77.399968   90.512409
25%     96.484568   87.686776   86.753694  102.127451  105.861881
50%    110.891939   98.942264  116.264027  121.898248  125.273050
75%    121.067498  119.248849  125.518589  130.107772  150.832313
max    138.879278  139.928055  146.840997  157.026562  166.111151

分布也显示在盒子和须状图上。这有助于了解分布如何直接比较。

绿线显示中位数,方框显示第 25 和第 75 百分位数,或中间 50%的数据。该比较还表明,将时期设置为 1000 的选择优于测试的替代方案。它还表明,在 2000 年或 4000 年的时期内可以实现最佳表现,但代价是平均表现更差。

Box and Whisker Plot Summarizing Epoch Results

框和晶须图总结时代结果

接下来,我们将看看批量大小的影响。

调整批量大小

批量大小控制更新网络权重的频率。

重要的是在 Keras 中,批量大小必须是测试大小和训练数据集的一个因子。

在上一节探索训练时期的数量时,批量大小固定为 4,它完全分为测试数据集(大小为 12)和测试数据集的截断版本(大小为 20)。

在本节中,我们将探讨改变批量大小的效果。我们将训练时期的数量保持在 1000。

1000 个时期的诊断和 4 的批量大小

作为提醒,上一节在第二个实验中评估了批量大小为 4,其中一些时期为 1000。

结果显示出错误的下降趋势,大多数运行一直持续到最后的训练时期。

Diagnostic Results with 1000 Epochs

1000 个时期的诊断结果

1000 个时期的诊断和 2 的批量大小

在本节中,我们将批量大小从 4 减半。

run()函数中的n_batch参数进行了此更改;例如:

n_batch = 2

运行该示例显示了与批量大小为 4 相同的总体趋势,可能在最后一个时期具有更高的 RMSE。

这些运行可能会显示出更快稳定 RMES 的行为,而不是看似继续下行趋势。

下面列出了每次运行的最终暴露的 RSME 分数。

0) TrainRMSE=63.510219, TestRMSE=115.855819
1) TrainRMSE=58.336003, TestRMSE=97.954374
2) TrainRMSE=69.163685, TestRMSE=96.721446
3) TrainRMSE=65.201764, TestRMSE=110.104828
4) TrainRMSE=62.146057, TestRMSE=112.153553
5) TrainRMSE=58.253952, TestRMSE=98.442715
6) TrainRMSE=67.306530, TestRMSE=108.132021
7) TrainRMSE=63.545292, TestRMSE=102.821356
8) TrainRMSE=61.693847, TestRMSE=99.859398
9) TrainRMSE=58.348250, TestRMSE=99.682159

还创建了每个时期的测试和训练 RMSE 得分的线图。

Diagnostic Results with 1000 Epochs and Batch Size of 2

1000 个时期和批量大小为 2 的诊断结果

让我们再试一次批量。

1000 个时期的诊断和 1 的批量大小

批量大小为 1 在技术上执行在线学习。

这是在每个训练模式之后更新网络的地方。这可以与批量学习形成对比,其中权重仅在每个时期结束时更新。

我们可以在run()函数中更改n_batch参数;例如:

n_batch = 1

同样,运行该示例将打印每次运行的最后一个时期的 RMSE 分数。

0) TrainRMSE=60.349798, TestRMSE=100.182293
1) TrainRMSE=62.624106, TestRMSE=95.716070
2) TrainRMSE=64.091859, TestRMSE=98.598958
3) TrainRMSE=59.929993, TestRMSE=96.139427
4) TrainRMSE=59.890593, TestRMSE=94.173619
5) TrainRMSE=55.944968, TestRMSE=106.644275
6) TrainRMSE=60.570245, TestRMSE=99.981562
7) TrainRMSE=56.704995, TestRMSE=111.404182
8) TrainRMSE=59.909065, TestRMSE=90.238473
9) TrainRMSE=60.863807, TestRMSE=105.331214

还创建了每个时期的测试和训练 RMSE 得分的线图。

该图表明测试 RMSE 随时间变化的可变性更大,并且可能是训练 RMSE 比较大的批量大小更快稳定。测试 RMSE 的可变性增加是可以预期的,因为对网络进行的大量更改会给每次更新提供如此少的反馈。

该图还表明,如果配置提供更多的训练时期,RMSE 的下降趋势可能会继续。

Diagnostic Results with 1000 Epochs and Batch Size of 1

1000 个时期和批量大小为 1 的诊断结果

结果摘要

与训练时期一样,我们可以客观地比较给定不同批量大小的网络表现。

每个配置运行 30 次,并根据最终结果计算汇总统计量。

...

# run a repeated experiment
def experiment(repeats, series, batch_size):
	# transform data to be stationary
	raw_values = series.values
	diff_values = difference(raw_values, 1)
	# transform data to be supervised learning
	supervised = timeseries_to_supervised(diff_values, 1)
	supervised_values = supervised.values
	# split data into train and test-sets
	train, test = supervised_values[0:-12], supervised_values[-12:]
	# transform the scale of the data
	scaler, train_scaled, test_scaled = scale(train, test)
	# run experiment
	error_scores = list()
	for r in range(repeats):
		# fit the model
		train_trimmed = train_scaled[2:, :]
		lstm_model = fit_lstm(train_trimmed, batch_size, 1000, 1)
		# forecast the entire training dataset to build up state for forecasting
		train_reshaped = train_trimmed[:, 0].reshape(len(train_trimmed), 1, 1)
		lstm_model.predict(train_reshaped, batch_size=batch_size)
		# forecast test dataset
		test_reshaped = test_scaled[:,0:-1]
		test_reshaped = test_reshaped.reshape(len(test_reshaped), 1, 1)
		output = lstm_model.predict(test_reshaped, batch_size=batch_size)
		predictions = list()
		for i in range(len(output)):
			yhat = output[i,0]
			X = test_scaled[i, 0:-1]
			# invert scaling
			yhat = invert_scale(scaler, X, yhat)
			# invert differencing
			yhat = inverse_difference(raw_values, yhat, len(test_scaled)+1-i)
			# store forecast
			predictions.append(yhat)
		# report performance
		rmse = sqrt(mean_squared_error(raw_values[-12:], predictions))
		print('%d) Test RMSE: %.3f' % (r+1, rmse))
		error_scores.append(rmse)
	return error_scores

# load dataset
series = read_csv('shampoo-sales.csv', header=0, parse_dates=[0], index_col=0, squeeze=True, date_parser=parser)
# experiment
repeats = 30
results = DataFrame()
# vary training batches
batches = [1, 2, 4]
for b in batches:
	results[str(b)] = experiment(repeats, series, b)
# summarize results
print(results.describe())
# save boxplot
results.boxplot()
pyplot.savefig('boxplot_batches.png')

仅从平均表现来看,结果表明较低的 RMSE,批量大小为 1.正如前一节所述,随着更多的训练时期,这可能会得到进一步改善。

                1           2           4
count   30.000000   30.000000   30.000000
mean    98.697017  102.642594  100.320203
std     12.227885    9.144163   15.957767
min     85.172215   85.072441   83.636365
25%     92.023175   96.834628   87.671461
50%     95.981688  101.139527   91.628144
75%    102.009268  110.171802  114.660192
max    147.688818  120.038036  135.290829

还创建了数据的框和胡须图,以帮助以图形方式比较分布。该图显示了作为绿线的中值表现,其中批量大小 4 显示最大可变性和最低中值 RMSE。

调整神经网络是平均表现和该表现的可变性的折衷,理想的结果是具有低可变性的低平均误差,这意味着它通常是良好且可再现的。

Box and Whisker Plot Summarizing Batch Size Results

框和晶须图总结批量大小结果

调整神经元数量

在本节中,我们将研究改变网络中神经元数量的影响。

神经元的数量会影响网络的学习能力。通常,更多的神经元能够以更长的训练时间为代价从问题中学习更多的结构。更多的学习能力也会产生可能过拟合训练数据的问题。

我们将使用批量大小为 4 和 1000 的训练时期。

1000 个时期和 1 个神经元的诊断

我们将从 1 个神经元开始。

提醒一下,这是从时代实验中测试的第二个配置。

Diagnostic Results with 1000 Epochs

1000 个时期的诊断结果

1000 个时期和 2 个神经元的诊断

我们可以将神经元的数量从 1 增加到 2.这有望提高网络的学习能力。

我们可以通过改变run()函数中的n_neurons变量来实现。

n_neurons = 2

运行此配置将打印每次运行的最后一个时期的 RMSE 分数。

结果表明一般表现良好,但不是很好。

0) TrainRMSE=59.466223, TestRMSE=95.554547
1) TrainRMSE=58.752515, TestRMSE=101.908449
2) TrainRMSE=58.061139, TestRMSE=86.589039
3) TrainRMSE=55.883708, TestRMSE=94.747927
4) TrainRMSE=58.700290, TestRMSE=86.393213
5) TrainRMSE=60.564511, TestRMSE=101.956549
6) TrainRMSE=63.160916, TestRMSE=98.925108
7) TrainRMSE=60.148595, TestRMSE=95.082825
8) TrainRMSE=63.029242, TestRMSE=89.285092
9) TrainRMSE=57.794717, TestRMSE=91.425071

还创建了每个时期的测试和训练 RMSE 得分的线图。

这更有说服力。它显示测试 RMSE 快速下降到约 500-750 迭代,其中拐点显示测试 RMSE 在所有运行中几乎全面上升。同时,训练数据集显示持续减少到最后的时期。

这些是训练数据集过拟合的良好迹象。

Diagnostic Results with 1000 Epochs and 2 Neurons

1000 个时期和 2 个神经元的诊断结果

让我们看看这种趋势是否会持续更多的神经元。

1000 个时期和 3 个神经元的诊断

本节查看相同配置,神经元数量增加到 3。

我们可以通过在run()函数中设置n_neurons变量来实现。

n_neurons = 3

运行此配置将打印每次运行的最后一个时期的 RMSE 分数。

结果与上一节类似;我们没有看到 2 或 3 个神经元的最终时期测试分数之间有太大的差异。最终的训练得分看起来似乎低于 3 个神经元,可能表现出过拟合的加速。

训练数据集中的拐点似乎比 2 个神经元实验更早发生,可能在 300-400 时代。

这些神经元数量的增加可能受益于减慢学习速度的额外变化。例如使用正常化方法如丢失,减少批量大小,并减少到训练时期的数量。

0) TrainRMSE=55.686242, TestRMSE=90.955555
1) TrainRMSE=55.198617, TestRMSE=124.989622
2) TrainRMSE=55.767668, TestRMSE=104.751183
3) TrainRMSE=60.716046, TestRMSE=93.566307
4) TrainRMSE=57.703663, TestRMSE=110.813226
5) TrainRMSE=56.874231, TestRMSE=98.588524
6) TrainRMSE=57.206756, TestRMSE=94.386134
7) TrainRMSE=55.770377, TestRMSE=124.949862
8) TrainRMSE=56.876467, TestRMSE=95.059656
9) TrainRMSE=57.067810, TestRMSE=94.123620

还创建了每个时期的测试和训练 RMSE 得分的线图。

Diagnostic Results with 1000 Epochs and 3 Neurons

1000 个时期和 3 个神经元的诊断结果

结果摘要

同样,我们可以客观地比较增加神经元数量的影响,同时保持所有其他网络配置的固定。

在本节中,我们重复每个实验 30 次,并将平均测试 RMSE 表现与 1 到 5 的神经元数量进行比较。

...

# run a repeated experiment
def experiment(repeats, series, neurons):
	# transform data to be stationary
	raw_values = series.values
	diff_values = difference(raw_values, 1)
	# transform data to be supervised learning
	supervised = timeseries_to_supervised(diff_values, 1)
	supervised_values = supervised.values
	# split data into train and test-sets
	train, test = supervised_values[0:-12], supervised_values[-12:]
	# transform the scale of the data
	scaler, train_scaled, test_scaled = scale(train, test)
	# run experiment
	error_scores = list()
	for r in range(repeats):
		# fit the model
		batch_size = 4
		train_trimmed = train_scaled[2:, :]
		lstm_model = fit_lstm(train_trimmed, batch_size, 1000, neurons)
		# forecast the entire training dataset to build up state for forecasting
		train_reshaped = train_trimmed[:, 0].reshape(len(train_trimmed), 1, 1)
		lstm_model.predict(train_reshaped, batch_size=batch_size)
		# forecast test dataset
		test_reshaped = test_scaled[:,0:-1]
		test_reshaped = test_reshaped.reshape(len(test_reshaped), 1, 1)
		output = lstm_model.predict(test_reshaped, batch_size=batch_size)
		predictions = list()
		for i in range(len(output)):
			yhat = output[i,0]
			X = test_scaled[i, 0:-1]
			# invert scaling
			yhat = invert_scale(scaler, X, yhat)
			# invert differencing
			yhat = inverse_difference(raw_values, yhat, len(test_scaled)+1-i)
			# store forecast
			predictions.append(yhat)
		# report performance
		rmse = sqrt(mean_squared_error(raw_values[-12:], predictions))
		print('%d) Test RMSE: %.3f' % (r+1, rmse))
		error_scores.append(rmse)
	return error_scores

# load dataset
series = read_csv('shampoo-sales.csv', header=0, parse_dates=[0], index_col=0, squeeze=True, date_parser=parser)
# experiment
repeats = 30
results = DataFrame()
# vary neurons
neurons = [1, 2, 3, 4, 5]
for n in neurons:
	results[str(n)] = experiment(repeats, series, n)
# summarize results
print(results.describe())
# save boxplot
results.boxplot()
pyplot.savefig('boxplot_neurons.png')

运行实验会打印每个配置的摘要统计量。

仅从平均表现来看,结果表明具有 1 个神经元的网络配置具有超过 1000 个时期的最佳表现,批量大小为 4.此配置还显示最紧密的方差。

                1           2           3           4           5
count   30.000000   30.000000   30.000000   30.000000   30.000000
mean    98.344696  103.268147  102.726894  112.453766  122.843032
std     13.538599   14.720989   12.905631   16.296657   25.586013
min     81.764721   87.731385   77.545899   85.632492   85.955093
25%     88.524334   94.040807   95.152752  102.477366  104.192588
50%     93.543948  100.330678  103.622600  110.906970  117.022724
75%    102.944050  105.087384  110.235754  118.653850  133.343669
max    132.934054  152.588092  130.551521  162.889845  184.678185

盒子和须状图显示中值测试集表现的明显趋势,其中神经元的增加导致测试 RMSE 的相应增加。

Box and Whisker Plot Summarizing Neuron Results

框和晶须图总结神经元结果

所有结果摘要

我们在本教程的 Shampoo Sales 数据集上完成了很多 LSTM 实验。

通常,似乎配置有 1 个神经元,批量大小为 4 并且训练 1000 个迭代的有状态 LSTM 可能是一个很好的配置。

结果还表明,批量大小为 1 并且适合更多时期的这种配置可能值得进一步探索。

调整神经网络是一项困难的实证研究,LSTM 也不例外。

本教程展示了配置行为随时间推移的诊断研究的好处,以及测试 RMSE 的客观研究。

然而,总会有更多的研究可以进行。下一节列出了一些想法。

扩展

本节列出了本教程中对实验进行扩展的一些想法。

如果您探索其中任何一项,请在评论中报告您的结果;我很想看看你想出了什么。

  • dropout。使用正则化方法减慢学习速度,例如在重复的 LSTM 连接上丢失。
  • 。通过在每层中添加更多层和不同数量的神经元来探索额外的分层学习能力。
  • 正规化。探索权重正则化(如 L1 和 L2)如何用于减慢某些配置上网络的学习和过拟合。
  • 优化算法。探索替代优化算法的使用,例如经典的梯度下降,以查看加速或减慢学习的特定配置是否可以带来好处。
  • 损失函数。探索替代损失函数的使用,看看它们是否可用于提升表现。
  • 功能和时间步。探索使用滞后观察作为输入特征和特征的输入时间步骤,以查看它们作为输入的存在是否可以改善模型的学习和/或预测能力。
  • 批量大。探索大于 4 的批量大小,可能需要进一步操纵训练和测试数据集的大小。

摘要

在本教程中,您了解了如何系统地研究 LSTM 网络的配置以进行时间序列预测。

具体来说,你学到了:

  • 如何设计用于评估模型配置的系统测试工具。
  • 如何使用模型诊断随着时间的推移,以及客观预测误差来解释模型行为。
  • 如何探索和解释训练时期,批量大小和神经元数量的影响。

您对调整 LSTM 或本教程有任何疑问吗? 在下面的评论中提出您的问题,我会尽力回答。

如何在时间序列预测训练期间更新 LSTM 网络

原文: machinelearningmastery.com/update-lstm-networks-training-time-series-forecasting/

使用神经网络模型进行时间序列预测的好处是可以在新数据可用时更新权重。

在本教程中,您将了解如何使用新数据更新长期短期记忆(LSTM)循环神经网络以进行时间序列预测。

完成本教程后,您将了解:

  • 如何用新数据更新 LSTM 神经网络。
  • 如何开发测试工具来评估不同的更新方案。
  • 如何解释使用新数据更新 LSTM 网络的结果。

让我们开始吧。

  • 2017 年 4 月更新:添加了缺少的 update_model()函数。

How to Update LSTM Networks During Training for Time Series Forecasting

如何在时间序列预测训练期间更新 LSTM 网络 照片由 Esteban Alvarez ,保留一些权利。

教程概述

本教程分为 9 个部分。他们是:

  1. 洗发水销售数据集
  2. 实验测试线束
  3. 实验:没有更新
  4. 实验:2 更新时期
  5. 实验:5 更新时期
  6. 实验:10 个更新时期
  7. 实验:20 个更新时期
  8. 实验:50 更新时期
  9. 结果比较

环境

本教程假定您已安装 Python SciPy 环境。您可以在此示例中使用 Python 2 或 3。

本教程假设您安装了 TensorFlow 或 Theano 后端的 Keras v2.0 或更高版本。

本教程还假设您安装了 scikit-learn,Pandas,NumPy 和 Matplotlib。

如果您在设置 Python 环境时需要帮助,请参阅以下帖子:

洗发水销售数据集

该数据集描述了 3 年期间每月洗发水的销售数量。

单位是销售计数,有 36 个观察。原始数据集归功于 Makridakis,Wheelwright 和 Hyndman(1998)。

您可以在此处下载并了解有关数据集的更多信息

下面的示例加载并创建已加载数据集的图。

# load and plot dataset
from pandas import read_csv
from pandas import datetime
from matplotlib import pyplot
# load dataset
def parser(x):
	return datetime.strptime('190'+x, '%Y-%m')
series = read_csv('shampoo-sales.csv', header=0, parse_dates=[0], index_col=0, squeeze=True, date_parser=parser)
# summarize first few rows
print(series.head())
# line plot
series.plot()
pyplot.show()

运行该示例将数据集作为 Pandas Series 加载并打印前 5 行。

Month
1901-01-01 266.0
1901-02-01 145.9
1901-03-01 183.1
1901-04-01 119.3
1901-05-01 180.3
Name: Sales, dtype: float64

然后创建该系列的线图,显示明显的增加趋势。

Line Plot of Shampoo Sales Dataset

洗发水销售数据集的线图

接下来,我们将了解实验中使用的 LSTM 配置和测试工具。

实验测试线束

本节介绍本教程中使用的测试工具。

数据拆分

我们将 Shampoo Sales 数据集分为两部分:训练和测试集。

前两年的数据将用于训练数据集,剩余的一年数据将用于测试集。

将使用训练数据集开发模型,并对测试数据集做出预测。

测试数据集的持久性预测(朴素预测)实现了每月洗发水销售 136.761 的错误。这为测试集提供了可接受的表现下限。

模型评估

将使用滚动预测场景,也称为前进模型验证。

测试数据集的每个时间步骤将一次一个地走。将使用模型对时间步长做出预测,然后将获取测试集的实际预期值,并使其可用于下一时间步的预测模型。

这模仿了一个真实世界的场景,每个月都会有新的洗发水销售观察结果,并用于下个月的预测。

这将通过训练和测试数据集的结构进行模拟。

将收集关于测试数据集的所有预测,并计算错误分数以总结模型的技能。将使用均方根误差(RMSE),因为它会对大错误进行处罚,并产生与预测数据相同的分数,即每月洗发水销售额。

数据准备

在我们将 LSTM 模型拟合到数据集之前,我们必须转换数据。

在拟合模型和做出预测之前,对数据集执行以下三个数据变换。

  1. 转换时间序列数据,使其静止。具体而言,滞后= 1 差分以消除数据中的增加趋势。
  2. 将时间序列转换为监督学习问题。具体而言,将数据组织成输入和输出模式,其中前一时间步的观察被用作预测当前时间步的观察的输入
  3. 将观察结果转换为具有特定比例。具体而言,要将数据重缩放为-1 到 1 之间的值,以满足 LSTM 模型的默认双曲正切激活函数。

在计算错误分数之前,这些变换在预测中反转以将它们返回到其原始比例。

LSTM 模型

我们将使用 LSTM 模型,其中 1 个神经元适合 500 个时期。

批量大小为 1 是必需的,因为我们将使用前向验证并对最后 12 个月的每个数据进行一步预测。

批量大小为 1 意味着该模型将使用在线训练(而不是批量训练或小批量训练)。因此,预计模型拟合将具有一些变化。

理想情况下,将使用更多的训练时期(例如 1000 或 1500),但这被截断为 500 以保持运行时间合理。

使用有效的 ADAM 优化算法和均方误差损失函数来拟合模型。

实验运行

每个实验场景将运行 10 次。

其原因在于,每次训练给定配置时,LSTM 网络的随机初始条件可导致非常不同的表现。

让我们深入研究实验。

实验:没有更新

在第一个实验中,我们将评估一次训练的 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
import matplotlib
import numpy
from numpy import concatenate

# date-time parsing function for loading the dataset
def parser(x):
	return datetime.strptime('190'+x, '%Y-%m')

# frame a sequence as a supervised learning problem
def timeseries_to_supervised(data, lag=1):
	df = DataFrame(data)
	columns = [df.shift(i) for i in range(1, lag+1)]
	columns.append(df)
	df = concat(columns, axis=1)
	df = df.drop(0)
	return df

# create a differenced series
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)

# invert differenced value
def inverse_difference(history, yhat, interval=1):
	return yhat + history[-interval]

# scale train and test data to [-1, 1]
def scale(train, test):
	# fit scaler
	scaler = MinMaxScaler(feature_range=(-1, 1))
	scaler = scaler.fit(train)
	# transform train
	train = train.reshape(train.shape[0], train.shape[1])
	train_scaled = scaler.transform(train)
	# transform test
	test = test.reshape(test.shape[0], test.shape[1])
	test_scaled = scaler.transform(test)
	return scaler, train_scaled, test_scaled

# inverse scaling for a forecasted value
def invert_scale(scaler, X, yhat):
	new_row = [x for x in X] + [yhat]
	array = numpy.array(new_row)
	array = array.reshape(1, len(array))
	inverted = scaler.inverse_transform(array)
	return inverted[0, -1]

# fit an LSTM network to training data
def fit_lstm(train, batch_size, nb_epoch, neurons):
	X, y = train[:, 0:-1], train[:, -1]
	X = X.reshape(X.shape[0], 1, X.shape[1])
	model = Sequential()
	model.add(LSTM(neurons, batch_input_shape=(batch_size, X.shape[1], X.shape[2]), stateful=True))
	model.add(Dense(1))
	model.compile(loss='mean_squared_error', optimizer='adam')
	for i in range(nb_epoch):
		model.fit(X, y, epochs=1, batch_size=batch_size, verbose=0, shuffle=False)
		model.reset_states()
	return model

# make a one-step forecast
def forecast_lstm(model, batch_size, X):
	X = X.reshape(1, 1, len(X))
	yhat = model.predict(X, batch_size=batch_size)
	return yhat[0,0]

# run a repeated experiment
def experiment(repeats, series):
	# transform data to be stationary
	raw_values = series.values
	diff_values = difference(raw_values, 1)
	# transform data to be supervised learning
	supervised = timeseries_to_supervised(diff_values, 1)
	supervised_values = supervised.values
	# split data into train and test-sets
	train, test = supervised_values[0:-12], supervised_values[-12:]
	# transform the scale of the data
	scaler, train_scaled, test_scaled = scale(train, test)
	# run experiment
	error_scores = list()
	for r in range(repeats):
		# fit the base model
		lstm_model = fit_lstm(train_scaled, 1, 500, 1)
		# forecast test dataset
		predictions = list()
		for i in range(len(test_scaled)):
			# predict
			X, y = test_scaled[i, 0:-1], test_scaled[i, -1]
			yhat = forecast_lstm(lstm_model, 1, X)
			# invert scaling
			yhat = invert_scale(scaler, X, yhat)
			# invert differencing
			yhat = inverse_difference(raw_values, yhat, len(test_scaled)+1-i)
			# store forecast
			predictions.append(yhat)
		# report performance
		rmse = sqrt(mean_squared_error(raw_values[-12:], predictions))
		print('%d) Test RMSE: %.3f' % (r+1, rmse))
		error_scores.append(rmse)
	return error_scores

# execute the experiment
def run():
	# load dataset
	series = read_csv('shampoo-sales.csv', header=0, parse_dates=[0], index_col=0, squeeze=True, date_parser=parser)
	# experiment
	repeats = 10
	results = DataFrame()
	# run experiment
	results['results'] = experiment(repeats, series)
	# summarize results
	print(results.describe())
	# save results
	results.to_csv('experiment_fixed.csv', index=False)

 # entry point
run()

运行该示例使用前向验证存储在测试数据集上计算的 RMSE 分数。它们存储在名为experiment_fixed.csv的文件中,以供以后分析。打印分数摘要,如下所示。

结果表明,平均表现优于持久性模型,显示测试 RMSE 为 109.565465,而持久性的洗发水销售额为 136.761。

          results
count   10.000000
mean   109.565465
std     14.329646
min     95.357198
25%     99.870983
50%    104.864387
75%    113.553952
max    138.261929

接下来,我们将开始查看在前向验证期间对模型进行更新的配置。

实验:2 更新时期

在本实验中,我们将模型拟合到所有训练数据上,然后在前向验证期间的每个预测之后更新模型。

然后,将用于在测试数据集中引出预测的每个测试模式添加到训练数据集中,并更新模型。

在这种情况下,该模型在进行下一次预测之前适合额外的 2 个训练时期。

使用与第一个实验中使用的相同的代码清单。代码清单的更改如下所示。

# Update LSTM model
def update_model(model, train, batch_size, updates):
	X, y = train[:, 0:-1], train[:, -1]
	X = X.reshape(X.shape[0], 1, X.shape[1])
	for i in range(updates):
		model.fit(X, y, nb_epoch=1, batch_size=batch_size, verbose=0, shuffle=False)
		model.reset_states()

# run a repeated experiment
def experiment(repeats, series, updates):
	# transform data to be stationary
	raw_values = series.values
	diff_values = difference(raw_values, 1)
	# transform data to be supervised learning
	supervised = timeseries_to_supervised(diff_values, 1)
	supervised_values = supervised.values
	# split data into train and test-sets
	train, test = supervised_values[0:-12], supervised_values[-12:]
	# transform the scale of the data
	scaler, train_scaled, test_scaled = scale(train, test)
	# run experiment
	error_scores = list()
	for r in range(repeats):
		# fit the base model
		lstm_model = fit_lstm(train_scaled, 1, 500, 1)
		# forecast test dataset
		train_copy = numpy.copy(train_scaled)
		predictions = list()
		for i in range(len(test_scaled)):
			# update model
			if i > 0:
				update_model(lstm_model, train_copy, 1, updates)
			# predict
			X, y = test_scaled[i, 0:-1], test_scaled[i, -1]
			yhat = forecast_lstm(lstm_model, 1, X)
			# invert scaling
			yhat = invert_scale(scaler, X, yhat)
			# invert differencing
			yhat = inverse_difference(raw_values, yhat, len(test_scaled)+1-i)
			# store forecast
			predictions.append(yhat)
			# add to training set
			train_copy = concatenate((train_copy, test_scaled[i,:].reshape(1, -1)))
		# report performance
		rmse = sqrt(mean_squared_error(raw_values[-12:], predictions))
		print('%d) Test RMSE: %.3f' % (r+1, rmse))
		error_scores.append(rmse)
	return error_scores

# execute the experiment
def run():
	# load dataset
	series = read_csv('shampoo-sales.csv', header=0, parse_dates=[0], index_col=0, squeeze=True, date_parser=parser)
	# experiment
	repeats = 10
	results = DataFrame()
	# run experiment
	updates = 2
	results['results'] = experiment(repeats, series, updates)
	# summarize results
	print(results.describe())
	# save results
	results.to_csv('experiment_update_2.csv', index=False)

 # entry point
run()

运行实验将最终测试 RMSE 分数保存在“experiment_update_2.csv”中,并打印结果的摘要统计量,如下所示。

          results
count   10.000000
mean    99.566270
std     10.511337
min     87.771671
25%     93.925243
50%     97.903038
75%    101.213058
max    124.748746

实验:5 更新时期

该实验重复上述更新实验,并在将每个测试模式添加到训练数据集之后将模型训练另外 5 个时期。

运行实验将最终测试 RMSE 分数保存在“experiment_update_5.csv”中,并打印结果的摘要统计量,如下所示。

          results
count   10.000000
mean   101.094469
std      9.422711
min     91.642706
25%     94.593701
50%     98.954743
75%    104.998420
max    123.651985

实验:10 个更新时期

该实验重复上述更新实验,并在将每个测试模式添加到训练数据集之后将模型训练另外 10 个时期。

运行实验将最终测试 RMSE 分数保存在“experiment_update_10.csv”中,并打印结果的摘要统计量,如下所示。

          results
count   10.000000
mean   108.806418
std     21.707665
min     92.161703
25%     94.872009
50%     99.652295
75%    112.607260
max    159.921749

实验:20 个更新时期

该实验重复上述更新实验,并在将每个测试模式添加到训练数据集之后将模型训练另外 20 个时期。

运行实验将最终测试 RMSE 分数保存在“experiment_update_20.csv”中,并打印结果的摘要统计量,如下所示。

          results
count   10.000000
mean   112.070895
std     16.631902
min     96.822760
25%    101.790705
50%    103.380896
75%    119.479211
max    140.828410

实验:50 更新时期

该实验重复上述更新实验,并在将每个测试模式添加到训练数据集之后将模型训练另外 50 个时期。

运行实验将最终测试 RMSE 分数保存在“experiment_update_50.csv”中,并打印结果的摘要统计量,如下所示。

          results
count   10.000000
mean   110.721971
std     22.788192
min     93.362982
25%     96.833140
50%     98.411940
75%    123.793652
max    161.463289

结果比较

在本节中,我们比较了之前实验中保存的结果。

我们加载每个保存的结果,使用描述性统计量汇总结果,并使用 box 和 whisker 图比较结果。

完整的代码清单如下。

from pandas import DataFrame
from pandas import read_csv
from matplotlib import pyplot
# load results into a dataframe
filenames = ['experiment_fixed.csv',
	'experiment_update_2.csv', 'experiment_update_5.csv',
	'experiment_update_10.csv', 'experiment_update_20.csv',
	'experiment_update_50.csv']
results = DataFrame()
for name in filenames:
	results[name[11:-4]] = read_csv(name, header=0)
# describe all results
print(results.describe())
# box and whisker plot
results.boxplot()
pyplot.show()

首先运行该示例计算并打印每个实验结果的描述性统计量。

如果我们看一下平均表现,我们可以看到固定模型提供了良好的表现基线,但我们发现适度数量的更新时期(20 和 50)平均会产生更差的测试集 RMSE。

我们看到少数更新时期导致更好的整体测试集表现,特别是 2 个时期,接着是 5 个时期。这是令人鼓舞的。

            fixed    update_2    update_5   update_10   update_20   update_50
count   10.000000   10.000000   10.000000   10.000000   10.000000   10.000000
mean   109.565465   99.566270  101.094469  108.806418  112.070895  110.721971
std     14.329646   10.511337    9.422711   21.707665   16.631902   22.788192
min     95.357198   87.771671   91.642706   92.161703   96.822760   93.362982
25%     99.870983   93.925243   94.593701   94.872009  101.790705   96.833140
50%    104.864387   97.903038   98.954743   99.652295  103.380896   98.411940
75%    113.553952  101.213058  104.998420  112.607260  119.479211  123.793652
max    138.261929  124.748746  123.651985  159.921749  140.828410  161.463289

还创建了一个盒子和须状图,用于比较每个实验的测试 RMSE 结果的分布。

该图突出显示了每个实验的中位数(绿线)以及中间 50%的数据(框)。该图描述了与平均表现相同的故事,表明少数训练时期(2 或 5 个时期)导致最佳的整体测试 RMSE。

该图显示测试 RMSE 上升,因为更新次数增加到 20 个时期,然后再次下降 50 个时期。这可能是改进模型(11 * 50 时期)或少量重复(10)的人工制品的重要进一步训练的标志。

Box and Whisker Plots Comparing the Number of Update Epochs

Box 和 Whisker Plots 比较更新时期的数量

重要的是要指出这些结果特定于模型配置和此数据集。

虽然这些实验确实为您自己的预测性建模问题执行类似的实验提供了框架,但很难将这些结果概括为超出此具体示例。

扩展

本节列出了本节中有关实验扩展的建议。

  • 统计显着性检验。我们可以计算成对统计显着性检验,例如学生 t 检验,以查看结果群体中均值之间的差异是否具有统计学意义。
  • 更多重复。我们可以将重复次数从 10 增加到 30,100 或更多,以使结果更加稳健。
  • 更多时代。基础 LSTM 模型仅适用于 500 个具有在线训练的时期,并且相信额外的训练时期将导致更准确的基线模型。减少了时期的数量以减少实验运行时间。
  • 与更多 Epochs 比较。更新模型的实验结果应直接与固定模型的实验进行比较,固定模型使用相同数量的总体时期来查看是否将额外的测试模式添加到训练数据集中会产生明显的差异。例如,可以将每个测试模式的 2 个更新时期与针对 500 +(12-1)* 2)或 522 个时期训练的固定模型进行比较,更新模型 5 与适合 500 +(12-1)的固定模型进行比较)* 5)或 555 个时代,依此类推。
  • 全新模型。在将每个测试模式添加到训练数据集后,添加一个适合新模型的实验。这是尝试过的,但延长的运行时间阻止了在完成本教程之前收集结果。预计这将提供与更新和固定模型的有趣比较点。

你有没有探索过这些扩展? 在评论中报告您的结果;我很想听听你发现了什么。

摘要

在本教程中,您了解了如何更新 LSTM 网络,因为新数据可用于 Python 中的时间序列预测。

具体来说,你学到了:

  • 如何设计一套系统的实验来探索更新 LSTM 模型的效果。
  • 如何在新数据可用时更新 LSTM 模型。
  • 对 LSTM 模型的更新可以产生更有效的预测模型,但是需要仔细校准预测问题。

你有任何问题吗? 在下面的评论中提出您的问题,我会尽力回答。

如何为时间序列预测使用 LSTM 网络的丢弃法

原文: machinelearningmastery.com/use-dropout-lstm-networks-time-series-forecasting/

长短期记忆(LSTM)模型是一种能够学习观察序列的循环神经网络。

这可能使它们成为一个非常适合时间序列预测的网络。

LSTM 的一个问题是他们可以轻松地过度训练训练数据,降低他们的预测技巧。

Dropout 是一种正规化方法,在训练网络时,LSTM 单元的输入和重复连接在概率上被排除在激活和权重更新之外。这具有减少过拟合和改善模型表现的效果。

在本教程中,您将了解如何在 LSTM 网络和设计实验中使用 dropout 来测试其对时间序列预测的有效性。

完成本教程后,您将了解:

  • 如何设计一个强大的测试工具来评估 LSTM 网络的时间序列预测。
  • 如何使用 LSTM 使用输入权重丢失来设计,执行和解释结果。
  • 如何设计,执行和解释使用 LSTM 重复丢失重量的结果。

让我们开始吧。

How to Use Dropout with LSTM Networks for Time Series Forecasting

如何使用 LSTM 网络的 Dropout 进行时间序列预测 照片来自 Jonas Bengtsson,保留一些权利。

教程概述

本教程分为 5 个部分。他们是:

  1. 洗发水销售数据集
  2. 实验测试线束
  3. 输入 dropout
  4. 经常性 dropout
  5. 审查结果

环境

本教程假定您已安装 Python SciPy 环境。您可以在此示例中使用 Python 2 或 3。

本教程假设您安装了 TensorFlow 或 Theano 后端的 Keras v2.0 或更高版本。

本教程还假设您安装了 scikit-learn,Pandas,NumPy 和 Matplotlib。

接下来,让我们看看标准时间序列预测问题,我们可以将其用作此实验的上下文。

如果您在设置 Python 环境时需要帮助,请参阅以下帖子:

洗发水销售数据集

该数据集描述了 3 年期间每月洗发水的销售数量。

单位是销售计数,有 36 个观察。原始数据集归功于 Makridakis,Wheelwright 和 Hyndman(1998)。

您可以在此处下载并了解有关数据集的更多信息

下面的示例加载并创建已加载数据集的图。

# load and plot dataset
from pandas import read_csv
from pandas import datetime
from matplotlib import pyplot
# load dataset
def parser(x):
	return datetime.strptime('190'+x, '%Y-%m')
series = read_csv('shampoo-sales.csv', header=0, parse_dates=[0], index_col=0, squeeze=True, date_parser=parser)
# summarize first few rows
print(series.head())
# line plot
series.plot()
pyplot.show()

运行该示例将数据集作为 Pandas Series 加载并打印前 5 行。

Month
1901-01-01 266.0
1901-02-01 145.9
1901-03-01 183.1
1901-04-01 119.3
1901-05-01 180.3
Name: Sales, dtype: float64

然后创建该系列的线图,显示明显的增加趋势。

Line Plot of Shampoo Sales Dataset

洗发水销售数据集的线图

接下来,我们将看一下实验中使用的模型配置和测试工具。

实验测试线束

本节介绍本教程中使用的测试工具。

数据拆分

我们将 Shampoo Sales 数据集分为两部分:训练和测试集。

前两年的数据将用于训练数据集,剩余的一年数据将用于测试集。

将使用训练数据集开发模型,并对测试数据集做出预测。

测试数据集的持久性预测(朴素预测)实现了每月洗发水销售 136.761 的错误。这在测试集上提供了较低的可接受表现限制。

模型评估

将使用滚动预测场景,也称为前进模型验证。

测试数据集的每个时间步骤将一次一个地走。将使用模型对时间步长做出预测,然后将获取测试集的实际预期值,并使其可用于下一时间步的预测模型。

这模仿了一个真实世界的场景,每个月都会有新的洗发水销售观察结果,并用于下个月的预测。

这将通过训练和测试数据集的结构进行模拟。

将收集关于测试数据集的所有预测,并计算错误分数以总结模型的技能。将使用均方根误差(RMSE),因为它会对大错误进行处罚,并产生与预测数据相同的分数,即每月洗发水销售额。

数据准备

在我们将模型拟合到数据集之前,我们必须转换数据。

在拟合模型和做出预测之前,对数据集执行以下三个数据变换。

  1. 转换时间序列数据,使其静止。具体而言,滞后= 1 差分以消除数据中的增加趋势。
  2. 将时间序列转换为监督学习问题。具体而言,将数据组织成输入和输出模式,其中前一时间步的观察被用作预测当前时间步的观察的输入
  3. 将观察结果转换为具有特定比例。具体而言,将数据重缩放为-1 到 1 之间的值。

这些变换在预测时反转,在计算和误差分数之前将它们恢复到原始比例。

LSTM 模型

我们将使用基础状态 LSTM 模型,其中 1 个神经元适合 1000 个时期。

批量大小为 1 是必需的,因为我们将使用前向验证并对最后 12 个月的测试数据进行一步预测。

批量大小为 1 意味着该模型将使用在线训练(而不是批量训练或小批量训练)。因此,预计模型拟合将具有一些变化。

理想情况下,将使用更多的训练时期(例如 1500),但这被截断为 1000 以保持运行时间合理。

使用有效的 ADAM 优化算法和均方误差损失函数来拟合模型。

实验运行

每个实验场景将运行 30 次,并且测试集上的 RMSE 得分将从每次运行结束时记录。

让我们深入研究实验。

基线 LSTM 模型

让我们从基线 LSTM 模型开始。

此问题的基线 LSTM 模型具有以下配置:

  • 滞后输入:1
  • 时代:1000
  • LSTM 隐藏层中的单位:3
  • 批量大小:4
  • 重复:3

完整的代码清单如下。

此代码清单将用作所有后续实验的基础,只有后续部分中提供的此代码清单的更改。

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
import matplotlib
# be able to save images on server
matplotlib.use('Agg')
from matplotlib import pyplot
import numpy

# date-time parsing function for loading the dataset
def parser(x):
	return datetime.strptime('190'+x, '%Y-%m')

# frame a sequence as a supervised learning problem
def timeseries_to_supervised(data, lag=1):
	df = DataFrame(data)
	columns = [df.shift(i) for i in range(1, lag+1)]
	columns.append(df)
	df = concat(columns, axis=1)
	return df

# create a differenced series
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)

# invert differenced value
def inverse_difference(history, yhat, interval=1):
	return yhat + history[-interval]

# scale train and test data to [-1, 1]
def scale(train, test):
	# fit scaler
	scaler = MinMaxScaler(feature_range=(-1, 1))
	scaler = scaler.fit(train)
	# transform train
	train = train.reshape(train.shape[0], train.shape[1])
	train_scaled = scaler.transform(train)
	# transform test
	test = test.reshape(test.shape[0], test.shape[1])
	test_scaled = scaler.transform(test)
	return scaler, train_scaled, test_scaled

# inverse scaling for a forecasted value
def invert_scale(scaler, X, yhat):
	new_row = [x for x in X] + [yhat]
	array = numpy.array(new_row)
	array = array.reshape(1, len(array))
	inverted = scaler.inverse_transform(array)
	return inverted[0, -1]

# fit an LSTM network to training data
def fit_lstm(train, n_batch, nb_epoch, n_neurons):
	X, y = train[:, 0:-1], train[:, -1]
	X = X.reshape(X.shape[0], 1, X.shape[1])
	model = Sequential()
	model.add(LSTM(n_neurons, batch_input_shape=(n_batch, X.shape[1], X.shape[2]), stateful=True))
	model.add(Dense(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()
	return model

# run a repeated experiment
def experiment(series, n_lag, n_repeats, n_epochs, n_batch, n_neurons):
	# transform data to be stationary
	raw_values = series.values
	diff_values = difference(raw_values, 1)
	# transform data to be supervised learning
	supervised = timeseries_to_supervised(diff_values, n_lag)
	supervised_values = supervised.values[n_lag:,:]
	# split data into train and test-sets
	train, test = supervised_values[0:-12], supervised_values[-12:]
	# transform the scale of the data
	scaler, train_scaled, test_scaled = scale(train, test)
	# run experiment
	error_scores = list()
	for r in range(n_repeats):
		# fit the model
		train_trimmed = train_scaled[2:, :]
		lstm_model = fit_lstm(train_trimmed, n_batch, n_epochs, n_neurons)
		# forecast test dataset
		test_reshaped = test_scaled[:,0:-1]
		test_reshaped = test_reshaped.reshape(len(test_reshaped), 1, 1)
		output = lstm_model.predict(test_reshaped, batch_size=n_batch)
		predictions = list()
		for i in range(len(output)):
			yhat = output[i,0]
			X = test_scaled[i, 0:-1]
			# invert scaling
			yhat = invert_scale(scaler, X, yhat)
			# invert differencing
			yhat = inverse_difference(raw_values, yhat, len(test_scaled)+1-i)
			# store forecast
			predictions.append(yhat)
		# report performance
		rmse = sqrt(mean_squared_error(raw_values[-12:], predictions))
		print('%d) Test RMSE: %.3f' % (r+1, rmse))
		error_scores.append(rmse)
	return error_scores

# configure the experiment
def run():
	# load dataset
	series = read_csv('shampoo-sales.csv', header=0, parse_dates=[0], index_col=0, squeeze=True, date_parser=parser)
	# configure the experiment
	n_lag = 1
	n_repeats = 30
	n_epochs = 1000
	n_batch = 4
	n_neurons = 3
	# run the experiment
	results = DataFrame()
	results['results'] = experiment(series, n_lag, n_repeats, n_epochs, n_batch, n_neurons)
	# summarize results
	print(results.describe())
	# save boxplot
	results.boxplot()
	pyplot.savefig('experiment_baseline.png')

# entry point
run()

运行实验将打印所有重复测试 RMSE 的摘要统计量。

我们可以看到,平均而言,这种模型配置实现了约 92 个月洗发水销售的测试 RMSE,标准偏差为 5。

          results
count   30.000000
mean    92.842537
std      5.748456
min     81.205979
25%     89.514367
50%     92.030003
75%     96.926145
max    105.247117

还会根据测试 RMSE 结果的分布创建一个盒子和胡须图并保存到文件中。

该图清楚地描述了结果的传播,突出了中间 50%的值(框)和中位数(绿线)。

Box and Whisker Plot of Baseline Performance on the Shampoo Sales Dataset

洗发水销售数据集中基线表现的盒子和晶须图

网络配置需要考虑的另一个角度是模型适应时的行为方式。

我们可以在每个训练时期之后评估训练和测试数据集上的模型,以了解配置是否过拟合或不适合问题。

我们将在每组实验的最佳结果上使用此诊断方法。将运行总共 10 次重复的配置,并且在线图上绘制每个训练迭代之后的训练和测试 RMSE 得分。

在这种情况下,我们将在适用于 1000 个时期的 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
import matplotlib
# be able to save images on server
matplotlib.use('Agg')
from matplotlib import pyplot
import numpy

# date-time parsing function for loading the dataset
def parser(x):
	return datetime.strptime('190'+x, '%Y-%m')

# frame a sequence as a supervised learning problem
def timeseries_to_supervised(data, lag=1):
	df = DataFrame(data)
	columns = [df.shift(i) for i in range(1, lag+1)]
	columns.append(df)
	df = concat(columns, axis=1)
	return df

# create a differenced series
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)

# scale train and test data to [-1, 1]
def scale(train, test):
	# fit scaler
	scaler = MinMaxScaler(feature_range=(-1, 1))
	scaler = scaler.fit(train)
	# transform train
	train = train.reshape(train.shape[0], train.shape[1])
	train_scaled = scaler.transform(train)
	# transform test
	test = test.reshape(test.shape[0], test.shape[1])
	test_scaled = scaler.transform(test)
	return scaler, train_scaled, test_scaled

# inverse scaling for a forecasted value
def invert_scale(scaler, X, yhat):
	new_row = [x for x in X] + [yhat]
	array = numpy.array(new_row)
	array = array.reshape(1, len(array))
	inverted = scaler.inverse_transform(array)
	return inverted[0, -1]

# evaluate the model on a dataset, returns RMSE in transformed units
def evaluate(model, raw_data, scaled_dataset, scaler, offset, batch_size):
	# separate
	X, y = scaled_dataset[:,0:-1], scaled_dataset[:,-1]
	# reshape
	reshaped = X.reshape(len(X), 1, 1)
	# forecast dataset
	output = model.predict(reshaped, batch_size=batch_size)
	# invert data transforms on forecast
	predictions = list()
	for i in range(len(output)):
		yhat = output[i,0]
		# invert scaling
		yhat = invert_scale(scaler, X[i], yhat)
		# invert differencing
		yhat = yhat + raw_data[i]
		# store forecast
		predictions.append(yhat)
	# report performance
	rmse = sqrt(mean_squared_error(raw_data[1:], predictions))
	# reset model state
	model.reset_states()
	return rmse

# fit an LSTM network to training data
def fit_lstm(train, test, raw, scaler, batch_size, nb_epoch, neurons):
	X, y = train[:, 0:-1], train[:, -1]
	X = X.reshape(X.shape[0], 1, X.shape[1])
	# prepare model
	model = Sequential()
	model.add(LSTM(neurons, batch_input_shape=(batch_size, X.shape[1], X.shape[2]), stateful=True))
	model.add(Dense(1))
	model.compile(loss='mean_squared_error', optimizer='adam')
	# fit model
	train_rmse, test_rmse = list(), list()
	for i in range(nb_epoch):
		model.fit(X, y, epochs=1, batch_size=batch_size, verbose=0, shuffle=False)
		model.reset_states()
		# evaluate model on train data
		raw_train = raw[-(len(train)+len(test)+1):-len(test)]
		train_rmse.append(evaluate(model, raw_train, train, scaler, 0, batch_size))
		# evaluate model on test data
		raw_test = raw[-(len(test)+1):]
		test_rmse.append(evaluate(model, raw_test, test, scaler, 0, batch_size))
	history = DataFrame()
	history['train'], history['test'] = train_rmse, test_rmse
	return history

# run diagnostic experiments
def run():
	# config
	n_lag = 1
	n_repeats = 10
	n_epochs = 1000
	n_batch = 4
	n_neurons = 3
	# load dataset
	series = read_csv('shampoo-sales.csv', header=0, parse_dates=[0], index_col=0, squeeze=True, date_parser=parser)
	# transform data to be stationary
	raw_values = series.values
	diff_values = difference(raw_values, 1)
	# transform data to be supervised learning
	supervised = timeseries_to_supervised(diff_values, n_lag)
	supervised_values = supervised.values[n_lag:,:]
	# split data into train and test-sets
	train, test = supervised_values[0:-12], supervised_values[-12:]
	# transform the scale of the data
	scaler, train_scaled, test_scaled = scale(train, test)
	# fit and evaluate model
	train_trimmed = train_scaled[2:, :]
	# run diagnostic tests
	for i in range(n_repeats):
		history = fit_lstm(train_trimmed, test_scaled, raw_values, scaler, n_batch, n_epochs, n_neurons)
		pyplot.plot(history['train'], color='blue')
		pyplot.plot(history['test'], color='orange')
		print('%d) TrainRMSE=%f, TestRMSE=%f' % (i+1, history['train'].iloc[-1], history['test'].iloc[-1]))
	pyplot.savefig('diagnostic_baseline.png')

# entry point
run()

运行诊断程序打印最终训练并测试每次运行的 RMSE。更有趣的是创建的最终线图。

线图显示了每个训练时期之后的训练 RMSE(蓝色)和测试 RMSE(橙色)。

在这种情况下,诊断图显示训练和测试 RMSE 稳定下降到大约 400-500 个时期,此后似乎可能发生一些过拟合。这表现为训练 RMSE 的持续下降和测试 RMSE 的增加。

Diagnostic Line Plot of the Baseline Model on the Shampoo Sales Daset

洗发水销售数据集基线模型的诊断线图

输入 dropout

Dropout 可以应用于 LSTM 节点内的输入连接。

输入的丢失意味着对于给定的概率,每个 LSTM 块的输入连接上的数据将从节点激活和权重更新中排除。

在 Keras 中,在创建 LSTM 层时使用dropout参数指定。丢失值是 0(无丢失)和 1(无连接)之间的百分比。

在这个实验中,我们将比较没有 dropout 率和 20%,40%和 60%的输入 dropout 率。

下面列出了更新的 fit_lstm(),_ 实验()_ 和run()函数,用于将输入丢失与 LSTM 一起使用。

# fit an LSTM network to training data
def fit_lstm(train, n_batch, nb_epoch, n_neurons, dropout):
	X, y = train[:, 0:-1], train[:, -1]
	X = X.reshape(X.shape[0], 1, X.shape[1])
	model = Sequential()
	model.add(LSTM(n_neurons, batch_input_shape=(n_batch, X.shape[1], X.shape[2]), stateful=True, dropout=dropout))
	model.add(Dense(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()
	return model

# run a repeated experiment
def experiment(series, n_lag, n_repeats, n_epochs, n_batch, n_neurons, dropout):
	# transform data to be stationary
	raw_values = series.values
	diff_values = difference(raw_values, 1)
	# transform data to be supervised learning
	supervised = timeseries_to_supervised(diff_values, n_lag)
	supervised_values = supervised.values[n_lag:,:]
	# split data into train and test-sets
	train, test = supervised_values[0:-12], supervised_values[-12:]
	# transform the scale of the data
	scaler, train_scaled, test_scaled = scale(train, test)
	# run experiment
	error_scores = list()
	for r in range(n_repeats):
		# fit the model
		train_trimmed = train_scaled[2:, :]
		lstm_model = fit_lstm(train_trimmed, n_batch, n_epochs, n_neurons, dropout)
		# forecast test dataset
		test_reshaped = test_scaled[:,0:-1]
		test_reshaped = test_reshaped.reshape(len(test_reshaped), 1, 1)
		output = lstm_model.predict(test_reshaped, batch_size=n_batch)
		predictions = list()
		for i in range(len(output)):
			yhat = output[i,0]
			X = test_scaled[i, 0:-1]
			# invert scaling
			yhat = invert_scale(scaler, X, yhat)
			# invert differencing
			yhat = inverse_difference(raw_values, yhat, len(test_scaled)+1-i)
			# store forecast
			predictions.append(yhat)
		# report performance
		rmse = sqrt(mean_squared_error(raw_values[-12:], predictions))
		print('%d) Test RMSE: %.3f' % (r+1, rmse))
		error_scores.append(rmse)
	return error_scores

# configure the experiment
def run():
	# load dataset
	series = read_csv('shampoo-sales.csv', header=0, parse_dates=[0], index_col=0, squeeze=True, date_parser=parser)
	# configure the experiment
	n_lag = 1
	n_repeats = 30
	n_epochs = 1000
	n_batch = 4
	n_neurons = 3
	n_dropout = [0.0, 0.2, 0.4, 0.6]
	# run the experiment
	results = DataFrame()
	for dropout in n_dropout:
		results[str(dropout)] = experiment(series, n_lag, n_repeats, n_epochs, n_batch, n_neurons, dropout)
	# summarize results
	print(results.describe())
	# save boxplot
	results.boxplot()
	pyplot.savefig('experiment_dropout_input.png')

运行此实验会打印每个已评估配置的描述性统计量。

结果表明,平均输入 dropout 率为 40%会带来更好的表现,但 dropout 率为 20%,40%和 60%的平均结果之间的差异非常小。所有人似乎都胜过 dropout。

              0.0         0.2        0.4        0.6
count   30.000000   30.000000  30.000000  30.000000
mean    97.578280   89.448450  88.957421  89.810789
std      7.927639    5.807239   4.070037   3.467317
min     84.749785   81.315336  80.662878  84.300135
25%     92.520968   84.712064  85.885858  87.766818
50%     97.324110   88.109654  88.790068  89.585945
75%    101.258252   93.642621  91.515127  91.109452
max    123.578235  104.528209  96.687333  99.660331

还会创建一个框和胡须图来比较每个配置的结果分布。

该图显示结果的扩散随输入 dropout 的增加而减少。该图还表明输入丢失率为 20%可能略低于中值测试 RMSE。

结果确实鼓励对所选 LSTM 配置使用一些输入丢失,可能设置为 40%。

Box and Whisker Plot of Input Dropout Performance on the Shampoo Sales Dataset

洗发水销售数据集中输入 dropout 表现的盒子和晶须图

我们可以查看 40%的输入丢失如何影响模型的动态,同时适合训练数据。

下面的代码总结了fit_lstm()run()函数与诊断脚本基线版本的更新。

# fit an LSTM network to training data
def fit_lstm(train, test, raw, scaler, batch_size, nb_epoch, neurons, dropout):
	X, y = train[:, 0:-1], train[:, -1]
	X = X.reshape(X.shape[0], 1, X.shape[1])
	# prepare model
	model = Sequential()
	model.add(LSTM(neurons, batch_input_shape=(batch_size, X.shape[1], X.shape[2]), stateful=True, dropout=dropout))
	model.add(Dense(1))
	model.compile(loss='mean_squared_error', optimizer='adam')
	# fit model
	train_rmse, test_rmse = list(), list()
	for i in range(nb_epoch):
		model.fit(X, y, epochs=1, batch_size=batch_size, verbose=0, shuffle=False)
		model.reset_states()
		# evaluate model on train data
		raw_train = raw[-(len(train)+len(test)+1):-len(test)]
		train_rmse.append(evaluate(model, raw_train, train, scaler, 0, batch_size))
		# evaluate model on test data
		raw_test = raw[-(len(test)+1):]
		test_rmse.append(evaluate(model, raw_test, test, scaler, 0, batch_size))
	history = DataFrame()
	history['train'], history['test'] = train_rmse, test_rmse
	return history

# run diagnostic experiments
def run():
	# config
	n_lag = 1
	n_repeats = 10
	n_epochs = 1000
	n_batch = 4
	n_neurons = 3
	dropout = 0.4
	# load dataset
	series = read_csv('shampoo-sales.csv', header=0, parse_dates=[0], index_col=0, squeeze=True, date_parser=parser)
	# transform data to be stationary
	raw_values = series.values
	diff_values = difference(raw_values, 1)
	# transform data to be supervised learning
	supervised = timeseries_to_supervised(diff_values, n_lag)
	supervised_values = supervised.values[n_lag:,:]
	# split data into train and test-sets
	train, test = supervised_values[0:-12], supervised_values[-12:]
	# transform the scale of the data
	scaler, train_scaled, test_scaled = scale(train, test)
	# fit and evaluate model
	train_trimmed = train_scaled[2:, :]
	# run diagnostic tests
	for i in range(n_repeats):
		history = fit_lstm(train_trimmed, test_scaled, raw_values, scaler, n_batch, n_epochs, n_neurons, dropout)
		pyplot.plot(history['train'], color='blue')
		pyplot.plot(history['test'], color='orange')
		print('%d) TrainRMSE=%f, TestRMSE=%f' % (i+1, history['train'].iloc[-1], history['test'].iloc[-1]))
	pyplot.savefig('diagnostic_dropout_input.png')

运行更新的诊断会在每个训练时期之后创建训练图并测试模型的 RMSE 表现以及输入丢失。

结果显示在训练上明显增加了凸起并测试了 RMSE 轨迹,这在测试 RMSE 分数上更为明显。

我们还可以看到过拟合的症状已经通过测试 RMSE 在整个 1000 个时期内持续下降来解决,这可能表明需要额外的训练时期来利用这种行为。

Diagnostic Line Plot of Input Dropout Performance on the Shampoo Sales Dataset

洗发水销售数据集中输入 dropout 表现的诊断线图

经常性 dropout

丢失也可以应用于 LSTM 单元上的循环输入信号。

在 Keras 中,这是通过在定义 LSTM 层时设置recurrent_dropout参数来实现的。

在这个实验中,我们将比较没有 dropout 率与 20%,40%和 60%的复发 dropout 率。

下面列出了更新的 fit_lstm(),_ 实验()_ 和run()函数,用于将输入丢失与 LSTM 一起使用。

# fit an LSTM network to training data
def fit_lstm(train, n_batch, nb_epoch, n_neurons, dropout):
	X, y = train[:, 0:-1], train[:, -1]
	X = X.reshape(X.shape[0], 1, X.shape[1])
	model = Sequential()
	model.add(LSTM(n_neurons, batch_input_shape=(n_batch, X.shape[1], X.shape[2]), stateful=True, recurrent_dropout=dropout))
	model.add(Dense(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()
	return model

# run a repeated experiment
def experiment(series, n_lag, n_repeats, n_epochs, n_batch, n_neurons, dropout):
	# transform data to be stationary
	raw_values = series.values
	diff_values = difference(raw_values, 1)
	# transform data to be supervised learning
	supervised = timeseries_to_supervised(diff_values, n_lag)
	supervised_values = supervised.values[n_lag:,:]
	# split data into train and test-sets
	train, test = supervised_values[0:-12], supervised_values[-12:]
	# transform the scale of the data
	scaler, train_scaled, test_scaled = scale(train, test)
	# run experiment
	error_scores = list()
	for r in range(n_repeats):
		# fit the model
		train_trimmed = train_scaled[2:, :]
		lstm_model = fit_lstm(train_trimmed, n_batch, n_epochs, n_neurons, dropout)
		# forecast test dataset
		test_reshaped = test_scaled[:,0:-1]
		test_reshaped = test_reshaped.reshape(len(test_reshaped), 1, 1)
		output = lstm_model.predict(test_reshaped, batch_size=n_batch)
		predictions = list()
		for i in range(len(output)):
			yhat = output[i,0]
			X = test_scaled[i, 0:-1]
			# invert scaling
			yhat = invert_scale(scaler, X, yhat)
			# invert differencing
			yhat = inverse_difference(raw_values, yhat, len(test_scaled)+1-i)
			# store forecast
			predictions.append(yhat)
		# report performance
		rmse = sqrt(mean_squared_error(raw_values[-12:], predictions))
		print('%d) Test RMSE: %.3f' % (r+1, rmse))
		error_scores.append(rmse)
	return error_scores

# configure the experiment
def run():
	# load dataset
	series = read_csv('shampoo-sales.csv', header=0, parse_dates=[0], index_col=0, squeeze=True, date_parser=parser)
	# configure the experiment
	n_lag = 1
	n_repeats = 30
	n_epochs = 1000
	n_batch = 4
	n_neurons = 3
	n_dropout = [0.0, 0.2, 0.4, 0.6]
	# run the experiment
	results = DataFrame()
	for dropout in n_dropout:
		results[str(dropout)] = experiment(series, n_lag, n_repeats, n_epochs, n_batch, n_neurons, dropout)
	# summarize results
	print(results.describe())
	# save boxplot
	results.boxplot()
	pyplot.savefig('experiment_dropout_recurrent.png')

运行此实验会打印每个已评估配置的描述性统计量。

平均结果表明,平均复发性 dropout 率为 20%或 40%是首选,但总体而言,结果并不比基线好多少。

              0.0         0.2         0.4         0.6
count   30.000000   30.000000   30.000000   30.000000
mean    95.743719   93.658016   93.706112   97.354599
std      9.222134    7.318882    5.591550    5.626212
min     80.144342   83.668154   84.585629   87.215540
25%     88.336066   87.071944   89.859503   93.940016
50%     96.703481   92.522428   92.698024   97.119864
75%    101.902782  100.554822   96.252689  100.915336
max    113.400863  106.222955  104.347850  114.160922

还会创建一个框和胡须图来比较每个配置的结果分布。

该图显示了更紧密的分布,反复 dropout 率为 40%,相比之下,20%和基线,可能使这种配置更可取。该图还强调,当使用反复丢失时,分布中的最小(最佳)测试 RMSE 似乎已受到影响,从而提供更差的表现。

Box and Whisker Plot of Recurrent Dropout Performance on the Shampoo Sales Dataset

洗发水销售数据集中反复 dropout 表现的盒子和晶须图

我们可以查看 40%的经常性 dropout 率如何影响模型的动态,同时适合训练数据。

下面的代码总结了fit_lstm()run()函数与诊断脚本基线版本的更新。

# fit an LSTM network to training data
def fit_lstm(train, test, raw, scaler, batch_size, nb_epoch, neurons, dropout):
	X, y = train[:, 0:-1], train[:, -1]
	X = X.reshape(X.shape[0], 1, X.shape[1])
	# prepare model
	model = Sequential()
	model.add(LSTM(neurons, batch_input_shape=(batch_size, X.shape[1], X.shape[2]), stateful=True, recurrent_dropout=dropout))
	model.add(Dense(1))
	model.compile(loss='mean_squared_error', optimizer='adam')
	# fit model
	train_rmse, test_rmse = list(), list()
	for i in range(nb_epoch):
		model.fit(X, y, epochs=1, batch_size=batch_size, verbose=0, shuffle=False)
		model.reset_states()
		# evaluate model on train data
		raw_train = raw[-(len(train)+len(test)+1):-len(test)]
		train_rmse.append(evaluate(model, raw_train, train, scaler, 0, batch_size))
		# evaluate model on test data
		raw_test = raw[-(len(test)+1):]
		test_rmse.append(evaluate(model, raw_test, test, scaler, 0, batch_size))
	history = DataFrame()
	history['train'], history['test'] = train_rmse, test_rmse
	return history

# run diagnostic experiments
def run():
	# config
	n_lag = 1
	n_repeats = 10
	n_epochs = 1000
	n_batch = 4
	n_neurons = 3
	dropout = 0.4
	# load dataset
	series = read_csv('shampoo-sales.csv', header=0, parse_dates=[0], index_col=0, squeeze=True, date_parser=parser)
	# transform data to be stationary
	raw_values = series.values
	diff_values = difference(raw_values, 1)
	# transform data to be supervised learning
	supervised = timeseries_to_supervised(diff_values, n_lag)
	supervised_values = supervised.values[n_lag:,:]
	# split data into train and test-sets
	train, test = supervised_values[0:-12], supervised_values[-12:]
	# transform the scale of the data
	scaler, train_scaled, test_scaled = scale(train, test)
	# fit and evaluate model
	train_trimmed = train_scaled[2:, :]
	# run diagnostic tests
	for i in range(n_repeats):
		history = fit_lstm(train_trimmed, test_scaled, raw_values, scaler, n_batch, n_epochs, n_neurons, dropout)
		pyplot.plot(history['train'], color='blue')
		pyplot.plot(history['test'], color='orange')
		print('%d) TrainRMSE=%f, TestRMSE=%f' % (i+1, history['train'].iloc[-1], history['test'].iloc[-1]))
	pyplot.savefig('diagnostic_dropout_recurrent.png')

运行更新的诊断会在每个训练时期之后创建训练图并测试模型的 RMSE 表现以及输入丢失。

该图显示了测试 RMSE 迹线上增加的凸起,对训练 RMSE 迹线几乎没有影响。该图还表明,在大约 500 个时期之后,如果不是测试 RMSE 的增加趋势,则该平台也是如此。

至少在这个 LSTM 配置和这个问题上,可能反复发生的丢失可能不会增加太多价值。

Diagnostic Line Plot of Recurrent Dropout Performance on the Shampoo Sales Dataset

洗发水销售数据集中经常性 dropout 表现的诊断线图

扩展

本节列出了在完成本教程后您可能希望考虑进一步实验的一些想法。

  • 输入层丢失。可能值得探讨在输入层上使用压差以及它如何影响 LSTM 的表现和过拟合。
  • 组合输入和循环。可能值得探索输入和重复丢失的组合,以查看是否可以提供任何额外的好处。
  • 其他正则化方法。使用 LSTM 网络探索其他正则化方法可能是值得的,例如各种输入,循环和偏置权重正则化函数。

进一步阅读

有关在 Keras 中使用 MLP 模型退出的更多信息,请参阅帖子:

以下是一些关于 LSTM 网络 dropout 的论文,您可能会发现这些论文对于进一步阅读非常有用。

摘要

在本教程中,您了解了如何将 dropout 与 LSTM 一起用于时间序列预测。

具体来说,你学到了:

  • 如何设计一个强大的测试工具来评估 LSTM 网络的时间序列预测。
  • 如何在 LSTM 上配置输入权重丢失以进行时间序列预测。
  • 如何在 LSTM 上配置循环重量丢失以进行时间序列预测。

您对使用 LSTM 网络的丢失有任何疑问吗? 在下面的评论中提出您的问题,我会尽力回答。

如何为时间序列预测使用 LSTM 网络中的特征

原文: machinelearningmastery.com/use-features-lstm-networks-time-series-forecasting/

Keras 中的长短期记忆(LSTM)网络支持多种输入功能。

这就提出了一个问题,即单变量时间序列的滞后观测是否可以用作 LSTM 的特征,以及这是否会改善预测表现。

在本教程中,我们将研究使用滞后观察作为 Python 中 LSTM 模型的特征。

完成本教程后,您将了解:

  • 如何开发测试工具以系统地评估 LSTM 功能以进行时间序列预测。
  • 使用不同数量的滞后观测值作为 LSTM 模型的输入特征的影响。
  • 对 LSTM 模型使用不同数量的滞后观察和匹配数量的神经元的影响。

让我们开始吧。

How to Use Features in LSTM Networks for Time Series Forecasting

如何使用 LSTM 网络中的功能进行时间​​序列预测 Tom Hodgkinson 的照片,保留一些权利。

教程概述

本教程分为 4 个部分。他们是:

  1. 洗发水销售数据集
  2. 实验测试线束
  3. 使用 Timesteps 的实验
  4. 时间步和神经元的实验

环境

本教程假定您已安装 Python SciPy 环境。您可以在此示例中使用 Python 2 或 3。

本教程假设您安装了 TensorFlow 或 Theano 后端的 Keras v2.0 或更高版本。

本教程还假设您安装了 scikit-learn,Pandas,NumPy 和 Matplotlib。

如果您在设置 Python 环境时需要帮助,请参阅以下帖子:

洗发水销售数据集

该数据集描述了 3 年期间每月洗发水的销售数量。

单位是销售计数,有 36 个观察。原始数据集归功于 Makridakis,Wheelwright 和 Hyndman(1998)。

您可以在此处下载并了解有关数据集的更多信息

下面的示例加载并创建已加载数据集的图。

# load and plot dataset
from pandas import read_csv
from pandas import datetime
from matplotlib import pyplot
# load dataset
def parser(x):
	return datetime.strptime('190'+x, '%Y-%m')
series = read_csv('shampoo-sales.csv', header=0, parse_dates=[0], index_col=0, squeeze=True, date_parser=parser)
# summarize first few rows
print(series.head())
# line plot
series.plot()
pyplot.show()

运行该示例将数据集作为 Pandas Series 加载并打印前 5 行。

Month
1901-01-01 266.0
1901-02-01 145.9
1901-03-01 183.1
1901-04-01 119.3
1901-05-01 180.3
Name: Sales, dtype: float64

然后创建该系列的线图,显示明显的增加趋势。

Line Plot of Shampoo Sales Dataset

洗发水销售数据集的线图

接下来,我们将了解实验中使用的 LSTM 配置和测试工具。

实验测试线束

本节介绍本教程中使用的测试工具。

数据拆分

我们将 Shampoo Sales 数据集分为两部分:训练和测试集。

前两年的数据将用于训练数据集,剩余的一年数据将用于测试集。

将使用训练数据集开发模型,并对测试数据集做出预测。

测试数据集的持久性预测(朴素预测)实现了每月洗发水销售 136.761 的错误。这在测试集上提供了较低的可接受表现限制。

模型评估

将使用滚动预测场景,也称为前进模型验证。

测试数据集的每个时间步骤将一次一个地走。将使用模型对时间步长做出预测,然后将获取测试集的实际预期值,并使其可用于下一时间步的预测模型。

这模仿了一个真实世界的场景,每个月都会有新的洗发水销售观察结果,并用于下个月的预测。

这将通过训练和测试数据集的结构进行模拟。

将收集关于测试数据集的所有预测,并计算错误分数以总结模型的技能。将使用均方根误差(RMSE),因为它会对大错误进行处罚,并产生与预测数据相同的分数,即每月洗发水销售额。

数据准备

在我们将 LSTM 模型拟合到数据集之前,我们必须转换数据。

在拟合模型和做出预测之前,对数据集执行以下三个数据变换。

  1. 转换时间序列数据,使其静止。具体而言,滞后= 1 差分以消除数据中的增加趋势。
  2. 将时间序列转换为监督学习问题。具体而言,将数据组织成输入和输出模式,其中前一时间步的观察被用作预测当前时间步的观察的输入
  3. 将观察结果转换为具有特定比例。具体而言,要将数据重缩放为-1 到 1 之间的值,以满足 LSTM 模型的默认双曲正切激活函数。

这些变换在预测时反转,在计算和误差分数之前将它们恢复到原始比例。

LSTM 模型

我们将使用基础状态 LSTM 模型,其中 1 个神经元适合 500 个时期。

批量大小为 1 是必需的,因为我们将使用前向验证并对最后 12 个月的测试数据进行一步预测。

批量大小为 1 意味着该模型将使用在线训练(而不是批量训练或小批量训练)。因此,预计模型拟合将具有一些变化。

理想情况下,将使用更多的训练时期(例如 1000 或 1500),但这被截断为 500 以保持运行时间合理。

使用有效的 ADAM 优化算法和均方误差损失函数来拟合模型。

实验运行

每个实验场景将运行 10 次。

其原因在于,每次训练给定配置时,LSTM 网络的随机初始条件可能导致非常不同的结果。

让我们深入研究实验。

具有特征的实验

我们将进行 5 次实验;每个将使用不同数量的滞后观察作为 1 至 5 的特征。

使用有状态 LSTM 时,具有 1 输入要素的表示将是默认表示。设计使用 2 到 5 个功能。希望是滞后观测的附加背景可以改善预测模型的表现。

在训练模型之前,单变量时间序列被转换为监督学习问题。指定数量的特征定义用于预测下一次观察的输入变量(X)的数量(y)。因此,对于表示中使用的每个要素,必须从数据集的开头删除许多行。这是因为没有先前的观察结果可用作数据集中第一个值的特征。

下面提供了测试 1 输入功能的完整代码清单。

对于 5 个实验中的每一个,run()函数中的特征参数从 1 到 5 变化。此外,结果在实验结束时保存到文件中,并且还必须针对每个不同的实验运行更改该文件名,例如,experiment_features_1.csvexperiment_features_2.csv

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
import matplotlib
import numpy
from numpy import concatenate

# date-time parsing function for loading the dataset
def parser(x):
	return datetime.strptime('190'+x, '%Y-%m')

# frame a sequence as a supervised learning problem
def timeseries_to_supervised(data, lag=1):
	df = DataFrame(data)
	columns = [df.shift(i) for i in range(1, lag+1)]
	columns.append(df)
	df = concat(columns, axis=1)
	return df

# create a differenced series
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)

# invert differenced value
def inverse_difference(history, yhat, interval=1):
	return yhat + history[-interval]

# scale train and test data to [-1, 1]
def scale(train, test):
	# fit scaler
	scaler = MinMaxScaler(feature_range=(-1, 1))
	scaler = scaler.fit(train)
	# transform train
	train = train.reshape(train.shape[0], train.shape[1])
	train_scaled = scaler.transform(train)
	# transform test
	test = test.reshape(test.shape[0], test.shape[1])
	test_scaled = scaler.transform(test)
	return scaler, train_scaled, test_scaled

# inverse scaling for a forecasted value
def invert_scale(scaler, X, yhat):
	new_row = [x for x in X] + [yhat]
	array = numpy.array(new_row)
	array = array.reshape(1, len(array))
	inverted = scaler.inverse_transform(array)
	return inverted[0, -1]

# fit an LSTM network to training data
def fit_lstm(train, batch_size, nb_epoch, neurons):
	X, y = train[:, 0:-1], train[:, -1]
	X = X.reshape(X.shape[0], 1, X.shape[1])
	model = Sequential()
	model.add(LSTM(neurons, batch_input_shape=(batch_size, X.shape[1], X.shape[2]), stateful=True))
	model.add(Dense(1))
	model.compile(loss='mean_squared_error', optimizer='adam')
	for i in range(nb_epoch):
		model.fit(X, y, epochs=1, batch_size=batch_size, verbose=0, shuffle=False)
		model.reset_states()
	return model

# make a one-step forecast
def forecast_lstm(model, batch_size, X):
	X = X.reshape(1, 1, len(X))
	yhat = model.predict(X, batch_size=batch_size)
	return yhat[0,0]

# run a repeated experiment
def experiment(repeats, series, features):
	# transform data to be stationary
	raw_values = series.values
	diff_values = difference(raw_values, 1)
	# transform data to be supervised learning
	supervised = timeseries_to_supervised(diff_values, features)
	supervised_values = supervised.values[features:,:]
	# split data into train and test-sets
	train, test = supervised_values[0:-12, :], supervised_values[-12:, :]
	# transform the scale of the data
	scaler, train_scaled, test_scaled = scale(train, test)
	# run experiment
	error_scores = list()
	for r in range(repeats):
		# fit the base model
		lstm_model = fit_lstm(train_scaled, 1, 500, 1)
		# forecast test dataset
		predictions = list()
		for i in range(len(test_scaled)):
			# predict
			X, y = test_scaled[i, 0:-1], test_scaled[i, -1]
			yhat = forecast_lstm(lstm_model, 1, X)
			# invert scaling
			yhat = invert_scale(scaler, X, yhat)
			# invert differencing
			yhat = inverse_difference(raw_values, yhat, len(test_scaled)+1-i)
			# store forecast
			predictions.append(yhat)
		# report performance
		rmse = sqrt(mean_squared_error(raw_values[-12:], predictions))
		print('%d) Test RMSE: %.3f' % (r+1, rmse))
		error_scores.append(rmse)
	return error_scores

# execute the experiment
def run():
	# load dataset
	series = read_csv('shampoo-sales.csv', header=0, parse_dates=[0], index_col=0, squeeze=True, date_parser=parser)
	# experiment
	repeats = 10
	results = DataFrame()
	# run experiment
	features = 1
	results['results'] = experiment(repeats, series, features)
	# summarize results
	print(results.describe())
	# save results
	results.to_csv('experiment_features_1.csv', index=False)

 # entry point
run()

针对 5 种不同数量的特征运行 5 个不同的实验。

如果有足够的内存和 CPU 资源,可以并行运行它们。这些实验不需要 GPU 资源,运行应该在几分钟到几十分钟内完成。

运行实验后,您应该有 5 个包含结果的文件,如下所示:

  • experiment_features_1.csv
  • experiment_features_2.csv
  • experiment_features_3.csv
  • experiment_features_4.csv
  • experiment_features_5.csv

我们可以编写一些代码来加载和汇总这些结果。

具体而言,查看每次运行的描述性统计量并使用方框和胡须图比较每次运行的结果非常有用。

下面列出了总结结果的代码。

from pandas import DataFrame
from pandas import read_csv
from matplotlib import pyplot
# load results into a dataframe
filenames = ['experiment_features_1.csv', 'experiment_features_2.csv',
	'experiment_features_3.csv','experiment_features_4.csv','experiment_features_5.csv']
results = DataFrame()
for name in filenames:
	results[name[11:-4]] = read_csv(name, header=0)
# describe all results
print(results.describe())
# box and whisker plot
results.boxplot()
pyplot.show()

首先运行代码会为每组结果打印描述性统计量。

我们可以从单独的平均表现中看出,使用单个功能的默认值可以获得最佳表现。在查看中位数测试 RMSE(第 50 百分位数)时也会显示这一点。

       features_1  features_2  features_3  features_4  features_5
count   10.000000   10.000000   10.000000   10.000000   10.000000
mean   104.588249  126.597800  118.268251  107.694178  116.414887
std     10.205840   18.639757   14.359983    8.683271   18.806281
min     89.046814   93.857991  103.900339   93.702085   98.245871
25%     97.850827  120.296634  107.664087  102.992045  105.660897
50%    103.713285  133.582095  116.123790  106.116922  112.950460
75%    111.441655  134.362198  121.794533  111.498255  117.926664
max    122.341580  149.807155  152.412861  123.006088  164.598542

还创建了比较结果分布的盒子和胡须图。

该情节与描述性统计量相同。随着功能数量的增加,测试 RMSE 似乎跃升了 2 个功能并且趋势向上。

Box and Whisker Plot of Test RMSE vs The Number of Input Features

测试 RMSE 的盒子和晶须图与输入特征的数量

至少在使用数据集和 LSTM 配置的情况下,没有观察到随着特征的增加而减少的误差的期望。

这就提出了一个问题,即网络的容量是否是一个限制因素。我们将在下一节中看到这一点。

特征和神经元的实验

LSTM 网络中的神经元(也称为单元)的数量定义了其学习能力。

在先前的实验中,可能使用一个神经元限制了网络的学习能力,使得它不能有效地使用滞后观察作为特征。

我们可以重复上述实验,并随着特征的增加增加 LSTM 中神经元的数量,看看它是否会导致表现的提高。

这可以通过更改实验函数中的行来实现:

lstm_model = fit_lstm(train_scaled, 1, 500, 1, features)

lstm_model = fit_lstm(train_scaled, 1, 500, features, features)

此外,我们可以通过在文件名中添加“_neurons”后缀来保持写入文件的结果与第一个实验的结果分开,例如,更改:

results.to_csv('experiment_features_1.csv', index=False)

results.to_csv('experiment_features_1_neurons.csv', index=False)

用这些变化重复相同的 5 个实验。

运行这些实验后,您应该有 5 个结果文件。

  • experiment_features_1_neurons.csv
  • experiment_features_2_neurons.csv
  • experiment_features_3_neurons.csv
  • experiment_features_4_neurons.csv
  • experiment_features_5_neurons.csv

与前一个实验一样,我们可以加载结果,计算描述性统计量,并创建一个盒子和须状图。完整的代码清单如下。

from pandas import DataFrame
from pandas import read_csv
from matplotlib import pyplot
# load results into a dataframe
filenames = ['experiment_features_1_neurons.csv', 'experiment_features_2_neurons.csv',
	'experiment_features_3_neurons.csv','experiment_features_4_neurons.csv','experiment_features_5_neurons.csv']
results = DataFrame()
for name in filenames:
	results[name[11:-12]] = read_csv(name, header=0)
# describe all results
print(results.describe())
# box and whisker plot
results.boxplot()
pyplot.show()

运行代码首先打印 5 个实验中的每一个的描述性统计量。

结果用一个神经元 LSTM 对第一组实验说明了不同的故事。当神经元数量和特征数量设置为 1 时,平均测试 RMSE 显得最低,然后随着神经元和特征的增加,误差增加。

       features_1  features_2  features_3  features_4  features_5
count   10.000000   10.000000   10.000000   10.000000   10.000000
mean   106.219189  138.411111  127.687128  154.281694  175.951500
std     16.100488   29.700981   21.411766   30.526294   44.839217
min     91.073598   92.641030  103.503546   94.063639  117.017109
25%     97.263723  125.748973  108.972440  134.805621  142.146601
50%     99.036766  133.639168  128.627349  162.295657  182.406707
75%    110.625302  146.896608  134.012859  176.969980  197.913894
max    146.638148  206.760081  170.899267  188.911768  250.685187

创建框和胡须图以比较分布。

随着神经元数量和输入特征的增加,扩散和中位表现的趋势几乎表明测试 RMSE 呈线性增加。

线性趋势可能表明增加的网络容量没有足够的时间来拟合数据。也许还需要增加时代数量。

Box and Whisker Plot of Test RMSE vs The Number of Neurons and Input Features

测试 RMSE 的盒子和晶须图与神经元和输入特征的数量

特征和神经元的实验更多时代

在本节中,我们重复上述实验,以增加具有特征数量的神经元数量,但将训练时期的数量从 500 增加到 1000。

这可以通过更改实验函数中的行来实现:

lstm_model = fit_lstm(train_scaled, 1, 500, features, features)

lstm_model = fit_lstm(train_scaled, 1, 1000, features, features)

此外,我们可以通过在文件名中添加“1000”后缀来保持写入文件的结果与上一次实验的结果分开,例如,更改:

results.to_csv('experiment_features_1_neurons.csv', index=False)

results.to_csv('experiment_features_1_neurons1000.csv', index=False)

用这些变化重复相同的 5 个实验。

运行这些实验后,您应该有 5 个结果文件。

  • experiment_features_1_neurons1000.csv
  • experiment_features_2_neurons1000.csv
  • experiment_features_3_neurons1000.csv
  • experiment_features_4_neurons1000.csv
  • experiment_features_5_neurons1000.csv

与前一个实验一样,我们可以加载结果,计算描述性统计量,并创建一个盒子和须状图。完整的代码清单如下。

from pandas import DataFrame
from pandas import read_csv
from matplotlib import pyplot
# load results into a dataframe
filenames = ['experiment_features_1_neurons1000.csv', 'experiment_features_2_neurons1000.csv',
	'experiment_features_3_neurons1000.csv','experiment_features_4_neurons1000.csv','experiment_features_5_neurons1000.csv']
results = DataFrame()
for name in filenames:
	results[name[11:-16]] = read_csv(name, header=0)
# describe all results
print(results.describe())
# box and whisker plot
results.boxplot()
pyplot.show()

运行代码首先打印 5 个实验中的每一个的描述性统计量。

结果与前一个实验的故事非常相似,训练时期数量减少了一半。平均而言,具有 1 个输入特征和 1 个神经元的模型优于其他配置。

       features_1  features_2  features_3  features_4  features_5
count   10.000000   10.000000   10.000000   10.000000   10.000000
mean   109.262674  158.295172  120.340623  149.741882  201.992209
std     13.850525   32.288109   45.219564   53.121113   82.986691
min     95.927393  111.936394   83.983325  111.017837   78.040385
25%     98.754253  130.875314   95.198556  122.287208  148.840499
50%    103.990988  167.915523  110.256517  129.552084  188.498836
75%    116.055435  180.679252  122.158321  154.283676  234.519359
max    133.270446  204.260072  242.186747  288.907803  335.595974

还创建了一个盒子和胡须图来比较分布。在情节中,我们看到了与描述性统计中明确相同的趋势。

至少在这个问题和选择的 LSTM 配置中,我们没有看到增加输入功能数量的任何明显好处。

Box and Whisker Plot of Test RMSE vs The Number of Neurons and Input Features and 1000 Epochs

测试 RMSE 的盒子和晶须图与神经元和输入特征的数量以及 1000 个时期

扩展

本节列出了您可能考虑探索的一些进一步调查的领域。

  • 诊断运行图。对于给定的实验,在多次运行的情况下查看训练和测试 RMSE 的图可能是有帮助的。这可能有助于梳理过拟合或过拟合是否正在发生,反过来又是解决它的方法。
  • 增加重复次数。使用 10 次重复导致相对少量的测试 RMSE 结果。将重复增加至 30 或 100(或甚至更高)可能导致更稳定的结果。

你有没有探索过这些扩展? 在下面的评论中分享您的发现;我很想听听你发现了什么。

摘要

在本教程中,您了解了如何使用滞后观察作为 LSTM 网络中的输入要素进行调查。

具体来说,你学到了:

  • 如何开发一个强大的测试工具来尝试使用 LSTM 进行输入表示。
  • 如何使用滞后观测作为 LSTM 时间序列预测的输入特征。
  • 如何通过增加输入功能来增加网络的学习能力。

您发现“_ 使用滞后观察作为输入功能可以提高模型技能 _”并未降低所选问题和 LSTM 配置的测试 RMSE。

你有任何问题吗? 在下面的评论中提出您的问题,我会尽力回答。