diff --git a/docs/dl-ts/SUMMARY.md b/docs/dl-ts/SUMMARY.md new file mode 100644 index 0000000000000000000000000000000000000000..c20e09877ff1fa17171e5465a5cd91f6a2f080be --- /dev/null +++ b/docs/dl-ts/SUMMARY.md @@ -0,0 +1,57 @@ ++ [如何开发人类活动识别的一维卷积神经网络模型](cnn-models-for-human-activity-recognition-time-series-classification.md) ++ [人类活动识别的深度学习模型](deep-learning-models-for-human-activity-recognition.md) ++ [如何评估人类活动识别的机器学习算法](evaluate-machine-learning-algorithms-for-human-activity-recognition.md) ++ [时间序列预测的多层感知器网络探索性配置](exploratory-configuration-multilayer-perceptron-network-time-series-forecasting.md) ++ [比较经典和机器学习方法进行时间序列预测的结果](findings-comparing-classical-and-machine-learning-methods-for-time-series-forecasting.md) ++ [如何通过深度学习快速获得时间序列预测的结果](get-good-results-fast-deep-learning-time-series-forecasting.md) ++ [如何利用 Python 处理序列预测问题中的缺失时间步长](handle-missing-timesteps-sequence-prediction-problems-python.md) ++ [如何建立预测大气污染日的概率预测模型](how-to-develop-a-probabilistic-forecasting-model-to-predict-air-pollution-days.md) ++ [如何开发一种熟练的机器学习时间序列预测模型](how-to-develop-a-skilful-time-series-forecasting-model.md) ++ [如何构建家庭用电自回归预测模型](how-to-develop-an-autoregression-forecast-model-for-household-electricity-consumption.md) ++ [如何开发多步空气污染时间序列预测的自回归预测模型](how-to-develop-autoregressive-forecasting-models-for-multi-step-air-pollution-time-series-forecasting.md) ++ [如何制定多站点多元空气污染时间序列预测的基线预测](how-to-develop-baseline-forecasts-for-multi-site-multivariate-air-pollution-time-series-forecasting.md) ++ [如何开发时间序列预测的卷积神经网络模型](how-to-develop-convolutional-neural-network-models-for-time-series-forecasting.md) ++ [如何开发卷积神经网络用于多步时间序列预测](how-to-develop-convolutional-neural-networks-for-multi-step-time-series-forecasting.md) ++ [如何开发单变量时间序列预测的深度学习模型](how-to-develop-deep-learning-models-for-univariate-time-series-forecasting.md) ++ [如何开发 LSTM 模型用于家庭用电的多步时间序列预测](how-to-develop-lstm-models-for-multi-step-time-series-forecasting-of-household-power-consumption.md) ++ [如何开发 LSTM 模型进行时间序列预测](how-to-develop-lstm-models-for-time-series-forecasting.md) ++ [如何开发多元多步空气污染时间序列预测的机器学习模型](how-to-develop-machine-learning-models-for-multivariate-multi-step-air-pollution-time-series-forecasting.md) ++ [如何开发多层感知器模型进行时间序列预测](how-to-develop-multilayer-perceptron-models-for-time-series-forecasting.md) ++ [如何开发人类活动识别时间序列分类的 RNN 模型](how-to-develop-rnn-models-for-human-activity-recognition-time-series-classification.md) ++ [如何开始深度学习的时间序列预测(7 天迷你课程)](how-to-get-started-with-deep-learning-for-time-series-forecasting-7-day-mini-course.md) ++ [如何网格搜索深度学习模型进行时间序列预测](how-to-grid-search-deep-learning-models-for-time-series-forecasting.md) ++ [如何对单变量时间序列预测的网格搜索朴素方法](how-to-grid-search-naive-methods-for-univariate-time-series-forecasting.md) ++ [如何在 Python 中搜索 SARIMA 模型超参数用于时间序列预测](how-to-grid-search-sarima-model-hyperparameters-for-time-series-forecasting-in-python.md) ++ [如何在 Python 中进行时间序列预测的网格搜索三次指数平滑](how-to-grid-search-triple-exponential-smoothing-for-time-series-forecasting-in-python.md) ++ [一个标准的人类活动识别问题的温和介绍](how-to-load-and-explore-a-standard-human-activity-recognition-problem.md) ++ [如何加载和探索家庭用电数据](how-to-load-and-explore-household-electricity-usage-data.md) ++ [如何加载,可视化和探索复杂的多变量多步时间序列预测数据集](how-to-load-visualize-and-explore-a-complex-multivariate-multistep-time-series-forecasting-dataset.md) ++ [如何从智能手机数据模拟人类活动](how-to-model-human-activity-from-smartphone-data.md) ++ [如何根据环境因素预测房间占用率](how-to-predict-room-occupancy-based-on-environmental-factors.md) ++ [如何使用脑波预测人眼是开放还是闭合](how-to-predict-whether-eyes-are-open-or-closed-using-brain-waves.md) ++ [如何在 Python 中扩展长短期内存网络的数据](how-to-scale-data-for-long-short-term-memory-networks-in-python.md) ++ [如何使用 TimeseriesGenerator 进行 Keras 中的时间序列预测](how-to-use-the-timeseriesgenerator-for-time-series-forecasting-in-keras.md) ++ [基于机器学习算法的室内运动时间序列分类](indoor-movement-time-series-classification-with-machine-learning-algorithms.md) ++ [用于时间序列预测的状态 LSTM 在线学习的不稳定性](instability-online-learning-stateful-lstm-time-series-forecasting.md) ++ [用于罕见事件时间序列预测的 LSTM 模型体系结构](lstm-model-architecture-for-rare-event-time-series-forecasting.md) ++ [用于时间序列预测的 4 种通用机器学习数据变换](machine-learning-data-transforms-for-time-series-forecasting.md) ++ [Python 中长短期记忆网络的多步时间序列预测](multi-step-time-series-forecasting-long-short-term-memory-networks-python.md) ++ [家庭用电机器学习的多步时间序列预测](multi-step-time-series-forecasting-with-machine-learning-models-for-household-electricity-consumption.md) ++ [Keras 中 LSTM 的多变量时间序列预测](multivariate-time-series-forecasting-lstms-keras.md) ++ [如何开发和评估朴素的家庭用电量预测方法](naive-methods-for-forecasting-household-electricity-consumption.md) ++ [如何为长短期记忆网络准备单变量时间序列数据](prepare-univariate-time-series-data-long-short-term-memory-networks.md) ++ [循环神经网络在时间序列预测中的应用](promise-recurrent-neural-networks-time-series-forecasting.md) ++ [如何在 Python 中使用差异变换删除趋势和季节性](remove-trends-seasonality-difference-transform-python.md) ++ [如何在 LSTM 中种子状态用于 Python 中的时间序列预测](seed-state-lstms-time-series-forecasting-python.md) ++ [使用 Python 进行时间序列预测的有状态和无状态 LSTM](stateful-stateless-lstm-time-series-forecasting-python.md) ++ [长短时记忆网络在时间序列预测中的适用性](suitability-long-short-term-memory-networks-time-series-forecasting.md) ++ [时间序列预测问题的分类](taxonomy-of-time-series-forecasting-problems.md) ++ [Python 中长短期记忆网络的时间序列预测](time-series-forecasting-long-short-term-memory-network-python.md) ++ [基于 Keras 的 Python 中 LSTM 循环神经网络的时间序列预测](time-series-prediction-lstm-recurrent-neural-networks-python-keras.md) ++ [Keras 中深度学习的时间序列预测](time-series-prediction-with-deep-learning-in-python-with-keras.md) ++ [如何用 Keras 调整 LSTM 超参数进行时间序列预测](tune-lstm-hyperparameters-keras-time-series-forecasting.md) ++ [如何在时间序列预测训练期间更新 LSTM 网络](update-lstm-networks-training-time-series-forecasting.md) ++ [如何使用 LSTM 网络的 Dropout 进行时间序列预测](use-dropout-lstm-networks-time-series-forecasting.md) ++ [如何使用 LSTM 网络中的特征进行时间序列预测](use-features-lstm-networks-time-series-forecasting.md) ++ [如何在 LSTM 网络中使用时间序列进行时间序列预测](use-timesteps-lstm-networks-time-series-forecasting.md) ++ [如何利用 LSTM 网络进行权重正则化进行时间序列预测](use-weight-regularization-lstm-networks-time-series-forecasting.md) \ No newline at end of file diff --git a/docs/dl-ts/cnn-models-for-human-activity-recognition-time-series-classification.md b/docs/dl-ts/cnn-models-for-human-activity-recognition-time-series-classification.md new file mode 100644 index 0000000000000000000000000000000000000000..c463ba00ff3d4f0e861f102dc4cf0250219ec242 --- /dev/null +++ b/docs/dl-ts/cnn-models-for-human-activity-recognition-time-series-classification.md @@ -0,0 +1,1464 @@ +# 如何开发人类活动识别的一维卷积神经网络模型 + +> 原文: [https://machinelearningmastery.com/cnn-models-for-human-activity-recognition-time-series-classification/](https://machinelearningmastery.com/cnn-models-for-human-activity-recognition-time-series-classification/) + +人类活动识别是将由专用线束或智能电话记录的加速度计数据序列分类为已知的明确定义的运动的问题。 + +该问题的经典方法涉及基于固定大小的窗口和训练机器学习模型(例如决策树的集合)的时间序列数据中的手工制作特征。困难在于此功能工程需要该领域的深厚专业知识。 + +最近,已经证明,诸如循环神经网络和一维卷积神经网络(CNN)之类的深度学习方法可以在很少或没有数据特征工程的情况下提供具有挑战性的活动识别任务的最新结果,而不是使用特征学习原始数据。 + +在本教程中,您将了解如何开发一维卷积神经网络,用于人类活动识别问题的时间序列分类。 + +完成本教程后,您将了解: + +* 如何为标准人类活动识别数据集加载和准备数据,并开发单个 1D CNN 模型,以在原始数据上实现出色的表现。 +* 如何进一步调整模型的表现,包括数据转换,过滤器映射和内核大小。 +* 如何开发一种复杂的多头一维卷积神经网络模型,提供类似集合的结果。 + +让我们开始吧。 + +![How to Develop 1D Convolutional Neural Network Models for Human Activity Recognition](img/cd9dece685075a929591317dbe05b37e.jpg) + +如何开发用于人类活动识别的一维卷积神经网络模型 +照片由 [Wolfgang Staudt](https://www.flickr.com/photos/wolfgangstaudt/2204054918/) ,保留一些权利。 + +## 教程概述 + +本教程分为四个部分;他们是: + +1. 使用智能手机数据集进行活动识别 +2. 开发一维卷积神经网络 +3. 调谐一维卷积神经网络 +4. 多头一维卷积神经网络 + +## 使用智能手机数据集进行活动识别 + +[人类活动识别](https://en.wikipedia.org/wiki/Activity_recognition),或简称为 HAR,是基于使用传感器的移动痕迹来预测人正在做什么的问题。 + +标准的人类活动识别数据集是 2012 年推出的“使用智能手机数据集的活动识别”。 + +它由 Davide Anguita 等人准备并提供。来自意大利热那亚大学的 2013 年论文“[使用智能手机进行人类活动识别的公共领域数据集](https://upcommons.upc.edu/handle/2117/20897)”中对该数据集进行了全面描述。该数据集在他们的 2012 年论文中用机器学习算法建模,标题为“[使用多类硬件友好支持向量机](https://link.springer.com/chapter/10.1007/978-3-642-35395-6_30)在智能手机上进行人类活动识别。“ + +数据集可用,可以从 UCI 机器学习库免费下载: + +* [使用智能手机数据集进行人类活动识别,UCI 机器学习库](https://archive.ics.uci.edu/ml/datasets/human+activity+recognition+using+smartphones) + +该数据来自 30 名年龄在 19 至 48 岁之间的受试者,其执行六项标准活动中的一项,同时佩戴记录运动数据的腰部智能手机。记录执行活动的每个受试者的视频,并从这些视频手动标记移动数据。 + +以下是在记录其移动数据的同时执行活动的主体的示例视频。 + +<iframe allow="autoplay; encrypted-media" allowfullscreen="" frameborder="0" height="375" src="https://www.youtube.com/embed/XOEN9W05_4A?feature=oembed" width="500"></iframe> + +进行的六项活动如下: + +1. 步行 +2. 走上楼 +3. 走楼下 +4. 坐在 +5. 常设 +6. 铺设 + +记录的运动数据是来自智能手机的 x,y 和 z 加速度计数据(线性加速度)和陀螺仪数据(角速度),特别是三星 Galaxy S II。以 50Hz(即每秒 50 个数据点)记录观察结果。每个受试者进行两次活动,一次是左侧设备,另一次是右侧设备。 + +原始数据不可用。相反,可以使用预处理版本的数据集。预处理步骤包括: + +* 使用噪声滤波器预处理加速度计和陀螺仪。 +* 将数据拆分为 2.56 秒(128 个数据点)的固定窗口,重叠率为 50%。 +* 将加速度计数据分割为重力(总)和身体运动分量。 + +特征工程应用于窗口数据,并且提供具有这些工程特征的数据的副本。 + +从每个窗口提取在人类活动识别领域中常用的许多时间和频率特征。结果是 561 元素的特征向量。 + +根据受试者的数据,将数据集分成训练(70%)和测试(30%)组。列车 21 个,测试 9 个。 + +使用旨在用于智能手机的支持向量机(例如定点算术)的实验结果导致测试数据集的预测准确度为 89%,实现与未修改的 SVM 实现类似的结果。 + +该数据集是免费提供的,可以从 UCI 机器学习库下载。 + +数据以单个 zip 文件的形式提供,大小约为 58 兆字节。此下载的直接链接如下: + +* [UCI HAR Dataset.zip](https://archive.ics.uci.edu/ml/machine-learning-databases/00240/UCI%20HAR%20Dataset.zip) + +下载数据集并将所有文件解压缩到当前工作目录中名为“HARDataset”的新目录中。 + +## 开发一维卷积神经网络 + +在本节中,我们将为人类活动识别数据集开发一维卷积神经网络模型(1D CNN)。 + +[卷积神经网络](https://machinelearningmastery.com/crash-course-convolutional-neural-networks/)模型是为图像分类问题而开发的,其中模型在称为特征学习的过程中学习二维输入的内部表示。 + +可以在一维数据序列上利用相同的过程,例如在用于人类活动识别的加速和陀螺仪数据的情况下。该模型学习从观察序列中提取特征以及如何将内部特征映射到不同的活动类型。 + +使用 CNN 进行序列分类的好处是,他们可以直接从原始时间序列数据中学习,而不需要领域专业知识来手动设计输入功能。该模型可以学习时间序列数据的内部表示,并且理想地实现与适合具有工程特征的数据集版本的模型相当的表现。 + +本节分为 4 部分;他们是: + +1. 加载数据 +2. 拟合和评估模型 +3. 总结结果 +4. 完整的例子 + +### 加载数据 + +第一步是将原始数据集加载到内存中。 + +原始数据中有三种主要信号类型:总加速度,车身加速度和车身陀螺仪。每个都有三个数据轴。这意味着每个时间步长总共有九个变量。 + +此外,每个数据系列已被划分为 2.65 秒数据的重叠窗口,或 128 个时间步长。这些数据窗口对应于上一节中工程特征(行)的窗口。 + +这意味着一行数据具有(128 * 9)或 1,152 个元素。这比前一节中 561 个元素向量的大小小一倍,并且可能存在一些冗余数据。 + +信号存储在 train 和 test 子目录下的/ _Inertial Signals_ /目录中。每个信号的每个轴都存储在一个单独的文件中,这意味着每个列车和测试数据集都有九个要加载的输入文件和一个要加载的输出文件。在给定一致的目录结构和文件命名约定的情况下,我们可以批量加载这些文件。 + +输入数据采用 CSV 格式,其中列由空格分隔。这些文件中的每一个都可以作为 NumPy 数组加载。下面的 _load_file()_ 函数在给定文件的文件路径的情况下加载数据集,并将加载的数据作为 NumPy 数组返回。 + +``` +# load a single file as a numpy array +def load_file(filepath): + dataframe = read_csv(filepath, header=None, delim_whitespace=True) + return dataframe.values +``` + +然后,我们可以将给定组(训练或测试)的所有数据加载到单个三维 NumPy 阵列中,其中阵列的尺寸为[_ 样本,时间步长,特征 _]。 + +为了更清楚,有 128 个时间步和 9 个特征,其中样本数是任何给定原始信号数据文件中的行数。 + +下面的 _load_group()_ 函数实现了这种行为。 [dstack()NumPy 函数](https://docs.scipy.org/doc/numpy-1.14.0/reference/generated/numpy.dstack.html)允许我们将每个加载的 3D 数组堆叠成单个 3D 数组,其中变量在第三维(特征)上分开。 + +``` +# load a list of files into a 3D array of [samples, timesteps, features] +def load_group(filenames, prefix=''): + loaded = list() + for name in filenames: + data = load_file(prefix + name) + loaded.append(data) + # stack group so that features are the 3rd dimension + loaded = dstack(loaded) + return loaded +``` + +我们可以使用此功能加载给定组的所有输入信号数据,例如火车或测试。 + +下面的 _load_dataset_group()_ 函数使用列车和测试目录之间的一致命名约定加载单个组的所有输入信号数据和输出数据。 + +``` +# load a dataset group, such as train or test +def load_dataset_group(group, prefix=''): + filepath = prefix + group + '/Inertial Signals/' + # load all 9 files as a single array + filenames = list() + # total acceleration + filenames += ['total_acc_x_'+group+'.txt', 'total_acc_y_'+group+'.txt', 'total_acc_z_'+group+'.txt'] + # body acceleration + filenames += ['body_acc_x_'+group+'.txt', 'body_acc_y_'+group+'.txt', 'body_acc_z_'+group+'.txt'] + # body gyroscope + filenames += ['body_gyro_x_'+group+'.txt', 'body_gyro_y_'+group+'.txt', 'body_gyro_z_'+group+'.txt'] + # load input data + X = load_group(filenames, filepath) + # load class output + y = load_file(prefix + group + '/y_'+group+'.txt') + return X, y +``` + +最后,我们可以加载每个列车和测试数据集。 + +输出数据定义为类号的整数。我们必须对这些类整数进行热编码,以使数据适合于拟合神经网络多类分类模型。我们可以通过调用 [to_categorical()Keras 函数](https://keras.io/utils/#to_categorical)来实现。 + +下面的 _load_dataset()_ 函数实现了这种行为,并返回列车并测试 _X_ 和 _y_ 元素,以便拟合和评估定义的模型。 + +``` +# load the dataset, returns train and test X and y elements +def load_dataset(prefix=''): + # load all train + trainX, trainy = load_dataset_group('train', prefix + 'HARDataset/') + print(trainX.shape, trainy.shape) + # load all test + testX, testy = load_dataset_group('test', prefix + 'HARDataset/') + print(testX.shape, testy.shape) + # zero-offset class values + trainy = trainy - 1 + testy = testy - 1 + # one hot encode y + trainy = to_categorical(trainy) + testy = to_categorical(testy) + print(trainX.shape, trainy.shape, testX.shape, testy.shape) + return trainX, trainy, testX, testy +``` + +### 拟合和评估模型 + +现在我们已将数据加载到内存中以便进行建模,我们可以定义,拟合和评估 1D CNN 模型。 + +我们可以定义一个名为 _evaluate_model()_ 的函数,它接受训练和测试数据集,拟合训练数据集上的模型,在测试数据集上对其进行评估,并返回模型表现的估计值。 + +首先,我们必须使用 Keras 深度学习库来定义 CNN 模型。该模型需要使用[_ 样本,时间步长,特征 _]进行三维输入。 + +这正是我们加载数据的方式,其中一个样本是时间序列数据的一个窗口,每个窗口有 128 个时间步长,时间步长有九个变量或特征。 + +模型的输出将是一个六元素向量,包含属于六种活动类型中每种活动类型的给定窗口的概率。 + +在拟合模型时需要这些输入和输出维度,我们可以从提供的训练数据集中提取它们。 + +``` +n_timesteps, n_features, n_outputs = trainX.shape[1], trainX.shape[2], trainy.shape[1] +``` + +为简单起见,该模型被定义为顺序 Keras 模型。 + +我们将模型定义为具有两个 1D CNN 层,然后是用于正则化的丢失层,然后是池化层。通常以两个为一组定义 CNN 层,以便为模型提供从输入数据学习特征的良好机会。 CNN 非常快速地学习,因此 dropout 层旨在帮助减缓学习过程,并希望能够产生更好的最终模型。池化层将学习的特征减少到其大小的 1/4,将它们合并到最基本的元素。 + +在 CNN 和汇集之后,将学习的特征展平为一个长向量,并在用于进行预测的输出层之前通过完全连接的层。完全连接的层理想地在学习的特征和输出之间提供缓冲,目的是在进行预测之前解释学习的特征。 + +对于此模型,我们将使用 64 个并行要素图的标准配置和 3 的内核大小。要素图是处理或解释输入的次数,而内核大小是被视为输入时间步数的数量。输入序列被读取或处理到特征映射上。 + +随机梯度下降的有效 [Adam](https://machinelearningmastery.com/adam-optimization-algorithm-for-deep-learning/) 版本将用于优化网络,并且鉴于我们正在学习多类别分类问题,将使用分类交叉熵损失函数。 + +下面列出了该模型的定义。 + +``` +model = Sequential() +model.add(Conv1D(filters=64, kernel_size=3, activation='relu', input_shape=(n_timesteps,n_features))) +model.add(Conv1D(filters=64, kernel_size=3, activation='relu')) +model.add(Dropout(0.5)) +model.add(MaxPooling1D(pool_size=2)) +model.add(Flatten()) +model.add(Dense(100, activation='relu')) +model.add(Dense(n_outputs, activation='softmax')) +model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) +``` + +该模型适用于固定数量的时期,在这种情况下为 10,并且将使用 32 个样本的批量大小,其中在更新模型的权重之前将 32 个数据窗口暴露给模型。 + +模型拟合后,将在测试数据集上进行评估,并返回测试数据集上拟合模型的精度。 + +下面列出了完整的 _evaluate_model()_ 函数。 + +``` +# fit and evaluate a model +def evaluate_model(trainX, trainy, testX, testy): + verbose, epochs, batch_size = 0, 10, 32 + n_timesteps, n_features, n_outputs = trainX.shape[1], trainX.shape[2], trainy.shape[1] + model = Sequential() + model.add(Conv1D(filters=64, kernel_size=3, activation='relu', input_shape=(n_timesteps,n_features))) + model.add(Conv1D(filters=64, kernel_size=3, activation='relu')) + model.add(Dropout(0.5)) + model.add(MaxPooling1D(pool_size=2)) + model.add(Flatten()) + model.add(Dense(100, activation='relu')) + model.add(Dense(n_outputs, activation='softmax')) + model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) + # fit network + model.fit(trainX, trainy, epochs=epochs, batch_size=batch_size, verbose=verbose) + # evaluate model + _, accuracy = model.evaluate(testX, testy, batch_size=batch_size, verbose=0) + return accuracy +``` + +网络结构或选择的超参数没有什么特别之处;它们只是这个问题的起点。 + +### 总结结果 + +我们无法从单一评估中判断模型的技能。 + +其原因是神经网络是随机的,这意味着当在相同数据上训练相同的模型配置时将产生不同的特定模型。 + +这是网络的一个特征,它为模型提供了自适应能力,但需要对模型进行稍微复杂的评估。 + +我们将多次重复对模型的评估,然后在每次运行中总结模型的表现。例如,我们可以调用 _evaluate_model()_ 共 10 次。这将导致必须总结的模型评估分数。 + +``` +# repeat experiment +scores = list() +for r in range(repeats): + score = evaluate_model(trainX, trainy, testX, testy) + score = score * 100.0 + print('>#%d: %.3f' % (r+1, score)) + scores.append(score) +``` + +我们可以通过计算和报告绩效的均值和标准差来总结得分样本。均值给出了数据集上模型的平均精度,而标准差给出了精度与平均值的平均方差。 + +下面的函数 _summarize_results()_ 总结了运行的结果。 + +``` +# summarize scores +def summarize_results(scores): + print(scores) + m, s = mean(scores), std(scores) + print('Accuracy: %.3f%% (+/-%.3f)' % (m, s)) +``` + +我们可以将重复评估,结果收集和结果汇总捆绑到实验的主要功能中,称为 _run_experiment()_,如下所示。 + +默认情况下,在报告模型表现之前,会对模型进行 10 次评估。 + +``` +# run an experiment +def run_experiment(repeats=10): + # load data + trainX, trainy, testX, testy = load_dataset() + # repeat experiment + scores = list() + for r in range(repeats): + score = evaluate_model(trainX, trainy, testX, testy) + score = score * 100.0 + print('>#%d: %.3f' % (r+1, score)) + scores.append(score) + # summarize results + summarize_results(scores) +``` + +### 完整的例子 + +现在我们已经拥有了所有的部分,我们可以将它们组合成一个有效的例子。 + +完整的代码清单如下。 + +``` +# cnn model +from numpy import mean +from numpy import std +from numpy import dstack +from pandas import read_csv +from matplotlib import pyplot +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import Flatten +from keras.layers import Dropout +from keras.layers.convolutional import Conv1D +from keras.layers.convolutional import MaxPooling1D +from keras.utils import to_categorical + +# load a single file as a numpy array +def load_file(filepath): + dataframe = read_csv(filepath, header=None, delim_whitespace=True) + return dataframe.values + +# load a list of files and return as a 3d numpy array +def load_group(filenames, prefix=''): + loaded = list() + for name in filenames: + data = load_file(prefix + name) + loaded.append(data) + # stack group so that features are the 3rd dimension + loaded = dstack(loaded) + return loaded + +# load a dataset group, such as train or test +def load_dataset_group(group, prefix=''): + filepath = prefix + group + '/Inertial Signals/' + # load all 9 files as a single array + filenames = list() + # total acceleration + filenames += ['total_acc_x_'+group+'.txt', 'total_acc_y_'+group+'.txt', 'total_acc_z_'+group+'.txt'] + # body acceleration + filenames += ['body_acc_x_'+group+'.txt', 'body_acc_y_'+group+'.txt', 'body_acc_z_'+group+'.txt'] + # body gyroscope + filenames += ['body_gyro_x_'+group+'.txt', 'body_gyro_y_'+group+'.txt', 'body_gyro_z_'+group+'.txt'] + # load input data + X = load_group(filenames, filepath) + # load class output + y = load_file(prefix + group + '/y_'+group+'.txt') + return X, y + +# load the dataset, returns train and test X and y elements +def load_dataset(prefix=''): + # load all train + trainX, trainy = load_dataset_group('train', prefix + 'HARDataset/') + print(trainX.shape, trainy.shape) + # load all test + testX, testy = load_dataset_group('test', prefix + 'HARDataset/') + print(testX.shape, testy.shape) + # zero-offset class values + trainy = trainy - 1 + testy = testy - 1 + # one hot encode y + trainy = to_categorical(trainy) + testy = to_categorical(testy) + print(trainX.shape, trainy.shape, testX.shape, testy.shape) + return trainX, trainy, testX, testy + +# fit and evaluate a model +def evaluate_model(trainX, trainy, testX, testy): + verbose, epochs, batch_size = 0, 10, 32 + n_timesteps, n_features, n_outputs = trainX.shape[1], trainX.shape[2], trainy.shape[1] + model = Sequential() + model.add(Conv1D(filters=64, kernel_size=3, activation='relu', input_shape=(n_timesteps,n_features))) + model.add(Conv1D(filters=64, kernel_size=3, activation='relu')) + model.add(Dropout(0.5)) + model.add(MaxPooling1D(pool_size=2)) + model.add(Flatten()) + model.add(Dense(100, activation='relu')) + model.add(Dense(n_outputs, activation='softmax')) + model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) + # fit network + model.fit(trainX, trainy, epochs=epochs, batch_size=batch_size, verbose=verbose) + # evaluate model + _, accuracy = model.evaluate(testX, testy, batch_size=batch_size, verbose=0) + return accuracy + +# summarize scores +def summarize_results(scores): + print(scores) + m, s = mean(scores), std(scores) + print('Accuracy: %.3f%% (+/-%.3f)' % (m, s)) + +# run an experiment +def run_experiment(repeats=10): + # load data + trainX, trainy, testX, testy = load_dataset() + # repeat experiment + scores = list() + for r in range(repeats): + score = evaluate_model(trainX, trainy, testX, testy) + score = score * 100.0 + print('>#%d: %.3f' % (r+1, score)) + scores.append(score) + # summarize results + summarize_results(scores) + +# run the experiment +run_experiment() +``` + +运行该示例首先打印已加载数据集的形状,然后打印列车和测试集的形状以及输入和输出元素。这确认了样本数,时间步长和变量,以及类的数量。 + +接下来,创建和评估模型,并为每个模型打印调试消息。 + +最后,打印分数样本,然后是平均值和标准差。我们可以看到该模型表现良好,实现了在原始数据集上训练的约 90.9%的分类准确度,标准偏差约为 1.3。 + +这是一个很好的结果,考虑到原始论文发表了 89%的结果,在具有重域特定特征工程的数据集上进行了训练,而不是原始数据集。 + +注意:鉴于算法的随机性,您的具体结果可能会有所不同。 + +``` +(7352, 128, 9) (7352, 1) +(2947, 128, 9) (2947, 1) +(7352, 128, 9) (7352, 6) (2947, 128, 9) (2947, 6) + +>#1: 91.347 +>#2: 91.551 +>#3: 90.804 +>#4: 90.058 +>#5: 89.752 +>#6: 90.940 +>#7: 91.347 +>#8: 87.547 +>#9: 92.637 +>#10: 91.890 + +[91.34713267729894, 91.55072955548015, 90.80420766881574, 90.05768578215134, 89.75229046487954, 90.93993892093654, 91.34713267729894, 87.54665761791652, 92.63657957244655, 91.89005768578215] + +Accuracy: 90.787% (+/-1.341) +``` + +现在我们已经看到了如何加载数据并适合 1D CNN 模型,我们可以研究是否可以通过一些超参数调整来进一步提升模型的技能。 + +## 调谐一维卷积神经网络 + +在本节中,我们将调整模型,以进一步提高问题的表现。 + +我们将看三个主要方面: + +1. 数据准备 +2. 过滤器数量 +3. 内核的大小 + +### 数据准备 + +在上一节中,我们没有执行任何数据准备。我们按原样使用了数据。 + +每个主要数据集(身体加速度,身体陀螺仪和总加速度)已经缩放到-1,1 的范围。不清楚数据是按受试者缩放还是跨所有受试者。 + +可能导致改进的一种可能的变换是在拟合模型之前标准化观察。 + +标准化是指改变每个变量的分布,使其平均值为零,标准差为 1.只有每个变量的分布是高斯分布才真正有意义。 + +我们可以通过绘制训练数据集中每个变量的直方图来快速检查每个变量的分布。 + +这方面的一个小难点是数据被分成 128 个时间步长的窗口,重叠 50%。因此,为了更好地了解数据分布,我们必须首先删除重复的观察(重叠),然后删除数据的窗口。 + +我们可以使用 NumPy 来做到这一点,首先切割数组并仅保留每个窗口的后半部分,然后将窗口展平为每个变量的长向量。这很快且很脏,并且意味着我们在第一个窗口的前半部分丢失了数据。 + +``` +# remove overlap +cut = int(trainX.shape[1] / 2) +longX = trainX[:, -cut:, :] +# flatten windows +longX = longX.reshape((longX.shape[0] * longX.shape[1], longX.shape[2])) +``` + +下面列出了加载数据,展平数据以及为九个变量中的每一个绘制直方图的完整示例。 + +``` +# plot distributions +from numpy import dstack +from pandas import read_csv +from keras.utils import to_categorical +from matplotlib import pyplot + +# load a single file as a numpy array +def load_file(filepath): + dataframe = read_csv(filepath, header=None, delim_whitespace=True) + return dataframe.values + +# load a list of files and return as a 3d numpy array +def load_group(filenames, prefix=''): + loaded = list() + for name in filenames: + data = load_file(prefix + name) + loaded.append(data) + # stack group so that features are the 3rd dimension + loaded = dstack(loaded) + return loaded + +# load a dataset group, such as train or test +def load_dataset_group(group, prefix=''): + filepath = prefix + group + '/Inertial Signals/' + # load all 9 files as a single array + filenames = list() + # total acceleration + filenames += ['total_acc_x_'+group+'.txt', 'total_acc_y_'+group+'.txt', 'total_acc_z_'+group+'.txt'] + # body acceleration + filenames += ['body_acc_x_'+group+'.txt', 'body_acc_y_'+group+'.txt', 'body_acc_z_'+group+'.txt'] + # body gyroscope + filenames += ['body_gyro_x_'+group+'.txt', 'body_gyro_y_'+group+'.txt', 'body_gyro_z_'+group+'.txt'] + # load input data + X = load_group(filenames, filepath) + # load class output + y = load_file(prefix + group + '/y_'+group+'.txt') + return X, y + +# load the dataset, returns train and test X and y elements +def load_dataset(prefix=''): + # load all train + trainX, trainy = load_dataset_group('train', prefix + 'HARDataset/') + print(trainX.shape, trainy.shape) + # load all test + testX, testy = load_dataset_group('test', prefix + 'HARDataset/') + print(testX.shape, testy.shape) + # zero-offset class values + trainy = trainy - 1 + testy = testy - 1 + # one hot encode y + trainy = to_categorical(trainy) + testy = to_categorical(testy) + print(trainX.shape, trainy.shape, testX.shape, testy.shape) + return trainX, trainy, testX, testy + +# plot a histogram of each variable in the dataset +def plot_variable_distributions(trainX): + # remove overlap + cut = int(trainX.shape[1] / 2) + longX = trainX[:, -cut:, :] + # flatten windows + longX = longX.reshape((longX.shape[0] * longX.shape[1], longX.shape[2])) + print(longX.shape) + pyplot.figure() + xaxis = None + for i in range(longX.shape[1]): + ax = pyplot.subplot(longX.shape[1], 1, i+1, sharex=xaxis) + ax.set_xlim(-1, 1) + if i == 0: + xaxis = ax + pyplot.hist(longX[:, i], bins=100) + pyplot.show() + +# load data +trainX, trainy, testX, testy = load_dataset() +# plot histograms +plot_variable_distributions(trainX) +``` + +运行该示例将创建一个包含九个直方图的图形,一个用于训练数据集中的每个变量。 + +图的顺序与加载数据的顺序相匹配,具体如下: + +1. 总加速度 x +2. 总加速度 y +3. 总加速度 z +4. 身体加速 x +5. 身体加速度 y +6. 身体加速度 z +7. 身体陀螺仪 x +8. 身体陀螺仪 y +9. Body Gyroscope z + +我们可以看到每个变量都具有类似高斯分布,除了第一个变量(总加速度 x)。 + +总加速度数据的分布比身体数据更平坦,这更加尖锐。 + +我们可以探索使用数据的幂变换来使分布更加高斯,尽管这是一个练习。 + +![Histograms of each variable in the training data set](img/72f05b1109bcadd15fd827120e5405fe.jpg) + +训练数据集中每个变量的直方图 + +数据具有足够的高斯类似性,以探索标准化变换是否有助于模型从原始观测中提取显着信号。 + +名为 _scale_data()_ 的以下函数可用于在拟合和评估模型之前标准化数据。 StandardScaler scikit-learn 类将用于执行转换。它首先适合训练数据(例如,找到每个变量的平均值和标准差),然后应用于训练和测试集。 + +标准化是可选的,因此我们可以应用该过程并将结果与​​相同的代码路径进行比较,而无需在受控实验中进行标准化。 + +``` +# standardize data +def scale_data(trainX, testX, standardize): + # remove overlap + cut = int(trainX.shape[1] / 2) + longX = trainX[:, -cut:, :] + # flatten windows + longX = longX.reshape((longX.shape[0] * longX.shape[1], longX.shape[2])) + # flatten train and test + flatTrainX = trainX.reshape((trainX.shape[0] * trainX.shape[1], trainX.shape[2])) + flatTestX = testX.reshape((testX.shape[0] * testX.shape[1], testX.shape[2])) + # standardize + if standardize: + s = StandardScaler() + # fit on training data + s.fit(longX) + # apply to training and test data + longX = s.transform(longX) + flatTrainX = s.transform(flatTrainX) + flatTestX = s.transform(flatTestX) + # reshape + flatTrainX = flatTrainX.reshape((trainX.shape)) + flatTestX = flatTestX.reshape((testX.shape)) + return flatTrainX, flatTestX +``` + +我们可以更新 _evaluate_model()_ 函数来获取参数,然后使用此参数来决定是否执行标准化。 + +``` +# fit and evaluate a model +def evaluate_model(trainX, trainy, testX, testy, param): + verbose, epochs, batch_size = 0, 10, 32 + n_timesteps, n_features, n_outputs = trainX.shape[1], trainX.shape[2], trainy.shape[1] + # scale data + trainX, testX = scale_data(trainX, testX, param) + model = Sequential() + model.add(Conv1D(filters=64, kernel_size=3, activation='relu', input_shape=(n_timesteps,n_features))) + model.add(Conv1D(filters=64, kernel_size=3, activation='relu')) + model.add(Dropout(0.5)) + model.add(MaxPooling1D(pool_size=2)) + model.add(Flatten()) + model.add(Dense(100, activation='relu')) + model.add(Dense(n_outputs, activation='softmax')) + model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) + # fit network + model.fit(trainX, trainy, epochs=epochs, batch_size=batch_size, verbose=verbose) + # evaluate model + _, accuracy = model.evaluate(testX, testy, batch_size=batch_size, verbose=0) + return accuracy +``` + +我们还可以更新 _run_experiment()_,为每个参数重复实验 10 次;在这种情况下,只有两个参数将被评估[ _False,True_ ],分别没有标准化和标准化。 + +``` +# run an experiment +def run_experiment(params, repeats=10): + # load data + trainX, trainy, testX, testy = load_dataset() + # test each parameter + all_scores = list() + for p in params: + # repeat experiment + scores = list() + for r in range(repeats): + score = evaluate_model(trainX, trainy, testX, testy, p) + score = score * 100.0 + print('>p=%d #%d: %.3f' % (p, r+1, score)) + scores.append(score) + all_scores.append(scores) + # summarize results + summarize_results(all_scores, params) +``` + +这将产生两个可以比较的结果样本。 + +我们将更新 _summarize_results()_ 函数,以汇总每个配置参数的结果样本,并创建一个箱形图来比较每个结果样本。 + +``` +# summarize scores +def summarize_results(scores, params): + print(scores, params) + # summarize mean and standard deviation + for i in range(len(scores)): + m, s = mean(scores[i]), std(scores[i]) + print('Param=%d: %.3f%% (+/-%.3f)' % (params[i], m, s)) + # boxplot of scores + pyplot.boxplot(scores, labels=params) + pyplot.savefig('exp_cnn_standardize.png') +``` + +这些更新将允许我们直接比较之前模型拟合的结果和数据集标准化后的模型拟合。 + +它也是一个通用的更改,允许我们在以下部分中评估和比较其他参数集的结果。 + +完整的代码清单如下。 + +``` +# cnn model with standardization +from numpy import mean +from numpy import std +from numpy import dstack +from pandas import read_csv +from matplotlib import pyplot +from sklearn.preprocessing import StandardScaler +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import Flatten +from keras.layers import Dropout +from keras.layers.convolutional import Conv1D +from keras.layers.convolutional import MaxPooling1D +from keras.utils import to_categorical + +# load a single file as a numpy array +def load_file(filepath): + dataframe = read_csv(filepath, header=None, delim_whitespace=True) + return dataframe.values + +# load a list of files and return as a 3d numpy array +def load_group(filenames, prefix=''): + loaded = list() + for name in filenames: + data = load_file(prefix + name) + loaded.append(data) + # stack group so that features are the 3rd dimension + loaded = dstack(loaded) + return loaded + +# load a dataset group, such as train or test +def load_dataset_group(group, prefix=''): + filepath = prefix + group + '/Inertial Signals/' + # load all 9 files as a single array + filenames = list() + # total acceleration + filenames += ['total_acc_x_'+group+'.txt', 'total_acc_y_'+group+'.txt', 'total_acc_z_'+group+'.txt'] + # body acceleration + filenames += ['body_acc_x_'+group+'.txt', 'body_acc_y_'+group+'.txt', 'body_acc_z_'+group+'.txt'] + # body gyroscope + filenames += ['body_gyro_x_'+group+'.txt', 'body_gyro_y_'+group+'.txt', 'body_gyro_z_'+group+'.txt'] + # load input data + X = load_group(filenames, filepath) + # load class output + y = load_file(prefix + group + '/y_'+group+'.txt') + return X, y + +# load the dataset, returns train and test X and y elements +def load_dataset(prefix=''): + # load all train + trainX, trainy = load_dataset_group('train', prefix + 'HARDataset/') + print(trainX.shape, trainy.shape) + # load all test + testX, testy = load_dataset_group('test', prefix + 'HARDataset/') + print(testX.shape, testy.shape) + # zero-offset class values + trainy = trainy - 1 + testy = testy - 1 + # one hot encode y + trainy = to_categorical(trainy) + testy = to_categorical(testy) + print(trainX.shape, trainy.shape, testX.shape, testy.shape) + return trainX, trainy, testX, testy + +# standardize data +def scale_data(trainX, testX, standardize): + # remove overlap + cut = int(trainX.shape[1] / 2) + longX = trainX[:, -cut:, :] + # flatten windows + longX = longX.reshape((longX.shape[0] * longX.shape[1], longX.shape[2])) + # flatten train and test + flatTrainX = trainX.reshape((trainX.shape[0] * trainX.shape[1], trainX.shape[2])) + flatTestX = testX.reshape((testX.shape[0] * testX.shape[1], testX.shape[2])) + # standardize + if standardize: + s = StandardScaler() + # fit on training data + s.fit(longX) + # apply to training and test data + longX = s.transform(longX) + flatTrainX = s.transform(flatTrainX) + flatTestX = s.transform(flatTestX) + # reshape + flatTrainX = flatTrainX.reshape((trainX.shape)) + flatTestX = flatTestX.reshape((testX.shape)) + return flatTrainX, flatTestX + +# fit and evaluate a model +def evaluate_model(trainX, trainy, testX, testy, param): + verbose, epochs, batch_size = 0, 10, 32 + n_timesteps, n_features, n_outputs = trainX.shape[1], trainX.shape[2], trainy.shape[1] + # scale data + trainX, testX = scale_data(trainX, testX, param) + model = Sequential() + model.add(Conv1D(filters=64, kernel_size=3, activation='relu', input_shape=(n_timesteps,n_features))) + model.add(Conv1D(filters=64, kernel_size=3, activation='relu')) + model.add(Dropout(0.5)) + model.add(MaxPooling1D(pool_size=2)) + model.add(Flatten()) + model.add(Dense(100, activation='relu')) + model.add(Dense(n_outputs, activation='softmax')) + model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) + # fit network + model.fit(trainX, trainy, epochs=epochs, batch_size=batch_size, verbose=verbose) + # evaluate model + _, accuracy = model.evaluate(testX, testy, batch_size=batch_size, verbose=0) + return accuracy + +# summarize scores +def summarize_results(scores, params): + print(scores, params) + # summarize mean and standard deviation + for i in range(len(scores)): + m, s = mean(scores[i]), std(scores[i]) + print('Param=%s: %.3f%% (+/-%.3f)' % (params[i], m, s)) + # boxplot of scores + pyplot.boxplot(scores, labels=params) + pyplot.savefig('exp_cnn_standardize.png') + +# run an experiment +def run_experiment(params, repeats=10): + # load data + trainX, trainy, testX, testy = load_dataset() + # test each parameter + all_scores = list() + for p in params: + # repeat experiment + scores = list() + for r in range(repeats): + score = evaluate_model(trainX, trainy, testX, testy, p) + score = score * 100.0 + print('>p=%s #%d: %.3f' % (p, r+1, score)) + scores.append(score) + all_scores.append(scores) + # summarize results + summarize_results(all_scores, params) + +# run the experiment +n_params = [False, True] +run_experiment(n_params) +``` + +运行该示例可能需要几分钟,具体取决于您的硬件。 + +为每个评估的模型打印表现。在运行结束时,总结了每个测试配置的表现,显示了平均值和标准偏差。 + +我们可以看到,在建模之前看起来确实标准化数据集确实导致表现的小提升,从大约 90.4%的准确度(接近我们在上一节中看到的)到大约 91.5%的准确度。 + +注意:鉴于算法的随机性,您的具体结果可能会有所不同。 + +``` +(7352, 128, 9) (7352, 1) +(2947, 128, 9) (2947, 1) +(7352, 128, 9) (7352, 6) (2947, 128, 9) (2947, 6) + +>p=False #1: 91.483 +>p=False #2: 91.245 +>p=False #3: 90.838 +>p=False #4: 89.243 +>p=False #5: 90.193 +>p=False #6: 90.465 +>p=False #7: 90.397 +>p=False #8: 90.567 +>p=False #9: 88.938 +>p=False #10: 91.144 +>p=True #1: 92.908 +>p=True #2: 90.940 +>p=True #3: 92.297 +>p=True #4: 91.822 +>p=True #5: 92.094 +>p=True #6: 91.313 +>p=True #7: 91.653 +>p=True #8: 89.141 +>p=True #9: 91.110 +>p=True #10: 91.890 + +[[91.48286392941975, 91.24533423820834, 90.83814048184594, 89.24329826942655, 90.19341703427214, 90.46487953851374, 90.39701391245333, 90.56667797760434, 88.93790295215473, 91.14353579911774], [92.90804207668816, 90.93993892093654, 92.29725144214456, 91.82219205972176, 92.09365456396336, 91.31319986426874, 91.65252799457076, 89.14149983033593, 91.10960298608755, 91.89005768578215]] [False, True] + +Param=False: 90.451% (+/-0.785) +Param=True: 91.517% (+/-0.965) +``` + +还创建了结果的框和胡须图。 + +这允许以非参数方式比较两个结果样本,显示每个样本的中值和中间 50%。 + +我们可以看到,标准化的结果分布与没有标准化的结果分布完全不同。这可能是一个真正的效果。 + +![Box and whisker plot of 1D CNN with and without standardization](img/08e022a87ed50436d814f99435741106.jpg) + +有和没有标准化的 1D CNN 的盒子和须状图 + +### 过滤器数量 + +现在我们有了一个实验框架,我们可以探索不同的模型的其他超参数。 + +CNN 的一个重要超参数是滤波器映射的数量。我们可以尝试一系列不同的值,从比我们开发的第一个模型中使用的 64 个更少到更多。 + +具体来说,我们将尝试以下数量的功能图: + +``` +n_params = [8, 16, 32, 64, 128, 256] +``` + +我们可以使用上一节中的相同代码并更新 _evaluate_model()_ 函数,以使用提供的参数作为 Conv1D 层中的过滤器数量。我们还可以更新 _summarize_results()_ 函数,将箱图保存为 _exp_cnn_filters.png_ 。 + +完整的代码示例如下所示。 + +``` +# cnn model with filters +from numpy import mean +from numpy import std +from numpy import dstack +from pandas import read_csv +from matplotlib import pyplot +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import Flatten +from keras.layers import Dropout +from keras.layers.convolutional import Conv1D +from keras.layers.convolutional import MaxPooling1D +from keras.utils import to_categorical + +# load a single file as a numpy array +def load_file(filepath): + dataframe = read_csv(filepath, header=None, delim_whitespace=True) + return dataframe.values + +# load a list of files and return as a 3d numpy array +def load_group(filenames, prefix=''): + loaded = list() + for name in filenames: + data = load_file(prefix + name) + loaded.append(data) + # stack group so that features are the 3rd dimension + loaded = dstack(loaded) + return loaded + +# load a dataset group, such as train or test +def load_dataset_group(group, prefix=''): + filepath = prefix + group + '/Inertial Signals/' + # load all 9 files as a single array + filenames = list() + # total acceleration + filenames += ['total_acc_x_'+group+'.txt', 'total_acc_y_'+group+'.txt', 'total_acc_z_'+group+'.txt'] + # body acceleration + filenames += ['body_acc_x_'+group+'.txt', 'body_acc_y_'+group+'.txt', 'body_acc_z_'+group+'.txt'] + # body gyroscope + filenames += ['body_gyro_x_'+group+'.txt', 'body_gyro_y_'+group+'.txt', 'body_gyro_z_'+group+'.txt'] + # load input data + X = load_group(filenames, filepath) + # load class output + y = load_file(prefix + group + '/y_'+group+'.txt') + return X, y + +# load the dataset, returns train and test X and y elements +def load_dataset(prefix=''): + # load all train + trainX, trainy = load_dataset_group('train', prefix + 'HARDataset/') + print(trainX.shape, trainy.shape) + # load all test + testX, testy = load_dataset_group('test', prefix + 'HARDataset/') + print(testX.shape, testy.shape) + # zero-offset class values + trainy = trainy - 1 + testy = testy - 1 + # one hot encode y + trainy = to_categorical(trainy) + testy = to_categorical(testy) + print(trainX.shape, trainy.shape, testX.shape, testy.shape) + return trainX, trainy, testX, testy + +# fit and evaluate a model +def evaluate_model(trainX, trainy, testX, testy, n_filters): + verbose, epochs, batch_size = 0, 10, 32 + n_timesteps, n_features, n_outputs = trainX.shape[1], trainX.shape[2], trainy.shape[1] + model = Sequential() + model.add(Conv1D(filters=n_filters, kernel_size=3, activation='relu', input_shape=(n_timesteps,n_features))) + model.add(Conv1D(filters=n_filters, kernel_size=3, activation='relu')) + model.add(Dropout(0.5)) + model.add(MaxPooling1D(pool_size=2)) + model.add(Flatten()) + model.add(Dense(100, activation='relu')) + model.add(Dense(n_outputs, activation='softmax')) + model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) + # fit network + model.fit(trainX, trainy, epochs=epochs, batch_size=batch_size, verbose=verbose) + # evaluate model + _, accuracy = model.evaluate(testX, testy, batch_size=batch_size, verbose=0) + return accuracy + +# summarize scores +def summarize_results(scores, params): + print(scores, params) + # summarize mean and standard deviation + for i in range(len(scores)): + m, s = mean(scores[i]), std(scores[i]) + print('Param=%d: %.3f%% (+/-%.3f)' % (params[i], m, s)) + # boxplot of scores + pyplot.boxplot(scores, labels=params) + pyplot.savefig('exp_cnn_filters.png') + +# run an experiment +def run_experiment(params, repeats=10): + # load data + trainX, trainy, testX, testy = load_dataset() + # test each parameter + all_scores = list() + for p in params: + # repeat experiment + scores = list() + for r in range(repeats): + score = evaluate_model(trainX, trainy, testX, testy, p) + score = score * 100.0 + print('>p=%d #%d: %.3f' % (p, r+1, score)) + scores.append(score) + all_scores.append(scores) + # summarize results + summarize_results(all_scores, params) + +# run the experiment +n_params = [8, 16, 32, 64, 128, 256] +run_experiment(n_params) +``` + +运行该示例将为每个指定数量的过滤器重复实验。 + +在运行结束时,将显示每个过滤器数量的结果摘要。 + +随着滤波器图数量的增加,我们可以看到平均表现提升的趋势。方差保持不变,可能 128 个特征映射可能是网络的良好配置。 + +``` +... +Param=8: 89.148% (+/-0.790) +Param=16: 90.383% (+/-0.613) +Param=32: 90.356% (+/-1.039) +Param=64: 90.098% (+/-0.615) +Param=128: 91.032% (+/-0.702) +Param=256: 90.706% (+/-0.997) +``` + +还创建了结果的框和胡须图,允许比较每个过滤器数量的结果。 + +从图中可以看出,随着特征图数量的增加,中值分类精度(框中的橙色线)的趋势向上。我们确实看到了 64 个特征图(我们的实验中的默认值或基线)的下降,这是令人惊讶的,并且可能在 32,128 和 256 个过滤器图中具有稳定性的平台。也许 32 将是一个更稳定的配置。 + +![Box and whisker plot of 1D CNN with different numbers of filter maps](img/da2a831510c95ca2ba65350d21b67c83.jpg) + +具有不同数量的滤波器映射的 1D CNN 的盒子和须状图 + +### 内核的大小 + +内核的大小是 1D CNN 调整的另一个重要的超参数。 + +内核大小控制输入序列的每个“_ 读取 _”中考虑的时间步数,然后将其投影到特征图上(通过卷积过程)。 + +较大的内核大小意味着不太严格的数据读取,但可能会导致输入的更通用的快照。 + +除了默认的三个时间步长之外,我们可以使用相同的实验设置并测试一套不同的内核大小。完整的值列表如下: + +``` +n_params = [2, 3, 5, 7, 11] +``` + +完整的代码清单如下: + +``` +# cnn model vary kernel size +from numpy import mean +from numpy import std +from numpy import dstack +from pandas import read_csv +from matplotlib import pyplot +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import Flatten +from keras.layers import Dropout +from keras.layers.convolutional import Conv1D +from keras.layers.convolutional import MaxPooling1D +from keras.utils import to_categorical + +# load a single file as a numpy array +def load_file(filepath): + dataframe = read_csv(filepath, header=None, delim_whitespace=True) + return dataframe.values + +# load a list of files and return as a 3d numpy array +def load_group(filenames, prefix=''): + loaded = list() + for name in filenames: + data = load_file(prefix + name) + loaded.append(data) + # stack group so that features are the 3rd dimension + loaded = dstack(loaded) + return loaded + +# load a dataset group, such as train or test +def load_dataset_group(group, prefix=''): + filepath = prefix + group + '/Inertial Signals/' + # load all 9 files as a single array + filenames = list() + # total acceleration + filenames += ['total_acc_x_'+group+'.txt', 'total_acc_y_'+group+'.txt', 'total_acc_z_'+group+'.txt'] + # body acceleration + filenames += ['body_acc_x_'+group+'.txt', 'body_acc_y_'+group+'.txt', 'body_acc_z_'+group+'.txt'] + # body gyroscope + filenames += ['body_gyro_x_'+group+'.txt', 'body_gyro_y_'+group+'.txt', 'body_gyro_z_'+group+'.txt'] + # load input data + X = load_group(filenames, filepath) + # load class output + y = load_file(prefix + group + '/y_'+group+'.txt') + return X, y + +# load the dataset, returns train and test X and y elements +def load_dataset(prefix=''): + # load all train + trainX, trainy = load_dataset_group('train', prefix + 'HARDataset/') + print(trainX.shape, trainy.shape) + # load all test + testX, testy = load_dataset_group('test', prefix + 'HARDataset/') + print(testX.shape, testy.shape) + # zero-offset class values + trainy = trainy - 1 + testy = testy - 1 + # one hot encode y + trainy = to_categorical(trainy) + testy = to_categorical(testy) + print(trainX.shape, trainy.shape, testX.shape, testy.shape) + return trainX, trainy, testX, testy + +# fit and evaluate a model +def evaluate_model(trainX, trainy, testX, testy, n_kernel): + verbose, epochs, batch_size = 0, 15, 32 + n_timesteps, n_features, n_outputs = trainX.shape[1], trainX.shape[2], trainy.shape[1] + model = Sequential() + model.add(Conv1D(filters=64, kernel_size=n_kernel, activation='relu', input_shape=(n_timesteps,n_features))) + model.add(Conv1D(filters=64, kernel_size=n_kernel, activation='relu')) + model.add(Dropout(0.5)) + model.add(MaxPooling1D(pool_size=2)) + model.add(Flatten()) + model.add(Dense(100, activation='relu')) + model.add(Dense(n_outputs, activation='softmax')) + model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) + # fit network + model.fit(trainX, trainy, epochs=epochs, batch_size=batch_size, verbose=verbose) + # evaluate model + _, accuracy = model.evaluate(testX, testy, batch_size=batch_size, verbose=0) + return accuracy + +# summarize scores +def summarize_results(scores, params): + print(scores, params) + # summarize mean and standard deviation + for i in range(len(scores)): + m, s = mean(scores[i]), std(scores[i]) + print('Param=%d: %.3f%% (+/-%.3f)' % (params[i], m, s)) + # boxplot of scores + pyplot.boxplot(scores, labels=params) + pyplot.savefig('exp_cnn_kernel.png') + +# run an experiment +def run_experiment(params, repeats=10): + # load data + trainX, trainy, testX, testy = load_dataset() + # test each parameter + all_scores = list() + for p in params: + # repeat experiment + scores = list() + for r in range(repeats): + score = evaluate_model(trainX, trainy, testX, testy, p) + score = score * 100.0 + print('>p=%d #%d: %.3f' % (p, r+1, score)) + scores.append(score) + all_scores.append(scores) + # summarize results + summarize_results(all_scores, params) + +# run the experiment +n_params = [2, 3, 5, 7, 11] +run_experiment(n_params) +``` + +运行该示例依次测试每个内核大小。 + +结果总结在运行结束时。随着内核大小的增加,我们可以看到模型表现的普遍提高。 + +结果表明,籽粒大小为 5 可能是好的,平均技能为约 91.8%,但也许大小为 7 或 11 也可能同样好,标准偏差较小。 + +``` +... +Param=2: 90.176% (+/-0.724) +Param=3: 90.275% (+/-1.277) +Param=5: 91.853% (+/-1.249) +Param=7: 91.347% (+/-0.852) +Param=11: 91.456% (+/-0.743) +``` + +还创建了结果的框和胡须图。 + +结果表明,较大的内核大小确实会产生更好的准确性,并且内核大小 7 可能在良好表现和低方差之间提供了良好的平衡。 + +![Box and whisker plot of 1D CNN with different numbers of kernel sizes](img/64107e8c8d652ad0e1c8b133c06bcabf.jpg) + +具有不同内核大小的 1D CNN 的盒子和须状图 + +这只是调整模型的开始,尽管我们关注的可能是更重要的元素。探索上述某些发现的组合可能会很有趣,看看表现是否可以进一步提升。 + +将重复次数从 10 增加到 30 或更多以确定它是否会导致更稳定的结果也可能是有趣的。 + +## 多头卷积神经网络 + +CNN 的另一种流行方法是使用多头模型,其中模型的每个头使用不同大小的内核读取输入时间步长。 + +例如,三头模型可以具有三种不同的内核大小 3,5,11,允许模型以三种不同的分辨率读取和解释序列数据。然后,在进行预测之前,来自所有三个头的解释在模型内连接并由完全连接的层解释。 + +我们可以使用 Keras 功能 API 实现多头 1D CNN。有关此 API 的温和介绍,请参阅帖子: + +* [如何使用 Keras 功能 API 进行深度学习](https://machinelearningmastery.com/keras-functional-api-deep-learning/) + +下面列出了 _evaluate_model()_ 函数的更新版本,它创建了一个三头 CNN 模型。 + +我们可以看到模型的每个头部都是相同的结构,尽管内核大小是变化的。然后,在进行预测之前,三个头在被解释之前进入单个合并层。 + +``` +# fit and evaluate a model +def evaluate_model(trainX, trainy, testX, testy): + verbose, epochs, batch_size = 0, 10, 32 + n_timesteps, n_features, n_outputs = trainX.shape[1], trainX.shape[2], trainy.shape[1] + # head 1 + inputs1 = Input(shape=(n_timesteps,n_features)) + conv1 = Conv1D(filters=64, kernel_size=3, activation='relu')(inputs1) + drop1 = Dropout(0.5)(conv1) + pool1 = MaxPooling1D(pool_size=2)(drop1) + flat1 = Flatten()(pool1) + # head 2 + inputs2 = Input(shape=(n_timesteps,n_features)) + conv2 = Conv1D(filters=64, kernel_size=5, activation='relu')(inputs2) + drop2 = Dropout(0.5)(conv2) + pool2 = MaxPooling1D(pool_size=2)(drop2) + flat2 = Flatten()(pool2) + # head 3 + inputs3 = Input(shape=(n_timesteps,n_features)) + conv3 = Conv1D(filters=64, kernel_size=11, activation='relu')(inputs3) + drop3 = Dropout(0.5)(conv3) + pool3 = MaxPooling1D(pool_size=2)(drop3) + flat3 = Flatten()(pool3) + # merge + merged = concatenate([flat1, flat2, flat3]) + # interpretation + dense1 = Dense(100, activation='relu')(merged) + outputs = Dense(n_outputs, activation='softmax')(dense1) + model = Model(inputs=[inputs1, inputs2, inputs3], outputs=outputs) + # save a plot of the model + plot_model(model, show_shapes=True, to_file='multichannel.png') + model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) + # fit network + model.fit([trainX,trainX,trainX], trainy, epochs=epochs, batch_size=batch_size, verbose=verbose) + # evaluate model + _, accuracy = model.evaluate([testX,testX,testX], testy, batch_size=batch_size, verbose=0) + return accuracy +``` + +创建模型时,会创建网络体系结构图;如下所示,它清楚地说明了构建的模型如何组合在一起。 + +![Plot of the Multi-Headed 1D Convolutional Neural Network](img/c1fe9958045eb17f2e1b6aeaeca1dbb8.jpg) + +多头一维卷积神经网络的图 + +模型的其他方面可以在各个方面变化,例如过滤器的数量或甚至数据本身的准备。 + +下面列出了多头 1D CNN 的完整代码示例。 + +``` +# multi-headed cnn model +from numpy import mean +from numpy import std +from numpy import dstack +from pandas import read_csv +from matplotlib import pyplot +from keras.utils import to_categorical +from keras.utils.vis_utils import plot_model +from keras.models import Model +from keras.layers import Input +from keras.layers import Dense +from keras.layers import Flatten +from keras.layers import Dropout +from keras.layers.convolutional import Conv1D +from keras.layers.convolutional import MaxPooling1D +from keras.layers.merge import concatenate + +# load a single file as a numpy array +def load_file(filepath): + dataframe = read_csv(filepath, header=None, delim_whitespace=True) + return dataframe.values + +# load a list of files and return as a 3d numpy array +def load_group(filenames, prefix=''): + loaded = list() + for name in filenames: + data = load_file(prefix + name) + loaded.append(data) + # stack group so that features are the 3rd dimension + loaded = dstack(loaded) + return loaded + +# load a dataset group, such as train or test +def load_dataset_group(group, prefix=''): + filepath = prefix + group + '/Inertial Signals/' + # load all 9 files as a single array + filenames = list() + # total acceleration + filenames += ['total_acc_x_'+group+'.txt', 'total_acc_y_'+group+'.txt', 'total_acc_z_'+group+'.txt'] + # body acceleration + filenames += ['body_acc_x_'+group+'.txt', 'body_acc_y_'+group+'.txt', 'body_acc_z_'+group+'.txt'] + # body gyroscope + filenames += ['body_gyro_x_'+group+'.txt', 'body_gyro_y_'+group+'.txt', 'body_gyro_z_'+group+'.txt'] + # load input data + X = load_group(filenames, filepath) + # load class output + y = load_file(prefix + group + '/y_'+group+'.txt') + return X, y + +# load the dataset, returns train and test X and y elements +def load_dataset(prefix=''): + # load all train + trainX, trainy = load_dataset_group('train', prefix + 'HARDataset/') + print(trainX.shape, trainy.shape) + # load all test + testX, testy = load_dataset_group('test', prefix + 'HARDataset/') + print(testX.shape, testy.shape) + # zero-offset class values + trainy = trainy - 1 + testy = testy - 1 + # one hot encode y + trainy = to_categorical(trainy) + testy = to_categorical(testy) + print(trainX.shape, trainy.shape, testX.shape, testy.shape) + return trainX, trainy, testX, testy + +# fit and evaluate a model +def evaluate_model(trainX, trainy, testX, testy): + verbose, epochs, batch_size = 0, 10, 32 + n_timesteps, n_features, n_outputs = trainX.shape[1], trainX.shape[2], trainy.shape[1] + # head 1 + inputs1 = Input(shape=(n_timesteps,n_features)) + conv1 = Conv1D(filters=64, kernel_size=3, activation='relu')(inputs1) + drop1 = Dropout(0.5)(conv1) + pool1 = MaxPooling1D(pool_size=2)(drop1) + flat1 = Flatten()(pool1) + # head 2 + inputs2 = Input(shape=(n_timesteps,n_features)) + conv2 = Conv1D(filters=64, kernel_size=5, activation='relu')(inputs2) + drop2 = Dropout(0.5)(conv2) + pool2 = MaxPooling1D(pool_size=2)(drop2) + flat2 = Flatten()(pool2) + # head 3 + inputs3 = Input(shape=(n_timesteps,n_features)) + conv3 = Conv1D(filters=64, kernel_size=11, activation='relu')(inputs3) + drop3 = Dropout(0.5)(conv3) + pool3 = MaxPooling1D(pool_size=2)(drop3) + flat3 = Flatten()(pool3) + # merge + merged = concatenate([flat1, flat2, flat3]) + # interpretation + dense1 = Dense(100, activation='relu')(merged) + outputs = Dense(n_outputs, activation='softmax')(dense1) + model = Model(inputs=[inputs1, inputs2, inputs3], outputs=outputs) + # save a plot of the model + plot_model(model, show_shapes=True, to_file='multichannel.png') + model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) + # fit network + model.fit([trainX,trainX,trainX], trainy, epochs=epochs, batch_size=batch_size, verbose=verbose) + # evaluate model + _, accuracy = model.evaluate([testX,testX,testX], testy, batch_size=batch_size, verbose=0) + return accuracy + +# summarize scores +def summarize_results(scores): + print(scores) + m, s = mean(scores), std(scores) + print('Accuracy: %.3f%% (+/-%.3f)' % (m, s)) + +# run an experiment +def run_experiment(repeats=10): + # load data + trainX, trainy, testX, testy = load_dataset() + # repeat experiment + scores = list() + for r in range(repeats): + score = evaluate_model(trainX, trainy, testX, testy) + score = score * 100.0 + print('>#%d: %.3f' % (r+1, score)) + scores.append(score) + # summarize results + summarize_results(scores) + +# run the experiment +run_experiment() +``` + +运行该示例在每次重复实验时打印模型的表现,然后将估计得分总结为平均值和标准差,正如我们在第一种情况下使用简单的 1D CNN 所做的那样。 + +我们可以看到该模型的平均表现约为 91.6%的分类精度,标准偏差约为 0.8。 + +该示例可以用作探索各种其他模型的基础,这些模型改变不同模型超参数甚至跨输入头的不同数据准备方案。 + +考虑到该模型中资源的相对三倍,将此结果与单头 CNN 进行比较并不是一个苹果对苹果的比较。也许苹果与苹果的比较将是具有相同架构的模型,并且在模型的每个输入头上具有相同数量的过滤器。 + +``` +>#1: 91.788 +>#2: 92.942 +>#3: 91.551 +>#4: 91.415 +>#5: 90.974 +>#6: 91.992 +>#7: 92.162 +>#8: 89.888 +>#9: 92.671 +>#10: 91.415 + +[91.78825924669155, 92.94197488971835, 91.55072955548015, 91.41499830335935, 90.97387173396675, 91.99185612487275, 92.16152019002375, 89.88802171700034, 92.67051238547675, 91.41499830335935] + +Accuracy: 91.680% (+/-0.823) +``` + +## 扩展 + +本节列出了一些扩展您可能希望探索的教程的想法。 + +* **日期准备**。探索其他数据准备方案,例如数据标准化以及标准化后的标准化。 +* **网络架构**。探索其他网络架构,例如更深入的 CNN 架构和更深层的全连接层,用于解释 CNN 输入功能。 +* **诊断**。使用简单的学习曲线诊断来解释模型在时期中的学习方式,以及更多的正则化,不同的学习率,或不同的批量大小或时期数可能导致更好的表现或更稳定的模型。 + +如果你探索任何这些扩展,我很想知道。 + +## 进一步阅读 + +如果您希望深入了解,本节将提供有关该主题的更多资源。 + +### 文件 + +* [使用智能手机进行人类活动识别的公共领域数据集](https://upcommons.upc.edu/handle/2117/20897),2013 年。 +* [智能手机上的人类活动识别使用多类硬件友好支持向量机](https://link.springer.com/chapter/10.1007/978-3-642-35395-6_30),2012。 + +### 用品 + +* [使用智能手机数据集进行人类活动识别,UCI 机器学习库](https://archive.ics.uci.edu/ml/datasets/human+activity+recognition+using+smartphones) +* [活动识别,维基百科](https://en.wikipedia.org/wiki/Activity_recognition) +* [使用智能手机传感器的活动识别实验,视频](https://www.youtube.com/watch?v=XOEN9W05_4A)。 + +## 摘要 + +在本教程中,您了解了如何开发一维卷积神经网络,用于人类活动识别问题的时间序列分类。 + +具体来说,你学到了: + +* 如何为标准人类活动识别数据集加载和准备数据,并开发单个 1D CNN 模型,以在原始数据上实现出色的表现。 +* 如何进一步调整模型的表现,包括数据转换,过滤器映射和内核大小。 +* 如何开发一种复杂的多头一维卷积神经网络模型,提供类似集合的结果。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/deep-learning-models-for-human-activity-recognition.md b/docs/dl-ts/deep-learning-models-for-human-activity-recognition.md new file mode 100644 index 0000000000000000000000000000000000000000..db6bd030c7a12e1ce752a409a2402f9c3617b3c4 --- /dev/null +++ b/docs/dl-ts/deep-learning-models-for-human-activity-recognition.md @@ -0,0 +1,352 @@ +# 人类活动识别的深度学习模型 + +> 原文: [https://machinelearningmastery.com/deep-learning-models-for-human-activity-recognition/](https://machinelearningmastery.com/deep-learning-models-for-human-activity-recognition/) + +人类活动识别(HAR)是具有挑战性的时间序列分类任务。 + +它涉及基于传感器数据预测人的移动,并且传统上涉及深度领域专业知识和来自信号处理的方法,以正确地设计来自原始数据的特征以适合机器学习模型。 + +最近,诸如卷积神经网络和循环神经网络的深度学习方法已经显示出通过自动学习原始传感器数据的特征而能够甚至实现最先进的结果。 + +在这篇文章中,您将发现人类活动识别的问题以及在这个问题上实现最先进表现的深度学习方法。 + +阅读这篇文章后,你会知道: + +* 活动识别是基于传感器数据(例如智能手机中的加速度计)预测人(通常在室内)的移动的问题。 +* 传感器数据流通常被分成称为窗口的子序列,并且每个窗口与更广泛的活动相关联,称为滑动窗口方法。 +* 卷积神经网络和长期短期记忆网络,或许两者结合在一起,最适合从原始传感器数据中学习特征并预测相关的运动。 + +让我们开始吧。 + +![Deep Learning Models for Human Activity Recognition](img/d6e72b46b3d1124712f8b7e1fb0da1ca.jpg) + +用于人类活动识别的深度学习模型 +照片由 [Simon Harrod](https://www.flickr.com/photos/sidibousaid/8238090492/) 拍摄,保留一些权利。 + +## 概观 + +这篇文章分为五个部分;他们是: + +1. 人类活动识别 +2. 神经网络建模的好处 +3. 监督学习数据表示 +4. 卷积神经网络模型 +5. 循环神经网络模型 + +## 人类活动识别 + +人类活动识别(简称 HAR)是涉及基于传感器数据识别人的特定运动或动作的广泛研究领域。 + +运动通常是在室内进行的典型活动,例如步行,说话,站立和坐着。它们也可能是更集中的活动,例如在厨房或工厂车间进行的那些类型的活动。 + +可以远程记录传感器数据,例如视频,雷达或其他无线方法。或者,可以将数据直接记录在对象上,例如通过携带具有加速度计和陀螺仪的定制硬件或智能电话。 + +> 基于传感器的活动识别从大量低水平传感器读数中寻找关于人类活动的深刻的高级知识 + +- [基于传感器的活动识别深度学习:调查](https://www.sciencedirect.com/science/article/pii/S016786551830045X),2018。 + +从历史上看,用于活动识别的传感器数据具有挑战性且收集成本高,需要定制硬件。现在,用于健身和健康监测的智能手机和其他个人跟踪设备便宜且无处不在。因此,来自这些设备的传感器数据收集起来更便宜,更常见,因此是一般活动识别问题的更常研究的版本。 + +问题是在给定传感器数据快照的情况下预测活动,通常是来自一个或少数传感器类型的数据。通常,该问题被构造为单变量或多变量时间序列分类任务。 + +这是一个具有挑战性的问题,因为没有明显或直接的方式将记录的传感器数据与特定的人类活动相关联,并且每个受试者可能执行具有显着变化的活动,导致所记录的传感器数据的变化。 + +目的是记录特定受试者的传感器数据和相应活动,根据该数据拟合模型,并推广模型以根据传感器数据对新看不见的受试者的活动进行分类。 + +## 神经网络建模的好处 + +传统上,来自信号处理领域的方法用于分析和提取收集的传感器数据。 + +这些方法用于特征工程,创建特定于域的,特定于传感器或特定于信号处理的特征以及原始数据的视图。然后对处理后的数据版本训练统计和机器学习模型。 + +此方法的局限性在于分析原始数据和设计拟合模型所需的功能所需的信号处理和领域专业知识。每个新数据集或传感器模态都需要这种专业知识。实质上,它昂贵且不可扩展。 + +> 然而,在大多数日常 HAR 任务中,这些方法可能严重依赖于启发式手工特征提取,其通常受到人类领域知识的限制。此外,这些方法只能学习浅层特征,导致无监督和增量任务的表现下降。由于这些限制,传统[模式识别]方法的表现在分类准确性和模型概括方面受到限制。 + +- [基于传感器的活动识别深度学习:调查](https://www.sciencedirect.com/science/article/pii/S016786551830045X),2018。 + +理想情况下,可以使用学习方法自动学习直接从原始数据进行准确预测所需的功能。这将允许快速且廉价地采用新问题,新数据集和新传感器模态。 + +最近,深度神经网络模型已经开始实现其特征学习的承诺,并且正在实现人类活动识别的最新结果。它们能够从原始传感器数据执行自动功能学习,并且能够在手工制作的特定于域的功能上执行优秀的模型。 + +> [...],特征提取和模型构建过程通常在深度学习模型中同时执行。这些功能可以通过网络自动学习,而不是手动设计。此外,深层神经网络还可以提取深层的高层表示,使其更适合于复杂的活动识别任务。 + +- [基于传感器的活动识别深度学习:调查](https://www.sciencedirect.com/science/article/pii/S016786551830045X),2018。 + +神经网络有两种主要方法适用于时间序列分类,并且已经证明使用来自商品智能手机和健身追踪设备的传感器数据在活动识别方面表现良好。 + +它们是卷积神经网络模型和循环神经网络模型。 + +> 建议 RNN 和 LSTM 识别具有自然顺序的短活动,而 CNN 更好地推断长期重复活动。原因是 RNN 可以利用传感器读数之间的时间顺序关系,CNN 更能够学习递归模式中包含的深层特征。 + +- [基于传感器的活动识别深度学习:调查](https://www.sciencedirect.com/science/article/pii/S016786551830045X),2018。 + +## 监督学习数据表示 + +在我们深入研究可用于人类活动识别的特定神经网络之前,我们需要讨论数据准备。 + +适用于时间序列分类的两种类型的神经网络都需要以特定方式准备数据以便适合模型。也就是说,在 _ 无监督学习 _'方式中,允许模型将信号数据与活动类相关联。 + +一种直接的数据准备方法,既可用于手工制作特征的经典机器学习方法,也可用于神经网络,包括将输入信号数据划分为信号窗口,其中给定窗口可能有一到几秒的观察时间数据。这通常被称为'_ 滑动窗口 _。 + +> 人类活动识别旨在从传感器捕获的一组观察中推断出一个或多个人的行为。通常,这是通过遵循用于特征提取的固定长度滑动窗口方法来执行的,其中必须修复两个参数:窗口的大小和移位。 + +- [用于活动识别的动态滑动窗口方法](https://link.springer.com/chapter/10.1007/978-3-642-22362-4_19),2011 + +每个窗口还与特定活动相关联。给定的数据窗口可以具有多个变量,例如加速度计传感器的 x,y 和 z 轴。 + +让我们以一个例子来具体化。 + +我们有 10 分钟的传感器数据;可能看起来像: + +``` +x, y, z, activity +1.1, 2.1, 0.1, 1 +1.2, 2.2, 0.2, 1 +1.3, 2.3, 0.3, 1 +... +``` + +如果数据以 8 Hz 记录,则意味着执行活动所用的一秒钟将有八行数据。 + +我们可以选择让一个数据窗口代表一秒钟的数据;这意味着 8 Hz 传感器有 8 行数据。如果我们有 x,y 和 z 数据,那意味着我们将有 3 个变量。因此,单个数据窗口将是具有八个时间步长和三个特征的二维阵列。 + +一个窗口代表一个样本。一分钟的数据代表 480 个传感器数据点,或 60 个 8 个时间步长的窗口。总共 10 分钟的数据将代表 4,800 个数据点,或 600 个数据窗口。 + +可以方便地根据样本或窗口的数量,窗口中的时间步数以及在每个时间步骤观察到的特征的数量来描述我们准备的传感器数据的形状。 + +``` +[samples, time steps, features] +``` + +我们以 8 Hz 记录的 10 分钟加速度计数据的示例将被概括为具有以下尺寸的三维阵列: + +``` +[600, 8, 3] +``` + +没有最佳窗口大小,它实际上取决于所使用的特定模型,收集的传感器数据的性质以及被分类的活动。 + +窗户的大小和模型的大小都有张力。较大的窗户需要较大的模型,较慢的训练,而较小的窗户需要较小的模型,更容易适应。 + +> 直观地,减小窗口大小允许更快的活动检测,以及减少的资源和能量需求。相反,通常考虑使用大数据窗口来识别复杂的活动 + +- [人类活动识别中的窗口大小影响](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4029702/),2014 年。 + +然而,通常使用一到两秒的传感器数据以便对活动的当前片段进行分类。 + +> 从结果来看,减少的窗口(2 秒或更短)被证明可以提供最准确的检测表现。事实上,对于非常短的窗口(0.25-0.5 秒),可获得最精确的识别器,从而可以完美识别大多数活动。与通常认为的相反,本研究表明,大窗口尺寸不一定转化为更好的识别表现。 + +- [人类活动识别中的窗口大小影响](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4029702/),2014 年。 + +将传感器数据流分成窗口可能会导致窗口错过一个活动到另一个活动的转换。因此,传统上将数据分成具有重叠的窗口是常见的,使得窗口的前半部分包含来自前一窗口的后半部分的观察,在 50%重叠的情况下。 + +> [...]不正确的长度可能会截断活动实例。在许多情况下,当窗口与一个活动的结尾和下一个活动的开头重叠时,错误会出现在活动的开头或结尾。在其他情况下,窗口长度可能太短而不能为识别过程提供最佳信息。 + +- [用于活动识别的动态滑动窗口方法](https://link.springer.com/chapter/10.1007/978-3-642-22362-4_19),2011 + +目前还不清楚给定问题是否需要具有重叠的窗口。 + +在采用神经网络模型时,使用重叠(例如 50%重叠)将使训练数据的大小加倍,这可能有助于建模较小的数据集,但也可能导致过度拟合训练数据集的模型。 + +> 对于某些应用,允许相邻窗口之间的重叠;但是,这种使用频率较低。 + +- [人类活动识别中的窗口大小影响](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4029702/),2014 年。 + +## 卷积神经网络模型 + +卷积神经网络模型(简称 CNN)是一种深度神经网络,它被开发用于图像数据,例如,如手写识别。 + +事实证明,它们在大规模训练时可以有效地挑战计算机视觉问题,例如识别和定位图像中的对象以及自动描述图像内容。 + +它们是由两种主要类型的元素组成的模型:卷积层和池化层。 + +卷积层使用内核读取输入,例如 2D 图像或 1D 信号,该内核一次读取小段并跨越整个输入字段。每次读取都会生成投影到滤镜图上的输入,并表示输入的内部解释。 + +池化层采用特征图投影并将其提取到最基本的元素,例如使用信号平均或信号最大化过程。 + +卷积和合并层可以在深度重复,提供输入信号的多层抽象。 + +这些网络的输出通常是一个或多个完全连接的层,用于解释已读取的内容并将此内部表示映射到类值。 + +有关卷积神经网络的更多信息,可以看到帖子: + +* [用于机器学习的卷积神经网络的速成课程](https://machinelearningmastery.com/crash-course-convolutional-neural-networks/) + +CNN 可以应用于人类活动识别数据。 + +CNN 模型学习将给定的信号数据窗口映射到模型读取每个数据窗口的活动,并准备窗口的内部表示。 + +> 当应用于像 HAR 这样的时间序列分类时,CNN 比其他模型具有两个优点:局部依赖性和尺度不变性。局部依赖性意味着 HAR 中的附近信号可能是相关的,而尺度不变性是指不同步幅或频率的尺度不变。 + +- [基于传感器的活动识别深度学习:调查](https://www.sciencedirect.com/science/article/pii/S016786551830045X),2018。 + +使用 CNN 进行 HAR 的第一项重要工作是 Ming Zeng 等人在他们的 2014 年论文“使用移动传感器进行人类活动识别的 Co [nvolutional 神经网络”。](https://ieeexplore.ieee.org/document/7026300/) + +在本文中,作者为加速度计数据开发了一个简单的 CNN 模型,其中加速度计数据的每个轴被馈送到单独的卷积层,汇集层,然后在被隐藏的完全连接层解释之前连接。 + +从纸上得到的图清楚地显示了模型的拓扑结构。它提供了一个很好的模板,用于 CNN 如何用于 HAR 问题和一般的时间序列分类。 + +![Depiction of CNN Model for Accelerompter Data](img/0332193c9b766456dd1810d1f257ae32.jpg) + +用于加速度计数据的 CNN 模型的描述 +取自“使用移动传感器进行人类活动识别的卷积神经网络” + +有许多方法可以模拟 CNN 的 HAR 问题。 + +一个有趣的例子是 Heeryon Cho 和 Sang Min Yoon 在他们的 2018 年论文题为“[基于划分和征服的 1N CNN 人类活动识别使用测试数据锐化](http://www.mdpi.com/1424-8220/18/4/1055)”。 + +在其中,他们将活动分为涉及运动的那些,称为“_ 动态 _”,以及那些主体静止的,称为“_ 静态 _”,然后开发 CNN 模型来区分这两个主要类别。然后,在每个类中,开发模型以区分该类型的活动,例如动态的“_ 行走 _”和静态的“_ 坐 _”。 + +![Separation of Activities as Dynamic or Static](img/cf7c8976614194befd7ba7073555bfd1.jpg) + +将活动分离为动态或静态 +取自“基于划分和征服的 1N CNN 人类活动识别使用测试数据锐化” + +他们将此称为两阶段建模方法。 + +> 我们不是直接使用单个 6 级分类器识别单个活动,而是应用分而治之的方法并构建一个两阶段活动识别过程,其中首先使用 2-来识别抽象活动,即动态和静态活动。类或二元分类器,然后使用两个 3 级分类器识别单个活动。 + +- [基于划分和征服的 1D CNN 人类活动识别使用测试数据锐化](http://www.mdpi.com/1424-8220/18/4/1055),2018。 + +开发了相当大的 CNN 模型,这反过来又使作者能够在具有挑战性的标准人类活动识别数据集上获得最先进的结果。 + +另一个有趣的方法是由 Wenchao Jiang 和 Zhaozheng Yin 在其 2015 年题为“[深度卷积神经网络](https://dl.acm.org/citation.cfm?id=2806333)使用可穿戴传感器的人类活动识别”的论文中提出的。 + +它们不是在信号数据上使用 1D CNN,而是将信号数据组合在一起以创建“_ 图像 _”,然后将其馈送到 2D CNN 并作为图像数据处理,其中信号的时间轴上有卷积,跨信号变量,特别是加速度计和陀螺仪数据。 + +> 首先,将原始信号逐行堆叠成信号图像[...]。在信号图像中,每个信号序列都有机会与每个其他序列相邻,这使得 DCNN 能够提取相邻信号之间的隐藏相关性。然后,将 2D 离散傅里叶变换(DFT)应用于信号图像,并选择其幅度作为我们的活动图像 + +- [通过深度卷积神经网络使用可穿戴传感器进行人类活动识别](https://dl.acm.org/citation.cfm?id=2806333),2015 年。 + +下面描述了将原始传感器数据处理成图像,然后从图像处理成“_ 活动图像 _”,即离散傅立叶变换的结果。 + +![Processing of Raw Sensor Data into an Image](img/7f07eb77886209ce3939e969da4cb79a.jpg) + +将原始传感器数据处理成图像 +取自“通过深度卷积神经网络使用可穿戴传感器的人类活动识别” + +最后,关于这一主题的另一篇优秀论文是 Charissa Ann Ronao 和 Sung-Bae Cho 在 2016 年题为“[使用深度学习神经网络](https://www.sciencedirect.com/science/article/pii/S0957417416302056)的智能手机传感器进行人类活动识别”。 + +仔细研究 CNN 的使用表明,较大的内核大小的信号数据是有用的并且有限的汇集。 + +> 实验表明,尽管每个附加层的特征复杂度水平差异减小,但是对于每个附加层,回归实际上都会得到相关且更复杂的特征。可以利用更宽的时间局部相关时间跨度(1×9-1×14),并且低池化大小(1×2 -1×3)被证明是有益的。 + +- [使用深度学习神经网络的智能手机传感器识别人类活动](https://www.sciencedirect.com/science/article/pii/S0957417416302056),2016 年。 + +有用的是,它们还为 CNN 模型提供了完整的超参数配置,可以为新的 HAR 和其他序列分类问题提供有用的起点,总结如下。 + +![Table of CNN Model Hyperparameter Configuration](img/19b6c63fd6c2e9d82bba960d447a3c7c.jpg) + +CNN 模型超参数配置表 +取自“使用深度学习神经网络的智能手机传感器识别人体活动”。 + +## 循环神经网络模型 + +循环神经网络(简称 RNN)是一种神经网络,旨在从序列数据中学习,例如随时间观察的序列,或句子中的单词序列。 + +称为长短期记忆网络(或简称 LSTM)的特定类型的 RNN 可能是最广泛使用的 RNN,因为其精心设计克服了在序列数据上训练稳定的 RNN 的一般困难。 + +LSTM 在大规模训练手写识别,语言建模和机器翻译等任务时已证明对挑战序列预测问题有效。 + +LSTM 模型中的一个层由特殊单元组成,这些单元具有控制输入,输出和循环连接的门,其权重是学习的。每个 LSTM 单元还具有内部存储器或状态,当读取输入序列时,该内部存储器或状态被累积,并且可以由网络用作一种局部变量或存储器寄存器。 + +有关长期短期内存网络的更多信息,请参阅帖子: + +* [深度学习的循环神经网络崩溃课程](https://machinelearningmastery.com/crash-course-recurrent-neural-networks-deep-learning/) + +与可以读取输入序列的 CNN 一样,LSTM 读取输入观察序列并开发其自己的输入序列的内部表示。与 CNN 不同,LSTM 的训练方式应特别注意观察结果和输入序列中时间步长的预测误差,称为反向传播。 + +有关随时间反向传播的更多信息,请参阅帖子: + +* [对时间反向传播的温和介绍](https://machinelearningmastery.com/gentle-introduction-backpropagation-time/) + +LSTM 可以应用于人类活动识别的问题。 + +LSTM 学习将传感器数据的每个窗口映射到活动,其中一次一个地读取输入序列中的观察,其中每个时间步骤可以包括一个或多个变量(例如,并行序列)。 + +简单的 LSTM 模型在 HAR 问题上的应用有限。 + +其中一个例子是 Abdulmajid Murad 和 Jae-Young Pyun 2017 年题为“[用于人类活动识别的深度循环神经网络](http://www.mdpi.com/1424-8220/17/11/2556)”的论文。 + +重要的是,在论文中,他们评论了 CNN 在传感器数据的固定大小窗口上操作的要求的限制,这是 LSTM 并不严格限制的。 + +> 但是,卷积内核的大小限制了捕获的数据样本之间的依赖关系范围。因此,典型的模型不适用于各种活动识别配置,并且需要固定长度的输入窗口。 + +- [用于人类活动识别的深度循环神经网络](http://www.mdpi.com/1424-8220/17/11/2556),2017 年。 + +他们探讨了 LSTM 的使用,它们既向前(正常)又向两个方向处理序列数据(双向 LSTM)。有趣的是,LSTM 预测传感器数据的子序列的每个输入时间步的活动,然后聚合这些活动以便预测窗口的活动。 + +> 每个时间步骤将有一个分数来预测在时间 t 发生的活动类型。通过将各个得分合并为单个预测来获得对整个窗口 T 的预测 + +- [用于人类活动识别的深度循环神经网络](http://www.mdpi.com/1424-8220/17/11/2556),2017 年。 + +下面的图表提供了 LSTM 模型的描述,后面是完全连接的层,用于解释原始传感器数据的内部表示。 + +![Depiction of LSTM RNN for Activity Recognition](img/7c90b7f5d31971ace6f72457f7ab296f.jpg) + +用于活动识别的 LSTM RNN 的描述 +取自“用于人类活动识别的深度循环神经网络”。 + +在 CNN-LSTM 模型或 ConvLSTM 模型中,在 HAR 问题上结合 CNN 使用 LSTM 可能更常见。 + +这是使用 CNN 模型从原始样本数据的子序列中提取特征的地方,然后由 LSTM 聚合解释来自 CNN 的每个子序列的输出特征。 + +这方面的一个例子是 Francisco Javier Ordonez 和 Daniel Roggen 的 2016 年论文题为“[用于多模式可穿戴活动识别的深度卷积和 LSTM 循环神经网络](http://www.mdpi.com/1424-8220/16/1/115/html)”。 + +> 我们为可穿戴活动识别引入了一个新的 DNN 框架,我们将其称为 DeepConvLSTM。该架构结合了卷积层和循环层。卷积层充当特征提取器,并在特征映射中提供输入传感器数据的抽象表示。循环层模拟特征图激活的时间动态。 + +- [用于多模式可穿戴活动识别的深度卷积和 LSTM 循环神经网络](http://www.mdpi.com/1424-8220/16/1/115/html),2016。 + +深度网络体系结构与四个卷积层一起使用,没有任何池化层,接着是两个 LSTM 层,用于在多个时间步骤中解释提取的特征。 + +作者声称,移除池化层是其模型体系结构的关键部分,其中在卷积层之后使用池化层会干扰卷积层学习对原始传感器数据进行下采样的能力。 + +> 在文献中,CNN 框架通常连续地包括卷积和汇集层,作为降低数据复杂性和引入平移不变特征的措施。然而,这种方法并不是架构的严格组成部分,并且在时间序列域中,DeepConvLSTM 不包括池化操作,因为网络的输入受到滑动窗口机制的约束[...],这一事实限制了可能性假设 DeepConvLSTM 需要由循环层处理数据序列,则对数据进行下采样。然而,在没有滑动窗口要求的情况下,池化机制可用于覆盖更深层的不同传感器数据时间尺度。 + +- [用于多模式可穿戴活动识别的深度卷积和 LSTM 循环神经网络](http://www.mdpi.com/1424-8220/16/1/115/html),2016。 + +下图取自纸张,使架构更清晰。请注意,图像中的层 6 和 7 实际上是 LSTM 层。 + +![Depiction of CNN LSTM Model for Activity Recognition](img/11bcc530e80a517db8f987595cad3d50.jpg) + +用于活动识别的 CNN LSTM 模型的描述 +取自“用于多模式可穿戴活动识别的深度卷积和 LSTM 循环神经网络”。 + +## 进一步阅读 + +如果您希望深入了解,本节将提供有关该主题的更多资源。 + +### 一般 + +* [基于传感器的活动识别深度学习:调查](https://www.sciencedirect.com/science/article/pii/S016786551830045X),2018。 + +### 滑动窗户 + +* [用于活动识别的动态滑动窗口方法](https://link.springer.com/chapter/10.1007/978-3-642-22362-4_19),2011。 +* [人类活动识别中的窗口大小影响](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4029702/),2014 年。 + +### 细胞神经网络 + +* [使用移动传感器进行人类活动识别的卷积神经网络](https://ieeexplore.ieee.org/document/7026300/),2014 年。 +* [基于划分和征服的 1D CNN 人类活动识别使用测试数据锐化](http://www.mdpi.com/1424-8220/18/4/1055),2018。 +* [通过深度卷积神经网络使用可穿戴传感器进行人类活动识别](https://dl.acm.org/citation.cfm?id=2806333),2015 年。 +* [使用深度学习神经网络的智能手机传感器识别人类活动](https://www.sciencedirect.com/science/article/pii/S0957417416302056),2016 年。 + +### RNNs + +* [用于人类活动识别的深度循环神经网络](http://www.mdpi.com/1424-8220/17/11/2556),2017 年。 +* [用于多模式可穿戴活动识别的深度卷积和 LSTM 循环神经网络](http://www.mdpi.com/1424-8220/16/1/115/html),2016。 + +## 摘要 + +在这篇文章中,您发现了人类活动识别的问题以及深度学习方法的使用,这些方法在这个问题上实现了最先进的表现。 + +具体来说,你学到了: + +* 活动识别是基于传感器数据(例如智能手机中的加速度计)预测人(通常在室内)的移动的问题。 +* 传感器数据流通常被分成称为窗口的子序列,并且每个窗口与更广泛的活动相关联,称为滑动窗口方法。 +* 卷积神经网络和长期短期记忆网络,或许两者结合在一起,最适合从原始传感器数据中学习特征并预测相关的运动。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/evaluate-machine-learning-algorithms-for-human-activity-recognition.md b/docs/dl-ts/evaluate-machine-learning-algorithms-for-human-activity-recognition.md new file mode 100644 index 0000000000000000000000000000000000000000..6c88fe08d34869786c3d0570693e4d743dae91c5 --- /dev/null +++ b/docs/dl-ts/evaluate-machine-learning-algorithms-for-human-activity-recognition.md @@ -0,0 +1,693 @@ +# 如何评估人类活动识别的机器学习算法 + +> 原文: [https://machinelearningmastery.com/evaluate-machine-learning-algorithms-for-human-activity-recognition/](https://machinelearningmastery.com/evaluate-machine-learning-algorithms-for-human-activity-recognition/) + +人类活动识别是将由专用线束或智能电话记录的加速度计数据序列分类为已知的明确定义的运动的问题。 + +该问题的经典方法涉及基于固定大小的窗口和训练机器学习模型(例如决策树的集合)的时间序列数据中的手工制作特征。困难在于此功能工程需要该领域的深厚专业知识。 + +最近,已经证明,诸如循环神经网络和一维卷积神经网络(CNN)之类的深度学习方法可以在很少或没有数据特征工程的情况下提供具有挑战性的活动识别任务的最新结果,而不是使用特征学习原始数据。 + +在本教程中,您将了解如何在“_ 使用智能手机的活动识别 _”数据集上评估各种机器学习算法。 + +完成本教程后,您将了解: + +* 如何在特征设计版本的活动识别数据集上加载和评估非线性和集成机器学习算法。 +* 如何在活动识别数据集的原始信号数据上加载和评估机器学习算法。 +* 如何定义能够进行特征学习的更复杂算法的预期表现的合理上下界,例如深度学习方法。 + +让我们开始吧。 + +![How to Evaluate Machine Learning Algorithms for Human Activity Recognition](img/9f387fe9e2be7d88dcf3a61fb4de878c.jpg) + +如何评估用于人类活动识别的机器学习算法 +照片由 [Murray Foubister](https://www.flickr.com/photos/mfoubister/41564699865/) ,保留一些权利。 + +## 教程概述 + +本教程分为三个部分;他们是: + +1. 使用智能手机数据集进行活动识别 +2. 建模特征工程数据 +3. 建模原始数据 + +## 使用智能手机数据集进行活动识别 + +[人类活动识别](https://en.wikipedia.org/wiki/Activity_recognition),或简称为 HAR,是基于使用传感器的移动痕迹来预测人正在做什么的问题。 + +标准人类活动识别数据集是 2012 年提供的“使用智能手机进行活动识别”数据集。 + +它由 Davide Anguita 等人准备并提供。来自意大利热那亚大学的 2013 年论文“[使用智能手机进行人类活动识别的公共领域数据集](https://upcommons.upc.edu/handle/2117/20897)”中对该数据集进行了全面描述。该数据集在他们的 2012 年论文中用机器学习算法建模,标题为“[使用多类硬件友好支持向量机](https://link.springer.com/chapter/10.1007/978-3-642-35395-6_30)在智能手机上进行人类活动识别。“ + +数据集可用,可以从 UCI 机器学习库免费下载: + +* [使用智能手机数据集进行人类活动识别,UCI 机器学习库](https://archive.ics.uci.edu/ml/datasets/human+activity+recognition+using+smartphones) + +该数据来自 30 名年龄在 19 至 48 岁之间的受试者,其执行六项标准活动中的一项,同时佩戴记录运动数据的腰部智能手机。记录执行活动的每个受试者的视频,并从这些视频手动标记移动数据。 + +以下是在记录其移动数据的同时执行活动的主体的示例视频。 + +<iframe allow="autoplay; encrypted-media" allowfullscreen="" frameborder="0" height="375" src="https://www.youtube.com/embed/XOEN9W05_4A?feature=oembed" width="500"></iframe> + +进行的六项活动如下: + +1. 步行 +2. 走上楼 +3. 走楼下 +4. 坐在 +5. 常设 +6. 铺设 + +记录的运动数据是来自智能手机的 x,y 和 z 加速度计数据(线性加速度)和陀螺仪数据(角速度),特别是三星 Galaxy S II。以 50Hz(即每秒 50 个数据点)记录观察结果。每个受试者进行两次活动,一次是左侧设备,另一次是右侧设备。 + +原始数据不可用。相反,可以使用预处理版本的数据集。预处理步骤包括: + +* 使用噪声滤波器预处理加速度计和陀螺仪。 +* 将数据拆分为 2.56 秒(128 个数据点)的固定窗口,重叠率为 50%。 +* 将加速度计数据分割为重力(总)和身体运动分量。 + +特征工程应用于窗口数据,并且提供具有这些工程特征的数据的副本。 + +从每个窗口提取在人类活动识别领域中常用的许多时间和频率特征。结果是 561 元素的特征向量。 + +根据受试者的数据,将数据集分成训练(70%)和测试(30%)组。列车 21 个,测试 9 个。 + +使用旨在用于智能手机的支持向量机(例如定点算术)的实验结果导致测试数据集的预测准确度为 89%,实现与未修改的 SVM 实现类似的结果。 + +该数据集是免费提供的,可以从 UCI 机器学习库下载。 + +数据以单个 zip 文件的形式提供,大小约为 58 兆字节。此下载的直接链接如下: + +* [UCI HAR Dataset.zip](https://archive.ics.uci.edu/ml/machine-learning-databases/00240/UCI%20HAR%20Dataset.zip) + +下载数据集并将所有文件解压缩到当前工作目录中名为“HARDataset”的新目录中。 + +## 建模特征工程数据 + +在本节中,我们将开发代码来加载数据集的特征工程版本并评估一套非线性机器学习算法,包括原始论文中使用的 SVM。 + +目标是在测试数据集上实现至少 89%的准确性。 + +使用特征工程版数据集的方法的结果为为原始数据版本开发的任何方法提供了基线。 + +本节分为五个部分;他们是: + +* 加载数据集 +* 定义模型 +* 评估模型 +* 总结结果 +* 完整的例子 + +### 加载数据集 + +第一步是加载列车并测试输入(X)和输出(y)数据。 + +具体来说,以下文件: + +* _HARDataset / train / X_train.txt_ +* _HARDataset / train / y_train.txt_ +* _HARDataset / test / X_test.txt_ +* _HARDataset / test / y_test.txt_ + +输入数据采用 CSV 格式,其中列通过空格分隔。这些文件中的每一个都可以作为 NumPy 数组加载。 + +下面的 _load_file()_ 函数在给定文件的文件路径的情况下加载数据集,并将加载的数据作为 NumPy 数组返回。 + +``` +# load a single file as a numpy array +def load_file(filepath): + dataframe = read_csv(filepath, header=None, delim_whitespace=True) + return dataframe.values +``` + +考虑到目录布局和文件名的相似性,我们可以调用此函数来加载给定列车或测试集组的 _X_ 和 _y_ 文件。下面的 _load_dataset_group()_ 函数将为一个组加载这两个文件,并将 X 和 y 元素作为 NumPy 数组返回。然后,此函数可用于加载列车和测试组的 X 和 y 元素。 + +``` +# load a dataset group, such as train or test +def load_dataset_group(group, prefix=''): + # load input data + X = load_file(prefix + group + '/X_'+group+'.txt') + # load class output + y = load_file(prefix + group + '/y_'+group+'.txt') + return X, y +``` + +最后,我们可以加载 train 和 test 数据集,并将它们作为 NumPy 数组返回,以便为拟合和评估机器学习模型做好准备。 + +``` +# load the dataset, returns train and test X and y elements +def load_dataset(prefix=''): + # load all train + trainX, trainy = load_dataset_group('train', prefix + 'HARDataset/') + print(trainX.shape, trainy.shape) + # load all test + testX, testy = load_dataset_group('test', prefix + 'HARDataset/') + print(testX.shape, testy.shape) + # flatten y + trainy, testy = trainy[:,0], testy[:,0] + print(trainX.shape, trainy.shape, testX.shape, testy.shape) + return trainX, trainy, testX, testy +``` + +我们可以调用这个函数来加载所有需要的数据;例如: + +``` +# load dataset +trainX, trainy, testX, testy = load_dataset() +``` + +### 定义模型 + +接下来,我们可以定义一个机器学习模型列表来评估这个问题。 + +我们将使用默认配置评估模型。我们目前不是在寻找这些模型的最佳配置,只是对具有默认配置的复杂模型在这个问题上表现如何的一般概念。 + +我们将评估一组不同的非线性和集成机器学习算法,具体来说: + +非线性算法: + +* k-最近邻居 +* 分类和回归树 +* 支持向量机 +* 朴素贝叶斯 + +集合算法: + +* 袋装决策树 +* 随机森林 +* 额外的树木 +* 梯度增压机 + +我们将定义模型并将它们存储在字典中,该字典将模型对象映射到有助于分析结果的简短名称。 + +下面的 _define_models()_ 函数定义了我们将评估的八个模型。 + +``` +# create a dict of standard models to evaluate {name:object} +def define_models(models=dict()): + # nonlinear models + models['knn'] = KNeighborsClassifier(n_neighbors=7) + models['cart'] = DecisionTreeClassifier() + models['svm'] = SVC() + models['bayes'] = GaussianNB() + # ensemble models + models['bag'] = BaggingClassifier(n_estimators=100) + models['rf'] = RandomForestClassifier(n_estimators=100) + models['et'] = ExtraTreesClassifier(n_estimators=100) + models['gbm'] = GradientBoostingClassifier(n_estimators=100) + print('Defined %d models' % len(models)) + return models +``` + +此功能非常易于扩展,您可以轻松更新以定义您希望的任何机器学习模型或模型配置。 + +### 评估模型 + +下一步是评估加载的数据集中定义的模型。 + +该步骤分为单个模型的评估和所有模型的评估。 + +我们将通过首先将其拟合到训练数据集上,对测试数据集进行预测,然后使用度量来评估预测来评估单个模型。在这种情况下,我们将使用分类精度来捕获模型的表现(或误差),给出六个活动(或类)的平衡观察。 + +下面的 _evaluate_model()_ 函数实现了此行为,评估给定模型并将分类精度返回为百分比。 + +``` +# evaluate a single model +def evaluate_model(trainX, trainy, testX, testy, model): + # fit the model + model.fit(trainX, trainy) + # make predictions + yhat = model.predict(testX) + # evaluate predictions + accuracy = accuracy_score(testy, yhat) + return accuracy * 100.0 +``` + +我们现在可以为每个定义的模型重复调用 _evaluate_model()_ 函数。 + +下面的 _evaluate_models()_ 函数实现此行为,获取已定义模型的字典,并返回映射到其分类精度的模型名称字典。 + +因为模型的评估可能需要几分钟,所以该函数在评估每个模型作为一些详细的反馈之后打印它们的表现。 + +``` +# evaluate a dict of models {name:object}, returns {name:score} +def evaluate_models(trainX, trainy, testX, testy, models): + results = dict() + for name, model in models.items(): + # evaluate the model + results[name] = evaluate_model(trainX, trainy, testX, testy, model) + # show process + print('>%s: %.3f' % (name, results[name])) + return results +``` + +### 总结结果 + +最后一步是总结研究结果。 + +我们可以按降序排列分类准确度对所有结果进行排序,因为我们对最大化准确性感兴趣。 + +然后可以打印评估模型的结果,清楚地显示每个评估模型的相对等级。 + +下面的 _summarize_results()_ 函数实现了这种行为。 + +``` +# print and plot the results +def summarize_results(results, maximize=True): + # create a list of (name, mean(scores)) tuples + mean_scores = [(k,v) for k,v in results.items()] + # sort tuples by mean score + mean_scores = sorted(mean_scores, key=lambda x: x[1]) + # reverse for descending order (e.g. for accuracy) + if maximize: + mean_scores = list(reversed(mean_scores)) + print() + for name, score in mean_scores: + print('Name=%s, Score=%.3f' % (name, score)) +``` + +### 完整的例子 + +我们知道我们已经完成了所有工作。 + +下面列出了在数据集的特征工程版本上评估一套八个机器学习模型的完整示例。 + +``` +# spot check on engineered-features +from pandas import read_csv +from sklearn.metrics import accuracy_score +from sklearn.neighbors import KNeighborsClassifier +from sklearn.tree import DecisionTreeClassifier +from sklearn.svm import SVC +from sklearn.naive_bayes import GaussianNB +from sklearn.ensemble import BaggingClassifier +from sklearn.ensemble import RandomForestClassifier +from sklearn.ensemble import ExtraTreesClassifier +from sklearn.ensemble import GradientBoostingClassifier + +# load a single file as a numpy array +def load_file(filepath): + dataframe = read_csv(filepath, header=None, delim_whitespace=True) + return dataframe.values + +# load a dataset group, such as train or test +def load_dataset_group(group, prefix=''): + # load input data + X = load_file(prefix + group + '/X_'+group+'.txt') + # load class output + y = load_file(prefix + group + '/y_'+group+'.txt') + return X, y + +# load the dataset, returns train and test X and y elements +def load_dataset(prefix=''): + # load all train + trainX, trainy = load_dataset_group('train', prefix + 'HARDataset/') + print(trainX.shape, trainy.shape) + # load all test + testX, testy = load_dataset_group('test', prefix + 'HARDataset/') + print(testX.shape, testy.shape) + # flatten y + trainy, testy = trainy[:,0], testy[:,0] + print(trainX.shape, trainy.shape, testX.shape, testy.shape) + return trainX, trainy, testX, testy + +# create a dict of standard models to evaluate {name:object} +def define_models(models=dict()): + # nonlinear models + models['knn'] = KNeighborsClassifier(n_neighbors=7) + models['cart'] = DecisionTreeClassifier() + models['svm'] = SVC() + models['bayes'] = GaussianNB() + # ensemble models + models['bag'] = BaggingClassifier(n_estimators=100) + models['rf'] = RandomForestClassifier(n_estimators=100) + models['et'] = ExtraTreesClassifier(n_estimators=100) + models['gbm'] = GradientBoostingClassifier(n_estimators=100) + print('Defined %d models' % len(models)) + return models + +# evaluate a single model +def evaluate_model(trainX, trainy, testX, testy, model): + # fit the model + model.fit(trainX, trainy) + # make predictions + yhat = model.predict(testX) + # evaluate predictions + accuracy = accuracy_score(testy, yhat) + return accuracy * 100.0 + +# evaluate a dict of models {name:object}, returns {name:score} +def evaluate_models(trainX, trainy, testX, testy, models): + results = dict() + for name, model in models.items(): + # evaluate the model + results[name] = evaluate_model(trainX, trainy, testX, testy, model) + # show process + print('>%s: %.3f' % (name, results[name])) + return results + +# print and plot the results +def summarize_results(results, maximize=True): + # create a list of (name, mean(scores)) tuples + mean_scores = [(k,v) for k,v in results.items()] + # sort tuples by mean score + mean_scores = sorted(mean_scores, key=lambda x: x[1]) + # reverse for descending order (e.g. for accuracy) + if maximize: + mean_scores = list(reversed(mean_scores)) + print() + for name, score in mean_scores: + print('Name=%s, Score=%.3f' % (name, score)) + +# load dataset +trainX, trainy, testX, testy = load_dataset() +# get model list +models = define_models() +# evaluate models +results = evaluate_models(trainX, trainy, testX, testy, models) +# summarize results +summarize_results(results) +``` + +运行该示例首先加载列车和测试数据集,显示每个输入和输出组件的形状。 + +然后依次评估八个模型,打印每个模型的表现。 + +最后,显示模型在测试集上的表现等级。 + +我们可以看到,ExtraTrees 集合方法和支持向量机非线性方法在测试集上实现了大约 94%的准确性。 + +这是一个很好的结果,超过原始论文中 SVM 报告的 89%。 + +考虑到算法的随机性,每次运行代码时,具体结果可能会有所不同。然而,考虑到数据集的大小,算法表现之间的相对关系应该相当稳定。 + +``` +(7352, 561) (7352, 1) +(2947, 561) (2947, 1) +(7352, 561) (7352,) (2947, 561) (2947,) +Defined 8 models +>knn: 90.329 +>cart: 86.020 +>svm: 94.028 +>bayes: 77.027 +>bag: 89.820 +>rf: 92.772 +>et: 94.028 +>gbm: 93.756 + +Name=et, Score=94.028 +Name=svm, Score=94.028 +Name=gbm, Score=93.756 +Name=rf, Score=92.772 +Name=knn, Score=90.329 +Name=bag, Score=89.820 +Name=cart, Score=86.020 +Name=bayes, Score=77.027 +``` + +这些结果显示了在准备数据和领域特定功能的工程中给定的领域专业知识的可能性。因此,这些结果可以作为通过更先进的方法可以追求的表现的上限,这些方法可以自动学习特征作为拟合模型的一部分,例如深度学习方法。 + +任何这样的先进方法都适合并评估从中得到工程特征的原始数据。因此,直接评估该数据的机器学习算法的表现可以提供任何更高级方法的表现的预期下限。 + +我们将在下一节中探讨这一点。 + +## 建模原始数据 + +我们可以使用相同的框架来评估原始数据上的机器学习模型。 + +原始数据确实需要更多工作才能加载。 + +原始数据中有三种主要信号类型:总加速度,车身加速度和车身陀螺仪。每个都有三个数据轴。这意味着每个时间步长总共有九个变量。 + +此外,每个数据系列已被划分为 2.65 秒数据的重叠窗口,或 128 个时间步长。这些数据窗口对应于上一节中工程特征(行)的窗口。 + +这意味着一行数据具有 128 * 9 或 1,152 个元素。这比前一节中 561 个元素向量的大小小一倍,并且可能存在一些冗余数据。 + +信号存储在列车和测试子目录下的 _/ Inertial Signals /_ 目录中。每个信号的每个轴都存储在一个单独的文件中,这意味着每个列车和测试数据集都有九个要加载的输入文件和一个要加载的输出文件。在给定一致的目录结构和文件命名约定的情况下,我们可以批量加载这些文件。 + +首先,我们可以将给定组的所有数据加载到单个三维 NumPy 数组中,其中数组的维数为[样本,时间步长,特征]。为了更清楚,有 128 个时间步和 9 个特征,其中样本数是任何给定原始信号数据文件中的行数。 + +下面的 _load_group()_ 函数实现了这种行为。 _dstack()_ NumPy 函数允许我们将每个加载的 3D 数组堆叠成单个 3D 数组,其中变量在第三维(特征)上分开。 + +``` +# load a list of files into a 3D array of [samples, timesteps, features] +def load_group(filenames, prefix=''): + loaded = list() + for name in filenames: + data = load_file(prefix + name) + loaded.append(data) + # stack group so that features are the 3rd dimension + loaded = dstack(loaded) + return loaded +``` + +我们可以使用此功能加载给定组的所有输入信号数据,例如火车或测试。 + +下面的 _load_dataset_group()_ 函数使用目录之间的一致命名约定加载单个组的所有输入信号数据和输出数据。 + +``` +# load a dataset group, such as train or test +def load_dataset_group(group, prefix=''): + filepath = prefix + group + '/Inertial Signals/' + # load all 9 files as a single array + filenames = list() + # total acceleration + filenames += ['total_acc_x_'+group+'.txt', 'total_acc_y_'+group+'.txt', 'total_acc_z_'+group+'.txt'] + # body acceleration + filenames += ['body_acc_x_'+group+'.txt', 'body_acc_y_'+group+'.txt', 'body_acc_z_'+group+'.txt'] + # body gyroscope + filenames += ['body_gyro_x_'+group+'.txt', 'body_gyro_y_'+group+'.txt', 'body_gyro_z_'+group+'.txt'] + # load input data + X = load_group(filenames, filepath) + # load class output + y = load_file(prefix + group + '/y_'+group+'.txt') + return X, y +``` + +最后,我们可以加载每个列车和测试数据集。 + +作为准备加载数据的一部分,我们必须将窗口和特征展平为一个长向量。 + +我们可以使用 NumPy 重塑功能执行此操作,并将[_ 样本,时间步长,特征 _]的三个维度转换为[_ 样本,时间步长*特征 _]的两个维度。 + +下面的 _load_dataset()_ 函数实现了这种行为,并返回列车并测试 _X_ 和 _y_ 元素,以便拟合和评估定义的模型。 + +``` +# load the dataset, returns train and test X and y elements +def load_dataset(prefix=''): + # load all train + trainX, trainy = load_dataset_group('train', prefix + 'HARDataset/') + print(trainX.shape, trainy.shape) + # load all test + testX, testy = load_dataset_group('test', prefix + 'HARDataset/') + print(testX.shape, testy.shape) + # flatten X + trainX = trainX.reshape((trainX.shape[0], trainX.shape[1] * trainX.shape[2])) + testX = testX.reshape((testX.shape[0], testX.shape[1] * testX.shape[2])) + # flatten y + trainy, testy = trainy[:,0], testy[:,0] + print(trainX.shape, trainy.shape, testX.shape, testy.shape) + return trainX, trainy, testX, testy +``` + +综合这些,下面列出了完整的例子。 + +``` +# spot check on raw data +from numpy import dstack +from pandas import read_csv +from sklearn.metrics import accuracy_score +from sklearn.neighbors import KNeighborsClassifier +from sklearn.tree import DecisionTreeClassifier +from sklearn.svm import SVC +from sklearn.naive_bayes import GaussianNB +from sklearn.ensemble import BaggingClassifier +from sklearn.ensemble import RandomForestClassifier +from sklearn.ensemble import ExtraTreesClassifier +from sklearn.ensemble import GradientBoostingClassifier + +# load a single file as a numpy array +def load_file(filepath): + dataframe = read_csv(filepath, header=None, delim_whitespace=True) + return dataframe.values + +# load a list of files into a 3D array of [samples, timesteps, features] +def load_group(filenames, prefix=''): + loaded = list() + for name in filenames: + data = load_file(prefix + name) + loaded.append(data) + # stack group so that features are the 3rd dimension + loaded = dstack(loaded) + return loaded + +# load a dataset group, such as train or test +def load_dataset_group(group, prefix=''): + filepath = prefix + group + '/Inertial Signals/' + # load all 9 files as a single array + filenames = list() + # total acceleration + filenames += ['total_acc_x_'+group+'.txt', 'total_acc_y_'+group+'.txt', 'total_acc_z_'+group+'.txt'] + # body acceleration + filenames += ['body_acc_x_'+group+'.txt', 'body_acc_y_'+group+'.txt', 'body_acc_z_'+group+'.txt'] + # body gyroscope + filenames += ['body_gyro_x_'+group+'.txt', 'body_gyro_y_'+group+'.txt', 'body_gyro_z_'+group+'.txt'] + # load input data + X = load_group(filenames, filepath) + # load class output + y = load_file(prefix + group + '/y_'+group+'.txt') + return X, y + +# load the dataset, returns train and test X and y elements +def load_dataset(prefix=''): + # load all train + trainX, trainy = load_dataset_group('train', prefix + 'HARDataset/') + print(trainX.shape, trainy.shape) + # load all test + testX, testy = load_dataset_group('test', prefix + 'HARDataset/') + print(testX.shape, testy.shape) + # flatten X + trainX = trainX.reshape((trainX.shape[0], trainX.shape[1] * trainX.shape[2])) + testX = testX.reshape((testX.shape[0], testX.shape[1] * testX.shape[2])) + # flatten y + trainy, testy = trainy[:,0], testy[:,0] + print(trainX.shape, trainy.shape, testX.shape, testy.shape) + return trainX, trainy, testX, testy + +# create a dict of standard models to evaluate {name:object} +def define_models(models=dict()): + # nonlinear models + models['knn'] = KNeighborsClassifier(n_neighbors=7) + models['cart'] = DecisionTreeClassifier() + models['svm'] = SVC() + models['bayes'] = GaussianNB() + # ensemble models + models['bag'] = BaggingClassifier(n_estimators=100) + models['rf'] = RandomForestClassifier(n_estimators=100) + models['et'] = ExtraTreesClassifier(n_estimators=100) + models['gbm'] = GradientBoostingClassifier(n_estimators=100) + print('Defined %d models' % len(models)) + return models + +# evaluate a single model +def evaluate_model(trainX, trainy, testX, testy, model): + # fit the model + model.fit(trainX, trainy) + # make predictions + yhat = model.predict(testX) + # evaluate predictions + accuracy = accuracy_score(testy, yhat) + return accuracy * 100.0 + +# evaluate a dict of models {name:object}, returns {name:score} +def evaluate_models(trainX, trainy, testX, testy, models): + results = dict() + for name, model in models.items(): + # evaluate the model + results[name] = evaluate_model(trainX, trainy, testX, testy, model) + # show process + print('>%s: %.3f' % (name, results[name])) + return results + +# print and plot the results +def summarize_results(results, maximize=True): + # create a list of (name, mean(scores)) tuples + mean_scores = [(k,v) for k,v in results.items()] + # sort tuples by mean score + mean_scores = sorted(mean_scores, key=lambda x: x[1]) + # reverse for descending order (e.g. for accuracy) + if maximize: + mean_scores = list(reversed(mean_scores)) + print() + for name, score in mean_scores: + print('Name=%s, Score=%.3f' % (name, score)) + +# load dataset +trainX, trainy, testX, testy = load_dataset() +# get model list +models = define_models() +# evaluate models +results = evaluate_models(trainX, trainy, testX, testy, models) +# summarize results +summarize_results(results) +``` + +首先运行该示例加载数据集。 + +我们可以看到原始序列和测试集具有与工程特征(分别为 7352 和 2947)相同数量的样本,并且正确加载了三维数据。我们还可以看到平面数据和将提供给模型的 1152 输入向量。 + +接下来依次评估八个定义的模型。 + +最终结果表明,决策树的集合在原始数据上表现最佳。梯度增强和额外树木以最高 87%和 86%的准确度表现最佳,比数据集的特征工程版本中表现最佳的模型低约 7 个点。 + +令人鼓舞的是,Extra Trees 集合方法在两个数据集上都表现良好;它表明它和类似的树集合方法可能适合这个问题,至少在这个简化的框架中。 + +我们还可以看到 SVM 下降到约 72%的准确度。 + +决策树集合的良好表现可能表明需要特征选择和集合方法能够选择与预测相关活动最相关的那些特征。 + +``` +(7352, 128, 9) (7352, 1) +(2947, 128, 9) (2947, 1) +(7352, 1152) (7352,) (2947, 1152) (2947,) +Defined 8 models +>knn: 61.893 +>cart: 72.141 +>svm: 76.960 +>bayes: 72.480 +>bag: 84.527 +>rf: 84.662 +>et: 86.902 +>gbm: 87.615 + +Name=gbm, Score=87.615 +Name=et, Score=86.902 +Name=rf, Score=84.662 +Name=bag, Score=84.527 +Name=svm, Score=76.960 +Name=bayes, Score=72.480 +Name=cart, Score=72.141 +Name=knn, Score=61.893 +``` + +如前一节所述,这些结果为可能尝试从原始数据自动学习更高阶特征(例如,通过深度学习方法中的特征学习)的任何更复杂的方法提供了精确度的下限。 + +总之,此类方法的界限在原始数据上从 GBM 的约 87%准确度扩展到高度处理的数据集上的额外树和 SVM 的约 94%,[87%至 94%]。 + +## 扩展 + +本节列出了一些扩展您可能希望探索的教程的想法。 + +* **更多算法**。在这个问题上只评估了八种机器学习算法;尝试一些线性方法,也许还有一些非线性和集合方法。 +* **算法调整**。没有调整机器学习算法;主要使用默认配置。选择一种方法,例如 SVM,ExtraTrees 或 Gradient Boosting,并搜索一组不同的超参数配置,以查看是否可以进一步提升问题的表现。 +* **数据缩放**。数据已经按比例缩放到[-1,1]。探索额外的扩展(例如标准化)是否可以带来更好的表现,可能是对这种扩展敏感的方法(如 kNN)。 + +如果你探索任何这些扩展,我很想知道。 + +## 进一步阅读 + +如果您希望深入了解,本节将提供有关该主题的更多资源。 + +### 文件 + +* [使用智能手机进行人类活动识别的公共领域数据集](https://upcommons.upc.edu/handle/2117/20897),2013 年。 +* [智能手机上的人类活动识别使用多类硬件友好支持向量机](https://link.springer.com/chapter/10.1007/978-3-642-35395-6_30),2012。 + +### 用品 + +* [使用智能手机数据集进行人类活动识别,UCI 机器学习库](https://archive.ics.uci.edu/ml/datasets/human+activity+recognition+using+smartphones) +* [活动识别,维基百科](https://en.wikipedia.org/wiki/Activity_recognition) +* [使用智能手机传感器的活动识别实验,视频](https://www.youtube.com/watch?v=XOEN9W05_4A)。 + +## 摘要 + +在本教程中,您了解了如何在'_ 使用智能手机的活动识别 _'数据集上评估各种机器学习算法。 + +具体来说,你学到了: + +* 如何在特征设计版本的活动识别数据集上加载和评估非线性和集成机器学习算法。 +* 如何在活动识别数据集的原始信号数据上加载和评估机器学习算法。 +* 如何定义能够进行特征学习的更复杂算法的预期表现的合理上下界,例如深度学习方法。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/exploratory-configuration-multilayer-perceptron-network-time-series-forecasting.md b/docs/dl-ts/exploratory-configuration-multilayer-perceptron-network-time-series-forecasting.md new file mode 100644 index 0000000000000000000000000000000000000000..d2a3e78dc06d727cb8c0dc826791a13393414075 --- /dev/null +++ b/docs/dl-ts/exploratory-configuration-multilayer-perceptron-network-time-series-forecasting.md @@ -0,0 +1,761 @@ +# 时间序列预测的多层感知器网络探索性配置 + +> 原文: [https://machinelearningmastery.com/exploratory-configuration-multilayer-perceptron-network-time-series-forecasting/](https://machinelearningmastery.com/exploratory-configuration-multilayer-perceptron-network-time-series-forecasting/) + +当开始使用神经网络的新预测建模项目时,可能会很困难。 + +有很多配置,并没有明确的想法从哪里开始。 + +系统化很重要。您可以打破糟糕的假设并快速磨练有效的配置和可能需要进一步调查的区域。 + +在本教程中,您将了解如何使用多层感知器(MLP)神经网络的探索性配置来为时间序列预测找到良好的首先模型。 + +完成本教程后,您将了解: + +* 如何设计一个强大的实验测试工具来评估 MLP 模型的时间序列预测。 +* 针对不同时期,神经元和滞后配置的系统实验设计。 +* 如何解释结果并使用诊断来了解有关良好表现模型的更多信息。 + +让我们开始吧。 + +* **2017 年 7 月更新**:更改了创建模型的功能,使其更具描述性。 + +![Exploratory Configuration of a Multilayer Perceptron Network for Time Series Forecasting](img/9ed1db54db87c397d09434b8aa65366e.jpg) + +用于时间序列预测的多层感知器网络的探索性配置 +照片由 [Lachlan Donald](https://www.flickr.com/photos/lox/33885582/) 拍摄,保留一些权利。 + +## 教程概述 + +本教程分为 6 个部分。他们是: + +1. 洗发水销售数据集 +2. 实验测试线束 +3. 不同的训练时代 +4. 改变隐藏层神经元 +5. 具有滞后的变隐藏层神经元 +6. 审查结果 + +### 环境 + +本教程假定您已安装 Python SciPy 环境。您可以在此示例中使用 Python 2 或 3。 + +本教程假设您安装了 TensorFlow 或 Theano 后端的 Keras v2.0 或更高版本。 + +本教程还假设您安装了 scikit-learn,Pandas,NumPy 和 Matplotlib。 + +接下来,让我们看看标准时间序列预测问题,我们可以将其用作此实验的上下文。 + +如果您在设置 Python 环境时需要帮助,请参阅以下帖子: + +* [如何使用 Anaconda 设置用于机器学习和深度学习的 Python 环境](http://machinelearningmastery.com/setup-python-environment-machine-learning-deep-learning-anaconda/) + +## 洗发水销售数据集 + +该数据集描述了 3 年期间每月洗发水的销售数量。 + +单位是销售计数,有 36 个观察。原始数据集归功于 Makridakis,Wheelwright 和 Hyndman(1998)。 + +[您可以在此处下载并了解有关数据集的更多信息](https://datamarket.com/data/set/22r0/sales-of-shampoo-over-a-three-year-period)。 + +下面的示例加载并创建已加载数据集的图。 + +``` +# 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](img/646e3de8684355414799cd9964ad1d4f.jpg) + +洗发水销售数据集的线图 + +接下来,我们将看一下实验中使用的模型配置和测试工具。 + +## 实验测试线束 + +本节介绍本教程中使用的测试工具。 + +### 数据拆分 + +我们将 Shampoo Sales 数据集分为两部分:训练和测试集。 + +前两年的数据将用于训练数据集,剩余的一年数据将用于测试集。 + +将使用训练数据集开发模型,并对测试数据集进行预测。 + +测试数据集的持久性预测(天真预测)实现了每月洗发水销售 136.761 的错误。这在测试集上提供了较低的可接受表现限制。 + +### 模型评估 + +将使用滚动预测场景,也称为前进模型验证。 + +测试数据集的每个时间步骤将一次一个地走。将使用模型对时间步长进行预测,然后将获取测试集的实际预期值,并使其可用于下一时间步的预测模型。 + +这模仿了一个真实世界的场景,每个月都会有新的洗发水销售观察结果,并用于下个月的预测。 + +这将通过列车和测试数据集的结构进行模拟。 + +将收集关于测试数据集的所有预测,并计算错误分数以总结模型的技能。将使用均方根误差(RMSE),因为它会对大错误进行处罚,并产生与预测数据相同的分数,即每月洗发水销售额。 + +### 数据准备 + +在我们将 MLP 模型拟合到数据集之前,我们必须转换数据。 + +在拟合模型和进行预测之前,对数据集执行以下三个数据变换。 + +1. **转换时间序列数据,使其静止**。具体而言,滞后= 1 差分以消除数据中的增加趋势。 +2. **将时间序列转换为监督学习问题**。具体而言,将数据组织成输入和输出模式,其中前一时间步的观察被用作预测当前时间步的观察的输入 +3. **将观察结果转换为具有特定比例**。具体而言,将数据重新调整为-1 到 1 之间的值。 + +这些变换在预测时反转,在计算和误差分数之前将它们恢复到原始比例。 + +### MLP 模型 + +我们将使用具有 1 个神经元隐藏层的基础 MLP 模型,对隐藏神经元的整流线性激活函数,以及输出神经元上的线性激活函数。 + +在可能的情况下使用批量大小为 4,并且截断训练数据以确保模式的数量可被 4 整除。在某些情况下,使用批量大小为 2。 + +通常,训练数据集在每个批次或每个时期之后进行混洗,这有助于将训练数据集拟合到分类和回归问题上。所有实验都关闭了改组,因为它似乎可以带来更好的表现。需要更多的研究来确认时间序列预测的结果。 + +使用有效的 ADAM 优化算法和均方误差损失函数来拟合模型。 + +### 实验运行 + +每个实验场景将运行 30 次,并且测试集上的 RMSE 得分将从每次运行结束时记录。 + +让我们深入研究实验。 + +## 不同的训练时代 + +在第一个实验中,我们将研究改变隐藏层中具有一个隐藏层和一个神经元的简单 MLP 的训练时期的数量。 + +我们将使用批量大小为 4 并评估训练时期 50,100,500,1000 和 2000。 + +完整的代码清单如下。 + +此代码清单将用作所有后续实验的基础,后续部分中仅提供对此代码的更改。 + +``` +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 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 MLP network to training data +def fit_model(train, batch_size, nb_epoch, neurons): + X, y = train[:, 0:-1], train[:, -1] + model = Sequential() + model.add(Dense(neurons, activation='relu', input_dim=X.shape[1])) + model.add(Dense(1)) + model.compile(loss='mean_squared_error', optimizer='adam') + model.fit(X, y, epochs=nb_epoch, batch_size=batch_size, verbose=0, shuffle=False) + return model + +# run a repeated experiment +def experiment(repeats, series, epochs, lag, 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, lag) + supervised_values = supervised.values[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(repeats): + # fit the model + batch_size = 4 + train_trimmed = train_scaled[2:, :] + model = fit_model(train_trimmed, batch_size, epochs, neurons) + # forecast test dataset + test_reshaped = test_scaled[:,0:-1] + output = 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() +lag = 1 +neurons = 1 +# vary training epochs +epochs = [50, 100, 500, 1000, 2000] +for e in epochs: + results[str(e)] = experiment(repeats, series, e, lag, neurons) +# summarize results +print(results.describe()) +# save boxplot +results.boxplot() +pyplot.savefig('boxplot_epochs.png') +``` + +运行实验在每次实验运行结束时打印测试集 RMSE。 + +在所有运行结束时,提供了一个摘要统计表,每个统计信息对应一行,每列有一个配置。 + +总结统计表明,平均 1000 个训练时期导致更好的表现,随着训练时期的增加,误差总体下降趋势。 + +``` + 50 100 500 1000 2000 +count 30.000000 30.000000 30.000000 30.000000 30.000000 +mean 129.660167 129.388944 111.444027 103.821703 107.500301 +std 30.926344 28.499592 23.181317 22.138705 24.780781 +min 94.598957 94.184903 89.506815 86.511801 86.452041 +25% 105.198414 105.722736 90.679930 90.058655 86.457260 +50% 129.705407 127.449491 93.508245 90.118331 90.074494 +75% 141.420145 149.625816 136.157299 135.510850 135.741340 +max 198.716220 198.704352 141.226816 139.994388 142.097747 +``` + +还创建了每个配置的测试 RMSE 分数分布的盒子和须状图,并保存到文件中。 + +该图强调了每个配置在测试 RMSE 分数(框)中显示相同的一般分布,中位数(绿线)随着训练时期的增加而向下趋势。 + +结果证实,配置的 MLP 训练 1000 是这个问题的一个很好的起点。 + +![Box and Whisker Plot of Vary Training Epochs for Time Series Forecasting on the Shampoo Sales Dataset](img/172142dd5af355a685628ce26804923d.jpg) + +用于洗发水销售数据集的时间序列预测的变化训练时期的盒子和晶须图 + +网络配置需要考虑的另一个角度是模型适应时的行为方式。 + +我们可以在每个训练时期之后评估训练和测试数据集上的模型,以了解配置是否过度拟合或不适合问题。 + +我们将在每组实验的最佳结果上使用此诊断方法。将运行总共 10 次重复的配置,并且在线图上绘制每个训练迭代之后的训练和测试 RMSE 得分。 + +在这种情况下,我们将在 MLP 适合 1000 个时期使用此诊断。 + +完整的诊断代码清单如下。 + +与前面的代码清单一样,下面的代码清单将用作本教程中所有诊断的基础,并且后续部分中仅提供对此清单的更改。 + +``` +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 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] + # forecast dataset + output = model.predict(X, 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 MLP network to training data +def fit(train, test, raw, scaler, batch_size, nb_epoch, neurons): + X, y = train[:, 0:-1], train[:, -1] + # prepare model + model = Sequential() + model.add(Dense(neurons, activation='relu', input_dim=X.shape[1])) + 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) + # 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 + repeats = 10 + n_batch = 4 + n_epochs = 1000 + n_neurons = 1 + n_lag = 1 + # 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(repeats): + history = fit(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('diagnostic_epochs.png') + +# entry point +run() +``` + +运行诊断程序打印最终列车并测试每次运行的 RMSE。更有趣的是创建的最终线图。 + +线图显示了每个训练时期之后的列车 RMSE(蓝色)和测试 RMSE(橙色)。 + +在这种情况下,诊断图显示在大约 400 个训练时期之后训练和测试 RMSE 几乎没有差异。火车和测试表现均接近平坦线。 + +这种快速平衡表明模型正在达到容量,并且可以从滞后观察或额外神经元方面的更多信息中受益。 + +![Diagnostic Line Plot of Train and Test Performance of 400 Epochs on the Shampoo Sales Dataset](img/95474940827213c21c63294561b2a74d.jpg) + +火车诊断线图和洗发水销售数据集 1000 个时期的测试表现 + +## 改变隐藏层神经元 + +在本节中,我们将研究改变单个隐藏层中神经元的数量。 + +增加神经元的数量会增加网络的学习能力,从而存在过度拟合训练数据的风险。 + +我们将探索将神经元的数量从 1 增加到 5 并使网络适合 1000 个时期。 + +下面列出了实验脚本的不同之处。 + +``` +# 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() +lag = 1 +epochs = 1000 +# vary neurons +neurons = [1, 2, 3, 4, 5] +for n in neurons: + results[str(n)] = experiment(repeats, series, epochs, lag, n) +# summarize results +print(results.describe()) +# save boxplot +results.boxplot() +pyplot.savefig('boxplot_neurons.png') +``` + +运行实验将打印每个配置的摘要统计信息。 + +从平均表现来看,它表明测试 RMSE 随着单个隐藏层中神经元数量的增加而减少。 + +最好的结果似乎是 3 个神经元。 + +``` + 1 2 3 4 5 +count 30.000000 30.000000 30.000000 30.000000 30.000000 +mean 105.107026 102.836520 92.675912 94.889952 96.577617 +std 23.130824 20.102353 10.266732 9.751318 6.421356 +min 86.565630 84.199871 83.388967 84.385293 87.208454 +25% 88.035396 89.386670 87.643954 89.154866 89.961809 +50% 90.084895 91.488484 90.670565 91.204303 96.717739 +75% 136.145248 104.416518 93.117926 100.228730 101.969331 +max 143.428154 140.923087 136.883946 135.891663 106.797563 +``` + +还创建了一个盒子和须状图,以总结和比较结果的分布。 + +该图证实了 3 个神经元与其他配置相比表现良好的建议,并且还表明结果的扩散也较小。这可能表明配置更稳定。 + +![Box and Whisker Plot of Varying Hidden Neurons for Time Series Forecasting on the Shampoo Sales Dataset](img/59fc895128bf8cad70ff6341dbce8dc2.jpg) + +用于洗发水销售数据集的时间序列预测的变异隐藏神经元的盒子和晶须图 + +同样,我们可以通过回顾适用于 1000 个时期的 3 个神经元的所选配置的诊断来更深入地潜水。 + +诊断脚本的更改仅限于 _run()_ 功能,如下所示。 + +``` +# run diagnostic experiments +def run(): + # config + repeats = 10 + n_batch = 4 + n_epochs = 1000 + n_neurons = 3 + n_lag = 1 + # 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(repeats): + history = fit(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('diagnostic_neurons.png') +``` + +运行诊断脚本为每个训练时期提供了训练和测试 RMSE 的线图。 + +这种诊断方法表明模型技能可能已经过时,可能大约有 400 个时代。该图还提出了过度拟合的可能情况,其中测试 RMSE 在过去 500 个训练时期略有增加,但训练 RMSE 没有强烈增加。 + +![Diagnostic Line Plot of Train and Test Performance of 3 Hidden Neurons on the Shampoo Sales Dataset](img/f483fd406f5bb07ab05d0afad48bddfa.jpg) + +火车诊断线图和 3 个隐藏神经元在洗发水销售数据集上的测试表现 + +## 具有滞后的变隐藏层神经元 + +在本节中,我们将考虑将滞后观测值作为输入增加,同时增加网络容量。 + +增加的滞后观察将自动缩放输入神经元的数量。例如,作为输入的 3 个滞后观察将导致 3 个输入神经元。 + +添加的输入将需要网络中的额外容量。因此,我们还将使用滞后观察的数量作为输入来缩放一个隐藏层中的神经元的数量。 + +我们将使用奇数个滞后观察作为 1,3,5 和 7 的输入,并分别使用相同数量的神经元。 + +输入数量的改变影响在将时间序列数据转换为监督学习问题期间的训练模式的总数。因此,对于本节中的所有实验,批量大小从 4 减少到 2。 + +在每次实验运行中总共使用 1000 个训练时期。 + +基础实验脚本的更改仅限于 _ 实验()_ 功能和实验运行,如下所示。 + +``` +# run a repeated experiment +def experiment(repeats, series, epochs, lag, 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, lag) + supervised_values = supervised.values[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(repeats): + # fit the model + batch_size = 2 + model = fit_model(train_scaled, batch_size, epochs, neurons) + # forecast test dataset + test_reshaped = test_scaled[:,0:-1] + output = 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() +epochs = 1000 +# vary neurons +neurons = [1, 3, 5, 7] +for n in neurons: + results[str(n)] = experiment(repeats, series, epochs, n, n) +# summarize results +print(results.describe()) +# save boxplot +results.boxplot() +pyplot.savefig('boxplot_neurons_lag.png') +``` + +运行实验会使用每个配置的描述性统计信息汇总结果。 + +结果表明滞后输入变量的所有增加随隐藏神经元的增加而降低表现。 + +值得注意的是 1 个神经元和 1 个输入配置,与上一节的结果相比,产生了类似的均值和标准差。 + +表现的降低可能与较小的批量大小有关,并且 1-神经元/ 1 滞后情况的结果不足以解释这一点。 + +``` + 1 3 5 7 +count 30.000000 30.000000 30.000000 30.000000 +mean 105.465038 109.447044 158.894730 147.024776 +std 20.827644 15.312300 43.177520 22.717514 +min 89.909627 77.426294 88.515319 95.801699 +25% 92.187690 102.233491 125.008917 132.335683 +50% 92.587411 109.506480 166.438582 145.078842 +75% 135.386125 118.635143 189.457325 166.329000 +max 139.941789 144.700754 232.962778 186.185471 +``` + +还创建了结果分布的盒子和须状图,允许比较配置。 + +有趣的是,与其他配置相比,使用 3 个神经元和 3 个输入变量显示更紧密的传播。这类似于上一节中所见的 3 个神经元和 1 个输入变量的观察结果。 + +![Box and Whisker Plot of Varying Lag Features and Hidden Neurons for Time Series Forecasting on the Shampoo Sales Dataset](img/df1e821df7175857cde68ed9aaea390b.jpg) + +用于洗发水销售数据集的时间序列预测的变化滞后特征和隐藏神经元的盒子和晶须图 + +我们还可以使用诊断来梳理模型动态在拟合模型时可能发生的变化。 + +3-lag / 3-neurons 的结果很有意思,我们将进一步研究它们。 + +诊断脚本的更改仅限于 _run()_ 功能。 + +``` +# run diagnostic experiments +def run(): + # config + repeats = 10 + n_batch = 2 + n_epochs = 1000 + n_neurons = 3 + n_lag = 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(repeats): + history = fit(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('diagnostic_neurons_lag.png') +``` + +运行诊断脚本会创建一个线图,显示在每个训练时期之后 10 次实验运行的列车和测试 RMSE。 + +结果表明在前 500 个时期内学习良好,并且可能在剩余的时期过度拟合,测试 RMSE 显示出增加的趋势,并且列车 RMSE 显示出下降趋势。 + +![Diagnostic Line Plot of Train and Test Performance of 3 Hidden Neurons and Lag Features on the Shampoo Sales Dataset](img/e82707712edc7aa1034e873eb9275227.jpg) + +火车诊断线图和 3 个隐藏神经元的测试表现和洗发水销售数据集的滞后特征 + +## 审查结果 + +我们在本教程中介绍了很多内容。让我们来复习。 + +* **时代**。我们研究了模型技能如何随着训练时期的变化而变化,并发现 1000 可能是一个很好的起点。 +* **神经元**。我们研究了隐藏层中神经元数量的变化,发现 3 个神经元可能是一个很好的配置。 +* **滞后输入**。我们考虑将滞后观察的数量作为输入变化,同时增加隐藏层中神经元的数量,并发现结果通常变得更糟,但是隐藏层中的 3 个神经元显示出兴趣。与其他实验相比,差的结果可能与批量大小从 4 变为 2 有关。 + +结果表明,在隐藏层中使用 1 个滞后输入,3 个神经元,并且适合作为首次切割模型配置的 1000 个时期。 + +这可以通过多种方式得到改善;下一节列出了一些想法。 + +### 扩展 + +本节列出了您可能想要探索的扩展和后续实验。 + +* **Shuffle vs No Shuffle** 。没有使用洗牌,这是不正常的。在拟合时间序列预测模型时,开发一个实验来比较改组与训练集的无改组。 +* **归一化方法**。数据重新调整为-1 到 1,这是 tanh 激活函数的典型值,未在模型配置中使用。探索其他重新缩放,例如 0-1 规范化和标准化以及对模型表现的影响。 +* **多层**。探索使用多个隐藏层来增加网络容量,以了解更复杂的多步模式。 +* **特色工程**。探索使用其他功能,例如错误时间序列,甚至每个观察的日期时间元素。 + +另外,看看帖子: + +* [如何提高深度学习效能](http://machinelearningmastery.com/improve-deep-learning-performance/) + +你尝试过这些扩展吗? +在以下评论中发布您的结果。 + +## 摘要 + +在本教程中,您了解了如何使用系统实验来探索多层感知器在时间序列预测中的配置,并开发出第一个切割模型。 + +具体来说,你学到了: + +* 如何开发一个强大的测试工具来评估时间序列预测的 MLP 模型。 +* 如何系统地评估训练时期,隐藏层神经元和滞后输入。 +* 如何使用诊断来帮助解释结果并建议后续实验。 + +您对本教程有任何疑问吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/findings-comparing-classical-and-machine-learning-methods-for-time-series-forecasting.md b/docs/dl-ts/findings-comparing-classical-and-machine-learning-methods-for-time-series-forecasting.md new file mode 100644 index 0000000000000000000000000000000000000000..cd42a6d10fbb327369898001fd58a38e845f09c1 --- /dev/null +++ b/docs/dl-ts/findings-comparing-classical-and-machine-learning-methods-for-time-series-forecasting.md @@ -0,0 +1,267 @@ +# 比较经典和机器学习方法进行时间序列预测的结果 + +> 原文: [https://machinelearningmastery.com/findings-comparing-classical-and-machine-learning-methods-for-time-series-forecasting/](https://machinelearningmastery.com/findings-comparing-classical-and-machine-learning-methods-for-time-series-forecasting/) + +通常报告机器学习和深度学习方法是所有预测建模问题的关键解决方案。 + +最近一项重要的研究评估并比较了许多经典和现代机器学习和深度学习方法在 1000 多个单变量时间序列预测问题的大量不同组合中的表现。 + +这项研究的结果表明,简单的经典方法,如线性方法和指数平滑,优于复杂和复杂的方法,如决策树,多层感知器(MLP)和长期短期记忆(LSTM)网络模型。 + +这些发现突出了评估经典方法并将其结果用作评估任何机器学习和时间序列预测的深度学习方法的基线的要求,以证明其增加的复杂性增加了预测技能。 + +在这篇文章中,您将发现这项最新研究的重要发现,评估和比较经典和现代机器学习方法在大量不同时间序列预测数据集上的表现。 + +阅读这篇文章后,你会知道: + +* 像 ETS 和 ARIMA 这样的经典方法胜过机器学习和深度学习方法,可以对单变量数据集进行一步预测。 +* 像 Theta 和 ARIMA 这样的经典方法胜过机器学习和深度学习方法,可以对单变量数据集进行多步预测。 +* 机器学习和深度学习方法还没有兑现他们对单变量时间序列预测的承诺,还有很多工作要做。 + +让我们开始吧。 + +![Findings Comparing Classical and Machine Learning Methods for Time Series Forecasting](img/3e0a3895f026fa2bc5f0f9f4d5dacf9a.jpg) + +研究结果比较时间序列预测的经典和机器学习方法 +[Lyndon Hatherall](https://www.flickr.com/photos/m8ee/8492207975/) 的照片,保留一些权利。 + +## 概观 + +[Spyros Makridakis](https://en.wikipedia.org/wiki/Spyros_Makridakis) ,等。发表于 2018 年的一项研究题为“[统计和机器学习预测方法:关注和前进方向](http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0194889)。” + +在这篇文章中,我们将仔细研究 Makridakis 等人的研究。仔细评估和比较经典时间序列预测方法与现代机器学习方法的表现。 + +这篇文章分为七个部分;他们是: + +1. 学习动机 +2. 时间序列数据集 +3. 时间序列预测方法 +4. 数据准备 +5. 一步预测结果 +6. 多步预测结果 +7. 成果 + +## 学习动机 + +该研究的目的是清楚地展示一套不同的机器学习方法的能力,与传统的时间序列预测方法相比,它可以收集大量不同的单变量时间序列预测问题。 + +该研究是对越来越多的论文的回应,并声称机器学习和深度学习方法为时间序列预测提供了优异的结果,几乎没有客观证据。 + +> 数以百计的论文提出了新的 ML 算法,提出了方法上的进步和准确性的改进。然而,关于它们作为标准预测工具的相对表现,可获得有限的客观证据。 + +- [统计和机器学习预测方法:关注点和前进方向](http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0194889),2018。 + +作者清楚地提出了大量索赔的三个问题;他们是: + +* 他们的结论基于一些甚至一个时间序列,提出了关于结果的统计显着性及其概括性的问题。 +* 对这些方法进行短期预测视野评估,通常是一步到位,不考虑中期和长期预测。 +* 没有使用基准来比较 ML 方法与其他方法的准确性。 + +作为回应,该研究包括八种经典方法和 10 种机器学习方法,使用一个步骤和多步骤预测,在 1,045 个月度时间序列的集合中进行评估。 + +虽然不是确定的,但结果旨在客观和稳健。 + +## 时间序列数据集 + +研究中使用的时间序列数据集来自 M3 竞赛中使用的时间序列数据集。 + +M3 竞赛是一系列竞赛中的第三项,旨在准确发现在实时系列预测问题中哪些算法在实践中表现良好。比赛结果发表在 2000 年题为“ [M3 竞赛:结果,结论和影响](https://www.sciencedirect.com/science/article/pii/S0169207000000571)”的论文中。 + +比赛中使用的数据集来自各行各业,并且具有从小时到年度的各种不同时间间隔。 + +> M3-Competition 的 3003 系列是在配额基础上选择的,包括各种类型的时间序列数据(微观,行业,宏观等)和连续观测之间的不同时间间隔(年度,季度等)。 + +下表摘自论文,提供了竞赛中使用的 3,003 个数据集的摘要。 + +![Table of Datasets, Industry and Time Interval Used in the M3-Competition](img/64e666aef1f30c62616410d8e1e2a5cc.jpg) + +M3 竞赛中使用的数据集,行业和时间间隔表 +取自“M3 竞赛:结果,结论和影响”。 + +竞争的结果是更简单的时间序列预测方法优于更复杂的方法,包括神经网络模型。 + +> 这项研究,前两次 M-Competitions 和许多其他实证研究已经证明,除了最简单的疑问之外,精心设计的理论结构或更复杂的方法并不一定能提高样本后预测的准确性,而不是简单的方法,尽管它们可以更好地适应统计模型到可用的历史数据。 + +- [M3-竞争:结果,结论和影响](https://www.sciencedirect.com/science/article/pii/S0169207000000571),2000。 + +我们在本文中回顾的最近一项评估机器学习方法的研究选择了 1,045 个时间序列的子集,每月间隔与 M3 竞赛中使用的间隔相比。 + +> ...使用 M3 竞赛中使用的 1045 个月度时间序列的大部分,在多个预测视野中评估此类表现。 + +- [统计和机器学习预测方法:关注点和前进方向](http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0194889),2018。 + +## 时间序列预测方法 + +该研究评估了八种经典(或简单)方法和 10 种机器学习方法的表现。 + +> ......八种传统统计方法和八种流行的 ML 方法,以及近两年来最受欢迎的两种方法。 + +- [统计和机器学习预测方法:关注点和前进方向](http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0194889),2018。 + +评估的八种经典方法如下: + +* 朴素 2,实际上是一个随季节调整的随机游走模型。 +* 简单的指数平滑。 +* 霍尔特。 +* 阻尼指数平滑。 +* SES,Holt 和 Damped 的平均值。 +* Theta 方法。 +* ARIMA,自动。 +* ETS,自动。 + +共有八种机器学习方法用于重现和比较 2010 年论文“[时间序列预测的机器学习模型的经验比较](https://www.tandfonline.com/doi/abs/10.1080/07474938.2010.481556)”中提供的结果。 + +他们是: + +* 多层感知器(MLP) +* 贝叶斯神经网络(BNN) +* 径向基函数(RBF) +* 广义循环神经网络(GRNN),也称为核回归 +* K-最近邻回归(KNN) +* CART 回归树(CART) +* 支持向量回归(SVR) +* 高斯过程(GP) + +另外两个'_ 现代 _'神经网络算法也被添加到列表中,因为它们的采用率最近有所上升;他们是: + +* 循环神经网络(RNN) +* 长短期记忆(LSTM) + +## 数据准备 + +再次,基于 2010 年论文“[时间序列预测的机器学习模型的经验比较](https://www.tandfonline.com/doi/abs/10.1080/07474938.2010.481556)”中描述的方法,使用了谨慎的数据准备方法。 + +在那篇论文中,每个时间序列都是使用幂变换进行调整,去季节化和去趋势化。 + +> [...]在计算 18 个预测之前,他们对系列进行了预处理,以实现平均值和方差的平稳性。这是使用对数转换,然后去季节化和最终缩放来完成的,同时还考虑了去除趋势分量的第一个差异。 + +- [统计和机器学习预测方法:关注点和前进方向](http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0194889),2018。 + +受这些操作的启发,对一步预测的 MLP 应用了五种不同数据转换的变体,并对它们的结果进行了比较。五个转变是: + +* 原始数据。 +* Box-Cox 功率变换。 +* 延长数据的时间。 +* 去除数据。 +* 所有三个变换(权力,去季节化,趋势)。 + +一般来说,人们发现最好的方法是应用功率变换并对数据进行去季节化,并且可能也会对系列产生不利影响。 + +> 根据 sMAPE 的最佳组合是 7 号(Box-Cox 转换,deseasonalization),而根据 MASE 的最佳组合是 10 号(Box-Cox 转换,deseasonalization 和 detrending) + +- [统计和机器学习预测方法:关注点和前进方向](http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0194889),2018。 + +## 一步预测结果 + +所有模型均使用一步时间序列预测进行评估。 + +具体而言,最后 18 个时间步骤用作测试集,并且模型适合于所有剩余的观察。对测试集中的 18 个观测值中的每个观测值进行单独的一步预测,可能使用前向验证方法,其中使用真实观测值作为输入以进行每个预测。 + +> 预测模型是使用前 n - 18 个观测值开发的,其中 n 是序列的长度。然后,生成了 18 个预测,并且与开发预测模型时未使用的实际值进行了评估。 + +- [统计和机器学习预测方法:关注点和前进方向](http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0194889),2018。 + +回顾结果,发现 MLP 和 BNN 从所有机器学习方法中获得最佳表现。 + +> 结果表明,MLP 和 BNN 的表现优于其余的 ML 方法。 + +- [统计和机器学习预测方法:关注点和前进方向](http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0194889),2018。 + +令人惊讶的结果是发现 RNN 和 LSTM 表现不佳。 + +> 应该指出的是,RNN 是不太准确的 ML 方法之一,这表明研究进展并不一定能保证预测表现的提高。这个结论也适用于 LSTM 的表现,LSTM 是另一种流行且更先进的 ML 方法,它也不能提高预测精度。 + +- [统计和机器学习预测方法:关注点和前进方向](http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0194889),2018。 + +比较所有方法的表现,发现机器学习方法都是通过简单的经典方法进行的,其中 ETS 和 ARIMA 模型的整体表现最佳。 + +这一发现证实了之前类似研究和竞赛的结果。 + +![Bar Chart Comparing Model Performance (sMAPE) for One-Step Forecasts](img/3618a63641ec8ba329ce9ed44aa54d58.jpg) + +条形图比较模型表现(sMAPE)的一步预测 +取自“统计和机器学习预测方法:关注和前进方向”。 + +## 多步预测结果 + +多步骤预测涉及在最后一次已知观察之前预测多个步骤。 + +针对机器学习方法评估了[多步预测](https://machinelearningmastery.com/multi-step-time-series-forecasting/)的三种方法;他们是: + +* 迭代预测 +* 直接预测 +* 多神经网络预测 + +发现经典方法再次优于机器学习方法。 + +在这种情况下,发现诸如 Theta,ARIMA 和指数平滑(梳状)的组合的方法实现了最佳表现。 + +> 简而言之,统计模型在所有预测视野中似乎总体上优于 ML 方法,根据所检查的误差指标,Theta,Comb 和 ARIMA 在竞争者中占主导地位。 + +- [统计和机器学习预测方法:关注点和前进方向](http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0194889),2018。 + +## 成果 + +该研究提供了重要的支持证据,即经典方法可能主导单变量时间序列预测,至少在评估的预测问题类型上如此。 + +该研究表明,对于单步和多步预测的单变量时间序列预测,机器学习和深度学习方法的表现更差,计算成本增加。 + +这些发现强烈鼓励使用经典方法,如 ETS,ARIMA 等,作为探索更精细方法之前的第一步,并要求将这些简单方法的结果用作表现的基线,以便更精细的方法必须清楚,以证明其使用的合理性。 + +它还强调了不仅要考虑仔细使用数据准备方法,而且需要主动测试针对给定问题的多种不同数据准备方案组合,以便发现哪种方法最有效,即使在经典方法的情况下也是如此。 + +机器学习和深度学习方法仍然可以在特定的单变量时间序列问题上获得更好的表现,并且应该进行评估。 + +该研究没有考虑更复杂的时间序列问题,例如那些数据集: + +* 复杂的不规则时间结构 +* 缺少观察 +* 噪音很大。 +* 多个变量之间复杂的相互关系。 + +该研究总结了一个诚实的困惑,为什么机器学习方法在实践中表现如此糟糕,因为它们在其他人工智能领域表现出色。 + +> 最有趣的问题和最大的挑战是找出其表现不佳的原因,目的是提高准确性并发挥其巨大潜力。人工智能学习算法已经彻底改变了各种领域的广泛应用,并且没有理由用 ML 方法在预测中无法实现同样的目标。因此,我们必须找到如何应用以提高他们更准确的预测能力。 + +- [统计和机器学习预测方法:关注点和前进方向](http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0194889),2018。 + +作者对 LSTM 和 RNN 进行了评论,这些评论通常被认为是一般的序列预测问题的深度学习方法,在这种情况下,它们在实践中表现明显不佳。 + +> [...]人们会期望更高级的 NN 类型的 RNN 和 LSTM 比 ARIMA 和其他统计方法更准确。 + +- [统计和机器学习预测方法:关注点和前进方向](http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0194889),2018。 + +他们评论说 LSTM 似乎更适合拟合或过度拟合训练数据集而不是预测它。 + +> 另一个有趣的例子可能是 LSTM 的情况与较简单的 NN(如 RNN 和 MLP)相比,报告更好的模型拟合但更差的预测准确性 + +- [统计和机器学习预测方法:关注点和前进方向](http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0194889),2018。 + +有工作要做,机器学习方法和深度学习方法比传统的统计方法更有学习时间序列数据的承诺,甚至通过自动特征学习直接对原始观察这样做。 + +> 鉴于他们的学习能力,ML 方法应该比简单的基准测试更好,比如指数平滑。接受问题是设计可行解决方案的第一步,我们希望 AI 和 ML 领域的人员能够接受实证研究结果并努力提高其方法的预测准确性。 + +- [统计和机器学习预测方法:关注点和前进方向](http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0194889),2018。 + +## 进一步阅读 + +如果您希望深入了解,本节将提供有关该主题的更多资源。 + +* [Makridakis 比赛,维基百科](https://en.wikipedia.org/wiki/Makridakis_Competitions) +* [M3-竞争:结果,结论和影响](https://www.sciencedirect.com/science/article/pii/S0169207000000571),2000。 +* [M4 竞赛:结果,发现,结论和前进方向](https://www.sciencedirect.com/science/article/pii/S0169207018300785),2018。 +* [统计和机器学习预测方法:关注点和前进方向](http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0194889),2018。 +* [时间序列预测机器学习模型的实证比较](https://www.tandfonline.com/doi/abs/10.1080/07474938.2010.481556),2010。 + +## 摘要 + +在这篇文章中,您发现了最近一项研究的重要发现,该研究评估和比较了经典和现代机器学习方法在大量不同时间序列预测数据集上的表现。 + +具体来说,你学到了: + +* 像 ETS 和 ARIMA 这样的经典方法胜过机器学习和深度学习方法,可以对单变量数据集进行一步预测。 +* 像 Theta 和 ARIMA 这样的经典方法胜过机器学习和深度学习方法,可以对单变量数据集进行多步预测。 +* 机器学习和深度学习方法尚未兑现其对单变量时间序列预测的承诺,还有很多工作要做。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/get-good-results-fast-deep-learning-time-series-forecasting.md b/docs/dl-ts/get-good-results-fast-deep-learning-time-series-forecasting.md new file mode 100644 index 0000000000000000000000000000000000000000..edf68410cd35c2367f8b99cda9db473984df97ba --- /dev/null +++ b/docs/dl-ts/get-good-results-fast-deep-learning-time-series-forecasting.md @@ -0,0 +1,159 @@ +# 如何通过深度学习快速获得时间序列预测的结果 + +> 原文: [https://machinelearningmastery.com/get-good-results-fast-deep-learning-time-series-forecasting/](https://machinelearningmastery.com/get-good-results-fast-deep-learning-time-series-forecasting/) + +#### 3 +您的预测建模问题设计实验和管理复杂性的策略。 + +新的时间序列预测项目很难开始。 + +鉴于多年的数据,适合深度学习模型可能需要数天或数周。你是如何开始的? + +对于一些从业者来说,这可能会导致项目一开始就瘫痪甚至拖延。在其他情况下,它可能导致陷入只尝试和使用以前工作的陷阱,而不是真正探索问题。 + +在这篇文章中,您将发现在将多层神经网络和长短期记忆(LSTM)循环神经网络模型等深度学习方法应用于时间序列预测问题时可以使用的实用策略。 + +这篇文章中的策略并非万无一失,但是我在处理大型时间序列数据集时发现了很难学到的经验法则。 + +阅读这篇文章后,你会知道: + +* 一种平衡思想探索和利用对你的问题起作用的策略。 +* 一种快速学习和利用数据扩展思路的策略,以确认它们能够解决更广泛的问题。 +* 一种策略,可以解决问题框架的复杂性以及所选深度学习模型的复杂性。 + +让我们开始吧。 + +## 1.勘探和开发战略 + +在搜索能够很好地解决问题的模型时,平衡探索和利用非常重要。 + +我建议两种不同的方法应该串联使用: + +* 诊断。 +* 网格搜索。 + +### 诊断 + +诊断涉及使用一组超参数执行运行,并在每个训练时期的训练和测试数据集上生成模型技能的痕迹。 + +这些图提供了对过度学习或学习不足以及特定超参数集的潜力的深入了解。 + +它们是健全性检查或种子,用于深入研究可以探索的参数范围,并防止您浪费时间,使其具有比合理要求更多的时代,或者太大的网络。 + +以下是来自模型运行的诊断图的示例,其显示了训练和验证 RMSE。 + +![Example Diagnostic Line Plot Comparing Train and Test Loss Over Training Epochs](img/c8ef908f2c8e1207087d46f000c468d7.jpg) + +示例诊断线图比较训练时期的训练和测试损失 + +### 网格搜索 + +基于对诊断结果的学习,网格搜索提供了针对特定模型超参数的一组值的扫描,例如神经元的数量,批量大小等。 + +它们允许您以分段方式系统地拨入特定的超参数值。 + +### 交织方法 + +我建议交错诊断运行和网格搜索运行。 + +您可以通过诊断检查您的假设,并通过网格搜索结果从有前途的想法中获得最佳效果。 + +我强烈建议你测试一下你对模型的每一个假设。这包括简单的事情,如数据缩放,权重初始化,甚至激活函数,损失函数等的选择。 + +与下面的数据处理策略一起使用,您将快速构建一个有关预测问题的有效和无效的映射。 + +下面是模型批量大小的网格搜索结果示例,显示每个实验重复 30 次的结果分布。 + +![Example Box and Whisker Plots Comparing a Model Skill For Different Model Parameter Values](img/cfc3b57cc10dc8b0937a34613f5d5f17.jpg) + +示例框和晶须图比较不同模型参数值的模型技巧 + +## 2.处理数据大小的策略 + +我建议首先使用较小的数据样本来测试想法并慢慢增加数据量,以查看在小样本上学到的东西是否包含更大的样本。 + +例如,如果您有多年的每小时测量,则可以按如下方式拆分数据: + +* 1 周样本。 +* 1 个月的样本。 +* 1 年样本。 +* 所有数据。 + +另一种方法是,您可以在整个数据集中拟合和探索模型,其中每个模型可能需要数天才能适应,这反过来意味着您的学习速度会大幅降低。 + +这种方法的好处是,您可以在几分钟内快速测试多次重复(例如统计上显着)的想法,然后将这些有前途的想法扩展到越来越多的数据。 + +一般来说,有了良好框架的监督学习问题,学习的确会随着数据而扩展。然而,存在这样的风险:在不同的数据规模上问题存在很大差异,并且结果不成立。您可以使用更简单的模型来检查这一点,这些模型可以更快地进行训练,并在早期就弄清楚这是否是一个问题。 + +最后,当您将模型扩展到更多数据时,您还可以减少实验的重复次数,以帮助加快结果的周转时间。 + +## 3.模型复杂性策略 + +与数据大小一样,模型的复杂性是另一个必须管理并可以扩展的问题。 + +我们可以从监督学习问题的框架和模型本身来看待这一点。 + +### 模型框架复杂性 + +例如,我们可以假设包括外生变量的时间序列预测问题(例如,多输入序列或多变量输入)。 + +我们可以扩展问题的复杂性,看看在一个复杂程度(例如,单变量输入)中工作的是复杂的复杂程度(多变量输入)。 + +例如,您可以通过以下方式处理模型复杂性: + +* 单变量输入,单步输出。 +* 单变量输入,多步输出。 +* 多变量输入,单步输出。 +* 多变量输入,多步输出。 + +这也可以扩展到多变量预测。 + +在每个步骤中,目标是证明增加复杂性可以提升模型的技能。 + +例如: + +* 神经网络模型能否胜过持久性预测模型? +* 神经网络模型能否胜过线性预测模型? +* 外生输入变量可以通过单变量输入提升模型的技能吗? +* 直接多步骤预测能否比递归单步预测更具技巧性? + +如果这些问题无法克服或轻易克服,它可以帮助您快速解决问题框架和所选模型。 + +### 模型能力的复杂性 + +当使用更复杂的神经网络模型(如 LSTM)时,可以使用相同的方法。 + +例如: + +* 将问题建模为输入到输出的映射(例如,没有内部状态或 BPTT)。 +* 将问题建模为仅在输入序列中具有内部状态的映射问题(无 BPTT)。 +* 将问题建模为具有内部状态和 BPTT 的映射问题。 + +在每个步骤中,增加的模型复杂性必须证明技能处于或高于先前的复杂程度。换句话说,增加的模型复杂性必须通过模型技能或能力的相应增加来证明。 + +例如: + +* LSTM 能否胜过带窗口的 MLP? +* 具有内部状态且没有 BPTT 的 LSTM 能否优于 LSTM,其中状态在每个样本后重置? +* BPTT 超过输入序列的 LSTM 能否优于每个时间步后更新的 LSTM 吗? + +### 进一步阅读 + +如果您要深入了解,本节将提供有关该主题的更多资源。 + +* [如何通过时间序列预测项目](http://machinelearningmastery.com/work-time-series-forecast-project/) +* [如何用 Keras 调整 LSTM 超参数进行时间序列预测](http://machinelearningmastery.com/tune-lstm-hyperparameters-keras-time-series-forecasting/) +* [如何提高深度学习效能](http://machinelearningmastery.com/improve-deep-learning-performance/) + +## 摘要 + +在本教程中,您了解了如何克服在深度学习项目开始时可能出现的瘫痪。 + +具体来说,您了解了如何系统地分解可用于快速获得结果的复杂性和策略: + +* 一种平衡思想探索和利用对你的问题起作用的策略。 +* 一种快速学习和利用数据扩展思路的策略,以确认它们能够解决更广泛的问题。 +* 一种策略,可以解决问题框架的复杂性以及所选深度学习模型的复杂性。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/handle-missing-timesteps-sequence-prediction-problems-python.md b/docs/dl-ts/handle-missing-timesteps-sequence-prediction-problems-python.md new file mode 100644 index 0000000000000000000000000000000000000000..9c331628e8f9ff03e9f5a3458552a0dc1972a26c --- /dev/null +++ b/docs/dl-ts/handle-missing-timesteps-sequence-prediction-problems-python.md @@ -0,0 +1,604 @@ +# 如何利用 Python 处理序列预测问题中的缺失时间步长 + +> 原文: [https://machinelearningmastery.com/handle-missing-timesteps-sequence-prediction-problems-python/](https://machinelearningmastery.com/handle-missing-timesteps-sequence-prediction-problems-python/) + +通常缺少来自序列数据的观察结果。 + +数据可能已损坏或不可用,但根据定义,您的数据也可能具有可变长度序列。具有较少时间步长的那些序列可被认为具有缺失值。 + +在本教程中,您将了解如何使用 Keras 深度学习库处理 Python 中序列预测问题缺失值的数据。 + +完成本教程后,您将了解: + +* 如何删除包含缺少时间步长的行。 +* 如何标记丢失的时间步骤并强制网络了解其含义。 +* 如何屏蔽缺失的时间步长并将其从模型中的计算中排除。 + +让我们开始吧。 + +![A Gentle Introduction to Linear Algebra](img/a3d40bba50bf998fdd0ac5f7625becad.jpg) + +线性代数的温和介绍 +[Steve Corey](https://www.flickr.com/photos/stevecorey/13939447959/) 的照片,保留一些权利。 + +## 概观 + +本节分为 3 部分;他们是: + +1. 回波序列预测问题 +2. 处理缺失的序列数据 +3. 学习缺少序列值 + +### 环境 + +本教程假定您已安装 Python SciPy 环境。您可以在此示例中使用 Python 2 或 3。 + +本教程假设您使用 TensorFlow(v1.1.0 +)或 Theano(v0.9 +)后端安装了 Keras(v2.0.4 +)。 + +本教程还假设您安装了 scikit-learn,Pandas,NumPy 和 Matplotlib。 + +如果您在设置 Python 环境时需要帮助,请参阅以下帖子: + +* [如何使用 Anaconda 设置用于机器学习和深度学习的 Python 环境](http://machinelearningmastery.com/setup-python-environment-machine-learning-deep-learning-anaconda/) + +## 回波序列预测问题 + +回声问题是一个人为的序列预测问题,其目标是在固定的先前时间步长处记住和预测观察,称为滞后观察。 + +例如,最简单的情况是预测从前一个时间步的观察结果,即回显它。例如: + +``` +Time 1: Input 45 +Time 2: Input 23, Output 45 +Time 3: Input 73, Output 23 +... +``` + +问题是,我们如何处理时间步 1? + +我们可以在 Python 中实现回声序列预测问题。 + +这涉及两个步骤:随机序列的生成和随机序列到有监督学习问题的转换。 + +### 生成随机序列 + +我们可以使用随机模块中的 [random()函数](https://docs.python.org/3/library/random.html)生成 0 到 1 之间的随机值序列。 + +我们可以将它放在一个名为 generate_sequence()的函数中,该函数将为所需的时间步长生成一系列随机浮点值。 + +此功能如下所列。 + +``` +# generate a sequence of random values +def generate_sequence(n_timesteps): + return [random() for _ in range(n_timesteps)] +``` + +### 框架作为监督学习 + +在使用神经网络时,必须将序列框定为监督学习问题。 + +这意味着序列需要分为输入和输出对。 + +该问题可以被构造为基于当前和先前时间步的函数进行预测。 + +或者更正式地说: + +``` +y(t) = f(X(t), X(t-1)) +``` + +其中 y(t)是当前时间步长的期望输出,f()是我们寻求用神经网络逼近的函数,X(t)和 X(t-1)是当前和之前的观测值时间步长。 + +输出可以等于先前的观察值,例如,y(t)= X(t-1),但是它可以很容易地是 y(t)= X(t)。我们针对这个问题进行训练的模型并不知道真正的表述,必须学习这种关系。 + +这模拟了真实的序列预测问题,其中我们将模型指定为一组固定的顺序时间步长的函数,但我们不知道从过去的观察到期望的输出值的实际函数关系。 + +我们可以将这个回声问题的框架实现为 python 中的监督学习问题。 + +[Pandas shift()函数](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.shift.html)可用于创建序列的移位版本,可用于表示先前时间步的观测值。这可以与原始序列连接以提供 X(t-1)和 X(t)输入值。 + +``` +df = DataFrame(sequence) +df = concat([df.shift(1), df], axis=1) +``` + +然后我们可以将 Pandas DataFrame 中的值作为输入序列(X),并使用第一列作为输出序列(y)。 + +``` +# specify input and output data +X, y = values, values[:, 0] +``` + +综上所述,我们可以定义一个函数,它将时间步数作为参数,并返回名为 generate_data()的序列学习的 X,y 数据。 + +``` +# generate data for the lstm +def generate_data(n_timesteps): + # generate sequence + sequence = generate_sequence(n_timesteps) + sequence = array(sequence) + # create lag + df = DataFrame(sequence) + df = concat([df.shift(1), df], axis=1) + values = df.values + # specify input and output data + X, y = values, values[:, 0] + return X, y +``` + +### 序列问题演示 + +我们可以将 generate_sequence()和 generate_data()代码绑定到一个工作示例中。 + +下面列出了完整的示例。 + +``` +from random import random +from numpy import array +from pandas import concat +from pandas import DataFrame + +# generate a sequence of random values +def generate_sequence(n_timesteps): + return [random() for _ in range(n_timesteps)] + +# generate data for the lstm +def generate_data(n_timesteps): + # generate sequence + sequence = generate_sequence(n_timesteps) + sequence = array(sequence) + # create lag + df = DataFrame(sequence) + df = concat([df.shift(1), df], axis=1) + values = df.values + # specify input and output data + X, y = values, values[:, 0] + return X, y + +# generate sequence +n_timesteps = 10 +X, y = generate_data(n_timesteps) +# print sequence +for i in range(n_timesteps): + print(X[i], '=>', y[i]) +``` + +运行此示例会生成一个序列,将其转换为监督表示,并打印每个 X,Y 对。 + +``` +[ nan 0.18961404] => nan +[ 0.18961404 0.25956078] => 0.189614044109 +[ 0.25956078 0.30322084] => 0.259560776929 +[ 0.30322084 0.72581287] => 0.303220844801 +[ 0.72581287 0.02916655] => 0.725812865047 +[ 0.02916655 0.88711086] => 0.0291665472554 +[ 0.88711086 0.34267107] => 0.88711086298 +[ 0.34267107 0.3844453 ] => 0.342671068373 +[ 0.3844453 0.89759621] => 0.384445299683 +[ 0.89759621 0.95278264] => 0.897596208691 +``` + +我们可以看到第一行有 NaN 值。 + +这是因为我们没有事先观察序列中的第一个值。我们必须用一些东西填补这个空间。 + +但我们无法使用 NaN 输入拟合模型。 + +## 处理缺失的序列数据 + +处理缺失的序列数据有两种主要方法。 + +它们将删除缺少数据的行,并使用其他值填充缺少的时间步。 + +有关处理缺失数据的更常用方法,请参阅帖子: + +* [如何使用 Python 处理丢失的数据](http://machinelearningmastery.com/handle-missing-data-python/) + +处理缺失序列数据的最佳方法取决于您的问题和您选择的网络配置。我建议探索每种方法,看看哪种方法效果最好。 + +### 删除缺失的序列数据 + +在我们回显前一个时间步骤中的观察的情况下,第一行数据不包含任何有用的信息。 + +也就是说,在上面的例子中,给定输入: + +``` +[ nan 0.18961404] +``` + +和输出: + +``` +nan +``` + +没有任何有意义的东西可以学习或预测。 + +这里最好的情况是删除这一行。 + +我们可以通过删除包含 NaN 值的所有行,在序列的制定过程中将其作为监督学习问题。具体来说,可以在将数据拆分为 X 和 y 分量之前调用 [dropna()函数](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.dropna.html)。 + +完整示例如下: + +``` +from random import random +from numpy import array +from pandas import concat +from pandas import DataFrame + +# generate a sequence of random values +def generate_sequence(n_timesteps): + return [random() for _ in range(n_timesteps)] + +# generate data for the lstm +def generate_data(n_timesteps): + # generate sequence + sequence = generate_sequence(n_timesteps) + sequence = array(sequence) + # create lag + df = DataFrame(sequence) + df = concat([df.shift(1), df], axis=1) + # remove rows with missing values + df.dropna(inplace=True) + values = df.values + # specify input and output data + X, y = values, values[:, 0] + return X, y + +# generate sequence +n_timesteps = 10 +X, y = generate_data(n_timesteps) +# print sequence +for i in range(len(X)): + print(X[i], '=>', y[i]) +``` + +运行该示例会导致 9 X,y 对而不是 10 对,并删除第一行。 + +``` +[ 0.60619475 0.24408238] => 0.606194746194 +[ 0.24408238 0.44873712] => 0.244082383195 +[ 0.44873712 0.92939547] => 0.448737123424 +[ 0.92939547 0.74481645] => 0.929395472523 +[ 0.74481645 0.69891311] => 0.744816453809 +[ 0.69891311 0.8420314 ] => 0.69891310578 +[ 0.8420314 0.58627624] => 0.842031399202 +[ 0.58627624 0.48125348] => 0.586276240292 +[ 0.48125348 0.75057094] => 0.481253484036 +``` + +### 替换缺失的序列数据 + +在回声问题被配置为在当前时间步骤回显观察的情况下,第一行将包含有意义的信息。 + +例如,我们可以将 y 的定义从值[:,0]更改为值[:,1]并重新运行演示以生成此问题的示例,如下所示: + +``` +[ nan 0.50513289] => 0.505132894821 +[ 0.50513289 0.22879667] => 0.228796667421 +[ 0.22879667 0.66980995] => 0.669809946421 +[ 0.66980995 0.10445146] => 0.104451463568 +[ 0.10445146 0.70642423] => 0.70642422679 +[ 0.70642423 0.10198636] => 0.101986362328 +[ 0.10198636 0.49648033] => 0.496480332278 +[ 0.49648033 0.06201137] => 0.0620113728356 +[ 0.06201137 0.40653087] => 0.406530870804 +[ 0.40653087 0.63299264] => 0.632992635565 +``` + +我们可以看到第一行给出了输入: + +``` +[ nan 0.50513289] +``` + +和输出: + +``` +0.505132894821 +``` + +这可以从输入中学到。 + +问题是,我们仍然需要处理 NaN 值。 + +我们可以用输入中不会自然出现的特定值(例如-1)替换所有 NaN 值,而不是删除具有 NaN 值的行。为此,我们可以使用 [fillna()Pandas 函数](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.fillna.html)。 + +完整示例如下: + +``` +from random import random +from numpy import array +from pandas import concat +from pandas import DataFrame + +# generate a sequence of random values +def generate_sequence(n_timesteps): + return [random() for _ in range(n_timesteps)] + +# generate data for the lstm +def generate_data(n_timesteps): + # generate sequence + sequence = generate_sequence(n_timesteps) + sequence = array(sequence) + # create lag + df = DataFrame(sequence) + df = concat([df.shift(1), df], axis=1) + # replace missing values with -1 + df.fillna(-1, inplace=True) + values = df.values + # specify input and output data + X, y = values, values[:, 1] + return X, y + +# generate sequence +n_timesteps = 10 +X, y = generate_data(n_timesteps) +# print sequence +for i in range(len(X)): + print(X[i], '=>', y[i]) +``` + +运行该示例,我们可以看到第一行的第一列中的 NaN 值被替换为-1 值。 + +``` +[-1\. 0.94641256] => 0.946412559807 +[ 0.94641256 0.11958645] => 0.119586451733 +[ 0.11958645 0.50597771] => 0.505977714614 +[ 0.50597771 0.92496641] => 0.924966407025 +[ 0.92496641 0.15011979] => 0.150119790096 +[ 0.15011979 0.69387197] => 0.693871974256 +[ 0.69387197 0.9194518 ] => 0.919451802966 +[ 0.9194518 0.78690337] => 0.786903370269 +[ 0.78690337 0.17017999] => 0.170179993691 +[ 0.17017999 0.82286572] => 0.822865722747 +``` + +## 学习缺少序列值 + +在学习具有标记缺失值的序列预测问题时,有两个主要选项。 + +该问题可以按原样建模,我们可以鼓励模型了解特定值意味着“缺失”。或者,可以屏蔽特殊缺失值并从预测计算中明确排除。 + +我们将通过两个输入来看看这两个案例的人为“回应当前观察”问题。 + +### 学习缺失的价值观 + +我们可以为预测问题开发 LSTM。 + +输入由 2 个时间步长和 1 个特征定义。在第一隐藏层中定义具有 5 个存储器单元的小 LSTM,并且具有线性激活功能的单个输出层。 + +使用均方误差丢失函数和具有默认配置的高效 ADAM 优化算法,网络将适合。 + +``` +# define model +model = Sequential() +model.add(LSTM(5, input_shape=(2, 1))) +model.add(Dense(1)) +model.compile(loss='mean_squared_error', optimizer='adam') +``` + +为了确保模型学习问题的广义解,即始终将输入作为输出返回(y(t)== X(t)),我们将在每个时期生成一个新的随机序列。该网络将适合 500 个时期,并且将在每个序列中的每个样本之后执行更新(batch_size = 1)。 + +``` +# fit model +for i in range(500): + X, y = generate_data(n_timesteps) + model.fit(X, y, epochs=1, batch_size=1, verbose=2) +``` + +一旦拟合,将生成另一个随机序列,并将来自模型的预测与预期值进行比较。这将提供模型技能的具体概念。 + +``` +# evaluate model on new data +X, y = generate_data(n_timesteps) +yhat = model.predict(X) +for i in range(len(X)): + print('Expected', y[i,0], 'Predicted', yhat[i,0]) +``` + +将所有这些结合在一起,下面提供了完整的代码清单。 + +``` +from random import random +from numpy import array +from pandas import concat +from pandas import DataFrame +from keras.models import Sequential +from keras.layers import LSTM +from keras.layers import Dense + +# generate a sequence of random values +def generate_sequence(n_timesteps): + return [random() for _ in range(n_timesteps)] + +# generate data for the lstm +def generate_data(n_timesteps): + # generate sequence + sequence = generate_sequence(n_timesteps) + sequence = array(sequence) + # create lag + df = DataFrame(sequence) + df = concat([df.shift(1), df], axis=1) + # replace missing values with -1 + df.fillna(-1, inplace=True) + values = df.values + # specify input and output data + X, y = values, values[:, 1] + # reshape + X = X.reshape(len(X), 2, 1) + y = y.reshape(len(y), 1) + return X, y + +n_timesteps = 10 +# define model +model = Sequential() +model.add(LSTM(5, input_shape=(2, 1))) +model.add(Dense(1)) +model.compile(loss='mean_squared_error', optimizer='adam') +# fit model +for i in range(500): + X, y = generate_data(n_timesteps) + model.fit(X, y, epochs=1, batch_size=1, verbose=2) +# evaluate model on new data +X, y = generate_data(n_timesteps) +yhat = model.predict(X) +for i in range(len(X)): + print('Expected', y[i,0], 'Predicted', yhat[i,0]) +``` + +运行该示例将打印每个时期的损失,并在运行结束时比较一个序列的预期输出与预测输出。 + +回顾最终预测,我们可以看到网络已经了解了问题并预测了“足够好”的输出,即使存在缺失值。 + +``` +... +Epoch 1/1 +0s - loss: 1.5992e-04 +Epoch 1/1 +0s - loss: 1.3409e-04 +Epoch 1/1 +0s - loss: 1.1581e-04 +Epoch 1/1 +0s - loss: 2.6176e-04 +Epoch 1/1 +0s - loss: 8.8303e-05 +Expected 0.390784174343 Predicted 0.394238 +Expected 0.688580469278 Predicted 0.690463 +Expected 0.347155799665 Predicted 0.329972 +Expected 0.345075533266 Predicted 0.333037 +Expected 0.456591840482 Predicted 0.450145 +Expected 0.842125610156 Predicted 0.839923 +Expected 0.354087132135 Predicted 0.342418 +Expected 0.601406667694 Predicted 0.60228 +Expected 0.368929815424 Predicted 0.351224 +Expected 0.716420996314 Predicted 0.719275 +``` + +您可以进一步尝试此示例,并将给定序列的 t-1 观察值的 50%标记为-1,并查看它如何影响模型的技能随时间的变化。 + +### 掩盖缺失的价值观 + +可以从网络中的所有计算中屏蔽标记的缺失输入值。 + +我们可以通过使用 [Masking 层](https://keras.io/layers/core/#masking)作为网络的第一层来实现。 + +定义层时,我们可以指定要屏蔽的输入中的哪个值。如果时间步长的所有要素都包含蒙版值,则整个时间步长将从计算中排除。 + +这为完全排除行并强制网络了解标记缺失值的影响提供了一个中间立场。 + +由于 Masking 层是网络中的第一个,因此必须指定输入的预期形状,如下所示: + +``` +model.add(Masking(mask_value=-1, input_shape=(2, 1))) +``` + +我们可以将所有这些结合起来并重新运行示例。完整的代码清单如下。 + +``` +from random import random +from numpy import array +from pandas import concat +from pandas import DataFrame +from keras.models import Sequential +from keras.layers import LSTM +from keras.layers import Dense +from keras.layers import Masking + +# generate a sequence of random values +def generate_sequence(n_timesteps): + return [random() for _ in range(n_timesteps)] + +# generate data for the lstm +def generate_data(n_timesteps): + # generate sequence + sequence = generate_sequence(n_timesteps) + sequence = array(sequence) + # create lag + df = DataFrame(sequence) + df = concat([df.shift(1), df], axis=1) + # replace missing values with -1 + df.fillna(-1, inplace=True) + values = df.values + # specify input and output data + X, y = values, values[:, 1] + # reshape + X = X.reshape(len(X), 2, 1) + y = y.reshape(len(y), 1) + return X, y + +n_timesteps = 10 +# define model +model = Sequential() +model.add(Masking(mask_value=-1, input_shape=(2, 1))) +model.add(LSTM(5)) +model.add(Dense(1)) +model.compile(loss='mean_squared_error', optimizer='adam') +# fit model +for i in range(500): + X, y = generate_data(n_timesteps) + model.fit(X, y, epochs=1, batch_size=1, verbose=2) +# evaluate model on new data +X, y = generate_data(n_timesteps) +yhat = model.predict(X) +for i in range(len(X)): + print('Expected', y[i,0], 'Predicted', yhat[i,0]) +``` + +同样,每个时期打印损失,并将预测与最终序列的预期值进行比较。 + +同样,预测看起来足够小到几位小数。 + +``` +... +Epoch 1/1 +0s - loss: 1.0252e-04 +Epoch 1/1 +0s - loss: 6.5545e-05 +Epoch 1/1 +0s - loss: 3.0831e-05 +Epoch 1/1 +0s - loss: 1.8548e-04 +Epoch 1/1 +0s - loss: 7.4286e-05 +Expected 0.550889403319 Predicted 0.538004 +Expected 0.24252028132 Predicted 0.243288 +Expected 0.718869927574 Predicted 0.724669 +Expected 0.355185878917 Predicted 0.347479 +Expected 0.240554707978 Predicted 0.242719 +Expected 0.769765554707 Predicted 0.776608 +Expected 0.660782450416 Predicted 0.656321 +Expected 0.692962017672 Predicted 0.694851 +Expected 0.0485233839401 Predicted 0.0722362 +Expected 0.35192019185 Predicted 0.339201 +``` + +### 选择哪种方法? + +这些一次性实验不足以评估在简单回波序列预测问题上最有效的方法。 + +他们提供的模板可以用于您自己的问题。 + +我鼓励您探索在序列预测问题中处理缺失值的 3 种不同方法。他们是: + +* 删除缺少值的行。 +* 标记并学习缺失值。 +* 掩盖和学习没有遗漏的价值观。 + +尝试针对序列预测问题的每种方法,并对看起来效果最好的方法进行加倍研究。 + +## 摘要 + +如果序列具有可变长度,则通常在序列预测问题中具有缺失值。 + +在本教程中,您了解了如何使用 Keras 处理 Python 中序列预测问题中的缺失数据。 + +具体来说,你学到了: + +* 如何删除包含缺失值的行。 +* 如何标记缺失值并强制模型了解其含义。 +* 如何屏蔽缺失值以将其从模型中的计算中排除。 + +您对处理丢失的序列数据有任何疑问吗? +在评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/how-to-develop-a-probabilistic-forecasting-model-to-predict-air-pollution-days.md b/docs/dl-ts/how-to-develop-a-probabilistic-forecasting-model-to-predict-air-pollution-days.md new file mode 100644 index 0000000000000000000000000000000000000000..800fb7a8282460145b78c58cea565ce28b41633b --- /dev/null +++ b/docs/dl-ts/how-to-develop-a-probabilistic-forecasting-model-to-predict-air-pollution-days.md @@ -0,0 +1,616 @@ +# 如何建立预测大气污染日的概率预测模型 + +> 原文: [https://machinelearningmastery.com/how-to-develop-a-probabilistic-forecasting-model-to-predict-air-pollution-days/](https://machinelearningmastery.com/how-to-develop-a-probabilistic-forecasting-model-to-predict-air-pollution-days/) + +空气污染的特点是地面臭氧浓度。 + +根据风速和温度等气象测量结果,可以预测明天地面臭氧是否会达到足够高的水平,以发布公共空气污染预警。 + +这是用于时间序列分类数据集的标准机器学习数据集的基础,简称为“_ 臭氧预测问题 _”。该数据集描述了休斯顿地区七年来的气象观测以及臭氧水平是否高于临界空气污染水平。 + +在本教程中,您将了解如何探索这些数据并开发概率预测模型,以预测德克萨斯州休斯顿的空气污染。 + +完成本教程后,您将了解: + +* 如何加载和准备臭氧日标准机器学习预测建模问题。 +* 如何开发一个天真的预测模型,并使用 Brier 技能分数评估预测。 +* 如何使用决策树集合开发技巧模型,并通过成功模型的超参数调整进一步提高技能。 + +让我们开始吧。 + +![How to Develop a Probabilistic Forecasting Model to Predict Air Pollution Days](img/155050bf84791ee8f293a979787b4719.jpg) + +如何建立预测空气污染日的概率预测模型 +照片由 [paramita](https://www.flickr.com/photos/parramitta/6715213551/) ,保留一些权利。 + +## 教程概述 + +本教程分为五个部分;他们是: + +1. 臭氧预测问题 +2. 加载和检查数据 +3. 朴素预测模型 +4. 集合树预测模型 +5. 调整梯度提升 + +## 臭氧预测问题 + +空气污染的特征在于地面臭氧的高度测量,通常被称为“[不良臭氧](https://en.wikipedia.org/wiki/Tropospheric_ozone)”,以区​​别于高层大气中的臭氧。 + +臭氧预测问题是时间序列分类预测问题,其涉及预测第二天是否将是高空气污染日(臭氧日)。气象组织可以利用臭氧日的预测来警告公众,使他们能够采取预防措施。 + +该数据集最初由 Kun Zhang 等人研究。他们在 2006 年的论文“[预测偏差随机臭氧天数:分析和解决方案](https://ieeexplore.ieee.org/abstract/document/4053100/)”,然后在他们的后续报告中再次提到“[预测偏差随机臭氧天:分析,解决方案及其他](https://link.springer.com/article/10.1007/s10115-007-0095-1) “。 + +这是一个具有挑战性的问题,因为高臭氧水平的物理机制是(或没有)被完全理解,这意味着预测不能像其他气象预报一样基于物理模拟,如温度和降雨量。 + +该数据集被用作开发预测模型的基础,该模型使用一系列可能与预测臭氧水平相关或可能不相关的变量,此外还有少数已知与所涉及的实际化学过程相关的变量。 + +> 然而,环境科学家普遍认为,目前从未探索过的大量其他特征在建立高度精确的臭氧预测模型中非常有用。然而,鲜为人知的是这些特征究竟是什么以及它们如何在臭氧形成中实际相互作用。 [...]今天的环境科学都没有知道如何使用它们。这为数据挖掘提供了绝佳的机会 + +- [预测偏差随机臭氧天数:分析和解决方案](https://ieeexplore.ieee.org/abstract/document/4053100/),2006 年。 + +在随后的一天预测高水平的地面臭氧是一个具有挑战性的问题,已知其具有随机性。这意味着预计会出现预测错误。因此,希望概率地对预测问题进行建模并预测臭氧日的概率,或者在前一天或几天给出观察结果。 + +该数据集包含七年的每日气象变量观测值(1998-2004 或 2,536 天)以及是否有臭氧日,在美国德克萨斯州的休斯顿,加尔维斯顿和布拉佐里亚地区进行。 + +每天共观察到 72 个变量,其中许多被认为与预测问题相关,其中 10 个已根据物理学确认为相关。 + +> [...]这 72 个特征中只有大约 10 个特征已经被环境科学家证实是有用和相关的,并且关于其他 60 个特征的相关性,既没有经验也没有理论信息。然而,空气质量控制科学家长期以来一直在猜测这些特征中的一些可能是有用的,但是无法发展理论或使用模拟来证明其相关性。 + +- [预测偏差随机臭氧天数:分析和解决方案](https://ieeexplore.ieee.org/abstract/document/4053100/),2006 年。 + +有 24 个变量跟踪每小时风速,另外 24 个变量跟踪一天中每小时的温度。数据集的两个版本可用于度量的不同平均周期,特别是 1 小时和 8 小时。 + +似乎缺少并可能有用的是每天观察到的臭氧,而不是二氧化硫臭氧日/非臭氧日。参数模型中使用的其他度量也不可用。 + +有趣的是,基于“[开发臭氧预测计划指南](https://nepis.epa.gov/Exe/ZyPURL.cgi?Dockey=2000D6KP.TXT)”,1999 年 EPA 指南中的描述,使用参数臭氧预测模型作为基线。该文件还描述了验证臭氧预报系统的标准方法。 + +总之,这是一个具有挑战性的预测问题,因为: + +* 存在大量变量,其重要性通常是未知的。 +* 输入变量及其相互关系可能会随时间而变化。 +* 对于需要处理的许多变量缺少观察结果。 +* 非臭氧日(非事件)远远超过臭氧日(事件),使得这些类别高度不平衡。 + +## 加载和检查数据 + +该数据集可从 UCI 机器学习库获得。 + +* [臭氧水平检测数据集](https://archive.ics.uci.edu/ml/datasets/ozone+level+detection) + +我们只会查看本教程中的 8 小时数据。下载“ _eighthr.data_ ”并将其放在当前的工作目录中。 + +检查数据文件,我们可以看到不同尺度的观察结果。 + +``` +1/1/1998,0.8,1.8,2.4,2.1,2,2.1,1.5,1.7,1.9,2.3,3.7,5.5,5.1,5.4,5.4,4.7,4.3,3.5,3.5,2.9,3.2,3.2,2.8,2.6,5.5,3.1,5.2,6.1,6.1,6.1,6.1,5.6,5.2,5.4,7.2,10.6,14.5,17.2,18.3,18.9,19.1,18.9,18.3,17.3,16.8,16.1,15.4,14.9,14.8,15,19.1,12.5,6.7,0.11,3.83,0.14,1612,-2.3,0.3,7.18,0.12,3178.5,-15.5,0.15,10.67,-1.56,5795,-12.1,17.9,10330,-55,0,0. +1/2/1998,2.8,3.2,3.3,2.7,3.3,3.2,2.9,2.8,3.1,3.4,4.2,4.5,4.5,4.3,5.5,5.1,3.8,3,2.6,3,2.2,2.3,2.5,2.8,5.5,3.4,15.1,15.3,15.6,15.6,15.9,16.2,16.2,16.2,16.6,17.8,19.4,20.6,21.2,21.8,22.4,22.1,20.8,19.1,18.1,17.2,16.5,16.1,16,16.2,22.4,17.8,9,0.25,-0.41,9.53,1594.5,-2.2,0.96,8.24,7.3,3172,-14.5,0.48,8.39,3.84,5805,14.05,29,10275,-55,0,0. +1/3/1998,2.9,2.8,2.6,2.1,2.2,2.5,2.5,2.7,2.2,2.5,3.1,4,4.4,4.6,5.6,5.4,5.2,4.4,3.5,2.7,2.9,3.9,4.1,4.6,5.6,3.5,16.6,16.7,16.7,16.8,16.8,16.8,16.9,16.9,17.1,17.6,19.1,21.3,21.8,22,22.1,22.2,21.3,19.8,18.6,18,18,18.2,18.3,18.4,22.2,18.7,9,0.56,0.89,10.17,1568.5,0.9,0.54,3.8,4.42,3160,-15.9,0.6,6.94,9.8,5790,17.9,41.3,10235,-40,0,0. +... +``` + +浏览文件,例如到 2003 年初,我们可以看到缺少的观察值标有“?”值。 + +``` +... +12/29/2002,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,11.7,0.09,5.59,3.79,1578,5.7,0.04,1.8,4.8,3181.5,-13,0.02,0.38,2.78,5835,-31.1,18.9,10250,-25,0.03,0. +12/30/2002,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,10.3,0.43,3.88,9.21,1525.5,1.8,0.87,9.17,9.96,3123,-11.3,0.03,11.23,10.79,5780,17,30.2,10175,-75,1.68,0. +12/31/2002,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,8.5,0.96,6.05,11.18,1433,-0.85,0.91,7.02,6.63,3014,-16.2,0.05,15.77,24.38,5625,31.15,48.75,10075,-100,0.05,0. +1/1/2003,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,7.2,5.7,4.5,4,3.6,3.3,3.1,3.2,6.7,11.1,13.8,15.8,17.2,18.6,20,21.1,21.5,20.4,19.1,17.8,17.4,16.9,16.6,14.9,21.5,12.6,6.4,0.6,12.91,-10.17,1421.5,1.95,0.55,11.97,-7.78,3006.5,-14.1,0.44,20.42,-13.31,5640,2.9,30.5,10095,35,0,0. +... +``` + +首先,我们可以使用 [read_csv()函数](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_csv.html)将数据作为 Pandas DataFrame 加载。没有数据头,我们可以解析第一列中的日期并将它们用作索引;下面列出了完整的示例。 + +``` +# load and summarize +from pandas import read_csv +from matplotlib import pyplot +# load dataset +data = read_csv('eighthr.data', header=None, index_col=0, parse_dates=True, squeeze=True) +print(data.shape) +# summarize class counts +counts = data.groupby(73).size() +for i in range(len(counts)): + percent = counts[i] / data.shape[0] * 100 + print('Class=%d, total=%d, percentage=%.3f' % (i, counts[i], percent)) +``` + +运行该示例确认有 2,534 天的数据和 73 个变量。 + +我们还可以看到阶级不平衡的性质,其中 93%以上的日子是非臭氧日,约 6%是臭氧日。 + +``` +(2534, 73) +Class=0, total=2374, percentage=93.686 +Class=1, total=160, percentage=6.314 +``` + +我们还可以在七年内创建输出变量的线图,以了解臭氧天数是否发生在一年中的任何特定时间。 + +``` +# load and plot output variable +from pandas import read_csv +from matplotlib import pyplot +# load dataset +data = read_csv('eighthr.data', header=None, index_col=0, parse_dates=True, squeeze=True) +# plot the output variable +pyplot.plot(data.index, data.values[:,-1]) +pyplot.show() +``` + +运行该示例将创建七年内输出变量的线图。 + +我们可以看到,每年中期都有臭氧天集群:北半球夏季或温暖的月份。 + +![Line plot of output variable over 7 years](img/03aa4be52b1d862ce62324e4606ebe3b.jpg) + +输出变量的线图超过 7 年 + +通过简要回顾一下观察结果,我们可以得到一些关于如何准备数据的想法: + +* 缺少数据需要处理。 +* 最简单的框架是根据今天的观察结果预测明天的臭氧日。 +* 温度可能与季节或一年中的时间相关,可能是一个有用的预测指标。 +* 数据变量可能需要缩放(标准化),甚至可能需要标准化,具体取决于所选的算法。 +* 预测概率将提供比预测类值更多的细微差别。 +* 也许我们可以使用五年(约 72%)来训练模型并在剩余的两年内测试它(约 28%) + +我们可以执行一些最小的数据准备。 + +下面的示例加载数据集,用 0.0 替换缺失的观测值,将数据构建为监督学习问题(明天根据今天的观察结果预测臭氧),并根据大量天数将数据分成火车和测试集。两年。 + +您可以探索替换缺失值的替代方法,例如输入平均值。此外,2004 年是一个闰年,因此将数据分成火车和测试集并不是一个干净的 5 - 2 年分裂,但是对于本教程来说足够接近。 + +``` +# load and prepare +from pandas import read_csv +from matplotlib import pyplot +from numpy import array +from numpy import hstack +from numpy import savetxt +# load dataset +data = read_csv('eighthr.data', header=None, index_col=0, parse_dates=True, squeeze=True) +values = data.values +# replace missing observations with 0 +values[values=='?'] = 0.0 +# frame as supervised learning +supervised = list() +for i in range(len(values) - 1): + X, y = values[i, :-1], values[i + 1, -1] + row = hstack((X,y)) + supervised.append(row) +supervised = array(supervised) +# split into train-test +split = 365 * 2 +train, test = supervised[:-split,:], supervised[-split:,:] +train, test = train.astype('float32'), test.astype('float32') +print(train.shape, test.shape) +# save prepared datasets +savetxt('train.csv', train, delimiter=',') +savetxt('test.csv', test, delimiter=',') +``` + +运行该示例将列车和测试集保存为 CSV 文件,并汇总两个数据集的形状。 + +``` +(1803, 73) (730, 73) +``` + +## 朴素预测模型 + +一个天真的模型可以预测每天臭氧日的概率。 + +这是一种天真的方法,因为它不使用除事件基本速率之外的任何信息。在气象预报的验证中,这被称为气候预报。 + +我们可以从训练数据集中估计臭氧日的概率,如下所示。 + +``` +# load datasets +train = loadtxt('train.csv', delimiter=',') +test = loadtxt('test.csv', delimiter=',') +# estimate naive probabilistic forecast +naive = sum(train[:,-1]) / train.shape[0] +``` + +然后,我们可以预测测试数据集中每天臭氧日的初始概率。 + +``` +# forecast the test dataset +yhat = [naive for _ in range(len(test))] +``` + +一旦我们有了预测,我们就可以对其进行评估。 + +评估概率预测的有用措施是 [Brier 评分](https://en.wikipedia.org/wiki/Brier_score)。该分数可以被认为是来自预期概率(0%或 1%)的预测概率(例如 5%)的均方误差。它是测试数据集中每天发生的错误的平均值。 + +我们感兴趣的是最小化 Brier 分数,较小的值更好,例如更小的错误。 + +我们可以使用 scikit-learn 库中的 [brier_score_loss()函数](http://scikit-learn.org/stable/modules/generated/sklearn.metrics.brier_score_loss.html)来评估预测的 Brier 分数。 + +``` +# evaluate forecast +testy = test[:, -1] +bs = brier_score_loss(testy, yhat) +print('Brier Score: %.6f' % bs) +``` + +对于熟练的模型,它必须具有比天真预测的分数更好的分数。 + +我们可以通过计算基于天真预测标准化 Brier 分数(BS)的 Brier 技能分数(BSS)来证明这一点。 + +我们预计天真预报的计算 BSS 将为 0.0。展望未来,我们有兴趣最大化此分数,例如较大的 BSS 分数更好。 + +``` +# calculate brier skill score +bs_ref = bs +bss = (bs - bs_ref) / (0 - bs_ref) +print('Brier Skill Score: %.6f' % bss) +``` + +下面列出了幼稚预测的完整示例。 + +``` +# naive prediction method +from sklearn.metrics import brier_score_loss +from numpy import loadtxt +# load datasets +train = loadtxt('train.csv', delimiter=',') +test = loadtxt('test.csv', delimiter=',') +# estimate naive probabilistic forecast +naive = sum(train[:,-1]) / train.shape[0] +print(naive) +# forecast the test dataset +yhat = [naive for _ in range(len(test))] +# evaluate forecast +testy = test[:, -1] +bs = brier_score_loss(testy, yhat) +print('Brier Score: %.6f' % bs) +# calculate brier skill score +bs_ref = bs +bss = (bs - bs_ref) / (0 - bs_ref) +print('Brier Skill Score: %.6f' % bss) +``` + +运行这个例子,我们可以看到臭氧日的天真概率甚至约为 7.2%。 + +使用基本费率作为预测会导致 Brier 技能为 0.039,预期 Brier 技能得分为 0.0(忽略该符号)。 + +``` +0.07265668330560178 +Brier Score: 0.039232 +Brier Skill Score: -0.000000 +``` + +我们现在准备探索一些机器学习方法,看看我们是否可以为此预测添加技能。 + +请注意,原始论文使用精确度和直接召回来评估方法的技巧,这是一种用于方法之间直接比较的令人惊讶的方法。 + +也许您可以探索的替代措施是 ROC 曲线下的面积(ROC AUC)。绘制最终模型的 ROC 曲线将允许模型的操作者选择阈值,该阈值提供真正的正(命中)和误报(误报)速率之间的期望平衡水平。 + +## 集合树预测模型 + +原始论文报告了袋装决策树的一些成功。 + +> 尽管我们对归纳学习器的选择是非穷尽的,但本文已经表明,归纳学习可以作为臭氧水平预测的一种选择方法,而基于集合的概率树提供比现有方法更好的预测(更高的召回率和精确度)。 + +- [预测偏差随机臭氧天数:分析和解决方案](https://ieeexplore.ieee.org/abstract/document/4053100/),2006 年。 + +出于以下几个原因,这并不奇怪: + +* 袋装决策树不需要任何数据缩放。 +* Bagged 决策树自动执行一种功能部分,忽略不相关的功能。 +* 袋装决策树预测合理校准的概率(例如,与 SVM 不同)。 + +这表明在测试机器学习算法时,这是一个很好的起点。 + +我们可以通过现场检查 scikit-learn 库中标准集合树方法样本的表现来快速入门,其默认配置和树数设置为 100。 + +具体来说,方法: + +* 袋装决策树(BaggingClassifier) +* 额外树木(ExtraTreesClassifier) +* 随机梯度提升(GradientBoostingClassifier) +* 随机森林(RandomForestClassifier) + +首先,我们必须将训练和测试数据集分成输入(X)和输出(y)组件,以便我们可以拟合 sklearn 模型。 + +``` +# load datasets +train = loadtxt('train.csv', delimiter=',') +test = loadtxt('test.csv', delimiter=',') +# split into inputs/outputs +trainX, trainy, testX, testy = train[:,:-1],train[:,-1],test[:,:-1],test[:,-1] +``` + +我们还需要 Brier 分数进行天真的预测,以便我们能够正确计算新模型的 Brier 技能分数。 + +``` +# estimate naive probabilistic forecast +naive = sum(train[:,-1]) / train.shape[0] +# forecast the test dataset +yhat = [naive for _ in range(len(test))] +# calculate naive bs +bs_ref = brier_score_loss(testy, yhat) +``` + +我们可以一般地评估单个 scikit-learn 模型的技能。 + +下面定义名为 _evaluate_once()_ 的函数,该函数适合并评估给定的已定义和配置的 scikit-learn 模型并返回 Brier 技能分数(BSS)。 + +``` +# evaluate a sklearn model +def evaluate_once(bs_ref, template, trainX, trainy, testX, testy): + # fit model + model = clone(template) + model.fit(trainX, trainy) + # predict probabilities for 0 and 1 + probs = model.predict_proba(testX) + # keep the probabilities for class=1 only + yhat = probs[:, 1] + # calculate brier score + bs = brier_score_loss(testy, yhat) + # calculate brier skill score + bss = (bs - bs_ref) / (0 - bs_ref) + return bss +``` + +集合树是一种随机机器学习方法。 + +这意味着当相同模型的相同配置在相同数据上训练时,它们将做出不同的预测。为了纠正这个问题,我们可以多次评估给定模型,例如 10 次,并计算每次运行的平均技能。 + +下面的函数将评估给定模型 10 次,打印平均 BSS 分数,并返回分数的总体用于分析。 + +``` +# evaluate an sklearn model n times +def evaluate(bs_ref, model, trainX, trainy, testX, testy, n=10): + scores = [evaluate_once(bs_ref, model, trainX, trainy, testX, testy) for _ in range(n)] + print('>%s, bss=%.6f' % (type(model), mean(scores))) + return scores +``` + +我们现在准备评估一套集合决策树算法。 + +下面列出了完整的示例。 + +``` +# evaluate ensemble tree methods +from numpy import loadtxt +from numpy import mean +from matplotlib import pyplot +from sklearn.base import clone +from sklearn.metrics import brier_score_loss +from sklearn.ensemble import BaggingClassifier +from sklearn.ensemble import ExtraTreesClassifier +from sklearn.ensemble import GradientBoostingClassifier +from sklearn.ensemble import RandomForestClassifier + +# evaluate a sklearn model +def evaluate_once(bs_ref, template, trainX, trainy, testX, testy): + # fit model + model = clone(template) + model.fit(trainX, trainy) + # predict probabilities for 0 and 1 + probs = model.predict_proba(testX) + # keep the probabilities for class=1 only + yhat = probs[:, 1] + # calculate brier score + bs = brier_score_loss(testy, yhat) + # calculate brier skill score + bss = (bs - bs_ref) / (0 - bs_ref) + return bss + +# evaluate an sklearn model n times +def evaluate(bs_ref, model, trainX, trainy, testX, testy, n=10): + scores = [evaluate_once(bs_ref, model, trainX, trainy, testX, testy) for _ in range(n)] + print('>%s, bss=%.6f' % (type(model), mean(scores))) + return scores + +# load datasets +train = loadtxt('train.csv', delimiter=',') +test = loadtxt('test.csv', delimiter=',') +# split into inputs/outputs +trainX, trainy, testX, testy = train[:,:-1],train[:,-1],test[:,:-1],test[:,-1] +# estimate naive probabilistic forecast +naive = sum(train[:,-1]) / train.shape[0] +# forecast the test dataset +yhat = [naive for _ in range(len(test))] +# calculate naive bs +bs_ref = brier_score_loss(testy, yhat) +# evaluate a suite of ensemble tree methods +scores, names = list(), list() +n_trees=100 +# bagging +model = BaggingClassifier(n_estimators=n_trees) +avg_bss = evaluate(bs_ref, model, trainX, trainy, testX, testy) +scores.append(avg_bss) +names.append('bagging') +# extra +model = ExtraTreesClassifier(n_estimators=n_trees) +avg_bss = evaluate(bs_ref, model, trainX, trainy, testX, testy) +scores.append(avg_bss) +names.append('extra') +# gbm +model = GradientBoostingClassifier(n_estimators=n_trees) +avg_bss = evaluate(bs_ref, model, trainX, trainy, testX, testy) +scores.append(avg_bss) +names.append('gbm') +# rf +model = RandomForestClassifier(n_estimators=n_trees) +avg_bss = evaluate(bs_ref, model, trainX, trainy, testX, testy) +scores.append(avg_bss) +names.append('rf') +# plot results +pyplot.boxplot(scores, labels=names) +pyplot.show() +``` + +运行该示例总结了 10 次运行中平均每个模型的平均 BSS。 + +鉴于算法的随机性,您的具体结果可能会有所不同,但趋势应该相同。 + +从平均 BSS 分数来看,它表明额外的树木,随机梯度增强和随机森林模型是最熟练的。 + +``` +>, bss=0.069762 +>, bss=0.103291 +>, bss=0.119803 +>, bss=0.102736 +``` + +绘制每个模型的分数的盒子和须状图。 + +他们所有跑步的所有模型都显示出天真预测的技巧(正分数),这是非常令人鼓舞的。 + +额外树木,随机梯度提升和随机森林的 BSS 分数的分布看起来都令人鼓舞。 + +![Box and whisker plot of ensemble decision tree BSS scores on the test set](img/583e09c543b0402e5d1979ca40390521.jpg) + +测试集上的集合决策树 BSS 分数的框和胡须图 + +## 调整梯度提升 + +鉴于随机梯度增强看起来很有希望,有必要探讨是否可以通过某些[参数调整](http://scikit-learn.org/stable/modules/generated/sklearn.ensemble.GradientBoostingClassifier.html)进一步提升模型的表现。 + +有许多参数可以调整模型,但调整模型的一些好的启发式方法包括: + +* 降低学习率( _learning_rate_ ),同时增加决策树的数量( _n_estimators_ )。 +* 增加决策树的最大深度( _max_depth_ ),同时减少可用于拟合树的样本数(_ 样本 _)。 + +我们可以根据这些原则检查一些参数,而不是网格搜索值。如果您有时间和计算资源,可以自己探索这些参数的网格搜索。 + +我们将比较 GBM 模型的四种配置: + +* **基线**:如前一节测试的那样( _learning_rate_ = 0.1, _n_estimators_ = 100,_ 子样本 _ = 1.0, _max_depth_ = 3) +* **lr** ,学习率较低且树木较多( _learning_rate_ = 0.01, _n_estimators_ = 500,_ 子样本 _ = 1.0, _max_depth_ = 3) +* **深度**,最大树深度增加,数据集采样量减少( _learning_rate_ = 0.1, _n_estimators_ = 100,_ 子样本 _ = 0.7, _max_depth_ =) +* **所有**,所有修改。 + +下面列出了完整的示例。 + +``` +# tune the gbm configuration +from numpy import loadtxt +from numpy import mean +from matplotlib import pyplot +from sklearn.base import clone +from sklearn.metrics import brier_score_loss +from sklearn.ensemble import BaggingClassifier +from sklearn.ensemble import ExtraTreesClassifier +from sklearn.ensemble import GradientBoostingClassifier +from sklearn.ensemble import RandomForestClassifier + +# evaluate a sklearn model +def evaluate_once(bs_ref, template, trainX, trainy, testX, testy): + # fit model + model = clone(template) + model.fit(trainX, trainy) + # predict probabilities for 0 and 1 + probs = model.predict_proba(testX) + # keep the probabilities for class=1 only + yhat = probs[:, 1] + # calculate brier score + bs = brier_score_loss(testy, yhat) + # calculate brier skill score + bss = (bs - bs_ref) / (0 - bs_ref) + return bss + +# evaluate an sklearn model n times +def evaluate(bs_ref, model, trainX, trainy, testX, testy, n=10): + scores = [evaluate_once(bs_ref, model, trainX, trainy, testX, testy) for _ in range(n)] + print('>%s, bss=%.6f' % (type(model), mean(scores))) + return scores + +# load datasets +train = loadtxt('train.csv', delimiter=',') +test = loadtxt('test.csv', delimiter=',') +# split into inputs/outputs +trainX, trainy, testX, testy = train[:,:-1],train[:,-1],test[:,:-1],test[:,-1] +# estimate naive probabilistic forecast +naive = sum(train[:,-1]) / train.shape[0] +# forecast the test dataset +yhat = [naive for _ in range(len(test))] +# calculate naive bs +bs_ref = brier_score_loss(testy, yhat) +# evaluate a suite of ensemble tree methods +scores, names = list(), list() +# base +model = GradientBoostingClassifier(learning_rate=0.1, n_estimators=100, subsample=1.0, max_depth=3) +avg_bss = evaluate(bs_ref, model, trainX, trainy, testX, testy) +scores.append(avg_bss) +names.append('base') +# learning rate +model = GradientBoostingClassifier(learning_rate=0.01, n_estimators=500, subsample=1.0, max_depth=3) +avg_bss = evaluate(bs_ref, model, trainX, trainy, testX, testy) +scores.append(avg_bss) +names.append('lr') +# depth +model = GradientBoostingClassifier(learning_rate=0.1, n_estimators=100, subsample=0.7, max_depth=7) +avg_bss = evaluate(bs_ref, model, trainX, trainy, testX, testy) +scores.append(avg_bss) +names.append('depth') +# all +model = GradientBoostingClassifier(learning_rate=0.01, n_estimators=500, subsample=0.7, max_depth=7) +avg_bss = evaluate(bs_ref, model, trainX, trainy, testX, testy) +scores.append(avg_bss) +names.append('all') +# plot results +pyplot.boxplot(scores, labels=names) +pyplot.show() +``` + +运行该示例为每个配置打印 10 个运行的平均每个模型的 BSS。 + +结果表明,单独学习率和树木数量的变化引起了对默认配置的一些提升。 + +结果还表明,包含每个变化的“所有”配置导致最佳平均 BSS。 + +``` +>, bss=0.119972 +>, bss=0.145596 +>, bss=0.095871 +>, bss=0.192175 +``` + +创建来自每个配置的 BSS 分数的框和胡须图。我们可以看到包含所有更改的配置明显优于基线模型和其他配置组合。 + +也许通过对模型进行微调的参数可以获得进一步的收益。 + +![Box and whisker plot of tuned GBM models showing BSS scores on the test set](img/36b4fc3c00fd972bf66949204672c737.jpg) + +调谐 GBM 模型的框和胡须图显示测试集上的 BSS 分数 + +有必要掌握论文中描述的参数模型以及使用它所需的数据,以便将其技能与最终模型的技能进行比较。 + +## 扩展 + +本节列出了一些扩展您可能希望探索的教程的想法。 + +* 探索使用前几天观察结果的模型框架。 +* 探索使用 ROC 曲线图和 ROC AUC 测量的模型评估。 +* 网格搜索梯度提升模型参数,并可能探索其他实现,如 XGBoost。 + +如果你探索任何这些扩展,我很想知道。 + +## 进一步阅读 + +如果您希望深入了解,本节将提供有关该主题的更多资源。 + +* [臭氧水平检测数据集,UCI 机器学习库](https://archive.ics.uci.edu/ml/datasets/ozone+level+detection)。 +* [预测偏差随机臭氧天数:分析和解决方案](https://ieeexplore.ieee.org/abstract/document/4053100/),2006 年。 +* [预测有偏差的随机臭氧天数:分析,解决方案以及](https://link.springer.com/article/10.1007/s10115-007-0095-1),2008 年。 +* [CAWCR 验证页面](http://www.cawcr.gov.au/projects/verification/) +* 维基百科上的[接收器操作特性](https://en.wikipedia.org/wiki/Receiver_operating_characteristic) + +## 摘要 + +在本教程中,您了解了如何开发概率预测模型来预测德克萨斯州休斯顿的空气污染。 + +具体来说,你学到了: + +* 如何加载和准备臭氧日标准机器学习预测建模问题。 +* 如何开发一个天真的预测模型,并使用 Brier 技能分数评估预测。 +* 如何使用决策树集合开发技巧模型,并通过成功模型的超参数调整进一步提高技能。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/how-to-develop-a-skilful-time-series-forecasting-model.md b/docs/dl-ts/how-to-develop-a-skilful-time-series-forecasting-model.md new file mode 100644 index 0000000000000000000000000000000000000000..6341441bef2ad566beef369ccb59bce5a7e54001 --- /dev/null +++ b/docs/dl-ts/how-to-develop-a-skilful-time-series-forecasting-model.md @@ -0,0 +1,251 @@ +# 如何开发一种熟练的机器学习时间序列预测模型 + +> 原文: [https://machinelearningmastery.com/how-to-develop-a-skilful-time-series-forecasting-model/](https://machinelearningmastery.com/how-to-develop-a-skilful-time-series-forecasting-model/) + +您交给数据并告知他们开发预测模型。 + +_ 你做什么的?_ + +这是一种常见的情况;远比大多数人想象的要普遍。 + +* 也许您会收到一个 CSV 文件。 +* 也许您可以访问数据库。 +* 也许你正在开始比赛。 + +问题可以合理地定义: + +* 您拥有或可以访问历史时间序列数据。 +* 您知道或可以找出需要预测的内容。 +* 您知道或者可以了解在评估候选模型时最重要的是什么。 + +那么你如何解决这个问题呢? + +除非你经历过这场审判,否则你可能会挣扎。 + +* 你可能很难,因为你是机器学习和时间序列领域的新手。 +* 即使您拥有机器学习经验,也可能会因为时间序列数据不同而挣扎。 +* 即使您具有时间序列预测背景,您也可能会遇到困难,因为机器学习方法可能优于您的数据的传统方法。 + +在所有这些情况下,您将从仔细和系统地解决问题中受益。 + +在这篇文章中,我想给你一个特定的,可操作的程序,你可以用它来处理你的时间序列预测问题。 + +让我们开始吧。 + +![How to Develop a Skilful Time Series Forecasting Model](img/e093b5862dc4ceffded85d788a5e9c20.jpg) + +如何开发熟练的时间序列预测模型 +照片由[制作肯尼亚](https://www.flickr.com/photos/makeitkenya/22036888582/),保留一些权利。 + +## 流程概述 + +这个过程的目标是尽可能快地获得“_ 足够好的 _”预测模型。 + +这个过程可能会或可能不会提供最好的模型,但它会提供一个好的模型:一个比基线预测更好的模型,如果存在这样的模型。 + +通常,此过程将提供的模型占问题可达到的 80%到 90%。 + +这个过程很快。因此,它专注于自动化。基于仔细分析,搜索超参数而不是指定超参数。我们鼓励您并行测试模型套件,快速了解哪些有效,哪些无效。 + +尽管如此,这个过程非常灵活,如果您有时间和资源,您可以在给定的步骤中循环或尽可能深入。 + +这个过程分为四个部分;他们是: + +1. 定义问题 +2. 设计测试线束 +3. 测试模型 +4. 完成模型 + +您会注意到该过程不同于预测建模问题的经典线性工作。这是因为它旨在快速获得工作预测模型,然后放慢速度,看看是否可以获得更好的模型。 + +您处理新的时间序列预测问题的过程是什么? +在评论中分享。 + +## 如何使用此过程 + +最大的错误是跳过步骤。 + +例如,几乎所有初学者所犯的错误就是直接进行建模,而没有深入了解正在解决的问题或如何稳健地评估候选解决方案。这几乎总会导致大量的浪费时间。 + +慢下来,按照流程,完成每一步。 + +我建议为每个可以随时重新运行的实验提供单独的代码。 + +这很重要,以便您在发现错误,修复代码和重新运行实验时可以回退。您正在运行实验并快速迭代,但如果您是草率的,那么您不能相信任何结果。在设计用于评估候选模型的测试工具时,这一点尤其重要。 + +让我们仔细看看这个过程的每一步。 + +## 1.定义问题 + +定义您的时间序列问题。 + +在每个主题中要考虑和激发问题的一些主题如下: + +1. 输入与输出 + 1. 预测的输入和输出是什么? +2. 内生与外生 + 1. 什么是内生和外生变量? +3. 非结构化与结构化 + 1. 时间序列变量是非结构化的还是结构化的? +4. 回归与分类 + 1. 您正在研究回归或分类预测建模问题吗? + 2. 有哪些方法来构建时间序列预测问题? +5. 单变量与多变量 + 1. 您是在处理单变量或多变量时间序列问题吗? +6. 单步与多步 + 1. 您需要单步骤还是多步骤预测? +7. 静态与动态 + 1. 您需要静态或动态更新的模型吗? + +即使您必须估计或猜测,也要回答每个问题。 + +一些有用的工具可以帮助您获得答案: + +* 数据可视化(例如线图等)。 +* 统计分析(例如 ACF / PACF 图等)。 +* 领域专家。 +* 项目利益相关 + +在了解更多信息后,请更新这些问题的答案。 + +## 2.设计测试线束 + +设计可用于评估候选模型的测试工具。 + +这包括用于估计模型技能的方法和用于评估预测的度量。 + +如果您正在寻找想法,下面是一个常见的时间序列预测模型评估方案: + +1. 将数据集拆分为火车和测试集。 +2. 在训练数据集上拟合候选方法。 +3. 直接对测试集进行预测或使用前向验证。 +4. 计算将预测与预期值进行比较的指标。 + +测试工具必须坚固耐用,您必须完全信任它提供的结果。 + +一个重要的考虑因素是确保用于数据准备的任何系数仅从训练数据集中估算,然后应用于测试集。这可能包括数据标准化的平均值和标准差。 + +## 3.测试模型 + +使用您的测试工具测试许多模型。 + +我建议您仔细设计实验,以测试标准模型的一套配置并让它们运行。每个实验都可以将结果记录到文件中,以便您可以快速发现每次运行中最前三到五个最熟练的配置。 + +您可以设计实验的一些常见方法类别包括: + +* 基线。 + * 持久性(网格搜索持久的滞后观察) + * 滚动移动平均线。 + * ... +* 自回归。 + * ARMA 用于固定数据。 + * ARIMA 用于趋势数据。 + * SARIMA 提供季节性数据。 + * ... +* 指数平滑。 + * 简单的平滑 + * Holt Winters 平滑 + * ... +* 线性机器学习。 + * 线性回归 + * 岭回归 + * 套索回归 + * 弹性网络回归 + * ...。 +* 非线性机器学习。 + * k-最近邻居 + * 分类和回归树 + * 支持向量回归 + * ... +* 合奏机器学习。 + * 套袋 + * 推进 + * 随机森林 + * 梯度提升 + * ... +* 深度学习。 + * MLP + * CNN + * LSTM + * 杂种 + * ... + +此列表基于单变量时间序列预测问题,但您可以根据问题的具体情况对其进行调整,例如:使用 VAR / VARMA /等。在多变量时间序列预测的情况下。 + +根据您的需要,插入更多您最喜欢的经典时间序列预测方法和机器学习方法。 + +这里的顺序很重要,其结构越来越复杂,从古典到现代的方法。早期的方法很简单,快速提供良好的结果;后来的方法更慢,更复杂,但也有更高的标准,以明确熟练。 + +由此产生的模型技能可用于棘轮。例如,最佳持久性配置的技能提供了所有其他模型必须超越的基线技能。如果自回归模型比持久性更好,那么它就会成为一个优秀的新级别,以便将方法视为技巧。 + +理想情况下,您希望在进入下一个级别之前耗尽每个级别。例如。充分利用自回归方法,并在继续使用指数平滑方法之前,将结果用作新基线来定义“熟练”。 + +我最后深入学习,因为通常神经网络在时间序列预测方面很差,但在这方面仍有很大的改进和实验空间。 + +您拥有的时间和资源越多,您可以评估的配置就越多。 + +例如,有了更多的时间和资源,您可以: + +* 在已知已经表现良好的配置周围以更精细的分辨率搜索模型配置。 +* 搜索更多模型超参数配置。 +* 使用分析在要搜索的模型超参数上设置更好的边界。 +* 使用领域知识更好地准备数据或设计输入功能。 +* 探索不同的潜在更复杂的方法。 +* 探索表现良好的基础模型的集合。 + +我还鼓励您将数据准备方案作为模型运行的超参数包括在内。 + +一些方法将执行一些基本数据准备,例如 ARIMA 中的差分,然而,通常不清楚究竟需要什么数据准备方案或方案组合来最佳地将数据集呈现给建模算法。而不是猜测,网格搜索并根据实际结果决定。 + +一些要考虑的数据准备方案包括: + +* 差异化以消除趋势。 +* 季节性差异以消除季节性。 +* 标准化为中心。 +* 标准化为重新缩放。 +* 电源转换使正常。 + +如此多的搜索可能会很慢。 + +加快模型评估的一些想法包括: + +* 通过云硬件(例如 Amazon EC2)并行使用多台计算机。 +* 减小列车或测试数据集的大小,以使评估过程更快。 +* 如果以后有时间,请使用更粗糙的超参数网格并圈回。 +* 也许不要为前进验证中的每个步骤重新设计模型。 + +## 4.完成模型 + +在上一个时间步骤结束时,您知道您的时间序列是否可预测。 + +如果它是可预测的,您将拥有一个熟悉该问题的前 5 到 10 个候选模型的列表。 + +您可以选择一个或多个模型并完成它们。这包括在所有可用的历史数据(训练和测试)上训练新的最终模型。 + +该模型已准备就绪;例如: + +* 预测未来。 +* 将模型保存到文件以供以后用于进行预测。 +* 将模型合并到用于进行预测的软件中。 + +如果您有时间,可以随时回到上一步,看看是否可以进一步改进最终模型。 + +如果数据随时间显着变化,则可能需要定期进行此操作。 + +## 进一步阅读 + +如果您希望深入了解,本节将提供有关该主题的更多资源。 + +* [什么是时间序列预测?](https://machinelearningmastery.com/time-series-forecasting/) +* [如何通过时间序列预测项目](https://machinelearningmastery.com/work-time-series-forecast-project/) +* [如何训练最终机器学习模型](https://machinelearningmastery.com/train-final-machine-learning-model/) + +## 摘要 + +在这篇文章中,您发现了一个简单的四步过程,您可以使用该过程快速发现针对时间序列预测问题的熟练预测模型。 + +你觉得这个过程有用吗? +请在下面告诉我。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/how-to-develop-an-autoregression-forecast-model-for-household-electricity-consumption.md b/docs/dl-ts/how-to-develop-an-autoregression-forecast-model-for-household-electricity-consumption.md new file mode 100644 index 0000000000000000000000000000000000000000..17f067da98e3e4ca01eb543e608c4714b5625e41 --- /dev/null +++ b/docs/dl-ts/how-to-develop-an-autoregression-forecast-model-for-household-electricity-consumption.md @@ -0,0 +1,770 @@ +# 如何构建家庭用电自回归预测模型 + +> 原文: [https://machinelearningmastery.com/how-to-develop-an-autoregression-forecast-model-for-household-electricity-consumption/](https://machinelearningmastery.com/how-to-develop-an-autoregression-forecast-model-for-household-electricity-consumption/) + +鉴于智能电表的兴起以及太阳能电池板等发电技术的广泛采用,可提供大量的用电数据。 + +该数据代表了多变量时间序列的功率相关变量,而这些变量又可用于建模甚至预测未来的电力消耗。 + +自相关模型非常简单,可以提供快速有效的方法,对电力消耗进行熟练的一步和多步预测。 + +在本教程中,您将了解如何开发和评估用于多步预测家庭功耗的自回归模型。 + +完成本教程后,您将了解: + +* 如何创建和分析单变量时间序列数据的自相关和部分自相关图。 +* 如何使用自相关图中的结果来配置自动回归模型。 +* 如何开发和评估用于进行一周预测的自相关模型。 + +让我们开始吧。 + +![How to Develop an Autoregression Forecast Model for Household Electricity Consumption](https://3qeqpr26caki16dnhd19sv6by6v-wpengine.netdna-ssl.com/wp-content/uploads/2018/10/How-to-Develop-an-Autoregression-Forecast-Model-for-Household-Electricity-Consumption.jpg) + +如何制定家庭用电的自回归预测模型 +[wongaboo](https://www.flickr.com/photos/27146806@N00/22122826108/) 的照片,保留一些权利。 + +## 教程概述 + +本教程分为五个部分;他们是: + +1. 问题描述 +2. 加载并准备数据集 +3. 模型评估 +4. 自相关分析 +5. 开发自回归模型 + +## 问题描述 + +'[家庭用电量](https://archive.ics.uci.edu/ml/datasets/individual+household+electric+power+consumption)'数据集是一个多变量时间序列数据集,描述了四年内单个家庭的用电量。 + +该数据是在 2006 年 12 月至 2010 年 11 月之间收集的,并且每分钟收集家庭内的能耗观察结果。 + +它是一个多变量系列,由七个变量组成(除日期和时间外);他们是: + +* **global_active_power** :家庭消耗的总有功功率(千瓦)。 +* **global_reactive_power** :家庭消耗的总无功功率(千瓦)。 +* **电压**:平均电压(伏特)。 +* **global_intensity** :平均电流强度(安培)。 +* **sub_metering_1** :厨房的有功电能(瓦特小时的有功电能)。 +* **sub_metering_2** :用于洗衣的有功能量(瓦特小时的有功电能)。 +* **sub_metering_3** :气候控制系统的有功电能(瓦特小时的有功电能)。 + +有功和无功电能参考[交流电](https://en.wikipedia.org/wiki/AC_power)的技术细节。 + +可以通过从总活动能量中减去三个定义的子计量变量的总和来创建第四个子计量变量,如下所示: + +``` +sub_metering_remainder = (global_active_power * 1000 / 60) - (sub_metering_1 + sub_metering_2 + sub_metering_3) +``` + +## 加载并准备数据集 + +数据集可以从 UCI 机器学习库下载为单个 20 兆字节的.zip 文件: + +* [household_power_consumption.zip](https://archive.ics.uci.edu/ml/machine-learning-databases/00235/household_power_consumption.zip) + +下载数据集并将其解压缩到当前工作目录中。您现在将拥有大约 127 兆字节的文件“ _household_power_consumption.txt_ ”并包含所有观察结果。 + +我们可以使用 _read_csv()_ 函数来加载数据,并将前两列合并到一个日期时间列中,我们可以将其用作索引。 + +``` +# load all data +dataset = read_csv('household_power_consumption.txt', sep=';', header=0, low_memory=False, infer_datetime_format=True, parse_dates={'datetime':[0,1]}, index_col=['datetime']) +``` + +接下来,我们可以用'_ 标记所有[缺失值](https://machinelearningmastery.com/handle-missing-timesteps-sequence-prediction-problems-python/)?_ '具有 _NaN_ 值的字符,这是一个浮点数。 + +这将允许我们将数据作为一个浮点值数组而不是混合类型(效率较低)。 + +``` +# mark all missing values +dataset.replace('?', nan, inplace=True) +# make dataset numeric +dataset = dataset.astype('float32') +``` + +我们还需要填写缺失值,因为它们已被标记。 + +一种非常简单的方法是从前一天的同一时间复制观察。我们可以在一个名为 _fill_missing()_ 的函数中实现它,该函数将从 24 小时前获取数据的 NumPy 数组并复制值。 + +``` +# fill missing values with a value at the same time one day ago +def fill_missing(values): + one_day = 60 * 24 + for row in range(values.shape[0]): + for col in range(values.shape[1]): + if isnan(values[row, col]): + values[row, col] = values[row - one_day, col] +``` + +我们可以将此函数直接应用于 DataFrame 中的数据。 + +``` +# fill missing +fill_missing(dataset.values) +``` + +现在,我们可以使用上一节中的计算创建一个包含剩余子计量的新列。 + +``` +# add a column for for the remainder of sub metering +values = dataset.values +dataset['sub_metering_4'] = (values[:,0] * 1000 / 60) - (values[:,4] + values[:,5] + values[:,6]) +``` + +我们现在可以将清理后的数据集版本保存到新文件中;在这种情况下,我们只需将文件扩展名更改为.csv,并将数据集保存为“ _household_power_consumption.csv_ ”。 + +``` +# save updated dataset +dataset.to_csv('household_power_consumption.csv') +``` + +将所有这些结合在一起,下面列出了加载,清理和保存数据集的完整示例。 + +``` +# load and clean-up data +from numpy import nan +from numpy import isnan +from pandas import read_csv +from pandas import to_numeric + +# fill missing values with a value at the same time one day ago +def fill_missing(values): + one_day = 60 * 24 + for row in range(values.shape[0]): + for col in range(values.shape[1]): + if isnan(values[row, col]): + values[row, col] = values[row - one_day, col] + +# load all data +dataset = read_csv('household_power_consumption.txt', sep=';', header=0, low_memory=False, infer_datetime_format=True, parse_dates={'datetime':[0,1]}, index_col=['datetime']) +# mark all missing values +dataset.replace('?', nan, inplace=True) +# make dataset numeric +dataset = dataset.astype('float32') +# fill missing +fill_missing(dataset.values) +# add a column for for the remainder of sub metering +values = dataset.values +dataset['sub_metering_4'] = (values[:,0] * 1000 / 60) - (values[:,4] + values[:,5] + values[:,6]) +# save updated dataset +dataset.to_csv('household_power_consumption.csv') +``` + +运行该示例将创建新文件' _household_power_consumption.csv_ ',我们可以将其用作建模项目的起点。 + +## 模型评估 + +在本节中,我们将考虑如何开发和评估家庭电力数据集的预测模型。 + +本节分为四个部分;他们是: + +1. 问题框架 +2. 评估指标 +3. 训练和测试集 +4. 前瞻性验证 + +### 问题框架 + +有许多方法可以利用和探索家庭用电量数据集。 + +在本教程中,我们将使用这些数据来探索一个非常具体的问题;那是: + +> 鉴于最近的耗电量,未来一周的预期耗电量是多少? + +这要求预测模型预测未来七天每天的总有功功率。 + +从技术上讲,考虑到多个预测步骤,这个问题的框架被称为多步骤时间序列预测问题。利用多个输入变量的模型可以称为多变量多步时间序列预测模型。 + +这种类型的模型在规划支出方面可能有助于家庭。在供应方面,它也可能有助于规划特定家庭的电力需求。 + +数据集的这种框架还表明,将每分钟功耗的观察结果下采样到每日总数是有用的。这不是必需的,但考虑到我们对每天的总功率感兴趣,这是有道理的。 + +我们可以使用 pandas DataFrame 上的 [resample()函数](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.resample.html)轻松实现这一点。使用参数' _D_ '调用此函数允许按日期时间索引的加载数据按天分组([查看所有偏移别名](http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases))。然后,我们可以计算每天所有观测值的总和,并为八个变量中的每一个创建每日耗电量数据的新数据集。 + +下面列出了完整的示例。 + +``` +# resample minute data to total for each day +from pandas import read_csv +# load the new file +dataset = read_csv('household_power_consumption.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +# resample data to daily +daily_groups = dataset.resample('D') +daily_data = daily_groups.sum() +# summarize +print(daily_data.shape) +print(daily_data.head()) +# save +daily_data.to_csv('household_power_consumption_days.csv') +``` + +运行该示例将创建一个新的每日总功耗数据集,并将结果保存到名为“ _household_power_consumption_days.csv_ ”的单独文件中。 + +我们可以将其用作数据集,用于拟合和评估所选问题框架的预测模型。 + +### 评估指标 + +预测将包含七个值,一个用于一周中的每一天。 + +多步预测问题通常分别评估每个预测时间步长。这有助于以下几个原因: + +* 在特定提前期评论技能(例如+1 天 vs +3 天)。 +* 在不同的交付时间基于他们的技能对比模型(例如,在+1 天的模型和在日期+5 的模型良好的模型)。 + +总功率的单位是千瓦,并且具有也在相同单位的误差度量将是有用的。均方根误差(RMSE)和平均绝对误差(MAE)都符合这个要求,尽管 RMSE 更常用,将在本教程中采用。与 MAE 不同,RMSE 更能预测预测误差。 + +此问题的表现指标是从第 1 天到第 7 天的每个提前期的 RMSE。 + +作为捷径,使用单个分数总结模型的表现以帮助模型选择可能是有用的。 + +可以使用的一个可能的分数是所有预测天数的 RMSE。 + +下面的函数 _evaluate_forecasts()_ 将实现此行为并基于多个七天预测返回模型的表现。 + +``` +# evaluate one or more weekly forecasts against expected values +def evaluate_forecasts(actual, predicted): + scores = list() + # calculate an RMSE score for each day + for i in range(actual.shape[1]): + # calculate mse + mse = mean_squared_error(actual[:, i], predicted[:, i]) + # calculate rmse + rmse = sqrt(mse) + # store + scores.append(rmse) + # calculate overall RMSE + s = 0 + for row in range(actual.shape[0]): + for col in range(actual.shape[1]): + s += (actual[row, col] - predicted[row, col])**2 + score = sqrt(s / (actual.shape[0] * actual.shape[1])) + return score, scores +``` + +运行该函数将首先返回整个 RMSE,无论白天,然后每天返回一系列 RMSE 分数。 + +### 训练和测试集 + +我们将使用前三年的数据来训练预测模型和评估模型的最后一年。 + +给定数据集中的数据将分为标准周。这些是从周日开始到周六结束的周。 + +这是使用所选模型框架的现实且有用的方法,其中可以预测未来一周的功耗。它也有助于建模,其中模型可用于预测特定日期(例如星期三)或整个序列。 + +我们将数据拆分为标准周,从测试数据集向后工作。 + +数据的最后一年是 2010 年,2010 年的第一个星期日是 1 月 3 日。数据于 2010 年 11 月中旬结束,数据中最接近的最后一个星期六是 11 月 20 日。这给出了 46 周的测试数据。 + +下面提供了测试数据集的每日数据的第一行和最后一行以供确认。 + +``` +2010-01-03,2083.4539999999984,191.61000000000055,350992.12000000034,8703.600000000033,3842.0,4920.0,10074.0,15888.233355799992 +... +2010-11-20,2197.006000000004,153.76800000000028,346475.9999999998,9320.20000000002,4367.0,2947.0,11433.0,17869.76663959999 +``` + +每日数据从 2006 年底开始。 + +数据集中的第一个星期日是 12 月 17 日,这是第二行数据。 + +将数据组织到标准周内为训练预测模型提供了 159 个完整的标准周。 + +``` +2006-12-17,3390.46,226.0059999999994,345725.32000000024,14398.59999999998,2033.0,4187.0,13341.0,36946.66673200004 +... +2010-01-02,1309.2679999999998,199.54600000000016,352332.8399999997,5489.7999999999865,801.0,298.0,6425.0,14297.133406600002 +``` + +下面的函数 _split_dataset()_ 将每日数据拆分为训练集和测试集,并将每个数据组织成标准周。 + +使用特定行偏移来使用数据集的知识来分割数据。然后使用 NumPy [split()函数](https://docs.scipy.org/doc/numpy/reference/generated/numpy.split.html)将分割数据集组织成每周数据。 + +``` +# split a univariate dataset into train/test sets +def split_dataset(data): + # split into standard weeks + train, test = data[1:-328], data[-328:-6] + # restructure into windows of weekly data + train = array(split(train, len(train)/7)) + test = array(split(test, len(test)/7)) + return train, test +``` + +我们可以通过加载每日数据集并打印列车和测试集的第一行和最后一行数据来测试此功能,以确认它们符合上述预期。 + +完整的代码示例如下所示。 + +``` +# split into standard weeks +from numpy import split +from numpy import array +from pandas import read_csv + +# split a univariate dataset into train/test sets +def split_dataset(data): + # split into standard weeks + train, test = data[1:-328], data[-328:-6] + # restructure into windows of weekly data + train = array(split(train, len(train)/7)) + test = array(split(test, len(test)/7)) + return train, test + +# load the new file +dataset = read_csv('household_power_consumption_days.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +train, test = split_dataset(dataset.values) +# validate train data +print(train.shape) +print(train[0, 0, 0], train[-1, -1, 0]) +# validate test +print(test.shape) +print(test[0, 0, 0], test[-1, -1, 0]) +``` + +运行该示例表明,列车数据集确实有 159 周的数据,而测试数据集有 46 周。 + +我们可以看到,第一行和最后一行的列车和测试数据集的总有效功率与我们定义为每组标准周界限的特定日期的数据相匹配。 + +``` +(159, 7, 8) +3390.46 1309.2679999999998 +(46, 7, 8) +2083.4539999999984 2197.006000000004 +``` + +### 前瞻性验证 + +将使用称为[前进验证](https://machinelearningmastery.com/backtest-machine-learning-models-time-series-forecasting/)的方案评估模型。 + +这是需要模型进行一周预测的地方,然后该模型的实际数据可用于模型,以便它可以用作在随后一周进行预测的基础。这对于如何在实践中使用模型以及对模型有益而使其能够利用最佳可用数据都是现实的。 + +我们可以通过分离输入数据和输出/预测数据来证明这一点。 + +``` +Input, Predict +[Week1] Week2 +[Week1 + Week2] Week3 +[Week1 + Week2 + Week3] Week4 +... +``` + +评估此数据集上的预测模型的前瞻性验证方法在下面实现,命名为 _evaluate_model()_。 + +为模型提供函数的名称作为参数“ _model_func_ ”。该功能负责定义模型,使模型适合训练数据,并进行一周的预测。 + +然后使用先前定义的 _evaluate_forecasts()_ 函数,针对测试数据集评估模型所做的预测。 + +``` +# evaluate a single model +def evaluate_model(model_func, train, test): + # history is a list of weekly data + history = [x for x in train] + # walk-forward validation over each week + predictions = list() + for i in range(len(test)): + # predict the week + yhat_sequence = model_func(history) + # store the predictions + predictions.append(yhat_sequence) + # get real observation and add to history for predicting the next week + history.append(test[i, :]) + predictions = array(predictions) + # evaluate predictions days for each week + score, scores = evaluate_forecasts(test[:, :, 0], predictions) + return score, scores +``` + +一旦我们对模型进行评估,我们就可以总结表现。 + +以下名为 _summarize_scores()_ 的函数将模型的表现显示为单行,以便与其他模型进行比较。 + +``` +# summarize scores +def summarize_scores(name, score, scores): + s_scores = ', '.join(['%.1f' % s for s in scores]) + print('%s: [%.3f] %s' % (name, score, s_scores)) +``` + +我们现在已经开始评估数据集上的预测模型的所有元素。 + +## 自相关分析 + +统计相关性总结了两个变量之间关系的强度。 + +我们可以假设每个变量的分布符合[高斯](https://machinelearningmastery.com/statistical-data-distributions/)(钟形曲线)分布。如果是这种情况,我们可以使用 Pearson 相关系数来总结变量之间的相关性。 + +Pearson 相关系数是介于-1 和 1 之间的数字,分别描述了负相关或正相关。值为零表示没有相关性。 + +我们可以计算时间序列观测值与之前时间步长的观测值之间的相关性,称为滞后。因为时间序列观测值的相关性是使用先前时间的相同序列的值计算的,所以这称为序列相关或自相关。 + +滞后时间序列自相关的图称为[自相关函数](https://machinelearningmastery.com/gentle-introduction-autocorrelation-partial-autocorrelation/),或首字母缩略词 ACF。该图有时称为[相关图](https://en.wikipedia.org/wiki/Correlogram),或自相关图。 + +部分自相关函数或 PACF 是时间序列中的观察与先前时间步骤的观察与中间观察的关系被移除之间的关系的总结。 + +观察的自相关和先前时间步的观察包括直接相关和间接相关。这些间接相关性是观察相关性的线性函数,以及在中间时间步骤的观察。 + +部分自相关函数试图消除这些间接相关性。没有进入数学,这是部分自相关的直觉。 + +我们可以分别使用 [plot_acf()](http://www.statsmodels.org/dev/generated/statsmodels.graphics.tsaplots.plot_acf.html)和 [plot_pacf()](http://www.statsmodels.org/dev/generated/statsmodels.graphics.tsaplots.plot_pacf.html) statsmodels 函数计算自相关和部分自相关图。 + +为了计算和绘制自相关,我们必须将数据转换为单变量时间序列。具体而言,观察到每日消耗的总功率。 + +下面的 _to_series()_ 功能将多元数据划分为每周窗口,并返回单个单变量时间序列。 + +``` +# convert windows of weekly multivariate data into a series of total power +def to_series(data): + # extract just the total power from each week + series = [week[:, 0] for week in data] + # flatten into a single series + series = array(series).flatten() + return series +``` + +我们可以为准备好的训练数据集调用此函数。 + +首先,必须加载每日功耗数据集。 + +``` +# load the new file +dataset = read_csv('household_power_consumption_days.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +``` + +然后必须使用标准周窗口结构将数据集拆分为训练集和测试集。 + +``` +# split into train and test +train, test = split_dataset(dataset.values) +``` + +然后可以从训练数据集中提取每日功耗的单变量时间序列。 + +``` +# convert training data into a series +series = to_series(train) +``` + +然后我们可以创建一个包含 ACF 和 PACF 图的单个图。可以指定延迟时间步数。我们将此修复为每日观察一年或 365 天。 + +``` +# plots +pyplot.figure() +lags = 365 +# acf +axis = pyplot.subplot(2, 1, 1) +plot_acf(series, ax=axis, lags=lags) +# pacf +axis = pyplot.subplot(2, 1, 2) +plot_pacf(series, ax=axis, lags=lags) +# show plot +pyplot.show() +``` + +下面列出了完整的示例。 + +我们预计明天和未来一周消耗的电量将取决于前几天消耗的电量。因此,我们期望在 ACF 和 PACF 图中看到强烈的自相关信号。 + +``` +# acf and pacf plots of total power +from numpy import split +from numpy import array +from pandas import read_csv +from matplotlib import pyplot +from statsmodels.graphics.tsaplots import plot_acf +from statsmodels.graphics.tsaplots import plot_pacf + +# split a univariate dataset into train/test sets +def split_dataset(data): + # split into standard weeks + train, test = data[1:-328], data[-328:-6] + # restructure into windows of weekly data + train = array(split(train, len(train)/7)) + test = array(split(test, len(test)/7)) + return train, test + +# convert windows of weekly multivariate data into a series of total power +def to_series(data): + # extract just the total power from each week + series = [week[:, 0] for week in data] + # flatten into a single series + series = array(series).flatten() + return series + +# load the new file +dataset = read_csv('household_power_consumption_days.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +# split into train and test +train, test = split_dataset(dataset.values) +# convert training data into a series +series = to_series(train) +# plots +pyplot.figure() +lags = 365 +# acf +axis = pyplot.subplot(2, 1, 1) +plot_acf(series, ax=axis, lags=lags) +# pacf +axis = pyplot.subplot(2, 1, 2) +plot_pacf(series, ax=axis, lags=lags) +# show plot +pyplot.show() +``` + +运行该示例将创建一个包含 ACF 和 PACF 图的单个图。 + +这些地块非常密集,难以阅读。然而,我们或许可以看到熟悉的自回归模式。 + +我们也可能会在一年内看到一些重要的滞后观察结果。进一步调查可能暗示季节性自相关成分,这不是一个令人惊讶的发现。 + +![ACF and PACF plots for the univariate series of power consumption](img/1c33c0de491fa073f947e93d52b6b1a7.jpg) + +ACF 和 PACF 绘制了单变量系列功耗 + +我们可以放大绘图并将滞后观测的数量从 365 更改为 50。 + +``` +lags = 50 +``` + +使用此更改结果重新运行代码示例是绘图的放大版本,杂乱程度更低。 + +我们可以清楚地看到两个图中熟悉的自回归模式。该模式由两个元素组成: + +* **ACF** :随着滞后增加而缓慢降低的大量显着滞后观察。 +* **PACF** :随着滞后的增加,一些显着的滞后观察突然下降。 + +ACF 图表明存在强自相关分量,而 PACF 图表明该分量对于前七个滞后观察是不同的。 + +这表明一个好的起始模型将是 AR(7);这是一个自回归模型,有 7 个滞后观察值作为输入。 + +![Zoomed in ACF and PACF plots for the univariate series of power consumption](img/e6b3e3acd0f040f57f08da354ba4b3b3.jpg) + +在 ACF 和 PACF 图中放大了单变量系列的功耗 + +## 开发自回归模型 + +我们可以为单变量的日常功耗系列开发自回归模型。 + +Statsmodels 库提供了多种开发 AR 模型的方法,例如使用 AR,ARMA,ARIMA 和 SARIMAX 类。 + +我们将使用 [ARIMA 实现](http://www.statsmodels.org/dev/generated/statsmodels.tsa.arima_model.ARIMA.html),因为它允许轻松扩展到差分和移动平均值。 + +首先,必须将包含数周先前观察的历史数据转换为每日功耗的单变量时间序列。我们可以使用上一节中开发的 _to_series()_ 函数。 + +``` +# convert history into a univariate series +series = to_series(history) +``` + +接下来,可以通过将参数传递给 ARIMA 类的构造函数来定义 ARIMA 模型。 + +我们将指定 AR(7)模型,其在 ARIMA 表示法中是 ARIMA(7,0,0)。 + +``` +# define the model +model = ARIMA(series, order=(7,0,0)) +``` + +接下来,该模型可以适合训练数据。我们将使用默认值并在拟合期间通过设置 _disp = False_ 禁用所有调试信息。 + +``` +# fit the model +model_fit = model.fit(disp=False) +``` + +现在模型已经适合,我们可以做出预测。 + +可以通过调用 _predict()_ 函数并将其传递给相对于训练数据的日期或索引的间隔来进行预测。我们将使用从训练数据之外的第一个时间步开始的指数,并将其延长六天,总共提供超过训练数据集的七天预测期。 + +``` +# make forecast +yhat = model_fit.predict(len(series), len(series)+6) +``` + +我们可以将所有这些包含在名为 _arima_forecast()_ 的函数中,该函数获取历史记录并返回一周的预测。 + +``` +# arima forecast +def arima_forecast(history): + # convert history into a univariate series + series = to_series(history) + # define the model + model = ARIMA(series, order=(7,0,0)) + # fit the model + model_fit = model.fit(disp=False) + # make forecast + yhat = model_fit.predict(len(series), len(series)+6) + return yhat +``` + +此功能可直接用于前面描述的测试工具中。 + +下面列出了完整的示例。 + +``` +# arima forecast +from math import sqrt +from numpy import split +from numpy import array +from pandas import read_csv +from sklearn.metrics import mean_squared_error +from matplotlib import pyplot +from statsmodels.tsa.arima_model import ARIMA + +# split a univariate dataset into train/test sets +def split_dataset(data): + # split into standard weeks + train, test = data[1:-328], data[-328:-6] + # restructure into windows of weekly data + train = array(split(train, len(train)/7)) + test = array(split(test, len(test)/7)) + return train, test + +# evaluate one or more weekly forecasts against expected values +def evaluate_forecasts(actual, predicted): + scores = list() + # calculate an RMSE score for each day + for i in range(actual.shape[1]): + # calculate mse + mse = mean_squared_error(actual[:, i], predicted[:, i]) + # calculate rmse + rmse = sqrt(mse) + # store + scores.append(rmse) + # calculate overall RMSE + s = 0 + for row in range(actual.shape[0]): + for col in range(actual.shape[1]): + s += (actual[row, col] - predicted[row, col])**2 + score = sqrt(s / (actual.shape[0] * actual.shape[1])) + return score, scores + +# summarize scores +def summarize_scores(name, score, scores): + s_scores = ', '.join(['%.1f' % s for s in scores]) + print('%s: [%.3f] %s' % (name, score, s_scores)) + +# evaluate a single model +def evaluate_model(model_func, train, test): + # history is a list of weekly data + history = [x for x in train] + # walk-forward validation over each week + predictions = list() + for i in range(len(test)): + # predict the week + yhat_sequence = model_func(history) + # store the predictions + predictions.append(yhat_sequence) + # get real observation and add to history for predicting the next week + history.append(test[i, :]) + predictions = array(predictions) + # evaluate predictions days for each week + score, scores = evaluate_forecasts(test[:, :, 0], predictions) + return score, scores + +# convert windows of weekly multivariate data into a series of total power +def to_series(data): + # extract just the total power from each week + series = [week[:, 0] for week in data] + # flatten into a single series + series = array(series).flatten() + return series + +# arima forecast +def arima_forecast(history): + # convert history into a univariate series + series = to_series(history) + # define the model + model = ARIMA(series, order=(7,0,0)) + # fit the model + model_fit = model.fit(disp=False) + # make forecast + yhat = model_fit.predict(len(series), len(series)+6) + return yhat + +# load the new file +dataset = read_csv('household_power_consumption_days.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +# split into train and test +train, test = split_dataset(dataset.values) +# define the names and functions for the models we wish to evaluate +models = dict() +models['arima'] = arima_forecast +# evaluate each model +days = ['sun', 'mon', 'tue', 'wed', 'thr', 'fri', 'sat'] +for name, func in models.items(): + # evaluate and get scores + score, scores = evaluate_model(func, train, test) + # summarize scores + summarize_scores(name, score, scores) + # plot scores + pyplot.plot(days, scores, marker='o', label=name) +# show plot +pyplot.legend() +pyplot.show() +``` + +运行该示例首先在测试数据集上打印 AR(7)模型的表现。 + +我们可以看到该模型实现了大约 381 千瓦的总体 RMSE。 + +与天真的预测模型相比,该模型具有技巧,例如使用一年前同一时间的观测预测前一周的模型,其总体 RMSE 约为 465 千瓦。 + +``` +arima: [381.636] 393.8, 398.9, 357.0, 377.2, 393.9, 306.1, 432.2 +``` + +还创建了预测的线图,显示了预测的七个交付时间中每个时段的 RMSE(千瓦)。 + +我们可以看到一个有趣的模式。 + +我们可能会认为早期的提前期比以后的提前期更容易预测,因为每个连续提前期的误差都会增加。 + +相反,我们看到星期五(提前期+6)是最容易预测的,星期六(提前期+7)是预测中最具挑战性的。我们还可以看到剩余的交付周期在中高到 300 千瓦的范围内都有类似的误差。 + +![Line plot of ARIMA forecast error for each forecasted lead times](img/a01cdac1fac30a42a3377da41d8f3d90.jpg) + +每个预测提前期的 ARIMA 预测误差线图 + +## 扩展 + +本节列出了一些扩展您可能希望探索的教程的想法。 + +* **调整 ARIMA** 。没有调整 ARIMA 模型的参数。探索或搜索一套 ARIMA 参数(q,d,p),看看表现是否可以进一步提高。 +* **探索季节性 AR** 。探索是否可以通过包含季节性自回归元素来改善 AR 模型的表现。这可能需要使用 SARIMA 模型。 +* **探索数据准备**。该模型直接适用于原始数据。探索标准化或标准化甚至功率变换是否可以进一步提高 AR 模型的技能。 + +如果你探索任何这些扩展,我很想知道。 + +## 进一步阅读 + +如果您希望深入了解,本节将提供有关该主题的更多资源。 + +### API + +* [pandas.read_csv API](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_csv.html) +* [pandas.DataFrame.resample API](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.resample.html) +* [重采样偏移别名](http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases) +* [sklearn.metrics.mean_squared_error API](http://scikit-learn.org/stable/modules/generated/sklearn.metrics.mean_squared_error.html) +* [numpy.split API](https://docs.scipy.org/doc/numpy/reference/generated/numpy.split.html) +* [statsmodels.graphics.tsaplots.plot_acf API](http://www.statsmodels.org/dev/generated/statsmodels.graphics.tsaplots.plot_acf.html) +* [statsmodels.graphics.tsaplots.plot_pacf API](http://www.statsmodels.org/dev/generated/statsmodels.graphics.tsaplots.plot_pacf.html) +* [statsmodels.tsa.arima_model.ARIMA API](http://www.statsmodels.org/dev/generated/statsmodels.tsa.arima_model.ARIMA.html) + +### 用品 + +* [个人家庭用电数据集,UCI 机器学习库。](https://archive.ics.uci.edu/ml/datasets/individual+household+electric+power+consumption) +* [交流电源,维基百科。](https://en.wikipedia.org/wiki/AC_power) +* [Correlogram,维基百科。](https://en.wikipedia.org/wiki/Correlogram) + +## 摘要 + +在本教程中,您了解了如何开发和评估用于多步预测家庭功耗的自回归模型。 + +具体来说,你学到了: + +* 如何创建和分析单变量时间序列数据的自相关和部分自相关图。 +* 如何使用自相关图中的结果来配置自动回归模型。 +* 如何开发和评估用于进行一周预测的自相关模型。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/how-to-develop-autoregressive-forecasting-models-for-multi-step-air-pollution-time-series-forecasting.md b/docs/dl-ts/how-to-develop-autoregressive-forecasting-models-for-multi-step-air-pollution-time-series-forecasting.md new file mode 100644 index 0000000000000000000000000000000000000000..99492898036fa709ad937f27e35f10a952995325 --- /dev/null +++ b/docs/dl-ts/how-to-develop-autoregressive-forecasting-models-for-multi-step-air-pollution-time-series-forecasting.md @@ -0,0 +1,1809 @@ +# 如何开发多步空气污染时间序列预测的自回归预测模型 + +> 原文: [https://machinelearningmastery.com/how-to-develop-autoregressive-forecasting-models-for-multi-step-air-pollution-time-series-forecasting/](https://machinelearningmastery.com/how-to-develop-autoregressive-forecasting-models-for-multi-step-air-pollution-time-series-forecasting/) + +实时世界时间序列预测具有挑战性,其原因不仅限于问题特征,例如具有多个输入变量,需要预测多个时间步骤,以及需要对多个物理站点执行相同类型的预测。 + +EMC Data Science Global Hackathon 数据集或简称“空气质量预测”数据集描述了多个站点的天气状况,需要预测随后三天的空气质量测量结果。 + +在深入研究时间序列预测的复杂机器学习和深度学习方法之前,重要的是要找到经典方法的局限性,例如使用 AR 或 ARIMA 方法开发自回归模型。 + +在本教程中,您将了解如何为多变量空气污染时间序列开发多步时间序列预测的自回归模型。 + +完成本教程后,您将了解: + +* 如何分析和计算时间序列数据的缺失值。 +* 如何开发和评估多步时间序列预测的自回归模型。 +* 如何使用备用数据插补方法改进自回归模型。 + +让我们开始吧。 + +![Impact of Dataset Size on Deep Learning Model Skill And Performance Estimates](img/d64e07469bc222ad87d3880144fb3396.jpg) + +数据集大小对深度学习模型技能和表现估计的影响 +照片由 [Eneas De Troya](https://www.flickr.com/photos/eneas/13632855754/) ,保留一些权利。 + +## 教程概述 + +本教程分为六个部分;他们是: + +1. 问题描述 +2. 模型评估 +3. 数据分析 +4. 开发自回归模型 +5. 具有全球归因策略的自回归模型 + +## 问题描述 + +空气质量预测数据集描述了多个地点的天气状况,需要预测随后三天的空气质量测量结果。 + +具体而言,对于多个站点,每小时提供 8 天的温度,压力,风速和风向等天气观测。目标是预测未来 3 天在多个地点的空气质量测量。预测的提前期不是连续的;相反,必须在 72 小时预测期内预测特定提前期。他们是: + +``` ++1, +2, +3, +4, +5, +10, +17, +24, +48, +72 +``` + +此外,数据集被划分为不相交但连续的数据块,其中 8 天的数据随后是需要预测的 3 天。 + +并非所有站点或块都可以获得所有观察结果,并且并非所有站点和块都可以使用所有输出变量。必须解决大部分缺失数据。 + +该数据集被用作 2012 年 Kaggle 网站上[短期机器学习竞赛](https://www.kaggle.com/c/dsg-hackathon)(或黑客马拉松)的基础。 + +根据从参与者中扣留的真实观察结果评估竞赛的提交,并使用平均绝对误差(MAE)进行评分。提交要求在由于缺少数据而无法预测的情况下指定-1,000,000 的值。实际上,提供了一个插入缺失值的模板,并且要求所有提交都采用(模糊的是什么)。 + +获胜者在滞留测试集([私人排行榜](https://www.kaggle.com/c/dsg-hackathon/leaderboard))上使用随机森林在滞后观察中获得了 0.21058 的 MAE。该帖子中提供了此解决方案的说明: + +* [把所有东西都扔进随机森林:Ben Hamner 赢得空气质量预测黑客马拉松](http://blog.kaggle.com/2012/05/01/chucking-everything-into-a-random-forest-ben-hamner-on-winning-the-air-quality-prediction-hackathon/),2012。 + +在本教程中,我们将探索如何为可用作基线的问题开发天真预测,以确定模型是否具有该问题的技能。 + +## 模型评估 + +在我们评估天真的预测方法之前,我们必须开发一个测试工具。 + +这至少包括如何准备数据以及如何评估预测。 + +### 加载数据集 + +第一步是下载数据集并将其加载到内存中。 + +数据集可以从 Kaggle 网站免费下载。您可能必须创建一个帐户并登录才能下载数据集。 + +下载整个数据集,例如“_ 将所有 _”下载到您的工作站,并使用名为' _AirQualityPrediction_ '的文件夹解压缩当前工作目录中的存档。 + +* [EMC 数据科学全球黑客马拉松(空气质量预测)数据](https://www.kaggle.com/c/dsg-hackathon/data) + +我们的重点将是包含训练数据集的' _TrainingData.csv_ '文件,特别是块中的数据,其中每个块是八个连续的观察日和目标变量。 + +我们可以使用 Pandas [read_csv()函数](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_csv.html)将数据文件加载到内存中,并在第 0 行指定标题行。 + +``` +# load dataset +dataset = read_csv('AirQualityPrediction/TrainingData.csv', header=0) +``` + +我们可以通过'chunkID'变量(列索引 1)对数据进行分组。 + +首先,让我们获取唯一的块标识符列表。 + +``` +chunk_ids = unique(values[:, 1]) +``` + +然后,我们可以收集每个块标识符的所有行,并将它们存储在字典中以便于访问。 + +``` +chunks = dict() +# sort rows by chunk id +for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks[chunk_id] = values[selection, :] +``` + +下面定义了一个名为 _to_chunks()_ 的函数,它接受加载数据的 NumPy 数组,并将 _chunk_id_ 的字典返回到块的行。 + +``` +# split the dataset by 'chunkID', return a dict of id to rows +def to_chunks(values, chunk_ix=1): + chunks = dict() + # get the unique chunk ids + chunk_ids = unique(values[:, chunk_ix]) + # group rows by chunk id + for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks[chunk_id] = values[selection, :] + return chunks +``` + +下面列出了加载数据集并将其拆分为块的完整示例。 + +``` +# load data and split into chunks +from numpy import unique +from pandas import read_csv + +# split the dataset by 'chunkID', return a dict of id to rows +def to_chunks(values, chunk_ix=1): + chunks = dict() + # get the unique chunk ids + chunk_ids = unique(values[:, chunk_ix]) + # group rows by chunk id + for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks[chunk_id] = values[selection, :] + return chunks + +# load dataset +dataset = read_csv('AirQualityPrediction/TrainingData.csv', header=0) +# group data by chunks +values = dataset.values +chunks = to_chunks(values) +print('Total Chunks: %d' % len(chunks)) +``` + +运行该示例将打印数据集中的块数。 + +``` +Total Chunks: 208 +``` + +### 数据准备 + +既然我们知道如何加载数据并将其拆分成块,我们就可以将它们分成训练和测试数据集。 + +尽管每个块内的实际观测数量可能差异很大,但每个块的每小时观察间隔为 8 天。 + +我们可以将每个块分成前五天的训练观察和最后三天的测试。 + +每个观察都有一行称为' _position_within_chunk_ ',从 1 到 192(8 天* 24 小时)不等。因此,我们可以将此列中值小于或等于 120(5 * 24)的所有行作为训练数据,将任何大于 120 的值作为测试数据。 + +此外,任何在列车或测试分割中没有任何观察的块都可以被丢弃,因为不可行。 + +在使用朴素模型时,我们只对目标变量感兴趣,而不对输入的气象变量感兴趣。因此,我们可以删除输入数据,并使列车和测试数据仅包含每个块的 39 个目标变量,以及块和观察时间内的位置。 + +下面的 _split_train_test()_ 函数实现了这种行为;给定一个块的字典,它将每个分成列车和测试块数据。 + +``` +# split each chunk into train/test sets +def split_train_test(chunks, row_in_chunk_ix=2): + train, test = list(), list() + # first 5 days of hourly observations for train + cut_point = 5 * 24 + # enumerate chunks + for k,rows in chunks.items(): + # split chunk rows by 'position_within_chunk' + train_rows = rows[rows[:,row_in_chunk_ix] <= cut_point, :] + test_rows = rows[rows[:,row_in_chunk_ix] > cut_point, :] + if len(train_rows) == 0 or len(test_rows) == 0: + print('>dropping chunk=%d: train=%s, test=%s' % (k, train_rows.shape, test_rows.shape)) + continue + # store with chunk id, position in chunk, hour and all targets + indices = [1,2,5] + [x for x in range(56,train_rows.shape[1])] + train.append(train_rows[:, indices]) + test.append(test_rows[:, indices]) + return train, test +``` + +我们不需要整个测试数据集;相反,我们只需要在三天时间内的特定提前期进行观察,特别是提前期: + +``` ++1, +2, +3, +4, +5, +10, +17, +24, +48, +72 +``` + +其中,每个提前期相对于训练期结束。 + +首先,我们可以将这些提前期放入函数中以便于参考: + +``` +# return a list of relative forecast lead times +def get_lead_times(): + return [1, 2 ,3, 4, 5, 10, 17, 24, 48, 72] +``` + +接下来,我们可以将测试数据集缩减为仅在首选提前期的数据。 + +我们可以通过查看' _position_within_chunk_ '列并使用提前期作为距离训练数据集末尾的偏移量来实现,例如: 120 + 1,120 +2 等 + +如果我们在测试集中找到匹配的行,则保存它,否则生成一行 NaN 观测值。 + +下面的函数 _to_forecasts()_ 实现了这一点,并为每个块的每个预测提前期返回一行 NumPy 数组。 + +``` +# convert the rows in a test chunk to forecasts +def to_forecasts(test_chunks, row_in_chunk_ix=1): + # get lead times + lead_times = get_lead_times() + # first 5 days of hourly observations for train + cut_point = 5 * 24 + forecasts = list() + # enumerate each chunk + for rows in test_chunks: + chunk_id = rows[0, 0] + # enumerate each lead time + for tau in lead_times: + # determine the row in chunk we want for the lead time + offset = cut_point + tau + # retrieve data for the lead time using row number in chunk + row_for_tau = rows[rows[:,row_in_chunk_ix]==offset, :] + # check if we have data + if len(row_for_tau) == 0: + # create a mock row [chunk, position, hour] + [nan...] + row = [chunk_id, offset, nan] + [nan for _ in range(39)] + forecasts.append(row) + else: + # store the forecast row + forecasts.append(row_for_tau[0]) + return array(forecasts) +``` + +我们可以将所有这些组合在一起并将数据集拆分为训练集和测试集,并将结果保存到新文件中。 + +完整的代码示例如下所示。 + +``` +# split data into train and test sets +from numpy import unique +from numpy import nan +from numpy import array +from numpy import savetxt +from pandas import read_csv + +# split the dataset by 'chunkID', return a dict of id to rows +def to_chunks(values, chunk_ix=1): + chunks = dict() + # get the unique chunk ids + chunk_ids = unique(values[:, chunk_ix]) + # group rows by chunk id + for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks[chunk_id] = values[selection, :] + return chunks + +# split each chunk into train/test sets +def split_train_test(chunks, row_in_chunk_ix=2): + train, test = list(), list() + # first 5 days of hourly observations for train + cut_point = 5 * 24 + # enumerate chunks + for k,rows in chunks.items(): + # split chunk rows by 'position_within_chunk' + train_rows = rows[rows[:,row_in_chunk_ix] <= cut_point, :] + test_rows = rows[rows[:,row_in_chunk_ix] > cut_point, :] + if len(train_rows) == 0 or len(test_rows) == 0: + print('>dropping chunk=%d: train=%s, test=%s' % (k, train_rows.shape, test_rows.shape)) + continue + # store with chunk id, position in chunk, hour and all targets + indices = [1,2,5] + [x for x in range(56,train_rows.shape[1])] + train.append(train_rows[:, indices]) + test.append(test_rows[:, indices]) + return train, test + +# return a list of relative forecast lead times +def get_lead_times(): + return [1, 2 ,3, 4, 5, 10, 17, 24, 48, 72] + +# convert the rows in a test chunk to forecasts +def to_forecasts(test_chunks, row_in_chunk_ix=1): + # get lead times + lead_times = get_lead_times() + # first 5 days of hourly observations for train + cut_point = 5 * 24 + forecasts = list() + # enumerate each chunk + for rows in test_chunks: + chunk_id = rows[0, 0] + # enumerate each lead time + for tau in lead_times: + # determine the row in chunk we want for the lead time + offset = cut_point + tau + # retrieve data for the lead time using row number in chunk + row_for_tau = rows[rows[:,row_in_chunk_ix]==offset, :] + # check if we have data + if len(row_for_tau) == 0: + # create a mock row [chunk, position, hour] + [nan...] + row = [chunk_id, offset, nan] + [nan for _ in range(39)] + forecasts.append(row) + else: + # store the forecast row + forecasts.append(row_for_tau[0]) + return array(forecasts) + +# load dataset +dataset = read_csv('AirQualityPrediction/TrainingData.csv', header=0) +# group data by chunks +values = dataset.values +chunks = to_chunks(values) +# split into train/test +train, test = split_train_test(chunks) +# flatten training chunks to rows +train_rows = array([row for rows in train for row in rows]) +# print(train_rows.shape) +print('Train Rows: %s' % str(train_rows.shape)) +# reduce train to forecast lead times only +test_rows = to_forecasts(test) +print('Test Rows: %s' % str(test_rows.shape)) +# save datasets +savetxt('AirQualityPrediction/naive_train.csv', train_rows, delimiter=',') +savetxt('AirQualityPrediction/naive_test.csv', test_rows, delimiter=',') +``` + +运行该示例首先评论了从数据集中移除了块 69 以获得不足的数据。 + +然后我们可以看到每个列车和测试集中有 42 列,一个用于块 ID,块内位置,一天中的小时和 39 个训练变量。 + +我们还可以看到测试数据集的显着缩小版本,其中行仅在预测前置时间。 + +新的训练和测试数据集分别保存在' _naive_train.csv_ '和' _naive_test.csv_ '文件中。 + +``` +>dropping chunk=69: train=(0, 95), test=(28, 95) +Train Rows: (23514, 42) +Test Rows: (2070, 42) +``` + +### 预测评估 + +一旦做出预测,就需要对它们进行评估。 + +在评估预测时,使用更简单的格式会很有帮助。例如,我们将使用 _[chunk] [变量] [时间]_ 的三维结构,其中变量是从 0 到 38 的目标变量数,time 是从 0 到 9 的提前期索引。 + +模型有望以这种格式进行预测。 + +我们还可以重新构建测试数据集以使此数据集进行比较。下面的 _prepare_test_forecasts()_ 函数实现了这一点。 + +``` +# convert the test dataset in chunks to [chunk][variable][time] format +def prepare_test_forecasts(test_chunks): + predictions = list() + # enumerate chunks to forecast + for rows in test_chunks: + # enumerate targets for chunk + chunk_predictions = list() + for j in range(3, rows.shape[1]): + yhat = rows[:, j] + chunk_predictions.append(yhat) + chunk_predictions = array(chunk_predictions) + predictions.append(chunk_predictions) + return array(predictions) +``` + +我们将使用平均绝对误差或 MAE 来评估模型。这是在竞争中使用的度量,并且在给定目标变量的非高斯分布的情况下是合理的选择。 + +如果提前期不包含测试集中的数据(例如 _NaN_ ),则不会计算该预测的错误。如果提前期确实在测试集中有数据但预测中没有数据,那么观察的全部大小将被视为错误。最后,如果测试集具有观察值并进行预测,则绝对差值将被记录为误差。 + +_calculate_error()_ 函数实现这些规则并返回给定预测的错误。 + +``` +# calculate the error between an actual and predicted value +def calculate_error(actual, predicted): + # give the full actual value if predicted is nan + if isnan(predicted): + return abs(actual) + # calculate abs difference + return abs(actual - predicted) +``` + +错误在所有块和所有提前期之间求和,然后取平均值。 + +将计算总体 MAE,但我们还将计算每个预测提前期的 MAE。这通常有助于模型选择,因为某些模型在不同的提前期可能会有不同的表现。 + +下面的 evaluate_forecasts()函数实现了这一点,计算了 _[chunk] [variable] [time]_ 格式中提供的预测和期望值的 MAE 和每个引导时间 MAE。 + +``` +# evaluate a forecast in the format [chunk][variable][time] +def evaluate_forecasts(predictions, testset): + lead_times = get_lead_times() + total_mae, times_mae = 0.0, [0.0 for _ in range(len(lead_times))] + total_c, times_c = 0, [0 for _ in range(len(lead_times))] + # enumerate test chunks + for i in range(len(test_chunks)): + # convert to forecasts + actual = testset[i] + predicted = predictions[i] + # enumerate target variables + for j in range(predicted.shape[0]): + # enumerate lead times + for k in range(len(lead_times)): + # skip if actual in nan + if isnan(actual[j, k]): + continue + # calculate error + error = calculate_error(actual[j, k], predicted[j, k]) + # update statistics + total_mae += error + times_mae[k] += error + total_c += 1 + times_c[k] += 1 + # normalize summed absolute errors + total_mae /= total_c + times_mae = [times_mae[i]/times_c[i] for i in range(len(times_mae))] + return total_mae, times_mae +``` + +一旦我们对模型进行评估,我们就可以呈现它。 + +下面的 _summarize_error()_ 函数首先打印模型表现的一行摘要,然后创建每个预测提前期的 MAE 图。 + +``` +# summarize scores +def summarize_error(name, total_mae, times_mae): + # print summary + lead_times = get_lead_times() + formatted = ['+%d %.3f' % (lead_times[i], times_mae[i]) for i in range(len(lead_times))] + s_scores = ', '.join(formatted) + print('%s: [%.3f MAE] %s' % (name, total_mae, s_scores)) + # plot summary + pyplot.plot([str(x) for x in lead_times], times_mae, marker='.') + pyplot.show() +``` + +我们现在准备开始探索天真预测方法的表现。 + +## 数据分析 + +将经典时间序列模型拟合到这些数据的第一步是仔细研究数据。 + +有 208 个(实际上是 207 个)可用的数据块,每个块有 39 个时间序列适合;这是总共 8,073 个需要适合数据的独立模型。这是很多模型,但模型是在相对少量的数据上进行训练,最多(5 * 24)或 120 次观测,并且模型是线性的,因此它可以快速找到拟合。 + +我们可以选择如何为数据配置模型;例如: + +* 所有时间序列的一种模型配置(最简单)。 +* 跨块的所有变量的一个模型配置(合理)。 +* 每个块的每个变量一个模型配置(最复杂)。 + +我们将研究所有系列的一种模型配置的最简单方法,但您可能想要探索一种或多种其他方法。 + +本节分为三个部分;他们是: + +1. 缺失数据 +2. 归咎于缺失数据 +3. 自相关图 + +### 缺失数据 + +经典时间序列方法要求时间序列完整,例如,没有遗漏的价值。 + +因此,第一步是研究目标变量的完整性或不完整性。 + +对于给定变量,可能缺少由缺失行定义的观察值。具体地,每个观察具有' _position_within_chunk_ '。我们期望训练数据集中的每个块有 120 个观察值,其中“ _positions_within_chunk_ ”从 1 到 120 包含。 + +因此,我们可以为每个变量创建一个 120 纳米值的数组,使用' _positions_within_chunk_ '值标记块中的所有观察值,剩下的任何内容都将标记为 _NaN_ 。然后我们可以绘制每个变量并寻找差距。 + +下面的 _variable_to_series()_ 函数将获取目标变量的块和给定列索引的行,并将为变量返回一系列 120 个时间步长,所有可用数据都标记为来自块。 + +``` +# layout a variable with breaks in the data for missing positions +def variable_to_series(chunk_train, col_ix, n_steps=5*24): + # lay out whole series + data = [nan for _ in range(n_steps)] + # mark all available data + for i in range(len(chunk_train)): + # get position in chunk + position = int(chunk_train[i, 1] - 1) + # store data + data[position] = chunk_train[i, col_ix] + return data +``` + +然后我们可以在一个块中为每个目标变量调用此函数并创建一个线图。 + +下面名为 _plot_variables()_ 的函数将实现此功能并创建一个图形,其中 39 个线图水平堆叠。 + +``` +# plot variables horizontally with gaps for missing data +def plot_variables(chunk_train, n_vars=39): + pyplot.figure() + for i in range(n_vars): + # convert target number into column number + col_ix = 3 + i + # mark missing obs for variable + series = variable_to_series(chunk_train, col_ix) + # plot + ax = pyplot.subplot(n_vars, 1, i+1) + ax.set_xticklabels([]) + ax.set_yticklabels([]) + pyplot.plot(series) + # show plot + pyplot.show() +``` + +将这些结合在一起,下面列出了完整的示例。创建第一个块中所有变量的图。 + +``` +# plot missing +from numpy import loadtxt +from numpy import nan +from numpy import unique +from matplotlib import pyplot + +# split the dataset by 'chunkID', return a list of chunks +def to_chunks(values, chunk_ix=0): + chunks = list() + # get the unique chunk ids + chunk_ids = unique(values[:, chunk_ix]) + # group rows by chunk id + for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks.append(values[selection, :]) + return chunks + +# layout a variable with breaks in the data for missing positions +def variable_to_series(chunk_train, col_ix, n_steps=5*24): + # lay out whole series + data = [nan for _ in range(n_steps)] + # mark all available data + for i in range(len(chunk_train)): + # get position in chunk + position = int(chunk_train[i, 1] - 1) + # store data + data[position] = chunk_train[i, col_ix] + return data + +# plot variables horizontally with gaps for missing data +def plot_variables(chunk_train, n_vars=39): + pyplot.figure() + for i in range(n_vars): + # convert target number into column number + col_ix = 3 + i + # mark missing obs for variable + series = variable_to_series(chunk_train, col_ix) + # plot + ax = pyplot.subplot(n_vars, 1, i+1) + ax.set_xticklabels([]) + ax.set_yticklabels([]) + pyplot.plot(series) + # show plot + pyplot.show() + +# load dataset +train = loadtxt('AirQualityPrediction/naive_train.csv', delimiter=',') +# group data by chunks +train_chunks = to_chunks(train) +# pick one chunk +rows = train_chunks[0] +# plot variables +plot_variables(rows) +``` + +运行该示例将创建一个包含 39 个线图的图形,一个用于第一个块中的每个目标变量。 + +我们可以在许多变量中看到季节性结构。这表明在建模之前对每个系列执行 24 小时季节差异可能是有益的。 + +这些图很小,您可能需要增加图的大小以清楚地看到数据。 + +我们可以看到有些变量我们没有数据。这些可以被检测和忽略,因为我们无法建模或预测它们。 + +我们可以看到许多系列中的差距,但差距很短,最多只能持续几个小时。这些可以通过在同一系列中的相同时间持续存在先前值或值来估算。 + +![Line Plots for All Targets in Chunk 1 With Missing Values Marked](img/cc240f9ff549b2514525e33149b186ba.jpg) + +具有缺失值的块 1 中所有目标的线图 + +随机查看其他几个块,许多块会产生具有大致相同观察结果的图。 + +但情况并非总是如此。 + +更新示例以绘制数据集中的第 4 个块(索引 3)。 + +``` +# pick one chunk +rows = train_chunks[3] +``` + +结果是一个人物讲述了一个非常不同的故事。 + +我们发现数据中存在持续数小时的差距,可能长达一天或更长时间。 + +这些系列在用于装配经典型号之前需要进行大幅修复。 + +使用同一小时内系列内的持久性或观察来输入缺失的数据可能是不够的。它们可能必须填充整个训练数据集中的平均值。 + +![Line Plots for All Targets in Chunk 4 With Missing Values Marked](img/a106bd114259a2130d22a6a27d22dabc.jpg) + +具有缺失值的块 4 中所有目标的线图 + +### 归咎于缺失数据 + +有许多方法来估算缺失的数据,我们无法知道哪个是最好的先验。 + +一种方法是使用多种不同的插补方法准备数据,并使用适合数据的模型技能来帮助指导最佳方法。 + +已经提出的一些估算方法包括: + +* 坚持系列中的最后一次观察,也称为线性插值。 +* 使用相同的小时填写系列中的值或平均值。 +* 在训练数据集中填写一天中相同小时的值或平均值。 + +使用组合也可能是有用的,例如,从系列中保留或填充小间隙,并从整个数据集中提取大间隙。 + +我们还可以通过填写缺失数据并查看图表以查看该系列是否合理来研究输入方法的效果。它原始,有效,快速。 + +首先,我们需要为每个块计算一个小时的并行序列,我们可以使用它来为块中的每个变量计算特定于小时的数据。 + +给定一系列部分填充的小时,下面的 _interpolate_hours()_ 函数将填充一天中缺少的小时数。它通过找到第一个标记的小时,然后向前计数,填写一天中的小时,然后向后执行相同的操作来完成此操作。 + +``` +# interpolate series of hours (in place) in 24 hour time +def interpolate_hours(hours): + # find the first hour + ix = -1 + for i in range(len(hours)): + if not isnan(hours[i]): + ix = i + break + # fill-forward + hour = hours[ix] + for i in range(ix+1, len(hours)): + # increment hour + hour += 1 + # check for a fill + if isnan(hours[i]): + hours[i] = hour % 24 + # fill-backward + hour = hours[ix] + for i in range(ix-1, -1, -1): + # decrement hour + hour -= 1 + # check for a fill + if isnan(hours[i]): + hours[i] = hour % 24 +``` + +我确信有更多的 Pythonic 方式来编写这个函数,但是我想把它全部用来使它显而易见。 + +我们可以在缺少数据的模拟小时列表上测试它。下面列出了完整的示例。 + +``` +# interpolate hours +from numpy import nan +from numpy import isnan + +# interpolate series of hours (in place) in 24 hour time +def interpolate_hours(hours): + # find the first hour + ix = -1 + for i in range(len(hours)): + if not isnan(hours[i]): + ix = i + break + # fill-forward + hour = hours[ix] + for i in range(ix+1, len(hours)): + # increment hour + hour += 1 + # check for a fill + if isnan(hours[i]): + hours[i] = hour % 24 + # fill-backward + hour = hours[ix] + for i in range(ix-1, -1, -1): + # decrement hour + hour -= 1 + # check for a fill + if isnan(hours[i]): + hours[i] = hour % 24 + +# define hours with missing data +data = [nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, 0, nan, 2, nan, nan, nan, nan, nan, nan, 9, 10, 11, 12, 13, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan] +print(data) +# fill in missing hours +interpolate_hours(data) +print(data) +``` + +首先运行示例打印带有缺失值的小时数据,然后正确填写所有小时数的相同序列。 + +``` +[nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, 0, nan, 2, nan, nan, nan, nan, nan, nan, 9, 10, 11, 12, 13, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan] +[11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 0, 1] +``` + +我们可以使用此函数为一个块准备一系列小时,这些块可用于使用特定于小时的信息填充块的缺失值。 + +我们可以从上一节中调用相同的 _variable_to_series()_ 函数来创建具有缺失值的小时系列(列索引 2),然后调用 _interpolate_hours()_ 来填补空白。 + +``` +# prepare sequence of hours for the chunk +hours = variable_to_series(rows, 2) +# interpolate hours +interpolate_hours(hours) +``` + +然后我们可以将时间传递给可以使用它的任何 impute 函数。 + +让我们尝试在相同系列中使用相同小时填充值中的缺失值。具体来说,我们将在系列中找到所有具有相同小时的行并计算中值。 + +下面的 _impute_missing()_ 获取块中的所有行,准备好的块的一天中的小时数,以及具有变量的缺失值和变量的列索引的系列。 + +它首先检查系列是否全部缺失数据,如果是这种情况则立即返回,因为不能执行任何插补。然后,它会在系列的时间步骤中进行枚举,当它检测到没有数据的时间步长时,它会收集序列中所有行,并使用相同小时的数据并计算中值。 + +``` +# impute missing data +def impute_missing(rows, hours, series, col_ix): + # count missing observations + n_missing = count_nonzero(isnan(series)) + # calculate ratio of missing + ratio = n_missing / float(len(series)) * 100 + # check for no data + if ratio == 100.0: + return series + # impute missing using the median value for hour in the series + imputed = list() + for i in range(len(series)): + if isnan(series[i]): + # get all rows with the same hour + matches = rows[rows[:,2]==hours[i]] + # fill with median value + value = nanmedian(matches[:, col_ix]) + imputed.append(value) + else: + imputed.append(series[i]) + return imputed +``` + +要查看此推算策略的影响,我们可以更新上一节中的 _plot_variables()_ 函数,首先绘制插补系列,然后绘制具有缺失值的原始系列。 + +这将允许插补值在原始系列的间隙中闪耀,我们可以看到结果是否合理。 + +_plot_variables()_ 函数的更新版本在下面列出了此更改,调用 _impute_missing()_ 函数来创建系列的推算版本并将小时系列作为参数。 + +``` +# plot variables horizontally with gaps for missing data +def plot_variables(chunk_train, hours, n_vars=39): + pyplot.figure() + for i in range(n_vars): + # convert target number into column number + col_ix = 3 + i + # mark missing obs for variable + series = variable_to_series(chunk_train, col_ix) + ax = pyplot.subplot(n_vars, 1, i+1) + ax.set_xticklabels([]) + ax.set_yticklabels([]) + # imputed + imputed = impute_missing(chunk_train, hours, series, col_ix) + # plot imputed + pyplot.plot(imputed) + # plot with missing + pyplot.plot(series) + # show plot + pyplot.show() +``` + +将所有这些结合在一起,下面列出了完整的示例。 + +``` +# impute missing +from numpy import loadtxt +from numpy import nan +from numpy import isnan +from numpy import count_nonzero +from numpy import unique +from numpy import nanmedian +from matplotlib import pyplot + +# split the dataset by 'chunkID', return a list of chunks +def to_chunks(values, chunk_ix=0): + chunks = list() + # get the unique chunk ids + chunk_ids = unique(values[:, chunk_ix]) + # group rows by chunk id + for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks.append(values[selection, :]) + return chunks + +# impute missing data +def impute_missing(rows, hours, series, col_ix): + # count missing observations + n_missing = count_nonzero(isnan(series)) + # calculate ratio of missing + ratio = n_missing / float(len(series)) * 100 + # check for no data + if ratio == 100.0: + return series + # impute missing using the median value for hour in the series + imputed = list() + for i in range(len(series)): + if isnan(series[i]): + # get all rows with the same hour + matches = rows[rows[:,2]==hours[i]] + # fill with median value + value = nanmedian(matches[:, col_ix]) + imputed.append(value) + else: + imputed.append(series[i]) + return imputed + +# interpolate series of hours (in place) in 24 hour time +def interpolate_hours(hours): + # find the first hour + ix = -1 + for i in range(len(hours)): + if not isnan(hours[i]): + ix = i + break + # fill-forward + hour = hours[ix] + for i in range(ix+1, len(hours)): + # increment hour + hour += 1 + # check for a fill + if isnan(hours[i]): + hours[i] = hour % 24 + # fill-backward + hour = hours[ix] + for i in range(ix-1, -1, -1): + # decrement hour + hour -= 1 + # check for a fill + if isnan(hours[i]): + hours[i] = hour % 24 + +# layout a variable with breaks in the data for missing positions +def variable_to_series(chunk_train, col_ix, n_steps=5*24): + # lay out whole series + data = [nan for _ in range(n_steps)] + # mark all available data + for i in range(len(chunk_train)): + # get position in chunk + position = int(chunk_train[i, 1] - 1) + # store data + data[position] = chunk_train[i, col_ix] + return data + +# plot variables horizontally with gaps for missing data +def plot_variables(chunk_train, hours, n_vars=39): + pyplot.figure() + for i in range(n_vars): + # convert target number into column number + col_ix = 3 + i + # mark missing obs for variable + series = variable_to_series(chunk_train, col_ix) + ax = pyplot.subplot(n_vars, 1, i+1) + ax.set_xticklabels([]) + ax.set_yticklabels([]) + # imputed + imputed = impute_missing(chunk_train, hours, series, col_ix) + # plot imputed + pyplot.plot(imputed) + # plot with missing + pyplot.plot(series) + # show plot + pyplot.show() + +# load dataset +train = loadtxt('AirQualityPrediction/naive_train.csv', delimiter=',') +# group data by chunks +train_chunks = to_chunks(train) +# pick one chunk +rows = train_chunks[0] +# prepare sequence of hours for the chunk +hours = variable_to_series(rows, 2) +# interpolate hours +interpolate_hours(hours) +# plot variables +plot_variables(rows, hours) +``` + +运行该示例将创建一个包含 39 个线图的单个图形:一个用于训练数据集中第一个块中的每个目标变量。 + +我们可以看到该系列是橙色的,显示原始数据,并且已经估算了间隙并标记为蓝色。 + +蓝色部分看似合理。 + +![Line Plots for All Targets in Chunk 1 With Imputed Missing Values](img/1a9d3b9af9ce4c5c0b708e4b5ced542d.jpg) + +带有插补缺失值的块 1 中所有目标的线图 + +我们可以在数据集中具有更多缺失数据的第 4 个块上尝试相同的方法。 + +``` +# pick one chunk +rows = train_chunks[0] +``` + +运行该示例会创建相同类型的图形,但在这里我们可以看到填充了估算值的大缺失段。 + +同样,序列看似合理,甚至在适当的时候显示每日季节周期结构。 + +![Line Plots for All Targets in Chunk 4 With Imputed Missing Values](img/0bb8244ec91efc61eb58b07ee1fd3c4a.jpg) + +带有插补缺失值的块 4 中所有目标的线图 + +这似乎是一个好的开始;您可以探索其他估算策略,并了解它们如何在线图或最终模型技能方面进行比较。 + +### 自相关图 + +现在我们知道如何填写缺失值,我们可以看一下系列数据的自相关图。 + +自相关图总结了每个观察结果与先前时间步骤的观察结果之间的关系。与部分自相关图一起,它们可用于确定 ARMA 模型的配置。 + +statsmodels 库提供 [plot_acf()](http://www.statsmodels.org/dev/generated/statsmodels.graphics.tsaplots.plot_acf.html)和 [plot_pacf()](http://www.statsmodels.org/dev/generated/statsmodels.graphics.tsaplots.plot_pacf.html)函数,可分别用于绘制 ACF 和 PACF 图。 + +我们可以更新 _plot_variables()_ 来创建这些图,这些图是 39 系列中每一个的每种类型之一。这是很多情节。 + +我们将垂直向左堆叠所有 ACF 图,并在右侧垂直堆叠所有 PACF 图。这是两列 39 个图。我们将绘图所考虑的滞后时间限制为 24 个时间步长(小时),并忽略每个变量与其自身的相关性,因为它是多余的。 + +下面列出了用于绘制 ACF 和 PACF 图的更新的 _plot_variables()_ 函数。 + +``` +# plot acf and pacf plots for each imputed variable series +def plot_variables(chunk_train, hours, n_vars=39): + pyplot.figure() + n_plots = n_vars * 2 + j = 0 + lags = 24 + for i in range(1, n_plots, 2): + # convert target number into column number + col_ix = 3 + j + j += 1 + # get series + series = variable_to_series(chunk_train, col_ix) + imputed = impute_missing(chunk_train, hours, series, col_ix) + # acf + axis = pyplot.subplot(n_vars, 2, i) + plot_acf(imputed, ax=axis, lags=lags, zero=False) + axis.set_title('') + axis.set_xticklabels([]) + axis.set_yticklabels([]) + # pacf + axis = pyplot.subplot(n_vars, 2, i+1) + plot_pacf(imputed, ax=axis, lags=lags, zero=False) + axis.set_title('') + axis.set_xticklabels([]) + axis.set_yticklabels([]) + # show plot + pyplot.show() +``` + +下面列出了完整的示例。 + +``` +# acf and pacf plots +from numpy import loadtxt +from numpy import nan +from numpy import isnan +from numpy import count_nonzero +from numpy import unique +from numpy import nanmedian +from matplotlib import pyplot +from statsmodels.graphics.tsaplots import plot_acf +from statsmodels.graphics.tsaplots import plot_pacf + +# split the dataset by 'chunkID', return a list of chunks +def to_chunks(values, chunk_ix=0): + chunks = list() + # get the unique chunk ids + chunk_ids = unique(values[:, chunk_ix]) + # group rows by chunk id + for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks.append(values[selection, :]) + return chunks + +# impute missing data +def impute_missing(rows, hours, series, col_ix): + # count missing observations + n_missing = count_nonzero(isnan(series)) + # calculate ratio of missing + ratio = n_missing / float(len(series)) * 100 + # check for no data + if ratio == 100.0: + return series + # impute missing using the median value for hour in the series + imputed = list() + for i in range(len(series)): + if isnan(series[i]): + # get all rows with the same hour + matches = rows[rows[:,2]==hours[i]] + # fill with median value + value = nanmedian(matches[:, col_ix]) + imputed.append(value) + else: + imputed.append(series[i]) + return imputed + +# interpolate series of hours (in place) in 24 hour time +def interpolate_hours(hours): + # find the first hour + ix = -1 + for i in range(len(hours)): + if not isnan(hours[i]): + ix = i + break + # fill-forward + hour = hours[ix] + for i in range(ix+1, len(hours)): + # increment hour + hour += 1 + # check for a fill + if isnan(hours[i]): + hours[i] = hour % 24 + # fill-backward + hour = hours[ix] + for i in range(ix-1, -1, -1): + # decrement hour + hour -= 1 + # check for a fill + if isnan(hours[i]): + hours[i] = hour % 24 + +# layout a variable with breaks in the data for missing positions +def variable_to_series(chunk_train, col_ix, n_steps=5*24): + # lay out whole series + data = [nan for _ in range(n_steps)] + # mark all available data + for i in range(len(chunk_train)): + # get position in chunk + position = int(chunk_train[i, 1] - 1) + # store data + data[position] = chunk_train[i, col_ix] + return data + +# plot acf and pacf plots for each imputed variable series +def plot_variables(chunk_train, hours, n_vars=39): + pyplot.figure() + n_plots = n_vars * 2 + j = 0 + lags = 24 + for i in range(1, n_plots, 2): + # convert target number into column number + col_ix = 3 + j + j += 1 + # get series + series = variable_to_series(chunk_train, col_ix) + imputed = impute_missing(chunk_train, hours, series, col_ix) + # acf + axis = pyplot.subplot(n_vars, 2, i) + plot_acf(imputed, ax=axis, lags=lags, zero=False) + axis.set_title('') + axis.set_xticklabels([]) + axis.set_yticklabels([]) + # pacf + axis = pyplot.subplot(n_vars, 2, i+1) + plot_pacf(imputed, ax=axis, lags=lags, zero=False) + axis.set_title('') + axis.set_xticklabels([]) + axis.set_yticklabels([]) + # show plot + pyplot.show() + +# load dataset +train = loadtxt('AirQualityPrediction/naive_train.csv', delimiter=',') +# group data by chunks +train_chunks = to_chunks(train) +# pick one chunk +rows = train_chunks[0] +# prepare sequence of hours for the chunk +hours = variable_to_series(rows, 2) +# interpolate hours +interpolate_hours(hours) +# plot variables +plot_variables(rows, hours) +``` + +运行该示例会在训练数据集的第一个块中为目标变量创建一个包含大量图的图形。 + +您可能需要增加绘图窗口的大小以更好地查看每个绘图的详细信息。 + +我们可以在左侧看到,大多数 ACF 图在滞后 1-2 步时显示出显着的相关性(高于显着性区域的点),在某些情况下可能滞后 1-3 步,在滞后时缓慢,稳定地减少 + +同样,在右边,我们可以看到 PACF 图中 1-2 个时间步长的显着滞后,陡峭的下降。 + +这有力地暗示了自相关过程,其顺序可能是 1,2 或 3,例如 AR(3)。 + +在左侧的 ACF 图中,我们还可以看到相关性中的每日周期。这可能表明在建模之前对数据进行季节性差异或使用能够进行季节性差异的 AR 模型的一些益处。 + +![ACF and PACF Plots for Target Variables in Chunk 1](img/eeb9a82272270efeac789d92769736f7.jpg) + +块 1 中目标变量的 ACF 和 PACF 图 + +我们可以重复对其他块的目标变量的分析,我们看到的图片大致相同。 + +它表明我们可以通过所有块的所有系列的一般 AR 模型配置逃脱。 + +## 开发自回归模型 + +在本节中,我们将为估算的目标序列数据开发一个自回归模型。 + +第一步是实现一个通用函数,用于为每个块进行预测。 + +该功能为训练数据集和测试集的输入列(块 ID,块和小时的位置)执行任务,并返回具有 _[chunk] [变量] [时间]的预期 3D 格式的所有块的预测 _。 + +该函数枚举预测中的块,然后枚举 39 个目标列,调用另一个名为 _forecast_variable()_ 的新函数,以便对给定目标变量的每个提前期进行预测。 + +完整的功能如下所列。 + +``` +# forecast for each chunk, returns [chunk][variable][time] +def forecast_chunks(train_chunks, test_input): + lead_times = get_lead_times() + predictions = list() + # enumerate chunks to forecast + for i in range(len(train_chunks)): + # prepare sequence of hours for the chunk + hours = variable_to_series(train_chunks[i], 2) + # interpolate hours + interpolate_hours(hours) + # enumerate targets for chunk + chunk_predictions = list() + for j in range(39): + yhat = forecast_variable(hours, train_chunks[i], test_input[i], lead_times, j) + chunk_predictions.append(yhat) + chunk_predictions = array(chunk_predictions) + predictions.append(chunk_predictions) + return array(predictions) +``` + +我们现在可以实现 _forecast_variable()_ 的一个版本。 + +对于每个变量,我们首先检查是否没有数据(例如所有 NaN),如果是,我们返回每个预测提前期的 NaN 预测。 + +然后我们使用 _variable_to_series()_ 从变量创建一个系列,然后通过调用 _impute_missing()_ 使用系列中的中位数来计算缺失值,两者都是在上一节。 + +最后,我们调用一个名为 _fit_and_forecast()_ 的新函数,该函数适合模型并预测 10 个预测前置时间。 + +``` +# forecast all lead times for one variable +def forecast_variable(hours, chunk_train, chunk_test, lead_times, target_ix): + # convert target number into column number + col_ix = 3 + target_ix + # check for no data + if not has_data(chunk_train[:, col_ix]): + forecast = [nan for _ in range(len(lead_times))] + return forecast + # get series + series = variable_to_series(chunk_train, col_ix) + # impute + imputed = impute_missing(chunk_train, hours, series, col_ix) + # fit AR model and forecast + forecast = fit_and_forecast(imputed) + return forecast +``` + +我们将 AR 模型适用于给定的推算系列。为此,我们将使用 [statsmodels ARIMA 类](http://www.statsmodels.org/dev/generated/statsmodels.tsa.arima_model.ARIMA.html)。如果您想探索任何 ARIMA 模型系列,我们将使用 ARIMA 代替 AR 来提供一些灵活性。 + +首先,我们必须定义模型,包括自回归过程的顺序,例如 AR(1)。 + +``` +# define the model +model = ARIMA(series, order=(1,0,0)) +``` + +接下来,该模型适用于推算系列。我们通过将 _disp_ 设置为 _False_ 来关闭拟合期间的详细信息。 + +``` +# fit the model +model_fit = model.fit(disp=False) +``` + +然后使用拟合模型预测系列结束后的 72 小时。 + +``` +# forecast 72 hours +yhat = model_fit.predict(len(series), len(series)+72) +``` + +我们只对特定的提前期感兴趣,因此我们准备一系列提前期,减 1 以将它们转换为数组索引,然后使用它们选择我们感兴趣的 10 个预测提前期的值。 + +``` +# extract lead times +lead_times = array(get_lead_times()) +indices = lead_times - 1 +return yhat[indices] +``` + +statsmodels ARIMA 模型使用线性代数库来拟合封面下的模型,有时适合过程在某些数据上可能不稳定。因此,它可以抛出异常或报告大量警告。 + +我们将捕获异常并返回 _NaN_ 预测,并在拟合和评估期间忽略所有警告。 + +下面的 _fit_and_forecast()_ 函数将所有这些联系在一起。 + +``` +# fit AR model and generate a forecast +def fit_and_forecast(series): + # define the model + model = ARIMA(series, order=(1,0,0)) + # return a nan forecast in case of exception + try: + # ignore statsmodels warnings + with catch_warnings(): + filterwarnings("ignore") + # fit the model + model_fit = model.fit(disp=False) + # forecast 72 hours + yhat = model_fit.predict(len(series), len(series)+72) + # extract lead times + lead_times = array(get_lead_times()) + indices = lead_times - 1 + return yhat[indices] + except: + return [nan for _ in range(len(get_lead_times()))] +``` + +我们现在准备评估 207 个训练块中每个系列中 39 个系列中每个系列的自回归过程。 + +我们将从测试 AR(1)过程开始。 + +完整的代码示例如下所示。 + +``` +# autoregression forecast +from numpy import loadtxt +from numpy import nan +from numpy import isnan +from numpy import count_nonzero +from numpy import unique +from numpy import array +from numpy import nanmedian +from statsmodels.tsa.arima_model import ARIMA +from matplotlib import pyplot +from warnings import catch_warnings +from warnings import filterwarnings + +# split the dataset by 'chunkID', return a list of chunks +def to_chunks(values, chunk_ix=0): + chunks = list() + # get the unique chunk ids + chunk_ids = unique(values[:, chunk_ix]) + # group rows by chunk id + for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks.append(values[selection, :]) + return chunks + +# return a list of relative forecast lead times +def get_lead_times(): + return [1, 2, 3, 4, 5, 10, 17, 24, 48, 72] + +# interpolate series of hours (in place) in 24 hour time +def interpolate_hours(hours): + # find the first hour + ix = -1 + for i in range(len(hours)): + if not isnan(hours[i]): + ix = i + break + # fill-forward + hour = hours[ix] + for i in range(ix+1, len(hours)): + # increment hour + hour += 1 + # check for a fill + if isnan(hours[i]): + hours[i] = hour % 24 + # fill-backward + hour = hours[ix] + for i in range(ix-1, -1, -1): + # decrement hour + hour -= 1 + # check for a fill + if isnan(hours[i]): + hours[i] = hour % 24 + +# return true if the array has any non-nan values +def has_data(data): + return count_nonzero(isnan(data)) < len(data) + +# impute missing data +def impute_missing(rows, hours, series, col_ix): + # impute missing using the median value for hour in the series + imputed = list() + for i in range(len(series)): + if isnan(series[i]): + # get all rows with the same hour + matches = rows[rows[:,2]==hours[i]] + # fill with median value + value = nanmedian(matches[:, col_ix]) + if isnan(value): + value = 0.0 + imputed.append(value) + else: + imputed.append(series[i]) + return imputed + +# layout a variable with breaks in the data for missing positions +def variable_to_series(chunk_train, col_ix, n_steps=5*24): + # lay out whole series + data = [nan for _ in range(n_steps)] + # mark all available data + for i in range(len(chunk_train)): + # get position in chunk + position = int(chunk_train[i, 1] - 1) + # store data + data[position] = chunk_train[i, col_ix] + return data + +# fit AR model and generate a forecast +def fit_and_forecast(series): + # define the model + model = ARIMA(series, order=(1,0,0)) + # return a nan forecast in case of exception + try: + # ignore statsmodels warnings + with catch_warnings(): + filterwarnings("ignore") + # fit the model + model_fit = model.fit(disp=False) + # forecast 72 hours + yhat = model_fit.predict(len(series), len(series)+72) + # extract lead times + lead_times = array(get_lead_times()) + indices = lead_times - 1 + return yhat[indices] + except: + return [nan for _ in range(len(get_lead_times()))] + +# forecast all lead times for one variable +def forecast_variable(hours, chunk_train, chunk_test, lead_times, target_ix): + # convert target number into column number + col_ix = 3 + target_ix + # check for no data + if not has_data(chunk_train[:, col_ix]): + forecast = [nan for _ in range(len(lead_times))] + return forecast + # get series + series = variable_to_series(chunk_train, col_ix) + # impute + imputed = impute_missing(chunk_train, hours, series, col_ix) + # fit AR model and forecast + forecast = fit_and_forecast(imputed) + return forecast + +# forecast for each chunk, returns [chunk][variable][time] +def forecast_chunks(train_chunks, test_input): + lead_times = get_lead_times() + predictions = list() + # enumerate chunks to forecast + for i in range(len(train_chunks)): + # prepare sequence of hours for the chunk + hours = variable_to_series(train_chunks[i], 2) + # interpolate hours + interpolate_hours(hours) + # enumerate targets for chunk + chunk_predictions = list() + for j in range(39): + yhat = forecast_variable(hours, train_chunks[i], test_input[i], lead_times, j) + chunk_predictions.append(yhat) + chunk_predictions = array(chunk_predictions) + predictions.append(chunk_predictions) + return array(predictions) + +# convert the test dataset in chunks to [chunk][variable][time] format +def prepare_test_forecasts(test_chunks): + predictions = list() + # enumerate chunks to forecast + for rows in test_chunks: + # enumerate targets for chunk + chunk_predictions = list() + for j in range(3, rows.shape[1]): + yhat = rows[:, j] + chunk_predictions.append(yhat) + chunk_predictions = array(chunk_predictions) + predictions.append(chunk_predictions) + return array(predictions) + +# calculate the error between an actual and predicted value +def calculate_error(actual, predicted): + # give the full actual value if predicted is nan + if isnan(predicted): + return abs(actual) + # calculate abs difference + return abs(actual - predicted) + +# evaluate a forecast in the format [chunk][variable][time] +def evaluate_forecasts(predictions, testset): + lead_times = get_lead_times() + total_mae, times_mae = 0.0, [0.0 for _ in range(len(lead_times))] + total_c, times_c = 0, [0 for _ in range(len(lead_times))] + # enumerate test chunks + for i in range(len(test_chunks)): + # convert to forecasts + actual = testset[i] + predicted = predictions[i] + # enumerate target variables + for j in range(predicted.shape[0]): + # enumerate lead times + for k in range(len(lead_times)): + # skip if actual in nan + if isnan(actual[j, k]): + continue + # calculate error + error = calculate_error(actual[j, k], predicted[j, k]) + # update statistics + total_mae += error + times_mae[k] += error + total_c += 1 + times_c[k] += 1 + # normalize summed absolute errors + total_mae /= total_c + times_mae = [times_mae[i]/times_c[i] for i in range(len(times_mae))] + return total_mae, times_mae + +# summarize scores +def summarize_error(name, total_mae, times_mae): + # print summary + lead_times = get_lead_times() + formatted = ['+%d %.3f' % (lead_times[i], times_mae[i]) for i in range(len(lead_times))] + s_scores = ', '.join(formatted) + print('%s: [%.3f MAE] %s' % (name, total_mae, s_scores)) + # plot summary + pyplot.plot([str(x) for x in lead_times], times_mae, marker='.') + pyplot.show() + +# load dataset +train = loadtxt('AirQualityPrediction/naive_train.csv', delimiter=',') +test = loadtxt('AirQualityPrediction/naive_test.csv', delimiter=',') +# group data by chunks +train_chunks = to_chunks(train) +test_chunks = to_chunks(test) +# forecast +test_input = [rows[:, :3] for rows in test_chunks] +forecast = forecast_chunks(train_chunks, test_input) +# evaluate forecast +actual = prepare_test_forecasts(test_chunks) +total_mae, times_mae = evaluate_forecasts(forecast, actual) +# summarize forecast +summarize_error('AR', total_mae, times_mae) +``` + +首先运行示例报告测试集的总体 MAE,然后是每个预测提前期的 MAE。 + +我们可以看到该模型实现了约 0.492 的 MAE,小于通过朴素持久模型实现的 MAE 0.520。这表明该方法确实具有一定的技巧。 + +``` +AR: [0.492 MAE] +1 0.225, +2 0.342, +3 0.410, +4 0.475, +5 0.512, +10 0.593, +17 0.586, +24 0.588, +48 0.588, +72 0.604 +``` + +创建每个预测提前期的 MAE 线图,显示预测误差随预测提前期的增加而线性增加。 + +![MAE vs Forecast Lead Time for AR(1)](img/f9e27244a1745ade5fcf281ea7b0edea.jpg) + +AR 与预测 AR 的预测时间(1) + +我们可以更改代码以测试其他 AR 模型。具体是 _fit_and_forecast()_ 函数中 ARIMA 模型的顺序。 + +AR(2)模型可以定义为: + +``` +model = ARIMA(series, order=(2,0,0)) +``` + +使用此更新运行代码显示错误进一步下降到总体 MAE 约 0.490。 + +``` +AR: [0.490 MAE] +1 0.229, +2 0.342, +3 0.412, +4 0.470, +5 0.503, +10 0.563, +17 0.576, +24 0.605, +48 0.597, +72 0.608 +``` + +我们也可以尝试 AR(3): + +``` +model = ARIMA(series, order=(3,0,0)) +``` + +使用更新重新运行示例显示与 AR(2)相比整体 MAE 增加。 + +AR(2)可能是一个很好的全局级配置,尽管预计为每个变量或每个系列量身定制的模型可能总体上表现更好。 + +``` +AR: [0.491 MAE] +1 0.232, +2 0.345, +3 0.412, +4 0.472, +5 0.504, +10 0.556, +17 0.575, +24 0.607, +48 0.599, +72 0.611 +``` + +## 具有全球归因策略的自回归模型 + +我们可以使用替代插补策略来评估 AR(2)模型。 + +我们可以计算所有块中变量的相同值,而不是计算块中系列中相同小时的中值。 + +我们可以更新 _impute_missing()_ 以将所有训练块作为参数,然后从给定小时的所有块收集行,以计算用于估算的中值。下面列出了该功能的更新版本。 + +``` +# impute missing data +def impute_missing(train_chunks, rows, hours, series, col_ix): + # impute missing using the median value for hour in all series + imputed = list() + for i in range(len(series)): + if isnan(series[i]): + # collect all rows across all chunks for the hour + all_rows = list() + for rows in train_chunks: + [all_rows.append(row) for row in rows[rows[:,2]==hours[i]]] + # calculate the central tendency for target + all_rows = array(all_rows) + # fill with median value + value = nanmedian(all_rows[:, col_ix]) + if isnan(value): + value = 0.0 + imputed.append(value) + else: + imputed.append(series[i]) + return imputed +``` + +为了将 train_chunks 传递给 _impute_missing()_ 函数,我们必须更新 _forecast_variable()_ 函数以将 _train_chunks_ 作为参数并传递给它,然后更新 _forecast_chunks()_ 函数以传递 _train_chunks_ 。 + +下面列出了使用全局插补策略的完整示例。 + +``` +# autoregression forecast with global impute strategy +from numpy import loadtxt +from numpy import nan +from numpy import isnan +from numpy import count_nonzero +from numpy import unique +from numpy import array +from numpy import nanmedian +from statsmodels.tsa.arima_model import ARIMA +from matplotlib import pyplot +from warnings import catch_warnings +from warnings import filterwarnings + +# split the dataset by 'chunkID', return a list of chunks +def to_chunks(values, chunk_ix=0): + chunks = list() + # get the unique chunk ids + chunk_ids = unique(values[:, chunk_ix]) + # group rows by chunk id + for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks.append(values[selection, :]) + return chunks + +# return a list of relative forecast lead times +def get_lead_times(): + return [1, 2, 3, 4, 5, 10, 17, 24, 48, 72] + +# interpolate series of hours (in place) in 24 hour time +def interpolate_hours(hours): + # find the first hour + ix = -1 + for i in range(len(hours)): + if not isnan(hours[i]): + ix = i + break + # fill-forward + hour = hours[ix] + for i in range(ix+1, len(hours)): + # increment hour + hour += 1 + # check for a fill + if isnan(hours[i]): + hours[i] = hour % 24 + # fill-backward + hour = hours[ix] + for i in range(ix-1, -1, -1): + # decrement hour + hour -= 1 + # check for a fill + if isnan(hours[i]): + hours[i] = hour % 24 + +# return true if the array has any non-nan values +def has_data(data): + return count_nonzero(isnan(data)) < len(data) + +# impute missing data +def impute_missing(train_chunks, rows, hours, series, col_ix): + # impute missing using the median value for hour in all series + imputed = list() + for i in range(len(series)): + if isnan(series[i]): + # collect all rows across all chunks for the hour + all_rows = list() + for rows in train_chunks: + [all_rows.append(row) for row in rows[rows[:,2]==hours[i]]] + # calculate the central tendency for target + all_rows = array(all_rows) + # fill with median value + value = nanmedian(all_rows[:, col_ix]) + if isnan(value): + value = 0.0 + imputed.append(value) + else: + imputed.append(series[i]) + return imputed + +# layout a variable with breaks in the data for missing positions +def variable_to_series(chunk_train, col_ix, n_steps=5*24): + # lay out whole series + data = [nan for _ in range(n_steps)] + # mark all available data + for i in range(len(chunk_train)): + # get position in chunk + position = int(chunk_train[i, 1] - 1) + # store data + data[position] = chunk_train[i, col_ix] + return data + +# fit AR model and generate a forecast +def fit_and_forecast(series): + # define the model + model = ARIMA(series, order=(2,0,0)) + # return a nan forecast in case of exception + try: + # ignore statsmodels warnings + with catch_warnings(): + filterwarnings("ignore") + # fit the model + model_fit = model.fit(disp=False) + # forecast 72 hours + yhat = model_fit.predict(len(series), len(series)+72) + # extract lead times + lead_times = array(get_lead_times()) + indices = lead_times - 1 + return yhat[indices] + except: + return [nan for _ in range(len(get_lead_times()))] + +# forecast all lead times for one variable +def forecast_variable(hours, train_chunks, chunk_train, chunk_test, lead_times, target_ix): + # convert target number into column number + col_ix = 3 + target_ix + # check for no data + if not has_data(chunk_train[:, col_ix]): + forecast = [nan for _ in range(len(lead_times))] + return forecast + # get series + series = variable_to_series(chunk_train, col_ix) + # impute + imputed = impute_missing(train_chunks, chunk_train, hours, series, col_ix) + # fit AR model and forecast + forecast = fit_and_forecast(imputed) + return forecast + +# forecast for each chunk, returns [chunk][variable][time] +def forecast_chunks(train_chunks, test_input): + lead_times = get_lead_times() + predictions = list() + # enumerate chunks to forecast + for i in range(len(train_chunks)): + # prepare sequence of hours for the chunk + hours = variable_to_series(train_chunks[i], 2) + # interpolate hours + interpolate_hours(hours) + # enumerate targets for chunk + chunk_predictions = list() + for j in range(39): + yhat = forecast_variable(hours, train_chunks, train_chunks[i], test_input[i], lead_times, j) + chunk_predictions.append(yhat) + chunk_predictions = array(chunk_predictions) + predictions.append(chunk_predictions) + return array(predictions) + +# convert the test dataset in chunks to [chunk][variable][time] format +def prepare_test_forecasts(test_chunks): + predictions = list() + # enumerate chunks to forecast + for rows in test_chunks: + # enumerate targets for chunk + chunk_predictions = list() + for j in range(3, rows.shape[1]): + yhat = rows[:, j] + chunk_predictions.append(yhat) + chunk_predictions = array(chunk_predictions) + predictions.append(chunk_predictions) + return array(predictions) + +# calculate the error between an actual and predicted value +def calculate_error(actual, predicted): + # give the full actual value if predicted is nan + if isnan(predicted): + return abs(actual) + # calculate abs difference + return abs(actual - predicted) + +# evaluate a forecast in the format [chunk][variable][time] +def evaluate_forecasts(predictions, testset): + lead_times = get_lead_times() + total_mae, times_mae = 0.0, [0.0 for _ in range(len(lead_times))] + total_c, times_c = 0, [0 for _ in range(len(lead_times))] + # enumerate test chunks + for i in range(len(test_chunks)): + # convert to forecasts + actual = testset[i] + predicted = predictions[i] + # enumerate target variables + for j in range(predicted.shape[0]): + # enumerate lead times + for k in range(len(lead_times)): + # skip if actual in nan + if isnan(actual[j, k]): + continue + # calculate error + error = calculate_error(actual[j, k], predicted[j, k]) + # update statistics + total_mae += error + times_mae[k] += error + total_c += 1 + times_c[k] += 1 + # normalize summed absolute errors + total_mae /= total_c + times_mae = [times_mae[i]/times_c[i] for i in range(len(times_mae))] + return total_mae, times_mae + +# summarize scores +def summarize_error(name, total_mae, times_mae): + # print summary + lead_times = get_lead_times() + formatted = ['+%d %.3f' % (lead_times[i], times_mae[i]) for i in range(len(lead_times))] + s_scores = ', '.join(formatted) + print('%s: [%.3f MAE] %s' % (name, total_mae, s_scores)) + # plot summary + pyplot.plot([str(x) for x in lead_times], times_mae, marker='.') + pyplot.show() + +# load dataset +train = loadtxt('AirQualityPrediction/naive_train.csv', delimiter=',') +test = loadtxt('AirQualityPrediction/naive_test.csv', delimiter=',') +# group data by chunks +train_chunks = to_chunks(train) +test_chunks = to_chunks(test) +# forecast +test_input = [rows[:, :3] for rows in test_chunks] +forecast = forecast_chunks(train_chunks, test_input) +# evaluate forecast +actual = prepare_test_forecasts(test_chunks) +total_mae, times_mae = evaluate_forecasts(forecast, actual) +# summarize forecast +summarize_error('AR', total_mae, times_mae) +``` + +运行该示例显示整体 MAE 进一步下降至约 0.487。 + +探索插补策略可能会很有趣,这种策略可以根据系列中缺少的数据量或填充的间隙来替换用于填充缺失值的方法。 + +``` +AR: [0.487 MAE] +1 0.228, +2 0.339, +3 0.409, +4 0.469, +5 0.499, +10 0.560, +17 0.573, +24 0.600, +48 0.595, +72 0.606 +``` + +还创建了 MAE 与预测提前期的线图。 + +![MAE vs Forecast Lead Time for AR(2) Impute With Global Strategy](img/fda2e2721e4632569246b9c334a37604.jpg) + +MAE 与 AR 的预测提前期(2)对全球战略的影响 + +## 扩展 + +本节列出了一些扩展您可能希望探索的教程的想法。 + +* **估算策略**。为每个系列中缺失的数据开发并评估另一种替代插补策略。 +* **数据准备**。探索应用于每种技术的数据准备技术是否可以提高模型技能,例如标准化,标准化和功率变换。 +* **差异**。探索差分(例如 1 步或 24 步(季节差分))是否可以使每个系列静止,从而产生更好的预测。 + +如果你探索任何这些扩展,我很想知道。 + +## 进一步阅读 + +如果您希望深入了解,本节将提供有关该主题的更多资源。 + +### 帖子 + +* [标准多变量,多步骤和多站点时间序列预测问题](https://machinelearningmastery.com/standard-multivariate-multi-step-multi-site-time-series-forecasting-problem/) +* [自相关和部分自相关的温和介绍](https://machinelearningmastery.com/gentle-introduction-autocorrelation-partial-autocorrelation/) +* [如何使用 Python 网格搜索 ARIMA 模型超参数](https://machinelearningmastery.com/grid-search-arima-hyperparameters-with-python/) + +### API + +* [statsmodels.graphics.tsaplots.plot_acf API](http://www.statsmodels.org/dev/generated/statsmodels.graphics.tsaplots.plot_acf.html) +* [statsmodels.graphics.tsaplots.plot_pacf API](http://www.statsmodels.org/dev/generated/statsmodels.graphics.tsaplots.plot_pacf.html) +* [statsmodels.tsa.arima_model.ARIMA API](http://www.statsmodels.org/dev/generated/statsmodels.tsa.arima_model.ARIMA.html) + +### 用品 + +* [EMC 数据科学全球黑客马拉松(空气质量预测)](https://www.kaggle.com/c/dsg-hackathon/data) +* [将所有东西放入随机森林:Ben Hamner 赢得空气质量预测黑客马拉松](http://blog.kaggle.com/2012/05/01/chucking-everything-into-a-random-forest-ben-hamner-on-winning-the-air-quality-prediction-hackathon/) +* [EMC 数据科学全球黑客马拉松(空气质量预测)的获奖代码](https://github.com/benhamner/Air-Quality-Prediction-Hackathon-Winning-Model) +* [分区模型的一般方法?](https://www.kaggle.com/c/dsg-hackathon/discussion/1821) + +## 摘要 + +在本教程中,您了解了如何为多变量空气污染时间序列开发多步时间序列预测的自回归模型。 + +具体来说,你学到了: + +* 如何分析和计算时间序列数据的缺失值。 +* 如何开发和评估多步时间序列预测的自回归模型。 +* 如何使用备用数据插补方法改进自回归模型。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/how-to-develop-baseline-forecasts-for-multi-site-multivariate-air-pollution-time-series-forecasting.md b/docs/dl-ts/how-to-develop-baseline-forecasts-for-multi-site-multivariate-air-pollution-time-series-forecasting.md new file mode 100644 index 0000000000000000000000000000000000000000..6c6948c7e3f8714a5ffb918ae480924443af776d --- /dev/null +++ b/docs/dl-ts/how-to-develop-baseline-forecasts-for-multi-site-multivariate-air-pollution-time-series-forecasting.md @@ -0,0 +1,1655 @@ +# 如何制定多站点多元空气污染时间序列预测的基线预测 + +> 原文: [https://machinelearningmastery.com/how-to-develop-baseline-forecasts-for-multi-site-multivariate-air-pollution-time-series-forecasting/](https://machinelearningmastery.com/how-to-develop-baseline-forecasts-for-multi-site-multivariate-air-pollution-time-series-forecasting/) + +实时世界时间序列预测具有挑战性,其原因不仅限于问题特征,例如具有多个输入变量,需要预测多个时间步骤,以及需要对多个物理站点执行相同类型的预测。 + +EMC Data Science Global Hackathon 数据集或简称“_ 空气质量预测 _”数据集描述了多个站点的天气状况,需要预测随后三天的空气质量测量结果。 + +使用新的时间序列预测数据集时,重要的第一步是开发模型表现基线,通过该基线可以比较所有其他更复杂策略的技能。基线预测策略简单快捷。它们被称为“天真”策略,因为它们对特定预测问题的假设很少或根本没有。 + +在本教程中,您将了解如何为多步骤多变量空气污染时间序列预测问题开发天真的预测方法。 + +完成本教程后,您将了解: + +* 如何开发用于评估大气污染数据集预测策略的测试工具。 +* 如何开发使用整个训练数据集中的数据的全球天真预测策略。 +* 如何开发使用来自预测的特定区间的数据的本地天真预测策略。 + +让我们开始吧。 + +![How to Develop Baseline Forecasts for Multi-Site Multivariate Air Pollution Time Series Forecasting](https://3qeqpr26caki16dnhd19sv6by6v-wpengine.netdna-ssl.com/wp-content/uploads/2018/10/How-to-Develop-Baseline-Forecasts-for-Multi-Site-Multivariate-Air-Pollution-Time-Series-Forecasting.jpg) + +如何制定多站点多元空气污染时间序列预测的基准预测 +照片由 [DAVID HOLT](https://www.flickr.com/photos/zongo/38524476520/) ,保留一些权利。 + +## 教程概述 + +本教程分为六个部分;他们是: + +* 问题描述 +* 天真的方法 +* 模型评估 +* 全球朴素的方法 +* 大块天真的方法 +* 结果摘要 + +## 问题描述 + +空气质量预测数据集描述了多个地点的天气状况,需要预测随后三天的空气质量测量结果。 + +具体而言,对于多个站点,每小时提供 8 天的温度,压力,风速和风向等天气观测。目标是预测未来 3 天在多个地点的空气质量测量。预测的提前期不是连续的;相反,必须在 72 小时预测期内预测特定提前期。他们是: + +``` ++1, +2, +3, +4, +5, +10, +17, +24, +48, +72 +``` + +此外,数据集被划分为不相交但连续的数据块,其中 8 天的数据随后是需要预测的 3 天。 + +并非所有站点或块都可以获得所有观察结果,并且并非所有站点和块都可以使用所有输出变量。必须解决大部分缺失数据。 + +该数据集被用作 2012 年 Kaggle 网站上[短期机器学习竞赛](https://www.kaggle.com/c/dsg-hackathon)(或黑客马拉松)的基础。 + +根据从参与者中扣留的真实观察结果评估竞赛的提交,并使用平均绝对误差(MAE)进行评分。提交要求在由于缺少数据而无法预测的情况下指定-1,000,000 的值。实际上,提供了一个插入缺失值的模板,并且要求所有提交都采用(模糊的是什么)。 + +获胜者在滞留测试集([私人排行榜](https://www.kaggle.com/c/dsg-hackathon/leaderboard))上使用随机森林在滞后观察中获得了 0.21058 的 MAE。该帖子中提供了此解决方案的说明: + +* [把所有东西都扔进随机森林:Ben Hamner 赢得空气质量预测黑客马拉松](http://blog.kaggle.com/2012/05/01/chucking-everything-into-a-random-forest-ben-hamner-on-winning-the-air-quality-prediction-hackathon/),2012。 + +在本教程中,我们将探索如何为可用作基线的问题开发天真预测,以确定模型是否具有该问题的技能。 + +## 天真的预测方法 + +预测表现的基线提供了一个比较点。 + +它是您问题的所有其他建模技术的参考点。如果模型达到或低于基线的表现,则应该修复或放弃该技术。 + +用于生成预测以计算基准表现的技术必须易于实现,并且不需要特定于问题的细节。原则是,如果复杂的预测方法不能胜过使用很少或没有特定问题信息的模型,那么它就没有技巧。 + +可以并且应该首先使用与问题无关的预测方法,然后是使用少量特定于问题的信息的天真方法。 + +可以使用的两个与问题无关的天真预测方法的例子包括: + +* 保留每个系列的最后观察值。 +* 预测每个系列的观测值的平均值。 + +数据被分成时间块或间隔。每个时间块都有多个站点的多个变量来预测。持久性预测方法在数据组织的这个组块级别是有意义的。 + +可以探索其他持久性方法;例如: + +* 每个系列的接下来三天预测前一天的观察结果。 +* 每个系列的接下来三天预测前三天的观察结果。 + +这些是需要探索的理想基线方法,但是大量缺失数据和大多数数据块的不连续结构使得在没有非平凡数据准备的情况下实施它们具有挑战性。 + +可以进一步详细说明预测每个系列的平均观测值;例如: + +* 预测每个系列的全局(跨块)平均值。 +* 预测每个系列的本地(块内)平均值。 + +每个系列都需要三天的预测,具有不同的开始时间,例如:一天的时间。因此,每个块的预测前置时间将在一天中的不同时段下降。 + +进一步详细说明预测平均值是为了纳入正在预测的一天中的小时数;例如: + +* 预测每个预测提前期的一小时的全球(跨块)平均值。 +* 预测每个预测提前期的当天(当地块)平均值。 + +在多个站点测量许多变量;因此,可以跨系列使用信息,例如计算预测提前期的每小时平均值或平均值。这些很有意思,但可能会超出天真的使命。 + +这是一个很好的起点,尽管可能会进一步详细阐述您可能想要考虑和探索的天真方法。请记住,目标是使用非常少的问题特定信息来开发预测基线。 + +总之,我们将研究针对此问题的五种不同的天真预测方法,其中最好的方法将提供表现的下限,通过该方法可以比较其他模型。他们是: + +1. 每个系列的全球平均价值 +2. 每个系列的预测提前期的全球平均值 +3. 每个系列的本地持久价值 +4. 每个系列的本地平均值 +5. 每个系列的预测提前期的当地平均值 + +## 模型评估 + +在我们评估天真的预测方法之前,我们必须开发一个测试工具。 + +这至少包括如何准备数据以及如何评估预测。 + +### 加载数据集 + +第一步是下载数据集并将其加载到内存中。 + +数据集可以从 Kaggle 网站免费下载。您可能必须创建一个帐户并登录才能下载数据集。 + +下载整个数据集,例如“_ 将所有 _”下载到您的工作站,并使用名为' _AirQualityPrediction_ '的文件夹解压缩当前工作目录中的存档。 + +* [EMC 数据科学全球黑客马拉松(空气质量预测)数据](https://www.kaggle.com/c/dsg-hackathon/data) + +我们的重点将是包含训练数据集的' _TrainingData.csv_ '文件,特别是块中的数据,其中每个块是八个连续的观察日和目标变量。 + +我们可以使用 Pandas [read_csv()函数](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_csv.html)将数据文件加载到内存中,并在第 0 行指定标题行。 + +``` +# load dataset +dataset = read_csv('AirQualityPrediction/TrainingData.csv', header=0) +``` + +我们可以通过'chunkID'变量(列索引 1)对数据进行分组。 + +首先,让我们获取唯一的块标识符列表。 + +``` +chunk_ids = unique(values[:, 1]) +``` + +然后,我们可以收集每个块标识符的所有行,并将它们存储在字典中以便于访问。 + +``` +chunks = dict() +# sort rows by chunk id +for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks[chunk_id] = values[selection, :] +``` + +下面定义了一个名为 _to_chunks()_ 的函数,它接受加载数据的 NumPy 数组,并将 _chunk_id_ 的字典返回到块的行。 + +``` +# split the dataset by 'chunkID', return a dict of id to rows +def to_chunks(values, chunk_ix=1): + chunks = dict() + # get the unique chunk ids + chunk_ids = unique(values[:, chunk_ix]) + # group rows by chunk id + for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks[chunk_id] = values[selection, :] + return chunks +``` + +下面列出了加载数据集并将其拆分为块的完整示例。 + +``` +# load data and split into chunks +from numpy import unique +from pandas import read_csv + +# split the dataset by 'chunkID', return a dict of id to rows +def to_chunks(values, chunk_ix=1): + chunks = dict() + # get the unique chunk ids + chunk_ids = unique(values[:, chunk_ix]) + # group rows by chunk id + for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks[chunk_id] = values[selection, :] + return chunks + +# load dataset +dataset = read_csv('AirQualityPrediction/TrainingData.csv', header=0) +# group data by chunks +values = dataset.values +chunks = to_chunks(values) +print('Total Chunks: %d' % len(chunks)) +``` + +运行该示例将打印数据集中的块数。 + +``` +Total Chunks: 208 +``` + +### 数据准备 + +既然我们知道如何加载数据并将其拆分成块,我们就可以将它们分成训练和测试数据集。 + +尽管每个块内的实际观测数量可能差异很大,但每个块的每小时观察间隔为 8 天。 + +我们可以将每个块分成前五天的训练观察和最后三天的测试。 + +每个观察都有一行称为' _position_within_chunk_ ',从 1 到 192(8 天* 24 小时)不等。因此,我们可以将此列中值小于或等于 120(5 * 24)的所有行作为训练数据,将任何大于 120 的值作为测试数据。 + +此外,任何在列车或测试分割中没有任何观察的块都可以被丢弃,因为不可行。 + +在使用朴素模型时,我们只对目标变量感兴趣,而不对输入的气象变量感兴趣。因此,我们可以删除输入数据,并使列车和测试数据仅包含每个块的 39 个目标变量,以及块和观察时间内的位置。 + +下面的 _split_train_test()_ 函数实现了这种行为;给定一个块的字典,它将每个分成列车和测试块数据。 + +``` +# split each chunk into train/test sets +def split_train_test(chunks, row_in_chunk_ix=2): + train, test = list(), list() + # first 5 days of hourly observations for train + cut_point = 5 * 24 + # enumerate chunks + for k,rows in chunks.items(): + # split chunk rows by 'position_within_chunk' + train_rows = rows[rows[:,row_in_chunk_ix] <= cut_point, :] + test_rows = rows[rows[:,row_in_chunk_ix] > cut_point, :] + if len(train_rows) == 0 or len(test_rows) == 0: + print('>dropping chunk=%d: train=%s, test=%s' % (k, train_rows.shape, test_rows.shape)) + continue + # store with chunk id, position in chunk, hour and all targets + indices = [1,2,5] + [x for x in range(56,train_rows.shape[1])] + train.append(train_rows[:, indices]) + test.append(test_rows[:, indices]) + return train, test +``` + +我们不需要整个测试数据集;相反,我们只需要在三天时间内的特定提前期进行观察,特别是提前期: + +``` ++1, +2, +3, +4, +5, +10, +17, +24, +48, +72 +``` + +其中,每个提前期相对于训练期结束。 + +首先,我们可以将这些提前期放入函数中以便于参考: + +``` +# return a list of relative forecast lead times +def get_lead_times(): + return [1, 2 ,3, 4, 5, 10, 17, 24, 48, 72] +``` + +接下来,我们可以将测试数据集缩减为仅在首选提前期的数据。 + +我们可以通过查看' _position_within_chunk_ '列并使用提前期作为距离训练数据集末尾的偏移量来实现,例如: 120 + 1,120 +2 等 + +如果我们在测试集中找到匹配的行,则保存它,否则生成一行 NaN 观测值。 + +下面的函数 _to_forecasts()_ 实现了这一点,并为每个块的每个预测提前期返回一行 NumPy 数组。 + +``` +# convert the rows in a test chunk to forecasts +def to_forecasts(test_chunks, row_in_chunk_ix=1): + # get lead times + lead_times = get_lead_times() + # first 5 days of hourly observations for train + cut_point = 5 * 24 + forecasts = list() + # enumerate each chunk + for rows in test_chunks: + chunk_id = rows[0, 0] + # enumerate each lead time + for tau in lead_times: + # determine the row in chunk we want for the lead time + offset = cut_point + tau + # retrieve data for the lead time using row number in chunk + row_for_tau = rows[rows[:,row_in_chunk_ix]==offset, :] + # check if we have data + if len(row_for_tau) == 0: + # create a mock row [chunk, position, hour] + [nan...] + row = [chunk_id, offset, nan] + [nan for _ in range(39)] + forecasts.append(row) + else: + # store the forecast row + forecasts.append(row_for_tau[0]) + return array(forecasts) +``` + +我们可以将所有这些组合在一起并将数据集拆分为训练集和测试集,并将结果保存到新文件中。 + +完整的代码示例如下所示。 + +``` +# split data into train and test sets +from numpy import unique +from numpy import nan +from numpy import array +from numpy import savetxt +from pandas import read_csv + +# split the dataset by 'chunkID', return a dict of id to rows +def to_chunks(values, chunk_ix=1): + chunks = dict() + # get the unique chunk ids + chunk_ids = unique(values[:, chunk_ix]) + # group rows by chunk id + for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks[chunk_id] = values[selection, :] + return chunks + +# split each chunk into train/test sets +def split_train_test(chunks, row_in_chunk_ix=2): + train, test = list(), list() + # first 5 days of hourly observations for train + cut_point = 5 * 24 + # enumerate chunks + for k,rows in chunks.items(): + # split chunk rows by 'position_within_chunk' + train_rows = rows[rows[:,row_in_chunk_ix] <= cut_point, :] + test_rows = rows[rows[:,row_in_chunk_ix] > cut_point, :] + if len(train_rows) == 0 or len(test_rows) == 0: + print('>dropping chunk=%d: train=%s, test=%s' % (k, train_rows.shape, test_rows.shape)) + continue + # store with chunk id, position in chunk, hour and all targets + indices = [1,2,5] + [x for x in range(56,train_rows.shape[1])] + train.append(train_rows[:, indices]) + test.append(test_rows[:, indices]) + return train, test + +# return a list of relative forecast lead times +def get_lead_times(): + return [1, 2 ,3, 4, 5, 10, 17, 24, 48, 72] + +# convert the rows in a test chunk to forecasts +def to_forecasts(test_chunks, row_in_chunk_ix=1): + # get lead times + lead_times = get_lead_times() + # first 5 days of hourly observations for train + cut_point = 5 * 24 + forecasts = list() + # enumerate each chunk + for rows in test_chunks: + chunk_id = rows[0, 0] + # enumerate each lead time + for tau in lead_times: + # determine the row in chunk we want for the lead time + offset = cut_point + tau + # retrieve data for the lead time using row number in chunk + row_for_tau = rows[rows[:,row_in_chunk_ix]==offset, :] + # check if we have data + if len(row_for_tau) == 0: + # create a mock row [chunk, position, hour] + [nan...] + row = [chunk_id, offset, nan] + [nan for _ in range(39)] + forecasts.append(row) + else: + # store the forecast row + forecasts.append(row_for_tau[0]) + return array(forecasts) + +# load dataset +dataset = read_csv('AirQualityPrediction/TrainingData.csv', header=0) +# group data by chunks +values = dataset.values +chunks = to_chunks(values) +# split into train/test +train, test = split_train_test(chunks) +# flatten training chunks to rows +train_rows = array([row for rows in train for row in rows]) +# print(train_rows.shape) +print('Train Rows: %s' % str(train_rows.shape)) +# reduce train to forecast lead times only +test_rows = to_forecasts(test) +print('Test Rows: %s' % str(test_rows.shape)) +# save datasets +savetxt('AirQualityPrediction/naive_train.csv', train_rows, delimiter=',') +savetxt('AirQualityPrediction/naive_test.csv', test_rows, delimiter=',') +``` + +运行该示例首先评论了从数据集中移除了块 69 以获得不足的数据。 + +然后我们可以看到每个列车和测试集中有 42 列,一个用于块 ID,块内位置,一天中的小时和 39 个训练变量。 + +我们还可以看到测试数据集的显着缩小版本,其中行仅在预测前置时间。 + +新的训练和测试数据集分别保存在' _naive_train.csv_ '和' _naive_test.csv_ '文件中。 + +``` +>dropping chunk=69: train=(0, 95), test=(28, 95) +Train Rows: (23514, 42) +Test Rows: (2070, 42) +``` + +### 预测评估 + +一旦做出预测,就需要对它们进行评估。 + +在评估预测时,使用更简单的格式会很有帮助。例如,我们将使用 _[chunk] [变量] [时间]_ 的三维结构,其中变量是从 0 到 38 的目标变量数,time 是从 0 到 9 的提前期索引。 + +模型有望以这种格式进行预测。 + +我们还可以重新构建测试数据集以使此数据集进行比较。下面的 _prepare_test_forecasts()_ 函数实现了这一点。 + +``` +# convert the test dataset in chunks to [chunk][variable][time] format +def prepare_test_forecasts(test_chunks): + predictions = list() + # enumerate chunks to forecast + for rows in test_chunks: + # enumerate targets for chunk + chunk_predictions = list() + for j in range(3, rows.shape[1]): + yhat = rows[:, j] + chunk_predictions.append(yhat) + chunk_predictions = array(chunk_predictions) + predictions.append(chunk_predictions) + return array(predictions) +``` + +我们将使用平均绝对误差或 MAE 来评估模型。这是在竞争中使用的度量,并且在给定目标变量的非高斯分布的情况下是合理的选择。 + +如果提前期不包含测试集中的数据(例如 _NaN_ ),则不会计算该预测的错误。如果提前期确实在测试集中有数据但预测中没有数据,那么观察的全部大小将被视为错误。最后,如果测试集具有观察值并进行预测,则绝对差值将被记录为误差。 + +_calculate_error()_ 函数实现这些规则并返回给定预测的错误。 + +``` +# calculate the error between an actual and predicted value +def calculate_error(actual, predicted): + # give the full actual value if predicted is nan + if isnan(predicted): + return abs(actual) + # calculate abs difference + return abs(actual - predicted) +``` + +错误在所有块和所有提前期之间求和,然后取平均值。 + +将计算总体 MAE,但我们还将计算每个预测提前期的 MAE。这通常有助于模型选择,因为某些模型在不同的提前期可能会有不同的表现。 + +下面的 evaluate_forecasts()函数实现了这一点,计算了 _[chunk] [variable] [time]_ 格式中提供的预测和期望值的 MAE 和每个引导时间 MAE。 + +``` +# evaluate a forecast in the format [chunk][variable][time] +def evaluate_forecasts(predictions, testset): + lead_times = get_lead_times() + total_mae, times_mae = 0.0, [0.0 for _ in range(len(lead_times))] + total_c, times_c = 0, [0 for _ in range(len(lead_times))] + # enumerate test chunks + for i in range(len(test_chunks)): + # convert to forecasts + actual = testset[i] + predicted = predictions[i] + # enumerate target variables + for j in range(predicted.shape[0]): + # enumerate lead times + for k in range(len(lead_times)): + # skip if actual in nan + if isnan(actual[j, k]): + continue + # calculate error + error = calculate_error(actual[j, k], predicted[j, k]) + # update statistics + total_mae += error + times_mae[k] += error + total_c += 1 + times_c[k] += 1 + # normalize summed absolute errors + total_mae /= total_c + times_mae = [times_mae[i]/times_c[i] for i in range(len(times_mae))] + return total_mae, times_mae +``` + +一旦我们对模型进行评估,我们就可以呈现它。 + +下面的 _summarize_error()_ 函数首先打印模型表现的一行摘要,然后创建每个预测提前期的 MAE 图。 + +``` +# summarize scores +def summarize_error(name, total_mae, times_mae): + # print summary + lead_times = get_lead_times() + formatted = ['+%d %.3f' % (lead_times[i], times_mae[i]) for i in range(len(lead_times))] + s_scores = ', '.join(formatted) + print('%s: [%.3f MAE] %s' % (name, total_mae, s_scores)) + # plot summary + pyplot.plot([str(x) for x in lead_times], times_mae, marker='.') + pyplot.show() +``` + +我们现在准备开始探索天真预测方法的表现。 + +## 全球朴素的方法 + +在本节中,我们将探索使用训练数据集中所有数据的天真预测方法,而不是约束我们正在进行预测的块。 + +我们将看两种方法: + +* 预测每个系列的平均值 +* 预测每个系列的每日小时的平均值 + +### 预测每个系列的平均值 + +第一步是实现一个通用函数,用于为每个块进行预测。 + +该函数获取测试集的训练数据集和输入列(块 ID,块中的位置和小时),并返回具有 _[块] [变量] [时间]的预期 3D 格式的所有块的预测 _。 + +该函数枚举预测中的块,然后枚举 39 个目标列,调用另一个名为 _forecast_variable()_ 的新函数,以便对给定目标变量的每个提前期进行预测。 + +完整的功能如下所列。 + +``` +# forecast for each chunk, returns [chunk][variable][time] +def forecast_chunks(train_chunks, test_input): + lead_times = get_lead_times() + predictions = list() + # enumerate chunks to forecast + for i in range(len(train_chunks)): + # enumerate targets for chunk + chunk_predictions = list() + for j in range(39): + yhat = forecast_variable(train_chunks, train_chunks[i], test_input[i], lead_times, j) + chunk_predictions.append(yhat) + chunk_predictions = array(chunk_predictions) + predictions.append(chunk_predictions) + return array(predictions) +``` + +我们现在可以实现 _forecast_variable()_ 的一个版本,该版本计算给定系列的平均值,并预测每个提前期的平均值。 + +首先,我们必须在所有块中收集目标列中的所有观测值,然后计算观测值的平均值,同时忽略 NaN 值。 _nanmean()_ NumPy 函数将计算阵列的平均值并忽略 _NaN_ 值。 + +下面的 _forecast_variable()_ 函数实现了这种行为。 + +``` +# forecast all lead times for one variable +def forecast_variable(train_chunks, chunk_train, chunk_test, lead_times, target_ix): + # convert target number into column number + col_ix = 3 + target_ix + # collect obs from all chunks + all_obs = list() + for chunk in train_chunks: + all_obs += [x for x in chunk[:, col_ix]] + # return the average, ignoring nan + value = nanmean(all_obs) + return [value for _ in lead_times] +``` + +我们现在拥有我们需要的一切。 + +下面列出了在所有预测提前期内预测每个系列的全局均值的完整示例。 + +``` +# forecast global mean +from numpy import loadtxt +from numpy import nan +from numpy import isnan +from numpy import count_nonzero +from numpy import unique +from numpy import array +from numpy import nanmean +from matplotlib import pyplot + +# split the dataset by 'chunkID', return a list of chunks +def to_chunks(values, chunk_ix=0): + chunks = list() + # get the unique chunk ids + chunk_ids = unique(values[:, chunk_ix]) + # group rows by chunk id + for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks.append(values[selection, :]) + return chunks + +# return a list of relative forecast lead times +def get_lead_times(): + return [1, 2 ,3, 4, 5, 10, 17, 24, 48, 72] + +# forecast all lead times for one variable +def forecast_variable(train_chunks, chunk_train, chunk_test, lead_times, target_ix): + # convert target number into column number + col_ix = 3 + target_ix + # collect obs from all chunks + all_obs = list() + for chunk in train_chunks: + all_obs += [x for x in chunk[:, col_ix]] + # return the average, ignoring nan + value = nanmean(all_obs) + return [value for _ in lead_times] + +# forecast for each chunk, returns [chunk][variable][time] +def forecast_chunks(train_chunks, test_input): + lead_times = get_lead_times() + predictions = list() + # enumerate chunks to forecast + for i in range(len(train_chunks)): + # enumerate targets for chunk + chunk_predictions = list() + for j in range(39): + yhat = forecast_variable(train_chunks, train_chunks[i], test_input[i], lead_times, j) + chunk_predictions.append(yhat) + chunk_predictions = array(chunk_predictions) + predictions.append(chunk_predictions) + return array(predictions) + +# convert the test dataset in chunks to [chunk][variable][time] format +def prepare_test_forecasts(test_chunks): + predictions = list() + # enumerate chunks to forecast + for rows in test_chunks: + # enumerate targets for chunk + chunk_predictions = list() + for j in range(3, rows.shape[1]): + yhat = rows[:, j] + chunk_predictions.append(yhat) + chunk_predictions = array(chunk_predictions) + predictions.append(chunk_predictions) + return array(predictions) + +# calculate the error between an actual and predicted value +def calculate_error(actual, predicted): + # give the full actual value if predicted is nan + if isnan(predicted): + return abs(actual) + # calculate abs difference + return abs(actual - predicted) + +# evaluate a forecast in the format [chunk][variable][time] +def evaluate_forecasts(predictions, testset): + lead_times = get_lead_times() + total_mae, times_mae = 0.0, [0.0 for _ in range(len(lead_times))] + total_c, times_c = 0, [0 for _ in range(len(lead_times))] + # enumerate test chunks + for i in range(len(test_chunks)): + # convert to forecasts + actual = testset[i] + predicted = predictions[i] + # enumerate target variables + for j in range(predicted.shape[0]): + # enumerate lead times + for k in range(len(lead_times)): + # skip if actual in nan + if isnan(actual[j, k]): + continue + # calculate error + error = calculate_error(actual[j, k], predicted[j, k]) + # update statistics + total_mae += error + times_mae[k] += error + total_c += 1 + times_c[k] += 1 + # normalize summed absolute errors + total_mae /= total_c + times_mae = [times_mae[i]/times_c[i] for i in range(len(times_mae))] + return total_mae, times_mae + +# summarize scores +def summarize_error(name, total_mae, times_mae): + # print summary + lead_times = get_lead_times() + formatted = ['+%d %.3f' % (lead_times[i], times_mae[i]) for i in range(len(lead_times))] + s_scores = ', '.join(formatted) + print('%s: [%.3f MAE] %s' % (name, total_mae, s_scores)) + # plot summary + pyplot.plot([str(x) for x in lead_times], times_mae, marker='.') + pyplot.show() + +# load dataset +train = loadtxt('AirQualityPrediction/naive_train.csv', delimiter=',') +test = loadtxt('AirQualityPrediction/naive_test.csv', delimiter=',') +# group data by chunks +train_chunks = to_chunks(train) +test_chunks = to_chunks(test) +# forecast +test_input = [rows[:, :3] for rows in test_chunks] +forecast = forecast_chunks(train_chunks, test_input) +# evaluate forecast +actual = prepare_test_forecasts(test_chunks) +total_mae, times_mae = evaluate_forecasts(forecast, actual) +# summarize forecast +summarize_error('Global Mean', total_mae, times_mae) +``` + +首先运行该示例打印的总体 MAE 为 0.634,然后是每个预测提前期的 MAE 分数。 + +``` +# Global Mean: [0.634 MAE] +1 0.635, +2 0.629, +3 0.638, +4 0.650, +5 0.649, +10 0.635, +17 0.634, +24 0.641, +48 0.613, +72 0.618 +``` + +创建一个线图,显示每个预测前置时间的 MAE 分数,从+1 小时到+72 小时。 + +我们无法看到预测前置时间与预测错误有任何明显的关系,正如我们对更熟练的模型所期望的那样。 + +![MAE by Forecast Lead Time With Global Mean](img/706562ef19b5c359fea66d29146de4ef.jpg) + +MAE 按预测带领全球均值时间 + +我们可以更新示例来预测全局中位数而不是平均值。 + +考虑到数据似乎显示的非高斯分布,中值可能比使用该数据的均值更有意义地用作集中趋势。 + +NumPy 提供 _nanmedian()_ 功能,我们可以在 _forecast_variable()_ 函数中代替 _nanmean()_。 + +完整更新的示例如下所示。 + +``` +# forecast global median +from numpy import loadtxt +from numpy import nan +from numpy import isnan +from numpy import count_nonzero +from numpy import unique +from numpy import array +from numpy import nanmedian +from matplotlib import pyplot + +# split the dataset by 'chunkID', return a list of chunks +def to_chunks(values, chunk_ix=0): + chunks = list() + # get the unique chunk ids + chunk_ids = unique(values[:, chunk_ix]) + # group rows by chunk id + for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks.append(values[selection, :]) + return chunks + +# return a list of relative forecast lead times +def get_lead_times(): + return [1, 2 ,3, 4, 5, 10, 17, 24, 48, 72] + +# forecast all lead times for one variable +def forecast_variable(train_chunks, chunk_train, chunk_test, lead_times, target_ix): + # convert target number into column number + col_ix = 3 + target_ix + # collect obs from all chunks + all_obs = list() + for chunk in train_chunks: + all_obs += [x for x in chunk[:, col_ix]] + # return the average, ignoring nan + value = nanmedian(all_obs) + return [value for _ in lead_times] + +# forecast for each chunk, returns [chunk][variable][time] +def forecast_chunks(train_chunks, test_input): + lead_times = get_lead_times() + predictions = list() + # enumerate chunks to forecast + for i in range(len(train_chunks)): + # enumerate targets for chunk + chunk_predictions = list() + for j in range(39): + yhat = forecast_variable(train_chunks, train_chunks[i], test_input[i], lead_times, j) + chunk_predictions.append(yhat) + chunk_predictions = array(chunk_predictions) + predictions.append(chunk_predictions) + return array(predictions) + +# convert the test dataset in chunks to [chunk][variable][time] format +def prepare_test_forecasts(test_chunks): + predictions = list() + # enumerate chunks to forecast + for rows in test_chunks: + # enumerate targets for chunk + chunk_predictions = list() + for j in range(3, rows.shape[1]): + yhat = rows[:, j] + chunk_predictions.append(yhat) + chunk_predictions = array(chunk_predictions) + predictions.append(chunk_predictions) + return array(predictions) + +# calculate the error between an actual and predicted value +def calculate_error(actual, predicted): + # give the full actual value if predicted is nan + if isnan(predicted): + return abs(actual) + # calculate abs difference + return abs(actual - predicted) + +# evaluate a forecast in the format [chunk][variable][time] +def evaluate_forecasts(predictions, testset): + lead_times = get_lead_times() + total_mae, times_mae = 0.0, [0.0 for _ in range(len(lead_times))] + total_c, times_c = 0, [0 for _ in range(len(lead_times))] + # enumerate test chunks + for i in range(len(test_chunks)): + # convert to forecasts + actual = testset[i] + predicted = predictions[i] + # enumerate target variables + for j in range(predicted.shape[0]): + # enumerate lead times + for k in range(len(lead_times)): + # skip if actual in nan + if isnan(actual[j, k]): + continue + # calculate error + error = calculate_error(actual[j, k], predicted[j, k]) + # update statistics + total_mae += error + times_mae[k] += error + total_c += 1 + times_c[k] += 1 + # normalize summed absolute errors + total_mae /= total_c + times_mae = [times_mae[i]/times_c[i] for i in range(len(times_mae))] + return total_mae, times_mae + +# summarize scores +def summarize_error(name, total_mae, times_mae): + # print summary + lead_times = get_lead_times() + formatted = ['+%d %.3f' % (lead_times[i], times_mae[i]) for i in range(len(lead_times))] + s_scores = ', '.join(formatted) + print('%s: [%.3f MAE] %s' % (name, total_mae, s_scores)) + # plot summary + pyplot.plot([str(x) for x in lead_times], times_mae, marker='.') + pyplot.show() + +# load dataset +train = loadtxt('AirQualityPrediction/naive_train.csv', delimiter=',') +test = loadtxt('AirQualityPrediction/naive_test.csv', delimiter=',') +# group data by chunks +train_chunks = to_chunks(train) +test_chunks = to_chunks(test) +# forecast +test_input = [rows[:, :3] for rows in test_chunks] +forecast = forecast_chunks(train_chunks, test_input) +# evaluate forecast +actual = prepare_test_forecasts(test_chunks) +total_mae, times_mae = evaluate_forecasts(forecast, actual) +# summarize forecast +summarize_error('Global Median', total_mae, times_mae) +``` + +运行该示例显示 MAE 下降至约 0.59,表明确实使用中位数作为集中趋势可能是更好的基线策略。 + +``` +Global Median: [0.598 MAE] +1 0.601, +2 0.594, +3 0.600, +4 0.611, +5 0.615, +10 0.594, +17 0.592, +24 0.602, +48 0.585, +72 0.580 +``` + +还创建了每个提前期 MAE 的线图。 + +![MAE by Forecast Lead Time With Global Median](img/e5fb4d95be292393f8e27fa5e79a413a.jpg) + +MAE 预测带领全球中位数的时间 + +### 预测每个系列的每日小时的平均值 + +我们可以通过系列更新用于计算集中趋势的朴素模型,以仅包括与预测提前期具有相同时段的行。 + +例如,如果+1 提前时间具有小时 6(例如 0600 或 6AM),那么我们可以在该小时的所有块中找到训练数据集中的所有其他行,并从这些行计算给定目标变量的中值。 。 + +我们在测试数据集中记录一天中的小时数,并在进行预测时将其提供给模型。一个问题是,在某些情况下,测试数据集没有给定提前期的记录,而且必须用 _NaN_ 值发明一个,包括小时的 _NaN_ 值。在这些情况下,不需要预测,因此我们将跳过它们并预测 _NaN_ 值。 + +下面的 _forecast_variable()_ 函数实现了这种行为,返回给定变量的每个提前期的预测。 + +效率不高,首先为每个变量预先计算每小时的中值,然后使用查找表进行预测可能会更有效。此时效率不是问题,因为我们正在寻找模型表现的基线。 + +``` +# forecast all lead times for one variable +def forecast_variable(train_chunks, chunk_train, chunk_test, lead_times, target_ix): + forecast = list() + # convert target number into column number + col_ix = 3 + target_ix + # enumerate lead times + for i in range(len(lead_times)): + # get the hour for this forecast lead time + hour = chunk_test[i, 2] + # check for no test data + if isnan(hour): + forecast.append(nan) + continue + # get all rows in training for this hour + all_rows = list() + for rows in train_chunks: + [all_rows.append(row) for row in rows[rows[:,2]==hour]] + # calculate the central tendency for target + all_rows = array(all_rows) + value = nanmedian(all_rows[:, col_ix]) + forecast.append(value) + return forecast +``` + +下面列出了按一天中的小时预测全球中值的完整示例。 + +``` +# forecast global median by hour of day +from numpy import loadtxt +from numpy import nan +from numpy import isnan +from numpy import count_nonzero +from numpy import unique +from numpy import array +from numpy import nanmedian +from matplotlib import pyplot + +# split the dataset by 'chunkID', return a list of chunks +def to_chunks(values, chunk_ix=0): + chunks = list() + # get the unique chunk ids + chunk_ids = unique(values[:, chunk_ix]) + # group rows by chunk id + for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks.append(values[selection, :]) + return chunks + +# return a list of relative forecast lead times +def get_lead_times(): + return [1, 2, 3, 4, 5, 10, 17, 24, 48, 72] + +# forecast all lead times for one variable +def forecast_variable(train_chunks, chunk_train, chunk_test, lead_times, target_ix): + forecast = list() + # convert target number into column number + col_ix = 3 + target_ix + # enumerate lead times + for i in range(len(lead_times)): + # get the hour for this forecast lead time + hour = chunk_test[i, 2] + # check for no test data + if isnan(hour): + forecast.append(nan) + continue + # get all rows in training for this hour + all_rows = list() + for rows in train_chunks: + [all_rows.append(row) for row in rows[rows[:,2]==hour]] + # calculate the central tendency for target + all_rows = array(all_rows) + value = nanmedian(all_rows[:, col_ix]) + forecast.append(value) + return forecast + +# forecast for each chunk, returns [chunk][variable][time] +def forecast_chunks(train_chunks, test_input): + lead_times = get_lead_times() + predictions = list() + # enumerate chunks to forecast + for i in range(len(train_chunks)): + # enumerate targets for chunk + chunk_predictions = list() + for j in range(39): + yhat = forecast_variable(train_chunks, train_chunks[i], test_input[i], lead_times, j) + chunk_predictions.append(yhat) + chunk_predictions = array(chunk_predictions) + predictions.append(chunk_predictions) + return array(predictions) + +# convert the test dataset in chunks to [chunk][variable][time] format +def prepare_test_forecasts(test_chunks): + predictions = list() + # enumerate chunks to forecast + for rows in test_chunks: + # enumerate targets for chunk + chunk_predictions = list() + for j in range(3, rows.shape[1]): + yhat = rows[:, j] + chunk_predictions.append(yhat) + chunk_predictions = array(chunk_predictions) + predictions.append(chunk_predictions) + return array(predictions) + +# calculate the error between an actual and predicted value +def calculate_error(actual, predicted): + # give the full actual value if predicted is nan + if isnan(predicted): + return abs(actual) + # calculate abs difference + return abs(actual - predicted) + +# evaluate a forecast in the format [chunk][variable][time] +def evaluate_forecasts(predictions, testset): + lead_times = get_lead_times() + total_mae, times_mae = 0.0, [0.0 for _ in range(len(lead_times))] + total_c, times_c = 0, [0 for _ in range(len(lead_times))] + # enumerate test chunks + for i in range(len(test_chunks)): + # convert to forecasts + actual = testset[i] + predicted = predictions[i] + # enumerate target variables + for j in range(predicted.shape[0]): + # enumerate lead times + for k in range(len(lead_times)): + # skip if actual in nan + if isnan(actual[j, k]): + continue + # calculate error + error = calculate_error(actual[j, k], predicted[j, k]) + # update statistics + total_mae += error + times_mae[k] += error + total_c += 1 + times_c[k] += 1 + # normalize summed absolute errors + total_mae /= total_c + times_mae = [times_mae[i]/times_c[i] for i in range(len(times_mae))] + return total_mae, times_mae + +# summarize scores +def summarize_error(name, total_mae, times_mae): + # print summary + lead_times = get_lead_times() + formatted = ['+%d %.3f' % (lead_times[i], times_mae[i]) for i in range(len(lead_times))] + s_scores = ', '.join(formatted) + print('%s: [%.3f MAE] %s' % (name, total_mae, s_scores)) + # plot summary + pyplot.plot([str(x) for x in lead_times], times_mae, marker='.') + pyplot.show() + +# load dataset +train = loadtxt('AirQualityPrediction/naive_train.csv', delimiter=',') +test = loadtxt('AirQualityPrediction/naive_test.csv', delimiter=',') +# group data by chunks +train_chunks = to_chunks(train) +test_chunks = to_chunks(test) +# forecast +test_input = [rows[:, :3] for rows in test_chunks] +forecast = forecast_chunks(train_chunks, test_input) +# evaluate forecast +actual = prepare_test_forecasts(test_chunks) +total_mae, times_mae = evaluate_forecasts(forecast, actual) +# summarize forecast +summarize_error('Global Median by Hour', total_mae, times_mae) +``` + +运行该示例总结了模型的表现,MAE 为 0.567,这是对每个系列的全局中位数的改进。 + +``` +Global Median by Hour: [0.567 MAE] +1 0.573, +2 0.565, +3 0.567, +4 0.579, +5 0.589, +10 0.559, +17 0.565, +24 0.567, +48 0.558, +72 0.551 +``` + +还创建了预测提前期 MAE 的线图,显示+72 具有最低的总体预测误差。这很有趣,并且可能表明基于小时的信息在更复杂的模型中可能是有用的。 + +![MAE by Forecast Lead Time With Global Median By Hour of Day](img/8cd2375501f527cda5b95eb580c969fb.jpg) + +MAE 按预测带领时间以全球中位数按天计算 + +## 大块天真的方法 + +使用特定于块的信息可能比使用来自整个训练数据集的全局信息具有更多的预测能力。 + +我们可以通过三种本地或块特定的天真预测方法来探索这个问题;他们是: + +* 预测每个系列的最后观察 +* 预测每个系列的平均值 +* 预测每个系列的每日小时的平均值 + +最后两个是在上一节中评估的全局策略的块特定版本。 + +### 预测每个系列的最后观察 + +预测块的最后一次非 NaN 观察可能是最简单的模型,通常称为持久性模型或天真模型。 + +下面的 _forecast_variable()_ 函数实现了此预测策略。 + +``` +# forecast all lead times for one variable +def forecast_variable(train_chunks, chunk_train, chunk_test, lead_times, target_ix): + # convert target number into column number + col_ix = 3 + target_ix + # extract the history for the series + history = chunk_train[:, col_ix] + # persist a nan if we do not find any valid data + persisted = nan + # enumerate history in verse order looking for the first non-nan + for value in reversed(history): + if not isnan(value): + persisted = value + break + # persist the same value for all lead times + forecast = [persisted for _ in range(len(lead_times))] + return forecast +``` + +下面列出了评估测试集上持久性预测策略的完整示例。 + +``` +# persist last observation +from numpy import loadtxt +from numpy import nan +from numpy import isnan +from numpy import count_nonzero +from numpy import unique +from numpy import array +from numpy import nanmedian +from matplotlib import pyplot + +# split the dataset by 'chunkID', return a list of chunks +def to_chunks(values, chunk_ix=0): + chunks = list() + # get the unique chunk ids + chunk_ids = unique(values[:, chunk_ix]) + # group rows by chunk id + for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks.append(values[selection, :]) + return chunks + +# return a list of relative forecast lead times +def get_lead_times(): + return [1, 2, 3, 4, 5, 10, 17, 24, 48, 72] + +# forecast all lead times for one variable +def forecast_variable(train_chunks, chunk_train, chunk_test, lead_times, target_ix): + # convert target number into column number + col_ix = 3 + target_ix + # extract the history for the series + history = chunk_train[:, col_ix] + # persist a nan if we do not find any valid data + persisted = nan + # enumerate history in verse order looking for the first non-nan + for value in reversed(history): + if not isnan(value): + persisted = value + break + # persist the same value for all lead times + forecast = [persisted for _ in range(len(lead_times))] + return forecast + +# forecast for each chunk, returns [chunk][variable][time] +def forecast_chunks(train_chunks, test_input): + lead_times = get_lead_times() + predictions = list() + # enumerate chunks to forecast + for i in range(len(train_chunks)): + # enumerate targets for chunk + chunk_predictions = list() + for j in range(39): + yhat = forecast_variable(train_chunks, train_chunks[i], test_input[i], lead_times, j) + chunk_predictions.append(yhat) + chunk_predictions = array(chunk_predictions) + predictions.append(chunk_predictions) + return array(predictions) + +# convert the test dataset in chunks to [chunk][variable][time] format +def prepare_test_forecasts(test_chunks): + predictions = list() + # enumerate chunks to forecast + for rows in test_chunks: + # enumerate targets for chunk + chunk_predictions = list() + for j in range(3, rows.shape[1]): + yhat = rows[:, j] + chunk_predictions.append(yhat) + chunk_predictions = array(chunk_predictions) + predictions.append(chunk_predictions) + return array(predictions) + +# calculate the error between an actual and predicted value +def calculate_error(actual, predicted): + # give the full actual value if predicted is nan + if isnan(predicted): + return abs(actual) + # calculate abs difference + return abs(actual - predicted) + +# evaluate a forecast in the format [chunk][variable][time] +def evaluate_forecasts(predictions, testset): + lead_times = get_lead_times() + total_mae, times_mae = 0.0, [0.0 for _ in range(len(lead_times))] + total_c, times_c = 0, [0 for _ in range(len(lead_times))] + # enumerate test chunks + for i in range(len(test_chunks)): + # convert to forecasts + actual = testset[i] + predicted = predictions[i] + # enumerate target variables + for j in range(predicted.shape[0]): + # enumerate lead times + for k in range(len(lead_times)): + # skip if actual in nan + if isnan(actual[j, k]): + continue + # calculate error + error = calculate_error(actual[j, k], predicted[j, k]) + # update statistics + total_mae += error + times_mae[k] += error + total_c += 1 + times_c[k] += 1 + # normalize summed absolute errors + total_mae /= total_c + times_mae = [times_mae[i]/times_c[i] for i in range(len(times_mae))] + return total_mae, times_mae + +# summarize scores +def summarize_error(name, total_mae, times_mae): + # print summary + lead_times = get_lead_times() + formatted = ['+%d %.3f' % (lead_times[i], times_mae[i]) for i in range(len(lead_times))] + s_scores = ', '.join(formatted) + print('%s: [%.3f MAE] %s' % (name, total_mae, s_scores)) + # plot summary + pyplot.plot([str(x) for x in lead_times], times_mae, marker='.') + pyplot.show() + +# load dataset +train = loadtxt('AirQualityPrediction/naive_train.csv', delimiter=',') +test = loadtxt('AirQualityPrediction/naive_test.csv', delimiter=',') +# group data by chunks +train_chunks = to_chunks(train) +test_chunks = to_chunks(test) +# forecast +test_input = [rows[:, :3] for rows in test_chunks] +forecast = forecast_chunks(train_chunks, test_input) +# evaluate forecast +actual = prepare_test_forecasts(test_chunks) +total_mae, times_mae = evaluate_forecasts(forecast, actual) +# summarize forecast +summarize_error('Persistence', total_mae, times_mae) +``` + +运行该示例将按预测提前期打印整体 MAE 和 MAE。 + +我们可以看到,持久性预测似乎胜过上一节中评估的所有全局策略。 + +这增加了一些支持,即合理假设特定于块的信息在建模此问题时很重要。 + +``` +Persistence: [0.520 MAE] +1 0.217, +2 0.330, +3 0.400, +4 0.471, +5 0.515, +10 0.648, +17 0.656, +24 0.589, +48 0.671, +72 0.708 +``` + +创建每个预测提前期的 MAE 线图。 + +重要的是,该图显示了随着预测提前期的增加而增加误差的预期行为。也就是说,进一步预测到未来,它越具挑战性,反过来,预期会产生越多的错误。 + +![MAE by Forecast Lead Time via Persistence](img/1d5a159cb9418c7c6d4b7bff4f258dfd.jpg) + +MAE 通过持续性预测提前期 + +### 预测每个系列的平均值 + +我们可以仅使用块中的数据来保持系列的平均值,而不是持续系列的最后一次观察。 + +具体来说,我们可以计算出系列的中位数,正如我们在上一节中发现的那样,似乎可以带来更好的表现。 + +_forecast_variable()_ 实现了这种本地策略。 + +``` +# forecast all lead times for one variable +def forecast_variable(train_chunks, chunk_train, chunk_test, lead_times, target_ix): + # convert target number into column number + col_ix = 3 + target_ix + # extract the history for the series + history = chunk_train[:, col_ix] + # calculate the central tendency + value = nanmedian(history) + # persist the same value for all lead times + forecast = [value for _ in range(len(lead_times))] + return forecast +``` + +下面列出了完整的示例。 + +``` +# forecast local median +from numpy import loadtxt +from numpy import nan +from numpy import isnan +from numpy import count_nonzero +from numpy import unique +from numpy import array +from numpy import nanmedian +from matplotlib import pyplot + +# split the dataset by 'chunkID', return a list of chunks +def to_chunks(values, chunk_ix=0): + chunks = list() + # get the unique chunk ids + chunk_ids = unique(values[:, chunk_ix]) + # group rows by chunk id + for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks.append(values[selection, :]) + return chunks + +# return a list of relative forecast lead times +def get_lead_times(): + return [1, 2, 3, 4, 5, 10, 17, 24, 48, 72] + +# forecast all lead times for one variable +def forecast_variable(train_chunks, chunk_train, chunk_test, lead_times, target_ix): + # convert target number into column number + col_ix = 3 + target_ix + # extract the history for the series + history = chunk_train[:, col_ix] + # calculate the central tendency + value = nanmedian(history) + # persist the same value for all lead times + forecast = [value for _ in range(len(lead_times))] + return forecast + +# forecast for each chunk, returns [chunk][variable][time] +def forecast_chunks(train_chunks, test_input): + lead_times = get_lead_times() + predictions = list() + # enumerate chunks to forecast + for i in range(len(train_chunks)): + # enumerate targets for chunk + chunk_predictions = list() + for j in range(39): + yhat = forecast_variable(train_chunks, train_chunks[i], test_input[i], lead_times, j) + chunk_predictions.append(yhat) + chunk_predictions = array(chunk_predictions) + predictions.append(chunk_predictions) + return array(predictions) + +# convert the test dataset in chunks to [chunk][variable][time] format +def prepare_test_forecasts(test_chunks): + predictions = list() + # enumerate chunks to forecast + for rows in test_chunks: + # enumerate targets for chunk + chunk_predictions = list() + for j in range(3, rows.shape[1]): + yhat = rows[:, j] + chunk_predictions.append(yhat) + chunk_predictions = array(chunk_predictions) + predictions.append(chunk_predictions) + return array(predictions) + +# calculate the error between an actual and predicted value +def calculate_error(actual, predicted): + # give the full actual value if predicted is nan + if isnan(predicted): + return abs(actual) + # calculate abs difference + return abs(actual - predicted) + +# evaluate a forecast in the format [chunk][variable][time] +def evaluate_forecasts(predictions, testset): + lead_times = get_lead_times() + total_mae, times_mae = 0.0, [0.0 for _ in range(len(lead_times))] + total_c, times_c = 0, [0 for _ in range(len(lead_times))] + # enumerate test chunks + for i in range(len(test_chunks)): + # convert to forecasts + actual = testset[i] + predicted = predictions[i] + # enumerate target variables + for j in range(predicted.shape[0]): + # enumerate lead times + for k in range(len(lead_times)): + # skip if actual in nan + if isnan(actual[j, k]): + continue + # calculate error + error = calculate_error(actual[j, k], predicted[j, k]) + # update statistics + total_mae += error + times_mae[k] += error + total_c += 1 + times_c[k] += 1 + # normalize summed absolute errors + total_mae /= total_c + times_mae = [times_mae[i]/times_c[i] for i in range(len(times_mae))] + return total_mae, times_mae + +# summarize scores +def summarize_error(name, total_mae, times_mae): + # print summary + lead_times = get_lead_times() + formatted = ['+%d %.3f' % (lead_times[i], times_mae[i]) for i in range(len(lead_times))] + s_scores = ', '.join(formatted) + print('%s: [%.3f MAE] %s' % (name, total_mae, s_scores)) + # plot summary + pyplot.plot([str(x) for x in lead_times], times_mae, marker='.') + pyplot.show() + +# load dataset +train = loadtxt('AirQualityPrediction/naive_train.csv', delimiter=',') +test = loadtxt('AirQualityPrediction/naive_test.csv', delimiter=',') +# group data by chunks +train_chunks = to_chunks(train) +test_chunks = to_chunks(test) +# forecast +test_input = [rows[:, :3] for rows in test_chunks] +forecast = forecast_chunks(train_chunks, test_input) +# evaluate forecast +actual = prepare_test_forecasts(test_chunks) +total_mae, times_mae = evaluate_forecasts(forecast, actual) +# summarize forecast +summarize_error('Local Median', total_mae, times_mae) +``` + +运行该示例总结了这种天真策略的表现,显示了大约 0.568 的 MAE,这比上述持久性策略更糟糕。 + +``` +Local Median: [0.568 MAE] +1 0.535, +2 0.542, +3 0.550, +4 0.568, +5 0.568, +10 0.562, +17 0.567, +24 0.605, +48 0.590, +72 0.593 +``` + +还创建了每个预测提前期的 MAE 线图,显示了每个提前期的常见误差增加曲线。 + +![MAE by Forecast Lead Time via Local Median](img/31658b9ea259c71ef6d3d30e79a3b647.jpg) + +MAE by Forecast Lead Time via Local Median + +### 预测每个系列的每日小时的平均值 + +最后,我们可以使用每个预测提前期特定时段的每个系列的平均值来拨入持久性策略。 + +发现这种方法在全球战略中是有效的。尽管存在使用更小数据样本的风险,但仅使用来自块的数据可能是有效的。 + +下面的 _forecast_variable()_ 函数实现了这个策略,首先查找具有预测提前期小时的所有行,然后计算给定目标变量的那些行的中值。 + +``` +# forecast all lead times for one variable +def forecast_variable(train_chunks, chunk_train, chunk_test, lead_times, target_ix): + forecast = list() + # convert target number into column number + col_ix = 3 + target_ix + # enumerate lead times + for i in range(len(lead_times)): + # get the hour for this forecast lead time + hour = chunk_test[i, 2] + # check for no test data + if isnan(hour): + forecast.append(nan) + continue + # select rows in chunk with this hour + selected = chunk_train[chunk_train[:,2]==hour] + # calculate the central tendency for target + value = nanmedian(selected[:, col_ix]) + forecast.append(value) + return forecast +``` + +下面列出了完整的示例。 + +``` +# forecast local median per hour of day +from numpy import loadtxt +from numpy import nan +from numpy import isnan +from numpy import unique +from numpy import array +from numpy import nanmedian +from matplotlib import pyplot + +# split the dataset by 'chunkID', return a list of chunks +def to_chunks(values, chunk_ix=0): + chunks = list() + # get the unique chunk ids + chunk_ids = unique(values[:, chunk_ix]) + # group rows by chunk id + for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks.append(values[selection, :]) + return chunks + +# return a list of relative forecast lead times +def get_lead_times(): + return [1, 2, 3, 4, 5, 10, 17, 24, 48, 72] + +# forecast all lead times for one variable +def forecast_variable(train_chunks, chunk_train, chunk_test, lead_times, target_ix): + forecast = list() + # convert target number into column number + col_ix = 3 + target_ix + # enumerate lead times + for i in range(len(lead_times)): + # get the hour for this forecast lead time + hour = chunk_test[i, 2] + # check for no test data + if isnan(hour): + forecast.append(nan) + continue + # select rows in chunk with this hour + selected = chunk_train[chunk_train[:,2]==hour] + # calculate the central tendency for target + value = nanmedian(selected[:, col_ix]) + forecast.append(value) + return forecast + +# forecast for each chunk, returns [chunk][variable][time] +def forecast_chunks(train_chunks, test_input): + lead_times = get_lead_times() + predictions = list() + # enumerate chunks to forecast + for i in range(len(train_chunks)): + # enumerate targets for chunk + chunk_predictions = list() + for j in range(39): + yhat = forecast_variable(train_chunks, train_chunks[i], test_input[i], lead_times, j) + chunk_predictions.append(yhat) + chunk_predictions = array(chunk_predictions) + predictions.append(chunk_predictions) + return array(predictions) + +# convert the test dataset in chunks to [chunk][variable][time] format +def prepare_test_forecasts(test_chunks): + predictions = list() + # enumerate chunks to forecast + for rows in test_chunks: + # enumerate targets for chunk + chunk_predictions = list() + for j in range(3, rows.shape[1]): + yhat = rows[:, j] + chunk_predictions.append(yhat) + chunk_predictions = array(chunk_predictions) + predictions.append(chunk_predictions) + return array(predictions) + +# calculate the error between an actual and predicted value +def calculate_error(actual, predicted): + # give the full actual value if predicted is nan + if isnan(predicted): + return abs(actual) + # calculate abs difference + return abs(actual - predicted) + +# evaluate a forecast in the format [chunk][variable][time] +def evaluate_forecasts(predictions, testset): + lead_times = get_lead_times() + total_mae, times_mae = 0.0, [0.0 for _ in range(len(lead_times))] + total_c, times_c = 0, [0 for _ in range(len(lead_times))] + # enumerate test chunks + for i in range(len(test_chunks)): + # convert to forecasts + actual = testset[i] + predicted = predictions[i] + # enumerate target variables + for j in range(predicted.shape[0]): + # enumerate lead times + for k in range(len(lead_times)): + # skip if actual in nan + if isnan(actual[j, k]): + continue + # calculate error + error = calculate_error(actual[j, k], predicted[j, k]) + # update statistics + total_mae += error + times_mae[k] += error + total_c += 1 + times_c[k] += 1 + # normalize summed absolute errors + total_mae /= total_c + times_mae = [times_mae[i]/times_c[i] for i in range(len(times_mae))] + return total_mae, times_mae + +# summarize scores +def summarize_error(name, total_mae, times_mae): + # print summary + lead_times = get_lead_times() + formatted = ['+%d %.3f' % (lead_times[i], times_mae[i]) for i in range(len(lead_times))] + s_scores = ', '.join(formatted) + print('%s: [%.3f MAE] %s' % (name, total_mae, s_scores)) + # plot summary + pyplot.plot([str(x) for x in lead_times], times_mae, marker='.') + pyplot.show() + +# load dataset +train = loadtxt('AirQualityPrediction/naive_train.csv', delimiter=',') +test = loadtxt('AirQualityPrediction/naive_test.csv', delimiter=',') +# group data by chunks +train_chunks = to_chunks(train) +test_chunks = to_chunks(test) +# forecast +test_input = [rows[:, :3] for rows in test_chunks] +forecast = forecast_chunks(train_chunks, test_input) +# evaluate forecast +actual = prepare_test_forecasts(test_chunks) +total_mae, times_mae = evaluate_forecasts(forecast, actual) +# summarize forecast +summarize_error('Local Median by Hour', total_mae, times_mae) +``` + +运行该示例打印的总体 MAE 约为 0.574,这比同一策略的全局变化更差。 + +如所怀疑的那样,这可能是由于样本量很小,最多五行训练数据对每个预测都有贡献。 + +``` +Local Median by Hour: [0.574 MAE] +1 0.561, +2 0.559, +3 0.568, +4 0.577, +5 0.577, +10 0.556, +17 0.551, +24 0.588, +48 0.601, +72 0.608 +``` + +还创建了每个预测提前期的 MAE 线图,显示了每个提前期的常见误差增加曲线。 + +![MAE by Forecast Lead Time via Local Median By Hour of Day](img/5bfa775f16608668d8981b772cb2d6f5.jpg) + +MAE 按预测提前时间按当地中位数按小时计算 + +## 结果摘要 + +我们可以总结本教程中审查的所有天真预测方法的表现。 + +下面的例子列出了每个小时的' _g_ '和' _l_ '用于全局和本地以及' _h_ '用于小时的每种方法变化。该示例创建了一个条形图,以便我们可以根据它们的相对表现来比较天真的策略。 + +``` +# summary of results +from matplotlib import pyplot +# results +results = { + 'g-mean':0.634, + 'g-med':0.598, + 'g-med-h':0.567, + 'l-per':0.520, + 'l-med':0.568, + 'l-med-h':0.574} +# plot +pyplot.bar(results.keys(), results.values()) +locs, labels = pyplot.xticks() +pyplot.setp(labels, rotation=30) +pyplot.show() +``` + +运行该示例会创建一个条形图,比较六种策略中每种策略的 MAE。 + +我们可以看到持久性策略优于所有其他方法,并且第二个最佳策略是使用一小时的每个系列的全局中位数。 + +在该列车/测试分离数据集上评估的模型必须达到低于 0.520 的总体 MAE 才能被认为是熟练的。 + +![Bar Chart with Summary of Naive Forecast Methods](img/21d06aff1fb1e5cd7453455d560e8c1f.jpg) + +带有朴素预测方法概要的条形图 + +## 扩展 + +本节列出了一些扩展您可能希望探索的教程的想法。 + +* **跨站点天真预测**。制定一个天真的预测策略,该策略使用跨站点的每个变量的信息,例如:不同站点的同一变量的不同目标变量。 +* **混合方法**。制定混合预测策略,该策略结合了本教程中描述的两个或更多天真预测策略的元素。 +* **朴素方法的集合**。制定集合预测策略,创建本教程中描述的两个或更多预测策略的线性组合。 + +如果你探索任何这些扩展,我很想知道。 + +## 进一步阅读 + +如果您希望深入了解,本节将提供有关该主题的更多资源。 + +### 帖子 + +* [标准多变量,多步骤和多站点时间序列预测问题](https://machinelearningmastery.com/standard-multivariate-multi-step-multi-site-time-series-forecasting-problem/) +* [如何使用 Python 进行时间序列预测的基线预测](https://machinelearningmastery.com/persistence-time-series-forecasting-with-python/) + +### 用品 + +* [EMC 数据科学全球黑客马拉松(空气质量预测)](https://www.kaggle.com/c/dsg-hackathon/data) +* [将所有东西放入随机森林:Ben Hamner 赢得空气质量预测黑客马拉松](http://blog.kaggle.com/2012/05/01/chucking-everything-into-a-random-forest-ben-hamner-on-winning-the-air-quality-prediction-hackathon/) +* [EMC 数据科学全球黑客马拉松(空气质量预测)的获奖代码](https://github.com/benhamner/Air-Quality-Prediction-Hackathon-Winning-Model) +* [分区模型的一般方法?](https://www.kaggle.com/c/dsg-hackathon/discussion/1821) + +## 摘要 + +在本教程中,您了解了如何为多步骤多变量空气污染时间序列预测问题开发天真的预测方法。 + +具体来说,你学到了: + +* 如何开发用于评估大气污染数据集预测策略的测试工具。 +* 如何开发使用整个训练数据集中的数据的全球天真预测策略。 +* 如何开发使用来自预测的特定区间的数据的本地天真预测策略。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/how-to-develop-convolutional-neural-network-models-for-time-series-forecasting.md b/docs/dl-ts/how-to-develop-convolutional-neural-network-models-for-time-series-forecasting.md new file mode 100644 index 0000000000000000000000000000000000000000..9edb22065f40f40f176512f69aad5e68e9a3ccba --- /dev/null +++ b/docs/dl-ts/how-to-develop-convolutional-neural-network-models-for-time-series-forecasting.md @@ -0,0 +1,1773 @@ +# 如何开发时间序列预测的卷积神经网络模型 + +> 原文: [https://machinelearningmastery.com/how-to-develop-convolutional-neural-network-models-for-time-series-forecasting/](https://machinelearningmastery.com/how-to-develop-convolutional-neural-network-models-for-time-series-forecasting/) + +卷积神经网络模型(简称 CNN)可以应用于时间序列预测。 + +有许多类型的 CNN 模型可用于每种特定类型的时间序列预测问题。 + +在本教程中,您将了解如何针对一系列标准时间序列预测问题开发一套 CNN 模型。 + +本教程的目的是为每种类型的时间序列问题提供每个模型的独立示例,作为模板,您可以根据特定的时间序列预测问题进行复制和调整。 + +完成本教程后,您将了解: + +* 如何开发 CNN 模型进行单变量时间序列预测。 +* 如何开发 CNN 模型进行多元时间序列预测。 +* 如何开发 CNN 模型进行多步时间序列预测。 + +这是一个庞大而重要的职位;您可能希望将其加入书签以供将来参考。 + +让我们开始吧。 + +![How to Develop Convolutional Neural Network Models for Time Series Forecasting](img/b41ef0d58cf243dc1ed17ede40d1bed6.jpg) + +如何开发用于时间序列预测的卷积神经网络模型 +照片由[土地管理局](https://www.flickr.com/photos/blmoregon/35464087364/),保留一些权利。 + +## 教程概述 + +在本教程中,我们将探讨如何为时间序列预测开发一套不同类型的 CNN 模型。 + +这些模型在小型设计的时间序列问题上进行了演示,旨在解决时间序列问题类型的风格。所选择的模型配置是任意的,并未针对每个问题进行优化;那不是目标。 + +本教程分为四个部分;他们是: + +1. 单变量 CNN 模型 +2. 多变量 CNN 模型 +3. 多步 CNN 模型 +4. 多变量多步 CNN 模型 + +## 单变量 CNN 模型 + +虽然传统上为二维图像数据开发,但 CNN 可用于模拟单变量时间序列预测问题。 + +单变量时间序列是由具有时间排序的单个观察序列组成的数据集,并且需要模型来从过去的一系列观察中学习以预测序列中的下一个值。 + +本节分为两部分;他们是: + +1. 数据准备 +2. CNN 模型 + +### 数据准备 + +在对单变量系列进行建模之前,必须准备好它。 + +CNN 模型将学习将过去观察序列作为输入映射到输出观察的函数。因此,必须将观察序列转换为模型可以从中学习的多个示例。 + +考虑给定的单变量序列: + +``` +[10, 20, 30, 40, 50, 60, 70, 80, 90] +``` + +我们可以将序列划分为多个称为样本的输入/输出模式,其中三个时间步长用作输入,一个时间步长用作正在学习的一步预测的输出。 + +``` +X, y +10, 20, 30 40 +20, 30, 40 50 +30, 40, 50 60 +... +``` + +下面的 _split_sequence()_ 函数实现了这种行为,并将给定的单变量序列分成多个样本,其中每个样本具有指定的时间步长,输出是单个时间步长。 + +``` +# split a univariate sequence into samples +def split_sequence(sequence, n_steps): + X, y = list(), list() + for i in range(len(sequence)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the sequence + if end_ix > len(sequence)-1: + break + # gather input and output parts of the pattern + seq_x, seq_y = sequence[i:end_ix], sequence[end_ix] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) +``` + +我们可以在上面的小型人为数据集上演示这个功能。 + +下面列出了完整的示例。 + +``` +# univariate data preparation +from numpy import array + +# split a univariate sequence into samples +def split_sequence(sequence, n_steps): + X, y = list(), list() + for i in range(len(sequence)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the sequence + if end_ix > len(sequence)-1: + break + # gather input and output parts of the pattern + seq_x, seq_y = sequence[i:end_ix], sequence[end_ix] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +raw_seq = [10, 20, 30, 40, 50, 60, 70, 80, 90] +# choose a number of time steps +n_steps = 3 +# split into samples +X, y = split_sequence(raw_seq, n_steps) +# summarize the data +for i in range(len(X)): + print(X[i], y[i]) +``` + +运行该示例将单变量系列分成六个样本,其中每个样本具有三个输入时间步长和一个输出时间步长。 + +``` +[10 20 30] 40 +[20 30 40] 50 +[30 40 50] 60 +[40 50 60] 70 +[50 60 70] 80 +[60 70 80] 90 +``` + +现在我们知道如何准备一个单变量系列进行建模,让我们看看开发一个可以学习输入到输出映射的 CNN 模型。 + +### CNN 模型 + +一维 CNN 是 CNN 模型,其具有在 1D 序列上操作的卷积隐藏层。接下来可能在某些情况下可能是第二个卷积层,例如非常长的输入序列,然后是池化层,其作用是将卷积层的输出提取到最显着的元素。 + +卷积和汇集层之后是密集的完全连接层,其解释由模型的卷积部分提取的特征。在卷积层和密集层之间使用展平层以将特征映射减少为单个一维向量。 + +我们可以如下定义用于单变量时间序列预测的 1D CNN 模型。 + +``` +# define model +model = Sequential() +model.add(Conv1D(filters=64, kernel_size=2, activation='relu', input_shape=(n_steps, n_features))) +model.add(MaxPooling1D(pool_size=2)) +model.add(Flatten()) +model.add(Dense(50, activation='relu')) +model.add(Dense(1)) +model.compile(optimizer='adam', loss='mse') +``` + +定义的关键是输入的形状;这就是模型期望的每个样本的输入,包括时间步数和特征数。 + +我们正在使用单变量系列,因此对于一个变量,要素的数量是一个。 + +输入的时间步数是我们在准备数据集时选择的数字,作为 _split_sequence()_ 函数的参数。 + +每个样本的输入形状在第一个隐藏层定义的 _input_shape_ 参数中指定。 + +我们几乎总是有多个样本,因此,模型将期望训练数据的输入组件具有尺寸或形状: + +``` +[samples, timesteps, features] +``` + +我们在前一节中的 _split_sequence()_ 函数输出具有[_ 样本,时间步长 _]形状​​的 X,因此我们可以轻松地对其进行整形,以便为一个特征提供额外的维度。 + +``` +# reshape from [samples, timesteps] into [samples, timesteps, features] +n_features = 1 +X = X.reshape((X.shape[0], X.shape[1], n_features)) +``` + +CNN 实际上并不将数据视为具有时间步长,而是将其视为可以执行卷积读取操作的序列,如一维图像。 + +在这个例子中,我们定义了一个带有 64 个滤波器映射和 2 的内核大小的卷积层。然后是最大池层和密集层来解释输入特征。指定输出层以预测单个数值。 + +使用随机梯度下降的有效 [Adam 版本拟合该模型,并使用均方误差或' _mse_ ',损失函数进行优化。](https://machinelearningmastery.com/adam-optimization-algorithm-for-deep-learning/) + +定义模型后,我们可以将其放在训练数据集上。 + +``` +# fit model +model.fit(X, y, epochs=1000, verbose=0) +``` + +在模型拟合后,我们可以使用它来进行预测。 + +我们可以通过提供输入来预测序列中的下一个值: + +``` +[70, 80, 90] +``` + +并期望模型预测如下: + +``` +[100] +``` + +该模型期望输入形状为[_ 样本,时间步长,特征 _]三维,因此,我们必须在进行预测之前对单个输入样本进行整形。 + +``` +# demonstrate prediction +x_input = array([70, 80, 90]) +x_input = x_input.reshape((1, n_steps, n_features)) +yhat = model.predict(x_input, verbose=0) +``` + +我们可以将所有这些结合在一起并演示如何开发单变量时间序列预测的 1D CNN 模型并进行单一预测。 + +``` +# univariate cnn example +from numpy import array +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import Flatten +from keras.layers.convolutional import Conv1D +from keras.layers.convolutional import MaxPooling1D + +# split a univariate sequence into samples +def split_sequence(sequence, n_steps): + X, y = list(), list() + for i in range(len(sequence)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the sequence + if end_ix > len(sequence)-1: + break + # gather input and output parts of the pattern + seq_x, seq_y = sequence[i:end_ix], sequence[end_ix] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +raw_seq = [10, 20, 30, 40, 50, 60, 70, 80, 90] +# choose a number of time steps +n_steps = 3 +# split into samples +X, y = split_sequence(raw_seq, n_steps) +# reshape from [samples, timesteps] into [samples, timesteps, features] +n_features = 1 +X = X.reshape((X.shape[0], X.shape[1], n_features)) +# define model +model = Sequential() +model.add(Conv1D(filters=64, kernel_size=2, activation='relu', input_shape=(n_steps, n_features))) +model.add(MaxPooling1D(pool_size=2)) +model.add(Flatten()) +model.add(Dense(50, activation='relu')) +model.add(Dense(1)) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit(X, y, epochs=1000, verbose=0) +# demonstrate prediction +x_input = array([70, 80, 90]) +x_input = x_input.reshape((1, n_steps, n_features)) +yhat = model.predict(x_input, verbose=0) +print(yhat) +``` + +运行该示例准备数据,拟合模型并进行预测。 + +鉴于算法的随机性,您的结果可能会有所不同;尝试运行几次这个例子。 + +我们可以看到模型预测序列中的下一个值。 + +``` +[[101.67965]] +``` + +## 多变量 CNN 模型 + +多变量时间序列数据是指每个时间步长有多个观察值的数据。 + +对于多变量时间序列数据,我们可能需要两种主要模型;他们是: + +1. 多输入系列。 +2. 多个并联系列。 + +让我们依次看看每一个。 + +### 多输入系列 + +问题可能有两个或更多并行输入时间序列和输出时间序列,这取决于输入时间序列。 + +输入时间序列是平行的,因为每个系列都在同一时间步骤中进行观察。 + +我们可以通过两个并行输入时间序列的简单示例来演示这一点,其中输出序列是输入序列的简单添加。 + +``` +# define input sequence +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95]) +out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))]) +``` + +我们可以将这三个数据数组重新整形为单个数据集,其中每一行都是一个时间步,每列是一个单独的时间序列。 + +这是将并行时间序列存储在 CSV 文件中的标准方法。 + +``` +# convert to [rows, columns] structure +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2, out_seq)) +``` + +下面列出了完整的示例。 + +``` +# multivariate data preparation +from numpy import array +from numpy import hstack +# define input sequence +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95]) +out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))]) +# convert to [rows, columns] structure +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2, out_seq)) +print(dataset) +``` + +运行该示例将打印数据集,每个时间步长为一行,两个输入和一个输出并行时间序列分别为一列。 + +``` +[[ 10 15 25] + [ 20 25 45] + [ 30 35 65] + [ 40 45 85] + [ 50 55 105] + [ 60 65 125] + [ 70 75 145] + [ 80 85 165] + [ 90 95 185]] +``` + +与单变量时间序列一样,我们必须将这些数据组织成具有输入和输出样本的样本。 + +1D CNN 模型需要足够的上下文来学习从输入序列到输出值的映射。 CNN 可以支持并行输入时间序列作为单独的通道,如图像的红色,绿色和蓝色分量。因此,我们需要将数据分成样本,保持两个输入序列的观察顺序。 + +如果我们选择三个输入时间步长,那么第一个样本将如下所示: + +输入: + +``` +10, 15 +20, 25 +30, 35 +``` + +输出: + +``` +65 +``` + +也就是说,每个并行系列的前三个时间步长被提供作为模型的输入,并且模型将其与第三时间步骤(在这种情况下为 65)的输出系列中的值相关联。 + +我们可以看到,在将时间序列转换为输入/输出样本以训练模型时,我们将不得不从输出时间序列中丢弃一些值,其中我们在先前时间步骤中没有输入时间序列中的值。反过来,选择输入时间步数的大小将对使用多少训练数据产生重要影响。 + +我们可以定义一个名为 _split_sequences()_ 的函数,该函数将采用数据集,因为我们已经为时间步长和行定义了并行序列和返回输入/输出样本的列。 + +``` +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the dataset + if end_ix > len(sequences): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :-1], sequences[end_ix-1, -1] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) +``` + +我们可以使用每个输入时间序列的三个时间步长作为输入在我们的数据集上测试此函数。 + +下面列出了完整的示例。 + +``` +# multivariate data preparation +from numpy import array +from numpy import hstack + +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the dataset + if end_ix > len(sequences): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :-1], sequences[end_ix-1, -1] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95]) +out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))]) +# convert to [rows, columns] structure +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2, out_seq)) +# choose a number of time steps +n_steps = 3 +# convert into input/output +X, y = split_sequences(dataset, n_steps) +print(X.shape, y.shape) +# summarize the data +for i in range(len(X)): + print(X[i], y[i]) +``` + +首先运行该示例打印 _X_ 和 _y_ 组件的形状。 + +我们可以看到 _X_ 组件具有三维结构。 + +第一个维度是样本数,在本例中为 7.第二个维度是每个样本的时间步数,在这种情况下为 3,即为函数指定的值。最后,最后一个维度指定并行时间序列的数量或变量的数量,在这种情况下,两个并行序列为 2。 + +这是 1D CNN 作为输入所期望的精确三维结构。数据即可使用而无需进一步重塑。 + +然后我们可以看到每个样本的输入和输出都被打印出来,显示了两个输入序列中每个样本的三个时间步长以及每个样本的相关输出。 + +``` +(7, 3, 2) (7,) + +[[10 15] + [20 25] + [30 35]] 65 +[[20 25] + [30 35] + [40 45]] 85 +[[30 35] + [40 45] + [50 55]] 105 +[[40 45] + [50 55] + [60 65]] 125 +[[50 55] + [60 65] + [70 75]] 145 +[[60 65] + [70 75] + [80 85]] 165 +[[70 75] + [80 85] + [90 95]] 185 +``` + +我们现在准备在这个数据上安装一维 CNN 模型,指定每个输入样本的预期时间步长和特征数,在这种情况下分别为 3 和 2。 + +``` +# define model +model = Sequential() +model.add(Conv1D(filters=64, kernel_size=2, activation='relu', input_shape=(n_steps, n_features))) +model.add(MaxPooling1D(pool_size=2)) +model.add(Flatten()) +model.add(Dense(50, activation='relu')) +model.add(Dense(1)) +model.compile(optimizer='adam', loss='mse') +``` + +在进行预测时,模型需要两个输入时间序列的三个时间步长。 + +我们可以预测输出系列中的下一个值,提供以下输入值: + +``` +80, 85 +90, 95 +100, 105 +``` + +具有三个时间步长和两个变量的一个样本的形状必须是[1,3,2]。 + +我们希望序列中的下一个值为 100 + 105 或 205。 + +``` +# demonstrate prediction +x_input = array([[80, 85], [90, 95], [100, 105]]) +x_input = x_input.reshape((1, n_steps, n_features)) +yhat = model.predict(x_input, verbose=0) +``` + +下面列出了完整的示例。 + +``` +# multivariate cnn example +from numpy import array +from numpy import hstack +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import Flatten +from keras.layers.convolutional import Conv1D +from keras.layers.convolutional import MaxPooling1D + +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the dataset + if end_ix > len(sequences): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :-1], sequences[end_ix-1, -1] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95]) +out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))]) +# convert to [rows, columns] structure +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2, out_seq)) +# choose a number of time steps +n_steps = 3 +# convert into input/output +X, y = split_sequences(dataset, n_steps) +# the dataset knows the number of features, e.g. 2 +n_features = X.shape[2] +# define model +model = Sequential() +model.add(Conv1D(filters=64, kernel_size=2, activation='relu', input_shape=(n_steps, n_features))) +model.add(MaxPooling1D(pool_size=2)) +model.add(Flatten()) +model.add(Dense(50, activation='relu')) +model.add(Dense(1)) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit(X, y, epochs=1000, verbose=0) +# demonstrate prediction +x_input = array([[80, 85], [90, 95], [100, 105]]) +x_input = x_input.reshape((1, n_steps, n_features)) +yhat = model.predict(x_input, verbose=0) +print(yhat) +``` + +运行该示例准备数据,拟合模型并进行预测。 + +``` +[[206.0161]] +``` + +还有另一种更精细的方法来模拟问题。 + +每个输入序列可以由单独的 CNN 处理,并且可以在对输出序列进行预测之前组合这些子模型中的每一个的输出。 + +我们可以将其称为多头 CNN 模型。根据正在建模的问题的具体情况,它可以提供更大的灵活性或更好的表现。例如,它允许您为每个输入系列配置不同的子模型,例如过滤器映射的数量和内核大小。 + +可以使用 [Keras 功能 API](https://machinelearningmastery.com/keras-functional-api-deep-learning/) 在 Keras 中定义此类型的模型。 + +首先,我们可以将第一个输入模型定义为 1D CNN,其输入层需要具有 _n_steps_ 和 1 个特征的向量。 + +``` +# first input model +visible1 = Input(shape=(n_steps, n_features)) +cnn1 = Conv1D(filters=64, kernel_size=2, activation='relu')(visible1) +cnn1 = MaxPooling1D(pool_size=2)(cnn1) +cnn1 = Flatten()(cnn1) +``` + +我们可以以相同的方式定义第二个输入子模型。 + +``` +# second input model +visible2 = Input(shape=(n_steps, n_features)) +cnn2 = Conv1D(filters=64, kernel_size=2, activation='relu')(visible2) +cnn2 = MaxPooling1D(pool_size=2)(cnn2) +cnn2 = Flatten()(cnn2) +``` + +现在已经定义了两个输入子模型,我们可以将每个模型的输出合并为一个长向量,可以在对输出序列进行预测之前对其进行解释。 + +``` +# merge input models +merge = concatenate([cnn1, cnn2]) +dense = Dense(50, activation='relu')(merge) +output = Dense(1)(dense) +``` + +然后我们可以将输入和输出联系在一起。 + +``` +model = Model(inputs=[visible1, visible2], outputs=output) +``` + +下图提供了该模型外观的示意图,包括每层输入和输出的形状。 + +![Plot of Multi-Headed 1D CNN for Multivariate Time Series Forecasting](img/90fa21bc957277eceb839b305b4ec6b6.jpg) + +多头 1D CNN 在多元时间序列预测中的应用 + +此模型要求输入作为两个元素的列表提供,其中列表中的每个元素包含一个子模型的数据。 + +为了实现这一点,我们可以将 3D 输入数据分成两个独立的输入数据阵列;这是从一个形状为[7,3,2]的数组到两个 3D 数组[7,3,1] + +``` +# one time series per head +n_features = 1 +# separate input data +X1 = X[:, :, 0].reshape(X.shape[0], X.shape[1], n_features) +X2 = X[:, :, 1].reshape(X.shape[0], X.shape[1], n_features) +``` + +然后可以提供这些数据以适合模型。 + +``` +# fit model +model.fit([X1, X2], y, epochs=1000, verbose=0) +``` + +类似地,我们必须在进行单个一步预测时将单个样本的数据准备为两个单独的二维数组。 + +``` +x_input = array([[80, 85], [90, 95], [100, 105]]) +x1 = x_input[:, 0].reshape((1, n_steps, n_features)) +x2 = x_input[:, 1].reshape((1, n_steps, n_features)) +``` + +我们可以将所有这些结合在一起;下面列出了完整的示例。 + +``` +# multivariate multi-headed 1d cnn example +from numpy import array +from numpy import hstack +from keras.models import Model +from keras.layers import Input +from keras.layers import Dense +from keras.layers import Flatten +from keras.layers.convolutional import Conv1D +from keras.layers.convolutional import MaxPooling1D +from keras.layers.merge import concatenate + +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the dataset + if end_ix > len(sequences): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :-1], sequences[end_ix-1, -1] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95]) +out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))]) +# convert to [rows, columns] structure +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2, out_seq)) +# choose a number of time steps +n_steps = 3 +# convert into input/output +X, y = split_sequences(dataset, n_steps) +# one time series per head +n_features = 1 +# separate input data +X1 = X[:, :, 0].reshape(X.shape[0], X.shape[1], n_features) +X2 = X[:, :, 1].reshape(X.shape[0], X.shape[1], n_features) +# first input model +visible1 = Input(shape=(n_steps, n_features)) +cnn1 = Conv1D(filters=64, kernel_size=2, activation='relu')(visible1) +cnn1 = MaxPooling1D(pool_size=2)(cnn1) +cnn1 = Flatten()(cnn1) +# second input model +visible2 = Input(shape=(n_steps, n_features)) +cnn2 = Conv1D(filters=64, kernel_size=2, activation='relu')(visible2) +cnn2 = MaxPooling1D(pool_size=2)(cnn2) +cnn2 = Flatten()(cnn2) +# merge input models +merge = concatenate([cnn1, cnn2]) +dense = Dense(50, activation='relu')(merge) +output = Dense(1)(dense) +model = Model(inputs=[visible1, visible2], outputs=output) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit([X1, X2], y, epochs=1000, verbose=0) +# demonstrate prediction +x_input = array([[80, 85], [90, 95], [100, 105]]) +x1 = x_input[:, 0].reshape((1, n_steps, n_features)) +x2 = x_input[:, 1].reshape((1, n_steps, n_features)) +yhat = model.predict([x1, x2], verbose=0) +print(yhat) +``` + +运行该示例准备数据,拟合模型并进行预测。 + +``` +[[205.871]] +``` + +### 多个并联系列 + +另一个时间序列问题是存在多个并行时间序列并且必须为每个时间序列预测值的情况。 + +例如,给定上一节的数据: + +``` +[[ 10 15 25] + [ 20 25 45] + [ 30 35 65] + [ 40 45 85] + [ 50 55 105] + [ 60 65 125] + [ 70 75 145] + [ 80 85 165] + [ 90 95 185]] +``` + +我们可能想要预测下一个时间步的三个时间序列中的每一个的值。 + +这可以称为多变量预测。 + +同样,必须将数据分成输入/输出样本以训练模型。 + +该数据集的第一个示例是: + +输入: + +``` +10, 15, 25 +20, 25, 45 +30, 35, 65 +``` + +输出: + +``` +40, 45, 85 +``` + +下面的 _split_sequences()_ 函数将分割多个并行时间序列,其中时间步长为行,每列一个系列为所需的输入/输出形状。 + +``` +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the dataset + if end_ix > len(sequences)-1: + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :], sequences[end_ix, :] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) +``` + +我们可以在人为的问题上证明这一点;下面列出了完整的示例。 + +``` +# multivariate output data prep +from numpy import array +from numpy import hstack + +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the dataset + if end_ix > len(sequences)-1: + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :], sequences[end_ix, :] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95]) +out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))]) +# convert to [rows, columns] structure +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2, out_seq)) +# choose a number of time steps +n_steps = 3 +# convert into input/output +X, y = split_sequences(dataset, n_steps) +print(X.shape, y.shape) +# summarize the data +for i in range(len(X)): + print(X[i], y[i]) +``` + +首先运行该示例打印准备好的 X 和 y 组件的形状。 + +X 的形状是三维的,包括样品的数量(6),每个样品选择的时间步数(3),以及平行时间序列或特征的数量(3)。 + +y 的形状是二维的,正如我们可能期望的样本数量(6)和每个样本的时间变量数量(3)。 + +该数据已准备好在 1D CNN 模型中使用,该模型期望每个样本的 X 和 y 分量具有三维输入和二维输出形状。 + +然后,打印每个样本,显示每个样本的输入和输出分量。 + +``` +(6, 3, 3) (6, 3) + +[[10 15 25] + [20 25 45] + [30 35 65]] [40 45 85] +[[20 25 45] + [30 35 65] + [40 45 85]] [ 50 55 105] +[[ 30 35 65] + [ 40 45 85] + [ 50 55 105]] [ 60 65 125] +[[ 40 45 85] + [ 50 55 105] + [ 60 65 125]] [ 70 75 145] +[[ 50 55 105] + [ 60 65 125] + [ 70 75 145]] [ 80 85 165] +[[ 60 65 125] + [ 70 75 145] + [ 80 85 165]] [ 90 95 185] +``` + +我们现在准备在这些数据上安装一维 CNN 模型。 + +在此模型中,通过 _input_shape_ 参数为输入层指定时间步数和并行系列(特征)。 + +并行序列的数量也用于指定输出层中模型预测的值的数量;再次,这是三个。 + +``` +# define model +model = Sequential() +model.add(Conv1D(filters=64, kernel_size=2, activation='relu', input_shape=(n_steps, n_features))) +model.add(MaxPooling1D(pool_size=2)) +model.add(Flatten()) +model.add(Dense(50, activation='relu')) +model.add(Dense(n_features)) +model.compile(optimizer='adam', loss='mse') +``` + +我们可以通过为每个系列提供三个时间步长的输入来预测三个并行系列中的每一个的下一个值。 + +``` +70, 75, 145 +80, 85, 165 +90, 95, 185 +``` + +用于进行单个预测的输入的形状必须是 1 个样本,3 个时间步长和 3 个特征,或者[1,3,3]。 + +``` +# demonstrate prediction +x_input = array([[70,75,145], [80,85,165], [90,95,185]]) +x_input = x_input.reshape((1, n_steps, n_features)) +yhat = model.predict(x_input, verbose=0) +``` + +我们希望向量输出为: + +``` +[100, 105, 205] +``` + +我们可以将所有这些结合在一起并演示下面的多变量输出时间序列预测的 1D CNN。 + +``` +# multivariate output 1d cnn example +from numpy import array +from numpy import hstack +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import Flatten +from keras.layers.convolutional import Conv1D +from keras.layers.convolutional import MaxPooling1D + +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the dataset + if end_ix > len(sequences)-1: + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :], sequences[end_ix, :] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95]) +out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))]) +# convert to [rows, columns] structure +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2, out_seq)) +# choose a number of time steps +n_steps = 3 +# convert into input/output +X, y = split_sequences(dataset, n_steps) +# the dataset knows the number of features, e.g. 2 +n_features = X.shape[2] +# define model +model = Sequential() +model.add(Conv1D(filters=64, kernel_size=2, activation='relu', input_shape=(n_steps, n_features))) +model.add(MaxPooling1D(pool_size=2)) +model.add(Flatten()) +model.add(Dense(50, activation='relu')) +model.add(Dense(n_features)) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit(X, y, epochs=3000, verbose=0) +# demonstrate prediction +x_input = array([[70,75,145], [80,85,165], [90,95,185]]) +x_input = x_input.reshape((1, n_steps, n_features)) +yhat = model.predict(x_input, verbose=0) +print(yhat) +``` + +运行该示例准备数据,拟合模型并进行预测。 + +``` +[[100.11272 105.32213 205.53436]] +``` + +与多输入系列一样,还有另一种更精细的方法来模拟问题。 + +每个输出系列可以由单独的输出 CNN 模型处理。 + +我们可以将其称为多输出 CNN 模型。根据正在建模的问题的具体情况,它可以提供更大的灵活性或更好的表现。 + +可以使用 [Keras 功能 API](https://machinelearningmastery.com/keras-functional-api-deep-learning/) 在 Keras 中定义此类型的模型。 + +首先,我们可以将第一个输入模型定义为 1D CNN 模型。 + +``` +# define model +visible = Input(shape=(n_steps, n_features)) +cnn = Conv1D(filters=64, kernel_size=2, activation='relu')(visible) +cnn = MaxPooling1D(pool_size=2)(cnn) +cnn = Flatten()(cnn) +cnn = Dense(50, activation='relu')(cnn) +``` + +然后,我们可以为我们希望预测的三个系列中的每一个定义一个输出层,其中每个输出子模型将预测单个时间步长。 + +``` +# define output 1 +output1 = Dense(1)(cnn) +# define output 2 +output2 = Dense(1)(cnn) +# define output 3 +output3 = Dense(1)(cnn) +``` + +然后,我们可以将输入和输出层组合到一个模型中。 + +``` +# tie together +model = Model(inputs=visible, outputs=[output1, output2, output3]) +model.compile(optimizer='adam', loss='mse') +``` + +为了使模型架构清晰,下面的示意图清楚地显示了模型的三个独立输出层以及每个层的输入和输出形状。 + +![Plot of Multi-Output 1D CNN for Multivariate Time Series Forecasting](img/8beda69b175d57109f5b4e227d2f9ff8.jpg) + +多输出 1D CNN 用于多元时间序列预测的图 + +在训练模型时,每个样本需要三个独立的输出阵列。我们可以通过将具有形状[7,3]的输出训练数据转换为具有形状[7,1]的三个阵列来实现这一点。 + +``` +# separate output +y1 = y[:, 0].reshape((y.shape[0], 1)) +y2 = y[:, 1].reshape((y.shape[0], 1)) +y3 = y[:, 2].reshape((y.shape[0], 1)) +``` + +可以在训练期间将这些阵列提供给模型。 + +``` +# fit model +model.fit(X, [y1,y2,y3], epochs=2000, verbose=0) +``` + +将所有这些结合在一起,下面列出了完整的示例。 + +``` +# multivariate output 1d cnn example +from numpy import array +from numpy import hstack +from keras.models import Model +from keras.layers import Input +from keras.layers import Dense +from keras.layers import Flatten +from keras.layers.convolutional import Conv1D +from keras.layers.convolutional import MaxPooling1D + +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the dataset + if end_ix > len(sequences)-1: + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :], sequences[end_ix, :] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95]) +out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))]) +# convert to [rows, columns] structure +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2, out_seq)) +# choose a number of time steps +n_steps = 3 +# convert into input/output +X, y = split_sequences(dataset, n_steps) +# the dataset knows the number of features, e.g. 2 +n_features = X.shape[2] +# separate output +y1 = y[:, 0].reshape((y.shape[0], 1)) +y2 = y[:, 1].reshape((y.shape[0], 1)) +y3 = y[:, 2].reshape((y.shape[0], 1)) +# define model +visible = Input(shape=(n_steps, n_features)) +cnn = Conv1D(filters=64, kernel_size=2, activation='relu')(visible) +cnn = MaxPooling1D(pool_size=2)(cnn) +cnn = Flatten()(cnn) +cnn = Dense(50, activation='relu')(cnn) +# define output 1 +output1 = Dense(1)(cnn) +# define output 2 +output2 = Dense(1)(cnn) +# define output 3 +output3 = Dense(1)(cnn) +# tie together +model = Model(inputs=visible, outputs=[output1, output2, output3]) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit(X, [y1,y2,y3], epochs=2000, verbose=0) +# demonstrate prediction +x_input = array([[70,75,145], [80,85,165], [90,95,185]]) +x_input = x_input.reshape((1, n_steps, n_features)) +yhat = model.predict(x_input, verbose=0) +print(yhat) +``` + +运行该示例准备数据,拟合模型并进行预测。 + +``` +[array([[100.96118]], dtype=float32), + array([[105.502686]], dtype=float32), + array([[205.98045]], dtype=float32)] +``` + +## 多步 CNN 模型 + +在实践中,1D CNN 模型在预测表示不同输出变量的向量输出(如在前面的示例中)或者表示一个变量的多个时间步长的向量输出方面几乎没有差别。 + +然而,训练数据的编制方式存在细微而重要的差异。在本节中,我们将演示使用向量模型开发多步预测模型的情况。 + +在我们查看模型的细节之前,让我们首先看一下多步预测的数据准备。 + +### 数据准备 + +与一步预测一样,用于多步时间序列预测的时间序列必须分为带有输入和输出组件的样本。 + +输入和输出组件都将包含多个时间步长,并且可以具有或不具有相同数量的步骤。 + +例如,给定单变量时间序列: + +``` +[10, 20, 30, 40, 50, 60, 70, 80, 90] +``` + +我们可以使用最后三个时间步作为输入并预测接下来的两个时间步。 + +第一个样本如下: + +输入: + +``` +[10, 20, 30] +``` + +输出: + +``` +[40, 50] +``` + +下面的 _split_sequence()_ 函数实现了这种行为,并将给定的单变量时间序列分割为具有指定数量的输入和输出时间步长的样本。 + +``` +# split a univariate sequence into samples +def split_sequence(sequence, n_steps_in, n_steps_out): + X, y = list(), list() + for i in range(len(sequence)): + # find the end of this pattern + end_ix = i + n_steps_in + out_end_ix = end_ix + n_steps_out + # check if we are beyond the sequence + if out_end_ix > len(sequence): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequence[i:end_ix], sequence[end_ix:out_end_ix] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) +``` + +我们可以在小型设计数据集上演示此功能。 + +下面列出了完整的示例。 + +``` +# multi-step data preparation +from numpy import array + +# split a univariate sequence into samples +def split_sequence(sequence, n_steps_in, n_steps_out): + X, y = list(), list() + for i in range(len(sequence)): + # find the end of this pattern + end_ix = i + n_steps_in + out_end_ix = end_ix + n_steps_out + # check if we are beyond the sequence + if out_end_ix > len(sequence): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequence[i:end_ix], sequence[end_ix:out_end_ix] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +raw_seq = [10, 20, 30, 40, 50, 60, 70, 80, 90] +# choose a number of time steps +n_steps_in, n_steps_out = 3, 2 +# split into samples +X, y = split_sequence(raw_seq, n_steps_in, n_steps_out) +# summarize the data +for i in range(len(X)): + print(X[i], y[i]) +``` + +运行该示例将单变量系列拆分为输入和输出时间步骤,并打印每个系列的输入和输出组件。 + +``` +[10 20 30] [40 50] +[20 30 40] [50 60] +[30 40 50] [60 70] +[40 50 60] [70 80] +[50 60 70] [80 90] +``` + +既然我们知道如何为多步预测准备数据,那么让我们看一下可以学习这种映射的一维 CNN 模型。 + +### 向量输出模型 + +1D CNN 可以直接输出向量,可以解释为多步预测。 + +在前一节中看到这种方法是每个输出时间序列的一个时间步骤被预测为向量。 + +与前一节中单变量数据的 1D CNN 模型一样,必须首先对准备好的样本进行重新整形。 CNN 希望数据具有[_ 样本,时间步长,特征 _]的三维结构,在这种情况下,我们只有一个特征,因此重塑是直截了当的。 + +``` +# reshape from [samples, timesteps] into [samples, timesteps, features] +n_features = 1 +X = X.reshape((X.shape[0], X.shape[1], n_features)) +``` + +通过 _n_steps_in_ 和 _n_steps_out_ 变量中指定的输入和输出步数,我们可以定义一个多步骤时间序列预测模型。 + +``` +# define model +model = Sequential() +model.add(Conv1D(filters=64, kernel_size=2, activation='relu', input_shape=(n_steps_in, n_features))) +model.add(MaxPooling1D(pool_size=2)) +model.add(Flatten()) +model.add(Dense(50, activation='relu')) +model.add(Dense(n_steps_out)) +model.compile(optimizer='adam', loss='mse') +``` + +该模型可以对单个样本进行预测。我们可以通过提供输入来预测数据集末尾之后的下两个步骤: + +``` +[70, 80, 90] +``` + +我们希望预测的输出为: + +``` +[100, 110] +``` + +正如模型所预期的那样,进行预测时输入数据的单个样本的形状对于 1 个样本,输入的 3 个时间步长和单个特征必须是[1,3,1]。 + +``` +# demonstrate prediction +x_input = array([70, 80, 90]) +x_input = x_input.reshape((1, n_steps_in, n_features)) +yhat = model.predict(x_input, verbose=0) +``` + +将所有这些结合在一起,下面列出了具有单变量时间序列的 1D CNN 用于多步预测。 + +``` +# univariate multi-step vector-output 1d cnn example +from numpy import array +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import Flatten +from keras.layers.convolutional import Conv1D +from keras.layers.convolutional import MaxPooling1D + +# split a univariate sequence into samples +def split_sequence(sequence, n_steps_in, n_steps_out): + X, y = list(), list() + for i in range(len(sequence)): + # find the end of this pattern + end_ix = i + n_steps_in + out_end_ix = end_ix + n_steps_out + # check if we are beyond the sequence + if out_end_ix > len(sequence): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequence[i:end_ix], sequence[end_ix:out_end_ix] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +raw_seq = [10, 20, 30, 40, 50, 60, 70, 80, 90] +# choose a number of time steps +n_steps_in, n_steps_out = 3, 2 +# split into samples +X, y = split_sequence(raw_seq, n_steps_in, n_steps_out) +# reshape from [samples, timesteps] into [samples, timesteps, features] +n_features = 1 +X = X.reshape((X.shape[0], X.shape[1], n_features)) +# define model +model = Sequential() +model.add(Conv1D(filters=64, kernel_size=2, activation='relu', input_shape=(n_steps_in, n_features))) +model.add(MaxPooling1D(pool_size=2)) +model.add(Flatten()) +model.add(Dense(50, activation='relu')) +model.add(Dense(n_steps_out)) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit(X, y, epochs=2000, verbose=0) +# demonstrate prediction +x_input = array([70, 80, 90]) +x_input = x_input.reshape((1, n_steps_in, n_features)) +yhat = model.predict(x_input, verbose=0) +print(yhat) +``` + +运行示例预测并打印序列中的后两个时间步骤。 + +``` +[[102.86651 115.08979]] +``` + +## 多变量多步 CNN 模型 + +在前面的部分中,我们研究了单变量,多变量和多步骤时间序列预测。 + +对于不同的问题,可以混合和匹配到目前为止呈现的不同类型的 1D CNN 模型。这也适用于涉及多变量和多步预测的时间序列预测问题,但可能更具挑战性。 + +在本节中,我们将探讨多变量多步骤时间序列预测的数据准备和建模的简短示例,作为模板来缓解这一挑战,具体来说: + +1. 多输入多步输出。 +2. 多个并行输入和多步输出。 + +也许最大的绊脚石是准备数据,所以这是我们关注的重点。 + +### 多输入多步输出 + +存在多变量时间序列预测问题,其中输出序列是分开的但取决于输入时间序列,并且输出序列需要多个时间步长。 + +例如,考虑前一部分的多变量时间序列: + +``` +[[ 10 15 25] + [ 20 25 45] + [ 30 35 65] + [ 40 45 85] + [ 50 55 105] + [ 60 65 125] + [ 70 75 145] + [ 80 85 165] + [ 90 95 185]] +``` + +我们可以使用两个输入时间序列中的每一个的三个先前时间步骤来预测输出时间序列的两个时间步长。 + +输入: + +``` +10, 15 +20, 25 +30, 35 +``` + +输出: + +``` +65 +85 +``` + +下面的 _split_sequences()_ 函数实现了这种行为。 + +``` +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps_in, n_steps_out): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps_in + out_end_ix = end_ix + n_steps_out-1 + # check if we are beyond the dataset + if out_end_ix > len(sequences): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :-1], sequences[end_ix-1:out_end_ix, -1] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) +``` + +我们可以在我们设计的数据集上证明这一点。下面列出了完整的示例。 + +``` +# multivariate multi-step data preparation +from numpy import array +from numpy import hstack + +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps_in, n_steps_out): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps_in + out_end_ix = end_ix + n_steps_out-1 + # check if we are beyond the dataset + if out_end_ix > len(sequences): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :-1], sequences[end_ix-1:out_end_ix, -1] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95]) +out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))]) +# convert to [rows, columns] structure +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2, out_seq)) +# choose a number of time steps +n_steps_in, n_steps_out = 3, 2 +# convert into input/output +X, y = split_sequences(dataset, n_steps_in, n_steps_out) +print(X.shape, y.shape) +# summarize the data +for i in range(len(X)): + print(X[i], y[i]) +``` + +首先运行该示例打印准备好的训练数据的形状。 + +我们可以看到样本的输入部分的形状是三维的,由六个样本组成,具有三个时间步长和两个输入时间序列的两个变量。 + +样本的输出部分对于六个样本是二维的,并且每个样本的两个时间步长是预测的。 + +然后打印制备的样品以确认数据是按照我们指定的方式制备的。 + +``` +(6, 3, 2) (6, 2) + +[[10 15] + [20 25] + [30 35]] [65 85] +[[20 25] + [30 35] + [40 45]] [ 85 105] +[[30 35] + [40 45] + [50 55]] [105 125] +[[40 45] + [50 55] + [60 65]] [125 145] +[[50 55] + [60 65] + [70 75]] [145 165] +[[60 65] + [70 75] + [80 85]] [165 185] +``` + +我们现在可以开发用于多步预测的 1D CNN 模型。 + +在这种情况下,我们将演示向量输出模型。下面列出了完整的示例。 + +``` +# multivariate multi-step 1d cnn example +from numpy import array +from numpy import hstack +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import Flatten +from keras.layers.convolutional import Conv1D +from keras.layers.convolutional import MaxPooling1D + +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps_in, n_steps_out): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps_in + out_end_ix = end_ix + n_steps_out-1 + # check if we are beyond the dataset + if out_end_ix > len(sequences): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :-1], sequences[end_ix-1:out_end_ix, -1] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95]) +out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))]) +# convert to [rows, columns] structure +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2, out_seq)) +# choose a number of time steps +n_steps_in, n_steps_out = 3, 2 +# convert into input/output +X, y = split_sequences(dataset, n_steps_in, n_steps_out) +# the dataset knows the number of features, e.g. 2 +n_features = X.shape[2] +# define model +model = Sequential() +model.add(Conv1D(filters=64, kernel_size=2, activation='relu', input_shape=(n_steps_in, n_features))) +model.add(MaxPooling1D(pool_size=2)) +model.add(Flatten()) +model.add(Dense(50, activation='relu')) +model.add(Dense(n_steps_out)) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit(X, y, epochs=2000, verbose=0) +# demonstrate prediction +x_input = array([[70, 75], [80, 85], [90, 95]]) +x_input = x_input.reshape((1, n_steps_in, n_features)) +yhat = model.predict(x_input, verbose=0) +print(yhat) +``` + +运行该示例适合模型并预测输出序列的下两个时间步骤超出数据集。 + +我们希望接下来的两个步骤是[185,205]。 + +这是一个具有挑战性的问题框架,数据非常少,模型的任意配置版本也很接近。 + +``` +[[185.57011 207.77893]] +``` + +### 多个并行输入和多步输出 + +并行时间序列的问题可能需要预测每个时间序列的多个时间步长。 + +例如,考虑前一部分的多变量时间序列: + +``` +[[ 10 15 25] + [ 20 25 45] + [ 30 35 65] + [ 40 45 85] + [ 50 55 105] + [ 60 65 125] + [ 70 75 145] + [ 80 85 165] + [ 90 95 185]] +``` + +我们可以使用三个时间序列中的每一个的最后三个步骤作为模型的输入,并预测三个时间序列中的每一个的下一个时间步长作为输出。 + +训练数据集中的第一个样本如下。 + +输入: + +``` +10, 15, 25 +20, 25, 45 +30, 35, 65 +``` + +输出: + +``` +40, 45, 85 +50, 55, 105 +``` + +下面的 _split_sequences()_ 函数实现了这种行为。 + +``` +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps_in, n_steps_out): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps_in + out_end_ix = end_ix + n_steps_out + # check if we are beyond the dataset + if out_end_ix > len(sequences): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :], sequences[end_ix:out_end_ix, :] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) +``` + +我们可以在小型设计数据集上演示此功能。 + +下面列出了完整的示例。 + +``` +# multivariate multi-step data preparation +from numpy import array +from numpy import hstack +from keras.models import Sequential +from keras.layers import LSTM +from keras.layers import Dense +from keras.layers import RepeatVector +from keras.layers import TimeDistributed + +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps_in, n_steps_out): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps_in + out_end_ix = end_ix + n_steps_out + # check if we are beyond the dataset + if out_end_ix > len(sequences): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :], sequences[end_ix:out_end_ix, :] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95]) +out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))]) +# convert to [rows, columns] structure +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2, out_seq)) +# choose a number of time steps +n_steps_in, n_steps_out = 3, 2 +# convert into input/output +X, y = split_sequences(dataset, n_steps_in, n_steps_out) +print(X.shape, y.shape) +# summarize the data +for i in range(len(X)): + print(X[i], y[i]) +``` + +首先运行该示例打印准备好的训练数据集的形状。 + +我们可以看到数据集的输入( _X_ )和输出( _Y_ )元素分别对于样本数,时间步长和变量或并行时间序列是三维的。 。 + +然后将每个系列的输入和输出元素并排打印,以便我们可以确认数据是按照我们的预期准备的。 + +``` +(5, 3, 3) (5, 2, 3) + +[[10 15 25] + [20 25 45] + [30 35 65]] [[ 40 45 85] + [ 50 55 105]] +[[20 25 45] + [30 35 65] + [40 45 85]] [[ 50 55 105] + [ 60 65 125]] +[[ 30 35 65] + [ 40 45 85] + [ 50 55 105]] [[ 60 65 125] + [ 70 75 145]] +[[ 40 45 85] + [ 50 55 105] + [ 60 65 125]] [[ 70 75 145] + [ 80 85 165]] +[[ 50 55 105] + [ 60 65 125] + [ 70 75 145]] [[ 80 85 165] + [ 90 95 185]] +``` + +我们现在可以为此数据集开发一维 CNN 模型。 + +在这种情况下,我们将使用向量输出模型。因此,我们必须展平每个样本的输出部分的三维结构,以便训练模型。这意味着,不是为每个系列预测两个步骤,而是对模型进行训练并预期直接预测六个数字的向量。 + +``` +# flatten output +n_output = y.shape[1] * y.shape[2] +y = y.reshape((y.shape[0], n_output)) +``` + +下面列出了完整的示例。 + +``` +# multivariate output multi-step 1d cnn example +from numpy import array +from numpy import hstack +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import Flatten +from keras.layers.convolutional import Conv1D +from keras.layers.convolutional import MaxPooling1D + +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps_in, n_steps_out): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps_in + out_end_ix = end_ix + n_steps_out + # check if we are beyond the dataset + if out_end_ix > len(sequences): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :], sequences[end_ix:out_end_ix, :] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95]) +out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))]) +# convert to [rows, columns] structure +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2, out_seq)) +# choose a number of time steps +n_steps_in, n_steps_out = 3, 2 +# convert into input/output +X, y = split_sequences(dataset, n_steps_in, n_steps_out) +# flatten output +n_output = y.shape[1] * y.shape[2] +y = y.reshape((y.shape[0], n_output)) +# the dataset knows the number of features, e.g. 2 +n_features = X.shape[2] +# define model +model = Sequential() +model.add(Conv1D(filters=64, kernel_size=2, activation='relu', input_shape=(n_steps_in, n_features))) +model.add(MaxPooling1D(pool_size=2)) +model.add(Flatten()) +model.add(Dense(50, activation='relu')) +model.add(Dense(n_output)) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit(X, y, epochs=7000, verbose=0) +# demonstrate prediction +x_input = array([[60, 65, 125], [70, 75, 145], [80, 85, 165]]) +x_input = x_input.reshape((1, n_steps_in, n_features)) +yhat = model.predict(x_input, verbose=0) +print(yhat) +``` + +运行该示例适合模型并预测超出数据集末尾的下两个时间步的三个时间步中的每一个的值。 + +我们希望这些系列和时间步骤的值如下: + +``` +90, 95, 185 +100, 105, 205 +``` + +我们可以看到模型预测合理地接近预期值。 + +``` +[[ 90.47855 95.621284 186.02629 100.48118 105.80815 206.52821 ]] +``` + +## 摘要 + +在本教程中,您了解了如何针对一系列标准时间序列预测问题开发一套 CNN 模型。 + +具体来说,你学到了: + +* 如何开发 CNN 模型进行单变量时间序列预测。 +* 如何开发 CNN 模型进行多元时间序列预测。 +* 如何开发 CNN 模型进行多步时间序列预测。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/how-to-develop-convolutional-neural-networks-for-multi-step-time-series-forecasting.md b/docs/dl-ts/how-to-develop-convolutional-neural-networks-for-multi-step-time-series-forecasting.md new file mode 100644 index 0000000000000000000000000000000000000000..2720fd25922c28b44ed51ad7662fe56d39c64607 --- /dev/null +++ b/docs/dl-ts/how-to-develop-convolutional-neural-networks-for-multi-step-time-series-forecasting.md @@ -0,0 +1,1450 @@ +# 如何开发卷积神经网络用于多步时间序列预测 + +> 原文: [https://machinelearningmastery.com/how-to-develop-convolutional-neural-networks-for-multi-step-time-series-forecasting/](https://machinelearningmastery.com/how-to-develop-convolutional-neural-networks-for-multi-step-time-series-forecasting/) + +鉴于智能电表的兴起以及太阳能电池板等发电技术的广泛采用,可提供大量的用电数据。 + +该数据代表了多变量时间序列的功率相关变量,而这些变量又可用于建模甚至预测未来的电力消耗。 + +与其他机器学习算法不同,卷积神经网络能够自动学习序列数据的特征,支持多变量数据,并且可以直接输出向量用于多步预测。因此,已经证明一维 CNN 表现良好,甚至在挑战性序列预测问题上实现了最先进的结果。 + +在本教程中,您将了解如何为多步时间序列预测开发一维卷积神经网络。 + +完成本教程后,您将了解: + +* 如何开发 CNN 用于单变量数据的多步时间序列预测模型。 +* 如何开发多变量数据的多通道多步时间序列预测模型。 +* 如何开发多元数据的多头多步时间序列预测模型。 + +让我们开始吧。 + +![How to Develop Convolutional Neural Networks for Multi-Step Time Series Forecasting](img/78e782b7bf2e6f07146732bbf87480b9.jpg) + +如何开发用于多步时间序列预测的卷积神经网络 +照片由 [Banalities](https://www.flickr.com/photos/richardsummers/4057257184/) ,保留一些权利。 + +## 教程概述 + +本教程分为七个部分;他们是: + +1. 问题描述 +2. 加载并准备数据集 +3. 模型评估 +4. 用于多步预测的 CNN +5. 具有单变量 CNN 的多步时间序列预测 +6. 使用多通道 CNN 的多步时间序列预测 +7. 具有多头 CNN 的多步时间序列预测 + +## 问题描述 + +'[家庭用电量](https://archive.ics.uci.edu/ml/datasets/individual+household+electric+power+consumption)'数据集是一个多变量时间序列数据集,描述了四年内单个家庭的用电量。 + +该数据是在 2006 年 12 月至 2010 年 11 月之间收集的,并且每分钟收集家庭内的能耗观察结果。 + +它是一个多变量系列,由七个变量组成(除日期和时间外);他们是: + +* **global_active_power** :家庭消耗的总有功功率(千瓦)。 +* **global_reactive_power** :家庭消耗的总无功功率(千瓦)。 +* **电压**:平均电压(伏特)。 +* **global_intensity** :平均电流强度(安培)。 +* **sub_metering_1** :厨房的有功电能(瓦特小时的有功电能)。 +* **sub_metering_2** :用于洗衣的有功能量(瓦特小时的有功电能)。 +* **sub_metering_3** :气候控制系统的有功电能(瓦特小时的有功电能)。 + +有功和无功电能参考[交流电](https://en.wikipedia.org/wiki/AC_power)的技术细节。 + +可以通过从总活动能量中减去三个定义的子计量变量的总和来创建第四个子计量变量,如下所示: + +``` +sub_metering_remainder = (global_active_power * 1000 / 60) - (sub_metering_1 + sub_metering_2 + sub_metering_3) +``` + +## 加载并准备数据集 + +数据集可以从 UCI 机器学习库下载为单个 20 兆字节的.zip 文件: + +* [household_power_consumption.zip](https://archive.ics.uci.edu/ml/machine-learning-databases/00235/household_power_consumption.zip) + +下载数据集并将其解压缩到当前工作目录中。您现在将拥有大约 127 兆字节的文件“ _household_power_consumption.txt_ ”并包含所有观察结果。 + +我们可以使用 _read_csv()_ 函数来加载数据,并将前两列合并到一个日期时间列中,我们可以将其用作索引。 + +``` +# load all data +dataset = read_csv('household_power_consumption.txt', sep=';', header=0, low_memory=False, infer_datetime_format=True, parse_dates={'datetime':[0,1]}, index_col=['datetime']) +``` + +接下来,我们可以用'_ 标记所有[缺失值](https://machinelearningmastery.com/handle-missing-timesteps-sequence-prediction-problems-python/)?_ '具有 _NaN_ 值的字符,这是一个浮点数。 + +这将允许我们将数据作为一个浮点值数组而不是混合类型(效率较低)。 + +``` +# mark all missing values +dataset.replace('?', nan, inplace=True) +# make dataset numeric +dataset = dataset.astype('float32') +``` + +我们还需要填写缺失值,因为它们已被标记。 + +一种非常简单的方法是从前一天的同一时间复制观察。我们可以在一个名为 _fill_missing()_ 的函数中实现它,该函数将从 24 小时前获取数据的 NumPy 数组并复制值。 + +``` +# fill missing values with a value at the same time one day ago +def fill_missing(values): + one_day = 60 * 24 + for row in range(values.shape[0]): + for col in range(values.shape[1]): + if isnan(values[row, col]): + values[row, col] = values[row - one_day, col] +``` + +我们可以将此函数直接应用于 DataFrame 中的数据。 + +``` +# fill missing +fill_missing(dataset.values) +``` + +现在,我们可以使用上一节中的计算创建一个包含剩余子计量的新列。 + +``` +# add a column for for the remainder of sub metering +values = dataset.values +dataset['sub_metering_4'] = (values[:,0] * 1000 / 60) - (values[:,4] + values[:,5] + values[:,6]) +``` + +我们现在可以将清理后的数据集版本保存到新文件中;在这种情况下,我们只需将文件扩展名更改为.csv,并将数据集保存为“ _household_power_consumption.csv_ ”。 + +``` +# save updated dataset +dataset.to_csv('household_power_consumption.csv') +``` + +将所有这些结合在一起,下面列出了加载,清理和保存数据集的完整示例。 + +``` +# load and clean-up data +from numpy import nan +from numpy import isnan +from pandas import read_csv +from pandas import to_numeric + +# fill missing values with a value at the same time one day ago +def fill_missing(values): + one_day = 60 * 24 + for row in range(values.shape[0]): + for col in range(values.shape[1]): + if isnan(values[row, col]): + values[row, col] = values[row - one_day, col] + +# load all data +dataset = read_csv('household_power_consumption.txt', sep=';', header=0, low_memory=False, infer_datetime_format=True, parse_dates={'datetime':[0,1]}, index_col=['datetime']) +# mark all missing values +dataset.replace('?', nan, inplace=True) +# make dataset numeric +dataset = dataset.astype('float32') +# fill missing +fill_missing(dataset.values) +# add a column for for the remainder of sub metering +values = dataset.values +dataset['sub_metering_4'] = (values[:,0] * 1000 / 60) - (values[:,4] + values[:,5] + values[:,6]) +# save updated dataset +dataset.to_csv('household_power_consumption.csv') +``` + +运行该示例将创建新文件' _household_power_consumption.csv_ ',我们可以将其用作建模项目的起点。 + +## 模型评估 + +在本节中,我们将考虑如何开发和评估家庭电力数据集的预测模型。 + +本节分为四个部分;他们是: + +1. 问题框架 +2. 评估指标 +3. 训练和测试集 +4. 前瞻性验证 + +### 问题框架 + +有许多方法可以利用和探索家庭用电量数据集。 + +在本教程中,我们将使用这些数据来探索一个非常具体的问题;那是: + +> 鉴于最近的耗电量,未来一周的预期耗电量是多少? + +这要求预测模型预测未来七天每天的总有功功率。 + +从技术上讲,考虑到多个预测步骤,这个问题的框架被称为多步骤时间序列预测问题。利用多个输入变量的模型可以称为多变量多步时间序列预测模型。 + +这种类型的模型在规划支出方面可能有助于家庭。在供应方面,它也可能有助于规划特定家庭的电力需求。 + +数据集的这种框架还表明,将每分钟功耗的观察结果下采样到每日总数是有用的。这不是必需的,但考虑到我们对每天的总功率感兴趣,这是有道理的。 + +我们可以使用 pandas DataFrame 上的 [resample()函数](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.resample.html)轻松实现这一点。使用参数' _D_ '调用此函数允许按日期时间索引的加载数据按天分组([查看所有偏移别名](http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases))。然后,我们可以计算每天所有观测值的总和,并为八个变量中的每一个创建每日耗电量数据的新数据集。 + +下面列出了完整的示例。 + +``` +# resample minute data to total for each day +from pandas import read_csv +# load the new file +dataset = read_csv('household_power_consumption.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +# resample data to daily +daily_groups = dataset.resample('D') +daily_data = daily_groups.sum() +# summarize +print(daily_data.shape) +print(daily_data.head()) +# save +daily_data.to_csv('household_power_consumption_days.csv') +``` + +运行该示例将创建一个新的每日总功耗数据集,并将结果保存到名为“ _household_power_consumption_days.csv_ ”的单独文件中。 + +我们可以将其用作数据集,用于拟合和评估所选问题框架的预测模型。 + +### 评估指标 + +预测将包含七个值,一个用于一周中的每一天。 + +多步预测问题通常分别评估每个预测时间步长。这有助于以下几个原因: + +* 在特定提前期评论技能(例如+1 天 vs +3 天)。 +* 在不同的交付时间基于他们的技能对比模型(例如,在+1 天的模型和在日期+5 的模型良好的模型)。 + +总功率的单位是千瓦,并且具有也在相同单位的误差度量将是有用的。均方根误差(RMSE)和平均绝对误差(MAE)都符合这个要求,尽管 RMSE 更常用,将在本教程中采用。与 MAE 不同,RMSE 更能预测预测误差。 + +此问题的表现指标是从第 1 天到第 7 天的每个提前期的 RMSE。 + +作为捷径,使用单个分数总结模型的表现以帮助模型选择可能是有用的。 + +可以使用的一个可能的分数是所有预测天数的 RMSE。 + +下面的函数 _evaluate_forecasts()_ 将实现此行为并基于多个七天预测返回模型的表现。 + +``` +# evaluate one or more weekly forecasts against expected values +def evaluate_forecasts(actual, predicted): + scores = list() + # calculate an RMSE score for each day + for i in range(actual.shape[1]): + # calculate mse + mse = mean_squared_error(actual[:, i], predicted[:, i]) + # calculate rmse + rmse = sqrt(mse) + # store + scores.append(rmse) + # calculate overall RMSE + s = 0 + for row in range(actual.shape[0]): + for col in range(actual.shape[1]): + s += (actual[row, col] - predicted[row, col])**2 + score = sqrt(s / (actual.shape[0] * actual.shape[1])) + return score, scores +``` + +运行该函数将首先返回整个 RMSE,无论白天,然后每天返回一系列 RMSE 分数。 + +### 训练和测试集 + +我们将使用前三年的数据来训练预测模型和评估模型的最后一年。 + +给定数据集中的数据将分为标准周。这些是从周日开始到周六结束的周。 + +这是使用所选模型框架的现实且有用的方法,其中可以预测未来一周的功耗。它也有助于建模,其中模型可用于预测特定日期(例如星期三)或整个序列。 + +我们将数据拆分为标准周,从测试数据集向后工作。 + +数据的最后一年是 2010 年,2010 年的第一个星期日是 1 月 3 日。数据于 2010 年 11 月中旬结束,数据中最接近的最后一个星期六是 11 月 20 日。这给出了 46 周的测试数据。 + +下面提供了测试数据集的每日数据的第一行和最后一行以供确认。 + +``` +2010-01-03,2083.4539999999984,191.61000000000055,350992.12000000034,8703.600000000033,3842.0,4920.0,10074.0,15888.233355799992 +... +2010-11-20,2197.006000000004,153.76800000000028,346475.9999999998,9320.20000000002,4367.0,2947.0,11433.0,17869.76663959999 +``` + +每日数据从 2006 年底开始。 + +数据集中的第一个星期日是 12 月 17 日,这是第二行数据。 + +将数据组织到标准周内为训练预测模型提供了 159 个完整的标准周。 + +``` +2006-12-17,3390.46,226.0059999999994,345725.32000000024,14398.59999999998,2033.0,4187.0,13341.0,36946.66673200004 +... +2010-01-02,1309.2679999999998,199.54600000000016,352332.8399999997,5489.7999999999865,801.0,298.0,6425.0,14297.133406600002 +``` + +下面的函数 _split_dataset()_ 将每日数据拆分为训练集和测试集,并将每个数据组织成标准周。 + +使用特定行偏移来使用数据集的知识来分割数据。然后使用 NumPy [split()函数](https://docs.scipy.org/doc/numpy/reference/generated/numpy.split.html)将分割数据集组织成每周数据。 + +``` +# split a univariate dataset into train/test sets +def split_dataset(data): + # split into standard weeks + train, test = data[1:-328], data[-328:-6] + # restructure into windows of weekly data + train = array(split(train, len(train)/7)) + test = array(split(test, len(test)/7)) + return train, test +``` + +我们可以通过加载每日数据集并打印列车和测试集的第一行和最后一行数据来测试此功能,以确认它们符合上述预期。 + +完整的代码示例如下所示。 + +``` +# split into standard weeks +from numpy import split +from numpy import array +from pandas import read_csv + +# split a univariate dataset into train/test sets +def split_dataset(data): + # split into standard weeks + train, test = data[1:-328], data[-328:-6] + # restructure into windows of weekly data + train = array(split(train, len(train)/7)) + test = array(split(test, len(test)/7)) + return train, test + +# load the new file +dataset = read_csv('household_power_consumption_days.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +train, test = split_dataset(dataset.values) +# validate train data +print(train.shape) +print(train[0, 0, 0], train[-1, -1, 0]) +# validate test +print(test.shape) +print(test[0, 0, 0], test[-1, -1, 0]) +``` + +运行该示例表明,列车数据集确实有 159 周的数据,而测试数据集有 46 周。 + +我们可以看到,第一行和最后一行的列车和测试数据集的总有效功率与我们定义为每组标准周界限的特定日期的数据相匹配。 + +``` +(159, 7, 8) +3390.46 1309.2679999999998 +(46, 7, 8) +2083.4539999999984 2197.006000000004 +``` + +### 前瞻性验证 + +将使用称为[前进验证](https://machinelearningmastery.com/backtest-machine-learning-models-time-series-forecasting/)的方案评估模型。 + +这是需要模型进行一周预测的地方,然后该模型的实际数据可用于模型,以便它可以用作在随后一周进行预测的基础。这对于如何在实践中使用模型以及对模型有益,使其能够利用最佳可用数据都是现实的。 + +我们可以通过分离输入数据和输出/预测数据来证明这一点。 + +``` +Input, Predict +[Week1] Week2 +[Week1 + Week2] Week3 +[Week1 + Week2 + Week3] Week4 +... +``` + +下面提供了评估此数据集上预测模型的前瞻性验证方法,命名为 _evaluate_model()_。 + +标准周格式的训练和测试数据集作为参数提供给函数。提供了另一个参数 _n_input_ ,用于定义模型将用作输入以进行预测的先前观察的数量。 + +调用两个新函数:一个用于根据称为 _build_model()_ 的训练数据构建模型,另一个用于使用该模型对每个新标准周进行预测,称为 _forecast()_ 。这些将在后续章节中介绍。 + +我们正在使用神经网络,因此它们通常很难训练但很快就能进行评估。这意味着模型的首选用法是在历史数据上构建一次,并使用它们来预测前向验证的每个步骤。模型在评估期间是静态的(即未更新)。 + +这与训练更快的其他模型不同,其中当新数据可用时,模型可以重新拟合或更新前进验证的每个步骤。有了足够的资源,就可以通过这种方式使用神经网络,但在本教程中我们不会这样做。 + +下面列出了完整的 _evaluate_model()_ 函数。 + +``` +# evaluate a single model +def evaluate_model(train, test, n_input): + # fit model + model = build_model(train, n_input) + # history is a list of weekly data + history = [x for x in train] + # walk-forward validation over each week + predictions = list() + for i in range(len(test)): + # predict the week + yhat_sequence = forecast(model, history, n_input) + # store the predictions + predictions.append(yhat_sequence) + # get real observation and add to history for predicting the next week + history.append(test[i, :]) + # evaluate predictions days for each week + predictions = array(predictions) + score, scores = evaluate_forecasts(test[:, :, 0], predictions) + return score, scores +``` + +一旦我们对模型进行评估,我们就可以总结表现。 + +下面的函数名为 _summarize_scores()_,将模型的表现显示为单行,以便与其他模型进行比较。 + +``` +# summarize scores +def summarize_scores(name, score, scores): + s_scores = ', '.join(['%.1f' % s for s in scores]) + print('%s: [%.3f] %s' % (name, score, s_scores)) +``` + +我们现在已经开始评估数据集上的预测模型的所有元素。 + +## 用于多步预测的 CNN + +卷积神经网络模型(简称 CNN)是一种深度神经网络,开发用于图像数据,如手写识别。 + +事实证明,它们在大规模训练时可以有效地挑战计算机视觉问题,例如识别和定位图像中的对象并自动描述图像内容。 + +它们是由两种主要类型的元素组成的模型:卷积层和池化层。 + +**卷积层**使用内核读取输入,例如 2D 图像或 1D 信号,该内核一次读取小段并跨越整个输入字段。每次读取都会导致对投影到滤镜图上的输入进行解释,并表示对输入的解释。 + +**汇集层**采用特征映射投影并将它们提取到最基本的元素,例如使用信号平均或信号最大化过程。 + +卷积和合并层可以在深度重复,提供输入信号的多层抽象。 + +这些网络的输出通常是一个或多个完全连接的层,用于解释已读取的内容并将此内部表示映射到类值。 + +有关卷积神经网络的更多信息,您可以看到帖子: + +* [用于机器学习的卷积神经网络的速成课程](https://machinelearningmastery.com/crash-course-convolutional-neural-networks/) + +卷积神经网络可用于多步时间序列预测。 + +* 卷积层可以读取输入数据的序列并自动提取特征。 +* 汇集层可以提取提取的特征,并将注意力集中在最显着的元素上。 +* 完全连接的层可以解释内部表示并输出表示多个时间步长的向量。 + +该方法的主要优点是自动特征学习和模型直接输出多步向量的能力。 + +CNN 可用于递归或直接预测策略,其中模型使得一步预测和输出作为后续预测的输入被馈送,并且其中一个模型被开发用于每个预测的时间步长。或者,CNN 可用于预测整个输出序列,作为整个向量的一步预测。这是前馈神经网络的一般优点。 + +使用 CNN 的一个重要的第二个好处是它们可以支持多个 1D 输入以进行预测。如果多步输出序列是多个输入序列的函数,则这很有用。这可以使用两种不同的模型配置来实现。 + +* **多输入通道**。这是每个输入序列作为单独的通道读取的地方,如图像的不同通道(例如红色,绿色和蓝色)。 +* **多输入磁头**。这是每个输入序列由不同的 CNN 子模型读取的地方,并且内部表示在被解释并用于进行预测之前被组合。 + +在本教程中,我们将探讨如何为多步时间序列预测开发三种不同类型的 CNN 模型;他们是: + +* CNN 用于使用单变量输入数据进行多步时间序列预测。 +* CNN 用于多步骤时间序列预测,通过信道提供多变量输入数据。 +* 通过子模型使用多变量输入数据进行多步时间序列预测的 CNN。 + +将在家庭电力预测问题上开发和演示这些模型。如果一个模型比一个天真的模型更好地实现表现,那么该模型被认为是技术性的,在 7 天的预测中,该模型的总体 RMSE 约为 465 千瓦。 + +我们不会专注于调整这些模型以实现最佳表现;相反,与天真的预测相比,我们将在熟练的模型上停下来。选择的结构和超参数通过一些试验和错误来选择。 + +## 具有单变量 CNN 的多步时间序列预测 + +在本节中,我们将开发一个卷积神经网络,用于仅使用每日功耗的单变量序列进行多步时间序列预测。 + +具体来说,问题的框架是: + +> 考虑到每日总耗电量的前几天,预测下一个标准周的每日耗电量。 + +用作输入的先前天数定义了 CNN 将读取并学习提取特征的数据的一维(1D)子序列。关于此输入的大小和性质的一些想法包括: + +* 所有前几天,最多数年的数据。 +* 前 7 天。 +* 前两周。 +* 前一个月。 +* 前一年。 +* 前一周和一周从一年前预测。 + +没有正确的答案;相反,可以测试每种方法和更多方法,并且可以使用模型的表现来选择导致最佳模型表现的输入的性质。 + +这些选择定义了有关实现的一些内容,例如: + +* 如何准备训练数据以适应模型。 +* 如何准备测试数据以评估模型。 +* 如何使用该模型在未来使用最终模型进行预测。 + +一个好的起点是使用前七天。 + +1D CNN 模型期望数据具有以下形状: + +``` +[samples, timesteps, features] +``` + +一个样本将包含七个时间步骤,其中一个功能用于每日总耗电量的七天。 + +训练数据集有 159 周的数据,因此训练数据集的形状为: + +``` +[159, 7, 1] +``` + +这是一个好的开始。此格式的数据将使用先前的标准周来预测下一个标准周。一个问题是 159 个实例对于神经网络来说并不是很多。 + +创建更多训练数据的方法是在训练期间更改问题,以预测前七天的下一个七天,无论标准周。 + +这仅影响训练数据,测试问题保持不变:预测给定前一标准周的下一个标准周的每日功耗。 + +这将需要一点准备训练数据。 + +训练数据在标准周内提供八个变量,特别是形状[159,7,8]。第一步是展平数据,以便我们有八个时间序列序列。 + +``` +# flatten data +data = data.reshape((data.shape[0]*data.shape[1], data.shape[2])) +``` + +然后,我们需要迭代时间步骤并将数据划分为重叠窗口;每次迭代沿着一个时间步移动并预测随后的七天。 + +例如: + +``` +Input, Output +[d01, d02, d03, d04, d05, d06, d07], [d08, d09, d10, d11, d12, d13, d14] +[d02, d03, d04, d05, d06, d07, d08], [d09, d10, d11, d12, d13, d14, d15] +... +``` + +我们可以通过跟踪输入和输出的开始和结束索引来实现这一点,因为我们在时间步长方面迭代展平数据的长度。 + +我们也可以通过参数化输入和输出的数量来实现这一点(例如 _n_input_ , _n_out_ ),这样您就可以尝试不同的值或根据自己的问题进行调整。 + +下面是一个名为 _to_supervised()_ 的函数,它采用周(历史)列表和用作输入和输出的时间步数,并以重叠移动窗口格式返回数据。 + +``` +# convert history into inputs and outputs +def to_supervised(train, n_input, n_out=7): + # flatten data + data = train.reshape((train.shape[0]*train.shape[1], train.shape[2])) + X, y = list(), list() + in_start = 0 + # step over the entire history one time step at a time + for _ in range(len(data)): + # define the end of the input sequence + in_end = in_start + n_input + out_end = in_end + n_out + # ensure we have enough data for this instance + if out_end < len(data): + x_input = data[in_start:in_end, 0] + x_input = x_input.reshape((len(x_input), 1)) + X.append(x_input) + y.append(data[in_end:out_end, 0]) + # move along one time step + in_start += 1 + return array(X), array(y) +``` + +当我们在整个训练数据集上运行此函数时,我们将 159 个样本转换为 1,099 个;具体地,变换的数据集具有形状 _X = [1099,7,1]_ 和 _y = [1099,7]。_ + +接下来,我们可以在训练数据上定义和拟合 CNN 模型。 + +这个多步骤时间序列预测问题是一个自回归。这意味着它可能最好建模,其中接下来的七天是先前时间步骤的观测功能。这和相对少量的数据意味着需要一个小型号。 + +我们将使用一个具有一个卷积层的模型,其中包含 16 个滤波器,内核大小为 3.这意味着七次输入序列将通过卷积操作一次读取三个时间步,并且此操作将执行 16 次。在将内部表示展平为一个长向量之前,池化层将这些要素图减小其大小的 1/4。然后,在输出层预测序列中的下一个七天之前,由完全连接的层解释。 + +我们将使用均方误差损失函数,因为它与我们选择的 RMSE 误差度量非常匹配。我们将使用随机梯度下降的有效 [Adam](https://machinelearningmastery.com/adam-optimization-algorithm-for-deep-learning/) 实现,并将模型拟合 20 个时期,批量大小为 4。 + +小批量大小和算法的随机性意味着相同的模型将在每次训练时学习输入到输出的略微不同的映射。这意味着[结果可能会在评估模型时发生变化](https://machinelearningmastery.com/randomness-in-machine-learning/)。您可以尝试多次运行模型并计算模型表现的平均值。 + +下面的 _build_model()_ 准备训练数据,定义模型,并将模型拟合到训练数据上,使拟合模型准备好进行预测。 + +``` +# train the model +def build_model(train, n_input): + # prepare data + train_x, train_y = to_supervised(train, n_input) + # define parameters + verbose, epochs, batch_size = 0, 20, 4 + n_timesteps, n_features, n_outputs = train_x.shape[1], train_x.shape[2], train_y.shape[1] + # define model + model = Sequential() + model.add(Conv1D(filters=16, kernel_size=3, activation='relu', input_shape=(n_timesteps,n_features))) + model.add(MaxPooling1D(pool_size=2)) + model.add(Flatten()) + model.add(Dense(10, activation='relu')) + model.add(Dense(n_outputs)) + model.compile(loss='mse', optimizer='adam') + # fit network + model.fit(train_x, train_y, epochs=epochs, batch_size=batch_size, verbose=verbose) + return model +``` + +现在我们知道如何拟合模型,我们可以看看如何使用模型进行预测。 + +通常,模型期望数据在进行预测时具有相同的三维形状。 + +在这种情况下,输入模式的预期形状是一个样本,每天消耗的一个功能的七天: + +``` +[1, 7, 1] +``` + +在对测试集进行预测时以及在将来使用最终模型进行预测时,数据必须具有此形状。如果将输入天数更改为 14,则必须相应更改训练数据的形状和进行预测时新样本的形状,以便有 14 个时间步长。在使用模型时,您必须继续使用它。 + +我们正在使用前向验证来评估模型,如上一节中所述。 + +这意味着我们有前一周的观察结果,以预测下周。这些被收集到一系列标准周,称为历史。 + +为了预测下一个标准周,我们需要检索观察的最后几天。与训练数据一样,我们必须首先展平历史数据以删除每周结构,以便最终得到八个平行时间序列。 + +``` +# flatten data +data = data.reshape((data.shape[0]*data.shape[1], data.shape[2])) +``` + +接下来,我们需要检索每日总功耗的最后七天(功能编号 0)。我们将像对训练数据那样进行参数化,以便将来可以修改模型用作输入的前几天的数量。 + +``` +# retrieve last observations for input data +input_x = data[-n_input:, 0] +``` + +接下来,我们将输入重塑为预期的三维结构。 + +``` +# reshape into [1, n_input, 1] +input_x = input_x.reshape((1, len(input_x), 1)) +``` + +然后,我们使用拟合模型和输入数据进行预测,并检索七天输出的向量。 + +``` +# forecast the next week +yhat = model.predict(input_x, verbose=0) +# we only want the vector forecast +yhat = yhat[0] +``` + +下面的 _forecast()_ 函数实现了这个功能,并将模型拟合到训练数据集,到目前为止观察到的数据历史以及模型预期的输入时间步数。 + +``` +# make a forecast +def forecast(model, history, n_input): + # flatten data + data = array(history) + data = data.reshape((data.shape[0]*data.shape[1], data.shape[2])) + # retrieve last observations for input data + input_x = data[-n_input:, 0] + # reshape into [1, n_input, 1] + input_x = input_x.reshape((1, len(input_x), 1)) + # forecast the next week + yhat = model.predict(input_x, verbose=0) + # we only want the vector forecast + yhat = yhat[0] + return yhat +``` + +而已;我们现在拥有了所需的一切,我们需要通过 CNN 模型对单变量数据集的每日总功耗进行多步时间序列预测。 + +我们可以将所有这些结合在一起。下面列出了完整的示例。 + +``` +# univariate multi-step cnn +from math import sqrt +from numpy import split +from numpy import array +from pandas import read_csv +from sklearn.metrics import mean_squared_error +from matplotlib import pyplot +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import Flatten +from keras.layers.convolutional import Conv1D +from keras.layers.convolutional import MaxPooling1D + +# split a univariate dataset into train/test sets +def split_dataset(data): + # split into standard weeks + train, test = data[1:-328], data[-328:-6] + # restructure into windows of weekly data + train = array(split(train, len(train)/7)) + test = array(split(test, len(test)/7)) + return train, test + +# evaluate one or more weekly forecasts against expected values +def evaluate_forecasts(actual, predicted): + scores = list() + # calculate an RMSE score for each day + for i in range(actual.shape[1]): + # calculate mse + mse = mean_squared_error(actual[:, i], predicted[:, i]) + # calculate rmse + rmse = sqrt(mse) + # store + scores.append(rmse) + # calculate overall RMSE + s = 0 + for row in range(actual.shape[0]): + for col in range(actual.shape[1]): + s += (actual[row, col] - predicted[row, col])**2 + score = sqrt(s / (actual.shape[0] * actual.shape[1])) + return score, scores + +# summarize scores +def summarize_scores(name, score, scores): + s_scores = ', '.join(['%.1f' % s for s in scores]) + print('%s: [%.3f] %s' % (name, score, s_scores)) + +# convert history into inputs and outputs +def to_supervised(train, n_input, n_out=7): + # flatten data + data = train.reshape((train.shape[0]*train.shape[1], train.shape[2])) + X, y = list(), list() + in_start = 0 + # step over the entire history one time step at a time + for _ in range(len(data)): + # define the end of the input sequence + in_end = in_start + n_input + out_end = in_end + n_out + # ensure we have enough data for this instance + if out_end < len(data): + x_input = data[in_start:in_end, 0] + x_input = x_input.reshape((len(x_input), 1)) + X.append(x_input) + y.append(data[in_end:out_end, 0]) + # move along one time step + in_start += 1 + return array(X), array(y) + +# train the model +def build_model(train, n_input): + # prepare data + train_x, train_y = to_supervised(train, n_input) + # define parameters + verbose, epochs, batch_size = 0, 20, 4 + n_timesteps, n_features, n_outputs = train_x.shape[1], train_x.shape[2], train_y.shape[1] + # define model + model = Sequential() + model.add(Conv1D(filters=16, kernel_size=3, activation='relu', input_shape=(n_timesteps,n_features))) + model.add(MaxPooling1D(pool_size=2)) + model.add(Flatten()) + model.add(Dense(10, activation='relu')) + model.add(Dense(n_outputs)) + model.compile(loss='mse', optimizer='adam') + # fit network + model.fit(train_x, train_y, epochs=epochs, batch_size=batch_size, verbose=verbose) + return model + +# make a forecast +def forecast(model, history, n_input): + # flatten data + data = array(history) + data = data.reshape((data.shape[0]*data.shape[1], data.shape[2])) + # retrieve last observations for input data + input_x = data[-n_input:, 0] + # reshape into [1, n_input, 1] + input_x = input_x.reshape((1, len(input_x), 1)) + # forecast the next week + yhat = model.predict(input_x, verbose=0) + # we only want the vector forecast + yhat = yhat[0] + return yhat + +# evaluate a single model +def evaluate_model(train, test, n_input): + # fit model + model = build_model(train, n_input) + # history is a list of weekly data + history = [x for x in train] + # walk-forward validation over each week + predictions = list() + for i in range(len(test)): + # predict the week + yhat_sequence = forecast(model, history, n_input) + # store the predictions + predictions.append(yhat_sequence) + # get real observation and add to history for predicting the next week + history.append(test[i, :]) + # evaluate predictions days for each week + predictions = array(predictions) + score, scores = evaluate_forecasts(test[:, :, 0], predictions) + return score, scores + +# load the new file +dataset = read_csv('household_power_consumption_days.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +# split into train and test +train, test = split_dataset(dataset.values) +# evaluate model and get scores +n_input = 7 +score, scores = evaluate_model(train, test, n_input) +# summarize scores +summarize_scores('cnn', score, scores) +# plot scores +days = ['sun', 'mon', 'tue', 'wed', 'thr', 'fri', 'sat'] +pyplot.plot(days, scores, marker='o', label='cnn') +pyplot.show() +``` + +运行该示例适合并评估模型,在所有七天内打印整体 RMSE,以及每个提前期的每日 RMSE。 + +鉴于算法的随机性,您的具体结果可能会有所不同。您可能想尝试几次运行该示例。 + +我们可以看到,在这种情况下,与天真的预测相比,该模型是巧妙的,实现了大约 404 千瓦的总体 RMSE,小于 465 千瓦的天真模型。 + +``` +cnn: [404.411] 436.1, 400.6, 346.2, 388.2, 405.5, 326.0, 502.9 +``` + +还创建了每日 RMSE 的图。该图显示,周二和周五可能比其他日子更容易预测,也许星期六在标准周结束时是最难预测的日子。 + +![Line Plot of RMSE per Day for Univariate CNN with 7-day Inputs](img/b933aab347ed22c0a80e593517694d80.jpg) + +具有 7 天输入的单变量 CNN 每日 RMSE 的线图 + +我们可以通过更改 _n_input_ 变量来增加用作 7 到 14 之间输入的前几天的数量。 + +``` +# evaluate model and get scores +n_input = 14 +``` + +使用此更改重新运行示例首先会打印模型表现的摘要。 + +具体结果可能有所不同;尝试运行几次这个例子。 + +在这种情况下,我们可以看到整体 RMSE 进一步下降,这表明进一步调整输入大小以及模型的内核大小可能会带来更好的表现。 + +``` +cnn: [396.497] 392.2, 412.8, 384.0, 389.0, 387.3, 381.0, 427.1 +``` + +比较每日 RMSE 分数,我们看到一些更好,有些比使用第七输入更差。 + +这可以建议以某种方式使用两个不同大小的输入的益处,例如两种方法的集合或者可能是以不同方式读取训练数据的单个模型(例如,多头模型)。 + +![Line Plot of RMSE per Day for Univariate CNN with 14-day Inputs](img/dac762b2375e9931ed3026c5f7a1a456.jpg) + +单变量 CNN 每日 RMSE 的线图,具有 14 天输入 + +## 使用多通道 CNN 的多步时间序列预测 + +在本节中,我们将更新上一节中开发的 CNN,以使用八个时间序列变量中的每一个来预测下一个标准周的每日总功耗。 + +我们将通过将每个一维时间序列作为单独的输入通道提供给模型来实现此目的。 + +然后,CNN 将使用单独的内核并将每个输入序列读取到一组单独的过滤器映射上,主要是从每个输入时间序列变量中学习特征。 + +这对于那些输出序列是来自多个不同特征的先前时间步骤的观察的某些功能的问题是有帮助的,而不仅仅是(或包括)预测的特征。目前还不清楚功耗问题是否属于这种情况,但我们仍可以探索它。 + +首先,我们必须更新训练数据的准备工作,以包括所有八项功能,而不仅仅是每日消耗的一项功能。它需要一行: + +``` +X.append(data[in_start:in_end, :]) +``` + +下面列出了具有此更改的完整 _to_supervised()_ 功能。 + +``` +# convert history into inputs and outputs +def to_supervised(train, n_input, n_out=7): + # flatten data + data = train.reshape((train.shape[0]*train.shape[1], train.shape[2])) + X, y = list(), list() + in_start = 0 + # step over the entire history one time step at a time + for _ in range(len(data)): + # define the end of the input sequence + in_end = in_start + n_input + out_end = in_end + n_out + # ensure we have enough data for this instance + if out_end < len(data): + X.append(data[in_start:in_end, :]) + y.append(data[in_end:out_end, 0]) + # move along one time step + in_start += 1 + return array(X), array(y) +``` + +我们还必须使用拟合模型更新用于进行预测的函数,以使用先前时间步骤中的所有八个特征。再次,另一个小变化: + +``` +# retrieve last observations for input data +input_x = data[-n_input:, :] +# reshape into [1, n_input, n] +input_x = input_x.reshape((1, input_x.shape[0], input_x.shape[1])) +``` + +具有此更改的完整 _forecast()_ 如下所示: + +``` +# make a forecast +def forecast(model, history, n_input): + # flatten data + data = array(history) + data = data.reshape((data.shape[0]*data.shape[1], data.shape[2])) + # retrieve last observations for input data + input_x = data[-n_input:, :] + # reshape into [1, n_input, n] + input_x = input_x.reshape((1, input_x.shape[0], input_x.shape[1])) + # forecast the next week + yhat = model.predict(input_x, verbose=0) + # we only want the vector forecast + yhat = yhat[0] + return yhat +``` + +我们将在前面部分的最后一部分中使用 14 天的先前观察到 8 个输入变量,这导致表现稍好一些。 + +``` +n_input = 14 +``` + +最后,上一节中使用的模型在这个问题的新框架上表现不佳。 + +数据量的增加需要更大,更复杂的模型,这种模型需要更长时间的训练。 + +通过一些试验和错误,一个表现良好的模型使用两个卷积层,32 个滤波器映射,然后汇集,然后另一个卷积层,16 个特征映射和汇集。解释特征的完全连接层增加到 100 个节点,该模型适用于 70 个迭代,批量大小为 16 个样本。 + +下面列出了更新的 _build_model()_ 函数,该函数定义并拟合训练数据集上的模型。 + +``` +# train the model +def build_model(train, n_input): + # prepare data + train_x, train_y = to_supervised(train, n_input) + # define parameters + verbose, epochs, batch_size = 0, 70, 16 + n_timesteps, n_features, n_outputs = train_x.shape[1], train_x.shape[2], train_y.shape[1] + # define model + model = Sequential() + model.add(Conv1D(filters=32, kernel_size=3, activation='relu', input_shape=(n_timesteps,n_features))) + model.add(Conv1D(filters=32, kernel_size=3, activation='relu')) + model.add(MaxPooling1D(pool_size=2)) + model.add(Conv1D(filters=16, kernel_size=3, activation='relu')) + model.add(MaxPooling1D(pool_size=2)) + model.add(Flatten()) + model.add(Dense(100, activation='relu')) + model.add(Dense(n_outputs)) + model.compile(loss='mse', optimizer='adam') + # fit network + model.fit(train_x, train_y, epochs=epochs, batch_size=batch_size, verbose=verbose) + return model +``` + +我们现在拥有为多变量输入数据开发多通道 CNN 以进行多步时间序列预测所需的所有元素。 + +下面列出了完整的示例。 + +``` +# multichannel multi-step cnn +from math import sqrt +from numpy import split +from numpy import array +from pandas import read_csv +from sklearn.metrics import mean_squared_error +from matplotlib import pyplot +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import Flatten +from keras.layers.convolutional import Conv1D +from keras.layers.convolutional import MaxPooling1D + +# split a univariate dataset into train/test sets +def split_dataset(data): + # split into standard weeks + train, test = data[1:-328], data[-328:-6] + # restructure into windows of weekly data + train = array(split(train, len(train)/7)) + test = array(split(test, len(test)/7)) + return train, test + +# evaluate one or more weekly forecasts against expected values +def evaluate_forecasts(actual, predicted): + scores = list() + # calculate an RMSE score for each day + for i in range(actual.shape[1]): + # calculate mse + mse = mean_squared_error(actual[:, i], predicted[:, i]) + # calculate rmse + rmse = sqrt(mse) + # store + scores.append(rmse) + # calculate overall RMSE + s = 0 + for row in range(actual.shape[0]): + for col in range(actual.shape[1]): + s += (actual[row, col] - predicted[row, col])**2 + score = sqrt(s / (actual.shape[0] * actual.shape[1])) + return score, scores + +# summarize scores +def summarize_scores(name, score, scores): + s_scores = ', '.join(['%.1f' % s for s in scores]) + print('%s: [%.3f] %s' % (name, score, s_scores)) + +# convert history into inputs and outputs +def to_supervised(train, n_input, n_out=7): + # flatten data + data = train.reshape((train.shape[0]*train.shape[1], train.shape[2])) + X, y = list(), list() + in_start = 0 + # step over the entire history one time step at a time + for _ in range(len(data)): + # define the end of the input sequence + in_end = in_start + n_input + out_end = in_end + n_out + # ensure we have enough data for this instance + if out_end < len(data): + X.append(data[in_start:in_end, :]) + y.append(data[in_end:out_end, 0]) + # move along one time step + in_start += 1 + return array(X), array(y) + +# train the model +def build_model(train, n_input): + # prepare data + train_x, train_y = to_supervised(train, n_input) + # define parameters + verbose, epochs, batch_size = 0, 70, 16 + n_timesteps, n_features, n_outputs = train_x.shape[1], train_x.shape[2], train_y.shape[1] + # define model + model = Sequential() + model.add(Conv1D(filters=32, kernel_size=3, activation='relu', input_shape=(n_timesteps,n_features))) + model.add(Conv1D(filters=32, kernel_size=3, activation='relu')) + model.add(MaxPooling1D(pool_size=2)) + model.add(Conv1D(filters=16, kernel_size=3, activation='relu')) + model.add(MaxPooling1D(pool_size=2)) + model.add(Flatten()) + model.add(Dense(100, activation='relu')) + model.add(Dense(n_outputs)) + model.compile(loss='mse', optimizer='adam') + # fit network + model.fit(train_x, train_y, epochs=epochs, batch_size=batch_size, verbose=verbose) + return model + +# make a forecast +def forecast(model, history, n_input): + # flatten data + data = array(history) + data = data.reshape((data.shape[0]*data.shape[1], data.shape[2])) + # retrieve last observations for input data + input_x = data[-n_input:, :] + # reshape into [1, n_input, n] + input_x = input_x.reshape((1, input_x.shape[0], input_x.shape[1])) + # forecast the next week + yhat = model.predict(input_x, verbose=0) + # we only want the vector forecast + yhat = yhat[0] + return yhat + +# evaluate a single model +def evaluate_model(train, test, n_input): + # fit model + model = build_model(train, n_input) + # history is a list of weekly data + history = [x for x in train] + # walk-forward validation over each week + predictions = list() + for i in range(len(test)): + # predict the week + yhat_sequence = forecast(model, history, n_input) + # store the predictions + predictions.append(yhat_sequence) + # get real observation and add to history for predicting the next week + history.append(test[i, :]) + # evaluate predictions days for each week + predictions = array(predictions) + score, scores = evaluate_forecasts(test[:, :, 0], predictions) + return score, scores + +# load the new file +dataset = read_csv('household_power_consumption_days.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +# split into train and test +train, test = split_dataset(dataset.values) +# evaluate model and get scores +n_input = 14 +score, scores = evaluate_model(train, test, n_input) +# summarize scores +summarize_scores('cnn', score, scores) +# plot scores +days = ['sun', 'mon', 'tue', 'wed', 'thr', 'fri', 'sat'] +pyplot.plot(days, scores, marker='o', label='cnn') +pyplot.show() +``` + +运行该示例适合并评估模型,在所有七天内打印整体 RMSE,以及每个提前期的每日 RMSE。 + +鉴于算法的随机性,您的具体结果可能会有所不同。您可能想尝试几次运行该示例。 + +我们可以看到,在这种情况下,使用所有八个输入变量确实导致整体 RMSE 分数的另一个小幅下降。 + +``` +cnn: [385.711] 422.2, 363.5, 349.8, 393.1, 357.1, 318.8, 474.3 +``` + +对于每日 RMSE 分数,我们确实看到一些更好,一些比上一节中的单变量 CNN 更差。 + +最后一天,周六,仍然是充满挑战的预测日,周五是一个轻松的预测日。设计模型可能会有一些好处,专门用于减少更难预测天数的误差。 + +可能有趣的是,可以通过调谐模型或者可能是多个不同模型的集合来进一步降低每日分数的方差。比较使用 7 天甚至 21 天输入数据的模型的表现以查看是否可以进一步获得也可能是有趣的。 + +![Line Plot of RMSE per Day for a Multichannel CNN with 14-day Inputs](img/7485fdb11bb815842db4d9c28200a198.jpg) + +具有 14 天输入的多通道 CNN 每天 RMSE 的线图 + +## 具有多头 CNN 的多步时间序列预测 + +我们可以进一步扩展 CNN 模型,为每个输入变量设置一个单独的子 CNN 模型或头部,我们可以将其称为多头 CNN 模型。 + +这需要修改模型的准备,进而修改训练和测试数据集的准备。 + +从模型开始,我们必须为八个输入变量中的每一个定义一个单独的 CNN 模型。 + +模型的配置(包括层数及其超参数)也进行了修改,以更好地适应新方法。新配置不是最佳配置,只需稍加试错即可找到。 + +使用更灵活的[功能 API 来定义多头模型以定义 Keras 模型](https://machinelearningmastery.com/keras-functional-api-deep-learning/)。 + +我们可以遍历每个变量并创建一个子模型,该子模型采用 14 天数据的一维序列,并输出包含序列中学习特征摘要的平面向量。这些向量中的每一个可以通过串联合并以产生一个非常长的向量,然后在进行预测之前由一些完全连接的层解释。 + +在我们构建子模型时,我们会跟踪输入层并在列表中展平层。这样我们就可以在模型对象的定义中指定输入,并使用合并层中的展平层列表。 + +``` +# create a channel for each variable +in_layers, out_layers = list(), list() +for i in range(n_features): + inputs = Input(shape=(n_timesteps,1)) + conv1 = Conv1D(filters=32, kernel_size=3, activation='relu')(inputs) + conv2 = Conv1D(filters=32, kernel_size=3, activation='relu')(conv1) + pool1 = MaxPooling1D(pool_size=2)(conv2) + flat = Flatten()(pool1) + # store layers + in_layers.append(inputs) + out_layers.append(flat) +# merge heads +merged = concatenate(out_layers) +# interpretation +dense1 = Dense(200, activation='relu')(merged) +dense2 = Dense(100, activation='relu')(dense1) +outputs = Dense(n_outputs)(dense2) +model = Model(inputs=in_layers, outputs=outputs) +# compile model +model.compile(loss='mse', optimizer='adam') +``` + +使用该模型时,它将需要八个数组作为输入:每个子模型一个。 + +在训练模型,评估模型以及使用最终模型进行预测时,这是必需的。 + +我们可以通过创建一个 3D 数组列表来实现这一点,其中每个 3D 数组包含[_ 样本,时间步长,1_ ],具有一个特征。 + +我们可以按以下格式准备训练数据集: + +``` +input_data = [train_x[:,:,i].reshape((train_x.shape[0],n_timesteps,1)) for i in range(n_features)] +``` + +下面列出了具有这些更改的更新的 _build_model()_ 函数。 + +``` +# train the model +def build_model(train, n_input): + # prepare data + train_x, train_y = to_supervised(train, n_input) + # define parameters + verbose, epochs, batch_size = 0, 25, 16 + n_timesteps, n_features, n_outputs = train_x.shape[1], train_x.shape[2], train_y.shape[1] + # create a channel for each variable + in_layers, out_layers = list(), list() + for i in range(n_features): + inputs = Input(shape=(n_timesteps,1)) + conv1 = Conv1D(filters=32, kernel_size=3, activation='relu')(inputs) + conv2 = Conv1D(filters=32, kernel_size=3, activation='relu')(conv1) + pool1 = MaxPooling1D(pool_size=2)(conv2) + flat = Flatten()(pool1) + # store layers + in_layers.append(inputs) + out_layers.append(flat) + # merge heads + merged = concatenate(out_layers) + # interpretation + dense1 = Dense(200, activation='relu')(merged) + dense2 = Dense(100, activation='relu')(dense1) + outputs = Dense(n_outputs)(dense2) + model = Model(inputs=in_layers, outputs=outputs) + # compile model + model.compile(loss='mse', optimizer='adam') + # plot the model + plot_model(model, show_shapes=True, to_file='multiheaded_cnn.png') + # fit network + input_data = [train_x[:,:,i].reshape((train_x.shape[0],n_timesteps,1)) for i in range(n_features)] + model.fit(input_data, train_y, epochs=epochs, batch_size=batch_size, verbose=verbose) + return model +``` + +构建模型时,会创建模型结构图并将其保存到文件中。 + +注意:对 plot_model()的调用要求安装 pygraphviz 和 pydot。如果这是一个问题,您可以注释掉这一行。 + +网络结构如下。 + +![Structure of the Multi Headed Convolutional Neural Network](img/dd8aef3b6e26536ca82c6e2827f90025.jpg) + +多头卷积神经网络的结构 + +接下来,我们可以在对测试数据集进行预测时更新输入样本的准备。 + +我们必须执行相同的更改,其中[1,14,8]的输入数组必须转换为八个 3D 数组的列表,每个数组都带有[1,14,1]。 + +``` +input_x = [input_x[:,i].reshape((1,input_x.shape[0],1)) for i in range(input_x.shape[1])] +``` + +下面列出了具有此更改的 _forecast()_ 函数。 + +``` +# make a forecast +def forecast(model, history, n_input): + # flatten data + data = array(history) + data = data.reshape((data.shape[0]*data.shape[1], data.shape[2])) + # retrieve last observations for input data + input_x = data[-n_input:, :] + # reshape into n input arrays + input_x = [input_x[:,i].reshape((1,input_x.shape[0],1)) for i in range(input_x.shape[1])] + # forecast the next week + yhat = model.predict(input_x, verbose=0) + # we only want the vector forecast + yhat = yhat[0] + return yhat +``` + +而已。 + +我们可以将所有这些结合在一起;下面列出了完整的示例。 + +``` +# multi headed multi-step cnn +from math import sqrt +from numpy import split +from numpy import array +from pandas import read_csv +from sklearn.metrics import mean_squared_error +from matplotlib import pyplot +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import Flatten +from keras.layers.convolutional import Conv1D +from keras.layers.convolutional import MaxPooling1D +from keras.models import Model +from keras.layers import Input +from keras.layers.merge import concatenate + +# split a univariate dataset into train/test sets +def split_dataset(data): + # split into standard weeks + train, test = data[1:-328], data[-328:-6] + # restructure into windows of weekly data + train = array(split(train, len(train)/7)) + test = array(split(test, len(test)/7)) + return train, test + +# evaluate one or more weekly forecasts against expected values +def evaluate_forecasts(actual, predicted): + scores = list() + # calculate an RMSE score for each day + for i in range(actual.shape[1]): + # calculate mse + mse = mean_squared_error(actual[:, i], predicted[:, i]) + # calculate rmse + rmse = sqrt(mse) + # store + scores.append(rmse) + # calculate overall RMSE + s = 0 + for row in range(actual.shape[0]): + for col in range(actual.shape[1]): + s += (actual[row, col] - predicted[row, col])**2 + score = sqrt(s / (actual.shape[0] * actual.shape[1])) + return score, scores + +# summarize scores +def summarize_scores(name, score, scores): + s_scores = ', '.join(['%.1f' % s for s in scores]) + print('%s: [%.3f] %s' % (name, score, s_scores)) + +# convert history into inputs and outputs +def to_supervised(train, n_input, n_out=7): + # flatten data + data = train.reshape((train.shape[0]*train.shape[1], train.shape[2])) + X, y = list(), list() + in_start = 0 + # step over the entire history one time step at a time + for _ in range(len(data)): + # define the end of the input sequence + in_end = in_start + n_input + out_end = in_end + n_out + # ensure we have enough data for this instance + if out_end < len(data): + X.append(data[in_start:in_end, :]) + y.append(data[in_end:out_end, 0]) + # move along one time step + in_start += 1 + return array(X), array(y) + +# plot training history +def plot_history(history): + # plot loss + pyplot.subplot(2, 1, 1) + pyplot.plot(history.history['loss'], label='train') + pyplot.plot(history.history['val_loss'], label='test') + pyplot.title('loss', y=0, loc='center') + pyplot.legend() + # plot rmse + pyplot.subplot(2, 1, 2) + pyplot.plot(history.history['rmse'], label='train') + pyplot.plot(history.history['val_rmse'], label='test') + pyplot.title('rmse', y=0, loc='center') + pyplot.legend() + pyplot.show() + +# train the model +def build_model(train, n_input): + # prepare data + train_x, train_y = to_supervised(train, n_input) + # define parameters + verbose, epochs, batch_size = 0, 25, 16 + n_timesteps, n_features, n_outputs = train_x.shape[1], train_x.shape[2], train_y.shape[1] + # create a channel for each variable + in_layers, out_layers = list(), list() + for i in range(n_features): + inputs = Input(shape=(n_timesteps,1)) + conv1 = Conv1D(filters=32, kernel_size=3, activation='relu')(inputs) + conv2 = Conv1D(filters=32, kernel_size=3, activation='relu')(conv1) + pool1 = MaxPooling1D(pool_size=2)(conv2) + flat = Flatten()(pool1) + # store layers + in_layers.append(inputs) + out_layers.append(flat) + # merge heads + merged = concatenate(out_layers) + # interpretation + dense1 = Dense(200, activation='relu')(merged) + dense2 = Dense(100, activation='relu')(dense1) + outputs = Dense(n_outputs)(dense2) + model = Model(inputs=in_layers, outputs=outputs) + # compile model + model.compile(loss='mse', optimizer='adam') + # fit network + input_data = [train_x[:,:,i].reshape((train_x.shape[0],n_timesteps,1)) for i in range(n_features)] + model.fit(input_data, train_y, epochs=epochs, batch_size=batch_size, verbose=verbose) + return model + +# make a forecast +def forecast(model, history, n_input): + # flatten data + data = array(history) + data = data.reshape((data.shape[0]*data.shape[1], data.shape[2])) + # retrieve last observations for input data + input_x = data[-n_input:, :] + # reshape into n input arrays + input_x = [input_x[:,i].reshape((1,input_x.shape[0],1)) for i in range(input_x.shape[1])] + # forecast the next week + yhat = model.predict(input_x, verbose=0) + # we only want the vector forecast + yhat = yhat[0] + return yhat + +# evaluate a single model +def evaluate_model(train, test, n_input): + # fit model + model = build_model(train, n_input) + # history is a list of weekly data + history = [x for x in train] + # walk-forward validation over each week + predictions = list() + for i in range(len(test)): + # predict the week + yhat_sequence = forecast(model, history, n_input) + # store the predictions + predictions.append(yhat_sequence) + # get real observation and add to history for predicting the next week + history.append(test[i, :]) + # evaluate predictions days for each week + predictions = array(predictions) + score, scores = evaluate_forecasts(test[:, :, 0], predictions) + return score, scores + +# load the new file +dataset = read_csv('household_power_consumption_days.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +# split into train and test +train, test = split_dataset(dataset.values) +# evaluate model and get scores +n_input = 14 +score, scores = evaluate_model(train, test, n_input) +# summarize scores +summarize_scores('cnn', score, scores) +# plot scores +days = ['sun', 'mon', 'tue', 'wed', 'thr', 'fri', 'sat'] +pyplot.plot(days, scores, marker='o', label='cnn') +pyplot.show() +``` + +运行该示例适合并评估模型,在所有七天内打印整体 RMSE,以及每个提前期的每日 RMSE。 + +鉴于算法的随机性,您的具体结果可能会有所不同。您可能想尝试几次运行该示例。 + +我们可以看到,在这种情况下,与天真的预测相比,整体 RMSE 非常熟练,但是所选择的配置可能不会比上一节中的多通道模型表现更好。 + +``` +cnn: [396.116] 414.5, 385.5, 377.2, 412.1, 371.1, 380.6, 428.1 +``` + +我们还可以看到每日 RMSE 分数的不同,更明显的概况,其中 Mon-Tue 和 Thu-Fri 可能比其他预测天更容易预测模型。 + +与其他预测模型结合使用时,这些结果可能很有用。 + +在架构中探索用于合并每个子模型的输出的替代方法可能是有趣的。 + +![Line Plot of RMSE per Day for a Multi-head CNN with 14-day Inputs](img/6ad416481f984e0a1fb77a16d5a16692.jpg) + +具有 14 天输入的多头 CNN 每天 RMSE 的线图 + +## 扩展 + +本节列出了一些扩展您可能希望探索的教程的想法。 + +* **输入大小**。探索用作模型输入的更多或更少天数,例如三天,21 天,30 天等。 +* **模型调整**。调整模型的结构和超参数,并进一步平均提升模型表现。 +* **数据缩放**。探索数据扩展(例如标准化和规范化)是否可用于改善任何 CNN 模型的表现。 +* **学习诊断**。使用诊断,例如列车的学习曲线和验证损失以及均方误差,以帮助调整 CNN 模型的结构和超参数。 +* **不同的内核大小**。将多通道 CNN 与多头 CNN 结合使用,并为每个磁头使用不同的内核大小,以查看此配置是否可以进一步提高表现。 + +如果你探索任何这些扩展,我很想知道。 + +## 进一步阅读 + +如果您希望深入了解,本节将提供有关该主题的更多资源。 + +### API + +* [pandas.read_csv API](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_csv.html) +* [pandas.DataFrame.resample API](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.resample.html) +* [重采样偏移别名](http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases) +* [sklearn.metrics.mean_squared_error API](http://scikit-learn.org/stable/modules/generated/sklearn.metrics.mean_squared_error.html) +* [numpy.split API](https://docs.scipy.org/doc/numpy/reference/generated/numpy.split.html) + +### 用品 + +* [个人家庭用电量数据集,UCI 机器学习库](https://archive.ics.uci.edu/ml/datasets/individual+household+electric+power+consumption)。 +* [交流电源,维基百科](https://en.wikipedia.org/wiki/AC_power)。 +* [多步时间序列预测的 4 种策略](https://machinelearningmastery.com/multi-step-time-series-forecasting/) +* [用于机器学习的卷积神经网络的速成课程](https://machinelearningmastery.com/crash-course-convolutional-neural-networks/) + +## 摘要 + +在本教程中,您了解了如何为多步时间序列预测开发一维卷积神经网络。 + +具体来说,你学到了: + +* 如何开发 CNN 用于单变量数据的多步时间序列预测模型。 +* 如何开发多变量数据的多通道多步时间序列预测模型。 +* 如何开发多元数据的多头多步时间序列预测模型。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/how-to-develop-deep-learning-models-for-univariate-time-series-forecasting.md b/docs/dl-ts/how-to-develop-deep-learning-models-for-univariate-time-series-forecasting.md new file mode 100644 index 0000000000000000000000000000000000000000..1932d011b766688ab4df3b30823eefd8f3b00b05 --- /dev/null +++ b/docs/dl-ts/how-to-develop-deep-learning-models-for-univariate-time-series-forecasting.md @@ -0,0 +1,1971 @@ +# 如何开发单变量时间序列预测的深度学习模型 + +> 原文: [https://machinelearningmastery.com/how-to-develop-deep-learning-models-for-univariate-time-series-forecasting/](https://machinelearningmastery.com/how-to-develop-deep-learning-models-for-univariate-time-series-forecasting/) + +深度学习神经网络能够自动学习和从原始数据中提取特征。 + +神经网络的这一特征可用于时间序列预测问题,其中模型可以直接在原始观测上开发,而不需要使用归一化和标准化来扩展数据或通过差分使数据静止。 + +令人印象深刻的是,简单的深度学习神经网络模型能够进行熟练的预测,与天真模型和调整 SARIMA 模型相比,单变量时间序列预测存在趋势和季节性成分且无需预处理的问题。 + +在本教程中,您将了解如何开发一套用于单变量时间序列预测的深度学习模型。 + +完成本教程后,您将了解: + +* 如何使用前向验证开发一个强大的测试工具来评估神经网络模型的表现。 +* 如何开发和评估简单多层感知器和卷积神经网络的时间序列预测。 +* 如何开发和评估 LSTM,CNN-LSTM 和 ConvLSTM 神经网络模型用于时间序列预测。 + +让我们开始吧。 + +![How to Develop Deep Learning Models for Univariate Time Series Forecasting](img/f8b55f0e298912845f002b6f20a5c28d.jpg) + +如何开发单变量时间序列预测的深度学习模型 +照片由 [Nathaniel McQueen](https://www.flickr.com/photos/nmcqueenphotography/40518405705/) ,保留一些权利。 + +## 教程概述 + +本教程分为五个部分;他们是: + +1. 问题描述 +2. 模型评估测试线束 +3. 多层感知器模型 +4. 卷积神经网络模型 +5. 循环神经网络模型 + +## 问题描述 + +'_ 月度汽车销售 _'数据集总结了 1960 年至 1968 年间加拿大魁北克省的月度汽车销量。 + +您可以从 [DataMarket](https://datamarket.com/data/set/22n4/monthly-car-sales-in-quebec-1960-1968#!ds=22n4&display=line) 了解有关数据集的更多信息。 + +直接从这里下载数据集: + +* [month-car-sales.csv](https://raw.githubusercontent.com/jbrownlee/Datasets/master/monthly-car-sales.csv) + +在当前工作目录中使用文件名“ _monthly-car-sales.csv_ ”保存文件。 + +我们可以使用函数 _read_csv()_ 将此数据集作为 Pandas 系列加载。 + +``` +# load +series = read_csv('monthly-car-sales.csv', header=0, index_col=0) +``` + +加载后,我们可以总结数据集的形状,以确定观察的数量。 + +``` +# summarize shape +print(series.shape) +``` + +然后我们可以创建该系列的线图,以了解该系列的结构。 + +``` +# plot +pyplot.plot(series) +pyplot.show() +``` + +我们可以将所有这些结合在一起;下面列出了完整的示例。 + +``` +# load and plot dataset +from pandas import read_csv +from matplotlib import pyplot +# load +series = read_csv('monthly-car-sales.csv', header=0, index_col=0) +# summarize shape +print(series.shape) +# plot +pyplot.plot(series) +pyplot.show() +``` + +首先运行该示例将打印数据集的形状。 + +``` +(108, 1) +``` + +该数据集是每月一次,有 9 年或 108 次观测。在我们的测试中,将使用去年或 12 个观测值作为测试集。 + +创建线图。数据集具有明显的趋势和季节性成分。季节性成分的期限可能是六个月或 12 个月。 + +![Line Plot of Monthly Car Sales](img/9b52cda406ff40e6e4fd66182089820c.jpg) + +月度汽车销售线图 + +从之前的实验中,我们知道一个幼稚的模型可以通过取预测月份的前三年的观测值的中位数来实现 1841.155 的均方根误差或 RMSE;例如: + +``` +yhat = median(-12, -24, -36) +``` + +负指数是指相对于预测月份的历史数据末尾的序列中的观察值。 + +从之前的实验中,我们知道 SARIMA 模型可以达到 1551.842 的 RMSE,其配置为 SARIMA(0,0,0),(1,1,0),12 其中没有为趋势指定元素和季节性差异计算周期为 12,并使用一个季节的 AR 模型。 + +朴素模型的表现为被认为熟练的模型提供了下限。任何在过去 12 个月内达到低于 1841.155 的预测表现的模型都具有技巧。 + +SARIMA 模型的表现可以衡量问题的良好模型。任何在过去 12 个月内达到预测表现低于 1551.842 的模型都应采用 SARIMA 模型。 + +现在我们已经定义了模型技能的问题和期望,我们可以看看定义测试工具。 + +## 模型评估测试线束 + +在本节中,我们将开发一个测试工具,用于开发和评估不同类型的神经网络模型,用于单变量时间序列预测。 + +本节分为以下几部分: + +1. 火车 - 测试分裂 +2. 系列作为监督学习 +3. 前瞻性验证 +4. 重复评估 +5. 总结表现 +6. 工作示例 + +### 火车 - 测试分裂 + +第一步是将加载的系列分成训练和测试集。 + +我们将使用前八年(96 个观测值)进行训练,最后 12 个用于测试集。 + +下面的 _train_test_split()_ 函数将拆分系列,将原始观察值和在测试集中使用的观察数作为参数。 + +``` +# split a univariate dataset into train/test sets +def train_test_split(data, n_test): + return data[:-n_test], data[-n_test:] +``` + +### 系列作为监督学习 + +接下来,我们需要能够将单变量观测系列框架化为监督学习问题,以便我们可以训练神经网络模型。 + +系列的监督学习框架意味着需要将数据拆分为模型从中学习和概括的多个示例。 + +每个样本必须同时具有输入组件和输出组件。 + +输入组件将是一些先前的观察,例如三年或 36 个时间步骤。 + +输出组件将是下个月的总销售额,因为我们有兴趣开发一个模型来进行一步预测。 + +我们可以使用 pandas DataFrame 上的 [shift()函数](http://pandas-docs.github.io/pandas-docs-travis/generated/pandas.DataFrame.shift.html)来实现它。它允许我们向下移动一列(向前移动)或向后移动(向后移动)。我们可以将该系列作为一列数据,然后创建列的多个副本,向前或向后移动,以便使用我们需要的输入和输出元素创建样本。 + +当一个系列向下移动时,会引入 _NaN_ 值,因为我们没有超出系列开头的值。 + +例如,系列定义为列: + +``` +(t) +1 +2 +3 +4 +``` + +可以预先移位和插入列: + +``` +(t-1), (t) +Nan, 1 +1, 2 +2, 3 +3, 4 +4 NaN +``` + +我们可以看到,在第二行,值 1 作为输入提供,作为前一时间步的观察,2 是系列中可以预测的下一个值,或者当 1 是预测模型时要学习的值作为输入呈现。 + +可以去除具有 _NaN_ 值的行。 + +下面的 _series_to_supervised()_ 函数实现了这种行为,允许您指定输入中使用的滞后观察数和每个样本的输出中使用的数。它还将删除具有 _NaN_ 值的行,因为它们不能用于训练或测试模型。 + +``` +# transform list into supervised learning format +def series_to_supervised(data, n_in=1, n_out=1): + df = DataFrame(data) + cols = list() + # input sequence (t-n, ... t-1) + for i in range(n_in, 0, -1): + cols.append(df.shift(i)) + # forecast sequence (t, t+1, ... t+n) + for i in range(0, n_out): + cols.append(df.shift(-i)) + # put it all together + agg = concat(cols, axis=1) + # drop rows with NaN values + agg.dropna(inplace=True) + return agg.values +``` + +### 前瞻性验证 + +可以使用[前进验证](https://machinelearningmastery.com/backtest-machine-learning-models-time-series-forecasting/)在测试集上评估时间序列预测模型。 + +前瞻性验证是一种方法,其中模型一次一个地对测试数据集中的每个观察进行预测。在对测试数据集中的时间步长进行每个预测之后,将预测的真实观察结果添加到测试数据集并使其可用于模型。 + +在进行后续预测之前,可以使用观察结果更简单的模型。考虑到更高的计算成本,更复杂的模型,例如神经网络,不会被改装。 + +然而,时间步骤的真实观察可以用作输入的一部分,用于在下一个时间步骤上进行预测。 + +首先,数据集分为训练集和测试集。我们将调用 _train_test_split()_ 函数来执行此拆分并传入预先指定数量的观察值以用作测试数据。 + +对于给定配置,模型将适合训练数据集一次。 + +我们将定义一个通用的 _model_fit()_ 函数来执行此操作,可以为我们稍后可能感兴趣的给定类型的神经网络填充该操作。该函数获取训练数据集和模型配置,并返回准备好进行预测的拟合模型。 + +``` +# fit a model +def model_fit(train, config): + return None +``` + +枚举测试数据集的每个时间步。使用拟合模型进行预测。 + +同样,我们将定义一个名为 _model_predict()_ 的通用函数,它采用拟合模型,历史和模型配置,并进行单个一步预测。 + +``` +# forecast with a pre-fit model +def model_predict(model, history, config): + return 0.0 +``` + +将预测添加到预测列表中,并将来自测试集的真实观察结果添加到用训练数据集中的所有观察结果播种的观察列表中。此列表在前向验证的每个步骤中构建,允许模型使用最新历史记录进行一步预测。 + +然后可以将所有预测与测试集中的真实值进行比较,并计算误差测量值。 + +我们将计算预测和真实值之间的均方根误差或 RMSE。 + +RMSE 计算为预测值与实际值之间的平方差的平均值的平方根。 _measure_rmse()_ 使用 [mean_squared_error()scikit-learn 函数](http://scikit-learn.org/stable/modules/generated/sklearn.metrics.mean_squared_error.html)在计算平方根之前首先计算均方误差或 MSE。 + +``` +# root mean squared error or rmse +def measure_rmse(actual, predicted): + return sqrt(mean_squared_error(actual, predicted)) +``` + +下面列出了将所有这些联系在一起的完整 _walk_forward_validation()_ 函数。 + +它采用数据集,用作测试集的观察数量以及模型的配置,并返回测试集上模型表现的 RMSE。 + +``` +# walk-forward validation for univariate data +def walk_forward_validation(data, n_test, cfg): + predictions = list() + # split dataset + train, test = train_test_split(data, n_test) + # fit model + model = model_fit(train, cfg) + # seed history with training dataset + history = [x for x in train] + # step over each time-step in the test set + for i in range(len(test)): + # fit model and make forecast for history + yhat = model_predict(model, history, cfg) + # store forecast in list of predictions + predictions.append(yhat) + # add actual observation to history for the next loop + history.append(test[i]) + # estimate prediction error + error = measure_rmse(test, predictions) + print(' > %.3f' % error) + return error +``` + +### 重复评估 + +神经网络模型是随机的。 + +这意味着,在给定相同的模型配置和相同的训练数据集的情况下,每次训练模型时将产生不同的内部权重集,这反过来将具有不同的表现。 + +这是一个好处,允许模型自适应并找到复杂问题的高表现配置。 + +在评估模型的表现和选择用于进行预测的最终模型时,这也是一个问题。 + +为了解决模型评估问题,我们将通过前向验证多次评估模型配置,并将错误报告为每次评估的平均误差。 + +对于大型神经网络而言,这并不总是可行的,并且可能仅适用于能够在几分钟或几小时内完成的小型网络。 + +下面的 _repeat_evaluate()_ 函数实现了这一点,并允许将重复次数指定为默认为 30 的可选参数,并返回模型表现得分列表:在本例中为 RMSE 值。 + +``` +# repeat evaluation of a config +def repeat_evaluate(data, config, n_test, n_repeats=30): + # fit and evaluate the model n times + scores = [walk_forward_validation(data, n_test, config) for _ in range(n_repeats)] + return scores +``` + +### 总结表现 + +最后,我们需要从多个重复中总结模型的表现。 + +我们将首先使用汇总统计汇总表现,特别是平均值和标准差。 + +我们还将使用盒子和须状图绘制模型表现分数的分布,以帮助了解表现的传播。 + +下面的 _summarize_scores()_ 函数实现了这一点,取了评估模型的名称和每次重复评估的分数列表,打印摘要并显示图表。 + +``` +# summarize model performance +def summarize_scores(name, scores): + # print a summary + scores_m, score_std = mean(scores), std(scores) + print('%s: %.3f RMSE (+/- %.3f)' % (name, scores_m, score_std)) + # box and whisker plot + pyplot.boxplot(scores) + pyplot.show() +``` + +### 工作示例 + +现在我们已经定义了测试工具的元素,我们可以将它们绑定在一起并定义一个简单的持久性模型。 + +具体而言,我们将计算先前观察的子集相对于预测时间的中值。 + +我们不需要拟合模型,因此 _model_fit()_ 函数将被实现为简单地返回 _ 无 _。 + +``` +# fit a model +def model_fit(train, config): + return None +``` + +我们将使用配置来定义先前观察中的索引偏移列表,该列表相对于将被用作预测的预测时间。例如,12 将使用 12 个月前(-12)相对于预测时间的观察。 + +``` +# define config +config = [12, 24, 36] +``` + +可以实现 model_predict()函数以使用此配置来收集观察值,然后返回这些观察值的中值。 + +``` +# forecast with a pre-fit model +def model_predict(model, history, config): + values = list() + for offset in config: + values.append(history[-offset]) + return median(values) +``` + +下面列出了使用简单持久性模型使用框架的完整示例。 + +``` +# persistence +from math import sqrt +from numpy import mean +from numpy import std +from pandas import DataFrame +from pandas import concat +from pandas import read_csv +from sklearn.metrics import mean_squared_error +from matplotlib import pyplot + +# split a univariate dataset into train/test sets +def train_test_split(data, n_test): + return data[:-n_test], data[-n_test:] + +# transform list into supervised learning format +def series_to_supervised(data, n_in=1, n_out=1): + df = DataFrame(data) + cols = list() + # input sequence (t-n, ... t-1) + for i in range(n_in, 0, -1): + cols.append(df.shift(i)) + # forecast sequence (t, t+1, ... t+n) + for i in range(0, n_out): + cols.append(df.shift(-i)) + # put it all together + agg = concat(cols, axis=1) + # drop rows with NaN values + agg.dropna(inplace=True) + return agg.values + +# root mean squared error or rmse +def measure_rmse(actual, predicted): + return sqrt(mean_squared_error(actual, predicted)) + +# difference dataset +def difference(data, interval): + return [data[i] - data[i - interval] for i in range(interval, len(data))] + +# fit a model +def model_fit(train, config): + return None + +# forecast with a pre-fit model +def model_predict(model, history, config): + values = list() + for offset in config: + values.append(history[-offset]) + return median(values) + +# walk-forward validation for univariate data +def walk_forward_validation(data, n_test, cfg): + predictions = list() + # split dataset + train, test = train_test_split(data, n_test) + # fit model + model = model_fit(train, cfg) + # seed history with training dataset + history = [x for x in train] + # step over each time-step in the test set + for i in range(len(test)): + # fit model and make forecast for history + yhat = model_predict(model, history, cfg) + # store forecast in list of predictions + predictions.append(yhat) + # add actual observation to history for the next loop + history.append(test[i]) + # estimate prediction error + error = measure_rmse(test, predictions) + print(' > %.3f' % error) + return error + +# repeat evaluation of a config +def repeat_evaluate(data, config, n_test, n_repeats=30): + # fit and evaluate the model n times + scores = [walk_forward_validation(data, n_test, config) for _ in range(n_repeats)] + return scores + +# summarize model performance +def summarize_scores(name, scores): + # print a summary + scores_m, score_std = mean(scores), std(scores) + print('%s: %.3f RMSE (+/- %.3f)' % (name, scores_m, score_std)) + # box and whisker plot + pyplot.boxplot(scores) + pyplot.show() + +series = read_csv('monthly-car-sales.csv', header=0, index_col=0) +data = series.values +# data split +n_test = 12 +# define config +config = [12, 24, 36] +# grid search +scores = repeat_evaluate(data, config, n_test) +# summarize scores +summarize_scores('persistence', scores) +``` + +运行该示例将打印在最近 12 个月的数据中使用前向验证评估的模型的 RMSE。 + +该模型被评估 30 次,但由于该模型没有随机元素,因此每次得分相同。 + +``` + > 1841.156 + > 1841.156 + > 1841.156 + > 1841.156 + > 1841.156 + > 1841.156 + > 1841.156 + > 1841.156 + > 1841.156 + > 1841.156 + > 1841.156 + > 1841.156 + > 1841.156 + > 1841.156 + > 1841.156 + > 1841.156 + > 1841.156 + > 1841.156 + > 1841.156 + > 1841.156 + > 1841.156 + > 1841.156 + > 1841.156 + > 1841.156 + > 1841.156 + > 1841.156 + > 1841.156 + > 1841.156 + > 1841.156 + > 1841.156 +persistence: 1841.156 RMSE (+/- 0.000) +``` + +我们可以看到模型的 RMSE 是 1841,提供了表现的下限,通过它我们可以评估模型是否熟练掌握问题。 + +![Box and Whisker Plot of Persistence RMSE Forecasting Car Sales](img/d886f5d7d84f1cced775bb6bea87ee3c.jpg) + +持久性 RMSE 预测汽车销售的盒子和晶须图 + +既然我们拥有强大的测试工具,我们就可以用它来评估一套神经网络模型。 + +## 多层感知器模型 + +我们将评估的第一个网络是多层感知器,简称 MLP。 + +这是一个简单的前馈神经网络模型,应该在考虑更复杂的模型之前进行评估。 + +MLP 可用于时间序列预测,方法是在先前时间步骤中进行多次观测,称为滞后观测,并将其用作输入要素并根据这些观测预测一个或多个时间步长。 + +这正是上一节中 _series_to_supervised()_ 函数提供的问题的框架。 + +因此,训练数据集是样本列表,其中每个样本在预测时间之前的几个月具有一定数量的观察,并且预测是序列中的下个月。例如: + +``` +X, y +month1, month2, month3, month4 +month2, month3, month4, month5 +month3, month4, month5, month6 +... +``` + +该模型将尝试概括这些样本,以便当提供超出模型已知的新样本时,它可以预测有用的东西;例如: + +``` +X, y +month4, month5, month6, ??? +``` + +我们将使用 Keras 深度学习库实现一个简单的 MLP。 + +该模型将具有输入层,其具有一些先前的观察结果。当我们定义第一个隐藏层时,可以使用 _input_dim_ 参数指定。该模型将具有单个隐藏层,其具有一定数量的节点,然后是单个输出层。 + +我们将在隐藏层上使用经过校正的线性激活函数,因为它表现良好。我们将在输出层使用线性激活函数(默认值),因为我们正在预测连续值。 + +网络的损失函数将是均方误差损失或 MSE,我们将使用随机梯度下降的高效 [Adam 风格来训练网络。](https://machinelearningmastery.com/adam-optimization-algorithm-for-deep-learning/) + +``` +# define model +model = Sequential() +model.add(Dense(n_nodes, activation='relu', input_dim=n_input)) +model.add(Dense(1)) +model.compile(loss='mse', optimizer='adam') +``` + +该模型将适合一些训练时期(对训练数据的暴露),并且可以指定批量大小以定义在每个时期内权重的更新频率。 + +下面列出了用于在训练数据集上拟合 MLP 模型的 _model_fit()_ 函数。 + +该函数要求配置为具有以下配置超参数的列表: + +* **n_input** :用作模型输入的滞后观察数。 +* **n_nodes** :隐藏层中使用的节点数。 +* **n_epochs** :将模型公开给整个训练数据集的次数。 +* **n_batch** :更新权重的时期内的样本数。 + +``` +# fit a model +def model_fit(train, config): + # unpack config + n_input, n_nodes, n_epochs, n_batch = config + # prepare data + data = series_to_supervised(train, n_in=n_input) + train_x, train_y = data[:, :-1], data[:, -1] + # define model + model = Sequential() + model.add(Dense(n_nodes, activation='relu', input_dim=n_input)) + model.add(Dense(1)) + model.compile(loss='mse', optimizer='adam') + # fit + model.fit(train_x, train_y, epochs=n_epochs, batch_size=n_batch, verbose=0) + return model +``` + +使用拟合 MLP 模型进行预测与调用 _predict()_ 函数并传入进行预测所需的一个样本值输入值一样简单。 + +``` +yhat = model.predict(x_input, verbose=0) +``` + +为了使预测超出已知数据的限制,这要求将最后 n 个已知观察值作为数组并用作输入。 + +_predict()_ 函数在进行预测时需要一个或多个输入样本,因此提供单个样本需要阵列具有[ _1,n_input_ ]形状​​,其中 _] n_input_ 是模型期望作为输入的时间步数。 + +类似地, _predict()_ 函数返回一个预测数组,每个样本一个作为输入提供。在一个预测的情况下,将存在具有一个值的数组。 + +下面的 _model_predict()_ 函数实现了这种行为,将模型,先前观察和模型配置作为参数,制定输入样本并进行一步预测,然后返回。 + +``` +# forecast with a pre-fit model +def model_predict(model, history, config): + # unpack config + n_input, _, _, _ = config + # prepare data + x_input = array(history[-n_input:]).reshape(1, n_input) + # forecast + yhat = model.predict(x_input, verbose=0) + return yhat[0] +``` + +我们现在拥有在月度汽车销售数据集上评估 MLP 模型所需的一切。 + +进行模型超参数的简单网格搜索,并选择下面的配置。这可能不是最佳配置,但却是最好的配置。 + +* **n_input** :24(例如 24 个月) +* **n_nodes** :500 +* **n_epochs** :100 +* **n_batch** :100 + +此配置可以定义为列表: + +``` +# define config +config = [24, 500, 100, 100] +``` + +请注意,当训练数据被构建为监督学习问题时,只有 72 个样本可用于训练模型。 + +使用 72 或更大的批量大小意味着使用批量梯度下降而不是小批量梯度下降来训练模型。这通常用于小数据集,并且意味着在每个时期结束时执行权重更新和梯度计算,而不是在每个时期内多次执行。 + +完整的代码示例如下所示。 + +``` +# evaluate mlp +from math import sqrt +from numpy import array +from numpy import mean +from numpy import std +from pandas import DataFrame +from pandas import concat +from pandas import read_csv +from sklearn.metrics import mean_squared_error +from keras.models import Sequential +from keras.layers import Dense +from matplotlib import pyplot + +# split a univariate dataset into train/test sets +def train_test_split(data, n_test): + return data[:-n_test], data[-n_test:] + +# transform list into supervised learning format +def series_to_supervised(data, n_in=1, n_out=1): + df = DataFrame(data) + cols = list() + # input sequence (t-n, ... t-1) + for i in range(n_in, 0, -1): + cols.append(df.shift(i)) + # forecast sequence (t, t+1, ... t+n) + for i in range(0, n_out): + cols.append(df.shift(-i)) + # put it all together + agg = concat(cols, axis=1) + # drop rows with NaN values + agg.dropna(inplace=True) + return agg.values + +# root mean squared error or rmse +def measure_rmse(actual, predicted): + return sqrt(mean_squared_error(actual, predicted)) + +# fit a model +def model_fit(train, config): + # unpack config + n_input, n_nodes, n_epochs, n_batch = config + # prepare data + data = series_to_supervised(train, n_in=n_input) + train_x, train_y = data[:, :-1], data[:, -1] + # define model + model = Sequential() + model.add(Dense(n_nodes, activation='relu', input_dim=n_input)) + model.add(Dense(1)) + model.compile(loss='mse', optimizer='adam') + # fit + model.fit(train_x, train_y, epochs=n_epochs, batch_size=n_batch, verbose=0) + return model + +# forecast with a pre-fit model +def model_predict(model, history, config): + # unpack config + n_input, _, _, _ = config + # prepare data + x_input = array(history[-n_input:]).reshape(1, n_input) + # forecast + yhat = model.predict(x_input, verbose=0) + return yhat[0] + +# walk-forward validation for univariate data +def walk_forward_validation(data, n_test, cfg): + predictions = list() + # split dataset + train, test = train_test_split(data, n_test) + # fit model + model = model_fit(train, cfg) + # seed history with training dataset + history = [x for x in train] + # step over each time-step in the test set + for i in range(len(test)): + # fit model and make forecast for history + yhat = model_predict(model, history, cfg) + # store forecast in list of predictions + predictions.append(yhat) + # add actual observation to history for the next loop + history.append(test[i]) + # estimate prediction error + error = measure_rmse(test, predictions) + print(' > %.3f' % error) + return error + +# repeat evaluation of a config +def repeat_evaluate(data, config, n_test, n_repeats=30): + # fit and evaluate the model n times + scores = [walk_forward_validation(data, n_test, config) for _ in range(n_repeats)] + return scores + +# summarize model performance +def summarize_scores(name, scores): + # print a summary + scores_m, score_std = mean(scores), std(scores) + print('%s: %.3f RMSE (+/- %.3f)' % (name, scores_m, score_std)) + # box and whisker plot + pyplot.boxplot(scores) + pyplot.show() + +series = read_csv('monthly-car-sales.csv', header=0, index_col=0) +data = series.values +# data split +n_test = 12 +# define config +config = [24, 500, 100, 100] +# grid search +scores = repeat_evaluate(data, config, n_test) +# summarize scores +summarize_scores('mlp', scores) +``` + +运行该示例为模型的 30 次重复评估中的每一次打印 RMSE。 + +在运行结束时,报告的平均和标准偏差 RMSE 约为 1,526 销售。 + +我们可以看到,平均而言,所选配置的表现优于天真模型(1841.155)和 SARIMA 模型(1551.842)。 + +这是令人印象深刻的,因为该模型直接对原始数据进行操作而不进行缩放或数据静止。 + +``` + > 1629.203 + > 1642.219 + > 1472.483 + > 1662.055 + > 1452.480 + > 1465.535 + > 1116.253 + > 1682.667 + > 1642.626 + > 1700.183 + > 1444.481 + > 1673.217 + > 1602.342 + > 1655.895 + > 1319.387 + > 1591.972 + > 1592.574 + > 1361.607 + > 1450.348 + > 1314.529 + > 1549.505 + > 1569.750 + > 1427.897 + > 1478.926 + > 1474.990 + > 1458.993 + > 1643.383 + > 1457.925 + > 1558.934 + > 1708.278 +mlp: 1526.688 RMSE (+/- 134.789) +``` + +创建 RMSE 分数的方框和胡须图,以总结模型表现的传播。 + +这有助于理解分数的传播。我们可以看到,尽管平均而言模型的表现令人印象深刻,但传播幅度很大。标准偏差略大于 134 销售额,这意味着更糟糕的案例模型运行,误差与平均误差相差 2 或 3 个标准差可能比天真模型差。 + +使用 MLP 模型的一个挑战是如何利用更高的技能并在多次运行中最小化模型的方差。 + +该问题通常适用于神经网络。您可以使用许多策略,但最简单的方法可能就是在所有可用数据上训练多个最终模型,并在进行预测时在集合中使用它们,例如:预测是 10 到 30 个模型的平均值。 + +![Box and Whisker Plot of Multilayer Perceptron RMSE Forecasting Car Sales](img/bd66ac0d66a3310bdf178d3f5d6b9e14.jpg) + +多层感知器 RMSE 预测汽车销售的盒子和晶须图 + +## 卷积神经网络模型 + +卷积神经网络(CNN)是为二维图像数据开发的一种神经网络,尽管它们可用于一维数据,例如文本序列和时间序列。 + +当对一维数据进行操作时,CNN 读取一系列滞后观察并学习提取与进行预测相关的特征。 + +我们将定义具有两个卷积层的 CNN,用于从输入序列中提取特征。每个都将具有可配置数量的滤波器和内核大小,并将使用经过整流的线性激活功能。滤波器的数量决定了读取和投影加权输入的并行字段的数量。内核大小定义了网络沿输入序列读取时每个快照内读取的时间步数。 + +``` +model.add(Conv1D(filters=n_filters, kernel_size=n_kernel, activation='relu', input_shape=(n_input, 1))) +model.add(Conv1D(filters=n_filters, kernel_size=n_kernel, activation='relu')) +``` + +在卷积层之后使用最大池化层将加权输入特征提取为最显着的特征,将输入大小减小 1/4。汇总输入在被解释之前被平展为一个长向量,并用于进行一步预测。 + +``` +model.add(MaxPooling1D(pool_size=2)) +model.add(Flatten()) +model.add(Dense(1)) +``` + +CNN 模型期望输入数据采用多个样本的形式,其中每个样本具有多个输入时间步长,与上一节中的 MLP 相同。 + +一个区别是 CNN 可以在每个时间步骤支持多个特征或类型的观察,其被解释为图像的通道。我们在每个时间步都只有一个特征,因此输入数据所需的三维形状将是[ _n_samples,n_input,1_ ]。 + +``` +train_x = train_x.reshape((train_x.shape[0], train_x.shape[1], 1)) +``` + +下面列出了用于在训练数据集上拟合 CNN 模型的 _model_fit()_ 函数。 + +该模型将以下五个配置参数作为列表: + +* **n_input** :用作模型输入的滞后观察数。 +* **n_filters** :并行滤波器的数量。 +* **n_kernel** :每次读取输入序列时考虑的时间步数。 +* **n_epochs** :将模型公开给整个训练数据集的次数。 +* **n_batch** :更新权重的时期内的样本数。 + +``` +# fit a model +def model_fit(train, config): + # unpack config + n_input, n_filters, n_kernel, n_epochs, n_batch = config + # prepare data + data = series_to_supervised(train, n_in=n_input) + train_x, train_y = data[:, :-1], data[:, -1] + train_x = train_x.reshape((train_x.shape[0], train_x.shape[1], 1)) + # define model + model = Sequential() + model.add(Conv1D(filters=n_filters, kernel_size=n_kernel, activation='relu', input_shape=(n_input, 1))) + model.add(Conv1D(filters=n_filters, kernel_size=n_kernel, activation='relu')) + model.add(MaxPooling1D(pool_size=2)) + model.add(Flatten()) + model.add(Dense(1)) + model.compile(loss='mse', optimizer='adam') + # fit + model.fit(train_x, train_y, epochs=n_epochs, batch_size=n_batch, verbose=0) + return model +``` + +使用拟合 CNN 模型进行预测非常类似于使用上一节中的拟合 MLP 模型进行预测。 + +一个区别在于我们要求我们指定在每个时间步骤观察到的特征数量,在这种情况下为 1.因此,当进行单个一步预测时,输入数组的形状必须是: + +``` +[1, n_input, 1] +``` + +下面的 _model_predict()_ 函数实现了这种行为。 + +``` +# forecast with a pre-fit model +def model_predict(model, history, config): + # unpack config + n_input, _, _, _, _ = config + # prepare data + x_input = array(history[-n_input:]).reshape((1, n_input, 1)) + # forecast + yhat = model.predict(x_input, verbose=0) + return yhat[0] +``` + +进行模型超参数的简单网格搜索,并选择下面的配置。这不是最佳配置,但却是最好的配置。 + +所选配置如下: + +* **n_input** :36(例如 3 年或 3 * 12) +* **n_filters** :256 +* **n_kernel** :3 +* **n_epochs** :100 +* **n_batch** :100(例如批量梯度下降) + +这可以指定为如下列表: + +``` +# define config +config = [36, 256, 3, 100, 100] +``` + +将所有这些结合在一起,下面列出了完整的示例。 + +``` +# evaluate cnn +from math import sqrt +from numpy import array +from numpy import mean +from numpy import std +from pandas import DataFrame +from pandas import concat +from pandas import read_csv +from sklearn.metrics import mean_squared_error +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import Flatten +from keras.layers.convolutional import Conv1D +from keras.layers.convolutional import MaxPooling1D +from matplotlib import pyplot + +# split a univariate dataset into train/test sets +def train_test_split(data, n_test): + return data[:-n_test], data[-n_test:] + +# transform list into supervised learning format +def series_to_supervised(data, n_in=1, n_out=1): + df = DataFrame(data) + cols = list() + # input sequence (t-n, ... t-1) + for i in range(n_in, 0, -1): + cols.append(df.shift(i)) + # forecast sequence (t, t+1, ... t+n) + for i in range(0, n_out): + cols.append(df.shift(-i)) + # put it all together + agg = concat(cols, axis=1) + # drop rows with NaN values + agg.dropna(inplace=True) + return agg.values + +# root mean squared error or rmse +def measure_rmse(actual, predicted): + return sqrt(mean_squared_error(actual, predicted)) + +# fit a model +def model_fit(train, config): + # unpack config + n_input, n_filters, n_kernel, n_epochs, n_batch = config + # prepare data + data = series_to_supervised(train, n_in=n_input) + train_x, train_y = data[:, :-1], data[:, -1] + train_x = train_x.reshape((train_x.shape[0], train_x.shape[1], 1)) + # define model + model = Sequential() + model.add(Conv1D(filters=n_filters, kernel_size=n_kernel, activation='relu', input_shape=(n_input, 1))) + model.add(Conv1D(filters=n_filters, kernel_size=n_kernel, activation='relu')) + model.add(MaxPooling1D(pool_size=2)) + model.add(Flatten()) + model.add(Dense(1)) + model.compile(loss='mse', optimizer='adam') + # fit + model.fit(train_x, train_y, epochs=n_epochs, batch_size=n_batch, verbose=0) + return model + +# forecast with a pre-fit model +def model_predict(model, history, config): + # unpack config + n_input, _, _, _, _ = config + # prepare data + x_input = array(history[-n_input:]).reshape((1, n_input, 1)) + # forecast + yhat = model.predict(x_input, verbose=0) + return yhat[0] + +# walk-forward validation for univariate data +def walk_forward_validation(data, n_test, cfg): + predictions = list() + # split dataset + train, test = train_test_split(data, n_test) + # fit model + model = model_fit(train, cfg) + # seed history with training dataset + history = [x for x in train] + # step over each time-step in the test set + for i in range(len(test)): + # fit model and make forecast for history + yhat = model_predict(model, history, cfg) + # store forecast in list of predictions + predictions.append(yhat) + # add actual observation to history for the next loop + history.append(test[i]) + # estimate prediction error + error = measure_rmse(test, predictions) + print(' > %.3f' % error) + return error + +# repeat evaluation of a config +def repeat_evaluate(data, config, n_test, n_repeats=30): + # fit and evaluate the model n times + scores = [walk_forward_validation(data, n_test, config) for _ in range(n_repeats)] + return scores + +# summarize model performance +def summarize_scores(name, scores): + # print a summary + scores_m, score_std = mean(scores), std(scores) + print('%s: %.3f RMSE (+/- %.3f)' % (name, scores_m, score_std)) + # box and whisker plot + pyplot.boxplot(scores) + pyplot.show() + +series = read_csv('monthly-car-sales.csv', header=0, index_col=0) +data = series.values +# data split +n_test = 12 +# define config +config = [36, 256, 3, 100, 100] +# grid search +scores = repeat_evaluate(data, config, n_test) +# summarize scores +summarize_scores('cnn', scores) +``` + +首先运行该示例,为每次重复的模型评估打印 RMSE。 + +在运行结束时,我们可以看到模型确实熟练,达到平均 RMSE 1,524.067,这比天真模型,SARIMA 模型,甚至上一节中的 MLP 模型更好。 + +这是令人印象深刻的,因为该模型直接对原始数据进行操作而不进行缩放或数据静止。 + +分数的标准偏差很大,约为 57 个销售额,但却是前一部分 MLP 模型观察到的方差大小的 1/3。我们有信心在坏情况下(3 个标准偏差),模型 RMSE 将保持低于(优于)天真模型的表现。 + +``` +> 1551.031 +> 1495.743 +> 1449.408 +> 1526.017 +> 1466.118 +> 1566.535 +> 1649.204 +> 1455.782 +> 1574.214 +> 1541.790 +> 1489.140 +> 1506.035 +> 1513.197 +> 1530.714 +> 1511.328 +> 1471.518 +> 1555.596 +> 1552.026 +> 1531.727 +> 1472.978 +> 1620.242 +> 1424.153 +> 1456.393 +> 1581.114 +> 1539.286 +> 1489.795 +> 1652.620 +> 1537.349 +> 1443.777 +> 1567.179 +cnn: 1524.067 RMSE (+/- 57.148) +``` + +创建分数的框和胡须图以帮助理解运行中的错误传播。 + +我们可以看到,差价看起来似乎偏向于更大的误差值,正如我们所预期的那样,尽管图的上部胡须(在这种情况下,最大误差不是异常值)仍然受限于 1,650 销售的 RMSE 。 + +![Box and Whisker Plot of Convolutional Neural Network RMSE Forecasting Car Sales](img/6271a0521f567098b8e9404a30eb6560.jpg) + +卷积神经网络 RMSE 预测汽车销售的盒子和晶须图 + +## 循环神经网络模型 + +循环神经网络或 RNN 是那些类型的神经网络,其使用来自先前步骤的网络输出作为输入以尝试跨序列数据自动学习。 + +长短期内存或 LSTM 网络是一种 RNN,其实现解决了在序列数据上训练 RNN 导致稳定模型的一般困难。它通过学习控制每个节点内的循环连接的内部门的权重来实现这一点。 + +尽管针对序列数据进行了开发, [LSTM 尚未证明在时间序列预测问题上有效](https://machinelearningmastery.com/suitability-long-short-term-memory-networks-time-series-forecasting/),其中输出是近期观测的函数,例如自动回归类型预测问题,例如汽车销售数据集。 + +然而,我们可以开发用于自回归问题的 LSTM 模型,并将其用作与其他神经网络模型进行比较的点。 + +在本节中,我们将探讨 LSTM 模型的三变量,用于单变量时间序列预测;他们是: + +* **LSTM** :LSTM 网络原样。 +* **CNN-LSTM** :学习输入功能的 CNN 网络和解释它们的 LSTM。 +* **ConvLSTM** :CNN 和 LSTM 的组合,其中 LSTM 单元使用 CNN 的卷积过程读取输入数据。 + +### LSTM + +LSTM 神经网络可用于单变量时间序列预测。 + +作为 RNN,它将一次一步地读取输入序列的每个时间步长。 LSTM 具有内部存储器,允许它在读取给定输入序列的步骤时累积内部状态。 + +在序列结束时,隐藏 LSTM 单元层中的每个节点将输出单个值。该值向量总结了 LSTM 从输入序列中学习或提取的内容。这可以在完成最终预测之前由完全连接的层解释。 + +``` +# define model +model = Sequential() +model.add(LSTM(n_nodes, activation='relu', input_shape=(n_input, 1))) +model.add(Dense(n_nodes, activation='relu')) +model.add(Dense(1)) +model.compile(loss='mse', optimizer='adam') +``` + +与 CNN 一样,LSTM 可以在每个时间步骤支持多个变量或功能。由于汽车销售数据集在每个时间步都只有一个值,我们可以将其固定为 1,既可以在 input_shape 参数[ _n_input,1_ ]中定义网络输入,也可以定义形状输入样本。 + +``` +train_x = train_x.reshape((train_x.shape[0], train_x.shape[1], 1)) +``` + +与不一次一步读取序列数据的 MLP 和 CNN 不同,如果数据是静止的,LSTM 确实表现更好。这意味着执行差异操作以消除趋势和季节性结构。 + +对于汽车销售数据集,我们可以通过执行季节性调整来制作数据信息,即从每个观察值中减去一年前的值。 + +``` +adjusted = value - value[-12] +``` + +这可以针对整个训练数据集系统地执行。这也意味着必须放弃观察的第一年,因为我们没有前一年的数据来区分它们。 + +下面的 _ 差异()_ 函数将使提供的数据集与提供的偏移量不同,称为差异顺序,例如差异顺序。 12 前一个月的一年。 + +``` +# difference dataset +def difference(data, interval): + return [data[i] - data[i - interval] for i in range(interval, len(data))] +``` + +我们可以使差值顺序成为模型的超参数,并且只有在提供非零值的情况下才执行操作。 + +下面提供了用于拟合 LSTM 模型的 _model_fit()_ 函数。 + +该模型需要一个包含五个模型超参数的列表;他们是: + +* **n_input** :用作模型输入的滞后观察数。 +* **n_nodes** :隐藏层中使用的 LSTM 单元数。 +* **n_epochs** :将模型公开给整个训练数据集的次数。 +* **n_batch** :更新权重的时期内的样本数。 +* **n_diff** :差值顺序或 0 如果不使用。 + +``` +# fit a model +def model_fit(train, config): + # unpack config + n_input, n_nodes, n_epochs, n_batch, n_diff = config + # prepare data + if n_diff > 0: + train = difference(train, n_diff) + data = series_to_supervised(train, n_in=n_input) + train_x, train_y = data[:, :-1], data[:, -1] + train_x = train_x.reshape((train_x.shape[0], train_x.shape[1], 1)) + # define model + model = Sequential() + model.add(LSTM(n_nodes, activation='relu', input_shape=(n_input, 1))) + model.add(Dense(n_nodes, activation='relu')) + model.add(Dense(1)) + model.compile(loss='mse', optimizer='adam') + # fit + model.fit(train_x, train_y, epochs=n_epochs, batch_size=n_batch, verbose=0) + return model +``` + +使用 LSTM 模型进行预测与使用 CNN 模型进行预测相同。 + +单个输入必须具有样本,时间步长和特征的三维结构,在这种情况下,我们只有 1 个样本和 1 个特征:[ _1,n_input,1_ ]。 + +如果执行差异操作,我们必须添加模型进行预测后减去的值。在制定用于进行预测的单个输入之前,我们还必须区分历史数据。 + +下面的 _model_predict()_ 函数实现了这种行为。 + +``` +# forecast with a pre-fit model +def model_predict(model, history, config): + # unpack config + n_input, _, _, _, n_diff = config + # prepare data + correction = 0.0 + if n_diff > 0: + correction = history[-n_diff] + history = difference(history, n_diff) + x_input = array(history[-n_input:]).reshape((1, n_input, 1)) + # forecast + yhat = model.predict(x_input, verbose=0) + return correction + yhat[0] +``` + +进行模型超参数的简单网格搜索,并选择下面的配置。这不是最佳配置,但却是最好的配置。 + +所选配置如下: + +* **n_input** :36(即 3 年或 3 * 12) +* **n_nodes** :50 +* **n_epochs** :100 +* **n_batch** :100(即批量梯度下降) +* **n_diff** :12(即季节性差异) + +这可以指定为一个列表: + +``` +# define config +config = [36, 50, 100, 100, 12] +``` + +将所有这些结合在一起,下面列出了完整的示例。 + +``` +# evaluate lstm +from math import sqrt +from numpy import array +from numpy import mean +from numpy import std +from pandas import DataFrame +from pandas import concat +from pandas import read_csv +from sklearn.metrics import mean_squared_error +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import LSTM +from matplotlib import pyplot + +# split a univariate dataset into train/test sets +def train_test_split(data, n_test): + return data[:-n_test], data[-n_test:] + +# transform list into supervised learning format +def series_to_supervised(data, n_in=1, n_out=1): + df = DataFrame(data) + cols = list() + # input sequence (t-n, ... t-1) + for i in range(n_in, 0, -1): + cols.append(df.shift(i)) + # forecast sequence (t, t+1, ... t+n) + for i in range(0, n_out): + cols.append(df.shift(-i)) + # put it all together + agg = concat(cols, axis=1) + # drop rows with NaN values + agg.dropna(inplace=True) + return agg.values + +# root mean squared error or rmse +def measure_rmse(actual, predicted): + return sqrt(mean_squared_error(actual, predicted)) + +# difference dataset +def difference(data, interval): + return [data[i] - data[i - interval] for i in range(interval, len(data))] + +# fit a model +def model_fit(train, config): + # unpack config + n_input, n_nodes, n_epochs, n_batch, n_diff = config + # prepare data + if n_diff > 0: + train = difference(train, n_diff) + data = series_to_supervised(train, n_in=n_input) + train_x, train_y = data[:, :-1], data[:, -1] + train_x = train_x.reshape((train_x.shape[0], train_x.shape[1], 1)) + # define model + model = Sequential() + model.add(LSTM(n_nodes, activation='relu', input_shape=(n_input, 1))) + model.add(Dense(n_nodes, activation='relu')) + model.add(Dense(1)) + model.compile(loss='mse', optimizer='adam') + # fit + model.fit(train_x, train_y, epochs=n_epochs, batch_size=n_batch, verbose=0) + return model + +# forecast with a pre-fit model +def model_predict(model, history, config): + # unpack config + n_input, _, _, _, n_diff = config + # prepare data + correction = 0.0 + if n_diff > 0: + correction = history[-n_diff] + history = difference(history, n_diff) + x_input = array(history[-n_input:]).reshape((1, n_input, 1)) + # forecast + yhat = model.predict(x_input, verbose=0) + return correction + yhat[0] + +# walk-forward validation for univariate data +def walk_forward_validation(data, n_test, cfg): + predictions = list() + # split dataset + train, test = train_test_split(data, n_test) + # fit model + model = model_fit(train, cfg) + # seed history with training dataset + history = [x for x in train] + # step over each time-step in the test set + for i in range(len(test)): + # fit model and make forecast for history + yhat = model_predict(model, history, cfg) + # store forecast in list of predictions + predictions.append(yhat) + # add actual observation to history for the next loop + history.append(test[i]) + # estimate prediction error + error = measure_rmse(test, predictions) + print(' > %.3f' % error) + return error + +# repeat evaluation of a config +def repeat_evaluate(data, config, n_test, n_repeats=30): + # fit and evaluate the model n times + scores = [walk_forward_validation(data, n_test, config) for _ in range(n_repeats)] + return scores + +# summarize model performance +def summarize_scores(name, scores): + # print a summary + scores_m, score_std = mean(scores), std(scores) + print('%s: %.3f RMSE (+/- %.3f)' % (name, scores_m, score_std)) + # box and whisker plot + pyplot.boxplot(scores) + pyplot.show() + +series = read_csv('monthly-car-sales.csv', header=0, index_col=0) +data = series.values +# data split +n_test = 12 +# define config +config = [36, 50, 100, 100, 12] +# grid search +scores = repeat_evaluate(data, config, n_test) +# summarize scores +summarize_scores('lstm', scores) +``` + +运行该示例,我们可以看到每次重复评估模型的 RMSE。 + +在运行结束时,我们可以看到平均 RMSE 约为 2,109,这比天真模型更差。这表明所选择的模型并不熟练,并且鉴于前面部分中用于查找模型配置的相同资源,它是最好的。 + +这提供了进一步的证据(虽然证据不足),LSTM,至少单独,可能不适合自回归型序列预测问题。 + +``` +> 2129.480 +> 2169.109 +> 2078.290 +> 2257.222 +> 2014.911 +> 2197.283 +> 2028.176 +> 2110.718 +> 2100.388 +> 2157.271 +> 1940.103 +> 2086.588 +> 1986.696 +> 2168.784 +> 2188.813 +> 2086.759 +> 2128.095 +> 2126.467 +> 2077.463 +> 2057.679 +> 2209.818 +> 2067.082 +> 1983.346 +> 2157.749 +> 2145.071 +> 2266.130 +> 2105.043 +> 2128.549 +> 1952.002 +> 2188.287 +lstm: 2109.779 RMSE (+/- 81.373) +``` + +还创建了一个盒子和胡须图,总结了 RMSE 分数的分布。 + +甚至模型的基本情况也没有达到天真模型的表现。 + +![Box and Whisker Plot of Long Short-Term Memory Neural Network RMSE Forecasting Car Sales](img/4fd5ceadaf6bf13c76301251b1e6c656.jpg) + +长短期记忆神经网络 RMSE 预测汽车销售的盒子和晶须图 + +### CNN LSTM + +我们已经看到 CNN 模型能够自动学习和从原始序列数据中提取特征而无需缩放或差分。 + +我们可以将此功能与 LSTM 结合使用,其中 CNN 模型应用于输入数据的子序列,其结果一起形成可由 LSTM 模型解释的提取特征的时间序列。 + +用于通过 LSTM 随时间读取多个子序列的 CNN 模型的这种组合称为 CNN-LSTM 模型。 + +该模型要求每个输入序列,例如, 36 个月,分为多个子序列,每个子序列由 CNN 模型读取,例如, 12 个时间步骤的 3 个子序列。将子序列划分多年可能是有意义的,但这只是一个假设,可以使用其他分裂,例如六个时间步骤的六个子序列。因此,对于子序列的数量和每个子序列参数的步数,使用 _n_seq_ 和 _n_steps_ 参数化该分裂。 + +``` +train_x = train_x.reshape((train_x.shape[0], n_seq, n_steps, 1)) +``` + +每个样本的滞后观察数量简单( _n_seq * n_steps_ )。 + +这是一个 4 维输入数组,现在尺寸为: + +``` +[samples, subsequences, timesteps, features] +``` + +必须对每个输入子序列应用相同的 CNN 模型。 + +我们可以通过将整个 CNN 模型包装在 _TimeDistributed_ 层包装器中来实现这一点。 + +``` +model = Sequential() +model.add(TimeDistributed(Conv1D(filters=n_filters, kernel_size=n_kernel, activation='relu', input_shape=(None,n_steps,1)))) +model.add(TimeDistributed(Conv1D(filters=n_filters, kernel_size=n_kernel, activation='relu'))) +model.add(TimeDistributed(MaxPooling1D(pool_size=2))) +model.add(TimeDistributed(Flatten())) +``` + +CNN 子模型的一个应用程序的输出将是向量。子模型到每个输入子序列的输出将是可由 LSTM 模型解释的时间序列的解释。接下来是完全连接的层,用于解释 LSTM 的结果,最后是输出层,用于进行一步预测。 + +``` +model.add(LSTM(n_nodes, activation='relu')) +model.add(Dense(n_nodes, activation='relu')) +model.add(Dense(1)) +``` + +完整的 _model_fit()_ 功能如下所示。 + +该模型需要一个包含七个超参数的列表;他们是: + +* **n_seq** :样本中的子序列数。 +* **n_steps** :每个子序列中的时间步数。 +* **n_filters** :并行滤波器的数量。 +* **n_kernel** :每次读取输入序列时考虑的时间步数。 +* **n_nodes** :隐藏层中使用的 LSTM 单元数。 +* **n_epochs** :将模型公开给整个训练数据集的次数。 +* **n_batch** :更新权重的时期内的样本数。 + +``` +# fit a model +def model_fit(train, config): + # unpack config + n_seq, n_steps, n_filters, n_kernel, n_nodes, n_epochs, n_batch = config + n_input = n_seq * n_steps + # prepare data + data = series_to_supervised(train, n_in=n_input) + train_x, train_y = data[:, :-1], data[:, -1] + train_x = train_x.reshape((train_x.shape[0], n_seq, n_steps, 1)) + # define model + model = Sequential() + model.add(TimeDistributed(Conv1D(filters=n_filters, kernel_size=n_kernel, activation='relu', input_shape=(None,n_steps,1)))) + model.add(TimeDistributed(Conv1D(filters=n_filters, kernel_size=n_kernel, activation='relu'))) + model.add(TimeDistributed(MaxPooling1D(pool_size=2))) + model.add(TimeDistributed(Flatten())) + model.add(LSTM(n_nodes, activation='relu')) + model.add(Dense(n_nodes, activation='relu')) + model.add(Dense(1)) + model.compile(loss='mse', optimizer='adam') + # fit + model.fit(train_x, train_y, epochs=n_epochs, batch_size=n_batch, verbose=0) + return model +``` + +使用拟合模型进行预测与 LSTM 或 CNN 大致相同,尽管添加了将每个样本分成具有给定数量的时间步长的子序列。 + +``` +# prepare data +x_input = array(history[-n_input:]).reshape((1, n_seq, n_steps, 1)) +``` + +更新后的 _model_predict()_ 功能如下所示。 + +``` +# forecast with a pre-fit model +def model_predict(model, history, config): + # unpack config + n_seq, n_steps, _, _, _, _, _ = config + n_input = n_seq * n_steps + # prepare data + x_input = array(history[-n_input:]).reshape((1, n_seq, n_steps, 1)) + # forecast + yhat = model.predict(x_input, verbose=0) + return yhat[0] +``` + +进行模型超参数的简单网格搜索,并选择下面的配置。这可能不是最佳配置,但它是最好的配置。 + +* **n_seq** :3(即 3 年) +* **n_steps** :12(即 1 个月) +* **n_filters** :64 +* **n_kernel** :3 +* **n_nodes** :100 +* **n_epochs** :200 +* **n_batch** :100(即批量梯度下降) + +我们可以将配置定义为列表;例如: + +``` +# define config +config = [3, 12, 64, 3, 100, 200, 100] +``` + +下面列出了评估用于预测单变量月度汽车销售的 CNN-LSTM 模型的完整示例。 + +``` +# evaluate cnn lstm +from math import sqrt +from numpy import array +from numpy import mean +from numpy import std +from pandas import DataFrame +from pandas import concat +from pandas import read_csv +from sklearn.metrics import mean_squared_error +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import LSTM +from keras.layers import TimeDistributed +from keras.layers import Flatten +from keras.layers.convolutional import Conv1D +from keras.layers.convolutional import MaxPooling1D +from matplotlib import pyplot + +# split a univariate dataset into train/test sets +def train_test_split(data, n_test): + return data[:-n_test], data[-n_test:] + +# transform list into supervised learning format +def series_to_supervised(data, n_in=1, n_out=1): + df = DataFrame(data) + cols = list() + # input sequence (t-n, ... t-1) + for i in range(n_in, 0, -1): + cols.append(df.shift(i)) + # forecast sequence (t, t+1, ... t+n) + for i in range(0, n_out): + cols.append(df.shift(-i)) + # put it all together + agg = concat(cols, axis=1) + # drop rows with NaN values + agg.dropna(inplace=True) + return agg.values + +# root mean squared error or rmse +def measure_rmse(actual, predicted): + return sqrt(mean_squared_error(actual, predicted)) + +# fit a model +def model_fit(train, config): + # unpack config + n_seq, n_steps, n_filters, n_kernel, n_nodes, n_epochs, n_batch = config + n_input = n_seq * n_steps + # prepare data + data = series_to_supervised(train, n_in=n_input) + train_x, train_y = data[:, :-1], data[:, -1] + train_x = train_x.reshape((train_x.shape[0], n_seq, n_steps, 1)) + # define model + model = Sequential() + model.add(TimeDistributed(Conv1D(filters=n_filters, kernel_size=n_kernel, activation='relu', input_shape=(None,n_steps,1)))) + model.add(TimeDistributed(Conv1D(filters=n_filters, kernel_size=n_kernel, activation='relu'))) + model.add(TimeDistributed(MaxPooling1D(pool_size=2))) + model.add(TimeDistributed(Flatten())) + model.add(LSTM(n_nodes, activation='relu')) + model.add(Dense(n_nodes, activation='relu')) + model.add(Dense(1)) + model.compile(loss='mse', optimizer='adam') + # fit + model.fit(train_x, train_y, epochs=n_epochs, batch_size=n_batch, verbose=0) + return model + +# forecast with a pre-fit model +def model_predict(model, history, config): + # unpack config + n_seq, n_steps, _, _, _, _, _ = config + n_input = n_seq * n_steps + # prepare data + x_input = array(history[-n_input:]).reshape((1, n_seq, n_steps, 1)) + # forecast + yhat = model.predict(x_input, verbose=0) + return yhat[0] + +# walk-forward validation for univariate data +def walk_forward_validation(data, n_test, cfg): + predictions = list() + # split dataset + train, test = train_test_split(data, n_test) + # fit model + model = model_fit(train, cfg) + # seed history with training dataset + history = [x for x in train] + # step over each time-step in the test set + for i in range(len(test)): + # fit model and make forecast for history + yhat = model_predict(model, history, cfg) + # store forecast in list of predictions + predictions.append(yhat) + # add actual observation to history for the next loop + history.append(test[i]) + # estimate prediction error + error = measure_rmse(test, predictions) + print(' > %.3f' % error) + return error + +# repeat evaluation of a config +def repeat_evaluate(data, config, n_test, n_repeats=30): + # fit and evaluate the model n times + scores = [walk_forward_validation(data, n_test, config) for _ in range(n_repeats)] + return scores + +# summarize model performance +def summarize_scores(name, scores): + # print a summary + scores_m, score_std = mean(scores), std(scores) + print('%s: %.3f RMSE (+/- %.3f)' % (name, scores_m, score_std)) + # box and whisker plot + pyplot.boxplot(scores) + pyplot.show() + +series = read_csv('monthly-car-sales.csv', header=0, index_col=0) +data = series.values +# data split +n_test = 12 +# define config +config = [3, 12, 64, 3, 100, 200, 100] +# grid search +scores = repeat_evaluate(data, config, n_test) +# summarize scores +summarize_scores('cnn-lstm', scores) +``` + +运行该示例为每次重复的模型评估打印 RMSE。 + +最终平均 RMSE 报告在约 1,626 的末尾,低于幼稚模型,但仍高于 SARIMA 模型。该分数的标准偏差也非常大,表明所选配置可能不如独立 CNN 模型稳定。 + +``` + > 1543.533 + > 1421.895 + > 1467.927 + > 1441.125 + > 1750.995 + > 1321.498 + > 1571.657 + > 1845.298 + > 1621.589 + > 1425.065 + > 1675.232 + > 1807.288 + > 2922.295 + > 1391.861 + > 1626.655 + > 1633.177 + > 1667.572 + > 1577.285 + > 1590.235 + > 1557.385 + > 1784.982 + > 1664.839 + > 1741.729 + > 1437.992 + > 1772.076 + > 1289.794 + > 1685.976 + > 1498.123 + > 1618.627 + > 1448.361 +cnn-lstm: 1626.735 RMSE (+/- 279.850) +``` + +还创建了一个盒子和胡须图,总结了 RMSE 分数的分布。 + +该图显示了一个非常差的表现异常值,仅低于 3,000 个销售额。 + +![Box and Whisker Plot of CNN-LSTM RMSE Forecasting Car Sales](img/5749f0ac52452ef48f62d8653a1de766.jpg) + +CNN-LSTM RMSE 预测汽车销售的盒子和晶须图 + +### ConvLSTM + +作为读取每个 LSTM 单元内的输入序列的一部分,可以执行卷积运算。 + +这意味着,LSTM 不是一次一步地读取序列,而是使用卷积过程(如 CNN)一次读取观察的块或子序列。 + +这与使用 LSTM 首先读取提取特征并使用 LSTM 解释结果不同;这是作为 LSTM 的一部分在每个时间步执行 CNN 操作。 + +这种类型的模型称为卷积 LSTM,简称 ConvLSTM。它在 Keras 中作为 2D 数据称为 ConvLSTM2D 的层提供。我们可以通过假设我们有一行包含多列来配置它以用于 1D 序列数据。 + +与 CNN-LSTM 一样,输入数据被分成子序列,其中每个子序列具有固定数量的时间步长,尽管我们还必须指定每个子序列中的行数,在这种情况下固定为 1。 + +``` +train_x = train_x.reshape((train_x.shape[0], n_seq, 1, n_steps, 1)) +``` + +形状是五维的,尺寸为: + +``` +[samples, subsequences, rows, columns, features] +``` + +与 CNN 一样,ConvLSTM 层允许我们指定过滤器映射的数量以及读取输入序列时使用的内核的大小。 + +``` +model.add(ConvLSTM2D(filters=n_filters, kernel_size=(1,n_kernel), activation='relu', input_shape=(n_seq, 1, n_steps, 1))) +``` + +层的输出是一系列过滤器映射,在解释之前必须首先将其展平,然后是输出层。 + +该模型需要一个包含七个超参数的列表,与 CNN-LSTM 相同;他们是: + +* **n_seq** :样本中的子序列数。 +* **n_steps** :每个子序列中的时间步数。 +* **n_filters** :并行滤波器的数量。 +* **n_kernel** :每次读取输入序列时考虑的时间步数。 +* **n_nodes** :隐藏层中使用的 LSTM 单元数。 +* **n_epochs** :将模型公开给整个训练数据集的次数。 +* **n_batch** :更新权重的时期内的样本数。 + +下面列出了实现所有这些功能的 _model_fit()_ 函数。 + +``` +# fit a model +def model_fit(train, config): + # unpack config + n_seq, n_steps, n_filters, n_kernel, n_nodes, n_epochs, n_batch = config + n_input = n_seq * n_steps + # prepare data + data = series_to_supervised(train, n_in=n_input) + train_x, train_y = data[:, :-1], data[:, -1] + train_x = train_x.reshape((train_x.shape[0], n_seq, 1, n_steps, 1)) + # define model + model = Sequential() + model.add(ConvLSTM2D(filters=n_filters, kernel_size=(1,n_kernel), activation='relu', input_shape=(n_seq, 1, n_steps, 1))) + model.add(Flatten()) + model.add(Dense(n_nodes, activation='relu')) + model.add(Dense(1)) + model.compile(loss='mse', optimizer='adam') + # fit + model.fit(train_x, train_y, epochs=n_epochs, batch_size=n_batch, verbose=0) + return model +``` + +使用拟合模型以与 CNN-LSTM 相同的方式进行预测,尽管我们将附加行维度固定为 1。 + +``` +# prepare data +x_input = array(history[-n_input:]).reshape((1, n_seq, 1, n_steps, 1)) +``` + +下面列出了用于进行单个一步预测的 _model_predict()_ 函数。 + +``` +# forecast with a pre-fit model +def model_predict(model, history, config): + # unpack config + n_seq, n_steps, _, _, _, _, _ = config + n_input = n_seq * n_steps + # prepare data + x_input = array(history[-n_input:]).reshape((1, n_seq, 1, n_steps, 1)) + # forecast + yhat = model.predict(x_input, verbose=0) + return yhat[0] +``` + +进行模型超参数的简单网格搜索,并选择下面的配置。 + +这可能不是最佳配置,但却是最好的配置。 + +* **n_seq** :3(即 3 年) +* **n_steps** :12(即 1 个月) +* **n_filters** :256 +* **n_kernel** :3 +* **n_nodes** :200 +* **n_epochs** :200 +* **n_batch** :100(即批量梯度下降) + +我们可以将配置定义为列表;例如: + +``` +# define config +config = [3, 12, 256, 3, 200, 200, 100] +``` + +我们可以将所有这些结合在一起。下面列出了评估每月汽车销售数据集一步预测的 ConvLSTM 模型的完整代码清单。 + +``` +# evaluate convlstm +from math import sqrt +from numpy import array +from numpy import mean +from numpy import std +from pandas import DataFrame +from pandas import concat +from pandas import read_csv +from sklearn.metrics import mean_squared_error +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import Flatten +from keras.layers import ConvLSTM2D +from matplotlib import pyplot + +# split a univariate dataset into train/test sets +def train_test_split(data, n_test): + return data[:-n_test], data[-n_test:] + +# transform list into supervised learning format +def series_to_supervised(data, n_in=1, n_out=1): + df = DataFrame(data) + cols = list() + # input sequence (t-n, ... t-1) + for i in range(n_in, 0, -1): + cols.append(df.shift(i)) + # forecast sequence (t, t+1, ... t+n) + for i in range(0, n_out): + cols.append(df.shift(-i)) + # put it all together + agg = concat(cols, axis=1) + # drop rows with NaN values + agg.dropna(inplace=True) + return agg.values + +# root mean squared error or rmse +def measure_rmse(actual, predicted): + return sqrt(mean_squared_error(actual, predicted)) + +# difference dataset +def difference(data, interval): + return [data[i] - data[i - interval] for i in range(interval, len(data))] + +# fit a model +def model_fit(train, config): + # unpack config + n_seq, n_steps, n_filters, n_kernel, n_nodes, n_epochs, n_batch = config + n_input = n_seq * n_steps + # prepare data + data = series_to_supervised(train, n_in=n_input) + train_x, train_y = data[:, :-1], data[:, -1] + train_x = train_x.reshape((train_x.shape[0], n_seq, 1, n_steps, 1)) + # define model + model = Sequential() + model.add(ConvLSTM2D(filters=n_filters, kernel_size=(1,n_kernel), activation='relu', input_shape=(n_seq, 1, n_steps, 1))) + model.add(Flatten()) + model.add(Dense(n_nodes, activation='relu')) + model.add(Dense(1)) + model.compile(loss='mse', optimizer='adam') + # fit + model.fit(train_x, train_y, epochs=n_epochs, batch_size=n_batch, verbose=0) + return model + +# forecast with a pre-fit model +def model_predict(model, history, config): + # unpack config + n_seq, n_steps, _, _, _, _, _ = config + n_input = n_seq * n_steps + # prepare data + x_input = array(history[-n_input:]).reshape((1, n_seq, 1, n_steps, 1)) + # forecast + yhat = model.predict(x_input, verbose=0) + return yhat[0] + +# walk-forward validation for univariate data +def walk_forward_validation(data, n_test, cfg): + predictions = list() + # split dataset + train, test = train_test_split(data, n_test) + # fit model + model = model_fit(train, cfg) + # seed history with training dataset + history = [x for x in train] + # step over each time-step in the test set + for i in range(len(test)): + # fit model and make forecast for history + yhat = model_predict(model, history, cfg) + # store forecast in list of predictions + predictions.append(yhat) + # add actual observation to history for the next loop + history.append(test[i]) + # estimate prediction error + error = measure_rmse(test, predictions) + print(' > %.3f' % error) + return error + +# repeat evaluation of a config +def repeat_evaluate(data, config, n_test, n_repeats=30): + # fit and evaluate the model n times + scores = [walk_forward_validation(data, n_test, config) for _ in range(n_repeats)] + return scores + +# summarize model performance +def summarize_scores(name, scores): + # print a summary + scores_m, score_std = mean(scores), std(scores) + print('%s: %.3f RMSE (+/- %.3f)' % (name, scores_m, score_std)) + # box and whisker plot + pyplot.boxplot(scores) + pyplot.show() + +series = read_csv('monthly-car-sales.csv', header=0, index_col=0) +data = series.values +# data split +n_test = 12 +# define config +config = [3, 12, 256, 3, 200, 200, 100] +# grid search +scores = repeat_evaluate(data, config, n_test) +# summarize scores +summarize_scores('convlstm', scores) +``` + +运行该示例为每次重复的模型评估打印 RMSE。 + +最终的平均 RMSE 报告在约 1,660 结束时,低于幼稚模型,但仍高于 SARIMA 模型。 + +这个结果可能与 CNN-LSTM 模型相当。该分数的标准偏差也非常大,表明所选配置可能不如独立 CNN 模型稳定。 + +``` + > 1825.246 + > 1862.674 + > 1684.313 + > 1310.448 + > 2109.668 + > 1507.912 + > 1431.118 + > 1442.692 + > 1400.548 + > 1732.381 + > 1523.824 + > 1611.898 + > 1805.970 + > 1616.015 + > 1649.466 + > 1521.884 + > 2025.655 + > 1622.886 + > 2536.448 + > 1526.532 + > 1866.631 + > 1562.625 + > 1491.386 + > 1506.270 + > 1843.981 + > 1653.084 + > 1650.430 + > 1291.353 + > 1558.616 + > 1653.231 +convlstm: 1660.840 RMSE (+/- 248.826) +``` + +还创建了一个盒子和胡须图,总结了 RMSE 分数的分布。 + +![Box and Whisker Plot of ConvLSTM RMSE Forecasting Car Sales](img/d727c0accd040bb84330b3c24dffd5d7.jpg) + +ConvLSTM RMSE 预测汽车销售的盒子和晶须图 + +## 扩展 + +本节列出了一些扩展您可能希望探索的教程的想法。 + +* **数据准备**。探索数据准备(例如规范化,标准化和/或差异化)是否可以列出任何模型的表现。 +* **网格搜索超参数**。对一个模型实施超参数的网格搜索,以查看是否可以进一步提升表现。 +* **学习曲线诊断**。创建一个模型的单一拟合并查看数据集的训练和验证分割的学习曲线,然后使用学习曲线的诊断来进一步调整模型超参数以提高模型表现。 +* **历史规模**。探索一种模型的不同数量的历史数据(滞后输入),以了解您是否可以进一步提高模型表现 +* **减少最终模型的差异**。探索一种或多种策略来减少其中一种神经网络模型的方差。 +* **前进期间更新**。探索作为前进验证的一部分重新拟合或更新神经网络模型是否可以进一步提高模型表现。 +* **更多参数化**。探索为一个模型添加更多模型参数化,例如使用其他层。 + +如果你探索任何这些扩展,我很想知道。 + +## 进一步阅读 + +如果您希望深入了解,本节将提供有关该主题的更多资源。 + +* [pandas.DataFrame.shift API](http://pandas-docs.github.io/pandas-docs-travis/generated/pandas.DataFrame.shift.html) +* [sklearn.metrics.mean_squared_error API](http://scikit-learn.org/stable/modules/generated/sklearn.metrics.mean_squared_error.html) +* [matplotlib.pyplot.boxplot API](https://matplotlib.org/api/_as_gen/matplotlib.pyplot.boxplot.html) +* [Keras 序列模型 API](https://keras.io/models/sequential/) + +## 摘要 + +在本教程中,您了解了如何开发一套用于单变量时间序列预测的深度学习模型。 + +具体来说,你学到了: + +* 如何使用前向验证开发一个强大的测试工具来评估神经网络模型的表现。 +* 如何开发和评估简单多层感知器和卷积神经网络的时间序列预测。 +* 如何开发和评估 LSTM,CNN-LSTM 和 ConvLSTM 神经网络模型用于时间序列预测。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/how-to-develop-lstm-models-for-multi-step-time-series-forecasting-of-household-power-consumption.md b/docs/dl-ts/how-to-develop-lstm-models-for-multi-step-time-series-forecasting-of-household-power-consumption.md new file mode 100644 index 0000000000000000000000000000000000000000..fa395e5ad08191b71484dbbf53cd999defbbd3ea --- /dev/null +++ b/docs/dl-ts/how-to-develop-lstm-models-for-multi-step-time-series-forecasting-of-household-power-consumption.md @@ -0,0 +1,1897 @@ +# 如何开发 LSTM 模型用于家庭用电的多步时间序列预测 + +> 原文: [https://machinelearningmastery.com/how-to-develop-lstm-models-for-multi-step-time-series-forecasting-of-household-power-consumption/](https://machinelearningmastery.com/how-to-develop-lstm-models-for-multi-step-time-series-forecasting-of-household-power-consumption/) + +鉴于智能电表的兴起以及太阳能电池板等发电技术的广泛采用,可提供大量的用电数据。 + +该数据代表了多变量时间序列的功率相关变量,而这些变量又可用于建模甚至预测未来的电力消耗。 + +与其他机器学习算法不同,长期短期记忆循环神经网络能够自动学习序列数据的特征,支持多变量数据,并且可以输出可用于多步预测的可变长度序列。 + +在本教程中,您将了解如何开发长期短期记忆循环神经网络,用于家庭功耗的多步时间序列预测。 + +完成本教程后,您将了解: + +* 如何开发和评估用于多步时间序列预测的单变量和多变量编码器 - 解码器 LSTM。 +* 如何开发和评估用于多步时间序列预测的 CNN-LSTM 编码器 - 解码器模型。 +* 如何开发和评估用于多步时间序列预测的 ConvLSTM 编码器 - 解码器模型。 + +让我们开始吧。 + +**注 1** :这篇文章摘录自:“[深度学习时间序列预测](https://machinelearningmastery.com/deep-learning-for-time-series-forecasting/)”。看一下,如果您想获得更多关于在时间序列预测问题上充分利用深度学习方法的分步教程。 + +**Note2** :这是一个相当高级的教程,如果你不熟悉 Python 中的时间序列预测,[从这里开始](https://machinelearningmastery.com/start-here/#timeseries)。如果您不熟悉时间序列的深度学习,[从这里开始](https://machinelearningmastery.com/start-here/#deep_learning_time_series)。如果你真的想开始使用时间序列的 LSTM,[从这里开始](https://machinelearningmastery.com/how-to-develop-lstm-models-for-time-series-forecasting/)。 + +![How to Develop LSTM Models for Multi-Step Time Series Forecasting of Household Power Consumption](https://3qeqpr26caki16dnhd19sv6by6v-wpengine.netdna-ssl.com/wp-content/uploads/2018/10/How-to-Develop-LSTM-Models-for-Multi-Step-Time-Series-Forecasting-of-Household-Power-Consumption.jpg) + +如何开发 LSTM 模型的多步时间序列预测家庭用电量 +照片由 [Ian Muttoo](https://www.flickr.com/photos/imuttoo/4257813689/) ,保留一些权利。 + +## 教程概述 + +本教程分为九个部分;他们是: + +1. 问题描述 +2. 加载并准备数据集 +3. 模型评估 +4. 用于多步预测的 LSTM +5. 具有单变量输入和向量输出的 LSTM 模型 +6. 具有单变量输入的编码器 - 解码器 LSTM 模型 +7. 具有多变量输入的编码器 - 解码器 LSTM 模型 +8. 具有单变量输入的 CNN-LSTM 编码器 - 解码器模型 +9. 具有单变量输入的 ConvLSTM 编码器 - 解码器模型 + +### Python 环境 + +本教程假设您安装了 Python SciPy 环境,理想情况下使用 Python 3。 + +您必须安装带有 TensorFlow 或 Theano 后端的 Keras(2.2 或更高版本)。 + +本教程还假设您安装了 scikit-learn,Pandas,NumPy 和 Matplotlib。 + +如果您需要有关环境的帮助,请参阅本教程: + +* [如何为机器学习和深度学习设置 Python 环境](https://machinelearningmastery.com/setup-python-environment-machine-learning-deep-learning-anaconda/) + +本教程不需要 GPU,但您可以在 Amazon Web Services 上以低成本方式访问 GPU。在本教程中学习如何: + +* [如何设置亚马逊 AWS EC2 GPU 以训练 Keras 深度学习模型](https://machinelearningmastery.com/develop-evaluate-large-deep-learning-models-keras-amazon-web-services/) + +让我们潜入。 + +## 问题描述 + +'[家庭用电量](https://archive.ics.uci.edu/ml/datasets/individual+household+electric+power+consumption)'数据集是一个多变量时间序列数据集,描述了四年内单个家庭的用电量。 + +有关此数据集的更多信息,请参阅帖子: + +* [如何加载和探索家庭用电数据](https://machinelearningmastery.com/how-to-load-and-explore-household-electricity-usage-data/) + +该数据是在 2006 年 12 月至 2010 年 11 月之间收集的,并且每分钟收集家庭内的能耗观察结果。 + +它是一个多变量系列,由七个变量组成(除日期和时间外);他们是: + +* **global_active_power** :家庭消耗的总有功功率(千瓦)。 +* **global_reactive_power** :家庭消耗的总无功功率(千瓦)。 +* **电压**:平均电压(伏特)。 +* **global_intensity** :平均电流强度(安培)。 +* **sub_metering_1** :厨房的有功电能(瓦特小时的有功电能)。 +* **sub_metering_2** :用于洗衣的有功能量(瓦特小时的有功电能)。 +* **sub_metering_3** :气候控制系统的有功电能(瓦特小时的有功电能)。 + +有功和无功电能参考[交流电](https://en.wikipedia.org/wiki/AC_power)的技术细节。 + +可以通过从总活动能量中减去三个定义的子计量变量的总和来创建第四个子计量变量,如下所示: + +``` +sub_metering_remainder = (global_active_power * 1000 / 60) - (sub_metering_1 + sub_metering_2 + sub_metering_3) +``` + +## 加载并准备数据集 + +数据集可以从 UCI 机器学习库下载为单个 20 兆字节的.zip 文件: + +* [household_power_consumption.zip](https://archive.ics.uci.edu/ml/machine-learning-databases/00235/household_power_consumption.zip) + +下载数据集并将其解压缩到当前工作目录中。您现在将拥有大约 127 兆字节的文件“ _household_power_consumption.txt_ ”并包含所有观察结果。 + +我们可以使用 _read_csv()_ 函数来加载数据,并将前两列合并到一个日期时间列中,我们可以将其用作索引。 + +``` +# load all data +dataset = read_csv('household_power_consumption.txt', sep=';', header=0, low_memory=False, infer_datetime_format=True, parse_dates={'datetime':[0,1]}, index_col=['datetime']) +``` + +接下来,我们可以用'_ 标记所有[缺失值](https://machinelearningmastery.com/handle-missing-timesteps-sequence-prediction-problems-python/)?_ '具有 _NaN_ 值的字符,这是一个浮点数。 + +这将允许我们将数据作为一个浮点值数组而不是混合类型(效率较低)。 + +``` +# mark all missing values +dataset.replace('?', nan, inplace=True) +# make dataset numeric +dataset = dataset.astype('float32') +``` + +我们还需要填写缺失值,因为它们已被标记。 + +一种非常简单的方法是从前一天的同一时间复制观察。我们可以在一个名为 _fill_missing()_ 的函数中实现它,该函数将从 24 小时前获取数据的 NumPy 数组并复制值。 + +``` +# fill missing values with a value at the same time one day ago +def fill_missing(values): + one_day = 60 * 24 + for row in range(values.shape[0]): + for col in range(values.shape[1]): + if isnan(values[row, col]): + values[row, col] = values[row - one_day, col] +``` + +我们可以将此函数直接应用于 DataFrame 中的数据。 + +``` +# fill missing +fill_missing(dataset.values) +``` + +现在,我们可以使用上一节中的计算创建一个包含剩余子计量的新列。 + +``` +# add a column for for the remainder of sub metering +values = dataset.values +dataset['sub_metering_4'] = (values[:,0] * 1000 / 60) - (values[:,4] + values[:,5] + values[:,6]) +``` + +我们现在可以将清理后的数据集版本保存到新文件中;在这种情况下,我们只需将文件扩展名更改为.csv,并将数据集保存为“ _household_power_consumption.csv_ ”。 + +``` +# save updated dataset +dataset.to_csv('household_power_consumption.csv') +``` + +将所有这些结合在一起,下面列出了加载,清理和保存数据集的完整示例。 + +``` +# load and clean-up data +from numpy import nan +from numpy import isnan +from pandas import read_csv +from pandas import to_numeric + +# fill missing values with a value at the same time one day ago +def fill_missing(values): + one_day = 60 * 24 + for row in range(values.shape[0]): + for col in range(values.shape[1]): + if isnan(values[row, col]): + values[row, col] = values[row - one_day, col] + +# load all data +dataset = read_csv('household_power_consumption.txt', sep=';', header=0, low_memory=False, infer_datetime_format=True, parse_dates={'datetime':[0,1]}, index_col=['datetime']) +# mark all missing values +dataset.replace('?', nan, inplace=True) +# make dataset numeric +dataset = dataset.astype('float32') +# fill missing +fill_missing(dataset.values) +# add a column for for the remainder of sub metering +values = dataset.values +dataset['sub_metering_4'] = (values[:,0] * 1000 / 60) - (values[:,4] + values[:,5] + values[:,6]) +# save updated dataset +dataset.to_csv('household_power_consumption.csv') +``` + +运行该示例将创建新文件' _household_power_consumption.csv_ ',我们可以将其用作建模项目的起点。 + +## 模型评估 + +在本节中,我们将考虑如何开发和评估家庭电力数据集的预测模型。 + +本节分为四个部分;他们是: + +1. 问题框架 +2. 评估指标 +3. 训练和测试集 +4. 前瞻性验证 + +### 问题框架 + +有许多方法可以利用和探索家庭用电量数据集。 + +在本教程中,我们将使用这些数据来探索一个非常具体的问题;那是: + +> 鉴于最近的耗电量,未来一周的预期耗电量是多少? + +这要求预测模型预测未来七天每天的总有功功率。 + +从技术上讲,考虑到多个预测步骤,这个问题的框架被称为多步骤时间序列预测问题。利用多个输入变量的模型可以称为多变量多步时间序列预测模型。 + +这种类型的模型在规划支出方面可能有助于家庭。在供应方面,它也可能有助于规划特定家庭的电力需求。 + +数据集的这种框架还表明,将每分钟功耗的观察结果下采样到每日总数是有用的。这不是必需的,但考虑到我们对每天的总功率感兴趣,这是有道理的。 + +我们可以使用 pandas DataFrame 上的 [resample()函数](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.resample.html)轻松实现这一点。使用参数' _D_ '调用此函数允许按日期时间索引的加载数据按天分组([查看所有偏移别名](http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases))。然后,我们可以计算每天所有观测值的总和,并为八个变量中的每一个创建每日耗电量数据的新数据集。 + +下面列出了完整的示例。 + +``` +# resample minute data to total for each day +from pandas import read_csv +# load the new file +dataset = read_csv('household_power_consumption.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +# resample data to daily +daily_groups = dataset.resample('D') +daily_data = daily_groups.sum() +# summarize +print(daily_data.shape) +print(daily_data.head()) +# save +daily_data.to_csv('household_power_consumption_days.csv') +``` + +运行该示例将创建一个新的每日总功耗数据集,并将结果保存到名为“ _household_power_consumption_days.csv_ ”的单独文件中。 + +我们可以将其用作数据集,用于拟合和评估所选问题框架的预测模型。 + +### 评估指标 + +预测将包含七个值,一个用于一周中的每一天。 + +多步预测问题通常分别评估每个预测时间步长。这有助于以下几个原因: + +* 在特定提前期评论技能(例如+1 天 vs +3 天)。 +* 在不同的交付时间基于他们的技能对比模型(例如,在+1 天的模型和在日期+5 的模型良好的模型)。 + +总功率的单位是千瓦,并且具有也在相同单位的误差度量将是有用的。均方根误差(RMSE)和平均绝对误差(MAE)都符合这个要求,尽管 RMSE 更常用,将在本教程中采用。与 MAE 不同,RMSE 更能预测预测误差。 + +此问题的表现指标是从第 1 天到第 7 天的每个提前期的 RMSE。 + +作为捷径,使用单个分数总结模型的表现以帮助模型选择可能是有用的。 + +可以使用的一个可能的分数是所有预测天数的 RMSE。 + +下面的函数 _evaluate_forecasts()_ 将实现此行为并基于多个七天预测返回模型的表现。 + +``` +# evaluate one or more weekly forecasts against expected values +def evaluate_forecasts(actual, predicted): + scores = list() + # calculate an RMSE score for each day + for i in range(actual.shape[1]): + # calculate mse + mse = mean_squared_error(actual[:, i], predicted[:, i]) + # calculate rmse + rmse = sqrt(mse) + # store + scores.append(rmse) + # calculate overall RMSE + s = 0 + for row in range(actual.shape[0]): + for col in range(actual.shape[1]): + s += (actual[row, col] - predicted[row, col])**2 + score = sqrt(s / (actual.shape[0] * actual.shape[1])) + return score, scores +``` + +运行该函数将首先返回整个 RMSE,无论白天,然后每天返回一系列 RMSE 分数。 + +### 训练和测试集 + +我们将使用前三年的数据来训练预测模型和评估模型的最后一年。 + +给定数据集中的数据将分为标准周。这些是从周日开始到周六结束的周。 + +这是使用所选模型框架的现实且有用的方法,其中可以预测未来一周的功耗。它也有助于建模,其中模型可用于预测特定日期(例如星期三)或整个序列。 + +我们将数据拆分为标准周,从测试数据集向后工作。 + +数据的最后一年是 2010 年,2010 年的第一个星期日是 1 月 3 日。数据于 2010 年 11 月中旬结束,数据中最接近的最后一个星期六是 11 月 20 日。这给出了 46 周的测试数据。 + +下面提供了测试数据集的每日数据的第一行和最后一行以供确认。 + +``` +2010-01-03,2083.4539999999984,191.61000000000055,350992.12000000034,8703.600000000033,3842.0,4920.0,10074.0,15888.233355799992 +... +2010-11-20,2197.006000000004,153.76800000000028,346475.9999999998,9320.20000000002,4367.0,2947.0,11433.0,17869.76663959999 +``` + +每日数据从 2006 年底开始。 + +数据集中的第一个星期日是 12 月 17 日,这是第二行数据。 + +将数据组织到标准周内为训练预测模型提供了 159 个完整的标准周。 + +``` +2006-12-17,3390.46,226.0059999999994,345725.32000000024,14398.59999999998,2033.0,4187.0,13341.0,36946.66673200004 +... +2010-01-02,1309.2679999999998,199.54600000000016,352332.8399999997,5489.7999999999865,801.0,298.0,6425.0,14297.133406600002 +``` + +下面的函数 _split_dataset()_ 将每日数据拆分为训练集和测试集,并将每个数据组织成标准周。 + +使用特定行偏移来使用数据集的知识来分割数据。然后使用 NumPy [split()函数](https://docs.scipy.org/doc/numpy/reference/generated/numpy.split.html)将分割数据集组织成每周数据。 + +``` +# split a univariate dataset into train/test sets +def split_dataset(data): + # split into standard weeks + train, test = data[1:-328], data[-328:-6] + # restructure into windows of weekly data + train = array(split(train, len(train)/7)) + test = array(split(test, len(test)/7)) + return train, test +``` + +我们可以通过加载每日数据集并打印列车和测试集的第一行和最后一行数据来测试此功能,以确认它们符合上述预期。 + +完整的代码示例如下所示。 + +``` +# split into standard weeks +from numpy import split +from numpy import array +from pandas import read_csv + +# split a univariate dataset into train/test sets +def split_dataset(data): + # split into standard weeks + train, test = data[1:-328], data[-328:-6] + # restructure into windows of weekly data + train = array(split(train, len(train)/7)) + test = array(split(test, len(test)/7)) + return train, test + +# load the new file +dataset = read_csv('household_power_consumption_days.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +train, test = split_dataset(dataset.values) +# validate train data +print(train.shape) +print(train[0, 0, 0], train[-1, -1, 0]) +# validate test +print(test.shape) +print(test[0, 0, 0], test[-1, -1, 0]) +``` + +运行该示例表明,列车数据集确实有 159 周的数据,而测试数据集有 46 周。 + +我们可以看到,第一行和最后一行的列车和测试数据集的总有效功率与我们定义为每组标准周界限的特定日期的数据相匹配。 + +``` +(159, 7, 8) +3390.46 1309.2679999999998 +(46, 7, 8) +2083.4539999999984 2197.006000000004 +``` + +### 前瞻性验证 + +将使用称为[前进验证](https://machinelearningmastery.com/backtest-machine-learning-models-time-series-forecasting/)的方案评估模型。 + +这是需要模型进行一周预测的地方,然后该模型的实际数据可用于模型,以便它可以用作在随后一周进行预测的基础。这对于如何在实践中使用模型以及对模型有益而使其能够利用最佳可用数据都是现实的。 + +我们可以通过分离输入数据和输出/预测数据来证明这一点。 + +``` +Input, Predict +[Week1] Week2 +[Week1 + Week2] Week3 +[Week1 + Week2 + Week3] Week4 +... +``` + +评估该数据集上的预测模型的前瞻性验证方法在下面提供名为 _evaluate_model()_。 + +标准周格式的训练和测试数据集作为参数提供给函数。提供了另一个参数 n_input,用于定义模型将用作输入以进行预测的先前观察的数量。 + +调用两个新函数:一个用于根据称为 _build_model()_ 的训练数据构建模型,另一个用于使用该模型对每个新标准周进行预测,称为 _forecast()_。这些将在后续章节中介绍。 + +我们正在使用神经网络,因此,它们通常很难训练,但很快就能进行评估。这意味着模型的首选用法是在历史数据上构建一次,并使用它们来预测前向验证的每个步骤。模型在评估期间是静态的(即未更新)。 + +这与其他模型不同,这些模型可以更快地进行训练,在新数据可用时,可以重新拟合或更新模型的每个步骤。有了足够的资源,就可以通过这种方式使用神经网络,但在本教程中我们不会这样做。 + +下面列出了完整的 _evaluate_model()_ 函数。 + +``` +# evaluate a single model +def evaluate_model(train, test, n_input): + # fit model + model = build_model(train, n_input) + # history is a list of weekly data + history = [x for x in train] + # walk-forward validation over each week + predictions = list() + for i in range(len(test)): + # predict the week + yhat_sequence = forecast(model, history, n_input) + # store the predictions + predictions.append(yhat_sequence) + # get real observation and add to history for predicting the next week + history.append(test[i, :]) + # evaluate predictions days for each week + predictions = array(predictions) + score, scores = evaluate_forecasts(test[:, :, 0], predictions) + return score, scores +``` + +一旦我们对模型进行评估,我们就可以总结表现。 + +以下名为 _summarize_scores()_ 的函数将模型的表现显示为单行,以便与其他模型进行比较。 + +``` +# summarize scores +def summarize_scores(name, score, scores): + s_scores = ', '.join(['%.1f' % s for s in scores]) + print('%s: [%.3f] %s' % (name, score, s_scores)) +``` + +我们现在已经开始评估数据集上的预测模型的所有元素。 + +## 用于多步预测的 LSTM + +循环神经网络或 RNN 专门用于工作,学习和预测序列数据。 + +循环神经网络是神经网络,其中来自一个时间步长的网络输出在随后的时间步骤中被提供作为输入。这允许模型基于当前时间步长的输入和对先前时间步骤中输出的直接知识来决定预测什么。 + +也许最成功和最广泛使用的 RNN 是长期短期记忆网络,或简称 LSTM。它之所以成功,是因为它克服了训练复现神经网络所带来的挑战,从而产生了稳定的模型。除了利用先前时间步的输出的循环连接之外,LSTM 还具有内部存储器,其操作类似于局部变量,允许它们在输入序列上累积状态。 + +有关 Recurrent Neural Networks 的更多信息,请参阅帖子: + +* [深度学习的循环神经网络崩溃课程](https://machinelearningmastery.com/crash-course-recurrent-neural-networks-deep-learning/) + +有关长期短期内存网络的更多信息,请参阅帖子: + +* [专家对长短期记忆网络的简要介绍](https://machinelearningmastery.com/gentle-introduction-long-short-term-memory-networks-experts/) + +LSTM 在多步时间序列预测方面具有许多优势;他们是: + +* **序列的原生支持**。 LSTM 是一种循环网络,因此设计用于将序列数据作为输入,不像其他模型,其中滞后观察必须作为输入特征呈现。 +* **多变量输入**。 LSTM 直接支持多变量输入的多个并行输入序列,不同于其中多变量输入以平面结构呈现的其他模型。 +* **向量输出**。与其他神经网络一样,LSTM 能够将输入数据直接映射到可以表示多个输出时间步长的输出向量。 + +此外,已经开发了专门设计用于进行多步序列预测的专用架构,通常称为序列到序列预测,或简称为 seq2seq。这很有用,因为多步时间序列预测是一种 seq2seq 预测。 + +针对 seq2seq 问题设计的循环神经网络架构的示例是编码器 - 解码器 LSTM。 + +编码器 - 解码器 LSTM 是由两个子模型组成的模型:一个称为编码器,其读取输入序列并将其压缩为固定长度的内部表示,以及称为解码器的输出模型,其解释内部表示并使用它预测输出序列。 + +事实证明,序列预测的编码器 - 解码器方法比直接输出向量更有效,并且是首选方法。 + +通常,已经发现 LSTM 在自回归类型问题上不是非常有效。这些是预测下一个时间步长是最近时间步长的函数。 + +有关此问题的更多信息,请参阅帖子: + +* [关于 LSTM 对时间序列预测的适用性](https://machinelearningmastery.com/suitability-long-short-term-memory-networks-time-series-forecasting/) + +一维卷积神经网络(CNN)已证明在自动学习输入序列的特征方面是有效的。 + +一种流行的方法是将 CNN 与 LSTM 组合,其中 CNN 作为编码器来学习输入数据的子序列的特征,这些子序列作为 LSTM 的时间步长提供。该架构称为 [CNN-LSTM](https://machinelearningmastery.com/cnn-long-short-term-memory-networks/) 。 + +有关此体系结构的更多信息,请参阅帖子: + +* [CNN 长短期记忆网络](https://machinelearningmastery.com/cnn-long-short-term-memory-networks/) + +CNN LSTM 架构的功率变化是 ConvLSTM,它直接在 LSTM 的单元内使用输入子序列的卷积读取。事实证明,这种方法对于时间序列分类非常有效,并且可以适用于多步骤时间序列预测。 + +在本教程中,我们将探索一套用于多步时间序列预测的 LSTM 架构。具体来说,我们将看看如何开发以下模型: + +* **LSTM** 模型,带有向量输出,用于多变量预测,具有单变量输入数据。 +* **编码器 - 解码器 LSTM** 模型,用于使用单变量输入数据进行多步预测。 +* **编码器 - 解码器 LSTM** 模型,用于多变量输入数据的多步预测。 +* **CNN-LSTM 编码器 - 解码器**模型,用于使用单变量输入数据进行多步预测。 +* **ConvLSTM 编码器 - 解码器**模型,用于使用单变量输入数据进行多步预测。 + +如果您不熟悉使用 LSTM 进行时间序列预测,我强烈推荐这篇文章: + +* [如何为时间序列预测开发 LSTM 模型](https://machinelearningmastery.com/how-to-develop-lstm-models-for-time-series-forecasting/) + +将在家庭电力预测问题上开发和演示这些模型。如果一个模型比一个天真的模型更好地实现表现,那么该模型被认为是技术性的,在 7 天的预测中,该模型的总体 RMSE 约为 465 千瓦。 + +我们不会专注于调整这些模型以实现最佳表现;相反,与天真的预测相比,我们将停止熟练的模型。选择的结构和超参数通过一些试验和错误来选择。分数应仅作为示例,而不是研究问题的最佳模型或配置。 + +鉴于模型的随机性,[良好实践](https://machinelearningmastery.com/evaluate-skill-deep-learning-models/)是多次评估给定模型并报告测试数据集的平均表现。为了简洁起见并保持代码简单,我们将在本教程中介绍单行模型。 + +我们无法知道哪种方法对于给定的多步预测问题最有效。探索一套方法是个好主意,以便发现哪些方法最适合您的特定数据集。 + +## 具有单变量输入和向量输出的 LSTM 模型 + +我们将从开发一个简单或香草 LSTM 模型开始,该模型读取每日总功耗的天数,并预测每日功耗的下一个标准周的向量输出。 + +这将为后续章节中开发的更精细的模型奠定基础。 + +用作输入的前几天定义了 LSTM 将读取并学习提取特征的数据的一维(1D)子序列。关于此输入的大小和性质的一些想法包括: + +* 所有前几天,最多数年的数据。 +* 前 7 天。 +* 前两周。 +* 前一个月。 +* 前一年。 +* 前一周和一周从一年前预测。 + +没有正确的答案;相反,可以测试每种方法和更多方法,并且可以使用模型的表现来选择导致最佳模型表现的输入的性质。 + +这些选择定义了一些东西: + +* 如何准备训练数据以适应模型。 +* 如何准备测试数据以评估模型。 +* 如何使用该模型在未来使用最终模型进行预测。 + +一个好的起点是使用前七天。 + +LSTM 模型期望数据具有以下形状: + +``` +[samples, timesteps, features] +``` + +一个样本将包含七个时间步骤,其中一个功能用于每日总耗电量的七天。 + +训练数据集有 159 周的数据,因此训练数据集的形状为: + +``` +[159, 7, 1] +``` + +这是一个好的开始。此格式的数据将使用先前的标准周来预测下一个标准周。一个问题是训练神经网络的 159 个实例并不是很多。 + +创建更多训练数据的方法是在训练期间更改问题,以预测前七天的下一个七天,无论标准周。 + +这仅影响训练数据,并且测试问题保持不变:预测给定前一标准周的下一个标准周的每日功耗。 + +这将需要一点准备训练数据。 + +训练数据在标准周内提供八个变量,特别是形状[ _159,7,8_ ]。第一步是展平数据,以便我们有八个时间序列序列。 + +``` +# flatten data +data = train.reshape((train.shape[0]*train.shape[1], train.shape[2])) +``` + +然后,我们需要迭代时间步骤并将数据划分为重叠窗口;每次迭代沿着一个时间步移动并预测随后的七天。 + +例如: + +``` +Input, Output +[d01, d02, d03, d04, d05, d06, d07], [d08, d09, d10, d11, d12, d13, d14] +[d02, d03, d04, d05, d06, d07, d08], [d09, d10, d11, d12, d13, d14, d15] +... +``` + +我们可以通过跟踪输入和输出的开始和结束索引来实现这一点,因为我们在时间步长方面迭代展平数据的长度。 + +我们也可以通过参数化输入和输出的数量来实现这一点(例如 _n_input_ , _n_out_ ),这样您就可以尝试不同的值或根据自己的问题进行调整。 + +下面是一个名为 _to_supervised()_ 的函数,它采用周(历史)列表和用作输入和输出的时间步数,并以重叠移动窗口格式返回数据。 + +``` +# convert history into inputs and outputs +def to_supervised(train, n_input, n_out=7): + # flatten data + data = train.reshape((train.shape[0]*train.shape[1], train.shape[2])) + X, y = list(), list() + in_start = 0 + # step over the entire history one time step at a time + for _ in range(len(data)): + # define the end of the input sequence + in_end = in_start + n_input + out_end = in_end + n_out + # ensure we have enough data for this instance + if out_end < len(data): + x_input = data[in_start:in_end, 0] + x_input = x_input.reshape((len(x_input), 1)) + X.append(x_input) + y.append(data[in_end:out_end, 0]) + # move along one time step + in_start += 1 + return array(X), array(y) +``` + +当我们在整个训练数据集上运行此函数时,我们将 159 个样本转换为 1,099 个;具体地,变换的数据集具有形状 _X = [1099,7,1]_ 和 _y = [1099,7]。_ + +接下来,我们可以在训练数据上定义和拟合 LSTM 模型。 + +这个多步骤时间序列预测问题是一个自回归。这意味着它可能最好建模,其中接下来的七天是先前时间步骤的观测功能。这和相对少量的数据意味着需要一个小型号。 + +我们将开发一个具有 200 个单元的单个隐藏 LSTM 层的模型。隐藏层中的单元数与输入序列中的时间步数无关。 LSTM 层之后是具有 200 个节点的完全连接层,其将解释 LSTM 层学习的特征。最后,输出层将直接预测具有七个元素的向量,输出序列中每天一个元素。 + +我们将使用均方误差损失函数,因为它与我们选择的 RMSE 误差度量非常匹配。我们将使用随机梯度下降的高效 [Adam 实现](https://machinelearningmastery.com/adam-optimization-algorithm-for-deep-learning/),并将模型拟合 70 个时期,批量大小为 16。 + +小批量大小和算法的随机性意味着相同的模型将在每次训练时学习输入到输出的略微不同的映射。这意味着评估模型时结果可能会有所不同。您可以尝试多次运行模型并计算模型表现的平均值。 + +下面的 _build_model()_ 准备训练数据,定义模型,并将模型拟合到训练数据上,使拟合模型准备好进行预测。 + +``` +# train the model +def build_model(train, n_input): + # prepare data + train_x, train_y = to_supervised(train, n_input) + # define parameters + verbose, epochs, batch_size = 0, 70, 16 + n_timesteps, n_features, n_outputs = train_x.shape[1], train_x.shape[2], train_y.shape[1] + # define model + model = Sequential() + model.add(LSTM(200, activation='relu', input_shape=(n_timesteps, n_features))) + model.add(Dense(100, activation='relu')) + model.add(Dense(n_outputs)) + model.compile(loss='mse', optimizer='adam') + # fit network + model.fit(train_x, train_y, epochs=epochs, batch_size=batch_size, verbose=verbose) + return model +``` + +现在我们知道如何拟合模型,我们可以看看如何使用模型进行预测。 + +通常,模型期望数据在进行预测时具有相同的三维形状。 + +在这种情况下,输入模式的预期形状是一个样本,每天消耗的一个功能的七天: + +``` +[1, 7, 1] +``` + +在对测试集进行预测时以及在将来使用最终模型进行预测时,数据必须具有此形状。如果在输入天数为 14 时更改数字,则必须相应更改训练数据的形状和进行预测时新样本的形状,以便有 14 个时间步长。在使用模型时,您必须继续使用它。 + +我们正在使用前向验证来评估模型,如上一节中所述。 + +这意味着我们有前一周的观察结果,以预测下周。这些被收集到一系列称为历史的标准周。 + +为了预测下一个标准周,我们需要检索观察的最后几天。与训练数据一样,我们必须首先展平历史数据以删除每周结构,以便最终得到八个平行时间序列。 + +``` +# flatten data +data = data.reshape((data.shape[0]*data.shape[1], data.shape[2])) +``` + +接下来,我们需要检索每日总功耗的最后七天(特征索引 0)。 + +我们将对训练数据进行参数化,以便将来可以修改模型输入的前几天的数量。 + +``` +# retrieve last observations for input data +input_x = data[-n_input:, 0] +``` + +接下来,我们将输入重塑为预期的三维结构。 + +``` +# reshape into [1, n_input, 1] +input_x = input_x.reshape((1, len(input_x), 1)) +``` + +然后,我们使用拟合模型和输入数据进行预测,并检索七天输出的向量。 + +``` +# forecast the next week +yhat = model.predict(input_x, verbose=0) +# we only want the vector forecast +yhat = yhat[0] +``` + +下面的 _forecast()_ 函数实现了这个功能,并将模型拟合到训练数据集,到目前为止观察到的数据历史以及模型预期的输入时间步数。 + +``` +# make a forecast +def forecast(model, history, n_input): + # flatten data + data = array(history) + data = data.reshape((data.shape[0]*data.shape[1], data.shape[2])) + # retrieve last observations for input data + input_x = data[-n_input:, 0] + # reshape into [1, n_input, 1] + input_x = input_x.reshape((1, len(input_x), 1)) + # forecast the next week + yhat = model.predict(input_x, verbose=0) + # we only want the vector forecast + yhat = yhat[0] + return yhat +``` + +而已;我们现在拥有了所需的一切,我们需要通过 LSTM 模型对单日数据集的每日总功耗进行多步时间序列预测。 + +我们可以将所有这些结合在一起。下面列出了完整的示例。 + +``` +# univariate multi-step lstm +from math import sqrt +from numpy import split +from numpy import array +from pandas import read_csv +from sklearn.metrics import mean_squared_error +from matplotlib import pyplot +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import Flatten +from keras.layers import LSTM + +# split a univariate dataset into train/test sets +def split_dataset(data): + # split into standard weeks + train, test = data[1:-328], data[-328:-6] + # restructure into windows of weekly data + train = array(split(train, len(train)/7)) + test = array(split(test, len(test)/7)) + return train, test + +# evaluate one or more weekly forecasts against expected values +def evaluate_forecasts(actual, predicted): + scores = list() + # calculate an RMSE score for each day + for i in range(actual.shape[1]): + # calculate mse + mse = mean_squared_error(actual[:, i], predicted[:, i]) + # calculate rmse + rmse = sqrt(mse) + # store + scores.append(rmse) + # calculate overall RMSE + s = 0 + for row in range(actual.shape[0]): + for col in range(actual.shape[1]): + s += (actual[row, col] - predicted[row, col])**2 + score = sqrt(s / (actual.shape[0] * actual.shape[1])) + return score, scores + +# summarize scores +def summarize_scores(name, score, scores): + s_scores = ', '.join(['%.1f' % s for s in scores]) + print('%s: [%.3f] %s' % (name, score, s_scores)) + +# convert history into inputs and outputs +def to_supervised(train, n_input, n_out=7): + # flatten data + data = train.reshape((train.shape[0]*train.shape[1], train.shape[2])) + X, y = list(), list() + in_start = 0 + # step over the entire history one time step at a time + for _ in range(len(data)): + # define the end of the input sequence + in_end = in_start + n_input + out_end = in_end + n_out + # ensure we have enough data for this instance + if out_end < len(data): + x_input = data[in_start:in_end, 0] + x_input = x_input.reshape((len(x_input), 1)) + X.append(x_input) + y.append(data[in_end:out_end, 0]) + # move along one time step + in_start += 1 + return array(X), array(y) + +# train the model +def build_model(train, n_input): + # prepare data + train_x, train_y = to_supervised(train, n_input) + # define parameters + verbose, epochs, batch_size = 0, 70, 16 + n_timesteps, n_features, n_outputs = train_x.shape[1], train_x.shape[2], train_y.shape[1] + # define model + model = Sequential() + model.add(LSTM(200, activation='relu', input_shape=(n_timesteps, n_features))) + model.add(Dense(100, activation='relu')) + model.add(Dense(n_outputs)) + model.compile(loss='mse', optimizer='adam') + # fit network + model.fit(train_x, train_y, epochs=epochs, batch_size=batch_size, verbose=verbose) + return model + +# make a forecast +def forecast(model, history, n_input): + # flatten data + data = array(history) + data = data.reshape((data.shape[0]*data.shape[1], data.shape[2])) + # retrieve last observations for input data + input_x = data[-n_input:, 0] + # reshape into [1, n_input, 1] + input_x = input_x.reshape((1, len(input_x), 1)) + # forecast the next week + yhat = model.predict(input_x, verbose=0) + # we only want the vector forecast + yhat = yhat[0] + return yhat + +# evaluate a single model +def evaluate_model(train, test, n_input): + # fit model + model = build_model(train, n_input) + # history is a list of weekly data + history = [x for x in train] + # walk-forward validation over each week + predictions = list() + for i in range(len(test)): + # predict the week + yhat_sequence = forecast(model, history, n_input) + # store the predictions + predictions.append(yhat_sequence) + # get real observation and add to history for predicting the next week + history.append(test[i, :]) + # evaluate predictions days for each week + predictions = array(predictions) + score, scores = evaluate_forecasts(test[:, :, 0], predictions) + return score, scores + +# load the new file +dataset = read_csv('household_power_consumption_days.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +# split into train and test +train, test = split_dataset(dataset.values) +# evaluate model and get scores +n_input = 7 +score, scores = evaluate_model(train, test, n_input) +# summarize scores +summarize_scores('lstm', score, scores) +# plot scores +days = ['sun', 'mon', 'tue', 'wed', 'thr', 'fri', 'sat'] +pyplot.plot(days, scores, marker='o', label='lstm') +pyplot.show() +``` + +运行该示例适合并评估模型,在所有七天内打印整体 RMSE,以及每个提前期的每日 RMSE。 + +鉴于算法的随机性,您的具体结果可能会有所不同。您可能想尝试几次运行该示例。 + +我们可以看到,在这种情况下,与天真的预测相比,该模型是巧妙的,实现了大约 399 千瓦的总体 RMSE,小于 465 千瓦的天真模型。 + +``` +lstm: [399.456] 419.4, 422.1, 384.5, 395.1, 403.9, 317.7, 441.5 +``` + +还创建了每日 RMSE 的图。 + +该图显示,周二和周五可能比其他日子更容易预测,也许星期六在标准周结束时是最难预测的日子。 + +![Line Plot of RMSE per Day for Univariate LSTM with Vector Output and 7-day Inputs](img/f09677b6d292acb15ace82e901a6a3a0.jpg) + +具有向量输出和 7 天输入的单变量 LSTM 的每日 RMSE 线图 + +我们可以通过更改 _n_input_ 变量来增加用作 7 到 14 之间输入的前几天的数量。 + +``` +# evaluate model and get scores +n_input = 14 +``` + +使用此更改重新运行示例首先会打印模型表现的摘要。 + +具体结果可能有所不同;尝试运行几次这个例子。 + +在这种情况下,我们可以看到整体 RMSE 进一步下降到大约 370 千瓦,这表明进一步调整输入大小以及模型中节点的数量可能会带来更好的表现。 + +``` +lstm: [370.028] 387.4, 377.9, 334.0, 371.2, 367.1, 330.4, 415.1 +``` + +比较每日 RMSE 分数,我们看到一些更好,有些比使用七天输入更差。 + +这可以建议以某种方式使用两个不同大小的输入的益处,例如两种方法的集合或者可能是以不同方式读取训练数据的单个模型(例如,多头模型)。 + +![Line Plot of RMSE per Day for Univariate LSTM with Vector Output and 14-day Inputs](img/0aa87b8d263d3b7495062f0820b93896.jpg) + +具有向量输出和 14 天输入的单变量 LSTM 每日 RMSE 的线图 + +## 具有单变量输入的编码器 - 解码器 LSTM 模型 + +在本节中,我们可以更新 vanilla LSTM 以使用编码器 - 解码器模型。 + +这意味着模型不会直接输出向量序列。相反,该模型将包括两个子模型,即用于读取和编码输入序列的编码器,以及将读取编码输入序列并对输出序列中的每个元素进行一步预测的解码器。 + +差异是微妙的,因为实际上两种方法实际上都预测了序列输出。 + +重要的区别在于,在解码器中使用 LSTM 模型,允许它既知道序列中前一天的预测值,又在输出序列时累积内部状态。 + +让我们仔细看看这个模型是如何定义的。 + +和以前一样,我们定义了一个包含 200 个单位的 LSTM 隐藏层。这是解码器模型,它将读取输入序列并输出一个 200 元素向量(每个单元一个输出),用于捕获输入序列中的特征。我们将使用 14 天的总功耗作为输入。 + +``` +# define model +model = Sequential() +model.add(LSTM(200, activation='relu', input_shape=(n_timesteps, n_features))) +``` + +我们将使用一种易于在 Keras 中实现的简单编码器 - 解码器架构,它与 LSTM 自动编码器的架构有很多相似之处。 + +首先,输入序列的内部表示重复多次,输出序列中的每个时间步长一次。该序列的向量将被呈现给 LSTM 解码器。 + +``` +model.add(RepeatVector(7)) +``` + +然后,我们将解码器定义为具有 200 个单位的 LSTM 隐藏层。重要的是,解码器将输出整个序列,而不仅仅是输出序列末尾的输出,就像我们对编码器一样。这意味着 200 个单位中的每一个都将为七天中的每一天输出一个值,表示输出序列中每天预测的基础。 + +``` +model.add(LSTM(200, activation='relu', return_sequences=True)) +``` + +然后,我们将使用完全连接的层来解释最终输出层之前的输出序列中的每个时间步长。重要的是,输出层预测输出序列中的单个步骤,而不是一次七天, + +这意味着我们将使用应用于输出序列中每个步骤的相同层。这意味着将使用相同的完全连接的层和输出层来处理由解码器提供的每个时间步长。为此,我们将解释层和输出层包装在 [TimeDistributed 包装器](https://machinelearningmastery.com/timedistributed-layer-for-long-short-term-memory-networks-in-python/)中,该包装器允许包装层用于解码器的每个时间步长。 + +``` +model.add(TimeDistributed(Dense(100, activation='relu'))) +model.add(TimeDistributed(Dense(1))) +``` + +这允许 LSTM 解码器找出输出序列中的每个步骤所需的上下文以及包裹的密集层以分别解释每个时间步骤,同时重用相同的权重来执行解释。另一种方法是展平 LSTM 解码器创建的所有结构并直接输出向量。您可以尝试将其作为扩展程序来查看它的比较方式。 + +因此,网络输出具有与输入相同结构的三维向量,其尺寸为[_ 样本,时间步长,特征 _]。 + +只有一个功能,每日消耗的总功率,总有七个功能。因此,单个一周的预测将具有以下大小:[ _1,7,1_ ]。 + +因此,在训练模型时,我们必须重新构造输出数据( _y_ )以具有三维结构而不是[_ 样本的二维结构,特征 _]用于上一节。 + +``` +# reshape output into [samples, timesteps, features] +train_y = train_y.reshape((train_y.shape[0], train_y.shape[1], 1)) +``` + +我们可以将所有这些绑定到下面列出的更新的 _build_model()_ 函数中。 + +``` +# train the model +def build_model(train, n_input): + # prepare data + train_x, train_y = to_supervised(train, n_input) + # define parameters + verbose, epochs, batch_size = 0, 20, 16 + n_timesteps, n_features, n_outputs = train_x.shape[1], train_x.shape[2], train_y.shape[1] + # reshape output into [samples, timesteps, features] + train_y = train_y.reshape((train_y.shape[0], train_y.shape[1], 1)) + # define model + model = Sequential() + model.add(LSTM(200, activation='relu', input_shape=(n_timesteps, n_features))) + model.add(RepeatVector(n_outputs)) + model.add(LSTM(200, activation='relu', return_sequences=True)) + model.add(TimeDistributed(Dense(100, activation='relu'))) + model.add(TimeDistributed(Dense(1))) + model.compile(loss='mse', optimizer='adam') + # fit network + model.fit(train_x, train_y, epochs=epochs, batch_size=batch_size, verbose=verbose) + return model +``` + +下面列出了编码器 - 解码器模型的完整示例。 + +``` +# univariate multi-step encoder-decoder lstm +from math import sqrt +from numpy import split +from numpy import array +from pandas import read_csv +from sklearn.metrics import mean_squared_error +from matplotlib import pyplot +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import Flatten +from keras.layers import LSTM +from keras.layers import RepeatVector +from keras.layers import TimeDistributed + +# split a univariate dataset into train/test sets +def split_dataset(data): + # split into standard weeks + train, test = data[1:-328], data[-328:-6] + # restructure into windows of weekly data + train = array(split(train, len(train)/7)) + test = array(split(test, len(test)/7)) + return train, test + +# evaluate one or more weekly forecasts against expected values +def evaluate_forecasts(actual, predicted): + scores = list() + # calculate an RMSE score for each day + for i in range(actual.shape[1]): + # calculate mse + mse = mean_squared_error(actual[:, i], predicted[:, i]) + # calculate rmse + rmse = sqrt(mse) + # store + scores.append(rmse) + # calculate overall RMSE + s = 0 + for row in range(actual.shape[0]): + for col in range(actual.shape[1]): + s += (actual[row, col] - predicted[row, col])**2 + score = sqrt(s / (actual.shape[0] * actual.shape[1])) + return score, scores + +# summarize scores +def summarize_scores(name, score, scores): + s_scores = ', '.join(['%.1f' % s for s in scores]) + print('%s: [%.3f] %s' % (name, score, s_scores)) + +# convert history into inputs and outputs +def to_supervised(train, n_input, n_out=7): + # flatten data + data = train.reshape((train.shape[0]*train.shape[1], train.shape[2])) + X, y = list(), list() + in_start = 0 + # step over the entire history one time step at a time + for _ in range(len(data)): + # define the end of the input sequence + in_end = in_start + n_input + out_end = in_end + n_out + # ensure we have enough data for this instance + if out_end < len(data): + x_input = data[in_start:in_end, 0] + x_input = x_input.reshape((len(x_input), 1)) + X.append(x_input) + y.append(data[in_end:out_end, 0]) + # move along one time step + in_start += 1 + return array(X), array(y) + +# train the model +def build_model(train, n_input): + # prepare data + train_x, train_y = to_supervised(train, n_input) + # define parameters + verbose, epochs, batch_size = 0, 20, 16 + n_timesteps, n_features, n_outputs = train_x.shape[1], train_x.shape[2], train_y.shape[1] + # reshape output into [samples, timesteps, features] + train_y = train_y.reshape((train_y.shape[0], train_y.shape[1], 1)) + # define model + model = Sequential() + model.add(LSTM(200, activation='relu', input_shape=(n_timesteps, n_features))) + model.add(RepeatVector(n_outputs)) + model.add(LSTM(200, activation='relu', return_sequences=True)) + model.add(TimeDistributed(Dense(100, activation='relu'))) + model.add(TimeDistributed(Dense(1))) + model.compile(loss='mse', optimizer='adam') + # fit network + model.fit(train_x, train_y, epochs=epochs, batch_size=batch_size, verbose=verbose) + return model + +# make a forecast +def forecast(model, history, n_input): + # flatten data + data = array(history) + data = data.reshape((data.shape[0]*data.shape[1], data.shape[2])) + # retrieve last observations for input data + input_x = data[-n_input:, 0] + # reshape into [1, n_input, 1] + input_x = input_x.reshape((1, len(input_x), 1)) + # forecast the next week + yhat = model.predict(input_x, verbose=0) + # we only want the vector forecast + yhat = yhat[0] + return yhat + +# evaluate a single model +def evaluate_model(train, test, n_input): + # fit model + model = build_model(train, n_input) + # history is a list of weekly data + history = [x for x in train] + # walk-forward validation over each week + predictions = list() + for i in range(len(test)): + # predict the week + yhat_sequence = forecast(model, history, n_input) + # store the predictions + predictions.append(yhat_sequence) + # get real observation and add to history for predicting the next week + history.append(test[i, :]) + # evaluate predictions days for each week + predictions = array(predictions) + score, scores = evaluate_forecasts(test[:, :, 0], predictions) + return score, scores + +# load the new file +dataset = read_csv('household_power_consumption_days.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +# split into train and test +train, test = split_dataset(dataset.values) +# evaluate model and get scores +n_input = 14 +score, scores = evaluate_model(train, test, n_input) +# summarize scores +summarize_scores('lstm', score, scores) +# plot scores +days = ['sun', 'mon', 'tue', 'wed', 'thr', 'fri', 'sat'] +pyplot.plot(days, scores, marker='o', label='lstm') +pyplot.show() +``` + +运行该示例适合模型并总结测试数据集的表现。 + +鉴于算法的随机性,您的具体结果可能会有所不同。您可能想尝试几次运行该示例。 + +我们可以看到,在这种情况下,该模型非常巧妙,总体 RMSE 得分约为 372 千瓦。 + +``` +lstm: [372.595] 379.5, 399.8, 339.6, 372.2, 370.9, 309.9, 424.8 +``` + +还创建了每日 RMSE 的线图,显示了与上一节中看到的类似的错误模式。 + +![Line Plot of RMSE per Day for Univariate Encoder-Decoder LSTM with 14-day Inputs](img/581b1e054e46aaa80b4e5cd32dfcbef2.jpg) + +具有 14 天输入的单变量编码器 - 解码器 LSTM 每天 RMSE 的线图 + +## 具有多变量输入的编码器 - 解码器 LSTM 模型 + +在本节中,我们将更新上一节中开发的编码器 - 解码器 LSTM,以使用八个时间序列变量中的每一个来预测下一个标准周的每日总功耗。 + +我们将通过将每个一维时间序列作为单独的输入序列提供给模型来实现此目的。 + +LSTM 将依次创建每个输入序列的内部表示,其将由解码器一起解释。 + +使用多变量输入有助于那些输出序列是来自多个不同特征的先前时间步骤的观察的某些功能的问题,而不仅仅是(或包括)预测的特征。目前还不清楚功耗问题是否属于这种情况,但我们仍可以探索它。 + +首先,我们必须更新训练数据的准备工作,以包括所有八项功能,而不仅仅是每日消耗的一项功能。它需要单行更改: + +``` +X.append(data[in_start:in_end, :]) +``` + +下面列出了具有此更改的完整 _to_supervised()_ 功能。 + +``` +# convert history into inputs and outputs +def to_supervised(train, n_input, n_out=7): + # flatten data + data = train.reshape((train.shape[0]*train.shape[1], train.shape[2])) + X, y = list(), list() + in_start = 0 + # step over the entire history one time step at a time + for _ in range(len(data)): + # define the end of the input sequence + in_end = in_start + n_input + out_end = in_end + n_out + # ensure we have enough data for this instance + if out_end < len(data): + X.append(data[in_start:in_end, :]) + y.append(data[in_end:out_end, 0]) + # move along one time step + in_start += 1 + return array(X), array(y) +``` + +我们还必须使用拟合模型更新用于进行预测的函数,以使用先前时间步骤中的所有八个特征。 + +再次,另一个小变化: + +``` +# retrieve last observations for input data +input_x = data[-n_input:, :] +# reshape into [1, n_input, n] +input_x = input_x.reshape((1, input_x.shape[0], input_x.shape[1])) +``` + +下面列出了具有此更改的完整 _forecast()_ 函数: + +``` +# make a forecast +def forecast(model, history, n_input): + # flatten data + data = array(history) + data = data.reshape((data.shape[0]*data.shape[1], data.shape[2])) + # retrieve last observations for input data + input_x = data[-n_input:, :] + # reshape into [1, n_input, n] + input_x = input_x.reshape((1, input_x.shape[0], input_x.shape[1])) + # forecast the next week + yhat = model.predict(input_x, verbose=0) + # we only want the vector forecast + yhat = yhat[0] + return yhat +``` + +直接使用相同的模型架构和配置,尽管我们将训练时期的数量从 20 增加到 50,因为输入数据量增加了 8 倍。 + +下面列出了完整的示例。 + +``` +# multivariate multi-step encoder-decoder lstm +from math import sqrt +from numpy import split +from numpy import array +from pandas import read_csv +from sklearn.metrics import mean_squared_error +from matplotlib import pyplot +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import Flatten +from keras.layers import LSTM +from keras.layers import RepeatVector +from keras.layers import TimeDistributed + +# split a univariate dataset into train/test sets +def split_dataset(data): + # split into standard weeks + train, test = data[1:-328], data[-328:-6] + # restructure into windows of weekly data + train = array(split(train, len(train)/7)) + test = array(split(test, len(test)/7)) + return train, test + +# evaluate one or more weekly forecasts against expected values +def evaluate_forecasts(actual, predicted): + scores = list() + # calculate an RMSE score for each day + for i in range(actual.shape[1]): + # calculate mse + mse = mean_squared_error(actual[:, i], predicted[:, i]) + # calculate rmse + rmse = sqrt(mse) + # store + scores.append(rmse) + # calculate overall RMSE + s = 0 + for row in range(actual.shape[0]): + for col in range(actual.shape[1]): + s += (actual[row, col] - predicted[row, col])**2 + score = sqrt(s / (actual.shape[0] * actual.shape[1])) + return score, scores + +# summarize scores +def summarize_scores(name, score, scores): + s_scores = ', '.join(['%.1f' % s for s in scores]) + print('%s: [%.3f] %s' % (name, score, s_scores)) + +# convert history into inputs and outputs +def to_supervised(train, n_input, n_out=7): + # flatten data + data = train.reshape((train.shape[0]*train.shape[1], train.shape[2])) + X, y = list(), list() + in_start = 0 + # step over the entire history one time step at a time + for _ in range(len(data)): + # define the end of the input sequence + in_end = in_start + n_input + out_end = in_end + n_out + # ensure we have enough data for this instance + if out_end < len(data): + X.append(data[in_start:in_end, :]) + y.append(data[in_end:out_end, 0]) + # move along one time step + in_start += 1 + return array(X), array(y) + +# train the model +def build_model(train, n_input): + # prepare data + train_x, train_y = to_supervised(train, n_input) + # define parameters + verbose, epochs, batch_size = 0, 50, 16 + n_timesteps, n_features, n_outputs = train_x.shape[1], train_x.shape[2], train_y.shape[1] + # reshape output into [samples, timesteps, features] + train_y = train_y.reshape((train_y.shape[0], train_y.shape[1], 1)) + # define model + model = Sequential() + model.add(LSTM(200, activation='relu', input_shape=(n_timesteps, n_features))) + model.add(RepeatVector(n_outputs)) + model.add(LSTM(200, activation='relu', return_sequences=True)) + model.add(TimeDistributed(Dense(100, activation='relu'))) + model.add(TimeDistributed(Dense(1))) + model.compile(loss='mse', optimizer='adam') + # fit network + model.fit(train_x, train_y, epochs=epochs, batch_size=batch_size, verbose=verbose) + return model + +# make a forecast +def forecast(model, history, n_input): + # flatten data + data = array(history) + data = data.reshape((data.shape[0]*data.shape[1], data.shape[2])) + # retrieve last observations for input data + input_x = data[-n_input:, :] + # reshape into [1, n_input, n] + input_x = input_x.reshape((1, input_x.shape[0], input_x.shape[1])) + # forecast the next week + yhat = model.predict(input_x, verbose=0) + # we only want the vector forecast + yhat = yhat[0] + return yhat + +# evaluate a single model +def evaluate_model(train, test, n_input): + # fit model + model = build_model(train, n_input) + # history is a list of weekly data + history = [x for x in train] + # walk-forward validation over each week + predictions = list() + for i in range(len(test)): + # predict the week + yhat_sequence = forecast(model, history, n_input) + # store the predictions + predictions.append(yhat_sequence) + # get real observation and add to history for predicting the next week + history.append(test[i, :]) + # evaluate predictions days for each week + predictions = array(predictions) + score, scores = evaluate_forecasts(test[:, :, 0], predictions) + return score, scores + +# load the new file +dataset = read_csv('household_power_consumption_days.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +# split into train and test +train, test = split_dataset(dataset.values) +# evaluate model and get scores +n_input = 14 +score, scores = evaluate_model(train, test, n_input) +# summarize scores +summarize_scores('lstm', score, scores) +# plot scores +days = ['sun', 'mon', 'tue', 'wed', 'thr', 'fri', 'sat'] +pyplot.plot(days, scores, marker='o', label='lstm') +pyplot.show() +``` + +运行该示例适合模型并总结测试数据集的表现。 + +实验发现该模型看起来不如单变量情况稳定,并且可能与输入的八个变量的不同尺度有关。 + +鉴于算法的随机性,您的具体结果可能会有所不同。您可能想尝试几次运行该示例。 + +我们可以看到,在这种情况下,该模型非常巧妙,总体 RMSE 得分约为 376 千瓦。 + +``` +lstm: [376.273] 378.5, 381.5, 328.4, 388.3, 361.2, 308.0, 467.2 +``` + +还创建了每日 RMSE 的线图。 + +![Line Plot of RMSE per Day for Multivariate Encoder-Decoder LSTM with 14-day Inputs](img/d463c92463bbfd80a905df0c8cc129aa.jpg) + +具有 14 天输入的多变量编码器 - 解码器 LSTM 每天 RMSE 的线图 + +## 具有单变量输入的 CNN-LSTM 编码器 - 解码器模型 + +卷积神经网络或 CNN 可以用作编码器 - 解码器架构中的编码器。 + +CNN 不直接支持序列输入;相反,1D CNN 能够读取序列输入并自动学习显着特征。然后可以按照正常情况由 LSTM 解码器解释这些。我们将使用 CNN 和 LSTM 的混合模型称为 [CNN-LSTM 模型](https://machinelearningmastery.com/cnn-long-short-term-memory-networks/),在这种情况下,我们在编码器 - 解码器架构中一起使用它们。 + +CNN 期望输入数据具有与 LSTM 模型相同的 3D 结构,尽管多个特征被读取为最终具有相同效果的不同通道。 + +我们将简化示例并关注具有单变量输入的 CNN-LSTM,但它可以很容易地更新以使用多变量输入,这是一个练习。 + +和以前一样,我们将使用包含 14 天每日总功耗的输入序列。 + +我们将为编码器定义一个简单但有效的 CNN 架构,该架构由两个卷积层和一个最大池层组成,其结果随后被展平。 + +第一个卷积层读取输入序列并将结果投影到要素图上。第二个对第一层创建的要素图执行相同的操作,尝试放大任何显着特征。我们将在每个卷积层使用 64 个特征映射,并以三个时间步长的内核大小读取输入序列。 + +最大池化层通过将 1/4 的值保持为最大(最大)信号来简化特征映射。然后将汇集层之后的蒸馏特征映射平展为一个长向量,然后可以将其用作解码过程的输入。 + +``` +model.add(Conv1D(filters=64, kernel_size=3, activation='relu', input_shape=(n_timesteps,n_features))) +model.add(Conv1D(filters=64, kernel_size=3, activation='relu')) +model.add(MaxPooling1D(pool_size=2)) +model.add(Flatten()) +``` + +解码器与前面部分中定义的相同。 + +唯一的另一个变化是将训练时期的数量设置为 20。 + +下面列出了具有这些更改的 _build_model()_ 函数。 + +``` +# train the model +def build_model(train, n_input): + # prepare data + train_x, train_y = to_supervised(train, n_input) + # define parameters + verbose, epochs, batch_size = 0, 20, 16 + n_timesteps, n_features, n_outputs = train_x.shape[1], train_x.shape[2], train_y.shape[1] + # reshape output into [samples, timesteps, features] + train_y = train_y.reshape((train_y.shape[0], train_y.shape[1], 1)) + # define model + model = Sequential() + model.add(Conv1D(filters=64, kernel_size=3, activation='relu', input_shape=(n_timesteps,n_features))) + model.add(Conv1D(filters=64, kernel_size=3, activation='relu')) + model.add(MaxPooling1D(pool_size=2)) + model.add(Flatten()) + model.add(RepeatVector(n_outputs)) + model.add(LSTM(200, activation='relu', return_sequences=True)) + model.add(TimeDistributed(Dense(100, activation='relu'))) + model.add(TimeDistributed(Dense(1))) + model.compile(loss='mse', optimizer='adam') + # fit network + model.fit(train_x, train_y, epochs=epochs, batch_size=batch_size, verbose=verbose) + return model +``` + +我们现在准备尝试使用 CNN 编码器的编码器 - 解码器架构。 + +完整的代码清单如下。 + +``` +# univariate multi-step encoder-decoder cnn-lstm +from math import sqrt +from numpy import split +from numpy import array +from pandas import read_csv +from sklearn.metrics import mean_squared_error +from matplotlib import pyplot +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import Flatten +from keras.layers import LSTM +from keras.layers import RepeatVector +from keras.layers import TimeDistributed +from keras.layers.convolutional import Conv1D +from keras.layers.convolutional import MaxPooling1D + +# split a univariate dataset into train/test sets +def split_dataset(data): + # split into standard weeks + train, test = data[1:-328], data[-328:-6] + # restructure into windows of weekly data + train = array(split(train, len(train)/7)) + test = array(split(test, len(test)/7)) + return train, test + +# evaluate one or more weekly forecasts against expected values +def evaluate_forecasts(actual, predicted): + scores = list() + # calculate an RMSE score for each day + for i in range(actual.shape[1]): + # calculate mse + mse = mean_squared_error(actual[:, i], predicted[:, i]) + # calculate rmse + rmse = sqrt(mse) + # store + scores.append(rmse) + # calculate overall RMSE + s = 0 + for row in range(actual.shape[0]): + for col in range(actual.shape[1]): + s += (actual[row, col] - predicted[row, col])**2 + score = sqrt(s / (actual.shape[0] * actual.shape[1])) + return score, scores + +# summarize scores +def summarize_scores(name, score, scores): + s_scores = ', '.join(['%.1f' % s for s in scores]) + print('%s: [%.3f] %s' % (name, score, s_scores)) + +# convert history into inputs and outputs +def to_supervised(train, n_input, n_out=7): + # flatten data + data = train.reshape((train.shape[0]*train.shape[1], train.shape[2])) + X, y = list(), list() + in_start = 0 + # step over the entire history one time step at a time + for _ in range(len(data)): + # define the end of the input sequence + in_end = in_start + n_input + out_end = in_end + n_out + # ensure we have enough data for this instance + if out_end < len(data): + x_input = data[in_start:in_end, 0] + x_input = x_input.reshape((len(x_input), 1)) + X.append(x_input) + y.append(data[in_end:out_end, 0]) + # move along one time step + in_start += 1 + return array(X), array(y) + +# train the model +def build_model(train, n_input): + # prepare data + train_x, train_y = to_supervised(train, n_input) + # define parameters + verbose, epochs, batch_size = 0, 20, 16 + n_timesteps, n_features, n_outputs = train_x.shape[1], train_x.shape[2], train_y.shape[1] + # reshape output into [samples, timesteps, features] + train_y = train_y.reshape((train_y.shape[0], train_y.shape[1], 1)) + # define model + model = Sequential() + model.add(Conv1D(filters=64, kernel_size=3, activation='relu', input_shape=(n_timesteps,n_features))) + model.add(Conv1D(filters=64, kernel_size=3, activation='relu')) + model.add(MaxPooling1D(pool_size=2)) + model.add(Flatten()) + model.add(RepeatVector(n_outputs)) + model.add(LSTM(200, activation='relu', return_sequences=True)) + model.add(TimeDistributed(Dense(100, activation='relu'))) + model.add(TimeDistributed(Dense(1))) + model.compile(loss='mse', optimizer='adam') + # fit network + model.fit(train_x, train_y, epochs=epochs, batch_size=batch_size, verbose=verbose) + return model + +# make a forecast +def forecast(model, history, n_input): + # flatten data + data = array(history) + data = data.reshape((data.shape[0]*data.shape[1], data.shape[2])) + # retrieve last observations for input data + input_x = data[-n_input:, 0] + # reshape into [1, n_input, 1] + input_x = input_x.reshape((1, len(input_x), 1)) + # forecast the next week + yhat = model.predict(input_x, verbose=0) + # we only want the vector forecast + yhat = yhat[0] + return yhat + +# evaluate a single model +def evaluate_model(train, test, n_input): + # fit model + model = build_model(train, n_input) + # history is a list of weekly data + history = [x for x in train] + # walk-forward validation over each week + predictions = list() + for i in range(len(test)): + # predict the week + yhat_sequence = forecast(model, history, n_input) + # store the predictions + predictions.append(yhat_sequence) + # get real observation and add to history for predicting the next week + history.append(test[i, :]) + # evaluate predictions days for each week + predictions = array(predictions) + score, scores = evaluate_forecasts(test[:, :, 0], predictions) + return score, scores + +# load the new file +dataset = read_csv('household_power_consumption_days.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +# split into train and test +train, test = split_dataset(dataset.values) +# evaluate model and get scores +n_input = 14 +score, scores = evaluate_model(train, test, n_input) +# summarize scores +summarize_scores('lstm', score, scores) +# plot scores +days = ['sun', 'mon', 'tue', 'wed', 'thr', 'fri', 'sat'] +pyplot.plot(days, scores, marker='o', label='lstm') +pyplot.show() +``` + +运行该示例适合模型并总结测试数据集的表现。 + +一些实验表明,使用两个卷积层使得模型比仅使用单个层更稳定。 + +鉴于算法的随机性,您的具体结果可能会有所不同。您可能想尝试几次运行该示例。 + +我们可以看到,在这种情况下,该模型非常巧妙,总体 RMSE 得分约为 372 千瓦。 + +``` +lstm: [372.055] 383.8, 381.6, 339.1, 371.8, 371.8, 319.6, 427.2 +``` + +还创建了每日 RMSE 的线图。 + +![Line Plot of RMSE per Day for Univariate Encoder-Decoder CNN LSTM with 14-day Inputs](img/667d8a4b2415019c4c974300451c5bc5.jpg) + +具有 14 天输入的单变量编码器 - 解码器 CNN LSTM 每天 RMSE 的线图 + +## 具有单变量输入的 ConvLSTM 编码器 - 解码器模型 + +CNN-LSTM 方法的进一步扩展是执行 CNN 的卷积(例如 CNN 如何读取输入序列数据)作为每个时间步长的 LSTM 的一部分。 + +这种组合称为卷积 LSTM,简称 ConvLSTM,CNN-LSTM 也用于时空数据。 + +与直接读取数据以计算内部状态和状态转换的 LSTM 不同,与解释 CNN 模型输出的 CNN-LSTM 不同,ConvLSTM 直接使用卷积作为读取 LSTM 单元本身输入的一部分。 + +有关如何在 LSTM 单元内计算 ConvLSTM 方程的更多信息,请参阅文章: + +* [卷积 LSTM 网络:用于降水预报的机器学习方法](https://arxiv.org/abs/1506.04214v1),2015。 + +Keras 库提供 [ConvLSTM2D 类](https://keras.io/layers/recurrent/#convlstm2d),支持用于 2D 数据的 ConvLSTM 模型。它可以配置为 1D 多变量时间序列预测。 + +默认情况下,ConvLSTM2D 类要求输入数据具有以下形状: + +``` +[samples, timesteps, rows, cols, channels] +``` + +其中每个时间步长数据被定义为(_ 行*列 _)数据点的图像。 + +我们正在使用一个总功耗的一维序列,如果我们假设我们使用两周的数据作为输入,我们可以将其解释为具有 14 列的一行。 + +对于 ConvLSTM,这将是一次读取:也就是说,LSTM 将读取 14 天的一个时间步长并在这些时间步骤之间执行卷积。 + +这不太理想。 + +相反,我们可以将 14 天分成两个子序列,长度为七天。然后,ConvLSTM 可以读取两个时间步骤,并在每个步骤中的七天数据上执行 CNN 过程。 + +对于这个选择的问题框架,ConvLSTM2D 的输入因此是: + +``` +[n, 2, 1, 7, 1] +``` + +要么: + +* **样本**:n,用于训练数据集中的示例数。 +* **时间**:2,对于我们拆分 14 天窗口的两个子序列。 +* **行**:1,用于每个子序列的一维形状。 +* **列**:7,每个子序列中的七天。 +* **频道**:1,我们正在使用的单一功能作为输入。 + +您可以探索其他配置,例如将 21 天的输入分为七天的三个子序列,和/或提供所有八个功能或通道作为输入。 + +我们现在可以为 ConvLSTM2D 模型准备数据。 + +首先,我们必须将训练数据集重塑为[_ 样本,时间步长,行,列,通道 _]的预期结构。 + +``` +# reshape into subsequences [samples, time steps, rows, cols, channels] +train_x = train_x.reshape((train_x.shape[0], n_steps, 1, n_length, n_features)) +``` + +然后,我们可以将编码器定义为 ConvLSTM 隐藏层,然后是准备好解码的展平层。 + +``` +model.add(ConvLSTM2D(filters=64, kernel_size=(1,3), activation='relu', input_shape=(n_steps, 1, n_length, n_features))) +model.add(Flatten()) +``` + +我们还将参数化子序列的数量( _n_steps_ )和每个子序列的长度( _n_length_ )并将它们作为参数传递。 + +模型和训练的其余部分是相同的。下面列出了具有这些更改的 _build_model()_ 函数。 + +``` +# train the model +def build_model(train, n_steps, n_length, n_input): + # prepare data + train_x, train_y = to_supervised(train, n_input) + # define parameters + verbose, epochs, batch_size = 0, 20, 16 + n_timesteps, n_features, n_outputs = train_x.shape[1], train_x.shape[2], train_y.shape[1] + # reshape into subsequences [samples, time steps, rows, cols, channels] + train_x = train_x.reshape((train_x.shape[0], n_steps, 1, n_length, n_features)) + # reshape output into [samples, timesteps, features] + train_y = train_y.reshape((train_y.shape[0], train_y.shape[1], 1)) + # define model + model = Sequential() + model.add(ConvLSTM2D(filters=64, kernel_size=(1,3), activation='relu', input_shape=(n_steps, 1, n_length, n_features))) + model.add(Flatten()) + model.add(RepeatVector(n_outputs)) + model.add(LSTM(200, activation='relu', return_sequences=True)) + model.add(TimeDistributed(Dense(100, activation='relu'))) + model.add(TimeDistributed(Dense(1))) + model.compile(loss='mse', optimizer='adam') + # fit network + model.fit(train_x, train_y, epochs=epochs, batch_size=batch_size, verbose=verbose) + return model +``` + +该模型期望五维数据作为输入。因此,我们还必须在进行预测时更新 _forecast()_ 函数中单个样本的准备。 + +``` +# reshape into [samples, time steps, rows, cols, channels] +input_x = input_x.reshape((1, n_steps, 1, n_length, 1)) +``` + +具有此变化的 _forecast()_ 函数以及参数化子序列如下所示。 + +``` +# make a forecast +def forecast(model, history, n_steps, n_length, n_input): + # flatten data + data = array(history) + data = data.reshape((data.shape[0]*data.shape[1], data.shape[2])) + # retrieve last observations for input data + input_x = data[-n_input:, 0] + # reshape into [samples, time steps, rows, cols, channels] + input_x = input_x.reshape((1, n_steps, 1, n_length, 1)) + # forecast the next week + yhat = model.predict(input_x, verbose=0) + # we only want the vector forecast + yhat = yhat[0] + return yhat +``` + +我们现在拥有评估编码器 - 解码器架构的所有元素,用于多步时间序列预测,其中 ConvLSTM 用作编码器。 + +完整的代码示例如下所示。 + +``` +# univariate multi-step encoder-decoder convlstm +from math import sqrt +from numpy import split +from numpy import array +from pandas import read_csv +from sklearn.metrics import mean_squared_error +from matplotlib import pyplot +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import Flatten +from keras.layers import LSTM +from keras.layers import RepeatVector +from keras.layers import TimeDistributed +from keras.layers import ConvLSTM2D + +# split a univariate dataset into train/test sets +def split_dataset(data): + # split into standard weeks + train, test = data[1:-328], data[-328:-6] + # restructure into windows of weekly data + train = array(split(train, len(train)/7)) + test = array(split(test, len(test)/7)) + return train, test + +# evaluate one or more weekly forecasts against expected values +def evaluate_forecasts(actual, predicted): + scores = list() + # calculate an RMSE score for each day + for i in range(actual.shape[1]): + # calculate mse + mse = mean_squared_error(actual[:, i], predicted[:, i]) + # calculate rmse + rmse = sqrt(mse) + # store + scores.append(rmse) + # calculate overall RMSE + s = 0 + for row in range(actual.shape[0]): + for col in range(actual.shape[1]): + s += (actual[row, col] - predicted[row, col])**2 + score = sqrt(s / (actual.shape[0] * actual.shape[1])) + return score, scores + +# summarize scores +def summarize_scores(name, score, scores): + s_scores = ', '.join(['%.1f' % s for s in scores]) + print('%s: [%.3f] %s' % (name, score, s_scores)) + +# convert history into inputs and outputs +def to_supervised(train, n_input, n_out=7): + # flatten data + data = train.reshape((train.shape[0]*train.shape[1], train.shape[2])) + X, y = list(), list() + in_start = 0 + # step over the entire history one time step at a time + for _ in range(len(data)): + # define the end of the input sequence + in_end = in_start + n_input + out_end = in_end + n_out + # ensure we have enough data for this instance + if out_end < len(data): + x_input = data[in_start:in_end, 0] + x_input = x_input.reshape((len(x_input), 1)) + X.append(x_input) + y.append(data[in_end:out_end, 0]) + # move along one time step + in_start += 1 + return array(X), array(y) + +# train the model +def build_model(train, n_steps, n_length, n_input): + # prepare data + train_x, train_y = to_supervised(train, n_input) + # define parameters + verbose, epochs, batch_size = 0, 20, 16 + n_timesteps, n_features, n_outputs = train_x.shape[1], train_x.shape[2], train_y.shape[1] + # reshape into subsequences [samples, time steps, rows, cols, channels] + train_x = train_x.reshape((train_x.shape[0], n_steps, 1, n_length, n_features)) + # reshape output into [samples, timesteps, features] + train_y = train_y.reshape((train_y.shape[0], train_y.shape[1], 1)) + # define model + model = Sequential() + model.add(ConvLSTM2D(filters=64, kernel_size=(1,3), activation='relu', input_shape=(n_steps, 1, n_length, n_features))) + model.add(Flatten()) + model.add(RepeatVector(n_outputs)) + model.add(LSTM(200, activation='relu', return_sequences=True)) + model.add(TimeDistributed(Dense(100, activation='relu'))) + model.add(TimeDistributed(Dense(1))) + model.compile(loss='mse', optimizer='adam') + # fit network + model.fit(train_x, train_y, epochs=epochs, batch_size=batch_size, verbose=verbose) + return model + +# make a forecast +def forecast(model, history, n_steps, n_length, n_input): + # flatten data + data = array(history) + data = data.reshape((data.shape[0]*data.shape[1], data.shape[2])) + # retrieve last observations for input data + input_x = data[-n_input:, 0] + # reshape into [samples, time steps, rows, cols, channels] + input_x = input_x.reshape((1, n_steps, 1, n_length, 1)) + # forecast the next week + yhat = model.predict(input_x, verbose=0) + # we only want the vector forecast + yhat = yhat[0] + return yhat + +# evaluate a single model +def evaluate_model(train, test, n_steps, n_length, n_input): + # fit model + model = build_model(train, n_steps, n_length, n_input) + # history is a list of weekly data + history = [x for x in train] + # walk-forward validation over each week + predictions = list() + for i in range(len(test)): + # predict the week + yhat_sequence = forecast(model, history, n_steps, n_length, n_input) + # store the predictions + predictions.append(yhat_sequence) + # get real observation and add to history for predicting the next week + history.append(test[i, :]) + # evaluate predictions days for each week + predictions = array(predictions) + score, scores = evaluate_forecasts(test[:, :, 0], predictions) + return score, scores + +# load the new file +dataset = read_csv('household_power_consumption_days.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +# split into train and test +train, test = split_dataset(dataset.values) +# define the number of subsequences and the length of subsequences +n_steps, n_length = 2, 7 +# define the total days to use as input +n_input = n_length * n_steps +score, scores = evaluate_model(train, test, n_steps, n_length, n_input) +# summarize scores +summarize_scores('lstm', score, scores) +# plot scores +days = ['sun', 'mon', 'tue', 'wed', 'thr', 'fri', 'sat'] +pyplot.plot(days, scores, marker='o', label='lstm') +pyplot.show() +``` + +运行该示例适合模型并总结测试数据集的表现。 + +一些实验表明,使用两个卷积层使得模型比仅使用单个层更稳定。 + +我们可以看到,在这种情况下,该模型非常巧妙,总体 RMSE 得分约为 367 千瓦。 + +``` +lstm: [367.929] 416.3, 379.7, 334.7, 362.3, 374.7, 284.8, 406.7 +``` + +还创建了每日 RMSE 的线图。 + +![Line Plot of RMSE per Day for Univariate Encoder-Decoder ConvLSTM with 14-day Inputs](img/cad0705f4ea93f292420695b0d6b2fb8.jpg) + +具有 14 天输入的单变量编码器 - 解码器 ConvLSTM 每天 RMSE 的线图 + +## 扩展 + +本节列出了一些扩展您可能希望探索的教程的想法。 + +* **输入大小**。探索用作模型输入的更多或更少天数,例如三天,21 天,30 天等。 +* **模型调整**。调整模型的结构和超参数,并进一步平均提升模型表现。 +* **数据缩放**。探索数据扩展(例如标准化和规范化)是否可用于改善任何 LSTM 模型的表现。 +* **学习诊断**。使用诊断,例如列车的学习曲线和验证损失以及均方误差,以帮助调整 LSTM 模型的结构和超参数。 + +如果你探索任何这些扩展,我很想知道。 + +## 进一步阅读 + +如果您希望深入了解,本节将提供有关该主题的更多资源。 + +### 帖子 + +* [多步时间序列预测的 4 种策略](https://machinelearningmastery.com/multi-step-time-series-forecasting/) +* [深度学习的循环神经网络崩溃课程](https://machinelearningmastery.com/crash-course-recurrent-neural-networks-deep-learning/) +* [专家对长短期记忆网络的简要介绍](https://machinelearningmastery.com/gentle-introduction-long-short-term-memory-networks-experts/) +* [关于 LSTM 对时间序列预测的适用性](https://machinelearningmastery.com/suitability-long-short-term-memory-networks-time-series-forecasting/) +* [CNN 长短期记忆网络](https://machinelearningmastery.com/cnn-long-short-term-memory-networks/) +* [如何开发 Keras 中序列到序列预测的编码器 - 解码器模型](https://machinelearningmastery.com/develop-encoder-decoder-model-sequence-sequence-prediction-keras/) + +### API + +* [pandas.read_csv API](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_csv.html) +* [pandas.DataFrame.resample API](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.resample.html) +* [重采样偏移别名](http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases) +* [sklearn.metrics.mean_squared_error API](http://scikit-learn.org/stable/modules/generated/sklearn.metrics.mean_squared_error.html) +* [numpy.split API](https://docs.scipy.org/doc/numpy/reference/generated/numpy.split.html) + +### 用品 + +* [个人家庭用电量数据集,UCI 机器学习库](https://archive.ics.uci.edu/ml/datasets/individual+household+electric+power+consumption)。 +* [交流电源,维基百科](https://en.wikipedia.org/wiki/AC_power)。 +* [卷积 LSTM 网络:用于降水预报的机器学习方法](https://arxiv.org/abs/1506.04214v1),2015。 + +## 摘要 + +在本教程中,您了解了如何开发长期短时记忆循环神经网络,用于家庭功耗的多步时间序列预测。 + +具体来说,你学到了: + +* 如何开发和评估用于多步时间序列预测的单变量和多变量编码器 - 解码器 LSTM。 +* 如何开发和评估用于多步时间序列预测的 CNN-LSTM 编码器 - 解码器模型。 +* 如何开发和评估用于多步时间序列预测的 ConvLSTM 编码器 - 解码器模型。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 + +**注**:这篇文章摘自“[深度学习时间序列预测](https://machinelearningmastery.com/deep-learning-for-time-series-forecasting/)”一书。看一下,如果您想获得更多关于在时间序列预测问题上充分利用深度学习方法的分步教程。 \ No newline at end of file diff --git a/docs/dl-ts/how-to-develop-lstm-models-for-time-series-forecasting.md b/docs/dl-ts/how-to-develop-lstm-models-for-time-series-forecasting.md new file mode 100644 index 0000000000000000000000000000000000000000..a07c9a5675cce5bfb7f0a0fae72f794827bee9cd --- /dev/null +++ b/docs/dl-ts/how-to-develop-lstm-models-for-time-series-forecasting.md @@ -0,0 +1,1926 @@ +# 如何开发 LSTM 模型进行时间序列预测 + +> 原文: [https://machinelearningmastery.com/how-to-develop-lstm-models-for-time-series-forecasting/](https://machinelearningmastery.com/how-to-develop-lstm-models-for-time-series-forecasting/) + +长短期内存网络(简称 LSTM)可应用于时间序列预测。 + +有许多类型的 LSTM 模型可用于每种特定类型的时间序列预测问题。 + +在本教程中,您将了解如何针对一系列标准时间序列预测问题开发一套 LSTM 模型。 + +本教程的目的是为每种类型的时间序列问题提供每个模型的独立示例,作为模板,您可以根据特定的时间序列预测问题进行复制和调整。 + +完成本教程后,您将了解: + +* 如何开发 LSTM 模型进行单变量时间序列预测。 +* 如何开发多变量时间序列预测的 LSTM 模型。 +* 如何开发 LSTM 模型进行多步时间序列预测。 + +这是一个庞大而重要的职位;您可能希望将其加入书签以供将来参考。 + +让我们开始吧。 + +![How to Develop LSTM Models for Time Series Forecasting](img/d886deae66a94f53af0a8769f359935c.jpg) + +如何开发用于时间序列预测的 LSTM 模型 +照片由 [N i c o l a](https://www.flickr.com/photos/15216811@N06/6704346543/) ,保留一些权利。 + +## 教程概述 + +在本教程中,我们将探索如何为时间序列预测开发一套不同类型的 LSTM 模型。 + +这些模型在小型设计的时间序列问题上进行了演示,旨在解决时间序列问题类型的风格。所选择的模型配置是任意的,并未针对每个问题进行优化;那不是目标。 + +本教程分为四个部分;他们是: + +1. 单变量 LSTM 模型 +2. 多变量 LSTM 模型 +3. 多步 LSTM 模型 +4. 多变量多步 LSTM 模型 + +## 单变量 LSTM 模型 + +LSTM 可用于模拟单变量时间序列预测问题。 + +这些问题包括一系列观察,并且需要模型来从过去的一系列观察中学习以预测序列中的下一个值。 + +我们将演示 LSTM 模型的多种变体,用于单变量时间序列预测。 + +本节分为六个部分;他们是: + +1. 数据准备 +2. 香草 LSTM +3. 堆叠式 LSTM +4. 双向 LSTM +5. CNN LSTM +6. ConvLSTM + +这些模型中的每一个都被演示为一步式单变量时间序列预测,但可以很容易地进行调整并用作其他类型的时间序列预测问题的模型的输入部分。 + +### 数据准备 + +在对单变量系列进行建模之前,必须准备好它。 + +LSTM 模型将学习一种函数,该函数将过去观察序列作为输入映射到输出观察。因此,必须将观察序列转换为 LSTM 可以学习的多个示例。 + +考虑给定的单变量序列: + +``` +[10, 20, 30, 40, 50, 60, 70, 80, 90] +``` + +我们可以将序列划分为多个称为样本的输入/输出模式,其中三个时间步长用作输入,一个时间步长用作正在学习的一步预测的输出。 + +``` +X, y +10, 20, 30 40 +20, 30, 40 50 +30, 40, 50 60 +... +``` + +下面的 _split_sequence()_ 函数实现了这种行为,并将给定的单变量序列分成多个样本,其中每个样本具有指定的时间步长,输出是单个时间步长。 + +``` +# split a univariate sequence into samples +def split_sequence(sequence, n_steps): + X, y = list(), list() + for i in range(len(sequence)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the sequence + if end_ix > len(sequence)-1: + break + # gather input and output parts of the pattern + seq_x, seq_y = sequence[i:end_ix], sequence[end_ix] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) +``` + +我们可以在上面的小型人为数据集上演示这个功能。 + +下面列出了完整的示例。 + +``` +# univariate data preparation +from numpy import array + +# split a univariate sequence into samples +def split_sequence(sequence, n_steps): + X, y = list(), list() + for i in range(len(sequence)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the sequence + if end_ix > len(sequence)-1: + break + # gather input and output parts of the pattern + seq_x, seq_y = sequence[i:end_ix], sequence[end_ix] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +raw_seq = [10, 20, 30, 40, 50, 60, 70, 80, 90] +# choose a number of time steps +n_steps = 3 +# split into samples +X, y = split_sequence(raw_seq, n_steps) +# summarize the data +for i in range(len(X)): + print(X[i], y[i]) +``` + +运行该示例将单变量系列分成六个样本,其中每个样本具有三个输入时间步长和一个输出时间步长。 + +``` +[10 20 30] 40 +[20 30 40] 50 +[30 40 50] 60 +[40 50 60] 70 +[50 60 70] 80 +[60 70 80] 90 +``` + +现在我们已经知道如何准备用于建模的单变量系列,让我们看看开发 LSTM 模型,它可以学习输入到输出的映射,从 Vanilla LSTM 开始。 + +### 香草 LSTM + +Vanilla LSTM 是 LSTM 模型,具有单个隐藏的 LSTM 单元层,以及用于进行预测的输出层。 + +我们可以如下定义用于单变量时间序列预测的 Vanilla LSTM。 + +``` +# define model +model = Sequential() +model.add(LSTM(50, activation='relu', input_shape=(n_steps, n_features))) +model.add(Dense(1)) +model.compile(optimizer='adam', loss='mse') +``` + +定义的关键是输入的形状;这就是模型期望的每个样本的输入,包括时间步数和特征数。 + +我们正在使用单变量系列,因此对于一个变量,要素的数量是一个。 + +输入的时间步数是我们在准备数据集时选择的数字,作为 _split_sequence()_ 函数的参数。 + +每个样本的输入形状在第一个隐藏层定义的 _input_shape_ 参数中指定。 + +我们几乎总是有多个样本,因此,模型将期望训练数据的输入组件具有尺寸或形状: + +``` +[samples, timesteps, features] +``` + +我们在上一节中的 _split_sequence()_ 函数输出具有[_ 样本,时间步长 _]形状​​的 X,因此我们可以轻松地对其进行整形,以便为一个特征提供额外的维度。 + +``` +# reshape from [samples, timesteps] into [samples, timesteps, features] +n_features = 1 +X = X.reshape((X.shape[0], X.shape[1], n_features)) +``` + +在这种情况下,我们定义隐藏层中具有 50 个 LSTM 单元的模型和预测单个数值的输出层。 + +使用随机梯度下降的有效 [Adam 版本拟合该模型,并使用均方误差或' _mse_ '损失函数进行优化。](https://machinelearningmastery.com/adam-optimization-algorithm-for-deep-learning/) + +定义模型后,我们可以将其放在训练数据集上。 + +``` +# fit model +model.fit(X, y, epochs=200, verbose=0) +``` + +在模型拟合后,我们可以使用它来进行预测。 + +我们可以通过提供输入来预测序列中的下一个值: + +``` +[70, 80, 90] +``` + +并期望模型预测如下: + +``` +[100] +``` + +该模型期望输入形状为[_ 样本,时间步长,特征 _]三维,因此,我们必须在进行预测之前对单个输入样本进行整形。 + +``` +# demonstrate prediction +x_input = array([70, 80, 90]) +x_input = x_input.reshape((1, n_steps, n_features)) +yhat = model.predict(x_input, verbose=0) +``` + +我们可以将所有这些结合在一起并演示如何开发用于单变量时间序列预测的 Vanilla LSTM 并进行单一预测。 + +``` +# univariate lstm example +from numpy import array +from keras.models import Sequential +from keras.layers import LSTM +from keras.layers import Dense + +# split a univariate sequence into samples +def split_sequence(sequence, n_steps): + X, y = list(), list() + for i in range(len(sequence)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the sequence + if end_ix > len(sequence)-1: + break + # gather input and output parts of the pattern + seq_x, seq_y = sequence[i:end_ix], sequence[end_ix] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +raw_seq = [10, 20, 30, 40, 50, 60, 70, 80, 90] +# choose a number of time steps +n_steps = 3 +# split into samples +X, y = split_sequence(raw_seq, n_steps) +# reshape from [samples, timesteps] into [samples, timesteps, features] +n_features = 1 +X = X.reshape((X.shape[0], X.shape[1], n_features)) +# define model +model = Sequential() +model.add(LSTM(50, activation='relu', input_shape=(n_steps, n_features))) +model.add(Dense(1)) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit(X, y, epochs=200, verbose=0) +# demonstrate prediction +x_input = array([70, 80, 90]) +x_input = x_input.reshape((1, n_steps, n_features)) +yhat = model.predict(x_input, verbose=0) +print(yhat) +``` + +运行该示例准备数据,拟合模型并进行预测。 + +鉴于算法的随机性,您的结果可能会有所不同;尝试运行几次这个例子。 + +我们可以看到模型预测序列中的下一个值。 + +``` +[[102.09213]] +``` + +### 堆叠式 LSTM + +多个隐藏的 LSTM 层可以在所谓的堆叠 LSTM 模型中一个堆叠在另一个之上。 + +LSTM 层需要三维输入,默认情况下,LSTM 将产生二维输出作为序列末尾的解释。 + +我们可以通过在层上设置 _return_sequences = True_ 参数,为输入数据中的每个时间步长输出 LSTM 来解决这个问题。这允许我们将隐藏的 LSTM 层的 3D 输出作为下一个输入。 + +因此,我们可以如下定义 Stacked LSTM。 + +``` +# define model +model = Sequential() +model.add(LSTM(50, activation='relu', return_sequences=True, input_shape=(n_steps, n_features))) +model.add(LSTM(50, activation='relu')) +model.add(Dense(1)) +model.compile(optimizer='adam', loss='mse') +``` + +我们可以将它们联系起来;完整的代码示例如下所示。 + +``` +# univariate stacked lstm example +from numpy import array +from keras.models import Sequential +from keras.layers import LSTM +from keras.layers import Dense + +# split a univariate sequence +def split_sequence(sequence, n_steps): + X, y = list(), list() + for i in range(len(sequence)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the sequence + if end_ix > len(sequence)-1: + break + # gather input and output parts of the pattern + seq_x, seq_y = sequence[i:end_ix], sequence[end_ix] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +raw_seq = [10, 20, 30, 40, 50, 60, 70, 80, 90] +# choose a number of time steps +n_steps = 3 +# split into samples +X, y = split_sequence(raw_seq, n_steps) +# reshape from [samples, timesteps] into [samples, timesteps, features] +n_features = 1 +X = X.reshape((X.shape[0], X.shape[1], n_features)) +# define model +model = Sequential() +model.add(LSTM(50, activation='relu', return_sequences=True, input_shape=(n_steps, n_features))) +model.add(LSTM(50, activation='relu')) +model.add(Dense(1)) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit(X, y, epochs=200, verbose=0) +# demonstrate prediction +x_input = array([70, 80, 90]) +x_input = x_input.reshape((1, n_steps, n_features)) +yhat = model.predict(x_input, verbose=0) +print(yhat) +``` + +运行该示例预测序列中的下一个值,我们预期该值为 100。 + +``` +[[102.47341]] +``` + +### 双向 LSTM + +在一些序列预测问题上,允许 LSTM 模型向前和向后学习输入序列并连接两种解释可能是有益的。 + +这称为[双向 LSTM](https://machinelearningmastery.com/develop-bidirectional-lstm-sequence-classification-python-keras/) 。 + +我们可以通过将第一个隐藏层包装在名为 Bidirectional 的包装层中来实现双向 LSTM 以进行单变量时间序列预测。 + +定义双向 LSTM 以向前和向后读取输入的示例如下。 + +``` +# define model +model = Sequential() +model.add(Bidirectional(LSTM(50, activation='relu'), input_shape=(n_steps, n_features))) +model.add(Dense(1)) +model.compile(optimizer='adam', loss='mse') +``` + +下面列出了用于单变量时间序列预测的双向 LSTM 的完整示例。 + +``` +# univariate bidirectional lstm example +from numpy import array +from keras.models import Sequential +from keras.layers import LSTM +from keras.layers import Dense +from keras.layers import Bidirectional + +# split a univariate sequence +def split_sequence(sequence, n_steps): + X, y = list(), list() + for i in range(len(sequence)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the sequence + if end_ix > len(sequence)-1: + break + # gather input and output parts of the pattern + seq_x, seq_y = sequence[i:end_ix], sequence[end_ix] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +raw_seq = [10, 20, 30, 40, 50, 60, 70, 80, 90] +# choose a number of time steps +n_steps = 3 +# split into samples +X, y = split_sequence(raw_seq, n_steps) +# reshape from [samples, timesteps] into [samples, timesteps, features] +n_features = 1 +X = X.reshape((X.shape[0], X.shape[1], n_features)) +# define model +model = Sequential() +model.add(Bidirectional(LSTM(50, activation='relu'), input_shape=(n_steps, n_features))) +model.add(Dense(1)) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit(X, y, epochs=200, verbose=0) +# demonstrate prediction +x_input = array([70, 80, 90]) +x_input = x_input.reshape((1, n_steps, n_features)) +yhat = model.predict(x_input, verbose=0) +print(yhat) +``` + +运行该示例预测序列中的下一个值,我们预期该值为 100。 + +``` +[[101.48093]] +``` + +### CNN LSTM + +卷积神经网络(简称 CNN)是一种为处理二维图像数据而开发的神经网络。 + +CNN 可以非常有效地从一维序列数据(例如单变量时间序列数据)中自动提取和学习特征。 + +CNN 模型可以在具有 LSTM 后端的混合模型中使用,其中 CNN 用于解释输入的子序列,这些子序列一起作为序列提供给 LSTM 模型以进行解释。 [这种混合模型称为 CNN-LSTM](https://machinelearningmastery.com/cnn-long-short-term-memory-networks/) 。 + +第一步是将输入序列分成可由 CNN 模型处理的子序列。例如,我们可以首先将单变量时间序列数据拆分为输入/输出样本,其中四个步骤作为输入,一个作为输出。然后可以将每个样品分成两个子样品,每个子样品具有两个时间步骤。 CNN 可以解释两个时间步的每个子序列,并提供对 LSTM 模型的子序列的时间序列解释以作为输入进行处理。 + +我们可以对此进行参数化,并将子序列的数量定义为 _n_seq_ ,将每个子序列的时间步数定义为 _n_steps_ 。然后可以将输入数据重新整形为具有所需的结构: + +``` +[samples, subsequences, timesteps, features] +``` + +例如: + +``` +# choose a number of time steps +n_steps = 4 +# split into samples +X, y = split_sequence(raw_seq, n_steps) +# reshape from [samples, timesteps] into [samples, subsequences, timesteps, features] +n_features = 1 +n_seq = 2 +n_steps = 2 +X = X.reshape((X.shape[0], n_seq, n_steps, n_features)) +``` + +我们希望在分别读取每个数据子序列时重用相同的 CNN 模型。 + +这可以通过将整个 CNN 模型包装在 [TimeDistributed 包装器](https://machinelearningmastery.com/timedistributed-layer-for-long-short-term-memory-networks-in-python/)中来实现,该包装器将每个输入应用整个模型一次,在这种情况下,每个输入子序列一次。 + +CNN 模型首先具有卷积层,用于读取子序列,该子序列需要指定多个过滤器和内核大小。过滤器的数量是输入序列的读取或解释的数量。内核大小是输入序列的每个“读取”操作所包含的时间步数。 + +卷积层后面跟着一个最大池池,它将过滤器图谱提取到其大小的 1/4,包括最显着的特征。然后将这些结构展平为单个一维向量,以用作 LSTM 层的单个输入时间步长。 + +``` +model.add(TimeDistributed(Conv1D(filters=64, kernel_size=1, activation='relu'), input_shape=(None, n_steps, n_features))) +model.add(TimeDistributed(MaxPooling1D(pool_size=2))) +model.add(TimeDistributed(Flatten())) +``` + +接下来,我们可以定义模型的 LSTM 部分,该部分解释 CNN 模型对输入序列的读取并进行预测。 + +``` +model.add(LSTM(50, activation='relu')) +model.add(Dense(1)) +``` + +我们可以将所有这些结合在一起;下面列出了用于单变量时间序列预测的 CNN-LSTM 模型的完整示例。 + +``` +# univariate cnn lstm example +from numpy import array +from keras.models import Sequential +from keras.layers import LSTM +from keras.layers import Dense +from keras.layers import Flatten +from keras.layers import TimeDistributed +from keras.layers.convolutional import Conv1D +from keras.layers.convolutional import MaxPooling1D + +# split a univariate sequence into samples +def split_sequence(sequence, n_steps): + X, y = list(), list() + for i in range(len(sequence)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the sequence + if end_ix > len(sequence)-1: + break + # gather input and output parts of the pattern + seq_x, seq_y = sequence[i:end_ix], sequence[end_ix] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +raw_seq = [10, 20, 30, 40, 50, 60, 70, 80, 90] +# choose a number of time steps +n_steps = 4 +# split into samples +X, y = split_sequence(raw_seq, n_steps) +# reshape from [samples, timesteps] into [samples, subsequences, timesteps, features] +n_features = 1 +n_seq = 2 +n_steps = 2 +X = X.reshape((X.shape[0], n_seq, n_steps, n_features)) +# define model +model = Sequential() +model.add(TimeDistributed(Conv1D(filters=64, kernel_size=1, activation='relu'), input_shape=(None, n_steps, n_features))) +model.add(TimeDistributed(MaxPooling1D(pool_size=2))) +model.add(TimeDistributed(Flatten())) +model.add(LSTM(50, activation='relu')) +model.add(Dense(1)) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit(X, y, epochs=500, verbose=0) +# demonstrate prediction +x_input = array([60, 70, 80, 90]) +x_input = x_input.reshape((1, n_seq, n_steps, n_features)) +yhat = model.predict(x_input, verbose=0) +print(yhat) +``` + +运行该示例预测序列中的下一个值,我们预期该值为 100。 + +``` +[[101.69263]] +``` + +### ConvLSTM + +与 CNN-LSTM 相关的一种 LSTM 是 ConvLSTM,其中输入的卷积读取直接建立在每个 LSTM 单元中。 + +ConvLSTM 是为读取二维时空数据而开发的,但可以用于单变量时间序列预测。 + +该层期望输入为二维图像序列,因此输入数据的形状必须为: + +``` +[samples, timesteps, rows, columns, features] +``` + +为了我们的目的,我们可以将每个样本分成时序将成为子序列数的子序列,或 _n_seq_ ,并且列将是每个子序列的时间步数,或 _n_steps_ 。当我们使用一维数据时,行数固定为 1。 + +我们现在可以将准备好的样品重新塑造成所需的结构。 + +``` +# choose a number of time steps +n_steps = 4 +# split into samples +X, y = split_sequence(raw_seq, n_steps) +# reshape from [samples, timesteps] into [samples, timesteps, rows, columns, features] +n_features = 1 +n_seq = 2 +n_steps = 2 +X = X.reshape((X.shape[0], n_seq, 1, n_steps, n_features)) +``` + +我们可以根据过滤器的数量将 ConvLSTM 定义为单个层,并根据(行,列)将二维内核大小定义为单层。当我们使用一维系列时,内核中的行数始终固定为 1。 + +然后必须将模型的输出展平,然后才能进行解释并进行预测。 + +``` +model.add(ConvLSTM2D(filters=64, kernel_size=(1,2), activation='relu', input_shape=(n_seq, 1, n_steps, n_features))) +model.add(Flatten()) +``` + +下面列出了用于一步式单变量时间序列预测的 ConvLSTM 的完整示例。 + +``` +# univariate convlstm example +from numpy import array +from keras.models import Sequential +from keras.layers import LSTM +from keras.layers import Dense +from keras.layers import Flatten +from keras.layers import ConvLSTM2D + +# split a univariate sequence into samples +def split_sequence(sequence, n_steps): + X, y = list(), list() + for i in range(len(sequence)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the sequence + if end_ix > len(sequence)-1: + break + # gather input and output parts of the pattern + seq_x, seq_y = sequence[i:end_ix], sequence[end_ix] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +raw_seq = [10, 20, 30, 40, 50, 60, 70, 80, 90] +# choose a number of time steps +n_steps = 4 +# split into samples +X, y = split_sequence(raw_seq, n_steps) +# reshape from [samples, timesteps] into [samples, timesteps, rows, columns, features] +n_features = 1 +n_seq = 2 +n_steps = 2 +X = X.reshape((X.shape[0], n_seq, 1, n_steps, n_features)) +# define model +model = Sequential() +model.add(ConvLSTM2D(filters=64, kernel_size=(1,2), activation='relu', input_shape=(n_seq, 1, n_steps, n_features))) +model.add(Flatten()) +model.add(Dense(1)) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit(X, y, epochs=500, verbose=0) +# demonstrate prediction +x_input = array([60, 70, 80, 90]) +x_input = x_input.reshape((1, n_seq, 1, n_steps, n_features)) +yhat = model.predict(x_input, verbose=0) +print(yhat) +``` + +运行该示例预测序列中的下一个值,我们预期该值为 100。 + +``` +[[103.68166]] +``` + +现在我们已经查看了单变量数据的 LSTM 模型,让我们将注意力转向多变量数据。 + +## 多变量 LSTM 模型 + +多变量时间序列数据是指每个时间步长有多个观察值的数据。 + +对于多变量时间序列数据,我们可能需要两种主要模型;他们是: + +1. 多输入系列。 +2. 多个并联系列。 + +让我们依次看看每一个。 + +### 多输入系列 + +问题可能有两个或更多并行输入时间序列和输出时间序列,这取决于输入时间序列。 + +输入时间序列是平行的,因为每个系列在同一时间步骤具有观察。 + +我们可以通过两个并行输入时间序列的简单示例来演示这一点,其中输出序列是输入序列的简单添加。 + +``` +# define input sequence +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95]) +out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))]) +``` + +我们可以将这三个数据数组重新整形为单个数据集,其中每一行都是一个时间步,每列都是一个单独的时间序列。这是将并行时间序列存储在 CSV 文件中的标准方法。 + +``` +# convert to [rows, columns] structure +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2, out_seq)) +``` + +下面列出了完整的示例。 + +``` +# multivariate data preparation +from numpy import array +from numpy import hstack +# define input sequence +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95]) +out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))]) +# convert to [rows, columns] structure +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2, out_seq)) +print(dataset) +``` + +运行该示例将打印数据集,每个时间步长为一行,两个输入和一个输出并行时间序列分别为一列。 + +``` +[[ 10 15 25] + [ 20 25 45] + [ 30 35 65] + [ 40 45 85] + [ 50 55 105] + [ 60 65 125] + [ 70 75 145] + [ 80 85 165] + [ 90 95 185]] +``` + +与单变量时间序列一样,我们必须将这些数据组织成具有输入和输出元素的样本。 + +LSTM 模型需要足够的上下文来学习从输入序列到输出值的映射。 LSTM 可以支持并行输入时间序列作为单独的变量或特征。因此,我们需要将数据分成样本,保持两个输入序列的观察顺序。 + +如果我们选择三个输入时间步长,那么第一个样本将如下所示: + +输入: + +``` +10, 15 +20, 25 +30, 35 +``` + +输出: + +``` +65 +``` + +也就是说,每个并行系列的前三个时间步长被提供作为模型的输入,并且模型将其与第三时间步骤(在这种情况下为 65)的输出系列中的值相关联。 + +我们可以看到,在将时间序列转换为输入/输出样本以训练模型时,我们将不得不从输出时间序列中丢弃一些值,其中我们在先前时间步骤中没有输入时间序列中的值。反过来,选择输入时间步数的大小将对使用多少训练数据产生重要影响。 + +我们可以定义一个名为 _split_sequences()_ 的函数,该函数将采用数据集,因为我们已经为时间步长和行定义了并行序列和返回输入/输出样本的列。 + +``` +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the dataset + if end_ix > len(sequences): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :-1], sequences[end_ix-1, -1] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) +``` + +我们可以使用每个输入时间序列的三个时间步长作为输入在我们的数据集上测试此函数。 + +下面列出了完整的示例。 + +``` +# multivariate data preparation +from numpy import array +from numpy import hstack + +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the dataset + if end_ix > len(sequences): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :-1], sequences[end_ix-1, -1] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95]) +out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))]) +# convert to [rows, columns] structure +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2, out_seq)) +# choose a number of time steps +n_steps = 3 +# convert into input/output +X, y = split_sequences(dataset, n_steps) +print(X.shape, y.shape) +# summarize the data +for i in range(len(X)): + print(X[i], y[i]) +``` + +首先运行该示例将打印 X 和 y 组件的形状。 + +我们可以看到 X 组件具有三维结构。 + +第一个维度是样本数,在本例中为 7.第二个维度是每个样本的时间步数,在这种情况下为 3,即为函数指定的值。最后,最后一个维度指定并行时间序列的数量或变量的数量,在这种情况下,两个并行序列为 2。 + +这是 LSTM 作为输入所期望的精确三维结构。数据即可使用而无需进一步重塑。 + +然后我们可以看到每个样本的输入和输出都被打印出来,显示了两个输入序列中每个样本的三个时间步长以及每个样本的相关输出。 + +``` +(7, 3, 2) (7,) + +[[10 15] + [20 25] + [30 35]] 65 +[[20 25] + [30 35] + [40 45]] 85 +[[30 35] + [40 45] + [50 55]] 105 +[[40 45] + [50 55] + [60 65]] 125 +[[50 55] + [60 65] + [70 75]] 145 +[[60 65] + [70 75] + [80 85]] 165 +[[70 75] + [80 85] + [90 95]] 185 +``` + +我们现在准备在这些数据上安装 LSTM 模型。 + +可以使用前一节中的任何种类的 LSTM,例如香草,堆叠,双向,CNN 或 ConvLSTM 模型。 + +我们将使用 Vanilla LSTM,其中通过 _input_shape_ 参数为输入层指定时间步数和并行系列(特征)。 + +``` +# define model +model = Sequential() +model.add(LSTM(50, activation='relu', input_shape=(n_steps, n_features))) +model.add(Dense(1)) +model.compile(optimizer='adam', loss='mse') +``` + +在进行预测时,模型需要两个输入时间序列的三个时间步长。 + +我们可以预测输出系列中的下一个值,提供以下输入值: + +``` +80, 85 +90, 95 +100, 105 +``` + +具有三个时间步长和两个变量的一个样本的形状必须是[1,3,2]。 + +我们希望序列中的下一个值为 100 + 105 或 205。 + +``` +# demonstrate prediction +x_input = array([[80, 85], [90, 95], [100, 105]]) +x_input = x_input.reshape((1, n_steps, n_features)) +yhat = model.predict(x_input, verbose=0) +``` + +下面列出了完整的示例。 + +``` +# multivariate lstm example +from numpy import array +from numpy import hstack +from keras.models import Sequential +from keras.layers import LSTM +from keras.layers import Dense + +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the dataset + if end_ix > len(sequences): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :-1], sequences[end_ix-1, -1] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95]) +out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))]) +# convert to [rows, columns] structure +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2, out_seq)) +# choose a number of time steps +n_steps = 3 +# convert into input/output +X, y = split_sequences(dataset, n_steps) +# the dataset knows the number of features, e.g. 2 +n_features = X.shape[2] +# define model +model = Sequential() +model.add(LSTM(50, activation='relu', input_shape=(n_steps, n_features))) +model.add(Dense(1)) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit(X, y, epochs=200, verbose=0) +# demonstrate prediction +x_input = array([[80, 85], [90, 95], [100, 105]]) +x_input = x_input.reshape((1, n_steps, n_features)) +yhat = model.predict(x_input, verbose=0) +print(yhat) +``` + +运行该示例准备数据,拟合模型并进行预测。 + +``` +[[208.13531]] +``` + +## 多个并联系列 + +另一个时间序列问题是存在多个并行时间序列并且必须为每个时间序列预测值的情况。 + +例如,给定上一节的数据: + +``` +[[ 10 15 25] + [ 20 25 45] + [ 30 35 65] + [ 40 45 85] + [ 50 55 105] + [ 60 65 125] + [ 70 75 145] + [ 80 85 165] + [ 90 95 185]] +``` + +我们可能想要预测下一个时间步的三个时间序列中的每一个的值。 + +这可以称为多变量预测。 + +同样,必须将数据分成输入/输出样本以训练模型。 + +该数据集的第一个示例是: + +输入: + +``` +10, 15, 25 +20, 25, 45 +30, 35, 65 +``` + +输出: + +``` +40, 45, 85 +``` + +下面的 _split_sequences()_ 函数将分割多个并行时间序列,其中时间步长为行,每列一个系列为所需的输入/输出形状。 + +``` +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the dataset + if end_ix > len(sequences)-1: + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :], sequences[end_ix, :] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) +``` + +我们可以在人为的问题上证明这一点;下面列出了完整的示例。 + +``` +# multivariate output data prep +from numpy import array +from numpy import hstack + +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the dataset + if end_ix > len(sequences)-1: + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :], sequences[end_ix, :] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95]) +out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))]) +# convert to [rows, columns] structure +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2, out_seq)) +# choose a number of time steps +n_steps = 3 +# convert into input/output +X, y = split_sequences(dataset, n_steps) +print(X.shape, y.shape) +# summarize the data +for i in range(len(X)): + print(X[i], y[i]) +``` + +首先运行该示例打印准备好的 X 和 y 组件的形状。 + +X 的形状是三维的,包括样品的数量(6),每个样品选择的时间步数(3),以及平行时间序列或特征的数量(3)。 + +y 的形状是二维的,正如我们可能期望的样本数量(6)和每个样本的时间变量数量(3)。 + +数据已准备好在 LSTM 模型中使用,该模型需要三维输入和每个样本的 X 和 y 分量的二维输出形状。 + +然后,打印每个样本,显示每个样本的输入和输出分量。 + +``` +(6, 3, 3) (6, 3) + +[[10 15 25] + [20 25 45] + [30 35 65]] [40 45 85] +[[20 25 45] + [30 35 65] + [40 45 85]] [ 50 55 105] +[[ 30 35 65] + [ 40 45 85] + [ 50 55 105]] [ 60 65 125] +[[ 40 45 85] + [ 50 55 105] + [ 60 65 125]] [ 70 75 145] +[[ 50 55 105] + [ 60 65 125] + [ 70 75 145]] [ 80 85 165] +[[ 60 65 125] + [ 70 75 145] + [ 80 85 165]] [ 90 95 185] +``` + +我们现在准备在这些数据上安装 LSTM 模型。 + +可以使用前一节中的任何种类的 LSTM,例如香草,堆叠,双向,CNN 或 ConvLSTM 模型。 + +我们将使用 Stacked LSTM,其中通过 _input_shape_ 参数为输入层指定时间步数和并行系列(特征)。并行序列的数量也用于指定输出层中模型预测的值的数量;再次,这是三个。 + +``` +# define model +model = Sequential() +model.add(LSTM(100, activation='relu', return_sequences=True, input_shape=(n_steps, n_features))) +model.add(LSTM(100, activation='relu')) +model.add(Dense(n_features)) +model.compile(optimizer='adam', loss='mse') +``` + +我们可以通过为每个系列提供三个时间步长的输入来预测三个并行系列中的每一个的下一个值。 + +``` +70, 75, 145 +80, 85, 165 +90, 95, 185 +``` + +用于进行单个预测的输入的形状必须是 1 个样本,3 个时间步长和 3 个特征,或者[1,3,3] + +``` +# demonstrate prediction +x_input = array([[70,75,145], [80,85,165], [90,95,185]]) +x_input = x_input.reshape((1, n_steps, n_features)) +yhat = model.predict(x_input, verbose=0) +``` + +我们希望向量输出为: + +``` +[100, 105, 205] +``` + +我们可以将所有这些结合在一起并演示下面的多变量输出时间序列预测的 Stacked LSTM。 + +``` +# multivariate output stacked lstm example +from numpy import array +from numpy import hstack +from keras.models import Sequential +from keras.layers import LSTM +from keras.layers import Dense + +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the dataset + if end_ix > len(sequences)-1: + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :], sequences[end_ix, :] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95]) +out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))]) +# convert to [rows, columns] structure +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2, out_seq)) +# choose a number of time steps +n_steps = 3 +# convert into input/output +X, y = split_sequences(dataset, n_steps) +# the dataset knows the number of features, e.g. 2 +n_features = X.shape[2] +# define model +model = Sequential() +model.add(LSTM(100, activation='relu', return_sequences=True, input_shape=(n_steps, n_features))) +model.add(LSTM(100, activation='relu')) +model.add(Dense(n_features)) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit(X, y, epochs=400, verbose=0) +# demonstrate prediction +x_input = array([[70,75,145], [80,85,165], [90,95,185]]) +x_input = x_input.reshape((1, n_steps, n_features)) +yhat = model.predict(x_input, verbose=0) +print(yhat) +``` + +运行该示例准备数据,拟合模型并进行预测。 + +``` +[[101.76599 108.730484 206.63577 ]] +``` + +## 多步 LSTM 模型 + +需要预测未来多个时间步长的时间序列预测问题可以称为多步时间序列预测。 + +具体而言,这些是预测范围或间隔超过一个时间步长的问题。 + +有两种主要类型的 LSTM 模型可用于多步预测;他们是: + +1. 向量输出模型 +2. 编码器 - 解码器模型 + +在我们查看这些模型之前,让我们首先看一下多步骤预测的数据准备。 + +### 数据准备 + +与一步预测一样,用于多步时间序列预测的时间序列必须分为带有输入和输出组件的样本。 + +输入和输出组件都将包含多个时间步长,并且可以具有或不具有相同数量的步骤。 + +例如,给定单变量时间序列: + +``` +[10, 20, 30, 40, 50, 60, 70, 80, 90] +``` + +我们可以使用最后三个时间步作为输入并预测接下来的两个时间步。 + +第一个样本如下: + +输入: + +``` +[10, 20, 30] +``` + +输出: + +``` +[40, 50] +``` + +下面的 _split_sequence()_ 函数实现了这种行为,并将给定的单变量时间序列分割为具有指定数量的输入和输出时间步长的样本。 + +``` +# split a univariate sequence into samples +def split_sequence(sequence, n_steps_in, n_steps_out): + X, y = list(), list() + for i in range(len(sequence)): + # find the end of this pattern + end_ix = i + n_steps_in + out_end_ix = end_ix + n_steps_out + # check if we are beyond the sequence + if out_end_ix > len(sequence): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequence[i:end_ix], sequence[end_ix:out_end_ix] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) +``` + +我们可以在小型设计数据集上演示此功能。 + +下面列出了完整的示例。 + +``` +# multi-step data preparation +from numpy import array + +# split a univariate sequence into samples +def split_sequence(sequence, n_steps_in, n_steps_out): + X, y = list(), list() + for i in range(len(sequence)): + # find the end of this pattern + end_ix = i + n_steps_in + out_end_ix = end_ix + n_steps_out + # check if we are beyond the sequence + if out_end_ix > len(sequence): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequence[i:end_ix], sequence[end_ix:out_end_ix] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +raw_seq = [10, 20, 30, 40, 50, 60, 70, 80, 90] +# choose a number of time steps +n_steps_in, n_steps_out = 3, 2 +# split into samples +X, y = split_sequence(raw_seq, n_steps_in, n_steps_out) +# summarize the data +for i in range(len(X)): + print(X[i], y[i]) +``` + +运行该示例将单变量系列拆分为输入和输出时间步骤,并打印每个系列的输入和输出组件。 + +``` +[10 20 30] [40 50] +[20 30 40] [50 60] +[30 40 50] [60 70] +[40 50 60] [70 80] +[50 60 70] [80 90] +``` + +既然我们知道如何为多步预测准备数据,那么让我们看看一些可以学习这种映射的 LSTM 模型。 + +### 向量输出模型 + +与其他类型的神经网络模型一样,LSTM 可以直接输出向量,可以解释为多步预测。 + +在前一节中看到这种方法是每个输出时间序列的一个时间步骤被预测为向量。 + +与前一节中单变量数据的 LSTM 一样,必须首先对准备好的样本进行重新整形。 LSTM 期望数据具有[_ 样本,时间步长,特征 _]的三维结构,在这种情况下,我们只有一个特征,因此重塑是直截了当的。 + +``` +# reshape from [samples, timesteps] into [samples, timesteps, features] +n_features = 1 +X = X.reshape((X.shape[0], X.shape[1], n_features)) +``` + +通过 _n_steps_in_ 和 _n_steps_out_ 变量中指定的输入和输出步数,我们可以定义一个多步骤时间序列预测模型。 + +可以使用任何呈现的 LSTM 模型类型,例如香草,堆叠,双向,CNN-LSTM 或 ConvLSTM。下面定义了用于多步预测的 Stacked LSTM。 + +``` +# define model +model = Sequential() +model.add(LSTM(100, activation='relu', return_sequences=True, input_shape=(n_steps_in, n_features))) +model.add(LSTM(100, activation='relu')) +model.add(Dense(n_steps_out)) +model.compile(optimizer='adam', loss='mse') +``` + +该模型可以对单个样本进行预测。我们可以通过提供输入来预测数据集末尾之后的下两个步骤: + +``` +[70, 80, 90] +``` + +我们希望预测的输出为: + +``` +[100, 110] +``` + +正如模型所预期的那样,进行预测时输入数据的单个样本的形状对于 1 个样本,输入的 3 个时间步长和单个特征必须是[1,3,1]。 + +``` +# demonstrate prediction +x_input = array([70, 80, 90]) +x_input = x_input.reshape((1, n_steps_in, n_features)) +yhat = model.predict(x_input, verbose=0) +``` + +将所有这些结合在一起,下面列出了具有单变量时间序列的用于多步预测的 Stacked LSTM。 + +``` +# univariate multi-step vector-output stacked lstm example +from numpy import array +from keras.models import Sequential +from keras.layers import LSTM +from keras.layers import Dense + +# split a univariate sequence into samples +def split_sequence(sequence, n_steps_in, n_steps_out): + X, y = list(), list() + for i in range(len(sequence)): + # find the end of this pattern + end_ix = i + n_steps_in + out_end_ix = end_ix + n_steps_out + # check if we are beyond the sequence + if out_end_ix > len(sequence): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequence[i:end_ix], sequence[end_ix:out_end_ix] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +raw_seq = [10, 20, 30, 40, 50, 60, 70, 80, 90] +# choose a number of time steps +n_steps_in, n_steps_out = 3, 2 +# split into samples +X, y = split_sequence(raw_seq, n_steps_in, n_steps_out) +# reshape from [samples, timesteps] into [samples, timesteps, features] +n_features = 1 +X = X.reshape((X.shape[0], X.shape[1], n_features)) +# define model +model = Sequential() +model.add(LSTM(100, activation='relu', return_sequences=True, input_shape=(n_steps_in, n_features))) +model.add(LSTM(100, activation='relu')) +model.add(Dense(n_steps_out)) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit(X, y, epochs=50, verbose=0) +# demonstrate prediction +x_input = array([70, 80, 90]) +x_input = x_input.reshape((1, n_steps_in, n_features)) +yhat = model.predict(x_input, verbose=0) +print(yhat) +``` + +运行示例预测并打印序列中的后两个时间步骤。 + +``` +[[100.98096 113.28924]] +``` + +### 编码器 - 解码器模型 + +专门为预测可变长度输出序列而开发的模型称为[编码器 - 解码器 LSTM](https://machinelearningmastery.com/encoder-decoder-long-short-term-memory-networks/) 。 + +该模型设计用于预测问题,其中存在输入和输出序列,即所谓的序列到序列或 seq2seq 问题,例如将文本从一种语言翻译成另一种语言。 + +该模型可用于多步时间序列预测。 + +顾名思义,该模型由两个子模型组成:编码器和解码器。 + +编码器是负责读取和解释输入序列的模型。编码器的输出是固定长度的向量,表示模型对序列的解释。编码器传统上是 Vanilla LSTM 模型,但也可以使用其他编码器模型,例如 Stacked,Bidirectional 和 CNN 模型。 + +``` +model.add(LSTM(100, activation='relu', input_shape=(n_steps_in, n_features))) +``` + +解码器使用编码器的输出作为输入。 + +首先,对输出序列中的每个所需时间步长重复一次编码器的固定长度输出。 + +``` +model.add(RepeatVector(n_steps_out)) +``` + +然后将该序列提供给 LSTM 解码器模型。模型必须为输出时间步骤中的每个值输出一个值,该值可由单个输出模型解释。 + +``` +model.add(LSTM(100, activation='relu', return_sequences=True)) +``` + +我们可以使用相同的一个或多个输出层在输出序列中进行每个一步预测。这可以通过将模型的输出部分包装在 [TimeDistributed 包装器](https://machinelearningmastery.com/timedistributed-layer-for-long-short-term-memory-networks-in-python/)中来实现。 + +``` +model.add(TimeDistributed(Dense(1))) +``` + +下面列出了用于多步时间序列预测的编码器 - 解码器模型的完整定义。 + +``` +# define model +model = Sequential() +model.add(LSTM(100, activation='relu', input_shape=(n_steps_in, n_features))) +model.add(RepeatVector(n_steps_out)) +model.add(LSTM(100, activation='relu', return_sequences=True)) +model.add(TimeDistributed(Dense(1))) +model.compile(optimizer='adam', loss='mse') +``` + +与其他 LSTM 模型一样,输入数据必须重新整形为[_ 样本,时间步长,特征 _]的预期三维形状。 + +``` +X = X.reshape((X.shape[0], X.shape[1], n_features)) +``` + +在编码器 - 解码器模型的情况下,训练数据集的输出或 y 部分也必须具有该形状。这是因为模型将使用每个输入样本的给定数量的特征预测给定数量的时间步长。 + +``` +y = y.reshape((y.shape[0], y.shape[1], n_features)) +``` + +下面列出了用于多步时间序列预测的编码器 - 解码器 LSTM 的完整示例。 + +``` +# univariate multi-step encoder-decoder lstm example +from numpy import array +from keras.models import Sequential +from keras.layers import LSTM +from keras.layers import Dense +from keras.layers import RepeatVector +from keras.layers import TimeDistributed + +# split a univariate sequence into samples +def split_sequence(sequence, n_steps_in, n_steps_out): + X, y = list(), list() + for i in range(len(sequence)): + # find the end of this pattern + end_ix = i + n_steps_in + out_end_ix = end_ix + n_steps_out + # check if we are beyond the sequence + if out_end_ix > len(sequence): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequence[i:end_ix], sequence[end_ix:out_end_ix] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +raw_seq = [10, 20, 30, 40, 50, 60, 70, 80, 90] +# choose a number of time steps +n_steps_in, n_steps_out = 3, 2 +# split into samples +X, y = split_sequence(raw_seq, n_steps_in, n_steps_out) +# reshape from [samples, timesteps] into [samples, timesteps, features] +n_features = 1 +X = X.reshape((X.shape[0], X.shape[1], n_features)) +y = y.reshape((y.shape[0], y.shape[1], n_features)) +# define model +model = Sequential() +model.add(LSTM(100, activation='relu', input_shape=(n_steps_in, n_features))) +model.add(RepeatVector(n_steps_out)) +model.add(LSTM(100, activation='relu', return_sequences=True)) +model.add(TimeDistributed(Dense(1))) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit(X, y, epochs=100, verbose=0) +# demonstrate prediction +x_input = array([70, 80, 90]) +x_input = x_input.reshape((1, n_steps_in, n_features)) +yhat = model.predict(x_input, verbose=0) +print(yhat) +``` + +运行示例预测并打印序列中的后两个时间步骤。 + +``` +[[[101.9736 + [116.213615]]] +``` + +## 多变量多步 LSTM 模型 + +在前面的部分中,我们研究了单变量,多变量和多步骤时间序列预测。 + +可以混合和匹配到目前为止针对不同问题呈现的不同类型的 LSTM 模型。这也适用于涉及多变量和多步预测的时间序列预测问题,但可能更具挑战性。 + +在本节中,我们将提供多个多步骤时间序列预测的数据准备和建模的简短示例,作为模板来缓解这一挑战,具体来说: + +1. 多输入多步输出。 +2. 多个并行输入和多步输出。 + +也许最大的绊脚石是准备数据,所以这是我们关注的重点。 + +### 多输入多步输出 + +存在多变量时间序列预测问题,其中输出序列是分开的但取决于输入时间序列,并且输出序列需要多个时间步长。 + +例如,考虑前一部分的多变量时间序列: + +``` +[[ 10 15 25] + [ 20 25 45] + [ 30 35 65] + [ 40 45 85] + [ 50 55 105] + [ 60 65 125] + [ 70 75 145] + [ 80 85 165] + [ 90 95 185]] +``` + +我们可以使用两个输入时间序列中的每一个的三个先前时间步骤来预测输出时间序列的两个时间步长。 + +输入: + +``` +10, 15 +20, 25 +30, 35 +``` + +输出: + +``` +65 +85 +``` + +下面的 _split_sequences()_ 函数实现了这种行为。 + +``` +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps_in, n_steps_out): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps_in + out_end_ix = end_ix + n_steps_out-1 + # check if we are beyond the dataset + if out_end_ix > len(sequences): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :-1], sequences[end_ix-1:out_end_ix, -1] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) +``` + +我们可以在我们设计的数据集上证明这一点。 + +下面列出了完整的示例。 + +``` +# multivariate multi-step data preparation +from numpy import array +from numpy import hstack + +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps_in, n_steps_out): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps_in + out_end_ix = end_ix + n_steps_out-1 + # check if we are beyond the dataset + if out_end_ix > len(sequences): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :-1], sequences[end_ix-1:out_end_ix, -1] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95]) +out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))]) +# convert to [rows, columns] structure +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2, out_seq)) +# choose a number of time steps +n_steps_in, n_steps_out = 3, 2 +# covert into input/output +X, y = split_sequences(dataset, n_steps_in, n_steps_out) +print(X.shape, y.shape) +# summarize the data +for i in range(len(X)): + print(X[i], y[i]) +``` + +首先运行该示例打印准备好的训练数据的形状。 + +我们可以看到样本输入部分的形状是三维的,由六个样本组成,有三个时间步长,两个变量用于两个输入时间序列。 + +样本的输出部分对于六个样本是二维的,并且每个样本的两个时间步长是预测的。 + +然后打印制备的样品以确认数据是按照我们指定的方式制备的。 + +``` +(6, 3, 2) (6, 2) + +[[10 15] + [20 25] + [30 35]] [65 85] +[[20 25] + [30 35] + [40 45]] [ 85 105] +[[30 35] + [40 45] + [50 55]] [105 125] +[[40 45] + [50 55] + [60 65]] [125 145] +[[50 55] + [60 65] + [70 75]] [145 165] +[[60 65] + [70 75] + [80 85]] [165 185] +``` + +我们现在可以开发用于多步预测的 LSTM 模型。 + +可以使用向量输出或编码器 - 解码器模型。在这种情况下,我们将使用 Stacked LSTM 演示向量输出。 + +下面列出了完整的示例。 + +``` +# multivariate multi-step stacked lstm example +from numpy import array +from numpy import hstack +from keras.models import Sequential +from keras.layers import LSTM +from keras.layers import Dense + +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps_in, n_steps_out): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps_in + out_end_ix = end_ix + n_steps_out-1 + # check if we are beyond the dataset + if out_end_ix > len(sequences): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :-1], sequences[end_ix-1:out_end_ix, -1] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95]) +out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))]) +# convert to [rows, columns] structure +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2, out_seq)) +# choose a number of time steps +n_steps_in, n_steps_out = 3, 2 +# covert into input/output +X, y = split_sequences(dataset, n_steps_in, n_steps_out) +# the dataset knows the number of features, e.g. 2 +n_features = X.shape[2] +# define model +model = Sequential() +model.add(LSTM(100, activation='relu', return_sequences=True, input_shape=(n_steps_in, n_features))) +model.add(LSTM(100, activation='relu')) +model.add(Dense(n_steps_out)) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit(X, y, epochs=200, verbose=0) +# demonstrate prediction +x_input = array([[70, 75], [80, 85], [90, 95]]) +x_input = x_input.reshape((1, n_steps_in, n_features)) +yhat = model.predict(x_input, verbose=0) +print(yhat) +``` + +运行该示例适合模型并预测输出序列的下两个时间步骤超出数据集。 + +我们希望接下来的两个步骤是:[185,205] + +这是一个具有挑战性的问题框架,数据非常少,模型的任意配置版本也很接近。 + +``` +[[188.70619 210.16513]] +``` + +### 多个并行输入和多步输出 + +并行时间序列的问题可能需要预测每个时间序列的多个时间步长。 + +例如,考虑前一部分的多变量时间序列: + +``` +[[ 10 15 25] + [ 20 25 45] + [ 30 35 65] + [ 40 45 85] + [ 50 55 105] + [ 60 65 125] + [ 70 75 145] + [ 80 85 165] + [ 90 95 185]] +``` + +我们可以使用三个时间序列中的每一个的最后三个步骤作为模型的输入,并预测三个时间序列中的每一个的下一个时间步长作为输出。 + +训练数据集中的第一个样本如下。 + +输入: + +``` +10, 15, 25 +20, 25, 45 +30, 35, 65 +``` + +输出: + +``` +40, 45, 85 +50, 55, 105 +``` + +下面的 _split_sequences()_ 函数实现了这种行为。 + +``` +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps_in, n_steps_out): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps_in + out_end_ix = end_ix + n_steps_out + # check if we are beyond the dataset + if out_end_ix > len(sequences): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :], sequences[end_ix:out_end_ix, :] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) +``` + +我们可以在小型设计数据集上演示此功能。 + +下面列出了完整的示例。 + +``` +# multivariate multi-step data preparation +from numpy import array +from numpy import hstack +from keras.models import Sequential +from keras.layers import LSTM +from keras.layers import Dense +from keras.layers import RepeatVector +from keras.layers import TimeDistributed + +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps_in, n_steps_out): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps_in + out_end_ix = end_ix + n_steps_out + # check if we are beyond the dataset + if out_end_ix > len(sequences): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :], sequences[end_ix:out_end_ix, :] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95]) +out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))]) +# convert to [rows, columns] structure +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2, out_seq)) +# choose a number of time steps +n_steps_in, n_steps_out = 3, 2 +# covert into input/output +X, y = split_sequences(dataset, n_steps_in, n_steps_out) +print(X.shape, y.shape) +# summarize the data +for i in range(len(X)): + print(X[i], y[i]) +``` + +首先运行该示例打印准备好的训练数据集的形状。 + +我们可以看到数据集的输入(X)和输出(Y)元素分别是样本数,时间步长和变量或并行时间序列的三维。 + +然后将每个系列的输入和输出元素并排打印,以便我们可以确认数据是按照我们的预期准备的。 + +``` +(5, 3, 3) (5, 2, 3) + +[[10 15 25] + [20 25 45] + [30 35 65]] [[ 40 45 85] + [ 50 55 105]] +[[20 25 45] + [30 35 65] + [40 45 85]] [[ 50 55 105] + [ 60 65 125]] +[[ 30 35 65] + [ 40 45 85] + [ 50 55 105]] [[ 60 65 125] + [ 70 75 145]] +[[ 40 45 85] + [ 50 55 105] + [ 60 65 125]] [[ 70 75 145] + [ 80 85 165]] +[[ 50 55 105] + [ 60 65 125] + [ 70 75 145]] [[ 80 85 165] + [ 90 95 185]] +``` + +我们可以使用向量输出或编码器解码器 LSTM 来模拟这个问题。在这种情况下,我们将使用编码器 - 解码器模型。 + +下面列出了完整的示例。 + +``` +# multivariate multi-step encoder-decoder lstm example +from numpy import array +from numpy import hstack +from keras.models import Sequential +from keras.layers import LSTM +from keras.layers import Dense +from keras.layers import RepeatVector +from keras.layers import TimeDistributed + +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps_in, n_steps_out): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps_in + out_end_ix = end_ix + n_steps_out + # check if we are beyond the dataset + if out_end_ix > len(sequences): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :], sequences[end_ix:out_end_ix, :] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95]) +out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))]) +# convert to [rows, columns] structure +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2, out_seq)) +# choose a number of time steps +n_steps_in, n_steps_out = 3, 2 +# covert into input/output +X, y = split_sequences(dataset, n_steps_in, n_steps_out) +# the dataset knows the number of features, e.g. 2 +n_features = X.shape[2] +# define model +model = Sequential() +model.add(LSTM(200, activation='relu', input_shape=(n_steps_in, n_features))) +model.add(RepeatVector(n_steps_out)) +model.add(LSTM(200, activation='relu', return_sequences=True)) +model.add(TimeDistributed(Dense(n_features))) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit(X, y, epochs=300, verbose=0) +# demonstrate prediction +x_input = array([[60, 65, 125], [70, 75, 145], [80, 85, 165]]) +x_input = x_input.reshape((1, n_steps_in, n_features)) +yhat = model.predict(x_input, verbose=0) +print(yhat) +``` + +运行该示例适合模型并预测超出数据集末尾的下两个时间步的三个时间步中的每一个的值。 + +我们希望这些系列和时间步骤的值如下: + +``` +90, 95, 185 +100, 105, 205 +``` + +我们可以看到模型预测合理地接近预期值。 + +``` +[[[ 91.86044 97.77231 189.66768 ] + [103.299355 109.18123 212.6863 ]]] +``` + +## 摘要 + +在本教程中,您了解了如何针对一系列标准时间序列预测问题开发一套 LSTM 模型。 + +具体来说,你学到了: + +* 如何开发 LSTM 模型进行单变量时间序列预测。 +* 如何开发多变量时间序列预测的 LSTM 模型。 +* 如何开发 LSTM 模型进行多步时间序列预测。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/how-to-develop-machine-learning-models-for-multivariate-multi-step-air-pollution-time-series-forecasting.md b/docs/dl-ts/how-to-develop-machine-learning-models-for-multivariate-multi-step-air-pollution-time-series-forecasting.md new file mode 100644 index 0000000000000000000000000000000000000000..c1ac4b6d22d95f9b687b9547f8159a0c19fa483e --- /dev/null +++ b/docs/dl-ts/how-to-develop-machine-learning-models-for-multivariate-multi-step-air-pollution-time-series-forecasting.md @@ -0,0 +1,1654 @@ +# 如何开发多元多步空气污染时间序列预测的机器学习模型 + +> 原文: [https://machinelearningmastery.com/how-to-develop-machine-learning-models-for-multivariate-multi-step-air-pollution-time-series-forecasting/](https://machinelearningmastery.com/how-to-develop-machine-learning-models-for-multivariate-multi-step-air-pollution-time-series-forecasting/) + +实时世界时间序列预测具有挑战性,其原因不仅限于问题特征,例如具有多个输入变量,需要预测多个时间步骤,以及需要对多个物理站点执行相同类型的预测。 + +EMC Data Science Global Hackathon 数据集或简称“空气质量预测”数据集描述了多个站点的天气状况,需要预测随后三天的空气质量测量结果。 + +机器学习算法可以应用于时间序列预测问题,并提供诸如处理具有嘈杂的复杂依赖性的多个输入变量的能力的好处。 + +在本教程中,您将了解如何开发用于空气污染数据的多步时间序列预测的机器学习模型。 + +完成本教程后,您将了解: + +* 如何估算缺失值并转换时间序列数据,以便可以通过监督学习算法对其进行建模。 +* 如何开发和评估一套线性算法用于多步时间序列预测。 +* 如何开发和评估一套非线性算法用于多步时间序列预测。 + +让我们开始吧。 + +![How to Develop Machine Learning Models for Multivariate Multi-Step Air Pollution Time Series Forecasting](img/5e71b25586ac103970bd9f298778c1db.jpg) + +如何开发多变量多步空气污染时间序列预测的机器学习模型 +照片由 [Eric Sc​​hmuttenmaer](https://www.flickr.com/photos/akeg/376488289/) ,保留一些权利。 + +## 教程概述 + +本教程分为九个部分;他们是: + +1. 问题描述 +2. 模型评估 +3. 机器学习建模 +4. 机器学习数据准备 +5. 模型评估测试线束 +6. 评估线性算法 +7. 评估非线性算法 +8. 调整滞后大小 + +## 问题描述 + +空气质量预测数据集描述了多个地点的天气状况,需要预测随后三天的空气质量测量结果。 + +具体而言,对于多个站点,每小时提供 8 天的温度,压力,风速和风向等天气观测。目标是预测未来 3 天在多个地点的空气质量测量。预测的提前期不是连续的;相反,必须在 72 小时预测期内预测特定提前期。他们是: + +``` ++1, +2, +3, +4, +5, +10, +17, +24, +48, +72 +``` + +此外,数据集被划分为不相交但连续的数据块,其中 8 天的数据随后是需要预测的 3 天。 + +并非所有站点或块都可以获得所有观察结果,并且并非所有站点和块都可以使用所有输出变量。必须解决大部分缺失数据。 + +该数据集被用作 2012 年 Kaggle 网站上[短期机器学习竞赛](https://www.kaggle.com/c/dsg-hackathon)(或黑客马拉松)的基础。 + +根据从参与者中扣留的真实观察结果评估竞赛的提交,并使用平均绝对误差(MAE)进行评分。提交要求在由于缺少数据而无法预测的情况下指定-1,000,000 的值。实际上,提供了一个插入缺失值的模板,并且要求所有提交都采用(模糊的是什么)。 + +获胜者在滞留测试集([私人排行榜](https://www.kaggle.com/c/dsg-hackathon/leaderboard))上使用随机森林在滞后观察中获得了 0.21058 的 MAE。该帖子中提供了此解决方案的说明: + +* [把所有东西都扔进随机森林:Ben Hamner 赢得空气质量预测黑客马拉松](http://blog.kaggle.com/2012/05/01/chucking-everything-into-a-random-forest-ben-hamner-on-winning-the-air-quality-prediction-hackathon/),2012。 + +在本教程中,我们将探索如何为可用作基线的问题开发天真预测,以确定模型是否具有该问题的技能。 + +## 模型评估 + +在我们评估天真的预测方法之前,我们必须开发一个测试工具。 + +这至少包括如何准备数据以及如何评估预测。 + +### 加载数据集 + +第一步是下载数据集并将其加载到内存中。 + +数据集可以从 Kaggle 网站免费下载。您可能必须创建一个帐户并登录才能下载数据集。 + +下载整个数据集,例如“_ 将所有 _”下载到您的工作站,并使用名为' _AirQualityPrediction_ '的文件夹解压缩当前工作目录中的存档。 + +* [EMC 数据科学全球黑客马拉松(空气质量预测)数据](https://www.kaggle.com/c/dsg-hackathon/data) + +我们的重点将是包含训练数据集的' _TrainingData.csv_ '文件,特别是块中的数据,其中每个块是八个连续的观察日和目标变量。 + +我们可以使用 Pandas [read_csv()函数](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_csv.html)将数据文件加载到内存中,并在第 0 行指定标题行。 + +``` +# load dataset +dataset = read_csv('AirQualityPrediction/TrainingData.csv', header=0) +``` + +我们可以通过'chunkID'变量(列索引 1)对数据进行分组。 + +首先,让我们获取唯一的块标识符列表。 + +``` +chunk_ids = unique(values[:, 1]) +``` + +然后,我们可以收集每个块标识符的所有行,并将它们存储在字典中以便于访问。 + +``` +chunks = dict() +# sort rows by chunk id +for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks[chunk_id] = values[selection, :] +``` + +下面定义了一个名为 _to_chunks()_ 的函数,它接受加载数据的 NumPy 数组,并将 _chunk_id_ 的字典返回到块的行。 + +``` +# split the dataset by 'chunkID', return a dict of id to rows +def to_chunks(values, chunk_ix=1): + chunks = dict() + # get the unique chunk ids + chunk_ids = unique(values[:, chunk_ix]) + # group rows by chunk id + for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks[chunk_id] = values[selection, :] + return chunks +``` + +下面列出了加载数据集并将其拆分为块的完整示例。 + +``` +# load data and split into chunks +from numpy import unique +from pandas import read_csv + +# split the dataset by 'chunkID', return a dict of id to rows +def to_chunks(values, chunk_ix=1): + chunks = dict() + # get the unique chunk ids + chunk_ids = unique(values[:, chunk_ix]) + # group rows by chunk id + for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks[chunk_id] = values[selection, :] + return chunks + +# load dataset +dataset = read_csv('AirQualityPrediction/TrainingData.csv', header=0) +# group data by chunks +values = dataset.values +chunks = to_chunks(values) +print('Total Chunks: %d' % len(chunks)) +``` + +运行该示例将打印数据集中的块数。 + +``` +Total Chunks: 208 +``` + +### 数据准备 + +既然我们知道如何加载数据并将其拆分成块,我们就可以将它们分成训练和测试数据集。 + +尽管每个块内的实际观测数量可能差异很大,但每个块的每小时观察间隔为 8 天。 + +我们可以将每个块分成前五天的训练观察和最后三天的测试。 + +每个观察都有一行称为' _position_within_chunk_ ',从 1 到 192(8 天* 24 小时)不等。因此,我们可以将此列中值小于或等于 120(5 * 24)的所有行作为训练数据,将任何大于 120 的值作为测试数据。 + +此外,任何在列车或测试分割中没有任何观察的块都可以被丢弃,因为不可行。 + +在使用朴素模型时,我们只对目标变量感兴趣,而不对输入的气象变量感兴趣。因此,我们可以删除输入数据,并使列车和测试数据仅包含每个块的 39 个目标变量,以及块和观察时间内的位置。 + +下面的 _split_train_test()_ 函数实现了这种行为;给定一个块的字典,它将每个分成列车和测试块数据。 + +``` +# split each chunk into train/test sets +def split_train_test(chunks, row_in_chunk_ix=2): + train, test = list(), list() + # first 5 days of hourly observations for train + cut_point = 5 * 24 + # enumerate chunks + for k,rows in chunks.items(): + # split chunk rows by 'position_within_chunk' + train_rows = rows[rows[:,row_in_chunk_ix] <= cut_point, :] + test_rows = rows[rows[:,row_in_chunk_ix] > cut_point, :] + if len(train_rows) == 0 or len(test_rows) == 0: + print('>dropping chunk=%d: train=%s, test=%s' % (k, train_rows.shape, test_rows.shape)) + continue + # store with chunk id, position in chunk, hour and all targets + indices = [1,2,5] + [x for x in range(56,train_rows.shape[1])] + train.append(train_rows[:, indices]) + test.append(test_rows[:, indices]) + return train, test +``` + +我们不需要整个测试数据集;相反,我们只需要在三天时间内的特定提前期进行观察,特别是提前期: + +``` ++1, +2, +3, +4, +5, +10, +17, +24, +48, +72 +``` + +其中,每个提前期相对于训练期结束。 + +首先,我们可以将这些提前期放入函数中以便于参考: + +``` +# return a list of relative forecast lead times +def get_lead_times(): + return [1, 2 ,3, 4, 5, 10, 17, 24, 48, 72] +``` + +接下来,我们可以将测试数据集缩减为仅在首选提前期的数据。 + +我们可以通过查看' _position_within_chunk_ '列并使用提前期作为距离训练数据集末尾的偏移量来实现,例如: 120 + 1,120 +2 等 + +如果我们在测试集中找到匹配的行,则保存它,否则生成一行 NaN 观测值。 + +下面的函数 _to_forecasts()_ 实现了这一点,并为每个块的每个预测提前期返回一行 NumPy 数组。 + +``` +# convert the rows in a test chunk to forecasts +def to_forecasts(test_chunks, row_in_chunk_ix=1): + # get lead times + lead_times = get_lead_times() + # first 5 days of hourly observations for train + cut_point = 5 * 24 + forecasts = list() + # enumerate each chunk + for rows in test_chunks: + chunk_id = rows[0, 0] + # enumerate each lead time + for tau in lead_times: + # determine the row in chunk we want for the lead time + offset = cut_point + tau + # retrieve data for the lead time using row number in chunk + row_for_tau = rows[rows[:,row_in_chunk_ix]==offset, :] + # check if we have data + if len(row_for_tau) == 0: + # create a mock row [chunk, position, hour] + [nan...] + row = [chunk_id, offset, nan] + [nan for _ in range(39)] + forecasts.append(row) + else: + # store the forecast row + forecasts.append(row_for_tau[0]) + return array(forecasts) +``` + +我们可以将所有这些组合在一起并将数据集拆分为训练集和测试集,并将结果保存到新文件中。 + +完整的代码示例如下所示。 + +``` +# split data into train and test sets +from numpy import unique +from numpy import nan +from numpy import array +from numpy import savetxt +from pandas import read_csv + +# split the dataset by 'chunkID', return a dict of id to rows +def to_chunks(values, chunk_ix=1): + chunks = dict() + # get the unique chunk ids + chunk_ids = unique(values[:, chunk_ix]) + # group rows by chunk id + for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks[chunk_id] = values[selection, :] + return chunks + +# split each chunk into train/test sets +def split_train_test(chunks, row_in_chunk_ix=2): + train, test = list(), list() + # first 5 days of hourly observations for train + cut_point = 5 * 24 + # enumerate chunks + for k,rows in chunks.items(): + # split chunk rows by 'position_within_chunk' + train_rows = rows[rows[:,row_in_chunk_ix] <= cut_point, :] + test_rows = rows[rows[:,row_in_chunk_ix] > cut_point, :] + if len(train_rows) == 0 or len(test_rows) == 0: + print('>dropping chunk=%d: train=%s, test=%s' % (k, train_rows.shape, test_rows.shape)) + continue + # store with chunk id, position in chunk, hour and all targets + indices = [1,2,5] + [x for x in range(56,train_rows.shape[1])] + train.append(train_rows[:, indices]) + test.append(test_rows[:, indices]) + return train, test + +# return a list of relative forecast lead times +def get_lead_times(): + return [1, 2 ,3, 4, 5, 10, 17, 24, 48, 72] + +# convert the rows in a test chunk to forecasts +def to_forecasts(test_chunks, row_in_chunk_ix=1): + # get lead times + lead_times = get_lead_times() + # first 5 days of hourly observations for train + cut_point = 5 * 24 + forecasts = list() + # enumerate each chunk + for rows in test_chunks: + chunk_id = rows[0, 0] + # enumerate each lead time + for tau in lead_times: + # determine the row in chunk we want for the lead time + offset = cut_point + tau + # retrieve data for the lead time using row number in chunk + row_for_tau = rows[rows[:,row_in_chunk_ix]==offset, :] + # check if we have data + if len(row_for_tau) == 0: + # create a mock row [chunk, position, hour] + [nan...] + row = [chunk_id, offset, nan] + [nan for _ in range(39)] + forecasts.append(row) + else: + # store the forecast row + forecasts.append(row_for_tau[0]) + return array(forecasts) + +# load dataset +dataset = read_csv('AirQualityPrediction/TrainingData.csv', header=0) +# group data by chunks +values = dataset.values +chunks = to_chunks(values) +# split into train/test +train, test = split_train_test(chunks) +# flatten training chunks to rows +train_rows = array([row for rows in train for row in rows]) +# print(train_rows.shape) +print('Train Rows: %s' % str(train_rows.shape)) +# reduce train to forecast lead times only +test_rows = to_forecasts(test) +print('Test Rows: %s' % str(test_rows.shape)) +# save datasets +savetxt('AirQualityPrediction/naive_train.csv', train_rows, delimiter=',') +savetxt('AirQualityPrediction/naive_test.csv', test_rows, delimiter=',') +``` + +运行该示例首先评论了从数据集中移除了块 69 以获得不足的数据。 + +然后我们可以看到每个列车和测试集中有 42 列,一个用于块 ID,块内位置,一天中的小时和 39 个训练变量。 + +我们还可以看到测试数据集的显着缩小版本,其中行仅在预测前置时间。 + +新的训练和测试数据集分别保存在' _naive_train.csv_ '和' _naive_test.csv_ '文件中。 + +``` +>dropping chunk=69: train=(0, 95), test=(28, 95) +Train Rows: (23514, 42) +Test Rows: (2070, 42) +``` + +### 预测评估 + +一旦做出预测,就需要对它们进行评估。 + +在评估预测时,使用更简单的格式会很有帮助。例如,我们将使用 _[chunk] [变量] [时间]_ 的三维结构,其中变量是从 0 到 38 的目标变量数,time 是从 0 到 9 的提前期索引。 + +模型有望以这种格式进行预测。 + +我们还可以重新构建测试数据集以使此数据集进行比较。下面的 _prepare_test_forecasts()_ 函数实现了这一点。 + +``` +# convert the test dataset in chunks to [chunk][variable][time] format +def prepare_test_forecasts(test_chunks): + predictions = list() + # enumerate chunks to forecast + for rows in test_chunks: + # enumerate targets for chunk + chunk_predictions = list() + for j in range(3, rows.shape[1]): + yhat = rows[:, j] + chunk_predictions.append(yhat) + chunk_predictions = array(chunk_predictions) + predictions.append(chunk_predictions) + return array(predictions) +``` + +我们将使用平均绝对误差或 MAE 来评估模型。这是在竞争中使用的度量,并且在给定目标变量的非高斯分布的情况下是合理的选择。 + +如果提前期不包含测试集中的数据(例如 _NaN_ ),则不会计算该预测的错误。如果提前期确实在测试集中有数据但预测中没有数据,那么观察的全部大小将被视为错误。最后,如果测试集具有观察值并进行预测,则绝对差值将被记录为误差。 + +_calculate_error()_ 函数实现这些规则并返回给定预测的错误。 + +``` +# calculate the error between an actual and predicted value +def calculate_error(actual, predicted): + # give the full actual value if predicted is nan + if isnan(predicted): + return abs(actual) + # calculate abs difference + return abs(actual - predicted) +``` + +错误在所有块和所有提前期之间求和,然后取平均值。 + +将计算总体 MAE,但我们还将计算每个预测提前期的 MAE。这通常有助于模型选择,因为某些模型在不同的提前期可能会有不同的表现。 + +下面的 evaluate_forecasts()函数实现了这一点,计算了 _[chunk] [variable] [time]_ 格式中提供的预测和期望值的 MAE 和每个引导时间 MAE。 + +``` +# evaluate a forecast in the format [chunk][variable][time] +def evaluate_forecasts(predictions, testset): + lead_times = get_lead_times() + total_mae, times_mae = 0.0, [0.0 for _ in range(len(lead_times))] + total_c, times_c = 0, [0 for _ in range(len(lead_times))] + # enumerate test chunks + for i in range(len(test_chunks)): + # convert to forecasts + actual = testset[i] + predicted = predictions[i] + # enumerate target variables + for j in range(predicted.shape[0]): + # enumerate lead times + for k in range(len(lead_times)): + # skip if actual in nan + if isnan(actual[j, k]): + continue + # calculate error + error = calculate_error(actual[j, k], predicted[j, k]) + # update statistics + total_mae += error + times_mae[k] += error + total_c += 1 + times_c[k] += 1 + # normalize summed absolute errors + total_mae /= total_c + times_mae = [times_mae[i]/times_c[i] for i in range(len(times_mae))] + return total_mae, times_mae +``` + +一旦我们对模型进行评估,我们就可以呈现它。 + +下面的 _summarize_error()_ 函数首先打印模型表现的一行摘要,然后创建每个预测提前期的 MAE 图。 + +``` +# summarize scores +def summarize_error(name, total_mae, times_mae): + # print summary + lead_times = get_lead_times() + formatted = ['+%d %.3f' % (lead_times[i], times_mae[i]) for i in range(len(lead_times))] + s_scores = ', '.join(formatted) + print('%s: [%.3f MAE] %s' % (name, total_mae, s_scores)) + # plot summary + pyplot.plot([str(x) for x in lead_times], times_mae, marker='.') + pyplot.show() +``` + +我们现在准备开始探索天真预测方法的表现。 + +机器学习建模 + +问题可以通过机器学习来建模。 + +大多数机器学习模型并不直接支持随时间推移的观测概念。相反,必须将滞后观察视为输入要素才能进行预测。 + +这是时间序列预测的机器学习算法的一个好处。具体来说,他们能够支持大量的输入功能。这些可能是一个或多个输入时间序列的滞后观察。 + +用于经典方法的时间序列预测的机器学习算法的其他一般好处包括: + +* 能够支持变量之间关系中的噪声特征和噪声。 +* 能够处理不相关的功能。 +* 能够支持变量之间的复杂关系。 + +该数据集面临的挑战是需要进行多步预测。机器学习方法有两种主要方法可用于进行多步预测;他们是: + +* **直接**。开发了一个单独的模型来预测每个预测提前期。 +* **递归**。开发单一模型以进行一步预测,并且递归地使用该模型,其中先前预测被用作输入以预测随后的提前期。 + +递归方法在预测短的连续交付时间块时是有意义的,而直接方法在预测不连续的交付周期时可能更有意义。直接方法可能更适合空气污染预测问题,因为我们有兴趣预测三天内 10 个连续和不连续交付时间的混合。 + +数据集有 39 个目标变量,我们根据预测的提前期为每个目标变量开发一个模型。这意味着我们需要(39 * 10)390 个机器学习模型。 + +使用机器学习算法进行时间序列预测的关键是输入数据的选择。我们可以考虑三个主要的数据来源,它们可以用作输入并映射到目标变量的每个预测提前期;他们是: + +* **单变量数据**,例如来自正在预测的目标变量的滞后观察。 +* **多变量数据**,例如来自其他变量(天气和目标)的滞后观测。 +* **元数据**,例如有关预测日期或时间的数据。 + +可以从所有块中提取数据,提供丰富的数据集,用于学习从输入到目标预测提前期的映射。 + +39 个目标变量实际上包含 14 个站点的 12 个变量。 + +由于提供数据的方式,建模的默认方法是将每个变量站点视为独立的。可以通过变量折叠数据,并为多个站点的变量使用相同的模型。 + +一些变量被故意贴错标签(例如,不同的数据使用具有相同标识符的变量)。然而,也许这些错误标记的变量可以从多站点模型中识别和排除。 + +## 机器学习数据准备 + +在我们探索此数据集的机器学习模型之前,我们必须以能够适合模型的方式准备数据。 + +这需要两个数据准备步骤: + +* 处理丢失的数据。 +* 准备输入输出模式。 + +目前,我们将关注 39 个目标变量并忽略气象和元数据。 + +### 处理丢失的数据 + +对于 39 个目标变量,块由五小时或更少的小时观察组成。 + +许多块没有全部五天的数据,并且没有任何块具有所有 39 个目标变量的数据。 + +在块没有目标变量数据的情况下,不需要预测。 + +如果一个块确实有一些目标变量的数据,但不是所有五天的值,那么该系列中将存在空白。这些间隙可能需要几个小时到一天的观察时间,有时甚至更长。 + +处理这些差距的三种候选战略如下: + +* 忽略差距。 +* 无间隙地使用数据。 +* 填补空白。 + +我们可以忽略这些差距。这样做的一个问题是,在将数据分成输入和输出时,数据不会是连续的。在训练模型时,输入将不一致,但可能意味着最后 n 小时的数据或数据分布在最后 _n_ 天。这种不一致将使学习从输入到输出的映射非常嘈杂,并且可能比模型更难以实现。 + +我们只能使用无间隙的数据。这是一个不错的选择。风险在于我们可能没有足够或足够的数据来适应模型。 + +最后,我们可以填补空白。这称为数据插补,有许多策略可用于填补空白。可能表现良好的三种方法包括: + +* 保持最后观察到的值向前(线性)。 +* 使用块中一天中的小时的中值。 +* 使用跨块的一小时的中值。 + +在本教程中,我们将使用后一种方法,并通过使用跨块的时间的中位数来填补空白。经过一点点测试后,这种方法似乎可以产生更多的训练样本和更好的模型表现。 + +对于给定变量,可能缺少由缺失行定义的观察值。具体地,每个观察具有' _position_within_chunk_ '。我们期望训练数据集中的每个块有 120 个观察值,其中“ _positions_within_chunk_ ”从 1 到 120 包含。 + +因此,我们可以为每个变量创建一个 120 _NaN_ 值的数组,使用' _positions_within_chunk_ '值标记块中的所有观察值,剩下的任何值都将被标记为 _NaN_ 。然后我们可以绘制每个变量并寻找差距。 + +下面的 _variable_to_series()_ 函数将获取目标变量的块和给定列索引的行,并将为变量返回一系列 120 个时间步长,所有可用数据都标记为来自块。 + +``` +# layout a variable with breaks in the data for missing positions +def variable_to_series(chunk_train, col_ix, n_steps=5*24): + # lay out whole series + data = [nan for _ in range(n_steps)] + # mark all available data + for i in range(len(chunk_train)): + # get position in chunk + position = int(chunk_train[i, 1] - 1) + # store data + data[position] = chunk_train[i, col_ix] + return data +``` + +我们需要为每个块计算一个小时的并行序列,我们可以使用它来为块中的每个变量输入小时特定数据。 + +给定一系列部分填充的小时,下面的 _interpolate_hours()_ 函数将填充一天中缺少的小时数。它通过找到第一个标记的小时,然后向前计数,填写一天中的小时,然后向后执行相同的操作来完成此操作。 + +``` +# interpolate series of hours (in place) in 24 hour time +def interpolate_hours(hours): + # find the first hour + ix = -1 + for i in range(len(hours)): + if not isnan(hours[i]): + ix = i + break + # fill-forward + hour = hours[ix] + for i in range(ix+1, len(hours)): + # increment hour + hour += 1 + # check for a fill + if isnan(hours[i]): + hours[i] = hour % 24 + # fill-backward + hour = hours[ix] + for i in range(ix-1, -1, -1): + # decrement hour + hour -= 1 + # check for a fill + if isnan(hours[i]): + hours[i] = hour % 24 +``` + +我们可以调用相同的 _variable_to_series()_ 函数(上面)来创建具有缺失值的系列小时(列索引 2),然后调用 _interpolate_hours()_ 来填补空白。 + +``` +# prepare sequence of hours for the chunk +hours = variable_to_series(rows, 2) +# interpolate hours +interpolate_hours(hours) +``` + +然后我们可以将时间传递给可以使用它的任何 impute 函数。 + +我们现在可以尝试在同一系列中使用相同小时填充值中的缺失值。具体来说,我们将在系列中找到所有具有相同小时的行并计算中值。 + +下面的 _impute_missing()_ 获取块中的所有行,准备好的块的一天中的小时数,以及具有变量的缺失值和变量的列索引的系列。 + +它首先检查系列是否全部缺失数据,如果是这种情况则立即返回,因为不能执行任何插补。然后,它会在系列的时间步骤中进行枚举,当它检测到没有数据的时间步长时,它会收集序列中所有行,并使用相同小时的数据并计算中值。 + +``` +# impute missing data +def impute_missing(train_chunks, rows, hours, series, col_ix): + # impute missing using the median value for hour in all series + imputed = list() + for i in range(len(series)): + if isnan(series[i]): + # collect all rows across all chunks for the hour + all_rows = list() + for rows in train_chunks: + [all_rows.append(row) for row in rows[rows[:,2]==hours[i]]] + # calculate the central tendency for target + all_rows = array(all_rows) + # fill with median value + value = nanmedian(all_rows[:, col_ix]) + if isnan(value): + value = 0.0 + imputed.append(value) + else: + imputed.append(series[i]) + return imputed +``` + +### 监督表示 + +我们需要将每个目标变量的系列变换为具有输入和输出的行,以便我们可以适应有监督的机器学习算法。 + +具体来说,我们有一个系列,如: + +``` +[1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +当使用 2 个滞后变量预测+1 的前置时间时,我们将系列分为输入( _X_ )和输出( _y_ )模式,如下所示: + +``` +X, y +1, 2, 3 +2, 3, 4 +3, 4, 5 +4, 5, 6 +5, 6, 7 +6, 7, 8 +7, 8, 9 +``` + +这首先要求我们选择一些滞后观察值作为输入。没有正确的答案;相反,测试不同的数字并查看哪些有效是一个好主意。 + +然后,我们必须将系列分为 10 个预测提前期中的每一个的监督学习格式。例如,使用 2 个滞后观察预测+24 可能如下所示: + +``` +X, y +1, 2, 24 +``` + +然后对 39 个目标变量中的每一个重复该过程。 + +然后,可以跨块来聚集为每个目标变量的每个提前期准备的模式,以提供模型的训练数据集。 + +我们还必须准备一个测试数据集。也就是说,为每个块的每个目标变量输入数据( _X_ ),以便我们可以将其用作输入来预测测试数据集中的提前期。如果我们选择滞后为 2,则测试数据集将包含每个块的每个目标变量的最后两个观察值。非常直截了当。 + +我们可以从定义一个函数开始,该函数将为给定的完整(插补)系列创建输入输出模式。 + +下面的 _supervised_for_lead_time()_ 函数将采用一系列滞后观察作为输入,预测前置时间进行预测,然后返回从该系列中抽取的输入/输出行列表。 + +``` +# created input/output patterns from a sequence +def supervised_for_lead_time(series, n_lag, lead_time): + samples = list() + # enumerate observations and create input/output patterns + for i in range(n_lag, len(series)): + end_ix = i + (lead_time - 1) + # check if can create a pattern + if end_ix >= len(series): + break + # retrieve input and output + start_ix = i - n_lag + row = series[start_ix:i] + [series[end_ix]] + samples.append(row) + return samples +``` + +理解这件作品很重要。 + +我们可以测试此函数并探索不同数量的滞后变量并预测小型测试数据集的提前期。 + +下面是一个完整的示例,它生成一系列 20 个整数并创建一个具有两个输入滞后的系列,并预测+6 前置时间。 + +``` +# test supervised to input/output patterns +from numpy import array + +# created input/output patterns from a sequence +def supervised_for_lead_time(series, n_lag, lead_time): + data = list() + # enumerate observations and create input/output patterns + for i in range(n_lag, len(series)): + end_ix = i + (lead_time - 1) + # check if can create a pattern + if end_ix >= len(series): + break + # retrieve input and output + start_ix = i - n_lag + row = series[start_ix:i] + [series[end_ix]] + data.append(row) + return array(data) + +# define test dataset +data = [x for x in range(20)] +# convert to supervised format +result = supervised_for_lead_time(data, 2, 6) +# display result +print(result) +``` + +运行该示例将打印显示滞后观察结果及其相关预测提前期的结果模式。 + +尝试使用此示例来熟悉此数据转换,因为它是使用机器学习算法建模时间序列的关键。 + +``` +[[ 0 1 7] + [ 1 2 8] + [ 2 3 9] + [ 3 4 10] + [ 4 5 11] + [ 5 6 12] + [ 6 7 13] + [ 7 8 14] + [ 8 9 15] + [ 9 10 16] + [10 11 17] + [11 12 18] + [12 13 19]] +``` + +我们现在可以为给定目标变量系列的每个预测提前期调用 _supervised_for_lead_time()_。 + +下面的 _target_to_supervised()_ 函数实现了这个功能。首先,将目标变量转换为系列,并使用上一节中开发的函数进行估算。然后为每个目标提前期创建训练样本。还创建了目标变量的测试样本。 + +然后,为该目标变量返回每个预测提前期的训练数据和测试输入数据。 + +``` +# create supervised learning data for each lead time for this target +def target_to_supervised(chunks, rows, hours, col_ix, n_lag): + train_lead_times = list() + # get series + series = variable_to_series(rows, col_ix) + if not has_data(series): + return None, [nan for _ in range(n_lag)] + # impute + imputed = impute_missing(chunks, rows, hours, series, col_ix) + # prepare test sample for chunk-variable + test_sample = array(imputed[-n_lag:]) + # enumerate lead times + lead_times = get_lead_times() + for lead_time in lead_times: + # make input/output data from series + train_samples = supervised_for_lead_time(imputed, n_lag, lead_time) + train_lead_times.append(train_samples) + return train_lead_times, test_sample +``` + +我们有件;我们现在需要定义驱动数据准备过程的函数。 + +此功能可构建训练和测试数据集。 + +该方法是枚举每个目标变量,并从所有块中收集每个提前期的训练数据。同时,我们在对测试数据集进行预测时收集所需的样本作为输入。 + +结果是具有维度 _[var] [提前期] [样本]_ 的训练数据集,其中最终维度是目标变量的预测提前期的训练样本行。该函数还返回具有维度 _[chunk] [var] [样本]_ 的测试数据集,其中最终维度是用于对块的目标变量进行预测的输入数据。 + +下面的 _data_prep()_ 函数实现了这种行为,并将块格式的数据和指定数量的滞后观察值用作输入。 + +``` +# prepare training [var][lead time][sample] and test [chunk][var][sample] +def data_prep(chunks, n_lag, n_vars=39): + lead_times = get_lead_times() + train_data = [[list() for _ in range(len(lead_times))] for _ in range(n_vars)] + test_data = [[list() for _ in range(n_vars)] for _ in range(len(chunks))] + # enumerate targets for chunk + for var in range(n_vars): + # convert target number into column number + col_ix = 3 + var + # enumerate chunks to forecast + for c_id in range(len(chunks)): + rows = chunks[c_id] + # prepare sequence of hours for the chunk + hours = variable_to_series(rows, 2) + # interpolate hours + interpolate_hours(hours) + # check for no data + if not has_data(rows[:, col_ix]): + continue + # convert series into training data for each lead time + train, test_sample = target_to_supervised(chunks, rows, hours, col_ix, n_lag) + # store test sample for this var-chunk + test_data[c_id][var] = test_sample + if train is not None: + # store samples per lead time + for lead_time in range(len(lead_times)): + # add all rows to the existing list of rows + train_data[var][lead_time].extend(train[lead_time]) + # convert all rows for each var-lead time to a numpy array + for lead_time in range(len(lead_times)): + train_data[var][lead_time] = array(train_data[var][lead_time]) + return array(train_data), array(test_data) +``` + +### 完整的例子 + +我们可以将所有内容组合在一起,并使用监督学习格式为机器学习算法准备列车和测试数据集。 + +在预测每个预测提前期时,我们将使用先前 12 小时的滞后观测作为输入。 + +然后,生成的训练和测试数据集将保存为二进制 NumPy 数组。 + +下面列出了完整的示例。 + +``` +# prepare data +from numpy import loadtxt +from numpy import nan +from numpy import isnan +from numpy import count_nonzero +from numpy import unique +from numpy import array +from numpy import nanmedian +from numpy import save + +# split the dataset by 'chunkID', return a list of chunks +def to_chunks(values, chunk_ix=0): + chunks = list() + # get the unique chunk ids + chunk_ids = unique(values[:, chunk_ix]) + # group rows by chunk id + for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks.append(values[selection, :]) + return chunks + +# return a list of relative forecast lead times +def get_lead_times(): + return [1, 2, 3, 4, 5, 10, 17, 24, 48, 72] + +# interpolate series of hours (in place) in 24 hour time +def interpolate_hours(hours): + # find the first hour + ix = -1 + for i in range(len(hours)): + if not isnan(hours[i]): + ix = i + break + # fill-forward + hour = hours[ix] + for i in range(ix+1, len(hours)): + # increment hour + hour += 1 + # check for a fill + if isnan(hours[i]): + hours[i] = hour % 24 + # fill-backward + hour = hours[ix] + for i in range(ix-1, -1, -1): + # decrement hour + hour -= 1 + # check for a fill + if isnan(hours[i]): + hours[i] = hour % 24 + +# return true if the array has any non-nan values +def has_data(data): + return count_nonzero(isnan(data)) < len(data) + +# impute missing data +def impute_missing(train_chunks, rows, hours, series, col_ix): + # impute missing using the median value for hour in all series + imputed = list() + for i in range(len(series)): + if isnan(series[i]): + # collect all rows across all chunks for the hour + all_rows = list() + for rows in train_chunks: + [all_rows.append(row) for row in rows[rows[:,2]==hours[i]]] + # calculate the central tendency for target + all_rows = array(all_rows) + # fill with median value + value = nanmedian(all_rows[:, col_ix]) + if isnan(value): + value = 0.0 + imputed.append(value) + else: + imputed.append(series[i]) + return imputed + +# layout a variable with breaks in the data for missing positions +def variable_to_series(chunk_train, col_ix, n_steps=5*24): + # lay out whole series + data = [nan for _ in range(n_steps)] + # mark all available data + for i in range(len(chunk_train)): + # get position in chunk + position = int(chunk_train[i, 1] - 1) + # store data + data[position] = chunk_train[i, col_ix] + return data + +# created input/output patterns from a sequence +def supervised_for_lead_time(series, n_lag, lead_time): + samples = list() + # enumerate observations and create input/output patterns + for i in range(n_lag, len(series)): + end_ix = i + (lead_time - 1) + # check if can create a pattern + if end_ix >= len(series): + break + # retrieve input and output + start_ix = i - n_lag + row = series[start_ix:i] + [series[end_ix]] + samples.append(row) + return samples + +# create supervised learning data for each lead time for this target +def target_to_supervised(chunks, rows, hours, col_ix, n_lag): + train_lead_times = list() + # get series + series = variable_to_series(rows, col_ix) + if not has_data(series): + return None, [nan for _ in range(n_lag)] + # impute + imputed = impute_missing(chunks, rows, hours, series, col_ix) + # prepare test sample for chunk-variable + test_sample = array(imputed[-n_lag:]) + # enumerate lead times + lead_times = get_lead_times() + for lead_time in lead_times: + # make input/output data from series + train_samples = supervised_for_lead_time(imputed, n_lag, lead_time) + train_lead_times.append(train_samples) + return train_lead_times, test_sample + +# prepare training [var][lead time][sample] and test [chunk][var][sample] +def data_prep(chunks, n_lag, n_vars=39): + lead_times = get_lead_times() + train_data = [[list() for _ in range(len(lead_times))] for _ in range(n_vars)] + test_data = [[list() for _ in range(n_vars)] for _ in range(len(chunks))] + # enumerate targets for chunk + for var in range(n_vars): + # convert target number into column number + col_ix = 3 + var + # enumerate chunks to forecast + for c_id in range(len(chunks)): + rows = chunks[c_id] + # prepare sequence of hours for the chunk + hours = variable_to_series(rows, 2) + # interpolate hours + interpolate_hours(hours) + # check for no data + if not has_data(rows[:, col_ix]): + continue + # convert series into training data for each lead time + train, test_sample = target_to_supervised(chunks, rows, hours, col_ix, n_lag) + # store test sample for this var-chunk + test_data[c_id][var] = test_sample + if train is not None: + # store samples per lead time + for lead_time in range(len(lead_times)): + # add all rows to the existing list of rows + train_data[var][lead_time].extend(train[lead_time]) + # convert all rows for each var-lead time to a numpy array + for lead_time in range(len(lead_times)): + train_data[var][lead_time] = array(train_data[var][lead_time]) + return array(train_data), array(test_data) + +# load dataset +train = loadtxt('AirQualityPrediction/naive_train.csv', delimiter=',') +test = loadtxt('AirQualityPrediction/naive_test.csv', delimiter=',') +# group data by chunks +train_chunks = to_chunks(train) +test_chunks = to_chunks(test) +# convert training data into supervised learning data +n_lag = 12 +train_data, test_data = data_prep(train_chunks, n_lag) +print(train_data.shape, test_data.shape) +# save train and test sets to file +save('AirQualityPrediction/supervised_train.npy', train_data) +save('AirQualityPrediction/supervised_test.npy', test_data) +``` + +运行示例可能需要一分钟。 + +结果是包含列车和测试数据集的两个二进制文件,我们可以在以下部分加载这些文件,以便训练和评估问题的机器学习算法。 + +## 模型评估测试线束 + +在我们开始评估算法之前,我们需要更多的测试工具元素。 + +首先,我们需要能够在训练数据上使用 scikit-learn 模型。下面的 _fit_model()_ 函数将复制模型配置,并使其适合所提供的训练数据。我们需要适应每个配置模型的许多(360)版本,因此这个函数将被调用很多。 + +``` +# fit a single model +def fit_model(model, X, y): + # clone the model configuration + local_model = clone(model) + # fit the model + local_model.fit(X, y) + return local_model +``` + +接下来,我们需要为每个变量拟合一个模型并预测提前期组合。 + +我们可以通过首先通过变量枚举训练数据集,然后通过提前期来完成此操作。然后我们可以拟合模型并将其存储在具有相同结构的列表列表中,具体为: _[var] [time] [model]_ 。 + +下面的 _fit_models()_ 函数实现了这一点。 + +``` +# fit one model for each variable and each forecast lead time [var][time][model] +def fit_models(model, train): + # prepare structure for saving models + models = [[list() for _ in range(train.shape[1])] for _ in range(train.shape[0])] + # enumerate vars + for i in range(train.shape[0]): + # enumerate lead times + for j in range(train.shape[1]): + # get data + data = train[i, j] + X, y = data[:, :-1], data[:, -1] + # fit model + local_model = fit_model(model, X, y) + models[i][j].append(local_model) + return models +``` + +拟合模型是缓慢的部分,可以从并行化中受益,例如使用 Joblib 库。这是一个扩展。 + +一旦模型适合,它们就可用于对测试数据集进行预测。 + +准备好的测试数据集首先按块组织,然后按目标变量组织。预测很快,首先要检查是否可以进行预测(我们有输入数据),如果是,则使用适当的模型作为目标变量。然后,将使用每个直接模型预测变量的 10 个预测前置时间中的每一个。 + +下面的 _make_predictions()_ 函数实现了这一点,将模型列表列表和加载的测试数据集作为参数,并返回结构 _[chunks] [var] [time]的预测数组 _。 + +``` +# return forecasts as [chunks][var][time] +def make_predictions(models, test): + lead_times = get_lead_times() + predictions = list() + # enumerate chunks + for i in range(test.shape[0]): + # enumerate variables + chunk_predictions = list() + for j in range(test.shape[1]): + # get the input pattern for this chunk and target + pattern = test[i,j] + # assume a nan forecast + forecasts = array([nan for _ in range(len(lead_times))]) + # check we can make a forecast + if has_data(pattern): + pattern = pattern.reshape((1, len(pattern))) + # forecast each lead time + forecasts = list() + for k in range(len(lead_times)): + yhat = models[j][k][0].predict(pattern) + forecasts.append(yhat[0]) + forecasts = array(forecasts) + # save forecasts fore each lead time for this variable + chunk_predictions.append(forecasts) + # save forecasts for this chunk + chunk_predictions = array(chunk_predictions) + predictions.append(chunk_predictions) + return array(predictions) +``` + +我们需要一个要评估的模型列表。 + +我们可以定义一个通用的 _get_models()_ 函数,该函数负责定义映射到已配置的 scikit-learn 模型对象的模型名称字典。 + +``` +# prepare a list of ml models +def get_models(models=dict()): + # ... + return models +``` + +最后,我们需要一个功能来推动模型评估过程。 + +给定模型字典,枚举模型,首先在训练数据上拟合模型矩阵,预测测试数据集,评估预测,并总结结果。 + +下面的 _evaluate_models()_ 函数实现了这一点。 + +``` +# evaluate a suite of models +def evaluate_models(models, train, test, actual): + for name, model in models.items(): + # fit models + fits = fit_models(model, train) + # make predictions + predictions = make_predictions(fits, test) + # evaluate forecast + total_mae, _ = evaluate_forecasts(predictions, actual) + # summarize forecast + summarize_error(name, total_mae) +``` + +我们现在拥有评估机器学习模型所需的一切。 + +## 评估线性算法 + +在本节中,我们将检查一套线性机器学习算法。 + +线性算法是假设输出是输入变量的线性函数的算法。这很像 ARIMA 等经典时间序列预测模型的假设。 + +现场检查意味着评估一套模型,以便大致了解哪些有效。我们感兴趣的是任何超过简单自回归模型 AR(2)的模型,其实现的 MAE 误差约为 0.487。 + +我们将使用默认配置测试八种线性机器学习算法;特别: + +* 线性回归 +* 套索线性回归 +* 岭回归 +* 弹性网络回归 +* 胡贝尔回归 +* Lasso Lars 线性回归 +* 被动攻击性回归 +* 随机梯度下降回归 + +我们可以在 _get_models()_ 函数中定义这些模型。 + +``` +# prepare a list of ml models +def get_models(models=dict()): + # linear models + models['lr'] = LinearRegression() + models['lasso'] = Lasso() + models['ridge'] = Ridge() + models['en'] = ElasticNet() + models['huber'] = HuberRegressor() + models['llars'] = LassoLars() + models['pa'] = PassiveAggressiveRegressor(max_iter=1000, tol=1e-3) + models['sgd'] = SGDRegressor(max_iter=1000, tol=1e-3) + print('Defined %d models' % len(models)) + return models +``` + +完整的代码示例如下所示。 + +``` +# evaluate linear algorithms +from numpy import load +from numpy import loadtxt +from numpy import nan +from numpy import isnan +from numpy import count_nonzero +from numpy import unique +from numpy import array +from sklearn.base import clone +from sklearn.linear_model import LinearRegression +from sklearn.linear_model import Lasso +from sklearn.linear_model import Ridge +from sklearn.linear_model import ElasticNet +from sklearn.linear_model import HuberRegressor +from sklearn.linear_model import LassoLars +from sklearn.linear_model import PassiveAggressiveRegressor +from sklearn.linear_model import SGDRegressor + +# split the dataset by 'chunkID', return a list of chunks +def to_chunks(values, chunk_ix=0): + chunks = list() + # get the unique chunk ids + chunk_ids = unique(values[:, chunk_ix]) + # group rows by chunk id + for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks.append(values[selection, :]) + return chunks + +# return true if the array has any non-nan values +def has_data(data): + return count_nonzero(isnan(data)) < len(data) + +# return a list of relative forecast lead times +def get_lead_times(): + return [1, 2, 3, 4, 5, 10, 17, 24, 48, 72] + +# fit a single model +def fit_model(model, X, y): + # clone the model configuration + local_model = clone(model) + # fit the model + local_model.fit(X, y) + return local_model + +# fit one model for each variable and each forecast lead time [var][time][model] +def fit_models(model, train): + # prepare structure for saving models + models = [[list() for _ in range(train.shape[1])] for _ in range(train.shape[0])] + # enumerate vars + for i in range(train.shape[0]): + # enumerate lead times + for j in range(train.shape[1]): + # get data + data = train[i, j] + X, y = data[:, :-1], data[:, -1] + # fit model + local_model = fit_model(model, X, y) + models[i][j].append(local_model) + return models + +# return forecasts as [chunks][var][time] +def make_predictions(models, test): + lead_times = get_lead_times() + predictions = list() + # enumerate chunks + for i in range(test.shape[0]): + # enumerate variables + chunk_predictions = list() + for j in range(test.shape[1]): + # get the input pattern for this chunk and target + pattern = test[i,j] + # assume a nan forecast + forecasts = array([nan for _ in range(len(lead_times))]) + # check we can make a forecast + if has_data(pattern): + pattern = pattern.reshape((1, len(pattern))) + # forecast each lead time + forecasts = list() + for k in range(len(lead_times)): + yhat = models[j][k][0].predict(pattern) + forecasts.append(yhat[0]) + forecasts = array(forecasts) + # save forecasts for each lead time for this variable + chunk_predictions.append(forecasts) + # save forecasts for this chunk + chunk_predictions = array(chunk_predictions) + predictions.append(chunk_predictions) + return array(predictions) + +# convert the test dataset in chunks to [chunk][variable][time] format +def prepare_test_forecasts(test_chunks): + predictions = list() + # enumerate chunks to forecast + for rows in test_chunks: + # enumerate targets for chunk + chunk_predictions = list() + for j in range(3, rows.shape[1]): + yhat = rows[:, j] + chunk_predictions.append(yhat) + chunk_predictions = array(chunk_predictions) + predictions.append(chunk_predictions) + return array(predictions) + +# calculate the error between an actual and predicted value +def calculate_error(actual, predicted): + # give the full actual value if predicted is nan + if isnan(predicted): + return abs(actual) + # calculate abs difference + return abs(actual - predicted) + +# evaluate a forecast in the format [chunk][variable][time] +def evaluate_forecasts(predictions, testset): + lead_times = get_lead_times() + total_mae, times_mae = 0.0, [0.0 for _ in range(len(lead_times))] + total_c, times_c = 0, [0 for _ in range(len(lead_times))] + # enumerate test chunks + for i in range(len(test_chunks)): + # convert to forecasts + actual = testset[i] + predicted = predictions[i] + # enumerate target variables + for j in range(predicted.shape[0]): + # enumerate lead times + for k in range(len(lead_times)): + # skip if actual in nan + if isnan(actual[j, k]): + continue + # calculate error + error = calculate_error(actual[j, k], predicted[j, k]) + # update statistics + total_mae += error + times_mae[k] += error + total_c += 1 + times_c[k] += 1 + # normalize summed absolute errors + total_mae /= total_c + times_mae = [times_mae[i]/times_c[i] for i in range(len(times_mae))] + return total_mae, times_mae + +# summarize scores +def summarize_error(name, total_mae): + print('%s: %.3f MAE' % (name, total_mae)) + +# prepare a list of ml models +def get_models(models=dict()): + # linear models + models['lr'] = LinearRegression() + models['lasso'] = Lasso() + models['ridge'] = Ridge() + models['en'] = ElasticNet() + models['huber'] = HuberRegressor() + models['llars'] = LassoLars() + models['pa'] = PassiveAggressiveRegressor(max_iter=1000, tol=1e-3) + models['sgd'] = SGDRegressor(max_iter=1000, tol=1e-3) + print('Defined %d models' % len(models)) + return models + +# evaluate a suite of models +def evaluate_models(models, train, test, actual): + for name, model in models.items(): + # fit models + fits = fit_models(model, train) + # make predictions + predictions = make_predictions(fits, test) + # evaluate forecast + total_mae, _ = evaluate_forecasts(predictions, actual) + # summarize forecast + summarize_error(name, total_mae) + +# load supervised datasets +train = load('AirQualityPrediction/supervised_train.npy') +test = load('AirQualityPrediction/supervised_test.npy') +print(train.shape, test.shape) +# load test chunks for validation +testset = loadtxt('AirQualityPrediction/naive_test.csv', delimiter=',') +test_chunks = to_chunks(testset) +actual = prepare_test_forecasts(test_chunks) +# prepare list of models +models = get_models() +# evaluate models +evaluate_models(models, train, test, actual) +``` + +运行该示例为每个评估的算法打印 MAE。 + +我们可以看到,与简单的 AR 模型相比,许多算法显示出技能,实现了低于 0.487 的 MAE。 + +Huber 回归似乎表现最佳(使用默认配置),实现了 0.434 的 MAE。 + +这很有趣,因为 [Huber 回归](http://scikit-learn.org/stable/modules/generated/sklearn.linear_model.HuberRegressor.html)或[稳健回归](https://en.wikipedia.org/wiki/Robust_regression)与 Huber 损失,是一种设计为对训练数据集中的异常值具有鲁棒性的方法。这可能表明其他方法可以通过更多的数据准备(例如标准化和/或异常值去除)来表现更好。 + +``` +lr: 0.454 MAE +lasso: 0.624 MAE +ridge: 0.454 MAE +en: 0.595 MAE +huber: 0.434 MAE +llars: 0.631 MAE +pa: 0.833 MAE +sgd: 0.457 MAE +``` + +## 非线性算法 + +我们可以使用相同的框架来评估一套非线性和集成机器学习算法的表现。 + +特别: + +**非线性算法** + +* k-最近邻居 +* 分类和回归树 +* 额外的树 +* 支持向量回归 + +**集合算法** + +* Adaboost 的 +* 袋装决策树 +* 随机森林 +* 额外的树木 +* 梯度增压机 + +下面的 _get_models()_ 函数定义了这九个模型。 + +``` +# prepare a list of ml models +def get_models(models=dict()): + # non-linear models + models['knn'] = KNeighborsRegressor(n_neighbors=7) + models['cart'] = DecisionTreeRegressor() + models['extra'] = ExtraTreeRegressor() + models['svmr'] = SVR() + # # ensemble models + n_trees = 100 + models['ada'] = AdaBoostRegressor(n_estimators=n_trees) + models['bag'] = BaggingRegressor(n_estimators=n_trees) + models['rf'] = RandomForestRegressor(n_estimators=n_trees) + models['et'] = ExtraTreesRegressor(n_estimators=n_trees) + models['gbm'] = GradientBoostingRegressor(n_estimators=n_trees) + print('Defined %d models' % len(models)) + return models +``` + +完整的代码清单如下。 + +``` +# spot check nonlinear algorithms +from numpy import load +from numpy import loadtxt +from numpy import nan +from numpy import isnan +from numpy import count_nonzero +from numpy import unique +from numpy import array +from sklearn.base import clone +from sklearn.neighbors import KNeighborsRegressor +from sklearn.tree import DecisionTreeRegressor +from sklearn.tree import ExtraTreeRegressor +from sklearn.svm import SVR +from sklearn.ensemble import AdaBoostRegressor +from sklearn.ensemble import BaggingRegressor +from sklearn.ensemble import RandomForestRegressor +from sklearn.ensemble import ExtraTreesRegressor +from sklearn.ensemble import GradientBoostingRegressor + +# split the dataset by 'chunkID', return a list of chunks +def to_chunks(values, chunk_ix=0): + chunks = list() + # get the unique chunk ids + chunk_ids = unique(values[:, chunk_ix]) + # group rows by chunk id + for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks.append(values[selection, :]) + return chunks + +# return true if the array has any non-nan values +def has_data(data): + return count_nonzero(isnan(data)) < len(data) + +# return a list of relative forecast lead times +def get_lead_times(): + return [1, 2, 3, 4, 5, 10, 17, 24, 48, 72] + +# fit a single model +def fit_model(model, X, y): + # clone the model configuration + local_model = clone(model) + # fit the model + local_model.fit(X, y) + return local_model + +# fit one model for each variable and each forecast lead time [var][time][model] +def fit_models(model, train): + # prepare structure for saving models + models = [[list() for _ in range(train.shape[1])] for _ in range(train.shape[0])] + # enumerate vars + for i in range(train.shape[0]): + # enumerate lead times + for j in range(train.shape[1]): + # get data + data = train[i, j] + X, y = data[:, :-1], data[:, -1] + # fit model + local_model = fit_model(model, X, y) + models[i][j].append(local_model) + return models + +# return forecasts as [chunks][var][time] +def make_predictions(models, test): + lead_times = get_lead_times() + predictions = list() + # enumerate chunks + for i in range(test.shape[0]): + # enumerate variables + chunk_predictions = list() + for j in range(test.shape[1]): + # get the input pattern for this chunk and target + pattern = test[i,j] + # assume a nan forecast + forecasts = array([nan for _ in range(len(lead_times))]) + # check we can make a forecast + if has_data(pattern): + pattern = pattern.reshape((1, len(pattern))) + # forecast each lead time + forecasts = list() + for k in range(len(lead_times)): + yhat = models[j][k][0].predict(pattern) + forecasts.append(yhat[0]) + forecasts = array(forecasts) + # save forecasts for each lead time for this variable + chunk_predictions.append(forecasts) + # save forecasts for this chunk + chunk_predictions = array(chunk_predictions) + predictions.append(chunk_predictions) + return array(predictions) + +# convert the test dataset in chunks to [chunk][variable][time] format +def prepare_test_forecasts(test_chunks): + predictions = list() + # enumerate chunks to forecast + for rows in test_chunks: + # enumerate targets for chunk + chunk_predictions = list() + for j in range(3, rows.shape[1]): + yhat = rows[:, j] + chunk_predictions.append(yhat) + chunk_predictions = array(chunk_predictions) + predictions.append(chunk_predictions) + return array(predictions) + +# calculate the error between an actual and predicted value +def calculate_error(actual, predicted): + # give the full actual value if predicted is nan + if isnan(predicted): + return abs(actual) + # calculate abs difference + return abs(actual - predicted) + +# evaluate a forecast in the format [chunk][variable][time] +def evaluate_forecasts(predictions, testset): + lead_times = get_lead_times() + total_mae, times_mae = 0.0, [0.0 for _ in range(len(lead_times))] + total_c, times_c = 0, [0 for _ in range(len(lead_times))] + # enumerate test chunks + for i in range(len(test_chunks)): + # convert to forecasts + actual = testset[i] + predicted = predictions[i] + # enumerate target variables + for j in range(predicted.shape[0]): + # enumerate lead times + for k in range(len(lead_times)): + # skip if actual in nan + if isnan(actual[j, k]): + continue + # calculate error + error = calculate_error(actual[j, k], predicted[j, k]) + # update statistics + total_mae += error + times_mae[k] += error + total_c += 1 + times_c[k] += 1 + # normalize summed absolute errors + total_mae /= total_c + times_mae = [times_mae[i]/times_c[i] for i in range(len(times_mae))] + return total_mae, times_mae + +# summarize scores +def summarize_error(name, total_mae): + print('%s: %.3f MAE' % (name, total_mae)) + +# prepare a list of ml models +def get_models(models=dict()): + # non-linear models + models['knn'] = KNeighborsRegressor(n_neighbors=7) + models['cart'] = DecisionTreeRegressor() + models['extra'] = ExtraTreeRegressor() + models['svmr'] = SVR() + # # ensemble models + n_trees = 100 + models['ada'] = AdaBoostRegressor(n_estimators=n_trees) + models['bag'] = BaggingRegressor(n_estimators=n_trees) + models['rf'] = RandomForestRegressor(n_estimators=n_trees) + models['et'] = ExtraTreesRegressor(n_estimators=n_trees) + models['gbm'] = GradientBoostingRegressor(n_estimators=n_trees) + print('Defined %d models' % len(models)) + return models + +# evaluate a suite of models +def evaluate_models(models, train, test, actual): + for name, model in models.items(): + # fit models + fits = fit_models(model, train) + # make predictions + predictions = make_predictions(fits, test) + # evaluate forecast + total_mae, _ = evaluate_forecasts(predictions, actual) + # summarize forecast + summarize_error(name, total_mae) + +# load supervised datasets +train = load('AirQualityPrediction/supervised_train.npy') +test = load('AirQualityPrediction/supervised_test.npy') +print(train.shape, test.shape) +# load test chunks for validation +testset = loadtxt('AirQualityPrediction/naive_test.csv', delimiter=',') +test_chunks = to_chunks(testset) +actual = prepare_test_forecasts(test_chunks) +# prepare list of models +models = get_models() +# evaluate models +evaluate_models(models, train, test, actual) +``` + +运行该示例,我们可以看到许多算法与自回归算法的基线相比表现良好,尽管在上一节中没有一个表现与 Huber 回归一样好。 + +支持向量回归和可能的梯度增强机器可能值得进一步研究分别达到 0.437 和 0.450 的 MAE。 + +``` +knn: 0.484 MAE +cart: 0.631 MAE +extra: 0.630 MAE +svmr: 0.437 MAE +ada: 0.717 MAE +bag: 0.471 MAE +rf: 0.470 MAE +et: 0.469 MAE +gbm: 0.450 MAE +``` + +## 调整滞后大小 + +在之前的抽查实验中,滞后观察的数量被任意固定为 12。 + +我们可以改变滞后观察的数量并评估对 MAE 的影响。一些算法可能需要更多或更少的先前观察,但是一般趋势可能跨越算法。 + +准备具有一系列不同数量的滞后观察的监督学习数据集并拟合并评估每个观察数据的 HuberRegressor。 + +我试验了以下数量的滞后观察: + +``` +[1, 3, 6, 12, 24, 36, 48] +``` + +结果如下: + +``` +1: 0.451 +3: 0.445 +6: 0.441 +12: 0.434 +24: 0.423 +36: 0.422 +48: 0.439 +``` + +下面提供了这些结果的图表。 + +![Line Plot of Number of Lag Observations vs MAE for Huber Regression](img/f164243f9642ad8edbbb2ba67e32a8e9.jpg) + +Huber 回归的滞后观测数与 MAE 的线图 + +随着滞后观测数量的增加,我们可以看到整体 MAE 降低的总趋势,至少到一个点,之后误差再次开始上升。 + +结果表明,至少对于 HuberRegressor 算法,36 个滞后观察可能是实现 MAE 为 0.422 的良好配置。 + +## 扩展 + +本节列出了一些扩展您可能希望探索的教程的想法。 + +* **数据准备**。探索简单数据准备(如标准化或统计异常值删除)是否可以提高模型表现。 +* **工程特征**。探索工程特征(如预测时段的中值)是否可以提高模型表现 +* **气象变量**。探索在模型中添加滞后气象变量是否可以提高表现。 +* **跨站点模型**。探索组合相同类型的目标变量以及跨站点重用模型是否会提高表现。 +* **算法调整**。探索调整一些表现更好的算法的超参数是否可以带来表现改进。 + +如果你探索任何这些扩展,我很想知道。 + +## 进一步阅读 + +如果您希望深入了解,本节将提供有关该主题的更多资源。 + +* [EMC 数据科学全球黑客马拉松(空气质量预测)](https://www.kaggle.com/c/dsg-hackathon/data) +* [将所有东西放入随机森林:Ben Hamner 赢得空气质量预测黑客马拉松](http://blog.kaggle.com/2012/05/01/chucking-everything-into-a-random-forest-ben-hamner-on-winning-the-air-quality-prediction-hackathon/) +* [EMC 数据科学全球黑客马拉松(空气质量预测)的获奖代码](https://github.com/benhamner/Air-Quality-Prediction-Hackathon-Winning-Model) +* [分区模型的一般方法?](https://www.kaggle.com/c/dsg-hackathon/discussion/1821) + +## 摘要 + +在本教程中,您了解了如何为空气污染数据的多步时间序列预测开发机器学习模型。 + +具体来说,你学到了: + +* 如何估算缺失值并转换时间序列数据,以便可以通过监督学习算法对其进行建模。 +* 如何开发和评估一套线性算法用于多步时间序列预测。 +* 如何开发和评估一套非线性算法用于多步时间序列预测。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/how-to-develop-multilayer-perceptron-models-for-time-series-forecasting.md b/docs/dl-ts/how-to-develop-multilayer-perceptron-models-for-time-series-forecasting.md new file mode 100644 index 0000000000000000000000000000000000000000..8c5aba987e2ae14d34b2fc6ed8a606819d2a8b15 --- /dev/null +++ b/docs/dl-ts/how-to-develop-multilayer-perceptron-models-for-time-series-forecasting.md @@ -0,0 +1,1705 @@ +# 如何开发多层感知器模型进行时间序列预测 + +> 原文: [https://machinelearningmastery.com/how-to-develop-multilayer-perceptron-models-for-time-series-forecasting/](https://machinelearningmastery.com/how-to-develop-multilayer-perceptron-models-for-time-series-forecasting/) + +多层感知器(简称 MLP)可应用于时间序列预测。 + +使用 MLP 进行时间序列预测的一个挑战是准备数据。具体而言,必须将滞后观察平坦化为特征向量。 + +在本教程中,您将了解如何针对一系列标准时间序列预测问题开发一套 MLP 模型。 + +本教程的目的是为每种类型的时间序列问题提供每个模型的独立示例,作为模板,您可以根据特定的时间序列预测问题进行复制和调整。 + +在本教程中,您将了解如何针对一系列标准时间序列预测问题开发一套多层感知器模型。 + +完成本教程后,您将了解: + +* 如何开发单变量时间序列预测的 MLP 模型。 +* 如何开发多元时间序列预测的 MLP 模型。 +* 如何开发 MLP 模型进行多步时间序列预测。 + +让我们开始吧。 + +![How to Develop Multilayer Perceptron Models for Time Series Forecasting](img/3845e70194ea7d465b653bbb0d8b993a.jpg) + +如何开发用于时间序列预测的多层感知器模型 +照片由[土地管理局](https://www.flickr.com/photos/mypubliclands/16358796247/),保留一些权利。 + +## 教程概述 + +本教程分为四个部分;他们是: + +1. 单变量 MLP 模型 +2. 多变量 MLP 模型 +3. 多步 MLP 模型 +4. 多变量多步 MLP 模型 + +## 单变量 MLP 模型 + +多层感知器(简称 MLP)可用于模拟单变量时间序列预测问题。 + +单变量时间序列是由具有时间顺序的单个观察序列组成的数据集,并且需要模型来从过去的一系列观察中学习以预测序列中的下一个值。 + +本节分为两部分;他们是: + +1. 数据准备 +2. MLP 模型 + +### 数据准备 + +在对单变量系列进行建模之前,必须准备好它。 + +MLP 模型将学习将过去观察序列作为输入映射到输出观察的函数。因此,必须将观察序列转换为模型可以从中学习的多个示例。 + +考虑给定的单变量序列: + +``` +[10, 20, 30, 40, 50, 60, 70, 80, 90] +``` + +我们可以将序列划分为多个称为样本的输入/输出模式,其中三个时间步长用作输入,一个时间步长用作正在学习的一步预测的输出。 + +``` +X, y +10, 20, 30 40 +20, 30, 40 50 +30, 40, 50 60 +... +``` + +下面的 _split_sequence()_ 函数实现了这种行为,并将给定的单变量序列分成多个样本,其中每个样本具有指定的时间步长,输出是单个时间步长。 + +``` +# split a univariate sequence into samples +def split_sequence(sequence, n_steps): + X, y = list(), list() + for i in range(len(sequence)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the sequence + if end_ix > len(sequence)-1: + break + # gather input and output parts of the pattern + seq_x, seq_y = sequence[i:end_ix], sequence[end_ix] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) +``` + +我们可以在上面的小型人为数据集上演示这个功能。 + +下面列出了完整的示例。 + +``` +# univariate data preparation +from numpy import array + +# split a univariate sequence into samples +def split_sequence(sequence, n_steps): + X, y = list(), list() + for i in range(len(sequence)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the sequence + if end_ix > len(sequence)-1: + break + # gather input and output parts of the pattern + seq_x, seq_y = sequence[i:end_ix], sequence[end_ix] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +raw_seq = [10, 20, 30, 40, 50, 60, 70, 80, 90] +# choose a number of time steps +n_steps = 3 +# split into samples +X, y = split_sequence(raw_seq, n_steps) +# summarize the data +for i in range(len(X)): + print(X[i], y[i]) +``` + +运行该示例将单变量系列分成六个样本,其中每个样本具有三个输入时间步长和一个输出时间步长。 + +``` +[10 20 30] 40 +[20 30 40] 50 +[30 40 50] 60 +[40 50 60] 70 +[50 60 70] 80 +[60 70 80] 90 +``` + +现在我们已经知道如何准备用于建模的单变量系列,让我们看看开发一个可以学习输入到输出的映射的 MLP 模型。 + +### MLP 模型 + +简单的 MLP 模型具有单个隐藏的节点层,以及用于进行预测的输出层。 + +我们可以如下定义用于单变量时间序列预测的 MLP。 + +``` +# define model +model = Sequential() +model.add(Dense(100, activation='relu', input_dim=n_steps)) +model.add(Dense(1)) +model.compile(optimizer='adam', loss='mse') +``` + +定义中重要的是输入的形状;这就是模型所期望的每个样本在时间步数方面的输入。 + +输入的时间步数是我们在准备数据集时选择的数字,作为 _split_sequence()_ 函数的参数。 + +每个样本的输入维度在第一个隐藏层定义的 _input_dim_ 参数中指定。从技术上讲,模型将每个时间步骤视为单独的特征而不是单独的时间步骤。 + +我们几乎总是有多个样本,因此,模型将期望训练数据的输入组件具有尺寸或形状: + +``` +[samples, features] +``` + +我们在上一节中的 _split_sequence()_ 函数输出 _X_ ,形状 _[样本,特征]_ 准备用于建模。 + +使用随机梯度下降的有效 [Adam 版本拟合该模型,并使用均方误差或' _mse_ ',损失函数进行优化。](https://machinelearningmastery.com/adam-optimization-algorithm-for-deep-learning/) + +定义模型后,我们可以将其放在训练数据集上。 + +``` +# fit model +model.fit(X, y, epochs=2000, verbose=0) +``` + +在模型拟合后,我们可以使用它来进行预测。 + +我们可以通过提供输入来预测序列中的下一个值: + +``` +[70, 80, 90] +``` + +并期望模型预测如下: + +``` +[100] +``` + +模型期望输入形状为 _[样本,特征]_ 为二维,因此,我们必须在进行预测之前重新整形单个输入样本,例如,对于 1 个样本,形状为[1,3]和 3 个时间步骤用作输入功能。 + +``` +# demonstrate prediction +x_input = array([70, 80, 90]) +x_input = x_input.reshape((1, n_steps)) +yhat = model.predict(x_input, verbose=0) +``` + +我们可以将所有这些结合在一起并演示如何为单变量时间序列预测开发 MLP 并进行单一预测。 + +``` +# univariate mlp example +from numpy import array +from keras.models import Sequential +from keras.layers import Dense + +# split a univariate sequence into samples +def split_sequence(sequence, n_steps): + X, y = list(), list() + for i in range(len(sequence)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the sequence + if end_ix > len(sequence)-1: + break + # gather input and output parts of the pattern + seq_x, seq_y = sequence[i:end_ix], sequence[end_ix] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +raw_seq = [10, 20, 30, 40, 50, 60, 70, 80, 90] +# choose a number of time steps +n_steps = 3 +# split into samples +X, y = split_sequence(raw_seq, n_steps) +# define model +model = Sequential() +model.add(Dense(100, activation='relu', input_dim=n_steps)) +model.add(Dense(1)) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit(X, y, epochs=2000, verbose=0) +# demonstrate prediction +x_input = array([70, 80, 90]) +x_input = x_input.reshape((1, n_steps)) +yhat = model.predict(x_input, verbose=0) +print(yhat) +``` + +运行该示例准备数据,拟合模型并进行预测。 + +鉴于算法的随机性,您的结果可能会有所不同;尝试运行几次这个例子。 + +我们可以看到模型预测序列中的下一个值。 + +``` +[[100.0109]] +``` + +## 多变量 MLP 模型 + +多变量时间序列数据是指每个时间步长有多个观察值的数据。 + +对于多变量时间序列数据,我们可能需要两种主要模型;他们是: + +1. 多输入系列。 +2. 多个并联系列。 + +让我们依次看看每一个。 + +### 多输入系列 + +问题可能有两个或更多并行输入时间序列和输出时间序列,这取决于输入时间序列。 + +输入时间序列是平行的,因为每个系列在同一时间步骤都有观察。 + +我们可以通过两个并行输入时间序列的简单示例来演示这一点,其中输出序列是输入序列的简单添加。 + +``` +# define input sequence +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95]) +out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))]) +``` + +我们可以将这三个数据数组重新整形为单个数据集,其中每一行都是一个时间步,每列是一个单独的时间序列。这是将并行时间序列存储在 CSV 文件中的标准方法。 + +``` +# convert to [rows, columns] structure +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2, out_seq)) +``` + +下面列出了完整的示例。 + +``` +# multivariate data preparation +from numpy import array +from numpy import hstack +# define input sequence +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95]) +out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))]) +# convert to [rows, columns] structure +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2, out_seq)) +print(dataset) +``` + +运行该示例将打印数据集,每个时间步长为一行,两个输入和一个输出并行时间序列分别为一列。 + +``` +[[ 10 15 25] + [ 20 25 45] + [ 30 35 65] + [ 40 45 85] + [ 50 55 105] + [ 60 65 125] + [ 70 75 145] + [ 80 85 165] + [ 90 95 185]] +``` + +与单变量时间序列一样,我们必须将这些数据组织成具有输入和输出样本的样本。 + +我们需要将数据分成样本,保持两个输入序列的观察顺序。 + +如果我们选择三个输入时间步长,那么第一个样本将如下所示: + +输入: + +``` +10, 15 +20, 25 +30, 35 +``` + +输出: + +``` +65 +``` + +也就是说,每个并行系列的前三个时间步长被提供作为模型的输入,并且模型将其与第三时间步骤的输出系列中的值相关联,在这种情况下为 65。 + +我们可以看到,在将时间序列转换为输入/输出样本以训练模型时,我们将不得不从输出时间序列中丢弃一些值,其中我们在先前时间步骤中没有输入时间序列中的值。反过来,选择输入时间步数的大小将对使用多少训练数据产生重要影响。 + +我们可以定义一个名为 _split_sequences()_ 的函数,该函数将采用数据集,因为我们已经为时间步长和行定义了并行序列和返回输入/输出样本的列。 + +``` +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the dataset + if end_ix > len(sequences): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :-1], sequences[end_ix-1, -1] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) +``` + +我们可以使用每个输入时间序列的三个时间步长作为输入在我们的数据集上测试此函数。 + +下面列出了完整的示例。 + +``` +# multivariate data preparation +from numpy import array +from numpy import hstack + +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the dataset + if end_ix > len(sequences): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :-1], sequences[end_ix-1, -1] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95]) +out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))]) +# convert to [rows, columns] structure +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2, out_seq)) +# choose a number of time steps +n_steps = 3 +# convert into input/output +X, y = split_sequences(dataset, n_steps) +print(X.shape, y.shape) +# summarize the data +for i in range(len(X)): + print(X[i], y[i]) +``` + +首先运行该示例将打印 X 和 y 组件的形状。 + +我们可以看到 X 组件具有三维结构。 + +第一个维度是样本数,在本例中为 7.第二个维度是每个样本的时间步数,在这种情况下为 3,即为函数指定的值。最后,最后一个维度指定并行时间序列的数量或变量的数量,在这种情况下,两个并行序列为 2。 + +然后我们可以看到每个样本的输入和输出都被打印出来,显示了两个输入序列中每个样本的三个时间步长以及每个样本的相关输出。 + +``` +(7, 3, 2) (7,) + +[[10 15] + [20 25] + [30 35]] 65 +[[20 25] + [30 35] + [40 45]] 85 +[[30 35] + [40 45] + [50 55]] 105 +[[40 45] + [50 55] + [60 65]] 125 +[[50 55] + [60 65] + [70 75]] 145 +[[60 65] + [70 75] + [80 85]] 165 +[[70 75] + [80 85] + [90 95]] 185 +``` + +在我们可以在这些数据上拟合 MLP 之前,我们必须平整输入样本的形状。 + +MLP 要求每个样本的输入部分的形状是向量。使用多变量输入,我们将有多个向量,每个时间步长一个。 + +我们可以展平每个输入样本的时间结构,以便: + +``` +[[10 15] +[20 25] +[30 35]] +``` + +变为: + +``` +[10, 15, 20, 25, 30, 35] +``` + +首先,我们可以计算每个输入向量的长度,作为时间步数乘以要素数或时间序列数。然后我们可以使用此向量大小来重塑输入。 + +``` +# flatten input +n_input = X.shape[1] * X.shape[2] +X = X.reshape((X.shape[0], n_input)) +``` + +我们现在可以为多变量输入定义 MLP 模型,其中向量长度用于输入维度参数。 + +``` +# define model +model = Sequential() +model.add(Dense(100, activation='relu', input_dim=n_input)) +model.add(Dense(1)) +model.compile(optimizer='adam', loss='mse') +``` + +在进行预测时,模型需要两个输入时间序列的三个时间步长。 + +我们可以预测输出系列中的下一个值,证明输入值: + +``` +80, 85 +90, 95 +100, 105 +``` + +具有 3 个时间步长和 2 个变量的 1 个样本的形状将是[1,3,2]。我们必须再次将其重塑为 1 个样本,其中包含 6 个元素的向量或[1,6] + +我们希望序列中的下一个值为 100 + 105 或 205。 + +``` +# demonstrate prediction +x_input = array([[80, 85], [90, 95], [100, 105]]) +x_input = x_input.reshape((1, n_input)) +yhat = model.predict(x_input, verbose=0) +``` + +下面列出了完整的示例。 + +``` +# multivariate mlp example +from numpy import array +from numpy import hstack +from keras.models import Sequential +from keras.layers import Dense + +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the dataset + if end_ix > len(sequences): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :-1], sequences[end_ix-1, -1] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95]) +out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))]) +# convert to [rows, columns] structure +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2, out_seq)) +# choose a number of time steps +n_steps = 3 +# convert into input/output +X, y = split_sequences(dataset, n_steps) +# flatten input +n_input = X.shape[1] * X.shape[2] +X = X.reshape((X.shape[0], n_input)) +# define model +model = Sequential() +model.add(Dense(100, activation='relu', input_dim=n_input)) +model.add(Dense(1)) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit(X, y, epochs=2000, verbose=0) +# demonstrate prediction +x_input = array([[80, 85], [90, 95], [100, 105]]) +x_input = x_input.reshape((1, n_input)) +yhat = model.predict(x_input, verbose=0) +print(yhat) +``` + +运行该示例准备数据,拟合模型并进行预测。 + +``` +[[205.04436]] +``` + +还有另一种更精细的方法来模拟问题。 + +每个输入序列可以由单独的 MLP 处理,并且可以在对输出序列进行预测之前组合这些子模型中的每一个的输出。 + +我们可以将其称为多头输入 MLP 模型。根据正在建模的问题的具体情况,它可以提供更大的灵活性或更好的表现。 + +可以使用 [Keras 功能 API](https://machinelearningmastery.com/keras-functional-api-deep-learning/) 在 Keras 中定义此类型的模型。 + +首先,我们可以将第一个输入模型定义为 MLP,其输入层需要具有 _n_steps_ 特征的向量。 + +``` +# first input model +visible1 = Input(shape=(n_steps,)) +dense1 = Dense(100, activation='relu')(visible1) +``` + +我们可以以相同的方式定义第二个输入子模型。 + +``` +# second input model +visible2 = Input(shape=(n_steps,)) +dense2 = Dense(100, activation='relu')(visible2) +``` + +既然已经定义了两个输入子模型,我们可以将每个模型的输出合并为一个长向量,可以在对输出序列进行预测之前对其进行解释。 + +``` +# merge input models +merge = concatenate([dense1, dense2]) +output = Dense(1)(merge) +``` + +然后我们可以将输入和输出联系在一起。 + +``` +model = Model(inputs=[visible1, visible2], outputs=output) +``` + +下图提供了该模型外观的示意图,包括每层输入和输出的形状。 + +![Plot of Multi-Headed MLP for Multivariate Time Series Forecasting](img/6588c295af3e1872fa8dc390b20a1c2b.jpg) + +多元时间序列预测的多头 MLP 图 + +此模型要求输入作为两个元素的列表提供,其中列表中的每个元素包含一个子模型的数据。 + +为了实现这一点,我们可以将 3D 输入数据分成两个独立的输入数据阵列:即从一个形状为[7,3,2]的阵列到两个形状为[7,3]的 2D 阵列 + +``` +# separate input data +X1 = X[:, :, 0] +X2 = X[:, :, 1] +``` + +然后可以提供这些数据以适合模型。 + +``` +# fit model +model.fit([X1, X2], y, epochs=2000, verbose=0) +``` + +类似地,我们必须在进行单个一步预测时将单个样本的数据准备为两个单独的二维数组。 + +``` +x_input = array([[80, 85], [90, 95], [100, 105]]) +x1 = x_input[:, 0].reshape((1, n_steps)) +x2 = x_input[:, 1].reshape((1, n_steps)) +``` + +我们可以将所有这些结合在一起;下面列出了完整的示例。 + +``` +# multivariate mlp example +from numpy import array +from numpy import hstack +from keras.models import Model +from keras.layers import Input +from keras.layers import Dense +from keras.layers.merge import concatenate + +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the dataset + if end_ix > len(sequences): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :-1], sequences[end_ix-1, -1] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95]) +out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))]) +# convert to [rows, columns] structure +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2, out_seq)) +# choose a number of time steps +n_steps = 3 +# convert into input/output +X, y = split_sequences(dataset, n_steps) +# separate input data +X1 = X[:, :, 0] +X2 = X[:, :, 1] +# first input model +visible1 = Input(shape=(n_steps,)) +dense1 = Dense(100, activation='relu')(visible1) +# second input model +visible2 = Input(shape=(n_steps,)) +dense2 = Dense(100, activation='relu')(visible2) +# merge input models +merge = concatenate([dense1, dense2]) +output = Dense(1)(merge) +model = Model(inputs=[visible1, visible2], outputs=output) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit([X1, X2], y, epochs=2000, verbose=0) +# demonstrate prediction +x_input = array([[80, 85], [90, 95], [100, 105]]) +x1 = x_input[:, 0].reshape((1, n_steps)) +x2 = x_input[:, 1].reshape((1, n_steps)) +yhat = model.predict([x1, x2], verbose=0) +print(yhat) +``` + +运行该示例准备数据,拟合模型并进行预测。 + +``` +[[206.05022]] +``` + +### 多个并联系列 + +另一个时间序列问题是存在多个并行时间序列并且必须为每个时间序列预测值的情况。 + +例如,给定上一节的数据: + +``` +[[ 10 15 25] + [ 20 25 45] + [ 30 35 65] + [ 40 45 85] + [ 50 55 105] + [ 60 65 125] + [ 70 75 145] + [ 80 85 165] + [ 90 95 185]] +``` + +我们可能想要预测下一个时间步的三个时间序列中的每一个的值。 + +这可以称为多变量预测。 + +同样,必须将数据分成输入/输出样本以训练模型。 + +该数据集的第一个示例是: + +输入: + +``` +10, 15, 25 +20, 25, 45 +30, 35, 65 +``` + +输出: + +``` +40, 45, 85 +``` + +下面的 _split_sequences()_ 函数将分割多个并行时间序列,其中时间步长为行,每列一个系列为所需的输入/输出形状。 + +``` +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the dataset + if end_ix > len(sequences)-1: + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :], sequences[end_ix, :] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) +``` + +我们可以在人为的问题上证明这一点;下面列出了完整的示例。 + +``` +# multivariate output data prep +from numpy import array +from numpy import hstack + +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the dataset + if end_ix > len(sequences)-1: + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :], sequences[end_ix, :] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95]) +out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))]) +# convert to [rows, columns] structure +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2, out_seq)) +# choose a number of time steps +n_steps = 3 +# convert into input/output +X, y = split_sequences(dataset, n_steps) +print(X.shape, y.shape) +# summarize the data +for i in range(len(X)): + print(X[i], y[i]) +``` + +首先运行该实例打印制备的 _X_ 和 _y_ 组分的形状。 + +_X_ 的形状是三维的,包括样品的数量(6),每个样品选择的时间步数(3),以及平行时间序列或特征的数量(3)。 + +_y_ 的形状是二维的,正如我们对样本数(6)和每个样本预测的时间变量数(3)所预期的那样。 + +然后,打印每个样本,显示每个样本的输入和输出分量。 + +``` +(6, 3, 3) (6, 3) + +[[10 15 25] + [20 25 45] + [30 35 65]] [40 45 85] +[[20 25 45] + [30 35 65] + [40 45 85]] [ 50 55 105] +[[ 30 35 65] + [ 40 45 85] + [ 50 55 105]] [ 60 65 125] +[[ 40 45 85] + [ 50 55 105] + [ 60 65 125]] [ 70 75 145] +[[ 50 55 105] + [ 60 65 125] + [ 70 75 145]] [ 80 85 165] +[[ 60 65 125] + [ 70 75 145] + [ 80 85 165]] [ 90 95 185] +``` + +我们现在准备在这些数据上安装 MLP 模型。 + +与之前的多变量输入情况一样,我们必须将输入数据样本的三维结构展平为[_ 样本,特征 _]的二维结构,其中滞后观察被模型视为特征。 + +``` +# flatten input +n_input = X.shape[1] * X.shape[2] +X = X.reshape((X.shape[0], n_input)) +``` + +模型输出将是一个向量,三个不同时间序列中的每一个都有一个元素。 + +``` +n_output = y.shape[1] +``` + +我们现在可以定义我们的模型,使用输入层的展平向量长度和进行预测时的向量长度作为向量长度。 + +``` +# define model +model = Sequential() +model.add(Dense(100, activation='relu', input_dim=n_input)) +model.add(Dense(n_output)) +model.compile(optimizer='adam', loss='mse') +``` + +我们可以通过为每个系列提供三个时间步长的输入来预测三个并行系列中的每一个的下一个值。 + +``` +70, 75, 145 +80, 85, 165 +90, 95, 185 +``` + +用于进行单个预测的输入的形状必须是 1 个样本,3 个时间步长和 3 个特征,或者[1,3,3]。同样,我们可以将其展平为[1,6]以满足模型的期望。 + +我们希望向量输出为: + +``` +[100, 105, 205] +``` + +``` +# demonstrate prediction +x_input = array([[70,75,145], [80,85,165], [90,95,185]]) +x_input = x_input.reshape((1, n_input)) +yhat = model.predict(x_input, verbose=0) +``` + +我们可以将所有这些结合在一起并演示下面的多变量输出时间序列预测的 MLP。 + +``` +# multivariate output mlp example +from numpy import array +from numpy import hstack +from keras.models import Sequential +from keras.layers import Dense + +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the dataset + if end_ix > len(sequences)-1: + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :], sequences[end_ix, :] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95]) +out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))]) +# convert to [rows, columns] structure +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2, out_seq)) +# choose a number of time steps +n_steps = 3 +# convert into input/output +X, y = split_sequences(dataset, n_steps) +# flatten input +n_input = X.shape[1] * X.shape[2] +X = X.reshape((X.shape[0], n_input)) +n_output = y.shape[1] +# define model +model = Sequential() +model.add(Dense(100, activation='relu', input_dim=n_input)) +model.add(Dense(n_output)) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit(X, y, epochs=2000, verbose=0) +# demonstrate prediction +x_input = array([[70,75,145], [80,85,165], [90,95,185]]) +x_input = x_input.reshape((1, n_input)) +yhat = model.predict(x_input, verbose=0) +print(yhat) +``` + +运行该示例准备数据,拟合模型并进行预测。 + +``` +[[100.95039 107.541306 206.81033 ]] +``` + +与多输入系列一样,还有另一种更精细的方法来模拟问题。 + +每个输出系列都可以由单独的输出 MLP 模型处理。 + +我们可以将其称为多输出 MLP 模型。根据正在建模的问题的具体情况,它可以提供更大的灵活性或更好的表现。 + +可以使用 [Keras 功能 API](https://machinelearningmastery.com/keras-functional-api-deep-learning/) 在 Keras 中定义此类型的模型。 + +首先,我们可以将输入模型定义为 MLP,其输入层需要平坦的特征向量。 + +``` +# define model +visible = Input(shape=(n_input,)) +dense = Dense(100, activation='relu')(visible) +``` + +然后,我们可以为我们希望预测的三个系列中的每一个定义一个输出层,其中每个输出子模型将预测单个时间步长。 + +``` +# define output 1 +output1 = Dense(1)(dense) +# define output 2 +output2 = Dense(1)(dense) +# define output 2 +output3 = Dense(1)(dense) +``` + +然后,我们可以将输入和输出层组合到一个模型中。 + +``` +# tie together +model = Model(inputs=visible, outputs=[output1, output2, output3]) +model.compile(optimizer='adam', loss='mse') +``` + +为了使模型架构清晰,下面的示意图清楚地显示了模型的三个独立输出层以及每个层的输入和输出形状。 + +![Plot of Multi-Output MLP for Multivariate Time Series Forecasting](img/ebee4c19d3410fa4364b1a96ee8f1bd3.jpg) + +多元时间序列预测的多输出 MLP 图 + +在训练模型时,每个样本需要三个独立的输出阵列。 + +我们可以通过将具有形状[7,3]的输出训练数据转换为具有形状[7,1]的三个阵列来实现这一点。 + +``` +# separate output +y1 = y[:, 0].reshape((y.shape[0], 1)) +y2 = y[:, 1].reshape((y.shape[0], 1)) +y3 = y[:, 2].reshape((y.shape[0], 1)) +``` + +可以在训练期间将这些阵列提供给模型。 + +``` +# fit model +model.fit(X, [y1,y2,y3], epochs=2000, verbose=0) +``` + +将所有这些结合在一起,下面列出了完整的示例。 + +``` +# multivariate output mlp example +from numpy import array +from numpy import hstack +from keras.models import Model +from keras.layers import Input +from keras.layers import Dense + +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps + # check if we are beyond the dataset + if end_ix > len(sequences)-1: + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :], sequences[end_ix, :] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95]) +out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))]) +# convert to [rows, columns] structure +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2, out_seq)) +# choose a number of time steps +n_steps = 3 +# convert into input/output +X, y = split_sequences(dataset, n_steps) +# flatten input +n_input = X.shape[1] * X.shape[2] +X = X.reshape((X.shape[0], n_input)) +# separate output +y1 = y[:, 0].reshape((y.shape[0], 1)) +y2 = y[:, 1].reshape((y.shape[0], 1)) +y3 = y[:, 2].reshape((y.shape[0], 1)) +# define model +visible = Input(shape=(n_input,)) +dense = Dense(100, activation='relu')(visible) +# define output 1 +output1 = Dense(1)(dense) +# define output 2 +output2 = Dense(1)(dense) +# define output 2 +output3 = Dense(1)(dense) +# tie together +model = Model(inputs=visible, outputs=[output1, output2, output3]) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit(X, [y1,y2,y3], epochs=2000, verbose=0) +# demonstrate prediction +x_input = array([[70,75,145], [80,85,165], [90,95,185]]) +x_input = x_input.reshape((1, n_input)) +yhat = model.predict(x_input, verbose=0) +print(yhat) +``` + +运行该示例准备数据,拟合模型并进行预测。 + +``` +[array([[100.86121]], dtype=float32), +array([[105.14738]], dtype=float32), +array([[205.97507]], dtype=float32)] +``` + +## 多步 MLP 模型 + +实际上,MLP 模型在预测表示不同输出变量的向量输出(如前例中所示)或表示一个变量的多个时间步长的向量输出方面几乎没有差别。 + +然而,训练数据的编制方式存在细微而重要的差异。在本节中,我们将演示使用向量模型开发多步预测模型的情况。 + +在我们查看模型的细节之前,让我们首先看一下多步预测的数据准备。 + +### 数据准备 + +与一步预测一样,用于多步时间序列预测的时间序列必须分为带有输入和输出组件的样本。 + +输入和输出组件都将包含多个时间步长,并且可以具有或不具有相同数量的步骤。 + +例如,给定单变量时间序列: + +``` +[10, 20, 30, 40, 50, 60, 70, 80, 90] +``` + +我们可以使用最后三个时间步作为输入并预测接下来的两个时间步。 + +第一个样本如下: + +输入: + +``` +[10, 20, 30] +``` + +输出: + +``` +[40, 50] +``` + +下面的 _split_sequence()_ 函数实现了这种行为,并将给定的单变量时间序列分割为具有指定数量的输入和输出时间步长的样本。 + +``` +# split a univariate sequence into samples +def split_sequence(sequence, n_steps_in, n_steps_out): + X, y = list(), list() + for i in range(len(sequence)): + # find the end of this pattern + end_ix = i + n_steps_in + out_end_ix = end_ix + n_steps_out + # check if we are beyond the sequence + if out_end_ix > len(sequence): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequence[i:end_ix], sequence[end_ix:out_end_ix] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) +``` + +我们可以在小型设计数据集上演示此功能。 + +下面列出了完整的示例。 + +``` +# multi-step data preparation +from numpy import array + +# split a univariate sequence into samples +def split_sequence(sequence, n_steps_in, n_steps_out): + X, y = list(), list() + for i in range(len(sequence)): + # find the end of this pattern + end_ix = i + n_steps_in + out_end_ix = end_ix + n_steps_out + # check if we are beyond the sequence + if out_end_ix > len(sequence): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequence[i:end_ix], sequence[end_ix:out_end_ix] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +raw_seq = [10, 20, 30, 40, 50, 60, 70, 80, 90] +# choose a number of time steps +n_steps_in, n_steps_out = 3, 2 +# split into samples +X, y = split_sequence(raw_seq, n_steps_in, n_steps_out) +# summarize the data +for i in range(len(X)): + print(X[i], y[i]) +``` + +运行该示例将单变量系列拆分为输入和输出时间步骤,并打印每个系列的输入和输出组件。 + +``` +[10 20 30] [40 50] +[20 30 40] [50 60] +[30 40 50] [60 70] +[40 50 60] [70 80] +[50 60 70] [80 90] +``` + +既然我们知道如何为多步预测准备数据,那么让我们看一下可以学习这种映射的 MLP 模型。 + +### 向量输出模型 + +MLP 可以直接输出向量,可以解释为多步预测。 + +在前一节中看到这种方法是每个输出时间序列的一个时间步骤被预测为向量。 + +通过 _n_steps_in_ 和 _n_steps_out_ 变量中指定的输入和输出步数,我们可以定义一个多步骤时间序列预测模型。 + +``` +# define model +model = Sequential() +model.add(Dense(100, activation='relu', input_dim=n_steps_in)) +model.add(Dense(n_steps_out)) +model.compile(optimizer='adam', loss='mse') +``` + +该模型可以对单个样本进行预测。我们可以通过提供输入来预测数据集末尾之后的下两个步骤: + +``` +[70, 80, 90] +``` + +我们希望预测的输出为: + +``` +[100, 110] +``` + +正如模型所预期的那样,进行预测时输入数据的单个样本的形状对于输入和单个特征的 1 个样本和 3 个时间步长(特征)必须是[1,3]。 + +``` +# demonstrate prediction +x_input = array([70, 80, 90]) +x_input = x_input.reshape((1, n_steps_in)) +yhat = model.predict(x_input, verbose=0) +``` + +将所有这些结合在一起,下面列出了具有单变量时间序列的多步骤预测的 MLP。 + +``` +# univariate multi-step vector-output mlp example +from numpy import array +from keras.models import Sequential +from keras.layers import Dense + +# split a univariate sequence into samples +def split_sequence(sequence, n_steps_in, n_steps_out): + X, y = list(), list() + for i in range(len(sequence)): + # find the end of this pattern + end_ix = i + n_steps_in + out_end_ix = end_ix + n_steps_out + # check if we are beyond the sequence + if out_end_ix > len(sequence): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequence[i:end_ix], sequence[end_ix:out_end_ix] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +raw_seq = [10, 20, 30, 40, 50, 60, 70, 80, 90] +# choose a number of time steps +n_steps_in, n_steps_out = 3, 2 +# split into samples +X, y = split_sequence(raw_seq, n_steps_in, n_steps_out) +# define model +model = Sequential() +model.add(Dense(100, activation='relu', input_dim=n_steps_in)) +model.add(Dense(n_steps_out)) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit(X, y, epochs=2000, verbose=0) +# demonstrate prediction +x_input = array([70, 80, 90]) +x_input = x_input.reshape((1, n_steps_in)) +yhat = model.predict(x_input, verbose=0) +print(yhat) +``` + +运行示例预测并打印序列中的后两个时间步骤。 + +``` +[[102.572365 113.88405 ]] +``` + +## 多变量多步 MLP 模型 + +在前面的部分中,我们研究了单变量,多变量和多步骤时间序列预测。 + +可以混合和匹配到目前为止针对不同问题呈现的不同类型的 MLP 模型。这也适用于涉及多变量和多步预测的时间序列预测问题,但它可能更具挑战性,特别是在准备数据和定义模型的输入和输出的形状时。 + +在本节中,我们将以多变量多步骤时间序列预测的数据准备和建模的简短示例作为模板来缓解这一挑战,具体来说: + +1. 多输入多步输出。 +2. 多个并行输入和多步输出。 + +也许最大的绊脚石是准备数据,所以这是我们关注的重点。 + +### 多输入多步输出 + +存在多变量时间序列预测问题,其中输出序列是分开的但取决于输入时间序列,并且输出序列需要多个时间步长。 + +例如,考虑前一部分的多变量时间序列: + +``` +[[ 10 15 25] + [ 20 25 45] + [ 30 35 65] + [ 40 45 85] + [ 50 55 105] + [ 60 65 125] + [ 70 75 145] + [ 80 85 165] + [ 90 95 185]] +``` + +我们可以使用两个输入时间序列中的每一个的三个先前时间步骤来预测输出时间序列的两个时间步长。 + +输入: + +``` +10, 15 +20, 25 +30, 35 +``` + +输出: + +``` +65 +85 +``` + +下面的 _split_sequences()_ 函数实现了这种行为。 + +``` +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps_in, n_steps_out): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps_in + out_end_ix = end_ix + n_steps_out-1 + # check if we are beyond the dataset + if out_end_ix > len(sequences): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :-1], sequences[end_ix-1:out_end_ix, -1] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) +``` + +我们可以在我们设计的数据集上证明这一点。下面列出了完整的示例。 + +``` +# multivariate multi-step data preparation +from numpy import array +from numpy import hstack + +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps_in, n_steps_out): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps_in + out_end_ix = end_ix + n_steps_out-1 + # check if we are beyond the dataset + if out_end_ix > len(sequences): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :-1], sequences[end_ix-1:out_end_ix, -1] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95]) +out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))]) +# convert to [rows, columns] structure +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2, out_seq)) +# choose a number of time steps +n_steps_in, n_steps_out = 3, 2 +# convert into input/output +X, y = split_sequences(dataset, n_steps_in, n_steps_out) +print(X.shape, y.shape) +# summarize the data +for i in range(len(X)): + print(X[i], y[i]) +``` + +首先运行该示例打印准备好的训练数据的形状。 + +我们可以看到样本的输入部分的形状是三维的,由六个样本组成,具有三个时间步长和两个输入时间序列的两个变量。 + +样本的输出部分对于六个样本是二维的,并且每个样本的两个时间步长是预测的。 + +然后打印制备的样品以确认数据是按照我们指定的方式制备的。 + +``` +(6, 3, 2) (6, 2) + +[[10 15] + [20 25] + [30 35]] [65 85] +[[20 25] + [30 35] + [40 45]] [ 85 105] +[[30 35] + [40 45] + [50 55]] [105 125] +[[40 45] + [50 55] + [60 65]] [125 145] +[[50 55] + [60 65] + [70 75]] [145 165] +[[60 65] + [70 75] + [80 85]] [165 185] +``` + +我们现在可以使用向量输出开发用于多步预测的 MLP 模型。 + +下面列出了完整的示例。 + +``` +# multivariate multi-step mlp example +from numpy import array +from numpy import hstack +from keras.models import Sequential +from keras.layers import Dense + +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps_in, n_steps_out): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps_in + out_end_ix = end_ix + n_steps_out-1 + # check if we are beyond the dataset + if out_end_ix > len(sequences): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :-1], sequences[end_ix-1:out_end_ix, -1] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95]) +out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))]) +# convert to [rows, columns] structure +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2, out_seq)) +# choose a number of time steps +n_steps_in, n_steps_out = 3, 2 +# convert into input/output +X, y = split_sequences(dataset, n_steps_in, n_steps_out) +# flatten input +n_input = X.shape[1] * X.shape[2] +X = X.reshape((X.shape[0], n_input)) +# define model +model = Sequential() +model.add(Dense(100, activation='relu', input_dim=n_input)) +model.add(Dense(n_steps_out)) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit(X, y, epochs=2000, verbose=0) +# demonstrate prediction +x_input = array([[70, 75], [80, 85], [90, 95]]) +x_input = x_input.reshape((1, n_input)) +yhat = model.predict(x_input, verbose=0) +print(yhat) +``` + +运行该示例适合模型并预测输出序列的下两个时间步骤超出数据集。 + +我们希望接下来的两个步骤是[185,205]。 + +这是一个具有挑战性的问题框架,数据非常少,模型的任意配置版本也很接近。 + +``` +[[186.53822 208.41725]] +``` + +### 多个并行输入和多步输出 + +并行时间序列的问题可能需要预测每个时间序列的多个时间步长。 + +例如,考虑前一部分的多变量时间序列: + +``` +[[ 10 15 25] + [ 20 25 45] + [ 30 35 65] + [ 40 45 85] + [ 50 55 105] + [ 60 65 125] + [ 70 75 145] + [ 80 85 165] + [ 90 95 185]] +``` + +我们可以使用三个时间序列中的每一个的最后三个步骤作为模型的输入,并预测三个时间序列中的每一个的下一个时间步长作为输出。 + +训练数据集中的第一个样本如下。 + +输入: + +``` +10, 15, 25 +20, 25, 45 +30, 35, 65 +``` + +输出: + +``` +40, 45, 85 +50, 55, 105 +``` + +下面的 _split_sequences()_ 函数实现了这种行为。 + +``` +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps_in, n_steps_out): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps_in + out_end_ix = end_ix + n_steps_out + # check if we are beyond the dataset + if out_end_ix > len(sequences): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :], sequences[end_ix:out_end_ix, :] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) +``` + +我们可以在小型设计数据集上演示此功能。 + +下面列出了完整的示例。 + +``` +# multivariate multi-step data preparation +from numpy import array +from numpy import hstack + +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps_in, n_steps_out): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps_in + out_end_ix = end_ix + n_steps_out + # check if we are beyond the dataset + if out_end_ix > len(sequences): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :], sequences[end_ix:out_end_ix, :] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95]) +out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))]) +# convert to [rows, columns] structure +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2, out_seq)) +# choose a number of time steps +n_steps_in, n_steps_out = 3, 2 +# convert into input/output +X, y = split_sequences(dataset, n_steps_in, n_steps_out) +print(X.shape, y.shape) +# summarize the data +for i in range(len(X)): + print(X[i], y[i]) +``` + +首先运行该示例打印准备好的训练数据集的形状。 + +我们可以看到数据集的输入( _X_ )和输出( _Y_ )元素分别对于样本数,时间步长和变量或并行时间序列是三维的。 。 + +然后将每个系列的输入和输出元素并排打印,以便我们可以确认数据是按照我们的预期准备的。 + +``` +(5, 3, 3) (5, 2, 3) + +[[10 15 25] + [20 25 45] + [30 35 65]] [[ 40 45 85] + [ 50 55 105]] +[[20 25 45] + [30 35 65] + [40 45 85]] [[ 50 55 105] + [ 60 65 125]] +[[ 30 35 65] + [ 40 45 85] + [ 50 55 105]] [[ 60 65 125] + [ 70 75 145]] +[[ 40 45 85] + [ 50 55 105] + [ 60 65 125]] [[ 70 75 145] + [ 80 85 165]] +[[ 50 55 105] + [ 60 65 125] + [ 70 75 145]] [[ 80 85 165] + [ 90 95 185]] +``` + +我们现在可以开发 MLP 模型来进行多变量多步预测。 + +除了展平输入数据的形状之外,正如我们在先前的例子中所做的那样,我们还必须平整输出数据的三维结构。这是因为 MLP 模型只能采用向量输入和输出。 + +``` +# flatten input +n_input = X.shape[1] * X.shape[2] +X = X.reshape((X.shape[0], n_input)) +# flatten output +n_output = y.shape[1] * y.shape[2] +y = y.reshape((y.shape[0], n_output)) +``` + +下面列出了完整的示例。 + +``` +# multivariate multi-step mlp example +from numpy import array +from numpy import hstack +from keras.models import Sequential +from keras.layers import Dense + +# split a multivariate sequence into samples +def split_sequences(sequences, n_steps_in, n_steps_out): + X, y = list(), list() + for i in range(len(sequences)): + # find the end of this pattern + end_ix = i + n_steps_in + out_end_ix = end_ix + n_steps_out + # check if we are beyond the dataset + if out_end_ix > len(sequences): + break + # gather input and output parts of the pattern + seq_x, seq_y = sequences[i:end_ix, :], sequences[end_ix:out_end_ix, :] + X.append(seq_x) + y.append(seq_y) + return array(X), array(y) + +# define input sequence +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95]) +out_seq = array([in_seq1[i]+in_seq2[i] for i in range(len(in_seq1))]) +# convert to [rows, columns] structure +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2, out_seq)) +# choose a number of time steps +n_steps_in, n_steps_out = 3, 2 +# convert into input/output +X, y = split_sequences(dataset, n_steps_in, n_steps_out) +# flatten input +n_input = X.shape[1] * X.shape[2] +X = X.reshape((X.shape[0], n_input)) +# flatten output +n_output = y.shape[1] * y.shape[2] +y = y.reshape((y.shape[0], n_output)) +# define model +model = Sequential() +model.add(Dense(100, activation='relu', input_dim=n_input)) +model.add(Dense(n_output)) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit(X, y, epochs=2000, verbose=0) +# demonstrate prediction +x_input = array([[60, 65, 125], [70, 75, 145], [80, 85, 165]]) +x_input = x_input.reshape((1, n_input)) +yhat = model.predict(x_input, verbose=0) +print(yhat) +``` + +运行该示例适合模型并预测超出数据集末尾的下两个时间步的三个时间步中的每一个的值。 + +我们希望这些系列和时间步骤的值如下: + +``` +90, 95, 185 +100, 105, 205 +``` + +我们可以看到模型预测合理地接近预期值。 + +``` +[[ 91.28376 96.567 188.37575 100.54482 107.9219 208.108 ] +``` + +## 摘要 + +在本教程中,您了解了如何针对一系列标准时间序列预测问题开发一套多层感知器或 MLP 模型。 + +具体来说,你学到了: + +* 如何开发单变量时间序列预测的 MLP 模型。 +* 如何开发多元时间序列预测的 MLP 模型。 +* 如何开发 MLP 模型进行多步时间序列预测。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/how-to-develop-rnn-models-for-human-activity-recognition-time-series-classification.md b/docs/dl-ts/how-to-develop-rnn-models-for-human-activity-recognition-time-series-classification.md new file mode 100644 index 0000000000000000000000000000000000000000..c5386db1fe27aabc0649ea197294d125b3f9f91c --- /dev/null +++ b/docs/dl-ts/how-to-develop-rnn-models-for-human-activity-recognition-time-series-classification.md @@ -0,0 +1,903 @@ +# 如何开发人类活动识别时间序列分类的 RNN 模型 + +> 原文: [https://machinelearningmastery.com/how-to-develop-rnn-models-for-human-activity-recognition-time-series-classification/](https://machinelearningmastery.com/how-to-develop-rnn-models-for-human-activity-recognition-time-series-classification/) + +人类活动识别是将由专用线束或智能电话记录的加速度计数据序列分类为已知的明确定义的运动的问题。 + +该问题的经典方法涉及基于固定大小的窗口和训练机器学习模型(例如决策树的集合)的时间序列数据中的手工制作特征。困难在于此功能工程需要该领域的强大专业知识。 + +最近,诸如 LSTM 之类的循环神经网络和利用一维卷积神经网络或 CNN 的变化等深度学习方法已经被证明可以在很少或没有数据的情况下提供具有挑战性的活动识别任务的最新结果特征工程,而不是使用原始数据的特征学习。 + +在本教程中,您将发现三种循环神经网络体系结构,用于对活动识别时间序列分类问题进行建模。 + +完成本教程后,您将了解: + +* 如何开发一种用于人类活动识别的长短期记忆循环神经网络。 +* 如何开发一维卷积神经网络 LSTM 或 CNN-LSTM 模型。 +* 如何针对同一问题开发一维卷积 LSTM 或 ConvLSTM 模型。 + +让我们开始吧。 + +![How to Develop RNN Models for Human Activity Recognition Time Series Classification](img/d330ca8c16c51dde05533f60b321bc56.jpg) + +如何开发用于人类活动识别的 RNN 模型时间序列分类 +照片由 [Bonnie Moreland](https://www.flickr.com/photos/icetsarina/25033478158/) ,保留一些权利。 + +## 教程概述 + +本教程分为四个部分;他们是: + +1. 使用智能手机数据集进行活动识别 +2. 开发 LSTM 网络模型 +3. 开发 CNN-LSTM 网络模型 +4. 开发 ConvLSTM 网络模型 + +## 使用智能手机数据集进行活动识别 + +[人类活动识别](https://en.wikipedia.org/wiki/Activity_recognition),或简称为 HAR,是基于使用传感器的移动痕迹来预测人正在做什么的问题。 + +标准的人类活动识别数据集是 2012 年推出的“使用智能手机数据集的活动识别”。 + +它由 Davide Anguita 等人准备并提供。来自意大利热那亚大学的 2013 年论文“[使用智能手机进行人类活动识别的公共领域数据集](https://upcommons.upc.edu/handle/2117/20897)”中对该数据集进行了全面描述。该数据集在他们的 2012 年论文中用机器学习算法建模,标题为“[使用多类硬件友好支持向量机](https://link.springer.com/chapter/10.1007/978-3-642-35395-6_30)在智能手机上进行人类活动识别。“ + +数据集可用,可以从 UCI 机器学习库免费下载: + +* [使用智能手机数据集进行人类活动识别,UCI 机器学习库](https://archive.ics.uci.edu/ml/datasets/human+activity+recognition+using+smartphones) + +该数据来自 30 名年龄在 19 至 48 岁之间的受试者,其执行六项标准活动中的一项,同时佩戴记录运动数据的腰部智能手机。记录执行活动的每个受试者的视频,并从这些视频手动标记移动数据。 + +以下是在记录其移动数据的同时执行活动的主体的示例视频。 + +<iframe allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" frameborder="0" height="375" src="https://www.youtube.com/embed/XOEN9W05_4A?feature=oembed" width="500"></iframe> + +进行的六项活动如下: + +1. 步行 +2. 走上楼 +3. 走楼下 +4. 坐在 +5. 常设 +6. 铺设 + +记录的运动数据是来自智能手机的 x,y 和 z 加速度计数据(线性加速度)和陀螺仪数据(角速度),特别是三星 Galaxy S II。以 50Hz(即每秒 50 个数据点)记录观察结果。每个受试者进行两次活动;一旦设备在左侧,一次设备在右侧。 + +原始数据不可用。相反,可以使用预处理版本的数据集。预处理步骤包括: + +* 使用噪声滤波器预处理加速度计和陀螺仪。 +* 将数据拆分为 2.56 秒(128 个数据点)的固定窗口,重叠率为 50%。将加速度计数据分割为重力(总)和身体运动分量。 + +特征工程应用于窗口数据,并且提供具有这些工程特征的数据的副本。 + +从每个窗口提取在人类活动识别领域中常用的许多时间和频率特征。结果是 561 元素的特征向量。 + +根据受试者的数据,将数据集分成训练(70%)和测试(30%)组。列车 21 个,测试 9 个。 + +使用旨在用于智能手机的支持向量机(例如定点算术)的实验结果导致测试数据集的预测准确度为 89%,实现与未修改的 SVM 实现类似的结果。 + +该数据集是免费提供的,可以从 UCI 机器学习库下载。 + +数据以单个 zip 文件的形式提供,大小约为 58 兆字节。此下载的直接链接如下: + +* [UCI HAR Dataset.zip](https://archive.ics.uci.edu/ml/machine-learning-databases/00240/UCI%20HAR%20Dataset.zip) + +下载数据集并将所有文件解压缩到当前工作目录中名为“HARDataset”的新目录中。 + +## 开发 LSTM 网络模型 + +在本节中,我们将为人类活动识别数据集开发长期短期记忆网络模型(LSTM)。 + +LSTM 网络模型是一种循环神经网络,能够学习和记忆长输入数据序列。它们适用于由长序列数据组成的数据,最多 200 到 400 个时间步长。它们可能非常适合这个问题。 + +该模型可以支持多个并行的输入数据序列,例如加速度计的每个轴和陀螺仪数据。该模型学习从观察序列中提取特征以及如何将内部特征映射到不同的活动类型。 + +使用 LSTM 进行序列分类的好处是,他们可以直接从原始时间序列数据中学习,反过来不需要领域专业知识来手动设计输入功能。该模型可以学习时间序列数据的内部表示,并且理想地实现与适合具有工程特征的数据集版本的模型相当的表现。 + +本节分为四个部分;他们是: + +1. 加载数据 +2. 拟合和评估模型 +3. 总结结果 +4. 完整的例子 + +## 加载数据 + +第一步是将原始数据集加载到内存中。 + +原始数据中有三种主要信号类型:总加速度,车身加速度和车身陀螺仪。每个都有 3 个数据轴。这意味着每个时间步长总共有九个变量。 + +此外,每个数据系列已被划分为 2.56 秒数据或 128 个时间步长的重叠窗口。这些数据窗口对应于上一节中工程特征(行)的窗口。 + +这意味着一行数据具有(128 * 9)或 1,152 个元素。这比前一节中 561 个元素向量的大小小一倍,并且可能存在一些冗余数据。 + +信号存储在 train 和 test 子目录下的/ _Inertial Signals_ /目录中。每个信号的每个轴都存储在一个单独的文件中,这意味着每个列车和测试数据集都有九个要加载的输入文件和一个要加载的输出文件。在给定一致的目录结构和文件命名约定的情况下,我们可以批量加载这些文件。 + +输入数据采用 CSV 格式,其中列由空格分隔。这些文件中的每一个都可以作为 NumPy 数组加载。下面的 _load_file()_ 函数在给定文件填充路径的情况下加载数据集,并将加载的数据作为 NumPy 数组返回。 + +``` +# load a single file as a numpy array +def load_file(filepath): + dataframe = read_csv(filepath, header=None, delim_whitespace=True) + return dataframe.values +``` + +然后,我们可以将给定组(训练或测试)的所有数据加载到单个三维 NumPy 阵列中,其中阵列的尺寸为[_ 样本,时间步长,特征 _]。 + +为了更清楚,有 128 个时间步和 9 个特征,其中样本数是任何给定原始信号数据文件中的行数。 + +下面的 _load_group()_ 函数实现了这种行为。 [dstack()NumPy 函数](https://docs.scipy.org/doc/numpy-1.14.0/reference/generated/numpy.dstack.html)允许我们将每个加载的 3D 数组堆叠成单个 3D 数组,其中变量在第三维(特征)上分开。 + +``` +# load a list of files into a 3D array of [samples, timesteps, features] +def load_group(filenames, prefix=''): + loaded = list() + for name in filenames: + data = load_file(prefix + name) + loaded.append(data) + # stack group so that features are the 3rd dimension + loaded = dstack(loaded) + return loaded +``` + +我们可以使用此功能加载给定组的所有输入信号数据,例如火车或测试。 + +下面的 _load_dataset_group()_ 函数使用目录之间的一致命名约定加载单个组的所有输入信号数据和输出数据。 + +``` +# load a dataset group, such as train or test +def load_dataset_group(group, prefix=''): + filepath = prefix + group + '/Inertial Signals/' + # load all 9 files as a single array + filenames = list() + # total acceleration + filenames += ['total_acc_x_'+group+'.txt', 'total_acc_y_'+group+'.txt', 'total_acc_z_'+group+'.txt'] + # body acceleration + filenames += ['body_acc_x_'+group+'.txt', 'body_acc_y_'+group+'.txt', 'body_acc_z_'+group+'.txt'] + # body gyroscope + filenames += ['body_gyro_x_'+group+'.txt', 'body_gyro_y_'+group+'.txt', 'body_gyro_z_'+group+'.txt'] + # load input data + X = load_group(filenames, filepath) + # load class output + y = load_file(prefix + group + '/y_'+group+'.txt') + return X, y +``` + +最后,我们可以加载每个列车和测试数据集。 + +输出数据定义为类号的整数。我们必须对这些类整数进行热编码,以使数据适合于拟合神经网络多类分类模型。我们可以通过调用 [to_categorical()Keras 函数](https://keras.io/utils/#to_categorical)来实现。 + +下面的 _load_dataset()_ 函数实现了这种行为,并返回训练并测试 X 和 y 元素,以便拟合和评估定义的模型。 + +``` +# load the dataset, returns train and test X and y elements +def load_dataset(prefix=''): + # load all train + trainX, trainy = load_dataset_group('train', prefix + 'HARDataset/') + print(trainX.shape, trainy.shape) + # load all test + testX, testy = load_dataset_group('test', prefix + 'HARDataset/') + print(testX.shape, testy.shape) + # zero-offset class values + trainy = trainy - 1 + testy = testy - 1 + # one hot encode y + trainy = to_categorical(trainy) + testy = to_categorical(testy) + print(trainX.shape, trainy.shape, testX.shape, testy.shape) + return trainX, trainy, testX, testy +``` + +### 拟合和评估模型 + +现在我们已将数据加载到内存中以便进行建模,我们可以定义,拟合和评估 LSTM 模型。 + +我们可以定义一个名为 _evaluate_model()_ 的函数,它接受训练和测试数据集,拟合训练数据集上的模型,在测试数据集上对其进行评估,并返回模型表现的估计值。 + +首先,我们必须使用 Keras 深度学习库来定义 LSTM 模型。该模型需要使用[_ 样本,时间步长,特征 _]进行三维输入。 + +这正是我们加载数据的方式,其中一个样本是时间序列数据的一个窗口,每个窗口有 128 个时间步长,时间步长有九个变量或特征。 + +模型的输出将是一个六元素向量,包含属于六种活动类型中每种活动类型的给定窗口的概率。 + +在拟合模型时需要输入和输出尺寸,我们可以从提供的训练数据集中提取它们。 + +``` +n_timesteps, n_features, n_outputs = trainX.shape[1], trainX.shape[2], trainy.shape[1] +``` + +为简单起见,该模型被定义为顺序 Keras 模型。 + +我们将模型定义为具有单个 LSTM 隐藏层。接下来是一个脱落层,旨在减少模型过度拟合到训练数据。最后,在使用最终输出层进行预测之前,使用密集的完全连接层来解释由 LSTM 隐藏层提取的特征。 + +随机梯度下降的有效 [Adam 版本将用于优化网络,并且鉴于我们正在学习多类别分类问题,将使用分类交叉熵损失函数。](https://machinelearningmastery.com/adam-optimization-algorithm-for-deep-learning/) + +下面列出了该模型的定义。 + +``` +model = Sequential() +model.add(LSTM(100, input_shape=(n_timesteps,n_features))) +model.add(Dropout(0.5)) +model.add(Dense(100, activation='relu')) +model.add(Dense(n_outputs, activation='softmax')) +model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) +``` + +该模型适用于固定数量的时期,在这种情况下为 15,并且将使用 64 个样本的批量大小,其中在更新模型的权重之前将 64 个数据窗口暴露给模型。 + +模型拟合后,将在测试数据集上进行评估,并返回测试数据集上拟合模型的精度。 + +注意,在拟合 LSTM 时,通常不对值序列数据进行混洗。这里我们在训练期间对输入数据的窗口进行随机播放(默认)。在这个问题中,我们感兴趣的是利用 LSTM 的能力来学习和提取窗口中时间步长的功能,而不是跨窗口。 + +下面列出了完整的 _evaluate_model()_ 函数。 + +``` +# fit and evaluate a model +def evaluate_model(trainX, trainy, testX, testy): + verbose, epochs, batch_size = 0, 15, 64 + n_timesteps, n_features, n_outputs = trainX.shape[1], trainX.shape[2], trainy.shape[1] + model = Sequential() + model.add(LSTM(100, input_shape=(n_timesteps,n_features))) + model.add(Dropout(0.5)) + model.add(Dense(100, activation='relu')) + model.add(Dense(n_outputs, activation='softmax')) + model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) + # fit network + model.fit(trainX, trainy, epochs=epochs, batch_size=batch_size, verbose=verbose) + # evaluate model + _, accuracy = model.evaluate(testX, testy, batch_size=batch_size, verbose=0) + return accuracy +``` + +网络结构或选择的超参数没有什么特别之处,它们只是这个问题的起点。 + +### 总结结果 + +我们无法从单一评估中判断模型的技能。 + +其原因是神经网络是随机的,这意味着当在相同数据上训练相同的模型配置时将产生不同的特定模型。 + +这是网络的一个特征,它为模型提供了自适应能力,但需要对模型进行稍微复杂的评估。 + +我们将多次重复对模型的评估,然后在每次运行中总结模型的表现。例如,我们可以调用 _evaluate_model()_ 共 10 次。这将导致必须总结的模型评估分数。 + +``` +# repeat experiment +scores = list() +for r in range(repeats): + score = evaluate_model(trainX, trainy, testX, testy) + score = score * 100.0 + print('>#%d: %.3f' % (r+1, score)) + scores.append(score) +``` + +我们可以通过计算和报告绩效的均值和标准差来总结得分样本。均值给出了数据集上模型的平均精度,而标准差给出了精度与平均值的平均方差。 + +下面的函数 _summarize_results()_ 总结了运行的结果。 + +``` +# summarize scores +def summarize_results(scores): + print(scores) + m, s = mean(scores), std(scores) + print('Accuracy: %.3f%% (+/-%.3f)' % (m, s)) +``` + +我们可以将重复评估,结果收集和结果汇总捆绑到实验的主要功能中,称为 _run_experiment()_,如下所示。 + +默认情况下,在报告模型表现之前,会对模型进行 10 次评估。 + +``` +# run an experiment +def run_experiment(repeats=10): + # load data + trainX, trainy, testX, testy = load_dataset() + # repeat experiment + scores = list() + for r in range(repeats): + score = evaluate_model(trainX, trainy, testX, testy) + score = score * 100.0 + print('>#%d: %.3f' % (r+1, score)) + scores.append(score) + # summarize results + summarize_results(scores) +``` + +### 完整的例子 + +现在我们已经拥有了所有的部分,我们可以将它们组合成一个有效的例子。 + +完整的代码清单如下。 + +``` +# lstm model +from numpy import mean +from numpy import std +from numpy import dstack +from pandas import read_csv +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import Flatten +from keras.layers import Dropout +from keras.layers import LSTM +from keras.utils import to_categorical +from matplotlib import pyplot + +# load a single file as a numpy array +def load_file(filepath): + dataframe = read_csv(filepath, header=None, delim_whitespace=True) + return dataframe.values + +# load a list of files and return as a 3d numpy array +def load_group(filenames, prefix=''): + loaded = list() + for name in filenames: + data = load_file(prefix + name) + loaded.append(data) + # stack group so that features are the 3rd dimension + loaded = dstack(loaded) + return loaded + +# load a dataset group, such as train or test +def load_dataset_group(group, prefix=''): + filepath = prefix + group + '/Inertial Signals/' + # load all 9 files as a single array + filenames = list() + # total acceleration + filenames += ['total_acc_x_'+group+'.txt', 'total_acc_y_'+group+'.txt', 'total_acc_z_'+group+'.txt'] + # body acceleration + filenames += ['body_acc_x_'+group+'.txt', 'body_acc_y_'+group+'.txt', 'body_acc_z_'+group+'.txt'] + # body gyroscope + filenames += ['body_gyro_x_'+group+'.txt', 'body_gyro_y_'+group+'.txt', 'body_gyro_z_'+group+'.txt'] + # load input data + X = load_group(filenames, filepath) + # load class output + y = load_file(prefix + group + '/y_'+group+'.txt') + return X, y + +# load the dataset, returns train and test X and y elements +def load_dataset(prefix=''): + # load all train + trainX, trainy = load_dataset_group('train', prefix + 'HARDataset/') + print(trainX.shape, trainy.shape) + # load all test + testX, testy = load_dataset_group('test', prefix + 'HARDataset/') + print(testX.shape, testy.shape) + # zero-offset class values + trainy = trainy - 1 + testy = testy - 1 + # one hot encode y + trainy = to_categorical(trainy) + testy = to_categorical(testy) + print(trainX.shape, trainy.shape, testX.shape, testy.shape) + return trainX, trainy, testX, testy + +# fit and evaluate a model +def evaluate_model(trainX, trainy, testX, testy): + verbose, epochs, batch_size = 0, 15, 64 + n_timesteps, n_features, n_outputs = trainX.shape[1], trainX.shape[2], trainy.shape[1] + model = Sequential() + model.add(LSTM(100, input_shape=(n_timesteps,n_features))) + model.add(Dropout(0.5)) + model.add(Dense(100, activation='relu')) + model.add(Dense(n_outputs, activation='softmax')) + model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) + # fit network + model.fit(trainX, trainy, epochs=epochs, batch_size=batch_size, verbose=verbose) + # evaluate model + _, accuracy = model.evaluate(testX, testy, batch_size=batch_size, verbose=0) + return accuracy + +# summarize scores +def summarize_results(scores): + print(scores) + m, s = mean(scores), std(scores) + print('Accuracy: %.3f%% (+/-%.3f)' % (m, s)) + +# run an experiment +def run_experiment(repeats=10): + # load data + trainX, trainy, testX, testy = load_dataset() + # repeat experiment + scores = list() + for r in range(repeats): + score = evaluate_model(trainX, trainy, testX, testy) + score = score * 100.0 + print('>#%d: %.3f' % (r+1, score)) + scores.append(score) + # summarize results + summarize_results(scores) + +# run the experiment +run_experiment() +``` + +运行该示例首先打印已加载数据集的形状,然后打印列车和测试集的形状以及输入和输出元素。这确认了样本数,时间步长和变量,以及类的数量。 + +接下来,创建和评估模型,并为每个模型打印调试消息。 + +最后,打印分数样本,然后是平均值和标准差。我们可以看到该模型表现良好,在原始数据集上实现了约 89.7%的分类准确度,标准偏差约为 1.3。 + +这是一个很好的结果,考虑到原始论文发表了 89%的结果,在具有重域特定特征工程的数据集上进行了训练,而不是原始数据集。 + +注意:鉴于算法的随机性,您的具体结果可能会有所不同。如果是这样,请尝试运行几次代码。 + +``` +(7352, 128, 9) (7352, 1) +(2947, 128, 9) (2947, 1) +(7352, 128, 9) (7352, 6) (2947, 128, 9) (2947, 6) + +>#1: 90.058 +>#2: 85.918 +>#3: 90.974 +>#4: 89.515 +>#5: 90.159 +>#6: 91.110 +>#7: 89.718 +>#8: 90.295 +>#9: 89.447 +>#10: 90.024 + +[90.05768578215134, 85.91788259246692, 90.97387173396675, 89.51476077366813, 90.15948422124194, 91.10960298608755, 89.71835765184933, 90.29521547336275, 89.44689514760775, 90.02375296912113] + +Accuracy: 89.722% (+/-1.371) +``` + +现在我们已经了解了如何开发用于时间序列分类的 LSTM 模型,让我们看看如何开发更复杂的 CNN LSTM 模型。 + +## 开发 CNN-LSTM 网络模型 + +CNN LSTM 架构涉及使用卷积神经网络(CNN)层对输入数据进行特征提取以及 LSTM 以支持序列预测。 + +CNN LSTM 是针对视觉时间序列预测问题以及从图像序列(例如视频)生成文本描述的应用而开发的。具体来说,问题是: + +* **活动识别**:生成在一系列图像中演示的活动的文本描述。 +* **图像说明**:生成单个图像的文本描述。 +* **视频说明**:生成图像序列的文本描述。 + +您可以在帖子中了解有关 CNN LSTM 架构的更多信息: + +* [CNN 长短期记忆网络](https://machinelearningmastery.com/cnn-long-short-term-memory-networks/) + +要了解有关组合这些模型的后果的更多信息,请参阅论文: + +* [卷积,长短期记忆,完全连接的深度神经网络](https://ieeexplore.ieee.org/document/7178838/),2015。 + +CNN LSTM 模型将以块为单位读取主序列的子序列,从每个块中提取特征,然后允许 LSTM 解释从每个块提取的特征。 + +实现此模型的一种方法是将 128 个时间步的每个窗口拆分为 CNN 模型要处理的子序列。例如,每个窗口中的 128 个时间步长可以分成 32 个时间步长的四个子序列。 + +``` +# reshape data into time steps of sub-sequences +n_steps, n_length = 4, 32 +trainX = trainX.reshape((trainX.shape[0], n_steps, n_length, n_features)) +testX = testX.reshape((testX.shape[0], n_steps, n_length, n_features)) +``` + +然后我们可以定义一个 CNN 模型,该模型期望以 32 个时间步长和 9 个特征的长度读取序列。 + +整个 CNN 模型可以包裹在 [TimeDistributed](https://machinelearningmastery.com/timedistributed-layer-for-long-short-term-memory-networks-in-python/) 层中,以允许相同的 CNN 模型在窗口的四个子序列中的每一个中读取。然后将提取的特征展平并提供给 LSTM 模型以进行读取,在最终映射到活动之前提取其自身的特征。 + +``` +# define model +model = Sequential() +model.add(TimeDistributed(Conv1D(filters=64, kernel_size=3, activation='relu'), input_shape=(None,n_length,n_features))) +model.add(TimeDistributed(Conv1D(filters=64, kernel_size=3, activation='relu'))) +model.add(TimeDistributed(Dropout(0.5))) +model.add(TimeDistributed(MaxPooling1D(pool_size=2))) +model.add(TimeDistributed(Flatten())) +model.add(LSTM(100)) +model.add(Dropout(0.5)) +model.add(Dense(100, activation='relu')) +model.add(Dense(n_outputs, activation='softmax')) +``` + +通常使用两个连续的 CNN 层,然后是丢失和最大池层,这是 CNN LSTM 模型中使用的简单结构。 + +下面列出了更新的 _evaluate_model()_。 + +``` +# fit and evaluate a model +def evaluate_model(trainX, trainy, testX, testy): + # define model + verbose, epochs, batch_size = 0, 25, 64 + n_timesteps, n_features, n_outputs = trainX.shape[1], trainX.shape[2], trainy.shape[1] + # reshape data into time steps of sub-sequences + n_steps, n_length = 4, 32 + trainX = trainX.reshape((trainX.shape[0], n_steps, n_length, n_features)) + testX = testX.reshape((testX.shape[0], n_steps, n_length, n_features)) + # define model + model = Sequential() + model.add(TimeDistributed(Conv1D(filters=64, kernel_size=3, activation='relu'), input_shape=(None,n_length,n_features))) + model.add(TimeDistributed(Conv1D(filters=64, kernel_size=3, activation='relu'))) + model.add(TimeDistributed(Dropout(0.5))) + model.add(TimeDistributed(MaxPooling1D(pool_size=2))) + model.add(TimeDistributed(Flatten())) + model.add(LSTM(100)) + model.add(Dropout(0.5)) + model.add(Dense(100, activation='relu')) + model.add(Dense(n_outputs, activation='softmax')) + model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) + # fit network + model.fit(trainX, trainy, epochs=epochs, batch_size=batch_size, verbose=verbose) + # evaluate model + _, accuracy = model.evaluate(testX, testy, batch_size=batch_size, verbose=0) + return accuracy +``` + +我们可以像上一节中的直线 LSTM 模型一样评估此模型。 + +完整的代码清单如下。 + +``` +# cnn lstm model +from numpy import mean +from numpy import std +from numpy import dstack +from pandas import read_csv +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import Flatten +from keras.layers import Dropout +from keras.layers import LSTM +from keras.layers import TimeDistributed +from keras.layers.convolutional import Conv1D +from keras.layers.convolutional import MaxPooling1D +from keras.utils import to_categorical +from matplotlib import pyplot + +# load a single file as a numpy array +def load_file(filepath): + dataframe = read_csv(filepath, header=None, delim_whitespace=True) + return dataframe.values + +# load a list of files and return as a 3d numpy array +def load_group(filenames, prefix=''): + loaded = list() + for name in filenames: + data = load_file(prefix + name) + loaded.append(data) + # stack group so that features are the 3rd dimension + loaded = dstack(loaded) + return loaded + +# load a dataset group, such as train or test +def load_dataset_group(group, prefix=''): + filepath = prefix + group + '/Inertial Signals/' + # load all 9 files as a single array + filenames = list() + # total acceleration + filenames += ['total_acc_x_'+group+'.txt', 'total_acc_y_'+group+'.txt', 'total_acc_z_'+group+'.txt'] + # body acceleration + filenames += ['body_acc_x_'+group+'.txt', 'body_acc_y_'+group+'.txt', 'body_acc_z_'+group+'.txt'] + # body gyroscope + filenames += ['body_gyro_x_'+group+'.txt', 'body_gyro_y_'+group+'.txt', 'body_gyro_z_'+group+'.txt'] + # load input data + X = load_group(filenames, filepath) + # load class output + y = load_file(prefix + group + '/y_'+group+'.txt') + return X, y + +# load the dataset, returns train and test X and y elements +def load_dataset(prefix=''): + # load all train + trainX, trainy = load_dataset_group('train', prefix + 'HARDataset/') + print(trainX.shape, trainy.shape) + # load all test + testX, testy = load_dataset_group('test', prefix + 'HARDataset/') + print(testX.shape, testy.shape) + # zero-offset class values + trainy = trainy - 1 + testy = testy - 1 + # one hot encode y + trainy = to_categorical(trainy) + testy = to_categorical(testy) + print(trainX.shape, trainy.shape, testX.shape, testy.shape) + return trainX, trainy, testX, testy + +# fit and evaluate a model +def evaluate_model(trainX, trainy, testX, testy): + # define model + verbose, epochs, batch_size = 0, 25, 64 + n_timesteps, n_features, n_outputs = trainX.shape[1], trainX.shape[2], trainy.shape[1] + # reshape data into time steps of sub-sequences + n_steps, n_length = 4, 32 + trainX = trainX.reshape((trainX.shape[0], n_steps, n_length, n_features)) + testX = testX.reshape((testX.shape[0], n_steps, n_length, n_features)) + # define model + model = Sequential() + model.add(TimeDistributed(Conv1D(filters=64, kernel_size=3, activation='relu'), input_shape=(None,n_length,n_features))) + model.add(TimeDistributed(Conv1D(filters=64, kernel_size=3, activation='relu'))) + model.add(TimeDistributed(Dropout(0.5))) + model.add(TimeDistributed(MaxPooling1D(pool_size=2))) + model.add(TimeDistributed(Flatten())) + model.add(LSTM(100)) + model.add(Dropout(0.5)) + model.add(Dense(100, activation='relu')) + model.add(Dense(n_outputs, activation='softmax')) + model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) + # fit network + model.fit(trainX, trainy, epochs=epochs, batch_size=batch_size, verbose=verbose) + # evaluate model + _, accuracy = model.evaluate(testX, testy, batch_size=batch_size, verbose=0) + return accuracy + +# summarize scores +def summarize_results(scores): + print(scores) + m, s = mean(scores), std(scores) + print('Accuracy: %.3f%% (+/-%.3f)' % (m, s)) + +# run an experiment +def run_experiment(repeats=10): + # load data + trainX, trainy, testX, testy = load_dataset() + # repeat experiment + scores = list() + for r in range(repeats): + score = evaluate_model(trainX, trainy, testX, testy) + score = score * 100.0 + print('>#%d: %.3f' % (r+1, score)) + scores.append(score) + # summarize results + summarize_results(scores) + +# run the experiment +run_experiment() +``` + +运行该示例总结了 10 个运行中每个运行的模型表现,然后报告了测试集上模型表现的最终摘要。 + +我们可以看到该模型的表现约为 90.6%,标准偏差约为 1%。 + +注意:鉴于算法的随机性,您的具体结果可能会有所不同。如果是这样,请尝试运行几次代码。 + +``` +>#1: 91.517 +>#2: 91.042 +>#3: 90.804 +>#4: 92.263 +>#5: 89.684 +>#6: 88.666 +>#7: 91.381 +>#8: 90.804 +>#9: 89.379 +>#10: 91.347 + +[91.51679674244994, 91.04173736002714, 90.80420766881574, 92.26331862911435, 89.68442483881914, 88.66644044791313, 91.38106549032915, 90.80420766881574, 89.37902952154734, 91.34713267729894] + +Accuracy: 90.689% (+/-1.051) +``` + +## 开发 ConvLSTM 网络模型 + +CNN LSTM 想法的进一步扩展是执行 CNN 的卷积(例如 CNN 如何读取输入序列数据)作为 LSTM 的一部分。 + +这种组合称为卷积 LSTM,简称 ConvLSTM,与 CNN LSTM 一样,也用于时空数据。 + +与直接读取数据以计算内部状态和状态转换的 LSTM 不同,并且与解释 CNN 模型的输出的 CNN LSTM 不同,ConvLSTM 直接使用卷积作为读取 LSTM 单元本身的输入的一部分。 + +有关如何在 LSTM 单元内计算 ConvLSTM 方程的更多信息,请参阅文章: + +* [卷积 LSTM 网络:用于降水预报的机器学习方法](https://arxiv.org/abs/1506.04214v1),2015。 + +Keras 库提供 [ConvLSTM2D 类](https://keras.io/layers/recurrent/#convlstm2d),支持用于 2D 数据的 ConvLSTM 模型。它可以配置为 1D 多变量时间序列分类。 + +默认情况下,ConvLSTM2D 类要求输入数据具有以下形状: + +``` +(samples, time, rows, cols, channels) +``` + +其中每个时间步数据被定义为(行*列)数据点的图像。 + +在上一节中,我们将给定的数据窗口(128 个时间步长)划分为 32 个时间步长的四个子序列。我们可以在定义 ConvLSTM2D 输入时使用相同的子序列方法,其中时间步数是窗口中子序列的数量,当我们处理一维数据时行数是 1,列数代表子序列中的时间步长数,在本例中为 32。 + +对于这个选择的问题框架,ConvLSTM2D 的输入因此是: + +* **样本**:n,表示数据集中的窗口数。 +* **时间**:4,对于我们将 128 个时间步长的窗口分成四个子序列。 +* **行**:1,用于每个子序列的一维形状。 +* **列**:32,表示输入子序列中的 32 个时间步长。 +* **频道**:9,为九个输入变量。 + +我们现在可以为 ConvLSTM2D 模型准备数据。 + +``` +n_timesteps, n_features, n_outputs = trainX.shape[1], trainX.shape[2], trainy.shape[1] +# reshape into subsequences (samples, time steps, rows, cols, channels) +n_steps, n_length = 4, 32 +trainX = trainX.reshape((trainX.shape[0], n_steps, 1, n_length, n_features)) +testX = testX.reshape((testX.shape[0], n_steps, 1, n_length, n_features)) +``` + +ConvLSTM2D 类需要根据 CNN 和 LSTM 进行配置。这包括指定滤波器的数量(例如 64),二维内核大小,在这种情况下(子序列时间步长的 1 行和 3 列),以及激活函数,在这种情况下是整流的线性。 + +与 CNN 或 LSTM 模型一样,输出必须展平为一个长向量,然后才能通过密集层进行解释。 + +``` +# define model +model = Sequential() +model.add(ConvLSTM2D(filters=64, kernel_size=(1,3), activation='relu', input_shape=(n_steps, 1, n_length, n_features))) +model.add(Dropout(0.5)) +model.add(Flatten()) +model.add(Dense(100, activation='relu')) +model.add(Dense(n_outputs, activation='softmax')) +``` + +然后我们可以在之前对 LSTM 和 CNN LSTM 模型进行评估。 + +下面列出了完整的示例。 + +``` +# convlstm model +from numpy import mean +from numpy import std +from numpy import dstack +from pandas import read_csv +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import Flatten +from keras.layers import Dropout +from keras.layers import LSTM +from keras.layers import TimeDistributed +from keras.layers import ConvLSTM2D +from keras.utils import to_categorical +from matplotlib import pyplot + +# load a single file as a numpy array +def load_file(filepath): + dataframe = read_csv(filepath, header=None, delim_whitespace=True) + return dataframe.values + +# load a list of files and return as a 3d numpy array +def load_group(filenames, prefix=''): + loaded = list() + for name in filenames: + data = load_file(prefix + name) + loaded.append(data) + # stack group so that features are the 3rd dimension + loaded = dstack(loaded) + return loaded + +# load a dataset group, such as train or test +def load_dataset_group(group, prefix=''): + filepath = prefix + group + '/Inertial Signals/' + # load all 9 files as a single array + filenames = list() + # total acceleration + filenames += ['total_acc_x_'+group+'.txt', 'total_acc_y_'+group+'.txt', 'total_acc_z_'+group+'.txt'] + # body acceleration + filenames += ['body_acc_x_'+group+'.txt', 'body_acc_y_'+group+'.txt', 'body_acc_z_'+group+'.txt'] + # body gyroscope + filenames += ['body_gyro_x_'+group+'.txt', 'body_gyro_y_'+group+'.txt', 'body_gyro_z_'+group+'.txt'] + # load input data + X = load_group(filenames, filepath) + # load class output + y = load_file(prefix + group + '/y_'+group+'.txt') + return X, y + +# load the dataset, returns train and test X and y elements +def load_dataset(prefix=''): + # load all train + trainX, trainy = load_dataset_group('train', prefix + 'HARDataset/') + print(trainX.shape, trainy.shape) + # load all test + testX, testy = load_dataset_group('test', prefix + 'HARDataset/') + print(testX.shape, testy.shape) + # zero-offset class values + trainy = trainy - 1 + testy = testy - 1 + # one hot encode y + trainy = to_categorical(trainy) + testy = to_categorical(testy) + print(trainX.shape, trainy.shape, testX.shape, testy.shape) + return trainX, trainy, testX, testy + +# fit and evaluate a model +def evaluate_model(trainX, trainy, testX, testy): + # define model + verbose, epochs, batch_size = 0, 25, 64 + n_timesteps, n_features, n_outputs = trainX.shape[1], trainX.shape[2], trainy.shape[1] + # reshape into subsequences (samples, time steps, rows, cols, channels) + n_steps, n_length = 4, 32 + trainX = trainX.reshape((trainX.shape[0], n_steps, 1, n_length, n_features)) + testX = testX.reshape((testX.shape[0], n_steps, 1, n_length, n_features)) + # define model + model = Sequential() + model.add(ConvLSTM2D(filters=64, kernel_size=(1,3), activation='relu', input_shape=(n_steps, 1, n_length, n_features))) + model.add(Dropout(0.5)) + model.add(Flatten()) + model.add(Dense(100, activation='relu')) + model.add(Dense(n_outputs, activation='softmax')) + model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) + # fit network + model.fit(trainX, trainy, epochs=epochs, batch_size=batch_size, verbose=verbose) + # evaluate model + _, accuracy = model.evaluate(testX, testy, batch_size=batch_size, verbose=0) + return accuracy + +# summarize scores +def summarize_results(scores): + print(scores) + m, s = mean(scores), std(scores) + print('Accuracy: %.3f%% (+/-%.3f)' % (m, s)) + +# run an experiment +def run_experiment(repeats=10): + # load data + trainX, trainy, testX, testy = load_dataset() + # repeat experiment + scores = list() + for r in range(repeats): + score = evaluate_model(trainX, trainy, testX, testy) + score = score * 100.0 + print('>#%d: %.3f' % (r+1, score)) + scores.append(score) + # summarize results + summarize_results(scores) + +# run the experiment +run_experiment() +``` + +与之前的实验一样,运行模型会在每次拟合和评估时打印模型的表现。最终模型表现的摘要在运行结束时给出。 + +我们可以看到,该模型在实现约 90%的准确度的问题上始终表现良好,可能比较大的 CNN LSTM 模型具有更少的资源。 + +注意:鉴于算法的随机性,您的具体结果可能会有所不同。如果是这样,请尝试运行几次代码。 + +``` +>#1: 90.092 +>#2: 91.619 +>#3: 92.128 +>#4: 90.533 +>#5: 89.243 +>#6: 90.940 +>#7: 92.026 +>#8: 91.008 +>#9: 90.499 +>#10: 89.922 + +[90.09161859518154, 91.61859518154056, 92.12758737699356, 90.53274516457415, 89.24329826942655, 90.93993892093654, 92.02578893790296, 91.00780454699695, 90.49881235154395, 89.92195453003053] + +Accuracy: 90.801% (+/-0.886) +``` + +## 扩展 + +本节列出了一些扩展您可能希望探索的教程的想法。 + +* **数据准备**。考虑探索简单的数据扩展方案是否可以进一步提升模型表现,例如标准化,标准化和电源转换。 +* **LSTM 变化**。 LSTM 架构的变体可以在此问题上实现更好的表现,例如堆叠 LSTM 和双向 LSTM。 +* **超参数调整**。考虑探索模型超参数的调整,例如单位数,训练时期,批量大小等。 + +如果你探索任何这些扩展,我很想知道。 + +## 进一步阅读 + +如果您希望深入了解,本节将提供有关该主题的更多资源。 + +### 文件 + +* [使用智能手机进行人类活动识别的公共领域数据集](https://upcommons.upc.edu/handle/2117/20897),2013 年。 +* [智能手机上的人类活动识别使用多类硬件友好支持向量机](https://link.springer.com/chapter/10.1007/978-3-642-35395-6_30),2012。 +* [卷积,长短期记忆,完全连接的深度神经网络](https://ieeexplore.ieee.org/document/7178838/),2015。 +* [卷积 LSTM 网络:用于降水预报的机器学习方法](https://arxiv.org/abs/1506.04214v1),2015。 + +### 用品 + +* [使用智能手机数据集进行人类活动识别,UCI 机器学习库](https://archive.ics.uci.edu/ml/datasets/human+activity+recognition+using+smartphones) +* [活动识别,维基百科](https://en.wikipedia.org/wiki/Activity_recognition) +* [使用智能手机传感器的活动识别实验,视频](https://www.youtube.com/watch?v=XOEN9W05_4A)。 + +## 摘要 + +在本教程中,您发现了三种循环神经网络体系结构,用于对活动识别时间序列分类问题进行建模。 + +具体来说,你学到了: + +* 如何开发一种用于人类活动识别的长短期记忆循环神经网络。 +* 如何开发一维卷积神经网络 LSTM 或 CNN LSTM 模型。 +* 如何针对同一问题开发一维卷积 LSTM 或 ConvLSTM 模型。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/how-to-get-started-with-deep-learning-for-time-series-forecasting-7-day-mini-course.md b/docs/dl-ts/how-to-get-started-with-deep-learning-for-time-series-forecasting-7-day-mini-course.md new file mode 100644 index 0000000000000000000000000000000000000000..c41b2e46c26c1269e3a111d0d94bdf6e3dd54955 --- /dev/null +++ b/docs/dl-ts/how-to-get-started-with-deep-learning-for-time-series-forecasting-7-day-mini-course.md @@ -0,0 +1,486 @@ +# 如何开始深度学习的时间序列预测(7 天迷你课程) + +> 原文: [https://machinelearningmastery.com/how-to-get-started-with-deep-learning-for-time-series-forecasting-7-day-mini-course/](https://machinelearningmastery.com/how-to-get-started-with-deep-learning-for-time-series-forecasting-7-day-mini-course/) + +### 时间序列预测速成课程的深度学习。 + +#### 在 7 天内为您的时间序列项目带来深度学习方法。 + +时间序列预测具有挑战性,尤其是在处理长序列,噪声数据,多步预测和多个输入和输出变量时。 + +深度学习方法为时间序列预测提供了许多希望,例如时间依赖的自动学习和趋势和季节性等时间结构的自动处理。 + +在本速成课程中,您将了解如何开始并自信地开发深度学习模型,以便在 7 天内使用 Python 进行时间序列预测问题。 + +这是一个重要且重要的帖子。您可能想要将其加入书签。 + +让我们开始吧。 + +[![How to Get Started with Deep Learning for Time Series Forecasting (7-Day Mini-Course)](img/ddabd4eb61af3e12ea5e27191572ab52.jpg)](https://3qeqpr26caki16dnhd19sv6by6v-wpengine.netdna-ssl.com/wp-content/uploads/2018/11/How-to-Get-Started-with-Deep-Learning-for-Time-Series-Forecasting-7-Day-Mini-Course.jpg) + +如何开始深度学习时间序列预测(7 天迷你课程) +摄影: [Brian Richardson](https://www.flickr.com/photos/seriousbri/3736154699/) ,保留一些权利。 + +## 谁是这个崩溃课程? + +在我们开始之前,让我们确保您在正确的位置。 + +以下列表提供了有关本课程设计对象的一般指导原则。 + +你得知道: + +* 您需要了解时间序列预测的基础知识。 +* 你需要了解基本的 Python,NumPy 和 Keras 的深度学习方法。 + +你不需要知道: + +* 你不需要成为一个数学家! +* 你不需要成为一名深度学习专家! +* 你不需要成为时间序列专家! + +这个速成课程将带您从了解一点机器学习的开发人员到可以为您自己的时间序列预测项目带来深度学习方法的开发人员。 + +**注意**:这个速成课程假设你有一个有效的 Python 2 或 3 SciPy 环境,至少安装了 NumPy 和 Keras 2。如果您需要有关环境的帮助,可以按照此处的分步教程进行操作: + +* [如何使用 Anaconda 设置用于机器学习和深度学习的 Python 环境](https://machinelearningmastery.com/setup-python-environment-machine-learning-deep-learning-anaconda/) + +## 速成课程概述 + +这个速成课程分为 7 节课。 + +您可以每天完成一节课(推荐)或在一天内完成所有课程(硬核)。这取决于你有空的时间和你的热情程度。 + +以下 7 个课程将通过深入学习 Python 中的时间序列预测来帮助您开始并提高工作效率: + +* **第 01 课**:深度学习的承诺 +* **第 02 课**:如何转换时间序列数据 +* **第 03 课**:时间序列预测的 MLP +* **第 04 课**:时间序列预测的 CNN +* **第 05 课**:时间序列预测的 LSTM +* **第 06 课:** CNN-LSTM 用于时间序列预测 +* **第 07 课**:编码器 - 解码器 LSTM 多步预测 + +每节课可能需要 60 秒或 30 分钟。花点时间,按照自己的进度完成课程。在下面的评论中提出问题甚至发布结果。 + +课程期望你去学习如何做事。我将给你提示,但每节课的部分内容是强迫你学习去哪里寻求帮助,以及深入学习,时间序列预测和 Python 中最好的工具(提示, _ 我直接在这个博客上找到了所有答案,使用搜索框 _)。 + +我确实以相关帖子的链接形式提供了更多帮助,因为我希望你建立一些信心和惯性。 + +在评论中发布您的结果,我会为你欢呼! + +挂在那里,不要放弃。 + +**注**:这只是一个速成课程。有关更多详细信息和 25 个充实教程,请参阅我的书,主题为“[深度学习时间序列预测](https://machinelearningmastery.com/deep-learning-for-time-series-forecasting/)”。 + +## 第一课:深度学习的承诺 + +在本课程中,您将发现时间序列预测的深度学习方法的前景。 + +通常,像 Multilayer Perceptrons 或 MLP 这样的神经网络提供的功能很少,例如: + +* **强健噪音**。神经网络对输入数据和映射函数中的噪声具有鲁棒性,甚至可以在存在缺失值的情况下支持学习和预测。 +* **非线性**。神经网络不会对映射函数做出强有力的假设,并且很容易学习线性和非线性关系。 +* **多变量输入**。可以指定任意数量的输入要素,为多变量预测提供直接支持。 +* **多步骤预测**。可以指定任意数量的输出值,为多步骤甚至多变量预测提供 + 直接支持。 + +仅就这些功能而言,前馈神经网络可用于时间序列预测。 + +### 你的任务 + +在本课程中,您必须提出卷积神经网络和循环神经网络的一种功能,这些功能可能有助于建模时间序列预测问题。 + +在下面的评论中发表您的答案。我很乐意看到你发现了什么。 + +### 更多信息 + +* [循环神经网络对时间序列预测的承诺](https://machinelearningmastery.com/promise-recurrent-neural-networks-time-series-forecasting/) + +在下一课中,您将了解如何转换时间序列预测的时间序列数据。 + +## 课程 02:如何转换时间序列的数据 + +在本课程中,您将了解如何将时间序列数据转换为监督学习格式。 + +大多数实际机器学习使用监督学习。 + +监督学习是输入变量(X)和输出变量(y)的地方,您可以使用算法来学习从输入到输出的映射函数。目标是近似真实的底层映射,以便在有新输入数据时,可以预测该数据的输出变量。 + +时间序列数据可以表达为监督学习。 + +给定时间序列数据集的数字序列,我们可以将数据重组为看起来像监督学习问题。我们可以使用前面的时间步长作为输入变量,并使用下一个时间步作为输出变量。 + +例如,系列: + +``` +1, 2, 3, 4, 5, ... +``` + +可以转换为具有输入和输出组件的样本,这些组件可以用作训练集的一部分,以训练监督学习模型,如深度学习神经网络。 + +``` +X, y +[1, 2, 3] 4 +[2, 3, 4] 5 +... +``` + +这称为滑动窗口转换,因为它就像在先前观察中滑动窗口一样,用作模型的输入以预测序列中的下一个值。在这种情况下,窗口宽度是 3 个时间步长。 + +### 你的任务 + +在本课程中,您必须开发 Python 代码,将每日女性分娩数据集转换为具有一定数量输入和一个输出的监督学习格式。 + +你可以从这里下载数据集: [daily-total-female-births.csv](https://raw.githubusercontent.com/jbrownlee/Datasets/master/daily-total-female-births.csv) + +在下面的评论中发表您的答案。我很乐意看到你发现了什么。 + +### 更多信息 + +* [时间序列预测作为监督学习](https://machinelearningmastery.com/time-series-forecasting-supervised-learning/) +* [如何将时间序列转换为 Python 中的监督学习问题](https://machinelearningmastery.com/convert-time-series-supervised-learning-problem-python/) +* [如何为长期短期记忆网络准备单变量时间序列数据](https://machinelearningmastery.com/prepare-univariate-time-series-data-long-short-term-memory-networks/) + +在下一课中,您将了解如何开发用于预测单变量时间序列的多层感知器深度学习模型。 + +## 第 03 课:时间序列预测的 MLP + +在本课程中,您将了解如何为单变量时间序列预测开发多层感知器模型或 MLP。 + +我们可以将一个简单的单变量问题定义为整数序列,使模型适合该序列,并让模型预测序列中的下一个值。我们将问题框架为 3 输入和 1 输出,例如:[10,20,30]作为输入,[40]作为输出。 + +首先,我们可以定义模型。我们将通过第一个隐藏层上的 _input_dim_ 参数将输入时间步数定义为 3。在这种情况下,我们将使用随机梯度下降的有效 Adam 版本并优化均方误差(' _mse_ ')损失函数。 + +一旦定义了模型,它就可以适合训练数据,并且拟合模型可以用于进行预测。 + +下面列出了完整的示例。 + +``` +# univariate mlp example +from numpy import array +from keras.models import Sequential +from keras.layers import Dense +# define dataset +X = array([[10, 20, 30], [20, 30, 40], [30, 40, 50], [40, 50, 60]]) +y = array([40, 50, 60, 70]) +# define model +model = Sequential() +model.add(Dense(100, activation='relu', input_dim=3)) +model.add(Dense(1)) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit(X, y, epochs=2000, verbose=0) +# demonstrate prediction +x_input = array([50, 60, 70]) +x_input = x_input.reshape((1, 3)) +yhat = model.predict(x_input, verbose=0) +print(yhat) +``` + +运行该示例将使模型适合数据,然后预测下一个样本外的值。 + +给定[50,60,70]作为输入,模型正确地预测 80 作为序列中的下一个值。 + +### 你的任务 + +在本课程中,您必须下载每日女性分娩数据集,将其分为训练集和测试集,并开发一个可以对测试集进行合理准确预测的模型。 + +你可以从这里下载数据集: [daily-total-female-births.csv](https://raw.githubusercontent.com/jbrownlee/Datasets/master/daily-total-female-births.csv) + +在下面的评论中发表您的答案。我很乐意看到你发现了什么。 + +### 更多信息 + +* [多层感知器神经网络速成课程](https://machinelearningmastery.com/neural-networks-crash-course/) +* [Keras 中深度学习的时间序列预测](https://machinelearningmastery.com/time-series-prediction-with-deep-learning-in-python-with-keras/) +* [用于时间序列预测的多层感知器网络的探索性配置](https://machinelearningmastery.com/exploratory-configuration-multilayer-perceptron-network-time-series-forecasting/) + +在下一课中,您将了解如何开发用于预测单变量时间序列的卷积神经网络模型。 + +## 第 04 课:CNN 进行时间序列预测 + +在本课程中,您将了解如何开发用于单变量时间序列预测的卷积神经网络模型或 CNN。 + +我们可以将一个简单的单变量问题定义为整数序列,使模型适合该序列,并让模型预测序列中的下一个值。我们将问题框架为 3 输入和 1 输出,例如:[10,20,30]作为输入,[40]作为输出。 + +与 MLP 模型的一个重要区别是 CNN 模型需要具有[_ 样本,时间步长,特征 _]形状的三维输入。我们将以[_ 样本,时间步长 _]的形式定义数据并相应地重新整形。 + +我们将通过第一个隐藏层上的 _input_shape_ 参数将输入时间步数定义为 3,将要素数定义为 1。 + +我们将使用一个卷积隐藏层,后跟最大池池。然后,在由 Dense 层解释并输出预测之前,将滤镜图展平。该模型使用随机梯度下降的有效 Adam 模型,并优化均方误差(' _mse_ ')损失函数。 + +一旦定义了模型,它就可以适合训练数据,并且拟合模型可以用于进行预测。 + +下面列出了完整的示例。 + +``` +# univariate cnn example +from numpy import array +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import Flatten +from keras.layers.convolutional import Conv1D +from keras.layers.convolutional import MaxPooling1D +# define dataset +X = array([[10, 20, 30], [20, 30, 40], [30, 40, 50], [40, 50, 60]]) +y = array([40, 50, 60, 70]) +# reshape from [samples, timesteps] into [samples, timesteps, features] +X = X.reshape((X.shape[0], X.shape[1], 1)) +# define model +model = Sequential() +model.add(Conv1D(filters=64, kernel_size=2, activation='relu', input_shape=(3, 1))) +model.add(MaxPooling1D(pool_size=2)) +model.add(Flatten()) +model.add(Dense(50, activation='relu')) +model.add(Dense(1)) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit(X, y, epochs=1000, verbose=0) +# demonstrate prediction +x_input = array([50, 60, 70]) +x_input = x_input.reshape((1, 3, 1)) +yhat = model.predict(x_input, verbose=0) +print(yhat) +``` + +运行该示例将使模型适合数据,然后预测下一个样本外的值。 + +给定[50,60,70]作为输入,模型正确地预测 80 作为序列中的下一个值。 + +### 你的任务 + +在本课程中,您必须下载每日女性分娩数据集,将其分为训练集和测试集,并开发一个可以对测试集进行合理准确预测的模型。 + +你可以从这里下载数据集: [daily-total-female-births.csv](https://raw.githubusercontent.com/jbrownlee/Datasets/master/daily-total-female-births.csv) + +在下面的评论中发表您的答案。我很乐意看到你发现了什么。 + +### 更多信息 + +* [用于机器学习的卷积神经网络的速成课程](https://machinelearningmastery.com/crash-course-convolutional-neural-networks/) + +在下一课中,您将了解如何开发长短期记忆网络模型以预测单变量时间序列。 + +## 课 05:时间序列预测的 LSTM + +在本课程中,您将了解如何开发长期短期记忆神经网络模型或 LSTM 以进行单变量时间序列预测。 + +我们可以将一个简单的单变量问题定义为整数序列,使模型适合该序列,并让模型预测序列中的下一个值。我们将问题框架为 3 输入和 1 输出,例如:[10,20,30]作为输入,[40]作为输出。 + +与 MLP 模型的重要区别在于,与 CNN 模型一样,LSTM 模型需要具有形状[_ 样本,时间步长,特征 _]的三维输入。我们将以[_ 样本,时间步长 _]的形式定义数据并相应地重新整形。 + +我们将通过第一个隐藏层上的 _input_shape_ 参数将输入时间步数定义为 3,将要素数定义为 1。 + +我们将使用一个 LSTM 层来处理 3 个时间步的每个输入子序列,然后使用 Dense 层来解释输入序列的摘要。该模型使用随机梯度下降的有效 Adam 模型,并优化均方误差(' _mse_ ')损失函数。 + +一旦定义了模型,它就可以适合训练数据,并且拟合模型可以用于进行预测。 + +下面列出了完整的示例。 + +``` +# univariate lstm example +from numpy import array +from keras.models import Sequential +from keras.layers import LSTM +from keras.layers import Dense +# define dataset +X = array([[10, 20, 30], [20, 30, 40], [30, 40, 50], [40, 50, 60]]) +y = array([40, 50, 60, 70]) +# reshape from [samples, timesteps] into [samples, timesteps, features] +X = X.reshape((X.shape[0], X.shape[1], 1)) +# define model +model = Sequential() +model.add(LSTM(50, activation='relu', input_shape=(3, 1))) +model.add(Dense(1)) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit(X, y, epochs=1000, verbose=0) +# demonstrate prediction +x_input = array([50, 60, 70]) +x_input = x_input.reshape((1, 3, 1)) +yhat = model.predict(x_input, verbose=0) +print(yhat) +``` + +运行该示例将使模型适合数据,然后预测下一个样本外的值。 + +给定[50,60,70]作为输入,模型正确地预测 80 作为序列中的下一个值。 + +### 你的任务 + +在本课程中,您必须下载每日女性分娩数据集,将其分为训练集和测试集,并开发一个可以对测试集进行合理准确预测的模型。 + +你可以从这里下载数据集: [daily-total-female-births.csv](https://raw.githubusercontent.com/jbrownlee/Datasets/master/daily-total-female-births.csv) + +在下面的评论中发表您的答案。我很乐意看到你发现了什么。 + +### 更多信息 + +* [专家对长短期记忆网络的简要介绍](https://machinelearningmastery.com/gentle-introduction-long-short-term-memory-networks-experts/) +* [深度学习的循环神经网络崩溃课程](https://machinelearningmastery.com/crash-course-recurrent-neural-networks-deep-learning/) + +在下一课中,您将了解如何针对单变量时间序列预测问题开发混合 CNN-LSTM 模型。 + +## 第 06 课:CNN-LSTM 用于时间序列预测 + +在本课程中,您将了解如何开发用于单变量时间序列预测的混合 CNN-LSTM 模型。 + +该模型的好处是该模型可以支持非常长的输入序列,可以通过 CNN 模型作为块或子序列读取,然后由 LSTM 模型拼凑在一起。 + +我们可以将一个简单的单变量问题定义为整数序列,使模型适合该序列,并让模型预测序列中的下一个值。我们将问题框架为 4 输入和 1 输出,例如:[10,20,30,40]作为输入,[50]作为输出。 + +当使用混合 CNN-LSTM 模型时,我们将进一步将每个样本分成更多的子序列。 CNN 模型将解释每个子序列,并且 LSTM 将来自子序列的解释拼凑在一起。因此,我们将每个样本分成 2 个子序列,每个子序列 2 次。 + +CNN 将被定义为每个子序列有一个特征需要 2 个时间步长。然后将整个 CNN 模型包装在 TimeDistributed 包装层中,以便可以将其应用于样本中的每个子序列。然后在模型输出预测之前由 LSTM 层解释结果。 + +该模型使用随机梯度下降的有效 Adam 模型,并优化均方误差('mse')损失函数。 + +一旦定义了模型,它就可以适合训练数据,并且拟合模型可以用于进行预测。 + +下面列出了完整的示例。 + +``` +# univariate cnn-lstm example +from numpy import array +from keras.models import Sequential +from keras.layers import LSTM +from keras.layers import Dense +from keras.layers import Flatten +from keras.layers import TimeDistributed +from keras.layers.convolutional import Conv1D +from keras.layers.convolutional import MaxPooling1D +# define dataset +X = array([[10, 20, 30, 40], [20, 30, 40, 50], [30, 40, 50, 60], [40, 50, 60, 70]]) +y = array([50, 60, 70, 80]) +# reshape from [samples, timesteps] into [samples, subsequences, timesteps, features] +X = X.reshape((X.shape[0], 2, 2, 1)) +# define model +model = Sequential() +model.add(TimeDistributed(Conv1D(filters=64, kernel_size=1, activation='relu'), input_shape=(None, 2, 1))) +model.add(TimeDistributed(MaxPooling1D(pool_size=2))) +model.add(TimeDistributed(Flatten())) +model.add(LSTM(50, activation='relu')) +model.add(Dense(1)) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit(X, y, epochs=500, verbose=0) +# demonstrate prediction +x_input = array([50, 60, 70, 80]) +x_input = x_input.reshape((1, 2, 2, 1)) +yhat = model.predict(x_input, verbose=0) +print(yhat) +``` + +运行该示例将使模型适合数据,然后预测下一个样本外的值。 + +给定[50,60,70,80]作为输入,模型正确地预测 90 作为序列中的下一个值。 + +### 你的任务 + +在本课程中,您必须下载每日女性分娩数据集,将其分为训练集和测试集,并开发一个可以对测试集进行合理准确预测的模型。 + +你可以从这里下载数据集: [daily-total-female-births.csv](https://raw.githubusercontent.com/jbrownlee/Datasets/master/daily-total-female-births.csv) + +在下面的评论中发表您的答案。我很乐意看到你发现了什么。 + +### 更多信息 + +* [CNN 长短期记忆网络](https://machinelearningmastery.com/cnn-long-short-term-memory-networks/) +* [如何在 Python](https://machinelearningmastery.com/timedistributed-layer-for-long-short-term-memory-networks-in-python/) 中为长期短期内存网络使用时间分布层 + +在下一课中,您将了解如何开发用于多步时间序列预测的编码器 - 解码器 LSTM 网络模型。 + +## 课程 07:编码器 - 解码器 LSTM 多步预测 + +在本课程中,您将了解如何为多步时间序列预测开发编码器 - 解码器 LSTM 网络模型。 + +我们可以将一个简单的单变量问题定义为整数序列,使模型适合该序列,并让模型预测序列中的下两个值。我们将问题框架为 3 输入和 2 输出,例如:[10,20,30]作为输入,[40,50]作为输出。 + +LSTM 模型需要具有[_ 样本,时间步长,特征 _]形状的三维输入。我们将以[_ 样本,时间步长 _]的形式定义数据并相应地重新整形。使用编码器 - 解码器模型时,输出也必须以这种方式成形。 + +我们将通过第一个隐藏层上的 _input_shape_ 参数将输入时间步数定义为 3,将要素数定义为 1。 + +我们将定义一个 LSTM 编码器来读取和编码 3 个时间步的输入序列。对于使用 RepeatVector 层的模型所需的两个输出时间步长,模型将重复编码序列 2 次。在使用包含在 TimeDistributed 层中的 Dense 输出层之前,这些将被馈送到解码器 LSTM 层,该层将为输出序列中的每个步骤产生一个输出。 + +该模型使用随机梯度下降的有效 Adam 模型,并优化均方误差(' _mse_ ')损失函数。 + +一旦定义了模型,它就可以适合训练数据,并且拟合模型可以用于进行预测。 + +下面列出了完整的示例。 + +``` +# multi-step encoder-decoder lstm example +from numpy import array +from keras.models import Sequential +from keras.layers import LSTM +from keras.layers import Dense +from keras.layers import RepeatVector +from keras.layers import TimeDistributed +# define dataset +X = array([[10, 20, 30], [20, 30, 40], [30, 40, 50], [40, 50, 60]]) +y = array([[40,50],[50,60],[60,70],[70,80]]) +# reshape from [samples, timesteps] into [samples, timesteps, features] +X = X.reshape((X.shape[0], X.shape[1], 1)) +y = y.reshape((y.shape[0], y.shape[1], 1)) +# define model +model = Sequential() +model.add(LSTM(100, activation='relu', input_shape=(3, 1))) +model.add(RepeatVector(2)) +model.add(LSTM(100, activation='relu', return_sequences=True)) +model.add(TimeDistributed(Dense(1))) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit(X, y, epochs=100, verbose=0) +# demonstrate prediction +x_input = array([50, 60, 70]) +x_input = x_input.reshape((1, 3, 1)) +yhat = model.predict(x_input, verbose=0) +print(yhat) +``` + +运行该示例将使模型适合数据,然后预测接下来的两个样本外值。 + +给定[50,60,70]作为输入,模型正确地预测[80,90]作为序列中的下两个值。 + +### 你的任务 + +在本课程中,您必须下载每日女性分娩数据集,将其分为训练集和测试集,并开发一个可以对测试集进行合理准确预测的模型。 + +你可以从这里下载数据集: [daily-total-female-births.csv](https://raw.githubusercontent.com/jbrownlee/Datasets/master/daily-total-female-births.csv) + +在下面的评论中发表您的答案。我很乐意看到你发现了什么。 + +### 更多信息 + +* [编码器 - 解码器长短期存储器网络](https://machinelearningmastery.com/encoder-decoder-long-short-term-memory-networks/) +* [多步时间序列预测的 4 种策略](https://machinelearningmastery.com/multi-step-time-series-forecasting/) +* [Python 中长期短期记忆网络的多步时间序列预测](https://machinelearningmastery.com/multi-step-time-series-forecasting-long-short-term-memory-networks-python/) + +## 结束! +(_ 看你有多远 _) + +你做到了。做得好! + +花点时间回顾一下你到底有多远。 + +你发现: + +* 深度学习神经网络对时间序列预测问题的承诺。 +* 如何将时间序列数据集转换为监督学习问题。 +* 如何为单变量时间序列预测问题开发多层感知器模型。 +* 如何建立一个单变量时间序列预测问题的卷积神经网络模型。 +* 如何为单变量时间序列预测问题开发长短期记忆网络模型。 +* 如何为单变量时间序列预测问题开发混合 CNN-LSTM 模型。 +* 如何为多步时间序列预测问题开发编码器 - 解码器 LSTM 模型。 + +这只是您深入学习时间序列预测的旅程的开始。继续练习和发展你的技能。 + +下一步,查看我的书[深度学习时间序列](https://machinelearningmastery.com/deep-learning-for-time-series-forecasting/)。 + +## 摘要 + +**你是如何使用迷你课程的?** +你喜欢这个速成班吗? + +**你有什么问题吗?有没有任何问题?** +让我知道。在下面发表评论。 \ No newline at end of file diff --git a/docs/dl-ts/how-to-grid-search-deep-learning-models-for-time-series-forecasting.md b/docs/dl-ts/how-to-grid-search-deep-learning-models-for-time-series-forecasting.md new file mode 100644 index 0000000000000000000000000000000000000000..869bf5d3a5d0a82e2e62ce96951bd4defc470f5e --- /dev/null +++ b/docs/dl-ts/how-to-grid-search-deep-learning-models-for-time-series-forecasting.md @@ -0,0 +1,1461 @@ +# 如何网格搜索深度学习模型进行时间序列预测 + +> 原文: [https://machinelearningmastery.com/how-to-grid-search-deep-learning-models-for-time-series-forecasting/](https://machinelearningmastery.com/how-to-grid-search-deep-learning-models-for-time-series-forecasting/) + +网格搜索通常不是我们可以使用深度学习方法执行的操作。 + +这是因为深度学习方法通​​常需要大量数据和大型模型,因此需要花费数小时,数天或数周才能训练的模型。 + +在数据集较小的情况下,例如单变量时间序列,可以使用网格搜索来调整深度学习模型的超参数。 + +在本教程中,您将了解如何为深度学习模型开发网格搜索超参数框架。 + +完成本教程后,您将了解: + +* 如何开发用于调整模型超参数的通用网格搜索框架。 +* 如何在航空公司乘客单变量时间序列预测问题上对多层感知器模型进行网格搜索超参数。 +* 如何使框架适应卷积和长期短期记忆神经网络的网格搜索超参数。 + +让我们开始吧。 + +![How to Grid Search Deep Learning Models for Time Series Forecasting](img/00332986f38919ccfc2da94100dfef15.jpg) + +如何网格搜索时间序列预测的深度学习模型 +照片由 [Hannes Flo](https://www.flickr.com/photos/hannesflo/40192605640/) ,保留一些权利。 + +## 教程概述 + +本教程分为五个部分;他们是: + +1. 时间序列问题 +2. 网格搜索框架 +3. 网格搜索多层感知器 +4. 网格搜索卷积神经网络 +5. 网格搜索长短期记忆网络 + +## 时间序列问题 + +'_ 月度航空公司乘客 _'数据集总结了 1949 年至 1960 年期间航空公司每月数千人的国际旅客总数。 + +直接从这里下载数据集: + +* [monthly-airline-passengers.csv](https://raw.githubusercontent.com/jbrownlee/Datasets/master/airline-passengers.csv) + +在当前工作目录中使用文件名“ _monthly-airline-passengers.csv_ ”保存文件。 + +我们可以使用函数 _read_csv()_ 将此数据集作为 Pandas 系列加载。 + +``` +# load +series = read_csv('monthly-airline-passengers.csv', header=0, index_col=0) +``` + +加载后,我们可以总结数据集的形状,以确定观察的数量。 + +``` +# summarize shape +print(series.shape) +``` + +然后我们可以创建该系列的线图,以了解该系列的结构。 + +``` +# plot +pyplot.plot(series) +pyplot.show() +``` + +我们可以将所有这些结合在一起;下面列出了完整的示例。 + +``` +# load and plot dataset +from pandas import read_csv +from matplotlib import pyplot +# load +series = read_csv('monthly-airline-passengers.csv', header=0, index_col=0) +# summarize shape +print(series.shape) +# plot +pyplot.plot(series) +pyplot.show() +``` + +首先运行该示例将打印数据集的形状。 + +``` +(144, 1) +``` + +该数据集是每月一次,有 12 年或 144 次观测。在我们的测试中,我们将使用去年或 12 个观测值作为测试集。 + +创建线图。数据集具有明显的趋势和季节性成分。季节性成分的期限为 12 个月。 + +![Line Plot of Monthly International Airline Passengers](img/7861f24e2b2586d15ce18a175792112a.jpg) + +每月国际航空公司乘客的线路情节 + +在本教程中,我们将介绍用于网格搜索的工具,但我们不会针对此问题优化模型超参数。相反,我们将演示如何通常网格搜索深度学习模型超参数,并找到与天真模型相比具有一定技巧的模型。 + +从之前的实验中,一个天真的模型可以通过持续 12 个月前的值(相对指数-12)来实现 50.70 的均方根误差或 RMSE(记住单位是数千名乘客)。 + +这个天真模型的表现提供了一个被认为适合这个问题的模型的约束。任何在过去 12 个月内达到低于 50.70 的预测表现的模型都具有技巧。 + +应该注意的是,调谐的 ETS 模型可以实现 17.09 的 RMSE,并且调谐的 SARIMA 可以实现 13.89 的 RMSE。这些为这个问题提供了一个调整良好的深度学习模型的预期的下限。 + +现在我们已经定义了模型技能的问题和期望,我们可以看看定义网格搜索测试工具。 + +## 网格搜索框架 + +在本节中,我们将开发一个网格搜索测试工具,可用于评估不同神经网络模型的一系列超参数,例如 MLP,CNN 和 LSTM。 + +本节分为以下几部分: + +1. 火车 - 测试分裂 +2. 系列作为监督学习 +3. 前瞻性验证 +4. 重复评估 +5. 总结表现 +6. 工作示例 + +### 火车 - 测试分裂 + +第一步是将加载的系列分成训练和测试集。 + +我们将使用前 11 年(132 个观测值)进行训练,最后 12 个用于测试集。 + +下面的 _train_test_split()_ 函数将拆分系列,将原始观察值和在测试集中使用的观察数作为参数。 + +``` +# split a univariate dataset into train/test sets +def train_test_split(data, n_test): + return data[:-n_test], data[-n_test:] +``` + +### 系列作为监督学习 + +接下来,我们需要能够将单变量观测系列框架化为监督学习问题,以便我们可以训练神经网络模型。 + +系列的监督学习框架意味着数据需要分成模型从中学习和概括的多个示例。 + +每个样本必须同时具有输入组件和输出组件。 + +输入组件将是一些先前的观察,例如三年或 36 个时间步骤。 + +输出组件将是下个月的总销售额,因为我们有兴趣开发一个模型来进行一步预测。 + +我们可以使用 pandas DataFrame 上的 [shift()函数](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.shift.html)来实现它。它允许我们向下移动一列(向前移动)或向后移动(向后移动)。我们可以将该系列作为一列数据,然后创建列的多个副本,向前或向后移动,以便使用我们需要的输入和输出元素创建样本。 + +当一个系列向下移动时,会引入 NaN 值,因为我们没有超出系列开头的值。 + +例如,系列定义为列: + +``` +(t) +1 +2 +3 +4 +``` + +此列可以预先移位并作为列插入: + +``` +(t-1), (t) +Nan, 1 +1, 2 +2, 3 +3, 4 +4 NaN +``` + +我们可以看到,在第二行,值 1 作为输入提供,作为前一时间步的观察,2 是系列中可以预测的下一个值,或者当 1 是预测模型时要学习的值作为输入呈现。 + +可以删除具有 NaN 值的行。 + +下面的 _series_to_supervised()_ 函数实现了这种行为,允许您指定输入中使用的滞后观察数和每个样本的输出中使用的数。它还将删除具有 NaN 值的行,因为它们不能用于训练或测试模型。 + +``` +# transform list into supervised learning format +def series_to_supervised(data, n_in=1, n_out=1): + df = DataFrame(data) + cols = list() + # input sequence (t-n, ... t-1) + for i in range(n_in, 0, -1): + cols.append(df.shift(i)) + # forecast sequence (t, t+1, ... t+n) + for i in range(0, n_out): + cols.append(df.shift(-i)) + # put it all together + agg = concat(cols, axis=1) + # drop rows with NaN values + agg.dropna(inplace=True) + return agg.values +``` + +### 前瞻性验证 + +可以使用前向验证在测试集上评估时间序列预测模型。 + +前瞻性验证是一种方法,其中模型一次一个地对测试数据集中的每个观察进行预测。在对测试数据集中的时间步长进行每个预测之后,将预测的真实观察结果添加到测试数据集并使其可用于模型。 + +在进行后续预测之前,可以使用观察结果更简单的模型。考虑到更高的计算成本,更复杂的模型,例如神经网络,不会被改装。 + +然而,时间步骤的真实观察可以用作输入的一部分,用于在下一个时间步骤上进行预测。 + +首先,数据集分为训练集和测试集。我们将调用 _train_test_split()_ 函数来执行此拆分并传入预先指定数量的观察值以用作测试数据。 + +对于给定配置,模型将适合训练数据集一次。 + +我们将定义一个通用的 _model_fit()_ 函数来执行此操作,可以为我们稍后可能感兴趣的给定类型的神经网络填充该操作。该函数获取训练数据集和模型配置,并返回准备好进行预测的拟合模型。 + +``` +# fit a model +def model_fit(train, config): + return None +``` + +枚举测试数据集的每个时间步。使用拟合模型进行预测。 + +同样,我们将定义一个名为 _model_predict()_ 的通用函数,它采用拟合模型,历史和模型配置,并进行单个一步预测。 + +``` +# forecast with a pre-fit model +def model_predict(model, history, config): + return 0.0 +``` + +将预测添加到预测列表中,并将来自测试集的真实观察结果添加到用训练数据集中的所有观察结果播种的观察列表中。此列表在前向验证的每个步骤中构建,允许模型使用最新历史记录进行一步预测。 + +然后可以将所有预测与测试集中的真实值进行比较,并计算误差测量值。 + +我们将计算预测和真实值之间的均方根误差或 RMSE。 + +RMSE 计算为预测值与实际值之间的平方差的平均值的平方根。 _measure_rmse()_ 使用 [mean_squared_error()scikit-learn](http://scikit-learn.org/stable/modules/generated/sklearn.metrics.mean_squared_error.html) 函数在计算平方根之前首先计算均方误差或 MSE。 + +``` +# root mean squared error or rmse +def measure_rmse(actual, predicted): + return sqrt(mean_squared_error(actual, predicted)) +``` + +下面列出了将所有这些联系在一起的完整 _walk_forward_validation()_ 函数。 + +它采用数据集,用作测试集的观察数量以及模型的配置,并返回测试集上模型表现的 RMSE。 + +``` +# walk-forward validation for univariate data +def walk_forward_validation(data, n_test, cfg): + predictions = list() + # split dataset + train, test = train_test_split(data, n_test) + # fit model + model = model_fit(train, cfg) + # seed history with training dataset + history = [x for x in train] + # step over each time-step in the test set + for i in range(len(test)): + # fit model and make forecast for history + yhat = model_predict(model, history, cfg) + # store forecast in list of predictions + predictions.append(yhat) + # add actual observation to history for the next loop + history.append(test[i]) + # estimate prediction error + error = measure_rmse(test, predictions) + print(' > %.3f' % error) + return error +``` + +### 重复评估 + +神经网络模型是随机的。 + +这意味着,在给定相同的模型配置和相同的训练数据集的情况下,每次训练模型时将产生不同的内部权重集,这反过来将具有不同的表现。 + +这是一个好处,允许模型自适应并找到复杂问题的高表现配置。 + +在评估模型的表现和选择用于进行预测的最终模型时,这也是一个问题。 + +为了解决模型评估问题,我们将通过[前进验证](https://machinelearningmastery.com/backtest-machine-learning-models-time-series-forecasting/)多次评估模型配置,并将错误报告为每次评估的平均误差。 + +对于大型神经网络而言,这并不总是可行的,并且可能仅适用于能够在几分钟或几小时内完成的小型网络。 + +下面的 _repeat_evaluate()_ 函数实现了这一点,并允许将重复次数指定为默认为 10 的可选参数,并返回所有重复的平均 RMSE 分数。 + +``` +# score a model, return None on failure +def repeat_evaluate(data, config, n_test, n_repeats=10): + # convert config to a key + key = str(config) + # fit and evaluate the model n times + scores = [walk_forward_validation(data, n_test, config) for _ in range(n_repeats)] + # summarize score + result = mean(scores) + print('> Model[%s] %.3f' % (key, result)) + return (key, result) +``` + +### 网格搜索 + +我们现在拥有框架的所有部分。 + +剩下的就是驱动搜索的功能。我们可以定义 _grid_search()_ 函数,该函数获取数据集,要搜索的配置列表以及用作测试集的观察数量并执行搜索。 + +一旦为每个配置计算平均分数,配置列表将按升序排序,以便首先列出最佳分数。 + +完整的功能如下所列。 + +``` +# grid search configs +def grid_search(data, cfg_list, n_test): + # evaluate configs + scores = scores = [score_model(data, n_test, cfg) for cfg in cfg_list] + # sort configs by error, asc + scores.sort(key=lambda tup: tup[1]) + return scores +``` + +### 工作示例 + +现在我们已经定义了测试工具的元素,我们可以将它们绑定在一起并定义一个简单的持久性模型。 + +我们不需要拟合模型,因此 _model_fit()_ 函数将被实现为简单地返回 None。 + +``` +# fit a model +def model_fit(train, config): + return None +``` + +我们将使用配置来定义先前观察中的索引偏移列表,该列表相对于将被用作预测的预测时间。例如,12 将使用 12 个月前(-12)相对于预测时间的观察。 + +``` +# define config +cfg_list = [1, 6, 12, 24, 36] +``` + +可以实现 _model_predict()_ 函数以使用此配置将值保持在负相对偏移处。 + +``` +# forecast with a pre-fit model +def model_predict(model, history, offset): + history[-offset] +``` + +下面列出了使用简单持久性模型使用框架的完整示例。 + +``` +# grid search persistence models for airline passengers +from math import sqrt +from numpy import mean +from pandas import read_csv +from sklearn.metrics import mean_squared_error + +# split a univariate dataset into train/test sets +def train_test_split(data, n_test): + return data[:-n_test], data[-n_test:] + +# root mean squared error or rmse +def measure_rmse(actual, predicted): + return sqrt(mean_squared_error(actual, predicted)) + +# fit a model +def model_fit(train, config): + return None + +# forecast with a pre-fit model +def model_predict(model, history, offset): + return history[-offset] + +# walk-forward validation for univariate data +def walk_forward_validation(data, n_test, cfg): + predictions = list() + # split dataset + train, test = train_test_split(data, n_test) + # fit model + model = model_fit(train, cfg) + # seed history with training dataset + history = [x for x in train] + # step over each time-step in the test set + for i in range(len(test)): + # fit model and make forecast for history + yhat = model_predict(model, history, cfg) + # store forecast in list of predictions + predictions.append(yhat) + # add actual observation to history for the next loop + history.append(test[i]) + # estimate prediction error + error = measure_rmse(test, predictions) + print(' > %.3f' % error) + return error + +# score a model, return None on failure +def repeat_evaluate(data, config, n_test, n_repeats=10): + # convert config to a key + key = str(config) + # fit and evaluate the model n times + scores = [walk_forward_validation(data, n_test, config) for _ in range(n_repeats)] + # summarize score + result = mean(scores) + print('> Model[%s] %.3f' % (key, result)) + return (key, result) + +# grid search configs +def grid_search(data, cfg_list, n_test): + # evaluate configs + scores = scores = [repeat_evaluate(data, cfg, n_test) for cfg in cfg_list] + # sort configs by error, asc + scores.sort(key=lambda tup: tup[1]) + return scores + +# define dataset +series = read_csv('monthly-airline-passengers.csv', header=0, index_col=0) +data = series.values +# data split +n_test = 12 +# model configs +cfg_list = [1, 6, 12, 24, 36] +# grid search +scores = grid_search(data, cfg_list, n_test) +print('done') +# list top 10 configs +for cfg, error in scores[:10]: + print(cfg, error) +``` + +运行该示例将打印在最近 12 个月的数据中使用前向验证评估的模型的 RMSE。 + +每个模型配置被评估 10 次,但是,因为模型没有随机元素,所以每次得分都相同。 + +在运行结束时,将报告前三个执行模型配置的配置和 RMSE。 + +我们可以看到,正如我们可能预期的那样,持续一年前的值(相对偏移-12)导致持久性模型的最佳表现。 + +``` +... + > 110.274 + > 110.274 + > 110.274 +> Model[36] 110.274 +done +12 50.708316214732804 +1 53.1515129919491 +24 97.10990337413241 +36 110.27352356753639 +6 126.73495965991387 +``` + +现在我们有一个强大的网格搜索模型超参数测试工具,我们可以用它来评估一套神经网络模型。 + +## 网格搜索多层感知器 + +我们可能希望调整 MLP 的许多方面。 + +我们将定义一个非常简单的模型,其中包含一个隐藏层,并定义五个超参数进行调整。他们是: + +* **n_input** :用作模型输入的先前输入数(例如 12 个月)。 +* **n_nodes** :隐藏层中使用的节点数(例如 50)。 +* **n_epochs** :训练时期的数量(例如 1000)。 +* **n_batch** :每个小批量中包含的样本数(例如 32)。 +* **n_diff** :差分顺序(例如 0 或 12)。 + +现代神经网络可以通过很少的预处理来处理原始数据,例如缩放和差分。然而,当涉及时间序列数据时,有时差异系列可以使问题更容易建模。 + +回想一下,[差分](https://machinelearningmastery.com/remove-trends-seasonality-difference-transform-python/)是数据的变换,使得从当前观察中减去先前观察的值,去除趋势或季节性结构。 + +我们将为网格搜索测试工具添加差异支持,以防它为您的特定问题增加价值。它确实为内部航空公司乘客数据集增加了价值。 + +下面的 _ 差异()_ 函数将计算数据集的给定顺序的差异。 + +``` +# difference dataset +def difference(data, order): + return [data[i] - data[i - order] for i in range(order, len(data))] +``` + +差异将是可选的,其中 0 的顺序表示没有差异,而 1 阶或 12 阶将要求在拟合模型之前差异数据并且模型的预测需要在返回预测之前反转差分。 + +我们现在可以定义在测试工具中安装 MLP 模型所需的元素。 + +首先,我们必须解压缩超参数列表。 + +``` +# unpack config +n_input, n_nodes, n_epochs, n_batch, n_diff = config +``` + +接下来,我们必须准备数据,包括差分,将数据转换为监督格式,并分离出数据样本的输入和输出方面。 + +``` +# prepare data +if n_diff > 0: + train = difference(train, n_diff) +# transform series into supervised format +data = series_to_supervised(train, n_in=n_input) +# separate inputs and outputs +train_x, train_y = data[:, :-1], data[:, -1] +``` + +我们现在可以使用提供的配置定义和拟合模型。 + +``` +# define model +model = Sequential() +model.add(Dense(n_nodes, activation='relu', input_dim=n_input)) +model.add(Dense(1)) +model.compile(loss='mse', optimizer='adam') +# fit model +model.fit(train_x, train_y, epochs=n_epochs, batch_size=n_batch, verbose=0) +``` + +下面列出了 _model_fit()_ 函数的完整实现。 + +``` +# fit a model +def model_fit(train, config): + # unpack config + n_input, n_nodes, n_epochs, n_batch, n_diff = config + # prepare data + if n_diff > 0: + train = difference(train, n_diff) + # transform series into supervised format + data = series_to_supervised(train, n_in=n_input) + # separate inputs and outputs + train_x, train_y = data[:, :-1], data[:, -1] + # define model + model = Sequential() + model.add(Dense(n_nodes, activation='relu', input_dim=n_input)) + model.add(Dense(1)) + model.compile(loss='mse', optimizer='adam') + # fit model + model.fit(train_x, train_y, epochs=n_epochs, batch_size=n_batch, verbose=0) + return model +``` + +五个选择的超参数绝不是要调整的模型的唯一或最佳超参数。您可以修改该功能以调整其他参数,例如更多隐藏层的添加和大小等等。 + +一旦模型适合,我们就可以使用它来进行预测。 + +如果数据差异,则必须反转差异以预测模型。这涉及将历史的相对偏移处的值添加回模型预测的值。 + +``` +# invert difference +correction = 0.0 +if n_diff > 0: + correction = history[-n_diff] +... +# correct forecast if it was differenced +return correction + yhat[0] +``` + +这也意味着必须区分历史记录,以便用于进行预测的输入数据具有预期的形式。 + +``` +# calculate difference +history = difference(history, n_diff) +``` + +准备好之后,我们可以使用历史数据创建单个样本作为模型的输入,以进行一步预测。 + +一个样本的形状必须是[1,n_input],其中 _n_input_ 是要使用的滞后观察数的选定数量。 + +``` +# shape input for model +x_input = array(history[-n_input:]).reshape((1, n_input)) +``` + +最后,可以进行预测。 + +``` +# make forecast +yhat = model.predict(x_input, verbose=0) +``` + +下面列出了 _model_predict()_ 函数的完整实现。 + +接下来,我们必须定义要为每个超参数尝试的值范围。 + +我们可以定义 _model_configs()_ 函数,该函数创建要尝试的不同参数组合的列表。 + +我们将定义一小部分配置作为示例,包括 12 个月的差异,我们预计这将是必需的。建议您尝试使用独立模型,查看学习曲线诊断图,并使用有关域的信息来设置超参数值到网格搜索的范围。 + +我们还鼓励您重复网格搜索以缩小显示更好表现的值范围。 + +下面列出了 _model_configs()_ 函数的实现。 + +``` +# create a list of configs to try +def model_configs(): + # define scope of configs + n_input = [12] + n_nodes = [50, 100] + n_epochs = [100] + n_batch = [1, 150] + n_diff = [0, 12] + # create configs + configs = list() + for i in n_input: + for j in n_nodes: + for k in n_epochs: + for l in n_batch: + for m in n_diff: + cfg = [i, j, k, l, m] + configs.append(cfg) + print('Total configs: %d' % len(configs)) + return configs +``` + +我们现在拥有网格搜索 MLP 模型所需的所有部分,用于单变量时间序列预测问题。 + +下面列出了完整的示例。 + +``` +# grid search mlps for airline passengers +from math import sqrt +from numpy import array +from numpy import mean +from pandas import DataFrame +from pandas import concat +from pandas import read_csv +from sklearn.metrics import mean_squared_error +from keras.models import Sequential +from keras.layers import Dense + +# split a univariate dataset into train/test sets +def train_test_split(data, n_test): + return data[:-n_test], data[-n_test:] + +# transform list into supervised learning format +def series_to_supervised(data, n_in=1, n_out=1): + df = DataFrame(data) + cols = list() + # input sequence (t-n, ... t-1) + for i in range(n_in, 0, -1): + cols.append(df.shift(i)) + # forecast sequence (t, t+1, ... t+n) + for i in range(0, n_out): + cols.append(df.shift(-i)) + # put it all together + agg = concat(cols, axis=1) + # drop rows with NaN values + agg.dropna(inplace=True) + return agg.values + +# root mean squared error or rmse +def measure_rmse(actual, predicted): + return sqrt(mean_squared_error(actual, predicted)) + +# difference dataset +def difference(data, order): + return [data[i] - data[i - order] for i in range(order, len(data))] + +# fit a model +def model_fit(train, config): + # unpack config + n_input, n_nodes, n_epochs, n_batch, n_diff = config + # prepare data + if n_diff > 0: + train = difference(train, n_diff) + # transform series into supervised format + data = series_to_supervised(train, n_in=n_input) + # separate inputs and outputs + train_x, train_y = data[:, :-1], data[:, -1] + # define model + model = Sequential() + model.add(Dense(n_nodes, activation='relu', input_dim=n_input)) + model.add(Dense(1)) + model.compile(loss='mse', optimizer='adam') + # fit model + model.fit(train_x, train_y, epochs=n_epochs, batch_size=n_batch, verbose=0) + return model + +# forecast with the fit model +def model_predict(model, history, config): + # unpack config + n_input, _, _, _, n_diff = config + # prepare data + correction = 0.0 + if n_diff > 0: + correction = history[-n_diff] + history = difference(history, n_diff) + # shape input for model + x_input = array(history[-n_input:]).reshape((1, n_input)) + # make forecast + yhat = model.predict(x_input, verbose=0) + # correct forecast if it was differenced + return correction + yhat[0] + +# walk-forward validation for univariate data +def walk_forward_validation(data, n_test, cfg): + predictions = list() + # split dataset + train, test = train_test_split(data, n_test) + # fit model + model = model_fit(train, cfg) + # seed history with training dataset + history = [x for x in train] + # step over each time-step in the test set + for i in range(len(test)): + # fit model and make forecast for history + yhat = model_predict(model, history, cfg) + # store forecast in list of predictions + predictions.append(yhat) + # add actual observation to history for the next loop + history.append(test[i]) + # estimate prediction error + error = measure_rmse(test, predictions) + print(' > %.3f' % error) + return error + +# score a model, return None on failure +def repeat_evaluate(data, config, n_test, n_repeats=10): + # convert config to a key + key = str(config) + # fit and evaluate the model n times + scores = [walk_forward_validation(data, n_test, config) for _ in range(n_repeats)] + # summarize score + result = mean(scores) + print('> Model[%s] %.3f' % (key, result)) + return (key, result) + +# grid search configs +def grid_search(data, cfg_list, n_test): + # evaluate configs + scores = scores = [repeat_evaluate(data, cfg, n_test) for cfg in cfg_list] + # sort configs by error, asc + scores.sort(key=lambda tup: tup[1]) + return scores + +# create a list of configs to try +def model_configs(): + # define scope of configs + n_input = [12] + n_nodes = [50, 100] + n_epochs = [100] + n_batch = [1, 150] + n_diff = [0, 12] + # create configs + configs = list() + for i in n_input: + for j in n_nodes: + for k in n_epochs: + for l in n_batch: + for m in n_diff: + cfg = [i, j, k, l, m] + configs.append(cfg) + print('Total configs: %d' % len(configs)) + return configs + +# define dataset +series = read_csv('monthly-airline-passengers.csv', header=0, index_col=0) +data = series.values +# data split +n_test = 12 +# model configs +cfg_list = model_configs() +# grid search +scores = grid_search(data, cfg_list, n_test) +print('done') +# list top 3 configs +for cfg, error in scores[:3]: + print(cfg, error) +``` + +运行该示例,我们可以看到框架总共要评估八种配置。 + +每个配置将被评估 10 次;这意味着将使用前向验证创建和评估 10 个模型,以在报告这 10 个分数的平均值并用于对配置进行评分之前计算 RMSE 分数。 + +然后对得分进行排序,最后报告具有最低 RMSE 的前 3 个配置。与报告 RMSE 为 50.70 的幼稚模型相比,发现了一种熟练的模型配置。 + +我们可以看到 18.98 的最佳 RMSE 是通过[12,100,100,1,12]的配置实现的,我们知道可以解释为: + +* **n_input** :12 +* **n_nodes** :100 +* **n_epochs** :100 +* **n_batch** :1 +* **n_diff** :12 + +下面列出了网格搜索的截断示例输出。 + +鉴于算法的随机性,您的具体分数可能会有所不同。 + +``` +Total configs: 8 + > 20.707 + > 29.111 + > 17.499 + > 18.918 + > 28.817 +... + > 21.015 + > 20.208 + > 18.503 +> Model[[12, 100, 100, 150, 12]] 19.674 +done +[12, 100, 100, 1, 12] 18.982720013625606 +[12, 50, 100, 150, 12] 19.33004059448595 +[12, 100, 100, 1, 0] 19.5389405532858 +``` + +## 网格搜索卷积神经网络 + +我们现在可以将框架调整为网格搜索 CNN 模型。 + +可以像使用 MLP 模型一样搜索大量相同的超参数集,除了隐藏层中的节点数可以由卷积层中的滤波器映射数和内核大小替换。 + +在 CNN 模型中选择的网格搜索超参数集如下: + +* **n_input** :用作模型输入的先前输入数(例如 12 个月)。 +* **n_filters** :卷积层中的滤波器映射的数量(例如 32)。 +* **n_kernel** :卷积层中的内核大小(例如 3)。 +* **n_epochs** :训练时期的数量(例如 1000)。 +* **n_batch** :每个小批量中包含的样本数(例如 32)。 +* **n_diff** :差分顺序(例如 0 或 12)。 + +您可能希望研究的一些额外的超参数是在池化层之前使用两个卷积层,重复卷积和池化层模式,使用丢失等等。 + +我们将定义一个非常简单的 CNN 模型,其中包含一个卷积层和一个最大池池。 + +``` +# define model +model = Sequential() +model.add(Conv1D(filters=n_filters, kernel_size=n_kernel, activation='relu', input_shape=(n_input, n_features))) +model.add(MaxPooling1D(pool_size=2)) +model.add(Flatten()) +model.add(Dense(1)) +model.compile(loss='mse', optimizer='adam') +``` + +必须以与 MLP 相同的方式准备数据。 + +与期望输入数据具有[样本,特征]形状的 MLP 不同,1D CNN 模型期望数据具有[_ 样本,时间步长,特征 _]的形状,其中特征映射到通道上并且在此案例 1 是我们每个月测量的一个变量。 + +``` +# reshape input data into [samples, timesteps, features] +n_features = 1 +train_x = train_x.reshape((train_x.shape[0], train_x.shape[1], n_features)) +``` + +下面列出了 _model_fit()_ 函数的完整实现。 + +``` +# fit a model +def model_fit(train, config): + # unpack config + n_input, n_filters, n_kernel, n_epochs, n_batch, n_diff = config + # prepare data + if n_diff > 0: + train = difference(train, n_diff) + # transform series into supervised format + data = series_to_supervised(train, n_in=n_input) + # separate inputs and outputs + train_x, train_y = data[:, :-1], data[:, -1] + # reshape input data into [samples, timesteps, features] + n_features = 1 + train_x = train_x.reshape((train_x.shape[0], train_x.shape[1], n_features)) + # define model + model = Sequential() + model.add(Conv1D(filters=n_filters, kernel_size=n_kernel, activation='relu', input_shape=(n_input, n_features))) + model.add(MaxPooling1D(pool_size=2)) + model.add(Flatten()) + model.add(Dense(1)) + model.compile(loss='mse', optimizer='adam') + # fit + model.fit(train_x, train_y, epochs=n_epochs, batch_size=n_batch, verbose=0) + return model +``` + +使用拟合 CNN 模型进行预测非常类似于使用拟合 MLP 进行预测。 + +同样,唯一的区别是一个样本值的输入数据必须具有三维形状。 + +``` +x_input = array(history[-n_input:]).reshape((1, n_input, 1)) +``` + +下面列出了 _model_predict()_ 函数的完整实现。 + +``` +# forecast with the fit model +def model_predict(model, history, config): + # unpack config + n_input, _, _, _, _, n_diff = config + # prepare data + correction = 0.0 + if n_diff > 0: + correction = history[-n_diff] + history = difference(history, n_diff) + x_input = array(history[-n_input:]).reshape((1, n_input, 1)) + # forecast + yhat = model.predict(x_input, verbose=0) + return correction + yhat[0] +``` + +最后,我们可以定义要评估的模型的配置列表。和以前一样,我们可以通过定义超参数值列表来尝试将它们组合成一个列表。我们将尝试少量配置以确保示例在合理的时间内执行。 + +完整的 _model_configs()_ 功能如下所示。 + +``` +# create a list of configs to try +def model_configs(): + # define scope of configs + n_input = [12] + n_filters = [64] + n_kernels = [3, 5] + n_epochs = [100] + n_batch = [1, 150] + n_diff = [0, 12] + # create configs + configs = list() + for a in n_input: + for b in n_filters: + for c in n_kernels: + for d in n_epochs: + for e in n_batch: + for f in n_diff: + cfg = [a,b,c,d,e,f] + configs.append(cfg) + print('Total configs: %d' % len(configs)) + return configs +``` + +我们现在拥有网格搜索卷积神经网络的超参数所需的所有元素,用于单变量时间序列预测。 + +下面列出了完整的示例。 + +``` +# grid search cnn for airline passengers +from math import sqrt +from numpy import array +from numpy import mean +from pandas import DataFrame +from pandas import concat +from pandas import read_csv +from sklearn.metrics import mean_squared_error +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import Flatten +from keras.layers.convolutional import Conv1D +from keras.layers.convolutional import MaxPooling1D + +# split a univariate dataset into train/test sets +def train_test_split(data, n_test): + return data[:-n_test], data[-n_test:] + +# transform list into supervised learning format +def series_to_supervised(data, n_in=1, n_out=1): + df = DataFrame(data) + cols = list() + # input sequence (t-n, ... t-1) + for i in range(n_in, 0, -1): + cols.append(df.shift(i)) + # forecast sequence (t, t+1, ... t+n) + for i in range(0, n_out): + cols.append(df.shift(-i)) + # put it all together + agg = concat(cols, axis=1) + # drop rows with NaN values + agg.dropna(inplace=True) + return agg.values + +# root mean squared error or rmse +def measure_rmse(actual, predicted): + return sqrt(mean_squared_error(actual, predicted)) + +# difference dataset +def difference(data, order): + return [data[i] - data[i - order] for i in range(order, len(data))] + +# fit a model +def model_fit(train, config): + # unpack config + n_input, n_filters, n_kernel, n_epochs, n_batch, n_diff = config + # prepare data + if n_diff > 0: + train = difference(train, n_diff) + # transform series into supervised format + data = series_to_supervised(train, n_in=n_input) + # separate inputs and outputs + train_x, train_y = data[:, :-1], data[:, -1] + # reshape input data into [samples, timesteps, features] + n_features = 1 + train_x = train_x.reshape((train_x.shape[0], train_x.shape[1], n_features)) + # define model + model = Sequential() + model.add(Conv1D(filters=n_filters, kernel_size=n_kernel, activation='relu', input_shape=(n_input, n_features))) + model.add(MaxPooling1D(pool_size=2)) + model.add(Flatten()) + model.add(Dense(1)) + model.compile(loss='mse', optimizer='adam') + # fit + model.fit(train_x, train_y, epochs=n_epochs, batch_size=n_batch, verbose=0) + return model + +# forecast with the fit model +def model_predict(model, history, config): + # unpack config + n_input, _, _, _, _, n_diff = config + # prepare data + correction = 0.0 + if n_diff > 0: + correction = history[-n_diff] + history = difference(history, n_diff) + x_input = array(history[-n_input:]).reshape((1, n_input, 1)) + # forecast + yhat = model.predict(x_input, verbose=0) + return correction + yhat[0] + +# walk-forward validation for univariate data +def walk_forward_validation(data, n_test, cfg): + predictions = list() + # split dataset + train, test = train_test_split(data, n_test) + # fit model + model = model_fit(train, cfg) + # seed history with training dataset + history = [x for x in train] + # step over each time-step in the test set + for i in range(len(test)): + # fit model and make forecast for history + yhat = model_predict(model, history, cfg) + # store forecast in list of predictions + predictions.append(yhat) + # add actual observation to history for the next loop + history.append(test[i]) + # estimate prediction error + error = measure_rmse(test, predictions) + print(' > %.3f' % error) + return error + +# score a model, return None on failure +def repeat_evaluate(data, config, n_test, n_repeats=10): + # convert config to a key + key = str(config) + # fit and evaluate the model n times + scores = [walk_forward_validation(data, n_test, config) for _ in range(n_repeats)] + # summarize score + result = mean(scores) + print('> Model[%s] %.3f' % (key, result)) + return (key, result) + +# grid search configs +def grid_search(data, cfg_list, n_test): + # evaluate configs + scores = scores = [repeat_evaluate(data, cfg, n_test) for cfg in cfg_list] + # sort configs by error, asc + scores.sort(key=lambda tup: tup[1]) + return scores + +# create a list of configs to try +def model_configs(): + # define scope of configs + n_input = [12] + n_filters = [64] + n_kernels = [3, 5] + n_epochs = [100] + n_batch = [1, 150] + n_diff = [0, 12] + # create configs + configs = list() + for a in n_input: + for b in n_filters: + for c in n_kernels: + for d in n_epochs: + for e in n_batch: + for f in n_diff: + cfg = [a,b,c,d,e,f] + configs.append(cfg) + print('Total configs: %d' % len(configs)) + return configs + +# define dataset +series = read_csv('monthly-airline-passengers.csv', header=0, index_col=0) +data = series.values +# data split +n_test = 12 +# model configs +cfg_list = model_configs() +# grid search +scores = grid_search(data, cfg_list, n_test) +print('done') +# list top 10 configs +for cfg, error in scores[:3]: + print(cfg, error) +``` + +运行该示例,我们可以看到只评估了八种不同的配置。 + +我们可以看到[12,64,5,100,1,12]的配置实现了 18.89 的 RMSE,与实现 50.70 的天真预测模型相比,这是巧妙的。 + +我们可以将此配置解压缩为: + +* **n_input** :12 +* **n_filters** :64 +* **n_kernel** :5 +* **n_epochs** :100 +* **n_batch** :1 +* **n_diff** :12 + +下面列出了网格搜索的截断示例输出。 + +鉴于算法的随机性,您的具体分数可能会有所不同。 + +``` +Total configs: 8 + > 23.372 + > 28.317 + > 31.070 +... + > 20.923 + > 18.700 + > 18.210 +> Model[[12, 64, 5, 100, 150, 12]] 19.152 +done +[12, 64, 5, 100, 1, 12] 18.89593462072732 +[12, 64, 5, 100, 150, 12] 19.152486150334234 +[12, 64, 3, 100, 150, 12] 19.44680151564605 +``` + +## 网格搜索长短期记忆网络 + +我们现在可以采用网格搜索 LSTM 模型的超参数。 + +LSTM 模型的超参数将与 MLP 相同;他们是: + +* **n_input** :用作模型输入的先前输入数(例如 12 个月)。 +* **n_nodes** :隐藏层中使用的节点数(例如 50)。 +* **n_epochs** :训练时期的数量(例如 1000)。 +* **n_batch** :每个小批量中包含的样本数(例如 32)。 +* **n_diff** :差分顺序(例如 0 或 12)。 + +我们将定义一个简单的 LSTM 模型,该模型具有单个隐藏的 LSTM 层和指定该层中单元数的节点数。 + +``` +# define model +model = Sequential() +model.add(LSTM(n_nodes, activation='relu', input_shape=(n_input, n_features))) +model.add(Dense(n_nodes, activation='relu')) +model.add(Dense(1)) +model.compile(loss='mse', optimizer='adam') +# fit model +model.fit(train_x, train_y, epochs=n_epochs, batch_size=n_batch, verbose=0) +``` + +探索调整其他配置(例如使用双向输入层,堆叠 LSTM 层,甚至是具有 CNN 或 ConvLSTM 输入模型的混合模型)可能会很有趣。 + +与 CNN 模型一样,LSTM 模型期望输入数据具有样本,时间步长和特征的三维形状。 + +``` +# reshape input data into [samples, timesteps, features] +n_features = 1 +train_x = train_x.reshape((train_x.shape[0], train_x.shape[1], n_features)) +``` + +下面列出了 _model_fit()_ 函数的完整实现。 + +``` +# fit a model +def model_fit(train, config): + # unpack config + n_input, n_nodes, n_epochs, n_batch, n_diff = config + # prepare data + if n_diff > 0: + train = difference(train, n_diff) + # transform series into supervised format + data = series_to_supervised(train, n_in=n_input) + # separate inputs and outputs + train_x, train_y = data[:, :-1], data[:, -1] + # reshape input data into [samples, timesteps, features] + n_features = 1 + train_x = train_x.reshape((train_x.shape[0], train_x.shape[1], n_features)) + # define model + model = Sequential() + model.add(LSTM(n_nodes, activation='relu', input_shape=(n_input, n_features))) + model.add(Dense(n_nodes, activation='relu')) + model.add(Dense(1)) + model.compile(loss='mse', optimizer='adam') + # fit model + model.fit(train_x, train_y, epochs=n_epochs, batch_size=n_batch, verbose=0) + return model +``` + +与 CNN 一样,用于进行预测的单个输入样本也必须重新形成预期的三维结构。 + +``` +# reshape sample into [samples, timesteps, features] +x_input = array(history[-n_input:]).reshape((1, n_input, 1)) +``` + +完整的 _model_predict()_ 功能如下所示。 + +``` +# forecast with the fit model +def model_predict(model, history, config): + # unpack config + n_input, _, _, _, n_diff = config + # prepare data + correction = 0.0 + if n_diff > 0: + correction = history[-n_diff] + history = difference(history, n_diff) + # reshape sample into [samples, timesteps, features] + x_input = array(history[-n_input:]).reshape((1, n_input, 1)) + # forecast + yhat = model.predict(x_input, verbose=0) + return correction + yhat[0] +``` + +我们现在可以定义用于创建要评估的模型配置列表的函数。 + +训练的 LSTM 模型比 MLP 和 CNN 模型慢得多;因此,您可能希望评估每次运行的配置更少。 + +我们将定义一组非常简单的两种配置来探索:随机和批量梯度下降。 + +``` +# create a list of configs to try +def model_configs(): + # define scope of configs + n_input = [12] + n_nodes = [100] + n_epochs = [50] + n_batch = [1, 150] + n_diff = [12] + # create configs + configs = list() + for i in n_input: + for j in n_nodes: + for k in n_epochs: + for l in n_batch: + for m in n_diff: + cfg = [i, j, k, l, m] + configs.append(cfg) + print('Total configs: %d' % len(configs)) + return configs +``` + +我们现在拥有了针对 LSTM 模型的网格搜索超参数所需的一切,用于单变量时间序列预测。 + +下面列出了完整的示例。 + +``` +# grid search lstm for airline passengers +from math import sqrt +from numpy import array +from numpy import mean +from pandas import DataFrame +from pandas import concat +from pandas import read_csv +from sklearn.metrics import mean_squared_error +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import LSTM + +# split a univariate dataset into train/test sets +def train_test_split(data, n_test): + return data[:-n_test], data[-n_test:] + +# transform list into supervised learning format +def series_to_supervised(data, n_in=1, n_out=1): + df = DataFrame(data) + cols = list() + # input sequence (t-n, ... t-1) + for i in range(n_in, 0, -1): + cols.append(df.shift(i)) + # forecast sequence (t, t+1, ... t+n) + for i in range(0, n_out): + cols.append(df.shift(-i)) + # put it all together + agg = concat(cols, axis=1) + # drop rows with NaN values + agg.dropna(inplace=True) + return agg.values + +# root mean squared error or rmse +def measure_rmse(actual, predicted): + return sqrt(mean_squared_error(actual, predicted)) + +# difference dataset +def difference(data, order): + return [data[i] - data[i - order] for i in range(order, len(data))] + +# fit a model +def model_fit(train, config): + # unpack config + n_input, n_nodes, n_epochs, n_batch, n_diff = config + # prepare data + if n_diff > 0: + train = difference(train, n_diff) + # transform series into supervised format + data = series_to_supervised(train, n_in=n_input) + # separate inputs and outputs + train_x, train_y = data[:, :-1], data[:, -1] + # reshape input data into [samples, timesteps, features] + n_features = 1 + train_x = train_x.reshape((train_x.shape[0], train_x.shape[1], n_features)) + # define model + model = Sequential() + model.add(LSTM(n_nodes, activation='relu', input_shape=(n_input, n_features))) + model.add(Dense(n_nodes, activation='relu')) + model.add(Dense(1)) + model.compile(loss='mse', optimizer='adam') + # fit model + model.fit(train_x, train_y, epochs=n_epochs, batch_size=n_batch, verbose=0) + return model + +# forecast with the fit model +def model_predict(model, history, config): + # unpack config + n_input, _, _, _, n_diff = config + # prepare data + correction = 0.0 + if n_diff > 0: + correction = history[-n_diff] + history = difference(history, n_diff) + # reshape sample into [samples, timesteps, features] + x_input = array(history[-n_input:]).reshape((1, n_input, 1)) + # forecast + yhat = model.predict(x_input, verbose=0) + return correction + yhat[0] + +# walk-forward validation for univariate data +def walk_forward_validation(data, n_test, cfg): + predictions = list() + # split dataset + train, test = train_test_split(data, n_test) + # fit model + model = model_fit(train, cfg) + # seed history with training dataset + history = [x for x in train] + # step over each time-step in the test set + for i in range(len(test)): + # fit model and make forecast for history + yhat = model_predict(model, history, cfg) + # store forecast in list of predictions + predictions.append(yhat) + # add actual observation to history for the next loop + history.append(test[i]) + # estimate prediction error + error = measure_rmse(test, predictions) + print(' > %.3f' % error) + return error + +# score a model, return None on failure +def repeat_evaluate(data, config, n_test, n_repeats=10): + # convert config to a key + key = str(config) + # fit and evaluate the model n times + scores = [walk_forward_validation(data, n_test, config) for _ in range(n_repeats)] + # summarize score + result = mean(scores) + print('> Model[%s] %.3f' % (key, result)) + return (key, result) + +# grid search configs +def grid_search(data, cfg_list, n_test): + # evaluate configs + scores = scores = [repeat_evaluate(data, cfg, n_test) for cfg in cfg_list] + # sort configs by error, asc + scores.sort(key=lambda tup: tup[1]) + return scores + +# create a list of configs to try +def model_configs(): + # define scope of configs + n_input = [12] + n_nodes = [100] + n_epochs = [50] + n_batch = [1, 150] + n_diff = [12] + # create configs + configs = list() + for i in n_input: + for j in n_nodes: + for k in n_epochs: + for l in n_batch: + for m in n_diff: + cfg = [i, j, k, l, m] + configs.append(cfg) + print('Total configs: %d' % len(configs)) + return configs + +# define dataset +series = read_csv('monthly-airline-passengers.csv', header=0, index_col=0) +data = series.values +# data split +n_test = 12 +# model configs +cfg_list = model_configs() +# grid search +scores = grid_search(data, cfg_list, n_test) +print('done') +# list top 10 configs +for cfg, error in scores[:3]: + print(cfg, error) +``` + +运行该示例,我们可以看到只评估了两个不同的配置。 + +我们可以看到[12,100,50,1,12]的配置实现了 21.24 的 RMSE,与实现 50.70 的天真预测模型相比,这是巧妙的。 + +该模型需要更多的调整,并且可以使用混合配置做得更好,例如将 CNN 模型作为输入。 + +我们可以将此配置解压缩为: + +* **n_input** :12 +* **n_nodes** :100 +* **n_epochs** :50 +* **n_batch** :1 +* **n_diff** :12 + +下面列出了网格搜索的截断示例输出。 + +鉴于算法的随机性,您的具体分数可能会有所不同。 + +``` +Total configs: 2 + > 20.488 + > 17.718 + > 21.213 +... + > 22.300 + > 20.311 + > 21.322 +> Model[[12, 100, 50, 150, 12]] 21.260 +done +[12, 100, 50, 1, 12] 21.243775750634093 +[12, 100, 50, 150, 12] 21.259553398553606 +``` + +## 扩展 + +本节列出了一些扩展您可能希望探索的教程的想法。 + +* **更多配置**。探索其中一个模型的大型配置套件,看看您是否能找到能够提高表现的配置。 +* **数据缩放**。更新网格搜索框架以在拟合模型和反转变换以进行预测之前还支持数据的缩放(标准化和/或标准化)。 +* **网络架构**。探索网格搜索给定模型的更大架构更改,例如添加更多隐藏层。 +* **新数据集**。在新的单变量时间序列数据集中探索给定模型的网格搜索。 +* **多变量**。更新网格搜索框架以支持小的多变量时间序列数据集,例如具有多个输入变量的数据集。 + +如果你探索任何这些扩展,我很想知道。 + +## 进一步阅读 + +如果您希望深入了解,本节将提供有关该主题的更多资源。 + +* [如何使用 Keras 网格搜索 Python 中的深度学习模型的超参数](https://machinelearningmastery.com/grid-search-hyperparameters-deep-learning-models-python-keras/) +* [Keras 核心层 API](https://keras.io/layers/core/) +* [Keras 卷积层 API](https://keras.io/layers/convolutional/) +* [Keras Recurrent Layers API](https://keras.io/layers/recurrent/) + +## 摘要 + +在本教程中,您了解了如何为深度学习模型开发网格搜索超参数框架。 + +具体来说,你学到了: + +* 如何开发用于调整模型超参数的通用网格搜索框架。 +* 如何在航空公司乘客单变量时间序列预测问题上对多层感知器模型进行网格搜索超参数。 +* 如何使框架适应卷积和长期短期记忆神经网络的网格搜索超参数。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/how-to-grid-search-naive-methods-for-univariate-time-series-forecasting.md b/docs/dl-ts/how-to-grid-search-naive-methods-for-univariate-time-series-forecasting.md new file mode 100644 index 0000000000000000000000000000000000000000..f9834e4796ca22881eb8556a4108eeb905630352 --- /dev/null +++ b/docs/dl-ts/how-to-grid-search-naive-methods-for-univariate-time-series-forecasting.md @@ -0,0 +1,1478 @@ +# 如何对单变量时间序列预测的网格搜索朴素方法 + +> 原文: [https://machinelearningmastery.com/how-to-grid-search-naive-methods-for-univariate-time-series-forecasting/](https://machinelearningmastery.com/how-to-grid-search-naive-methods-for-univariate-time-series-forecasting/) + +简单的预测方法包括天真地使用最后一个观测值作为预测或先前观测值的平均值。 + +在使用更复杂的方法之前评估简单预测方法对单变量时间序列预测问题的表现非常重要,因为它们的表现提供了一个下限和比较点,可用于确定模型是否具有给定技能的技能问题。 + +尽管简单,诸如幼稚和平均预测策略之类的方法可以根据选择哪个先前观察持续存在或者先前观察到多少平均值来调整到特定问题。通常,调整这些简单策略的超参数可以为模型表现提供更强大和可防御的下限,以及可以为更复杂方法的选择和配置提供信息的令人惊讶的结果。 + +在本教程中,您将了解如何从头开始构建一个框架,用于网格搜索简单朴素和平均策略,用于使用单变量数据进行时间序列预测。 + +完成本教程后,您将了解: + +* 如何使用前向验证从头开始开发网格搜索简单模型的框架。 +* 如何为出生日常时间序列数据网格搜索简单模型超参数。 +* 如何为洗发水销售,汽车销售和温度的月度时间序列数据网格搜索简单模型超参数。 + +让我们开始吧。 + +![How to Grid Search Naive Methods for Univariate Time Series Forecasting](img/18f93f0283af63bcca8a5887473b3e99.jpg) + +如何网格搜索单变量时间序列预测的朴素方法 +照片由 [Rob 和 Stephanie Levy](https://www.flickr.com/photos/robandstephanielevy/526862866/) ,保留一些权利。 + +## 教程概述 + +本教程分为六个部分;他们是: + +1. 简单的预测策略 +2. 开发网格搜索框架 +3. 案例研究 1:没有趋势或季节性 +4. 案例研究 2:趋势 +5. 案例研究 3:季节性 +6. 案例研究 4:趋势和季节性 + +## 简单的预测策略 + +在测试更复杂的模型之前测试简单的预测策略是非常重要和有用的。 + +简单的预测策略是那些对预测问题的性质很少或根本没有假设的策略,并且可以快速实现和计算。 + +结果可用作表现的基线,并用作比较点。如果模型的表现优于简单预测策略的表现,则可以说它具有技巧性。 + +简单预测策略有两个主题;他们是: + +* **天真**,或直接使用观察值。 +* **平均**,或使用先前观察计算的统计量。 + +让我们仔细看看这两种策略。 + +### 天真的预测策略 + +天真的预测涉及直接使用先前的观察作为预测而没有任何改变。 + +它通常被称为持久性预测,因为之前的观察是持久的。 + +对于季节性数据,可以稍微调整这种简单的方法。在这种情况下,可以保持前一周期中的同时观察。 + +这可以进一步推广到将历史数据中的每个可能偏移量测试,以用于保持预测值。 + +例如,给定系列: + +``` +[1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +我们可以将最后一次观察(相对指数-1)保持为值 9,或者将第二次最后一次观察(相对指数-2)保持为 8,依此类推。 + +### 平均预测策略 + +天真预测之上的一步是平均先前值的策略。 + +所有先前的观察结果均使用均值或中位数进行收集和平均,而不对数据进行其他处理。 + +在某些情况下,我们可能希望将平均计算中使用的历史缩短到最后几个观察结果。 + +我们可以将这一点推广到测试每个可能的 n 个先验观察的集合以包括在平均计算中的情况。 + +例如,给定系列: + +``` +[1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +我们可以平均最后一个观察(9),最后两个观察(8,9),依此类推。 + +在季节性数据的情况下,我们可能希望将周期中的最后 n 次先前观测值与预测时间进行平均。 + +例如,给定具有 3 步循环的系列: + +``` +[1, 2, 3, 1, 2, 3, 1, 2, 3] +``` + +我们可以使用窗口大小为 3 并平均最后一个观察值(-3 或 1),最后两个观察值(-3 或 1 和 - (3 * 2)或 1),依此类推。 + +## 开发网格搜索框架 + +在本节中,我们将开发一个网格搜索框架,用于搜索前一节中描述的两个简单预测策略,即天真和平均策略。 + +我们可以从实施一个天真的预测策略开始。 + +对于给定的历史观测数据集,我们可以在该历史中保留任何值,即从索引-1 处的先前观察到历史上的第一次观察 - (len(data))。 + +下面的 _naive_forecast()_ 函数实现了从 1 到数据集长度的给定偏移的朴素预测策略。 + +``` +# one-step naive forecast +def naive_forecast(history, n): + return history[-n] +``` + +我们可以在一个小的设计数据集上测试这个功能。 + +``` +# one-step naive forecast +def naive_forecast(history, n): + return history[-n] + +# define dataset +data = [10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0] +print(data) +# test naive forecast +for i in range(1, len(data)+1): + print(naive_forecast(data, i)) +``` + +首先运行示例打印设计的数据集,然后打印历史数据集中每个偏移的朴素预测。 + +``` +[10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0] +100.0 +90.0 +80.0 +70.0 +60.0 +50.0 +40.0 +30.0 +20.0 +10.0 +``` + +我们现在可以考虑为平均预测策略开发一个函数。 + +平均最后 n 次观测是直截了当的;例如: + +``` +from numpy import mean +result = mean(history[-n:]) +``` + +我们可能还想测试观察分布是非高斯分布的情况下的中位数。 + +``` +from numpy import median +result = median(history[-n:]) +``` + +下面的 _average_forecast()_ 函数实现了这一过程,它将历史数据和一个配置数组或元组指定为平均值作为整数的先前值的数量,以及一个描述计算平均值的方法的字符串(' _ 表示 _'或'_ 中值 _')。 + +``` +# one-step average forecast +def average_forecast(history, config): + n, avg_type = config + # mean of last n values + if avg_type is 'mean': + return mean(history[-n:]) + # median of last n values + return median(history[-n:]) +``` + +下面列出了一个小型人为数据集的完整示例。 + +``` +from numpy import mean +from numpy import median + +# one-step average forecast +def average_forecast(history, config): + n, avg_type = config + # mean of last n values + if avg_type is 'mean': + return mean(history[-n:]) + # median of last n values + return median(history[-n:]) + +# define dataset +data = [10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0] +print(data) +# test naive forecast +for i in range(1, len(data)+1): + print(average_forecast(data, (i, 'mean'))) +``` + +运行该示例将系列中的下一个值预测为先前观察的连续子集的平均值,从-1 到-10,包括在内。 + +``` +[10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0] +100.0 +95.0 +90.0 +85.0 +80.0 +75.0 +70.0 +65.0 +60.0 +55.0 +``` + +我们可以更新函数以支持季节性数据的平均值,并考虑季节性偏移。 + +可以向函数添加偏移量参数,当未设置为 1 时,将在收集要包括在平均值中的值之前确定先前观察的数量向后计数。 + +例如,如果 n = 1 且 offset = 3,则从 n * offset 或 1 * 3 = -3 处的单个值计算平均值。如果 n = 2 且 offset = 3,则从 1 * 3 或-3 和 2 * 3 或-6 的值计算平均值。 + +当季节性配置(n *偏移)超出历史观察结束时,我们还可以添加一些保护来引发异常。 + +更新的功能如下所示。 + +``` +# one-step average forecast +def average_forecast(history, config): + n, offset, avg_type = config + values = list() + if offset == 1: + values = history[-n:] + else: + # skip bad configs + if n*offset > len(history): + raise Exception('Config beyond end of data: %d %d' % (n,offset)) + # try and collect n values using offset + for i in range(1, n+1): + ix = i * offset + values.append(history[-ix]) + # mean of last n values + if avg_type is 'mean': + return mean(values) + # median of last n values + return median(values) +``` + +我们可以在季节性循环的小型人为数据集上测试这个函数。 + +下面列出了完整的示例。 + +``` +from numpy import mean +from numpy import median + +# one-step average forecast +def average_forecast(history, config): + n, offset, avg_type = config + values = list() + if offset == 1: + values = history[-n:] + else: + # skip bad configs + if n*offset > len(history): + raise Exception('Config beyond end of data: %d %d' % (n,offset)) + # try and collect n values using offset + for i in range(1, n+1): + ix = i * offset + values.append(history[-ix]) + # mean of last n values + if avg_type is 'mean': + return mean(values) + # median of last n values + return median(values) + +# define dataset +data = [10.0, 20.0, 30.0, 10.0, 20.0, 30.0, 10.0, 20.0, 30.0] +print(data) +# test naive forecast +for i in [1, 2, 3]: + print(average_forecast(data, (i, 3, 'mean'))) +``` + +运行该示例计算[10],[10,10]和[10,10,10]的平均值。 + +``` +[10.0, 20.0, 30.0, 10.0, 20.0, 30.0, 10.0, 20.0, 30.0] +10.0 +10.0 +10.0 +``` + +可以将天真预测策略和平均预测策略结合在一起,形成相同的功能。 + +这些方法之间存在一些重叠,特别是 _n-_ 偏移到历史记录中,用于持久化值或确定要平均的值的数量。 + +让一个函数支持这两种策略是有帮助的,这样我们就可以同时测试这两种策略的一套配置,作为简单模型的更广泛网格搜索的一部分。 + +下面的 _simple_forecast()_ 函数将两种策略组合成一个函数。 + +``` +# one-step simple forecast +def simple_forecast(history, config): + n, offset, avg_type = config + # persist value, ignore other config + if avg_type == 'persist': + return history[-n] + # collect values to average + values = list() + if offset == 1: + values = history[-n:] + else: + # skip bad configs + if n*offset > len(history): + raise Exception('Config beyond end of data: %d %d' % (n,offset)) + # try and collect n values using offset + for i in range(1, n+1): + ix = i * offset + values.append(history[-ix]) + # check if we can average + if len(values) < 2: + raise Exception('Cannot calculate average') + # mean of last n values + if avg_type == 'mean': + return mean(values) + # median of last n values + return median(values) +``` + +接下来,我们需要建立一些函数,通过前向验证重复拟合和评估模型,包括将数据集拆分为训练集和测试集以及评估一步预测。 + +我们可以使用给定指定大小的分割的切片来分割列表或 NumPy 数据数组,例如,从测试集中的数据中使用的时间步数。 + +下面的 _train_test_split()_ 函数为提供的数据集和要在测试集中使用的指定数量的时间步骤实现此功能。 + +``` +# split a univariate dataset into train/test sets +def train_test_split(data, n_test): + return data[:-n_test], data[-n_test:] +``` + +在对测试数据集中的每个步骤进行预测之后,需要将它们与测试集进行比较以计算错误分数。 + +时间序列预测有许多流行的错误分数。在这种情况下,我们将使用均方根误差(RMSE),但您可以将其更改为您的首选度量,例如 MAPE,MAE 等 + +下面的 _measure_rmse()_ 函数将根据实际(测试集)和预测值列表计算 RMSE。 + +``` +# root mean squared error or rmse +def measure_rmse(actual, predicted): + return sqrt(mean_squared_error(actual, predicted)) +``` + +我们现在可以实现[前进验证方案](https://machinelearningmastery.com/backtest-machine-learning-models-time-series-forecasting/)。这是评估尊重观测时间顺序的时间序列预测模型的标准方法。 + +首先,使用 _train_test_split(_ _)_ 函数将提供的单变量时间序列数据集分成训练集和测试集。然后枚举测试集中的观察数。对于每一个我们都适合所有历史的模型,并进行一步预测。然后将对时间步骤的真实观察添加到历史中,并重复该过程。调用 _simple_forecast_ _()_ 函数以适合模型并进行预测。最后,通过调用 _measure_rmse()_ 函数,将所有一步预测与实际测试集进行比较,计算错误分数。 + +下面的 _walk_forward_validation()_ 函数实现了这一点,采用单变量时间序列,在测试集中使用的一些时间步骤,以及模型配置数组。 + +``` +# walk-forward validation for univariate data +def walk_forward_validation(data, n_test, cfg): + predictions = list() + # split dataset + train, test = train_test_split(data, n_test) + # seed history with training dataset + history = [x for x in train] + # step over each time-step in the test set + for i in range(len(test)): + # fit model and make forecast for history + yhat = simple_forecast(history, cfg) + # store forecast in list of predictions + predictions.append(yhat) + # add actual observation to history for the next loop + history.append(test[i]) + # estimate prediction error + error = measure_rmse(test, predictions) + return error +``` + +如果您对进行多步预测感兴趣,可以在 _simple_forecast_ _()_ 函数中更改 _ 预测(_ _)_ 的调用并且还改变 _measure_rmse()_ 函数中的误差计算。 + +我们可以使用不同的模型配置列表重复调用 _walk_forward_validation()_。 + +一个可能的问题是,可能不会为模型调用某些模型配置组合,并会抛出异常。 + +我们可以在网格搜索期间捕获异常并忽略警告,方法是将所有调用包含在 _walk_forward_validation(_ _)_ 中,并使用 try-except 和 block 来忽略警告。我们还可以添加调试支持来禁用这些保护,以防我们想要查看实际情况。最后,如果确实发生错误,我们可以返回 _ 无 _ 结果;否则,我们可以打印一些关于评估的每个模型的技能的信息。当评估大量模型时,这很有用。 + +下面的 _score_model()_ 函数实现了这个并返回(键和结果)的元组,其中键是测试模型配置的字符串版本。 + +``` +# score a model, return None on failure +def score_model(data, n_test, cfg, debug=False): + result = None + # convert config to a key + key = str(cfg) + # show all warnings and fail on exception if debugging + if debug: + result = walk_forward_validation(data, n_test, cfg) + else: + # one failure during model validation suggests an unstable config + try: + # never show warnings when grid searching, too noisy + with catch_warnings(): + filterwarnings("ignore") + result = walk_forward_validation(data, n_test, cfg) + except: + error = None + # check for an interesting result + if result is not None: + print(' > Model[%s] %.3f' % (key, result)) + return (key, result) +``` + +接下来,我们需要一个循环来测试不同模型配置的列表。 + +这是驱动网格搜索过程的主要功能,并将为每个模型配置调用 _score_model()_ 函数。 + +通过并行评估模型配置,我们可以大大加快网格搜索过程。一种方法是使用 [Joblib 库](https://pythonhosted.org/joblib/)。 + +我们可以定义一个 Parallel 对象,其中包含要使用的核心数,并将其设置为硬件中检测到的分数。 + +``` +executor = Parallel(n_jobs=cpu_count(), backend='multiprocessing') +``` + +然后我们可以创建一个并行执行的任务列表,这将是我们每个模型配置对 score_model()函数的一次调用。 + +``` +tasks = (delayed(score_model)(data, n_test, cfg) for cfg in cfg_list) +``` + +最后,我们可以使用 _Parallel_ 对象并行执行任务列表。 + +``` +scores = executor(tasks) +``` + +而已。 + +我们还可以提供评估所有模型配置的非并行版本,以防我们想要调试某些内容。 + +``` +scores = [score_model(data, n_test, cfg) for cfg in cfg_list] +``` + +评估配置列表的结果将是元组列表,每个元组的名称总结了特定的模型配置,并且使用该配置评估的模型的错误为 RMSE 或 _ 无 _(如果有)一个错误。 + +我们可以过滤掉所有设置为 _ 无 _ 的分数。 + +``` +scores = [r for r in scores if r[1] != None] +``` + +然后我们可以按照升序排列列表中的所有元组(最好是第一个),然后返回此分数列表以供审阅。 + +给定单变量时间序列数据集,模型配置列表(列表列表)以及在测试集中使用的时间步数,下面的 _grid_search()_ 函数实现此行为。可选的并行参数允许对所有内核的模型进行开启或关闭调整,默认情况下处于打开状态。 + +``` +# grid search configs +def grid_search(data, cfg_list, n_test, parallel=True): + scores = None + if parallel: + # execute configs in parallel + executor = Parallel(n_jobs=cpu_count(), backend='multiprocessing') + tasks = (delayed(score_model)(data, n_test, cfg) for cfg in cfg_list) + scores = executor(tasks) + else: + scores = [score_model(data, n_test, cfg) for cfg in cfg_list] + # remove empty results + scores = [r for r in scores if r[1] != None] + # sort configs by error, asc + scores.sort(key=lambda tup: tup[1]) + return scores +``` + +我们差不多完成了。 + +剩下要做的唯一事情是定义模型配置列表以尝试数据集。 + +我们可以一般地定义它。我们可能想要指定的唯一参数是系列中季节性组件的周期性(偏移量)(如果存在)。默认情况下,我们假设没有季节性组件。 + +下面的 _simple_configs()_ 函数将创建要评估的模型配置列表。 + +该函数仅需要历史数据的最大长度作为参数,并且可选地需要任何季节性组件的周期性,其默认为 1(无季节性组件)。 + +``` +# create a set of simple configs to try +def simple_configs(max_length, offsets=[1]): + configs = list() + for i in range(1, max_length+1): + for o in offsets: + for t in ['persist', 'mean', 'median']: + cfg = [i, o, t] + configs.append(cfg) + return configs +``` + +我们现在有一个网格搜索简单模型超参数的框架,通过一步前进验证。 + +它是通用的,适用于作为列表或 NumPy 数组提供的任何内存中单变量时间序列。 + +我们可以通过在人为设计的 10 步数据集上进行测试来确保所有部分协同工作。 + +下面列出了完整的示例。 + +``` +# grid search simple forecasts +from math import sqrt +from numpy import mean +from numpy import median +from multiprocessing import cpu_count +from joblib import Parallel +from joblib import delayed +from warnings import catch_warnings +from warnings import filterwarnings +from sklearn.metrics import mean_squared_error + +# one-step simple forecast +def simple_forecast(history, config): + n, offset, avg_type = config + # persist value, ignore other config + if avg_type == 'persist': + return history[-n] + # collect values to average + values = list() + if offset == 1: + values = history[-n:] + else: + # skip bad configs + if n*offset > len(history): + raise Exception('Config beyond end of data: %d %d' % (n,offset)) + # try and collect n values using offset + for i in range(1, n+1): + ix = i * offset + values.append(history[-ix]) + # check if we can average + if len(values) < 2: + raise Exception('Cannot calculate average') + # mean of last n values + if avg_type == 'mean': + return mean(values) + # median of last n values + return median(values) + +# root mean squared error or rmse +def measure_rmse(actual, predicted): + return sqrt(mean_squared_error(actual, predicted)) + +# split a univariate dataset into train/test sets +def train_test_split(data, n_test): + return data[:-n_test], data[-n_test:] + +# walk-forward validation for univariate data +def walk_forward_validation(data, n_test, cfg): + predictions = list() + # split dataset + train, test = train_test_split(data, n_test) + # seed history with training dataset + history = [x for x in train] + # step over each time-step in the test set + for i in range(len(test)): + # fit model and make forecast for history + yhat = simple_forecast(history, cfg) + # store forecast in list of predictions + predictions.append(yhat) + # add actual observation to history for the next loop + history.append(test[i]) + # estimate prediction error + error = measure_rmse(test, predictions) + return error + +# score a model, return None on failure +def score_model(data, n_test, cfg, debug=False): + result = None + # convert config to a key + key = str(cfg) + # show all warnings and fail on exception if debugging + if debug: + result = walk_forward_validation(data, n_test, cfg) + else: + # one failure during model validation suggests an unstable config + try: + # never show warnings when grid searching, too noisy + with catch_warnings(): + filterwarnings("ignore") + result = walk_forward_validation(data, n_test, cfg) + except: + error = None + # check for an interesting result + if result is not None: + print(' > Model[%s] %.3f' % (key, result)) + return (key, result) + +# grid search configs +def grid_search(data, cfg_list, n_test, parallel=True): + scores = None + if parallel: + # execute configs in parallel + executor = Parallel(n_jobs=cpu_count(), backend='multiprocessing') + tasks = (delayed(score_model)(data, n_test, cfg) for cfg in cfg_list) + scores = executor(tasks) + else: + scores = [score_model(data, n_test, cfg) for cfg in cfg_list] + # remove empty results + scores = [r for r in scores if r[1] != None] + # sort configs by error, asc + scores.sort(key=lambda tup: tup[1]) + return scores + +# create a set of simple configs to try +def simple_configs(max_length, offsets=[1]): + configs = list() + for i in range(1, max_length+1): + for o in offsets: + for t in ['persist', 'mean', 'median']: + cfg = [i, o, t] + configs.append(cfg) + return configs + +if __name__ == '__main__': + # define dataset + data = [10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0] + print(data) + # data split + n_test = 4 + # model configs + max_length = len(data) - n_test + cfg_list = simple_configs(max_length) + # grid search + scores = grid_search(data, cfg_list, n_test) + print('done') + # list top 3 configs + for cfg, error in scores[:3]: + print(cfg, error) +``` + +首先运行该示例打印设计的时间序列数据集。 + +接下来,在评估模型配置及其错误时报告它们。 + +最后,报告前三种配置的配置和错误。 + +我们可以看到,配置为 1 的持久性模型(例如,持续最后一次观察)实现了所测试的简单模型的最佳表现,如预期的那样。 + +``` +[10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0] + +> Model[[1, 1, 'persist']] 10.000 +> Model[[2, 1, 'persist']] 20.000 +> Model[[2, 1, 'mean']] 15.000 +> Model[[2, 1, 'median']] 15.000 +> Model[[3, 1, 'persist']] 30.000 +> Model[[4, 1, 'persist']] 40.000 +> Model[[5, 1, 'persist']] 50.000 +> Model[[5, 1, 'mean']] 30.000 +> Model[[3, 1, 'mean']] 20.000 +> Model[[4, 1, 'median']] 25.000 +> Model[[6, 1, 'persist']] 60.000 +> Model[[4, 1, 'mean']] 25.000 +> Model[[3, 1, 'median']] 20.000 +> Model[[6, 1, 'mean']] 35.000 +> Model[[5, 1, 'median']] 30.000 +> Model[[6, 1, 'median']] 35.000 +done + +[1, 1, 'persist'] 10.0 +[2, 1, 'mean'] 15.0 +[2, 1, 'median'] 15.0 +``` + +现在我们有一个强大的网格搜索简单模型超参数框架,让我们在一套标准的单变量时间序列数据集上进行测试。 + +每个数据集上显示的结果提供了一个表现基准,可用于比较更复杂的方法,如 SARIMA,ETS,甚至机器学习方法。 + +## 案例研究 1:没有趋势或季节性 + +“每日女性分娩”数据集总结了 1959 年美国加利福尼亚州每日女性总分娩数。 + +数据集没有明显的趋势或季节性成分。 + +![Line Plot of the Daily Female Births Dataset](img/dabb24b02643324fb9d69586be439808.jpg) + +每日女性出生数据集的线图 + +您可以从 [DataMarket](https://datamarket.com/data/set/235k/daily-total-female-births-in-california-1959#!ds=235k&display=line) 了解有关数据集的更多信息。 + +直接从这里下载数据集: + +* [每日总数 - 女性分娩.sv](https://raw.githubusercontent.com/jbrownlee/Datasets/master/daily-total-female-births.csv) + +在当前工作目录中使用文件名“ _daily-total-female-births.csv_ ”保存文件。 + +我们可以使用函数 _read_csv()_ 将此数据集作为 Pandas 系列加载。 + +``` +series = read_csv('daily-total-female-births.csv', header=0, index_col=0) +``` + +数据集有一年或 365 个观测值。我们将使用前 200 个进行训练,将剩余的 165 个作为测试集。 + +下面列出了搜索每日女性单变量时间序列预测问题的完整示例网格。 + +``` +# grid search simple forecast for daily female births +from math import sqrt +from numpy import mean +from numpy import median +from multiprocessing import cpu_count +from joblib import Parallel +from joblib import delayed +from warnings import catch_warnings +from warnings import filterwarnings +from sklearn.metrics import mean_squared_error +from pandas import read_csv + +# one-step simple forecast +def simple_forecast(history, config): + n, offset, avg_type = config + # persist value, ignore other config + if avg_type == 'persist': + return history[-n] + # collect values to average + values = list() + if offset == 1: + values = history[-n:] + else: + # skip bad configs + if n*offset > len(history): + raise Exception('Config beyond end of data: %d %d' % (n,offset)) + # try and collect n values using offset + for i in range(1, n+1): + ix = i * offset + values.append(history[-ix]) + # check if we can average + if len(values) < 2: + raise Exception('Cannot calculate average') + # mean of last n values + if avg_type == 'mean': + return mean(values) + # median of last n values + return median(values) + +# root mean squared error or rmse +def measure_rmse(actual, predicted): + return sqrt(mean_squared_error(actual, predicted)) + +# split a univariate dataset into train/test sets +def train_test_split(data, n_test): + return data[:-n_test], data[-n_test:] + +# walk-forward validation for univariate data +def walk_forward_validation(data, n_test, cfg): + predictions = list() + # split dataset + train, test = train_test_split(data, n_test) + # seed history with training dataset + history = [x for x in train] + # step over each time-step in the test set + for i in range(len(test)): + # fit model and make forecast for history + yhat = simple_forecast(history, cfg) + # store forecast in list of predictions + predictions.append(yhat) + # add actual observation to history for the next loop + history.append(test[i]) + # estimate prediction error + error = measure_rmse(test, predictions) + return error + +# score a model, return None on failure +def score_model(data, n_test, cfg, debug=False): + result = None + # convert config to a key + key = str(cfg) + # show all warnings and fail on exception if debugging + if debug: + result = walk_forward_validation(data, n_test, cfg) + else: + # one failure during model validation suggests an unstable config + try: + # never show warnings when grid searching, too noisy + with catch_warnings(): + filterwarnings("ignore") + result = walk_forward_validation(data, n_test, cfg) + except: + error = None + # check for an interesting result + if result is not None: + print(' > Model[%s] %.3f' % (key, result)) + return (key, result) + +# grid search configs +def grid_search(data, cfg_list, n_test, parallel=True): + scores = None + if parallel: + # execute configs in parallel + executor = Parallel(n_jobs=cpu_count(), backend='multiprocessing') + tasks = (delayed(score_model)(data, n_test, cfg) for cfg in cfg_list) + scores = executor(tasks) + else: + scores = [score_model(data, n_test, cfg) for cfg in cfg_list] + # remove empty results + scores = [r for r in scores if r[1] != None] + # sort configs by error, asc + scores.sort(key=lambda tup: tup[1]) + return scores + +# create a set of simple configs to try +def simple_configs(max_length, offsets=[1]): + configs = list() + for i in range(1, max_length+1): + for o in offsets: + for t in ['persist', 'mean', 'median']: + cfg = [i, o, t] + configs.append(cfg) + return configs + +if __name__ == '__main__': + # define dataset + series = read_csv('daily-total-female-births.csv', header=0, index_col=0) + data = series.values + print(data) + # data split + n_test = 165 + # model configs + max_length = len(data) - n_test + cfg_list = simple_configs(max_length) + # grid search + scores = grid_search(data, cfg_list, n_test) + print('done') + # list top 3 configs + for cfg, error in scores[:3]: + print(cfg, error) +``` + +运行该示例将打印模型配置,并在评估模型时打印 RMSE。 + +在运行结束时报告前三个模型配置及其错误。 + +我们可以看到最好的结果是出生率约为 6.93 的 RMSE,具有以下配置: + +* **策略**:平均值 +* **n** :22 +* **函数**:mean() + +这是令人惊讶的,因为缺乏趋势或季节性,我会预期持续-1 或整个历史数据集的平均值,以产生最佳表现。 + +``` +... +> Model[[186, 1, 'mean']] 7.523 +> Model[[200, 1, 'median']] 7.681 +> Model[[186, 1, 'median']] 7.691 +> Model[[187, 1, 'persist']] 11.137 +> Model[[187, 1, 'mean']] 7.527 +done + +[22, 1, 'mean'] 6.930411499775709 +[23, 1, 'mean'] 6.932293117115201 +[21, 1, 'mean'] 6.951918385845375 +``` + +## 案例研究 2:趋势 + +“洗发水”数据集总结了三年内洗发水的月销售额。 + +数据集包含明显的趋势,但没有明显的季节性成分。 + +![Line Plot of the Monthly Shampoo Sales Dataset](img/334f0b617304a565e1d93a31bdd1c50d.jpg) + +月度洗发水销售数据集的线图 + +您可以从 [DataMarket](https://datamarket.com/data/set/22r0/sales-of-shampoo-over-a-three-year-period#!ds=22r0&display=line) 了解有关数据集的更多信息。 + +直接从这里下载数据集: + +* [shampoo.csv](https://raw.githubusercontent.com/jbrownlee/Datasets/master/shampoo.csv) + +在当前工作目录中使用文件名“ _shampoo.csv_ ”保存文件。 + +我们可以使用函数 _read_csv()_ 将此数据集作为 Pandas 系列加载。 + +``` +# parse dates +def custom_parser(x): + return datetime.strptime('195'+x, '%Y-%m') + +# load dataset +series = read_csv('shampoo.csv', header=0, index_col=0, date_parser=custom_parser) +``` + +数据集有三年,或 36 个观测值。我们将使用前 24 个用于训练,其余 12 个用作测试集。 + +下面列出了搜索洗发水销售单变量时间序列预测问题的完整示例网格。 + +``` +# grid search simple forecast for monthly shampoo sales +from math import sqrt +from numpy import mean +from numpy import median +from multiprocessing import cpu_count +from joblib import Parallel +from joblib import delayed +from warnings import catch_warnings +from warnings import filterwarnings +from sklearn.metrics import mean_squared_error +from pandas import read_csv +from pandas import datetime + +# one-step simple forecast +def simple_forecast(history, config): + n, offset, avg_type = config + # persist value, ignore other config + if avg_type == 'persist': + return history[-n] + # collect values to average + values = list() + if offset == 1: + values = history[-n:] + else: + # skip bad configs + if n*offset > len(history): + raise Exception('Config beyond end of data: %d %d' % (n,offset)) + # try and collect n values using offset + for i in range(1, n+1): + ix = i * offset + values.append(history[-ix]) + # check if we can average + if len(values) < 2: + raise Exception('Cannot calculate average') + # mean of last n values + if avg_type == 'mean': + return mean(values) + # median of last n values + return median(values) + +# root mean squared error or rmse +def measure_rmse(actual, predicted): + return sqrt(mean_squared_error(actual, predicted)) + +# split a univariate dataset into train/test sets +def train_test_split(data, n_test): + return data[:-n_test], data[-n_test:] + +# walk-forward validation for univariate data +def walk_forward_validation(data, n_test, cfg): + predictions = list() + # split dataset + train, test = train_test_split(data, n_test) + # seed history with training dataset + history = [x for x in train] + # step over each time-step in the test set + for i in range(len(test)): + # fit model and make forecast for history + yhat = simple_forecast(history, cfg) + # store forecast in list of predictions + predictions.append(yhat) + # add actual observation to history for the next loop + history.append(test[i]) + # estimate prediction error + error = measure_rmse(test, predictions) + return error + +# score a model, return None on failure +def score_model(data, n_test, cfg, debug=False): + result = None + # convert config to a key + key = str(cfg) + # show all warnings and fail on exception if debugging + if debug: + result = walk_forward_validation(data, n_test, cfg) + else: + # one failure during model validation suggests an unstable config + try: + # never show warnings when grid searching, too noisy + with catch_warnings(): + filterwarnings("ignore") + result = walk_forward_validation(data, n_test, cfg) + except: + error = None + # check for an interesting result + if result is not None: + print(' > Model[%s] %.3f' % (key, result)) + return (key, result) + +# grid search configs +def grid_search(data, cfg_list, n_test, parallel=True): + scores = None + if parallel: + # execute configs in parallel + executor = Parallel(n_jobs=cpu_count(), backend='multiprocessing') + tasks = (delayed(score_model)(data, n_test, cfg) for cfg in cfg_list) + scores = executor(tasks) + else: + scores = [score_model(data, n_test, cfg) for cfg in cfg_list] + # remove empty results + scores = [r for r in scores if r[1] != None] + # sort configs by error, asc + scores.sort(key=lambda tup: tup[1]) + return scores + +# create a set of simple configs to try +def simple_configs(max_length, offsets=[1]): + configs = list() + for i in range(1, max_length+1): + for o in offsets: + for t in ['persist', 'mean', 'median']: + cfg = [i, o, t] + configs.append(cfg) + return configs + +# parse dates +def custom_parser(x): + return datetime.strptime('195'+x, '%Y-%m') + +if __name__ == '__main__': + # load dataset + series = read_csv('shampoo.csv', header=0, index_col=0, date_parser=custom_parser) + data = series.values + print(data.shape) + # data split + n_test = 12 + # model configs + max_length = len(data) - n_test + cfg_list = simple_configs(max_length) + # grid search + scores = grid_search(data, cfg_list, n_test) + print('done') + # list top 3 configs + for cfg, error in scores[:3]: + print(cfg, error) +``` + +运行该示例将打印配置,并在评估模型时打印 RMSE。 + +在运行结束时报告前三个模型配置及其错误。 + +我们可以看到最好的结果是 RMSE 约 95.69 销售,具有以下配置: + +* **策略**:坚持 +* **n** :2 + +这是令人惊讶的,因为数据的趋势结构表明持久的前一个值(-1)将是最好的方法,而不是持久的倒数第二个值。 + +``` +... +> Model[[23, 1, 'mean']] 209.782 +> Model[[23, 1, 'median']] 221.863 +> Model[[24, 1, 'persist']] 305.635 +> Model[[24, 1, 'mean']] 213.466 +> Model[[24, 1, 'median']] 226.061 +done + +[2, 1, 'persist'] 95.69454007413378 +[2, 1, 'mean'] 96.01140340258198 +[2, 1, 'median'] 96.01140340258198 +``` + +## 案例研究 3:季节性 + +“月平均温度”数据集总结了 1920 至 1939 年华氏诺丁汉城堡的月平均气温,以华氏度为单位。 + +数据集具有明显的季节性成分,没有明显的趋势。 + +![Line Plot of the Monthly Mean Temperatures Dataset](img/72a4721b5d8d493844936208df537995.jpg) + +月平均气温数据集的线图 + +您可以从 [DataMarket](https://datamarket.com/data/set/22li/mean-monthly-air-temperature-deg-f-nottingham-castle-1920-1939#!ds=22li&display=line) 了解有关数据集的更多信息。 + +直接从这里下载数据集: + +* [monthly-mean-temp.csv](https://raw.githubusercontent.com/jbrownlee/Datasets/master/monthly-mean-temp.csv) + +在当前工作目录中使用文件名“ _monthly-mean-temp.csv_ ”保存文件。 + +我们可以使用函数 _read_csv()_ 将此数据集作为 Pandas 系列加载。 + +``` +series = read_csv('monthly-mean-temp.csv', header=0, index_col=0) +``` + +数据集有 20 年,或 240 个观测值。我们将数据集修剪为过去五年的数据(60 个观测值),以加快模型评估过程并使用去年或 12 个观测值进行测试集。 + +``` +# trim dataset to 5 years +data = data[-(5*12):] +``` + +季节性成分的周期约为一年,或 12 个观测值。在准备模型配置时,我们将此作为调用 _simple_configs()_ 函数的季节性时段。 + +``` +# model configs +cfg_list = simple_configs(seasonal=[0, 12]) +``` + +下面列出了搜索月平均温度时间序列预测问题的完整示例网格。 + +``` +# grid search simple forecast for monthly mean temperature +from math import sqrt +from numpy import mean +from numpy import median +from multiprocessing import cpu_count +from joblib import Parallel +from joblib import delayed +from warnings import catch_warnings +from warnings import filterwarnings +from sklearn.metrics import mean_squared_error +from pandas import read_csv + +# one-step simple forecast +def simple_forecast(history, config): + n, offset, avg_type = config + # persist value, ignore other config + if avg_type == 'persist': + return history[-n] + # collect values to average + values = list() + if offset == 1: + values = history[-n:] + else: + # skip bad configs + if n*offset > len(history): + raise Exception('Config beyond end of data: %d %d' % (n,offset)) + # try and collect n values using offset + for i in range(1, n+1): + ix = i * offset + values.append(history[-ix]) + # check if we can average + if len(values) < 2: + raise Exception('Cannot calculate average') + # mean of last n values + if avg_type == 'mean': + return mean(values) + # median of last n values + return median(values) + +# root mean squared error or rmse +def measure_rmse(actual, predicted): + return sqrt(mean_squared_error(actual, predicted)) + +# split a univariate dataset into train/test sets +def train_test_split(data, n_test): + return data[:-n_test], data[-n_test:] + +# walk-forward validation for univariate data +def walk_forward_validation(data, n_test, cfg): + predictions = list() + # split dataset + train, test = train_test_split(data, n_test) + # seed history with training dataset + history = [x for x in train] + # step over each time-step in the test set + for i in range(len(test)): + # fit model and make forecast for history + yhat = simple_forecast(history, cfg) + # store forecast in list of predictions + predictions.append(yhat) + # add actual observation to history for the next loop + history.append(test[i]) + # estimate prediction error + error = measure_rmse(test, predictions) + return error + +# score a model, return None on failure +def score_model(data, n_test, cfg, debug=False): + result = None + # convert config to a key + key = str(cfg) + # show all warnings and fail on exception if debugging + if debug: + result = walk_forward_validation(data, n_test, cfg) + else: + # one failure during model validation suggests an unstable config + try: + # never show warnings when grid searching, too noisy + with catch_warnings(): + filterwarnings("ignore") + result = walk_forward_validation(data, n_test, cfg) + except: + error = None + # check for an interesting result + if result is not None: + print(' > Model[%s] %.3f' % (key, result)) + return (key, result) + +# grid search configs +def grid_search(data, cfg_list, n_test, parallel=True): + scores = None + if parallel: + # execute configs in parallel + executor = Parallel(n_jobs=cpu_count(), backend='multiprocessing') + tasks = (delayed(score_model)(data, n_test, cfg) for cfg in cfg_list) + scores = executor(tasks) + else: + scores = [score_model(data, n_test, cfg) for cfg in cfg_list] + # remove empty results + scores = [r for r in scores if r[1] != None] + # sort configs by error, asc + scores.sort(key=lambda tup: tup[1]) + return scores + +# create a set of simple configs to try +def simple_configs(max_length, offsets=[1]): + configs = list() + for i in range(1, max_length+1): + for o in offsets: + for t in ['persist', 'mean', 'median']: + cfg = [i, o, t] + configs.append(cfg) + return configs + +if __name__ == '__main__': + # define dataset + series = read_csv('monthly-mean-temp.csv', header=0, index_col=0) + data = series.values + print(data) + # data split + n_test = 12 + # model configs + max_length = len(data) - n_test + cfg_list = simple_configs(max_length, offsets=[1,12]) + # grid search + scores = grid_search(data, cfg_list, n_test) + print('done') + # list top 3 configs + for cfg, error in scores[:3]: + print(cfg, error) +``` + +运行该示例将打印模型配置,并在评估模型时打印 RMSE。 + +在运行结束时报告前三个模型配置及其错误。 + +我们可以看到最好的结果是大约 1.501 度的 RMSE,具有以下配置: + +* **策略**:平均值 +* **n** :4 +* **偏移**:12 +* **函数**:mean() + +这个发现并不太令人惊讶。鉴于数据的季节性结构,我们预计年度周期中先前点的最后几个观测值的函数是有效的。 + +``` +... +> Model[[227, 12, 'persist']] 5.365 +> Model[[228, 1, 'persist']] 2.818 +> Model[[228, 1, 'mean']] 8.258 +> Model[[228, 1, 'median']] 8.361 +> Model[[228, 12, 'persist']] 2.818 +done +[4, 12, 'mean'] 1.5015616870445234 +[8, 12, 'mean'] 1.5794579766489512 +[13, 12, 'mean'] 1.586186052546763 +``` + +## 案例研究 4:趋势和季节性 + +“月度汽车销售”数据集总结了 1960 年至 1968 年间加拿大魁北克省的月度汽车销量。 + +数据集具有明显的趋势和季节性成分。 + +![Line Plot of the Monthly Car Sales Dataset](img/6ff80f643e5ca7c00df5ce9e8b51cbc5.jpg) + +月度汽车销售数据集的线图 + +您可以从 [DataMarket](https://datamarket.com/data/set/22n4/monthly-car-sales-in-quebec-1960-1968#!ds=22n4&display=line) 了解有关数据集的更多信息。 + +直接从这里下载数据集: + +* [month-car-sales.csv](https://raw.githubusercontent.com/jbrownlee/Datasets/master/monthly-car-sales.csv) + +在当前工作目录中使用文件名“ _monthly-car-sales.csv_ ”保存文件。 + +我们可以使用函数 _read_csv()_ 将此数据集作为 Pandas 系列加载。 + +``` +series = read_csv('monthly-car-sales.csv', header=0, index_col=0) +``` + +数据集有 9 年或 108 个观测值。我们将使用去年或 12 个观测值作为测试集。 + +季节性成分的期限可能是六个月或 12 个月。在准备模型配置时,我们将尝试将两者作为调用 _simple_configs()_ 函数的季节性时段。 + +``` +# model configs +cfg_list = simple_configs(seasonal=[0,6,12]) +``` + +下面列出了搜索月度汽车销售时间序列预测问题的完整示例网格。 + +``` +# grid search simple forecast for monthly car sales +from math import sqrt +from numpy import mean +from numpy import median +from multiprocessing import cpu_count +from joblib import Parallel +from joblib import delayed +from warnings import catch_warnings +from warnings import filterwarnings +from sklearn.metrics import mean_squared_error +from pandas import read_csv + +# one-step simple forecast +def simple_forecast(history, config): + n, offset, avg_type = config + # persist value, ignore other config + if avg_type == 'persist': + return history[-n] + # collect values to average + values = list() + if offset == 1: + values = history[-n:] + else: + # skip bad configs + if n*offset > len(history): + raise Exception('Config beyond end of data: %d %d' % (n,offset)) + # try and collect n values using offset + for i in range(1, n+1): + ix = i * offset + values.append(history[-ix]) + # check if we can average + if len(values) < 2: + raise Exception('Cannot calculate average') + # mean of last n values + if avg_type == 'mean': + return mean(values) + # median of last n values + return median(values) + +# root mean squared error or rmse +def measure_rmse(actual, predicted): + return sqrt(mean_squared_error(actual, predicted)) + +# split a univariate dataset into train/test sets +def train_test_split(data, n_test): + return data[:-n_test], data[-n_test:] + +# walk-forward validation for univariate data +def walk_forward_validation(data, n_test, cfg): + predictions = list() + # split dataset + train, test = train_test_split(data, n_test) + # seed history with training dataset + history = [x for x in train] + # step over each time-step in the test set + for i in range(len(test)): + # fit model and make forecast for history + yhat = simple_forecast(history, cfg) + # store forecast in list of predictions + predictions.append(yhat) + # add actual observation to history for the next loop + history.append(test[i]) + # estimate prediction error + error = measure_rmse(test, predictions) + return error + +# score a model, return None on failure +def score_model(data, n_test, cfg, debug=False): + result = None + # convert config to a key + key = str(cfg) + # show all warnings and fail on exception if debugging + if debug: + result = walk_forward_validation(data, n_test, cfg) + else: + # one failure during model validation suggests an unstable config + try: + # never show warnings when grid searching, too noisy + with catch_warnings(): + filterwarnings("ignore") + result = walk_forward_validation(data, n_test, cfg) + except: + error = None + # check for an interesting result + if result is not None: + print(' > Model[%s] %.3f' % (key, result)) + return (key, result) + +# grid search configs +def grid_search(data, cfg_list, n_test, parallel=True): + scores = None + if parallel: + # execute configs in parallel + executor = Parallel(n_jobs=cpu_count(), backend='multiprocessing') + tasks = (delayed(score_model)(data, n_test, cfg) for cfg in cfg_list) + scores = executor(tasks) + else: + scores = [score_model(data, n_test, cfg) for cfg in cfg_list] + # remove empty results + scores = [r for r in scores if r[1] != None] + # sort configs by error, asc + scores.sort(key=lambda tup: tup[1]) + return scores + +# create a set of simple configs to try +def simple_configs(max_length, offsets=[1]): + configs = list() + for i in range(1, max_length+1): + for o in offsets: + for t in ['persist', 'mean', 'median']: + cfg = [i, o, t] + configs.append(cfg) + return configs + +if __name__ == '__main__': + # define dataset + series = read_csv('monthly-car-sales.csv', header=0, index_col=0) + data = series.values + print(data) + # data split + n_test = 12 + # model configs + max_length = len(data) - n_test + cfg_list = simple_configs(max_length, offsets=[1,12]) + # grid search + scores = grid_search(data, cfg_list, n_test) + print('done') + # list top 3 configs + for cfg, error in scores[:3]: + print(cfg, error) +``` + +运行该示例将打印模型配置,并在评估模型时打印 RMSE。 + +在运行结束时报告前三个模型配置及其错误。 + +我们可以看到最好的结果是大约 1841.155 销售的 RMSE,具有以下配置: + +* **策略**:平均值 +* **n** :3 +* **偏移**:12 +* **功能**:中位数() + +所选模型是先前周期中同一点的最后几次观察的函数并不奇怪,尽管使用中位数而不是均值可能不会立即明显,结果比平均值好得多。 + +``` +... +> Model[[79, 1, 'median']] 5124.113 +> Model[[91, 12, 'persist']] 9580.149 +> Model[[79, 12, 'persist']] 8641.529 +> Model[[92, 1, 'persist']] 9830.921 +> Model[[92, 1, 'mean']] 5148.126 +done +[3, 12, 'median'] 1841.1559321976688 +[3, 12, 'mean'] 2115.198495632485 +[4, 12, 'median'] 2184.37708988932 +``` + +## 扩展 + +本节列出了一些扩展您可能希望探索的教程的想法。 + +* **地块预测**。更新框架以重新拟合具有最佳配置的模型并预测整个测试数据集,然后将预测与测试集中的实际观察值进行比较。 +* **漂移方法**。实施简单预测的漂移方法,并将结果与​​平均和天真的方法进行比较。 +* **另一个数据集**。将开发的框架应用于另外的单变量时间序列问题(例如,来自[时间序列数据集库](https://datamarket.com/data/list/?q=provider:tsdl))。 + +如果你探索任何这些扩展,我很想知道。 + +## 进一步阅读 + +如果您希望深入了解,本节将提供有关该主题的更多资源。 + +* [预测,维基百科](https://en.wikipedia.org/wiki/Forecasting) +* [Joblib:运行 Python 函数作为管道作业](https://pythonhosted.org/joblib/) +* [时间序列数据集库](https://datamarket.com/data/list/?q=provider:tsdl),DataMarket。 + +## 摘要 + +在本教程中,您了解了如何从头开始构建一个框架,用于网格搜索简单的天真和平均策略,用于使用单变量数据进行时间序列预测。 + +具体来说,你学到了: + +* 如何使用前向验证从头开始开发网格搜索简单模型的框架。 +* 如何为出生日常时间序列数据网格搜索简单模型超参数。 +* 如何为洗发水销售,汽车销售和温度的月度时间序列数据网格搜索简单模型超参数。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/how-to-grid-search-sarima-model-hyperparameters-for-time-series-forecasting-in-python.md b/docs/dl-ts/how-to-grid-search-sarima-model-hyperparameters-for-time-series-forecasting-in-python.md new file mode 100644 index 0000000000000000000000000000000000000000..b12fee2da9ea8cd486536435f35aaac9db79db9c --- /dev/null +++ b/docs/dl-ts/how-to-grid-search-sarima-model-hyperparameters-for-time-series-forecasting-in-python.md @@ -0,0 +1,1274 @@ +# 如何在 Python 中搜索 SARIMA 模型超参数用于时间序列预测 + +> 原文: [https://machinelearningmastery.com/how-to-grid-search-sarima-model-hyperparameters-for-time-series-forecasting-in-python/](https://machinelearningmastery.com/how-to-grid-search-sarima-model-hyperparameters-for-time-series-forecasting-in-python/) + +季节性自回归整合移动平均线(SARIMA)模型是一种对可能包含趋势和季节性成分的单变量时间序列数据进行建模的方法。 + +它是时间序列预测的有效方法,但需要仔细分析和领域专业知识才能配置七个或更多模型超参数。 + +配置模型的另一种方法是利用快速和并行的现代硬件来网格搜索一套超参数配置,以便发现最有效的方法。通常,此过程可以揭示非直观模型配置,这些配置导致预测误差低于通过仔细分析指定的配置。 + +在本教程中,您将了解如何开发网格搜索所有 SARIMA 模型超参数的框架,以进行单变量时间序列预测。 + +完成本教程后,您将了解: + +* 如何使用前向验证从头开始开发网格搜索 SARIMA 模型的框架。 +* 如何为出生日常时间序列数据网格搜索 SARIMA 模型超参数。 +* 如何对洗发水销售,汽车销售和温度的月度时间序列数据进行网格搜索 SARIMA 模型超参数。 + +让我们开始吧。 + +![How to Grid Search SARIMA Model Hyperparameters for Time Series Forecasting in Python](img/708ea207c23e8bbb98e0546431cbfbd8.jpg) + +如何在 Python 中搜索用于时间序列预测的 SARIMA 模型超参数 +[Thomas](https://www.flickr.com/photos/photommo/17832992898/) 的照片,保留一些权利。 + +## 教程概述 + +本教程分为六个部分;他们是: + +1. SARIMA 用于时间序列预测 +2. 开发网格搜索框架 +3. 案例研究 1:没有趋势或季节性 +4. 案例研究 2:趋势 +5. 案例研究 3:季节性 +6. 案例研究 4:趋势和季节性 + +## SARIMA 用于时间序列预测 + +季节性自回归整合移动平均线,SARIMA 或季节性 ARIMA,是 ARIMA 的扩展,明确支持具有季节性成分的单变量时间序列数据。 + +它增加了三个新的超参数来指定系列季节性成分的自回归(AR),差分(I)和移动平均(MA),以及季节性周期的附加参数。 + +> 通过在 ARIMA 中包含额外的季节性术语来形成季节性 ARIMA 模型[...]模型的季节性部分由与模型的非季节性组成非常相似的术语组成,但它们涉及季节性时段的后移。 + +- 第 242 页,[预测:原则和实践](https://amzn.to/2xlJsfV),2013。 + +配置 SARIMA 需要为系列的趋势和季节性元素选择超参数。 + +有三个趋势元素需要配置。 + +它们与 ARIMA 模型相同;特别: + +* **p** :趋势自动回归顺序。 +* **d** :趋势差异顺序。 +* **q** :趋势均线。 + +有四个不属于 ARIMA 的季节性元素必须配置;他们是: + +* **P** :季节性自回归顺序。 +* **D** :季节性差异顺序。 +* **Q** :季节性移动平均线。 +* **m** :单个季节性时段的时间步数。 + +同时,SARIMA 模型的表示法指定为: + +``` +SARIMA(p,d,q)(P,D,Q)m +``` + +SARIMA 模型可以通过模型配置参数包含 ARIMA,ARMA,AR 和 MA 模型。 + +可以通过分析自相关和部分自相关图来配置模型的趋势和季节性超参数,这可能需要一些专业知识。 + +另一种方法是对一组模型配置进行网格搜索,并发现哪些配置最适合特定的单变量时间序列。 + +> 季节性 ARIMA 模型可能具有大量参数和术语组合。因此,在拟合数据时尝试各种模型并使用适当的标准选择最佳拟合模型是合适的... + +- 第 143-144 页,[介绍时间序列与 R](https://amzn.to/2smB9LR) ,2009 年。 + +这种方法在现代计算机上比分析过程更快,并且可以揭示可能不明显的令人惊讶的结果并导致较低的预测误差。 + +## 开发网格搜索框架 + +在本节中,我们将针对给定的单变量时间序列预测问题开发网格搜索 SARIMA 模型超参数的框架。 + +我们将使用 statsmodels 库提供的 [SARIMA](http://www.statsmodels.org/dev/generated/statsmodels.tsa.statespace.sarimax.SARIMAX.html) 的实现。 + +该模型具有超参数,可控制为系列,趋势和季节性执行的模型的性质,具体为: + +* **order** :用于趋势建模的元组 p,d 和 q 参数。 +* **sesonal_order** :用于建模季节性的 P,D,Q 和 m 参数元组 +* **趋势**:用于将确定性趋势模型控制为“n”,“c”,“t”,“ct”之一的参数,无趋势,常数,线性和常数,线性趋势,分别。 + +如果您对问题了解得足以指定其中一个或多个参数,则应指定它们。如果没有,您可以尝试网格搜索这些参数。 + +我们可以通过定义一个适合具有给定配置的模型的函数来开始,并进行一步预测。 + +下面的 _sarima_forecast()_ 实现了这种行为。 + +该函数采用连续先前观察的数组或列表以及用于配置模型的配置参数列表,特别是两个元组和趋势顺序,季节性顺序趋势和参数的字符串。 + +我们还尝试通过放宽约束来使模型健壮,例如数据必须是静止的并且 MA 变换是可逆的。 + +``` +# one-step sarima forecast +def sarima_forecast(history, config): + order, sorder, trend = config + # define model + model = SARIMAX(history, order=order, seasonal_order=sorder, trend=trend, enforce_stationarity=False, enforce_invertibility=False) + # fit model + model_fit = model.fit(disp=False) + # make one step forecast + yhat = model_fit.predict(len(history), len(history)) + return yhat[0] +``` + +接下来,我们需要建立一些函数,通过前向验证重复拟合和评估模型,包括将数据集拆分为训练集和测试集以及评估一步预测。 + +我们可以使用给定指定大小的分割的切片来分割列表或 NumPy 数据数组,例如,从测试集中的数据中使用的时间步数。 + +下面的 _train_test_split()_ 函数为提供的数据集和要在测试集中使用的指定数量的时间步骤实现此功能。 + +``` +# split a univariate dataset into train/test sets +def train_test_split(data, n_test): + return data[:-n_test], data[-n_test:] +``` + +在对测试数据集中的每个步骤进行预测之后,需要将它们与测试集进行比较以计算错误分数。 + +时间序列预测有许多流行的错误分数。在这种情况下,我们将使用均方根误差(RMSE),但您可以将其更改为您的首选度量,例如 MAPE,MAE 等 + +下面的 _measure_rmse()_ 函数将根据实际(测试集)和预测值列表计算 RMSE。 + +``` +# root mean squared error or rmse +def measure_rmse(actual, predicted): + return sqrt(mean_squared_error(actual, predicted)) +``` + +我们现在可以实现前向验证方案。这是评估尊重观测时间顺序的时间序列预测模型的标准方法。 + +首先,使用 _train_test_split()_ 函数将提供的单变量时间序列数据集分成训练集和测试集。然后枚举测试集中的观察数。对于每一个我们都适合所有历史的模型,并进行一步预测。然后将对时间步骤的真实观察添加到历史中并重复该过程。调用 _sarima_forecast()_ 函数以适合模型并进行预测。最后,通过调用 _measure_rmse()_ 函数,将所有一步预测与实际测试集进行比较,计算错误分数。 + +下面的 _walk_forward_validation()_ 函数实现了这一点,采用单变量时间序列,在测试集中使用的一些时间步骤,以及模型配置数组。 + +``` +# walk-forward validation for univariate data +def walk_forward_validation(data, n_test, cfg): + predictions = list() + # split dataset + train, test = train_test_split(data, n_test) + # seed history with training dataset + history = [x for x in train] + # step over each time-step in the test set + for i in range(len(test)): + # fit model and make forecast for history + yhat = sarima_forecast(history, cfg) + # store forecast in list of predictions + predictions.append(yhat) + # add actual observation to history for the next loop + history.append(test[i]) + # estimate prediction error + error = measure_rmse(test, predictions) + return error +``` + +如果您对进行多步预测感兴趣,可以在 _sarima_forecast()_ 函数中更改 _predict()_ 的调用,并更改 _ 中的错误计算 measure_rmse()_ 功能。 + +我们可以使用不同的模型配置列表重复调用 _walk_forward_validation()_。 + +一个可能的问题是,可能不会为模型调用模型配置的某些组合,并且会抛出异常,例如,指定数据中季节性结构的一些但不是所有方面。 + +此外,某些型号还可能会对某些数据发出警告,例如:来自 statsmodels 库调用的线性代数库。 + +我们可以在网格搜索期间捕获异常并忽略警告,方法是将所有调用包含在 _walk_forward_validation()_ 中,并使用 try-except 和 block 来忽略警告。我们还可以添加调试支持来禁用这些保护,以防我们想要查看实际情况。最后,如果确实发生了错误,我们可以返回 None 结果,否则我们可以打印一些有关每个模型评估技能的信息。当评估大量模型时,这很有用。 + +下面的 _score_model()_ 函数实现了这个并返回(键和结果)的元组,其中键是测试模型配置的字符串版本。 + +``` +# score a model, return None on failure +def score_model(data, n_test, cfg, debug=False): + result = None + # convert config to a key + key = str(cfg) + # show all warnings and fail on exception if debugging + if debug: + result = walk_forward_validation(data, n_test, cfg) + else: + # one failure during model validation suggests an unstable config + try: + # never show warnings when grid searching, too noisy + with catch_warnings(): + filterwarnings("ignore") + result = walk_forward_validation(data, n_test, cfg) + except: + error = None + # check for an interesting result + if result is not None: + print(' > Model[%s] %.3f' % (key, result)) + return (key, result) +``` + +接下来,我们需要一个循环来测试不同模型配置的列表。 + +这是驱动网格搜索过程的主要功能,并将为每个模型配置调用 _score_model()_ 函数。 + +通过并行评估模型配置,我们可以大大加快网格搜索过程。一种方法是使用 [Joblib 库](https://pythonhosted.org/joblib/)。 + +我们可以定义一个 Parallel 对象,其中包含要使用的核心数,并将其设置为硬件中检测到的分数。 + +``` +executor = Parallel(n_jobs=cpu_count(), backend='multiprocessing') +``` + +然后我们可以创建一个并行执行的任务列表,这将是对我们拥有的每个模型配置的 _score_model()_ 函数的一次调用。 + +``` +tasks = (delayed(score_model)(data, n_test, cfg) for cfg in cfg_list) +``` + +最后,我们可以使用 Parallel 对象并行执行任务列表。 + +``` +scores = executor(tasks) +``` + +而已。 + +我们还可以提供评估所有模型配置的非并行版本,以防我们想要调试某些内容。 + +``` +scores = [score_model(data, n_test, cfg) for cfg in cfg_list] +``` + +评估配置列表的结果将是元组列表,每个元组都有一个名称,该名称总结了特定的模型配置,并且使用该配置评估的模型的错误为 RMSE,如果出现错误则为 None。 + +我们可以使用“无”过滤掉所有分数。 + +``` +scores = [r for r in scores if r[1] != None] +``` + +然后我们可以按照升序排列列表中的所有元组(最好是第一个),然后返回此分数列表以供审阅。 + +给定单变量时间序列数据集,模型配置列表(列表列表)以及在测试集中使用的时间步数,下面的 _grid_search()_ 函数实现此行为。可选的 _ 并行 _ 参数允许对所有内核的模型进行开启或关闭调整,默认情况下处于打开状态。 + +``` +# grid search configs +def grid_search(data, cfg_list, n_test, parallel=True): + scores = None + if parallel: + # execute configs in parallel + executor = Parallel(n_jobs=cpu_count(), backend='multiprocessing') + tasks = (delayed(score_model)(data, n_test, cfg) for cfg in cfg_list) + scores = executor(tasks) + else: + scores = [score_model(data, n_test, cfg) for cfg in cfg_list] + # remove empty results + scores = [r for r in scores if r[1] != None] + # sort configs by error, asc + scores.sort(key=lambda tup: tup[1]) + return scores +``` + +我们差不多完成了。 + +剩下要做的唯一事情是定义模型配置列表以尝试数据集。 + +我们可以一般地定义它。我们可能想要指定的唯一参数是系列中季节性组件的周期性(如果存在)。默认情况下,我们假设没有季节性组件。 + +下面的 _sarima_configs()_ 函数将创建要评估的模型配置列表。 + +这些配置假设趋势和季节性的每个 AR,MA 和 I 分量都是低阶的,例如,关(0)或[1,2]。如果您认为订单可能更高,则可能需要扩展这些范围。可以指定季节性时段的可选列表,您甚至可以更改该功能以指定您可能了解的有关时间序列的其他元素。 + +理论上,有 1,296 种可能的模型配置需要评估,但在实践中,许多模型配置无效并会导致我们将陷入和忽略的错误。 + +``` +# create a set of sarima configs to try +def sarima_configs(seasonal=[0]): + models = list() + # define config lists + p_params = [0, 1, 2] + d_params = [0, 1] + q_params = [0, 1, 2] + t_params = ['n','c','t','ct'] + P_params = [0, 1, 2] + D_params = [0, 1] + Q_params = [0, 1, 2] + m_params = seasonal + # create config instances + for p in p_params: + for d in d_params: + for q in q_params: + for t in t_params: + for P in P_params: + for D in D_params: + for Q in Q_params: + for m in m_params: + cfg = [(p,d,q), (P,D,Q,m), t] + models.append(cfg) + return models +``` + +我们现在有一个网格搜索 SARIMA 模型超参数的框架,通过一步前进验证。 + +它是通用的,适用于作为列表或 NumPy 数组提供的任何内存中单变量时间序列。 + +我们可以通过在人为设计的 10 步数据集上进行测试来确保所有部分协同工作。 + +下面列出了完整的示例。 + +``` +# grid search sarima hyperparameters +from math import sqrt +from multiprocessing import cpu_count +from joblib import Parallel +from joblib import delayed +from warnings import catch_warnings +from warnings import filterwarnings +from statsmodels.tsa.statespace.sarimax import SARIMAX +from sklearn.metrics import mean_squared_error + +# one-step sarima forecast +def sarima_forecast(history, config): + order, sorder, trend = config + # define model + model = SARIMAX(history, order=order, seasonal_order=sorder, trend=trend, enforce_stationarity=False, enforce_invertibility=False) + # fit model + model_fit = model.fit(disp=False) + # make one step forecast + yhat = model_fit.predict(len(history), len(history)) + return yhat[0] + +# root mean squared error or rmse +def measure_rmse(actual, predicted): + return sqrt(mean_squared_error(actual, predicted)) + +# split a univariate dataset into train/test sets +def train_test_split(data, n_test): + return data[:-n_test], data[-n_test:] + +# walk-forward validation for univariate data +def walk_forward_validation(data, n_test, cfg): + predictions = list() + # split dataset + train, test = train_test_split(data, n_test) + # seed history with training dataset + history = [x for x in train] + # step over each time-step in the test set + for i in range(len(test)): + # fit model and make forecast for history + yhat = sarima_forecast(history, cfg) + # store forecast in list of predictions + predictions.append(yhat) + # add actual observation to history for the next loop + history.append(test[i]) + # estimate prediction error + error = measure_rmse(test, predictions) + return error + +# score a model, return None on failure +def score_model(data, n_test, cfg, debug=False): + result = None + # convert config to a key + key = str(cfg) + # show all warnings and fail on exception if debugging + if debug: + result = walk_forward_validation(data, n_test, cfg) + else: + # one failure during model validation suggests an unstable config + try: + # never show warnings when grid searching, too noisy + with catch_warnings(): + filterwarnings("ignore") + result = walk_forward_validation(data, n_test, cfg) + except: + error = None + # check for an interesting result + if result is not None: + print(' > Model[%s] %.3f' % (key, result)) + return (key, result) + +# grid search configs +def grid_search(data, cfg_list, n_test, parallel=True): + scores = None + if parallel: + # execute configs in parallel + executor = Parallel(n_jobs=cpu_count(), backend='multiprocessing') + tasks = (delayed(score_model)(data, n_test, cfg) for cfg in cfg_list) + scores = executor(tasks) + else: + scores = [score_model(data, n_test, cfg) for cfg in cfg_list] + # remove empty results + scores = [r for r in scores if r[1] != None] + # sort configs by error, asc + scores.sort(key=lambda tup: tup[1]) + return scores + +# create a set of sarima configs to try +def sarima_configs(seasonal=[0]): + models = list() + # define config lists + p_params = [0, 1, 2] + d_params = [0, 1] + q_params = [0, 1, 2] + t_params = ['n','c','t','ct'] + P_params = [0, 1, 2] + D_params = [0, 1] + Q_params = [0, 1, 2] + m_params = seasonal + # create config instances + for p in p_params: + for d in d_params: + for q in q_params: + for t in t_params: + for P in P_params: + for D in D_params: + for Q in Q_params: + for m in m_params: + cfg = [(p,d,q), (P,D,Q,m), t] + models.append(cfg) + return models + +if __name__ == '__main__': + # define dataset + data = [10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0] + print(data) + # data split + n_test = 4 + # model configs + cfg_list = sarima_configs() + # grid search + scores = grid_search(data, cfg_list, n_test) + print('done') + # list top 3 configs + for cfg, error in scores[:3]: + print(cfg, error) +``` + +首先运行该示例打印设计的时间序列数据集。 + +接下来,在评估模型配置及其错误时报告模型配置及其错误,为简洁起见,将其截断。 + +最后,报告前三种配置的配置和错误。我们可以看到,许多模型在这个简单的线性增长的时间序列问题上实现了完美的表现。 + +``` +[10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0] + +... + > Model[[(2, 0, 0), (2, 0, 0, 0), 'ct']] 0.001 + > Model[[(2, 0, 0), (2, 0, 1, 0), 'ct']] 0.000 + > Model[[(2, 0, 1), (0, 0, 0, 0), 'n']] 0.000 + > Model[[(2, 0, 1), (0, 0, 1, 0), 'n']] 0.000 +done + +[(2, 1, 0), (1, 0, 0, 0), 'n'] 0.0 +[(2, 1, 0), (2, 0, 0, 0), 'n'] 0.0 +[(2, 1, 1), (1, 0, 1, 0), 'n'] 0.0 +``` + +现在我们有一个强大的网格搜索 SARIMA 模型超参数框架,让我们在一套标准的单变量时间序列数据集上进行测试。 + +选择数据集用于演示目的;我并不是说 SARIMA 模型是每个数据集的最佳方法;在某些情况下,或许 ETS 或其他更合适的东西。 + +## 案例研究 1:没有趋势或季节性 + +“每日女性分娩”数据集总结了 1959 年美国加利福尼亚州每日女性总分娩数。 + +数据集没有明显的趋势或季节性成分。 + +![Line Plot of the Daily Female Births Dataset](img/dabb24b02643324fb9d69586be439808.jpg) + +每日女性出生数据集的线图 + +您可以从 [DataMarket](https://datamarket.com/data/set/235k/daily-total-female-births-in-california-1959#!ds=235k&display=line) 了解有关数据集的更多信息。 + +直接从这里下载数据集: + +* [每日总数 - 女性分娩.sv](https://raw.githubusercontent.com/jbrownlee/Datasets/master/daily-total-female-births.csv) + +在当前工作目录中使用文件名“ _daily-total-female-births.csv_ ”保存文件。 + +我们可以使用函数 _read_csv()_ 将此数据集作为 Pandas 系列加载。 + +``` +series = read_csv('daily-total-female-births.csv', header=0, index_col=0) +``` + +数据集有一年或 365 个观测值。我们将使用前 200 个进行训练,将剩余的 165 个作为测试集。 + +下面列出了搜索每日女性单变量时间序列预测问题的完整示例网格。 + +``` +# grid search sarima hyperparameters for daily female dataset +from math import sqrt +from multiprocessing import cpu_count +from joblib import Parallel +from joblib import delayed +from warnings import catch_warnings +from warnings import filterwarnings +from statsmodels.tsa.statespace.sarimax import SARIMAX +from sklearn.metrics import mean_squared_error +from pandas import read_csv + +# one-step sarima forecast +def sarima_forecast(history, config): + order, sorder, trend = config + # define model + model = SARIMAX(history, order=order, seasonal_order=sorder, trend=trend, enforce_stationarity=False, enforce_invertibility=False) + # fit model + model_fit = model.fit(disp=False) + # make one step forecast + yhat = model_fit.predict(len(history), len(history)) + return yhat[0] + +# root mean squared error or rmse +def measure_rmse(actual, predicted): + return sqrt(mean_squared_error(actual, predicted)) + +# split a univariate dataset into train/test sets +def train_test_split(data, n_test): + return data[:-n_test], data[-n_test:] + +# walk-forward validation for univariate data +def walk_forward_validation(data, n_test, cfg): + predictions = list() + # split dataset + train, test = train_test_split(data, n_test) + # seed history with training dataset + history = [x for x in train] + # step over each time-step in the test set + for i in range(len(test)): + # fit model and make forecast for history + yhat = sarima_forecast(history, cfg) + # store forecast in list of predictions + predictions.append(yhat) + # add actual observation to history for the next loop + history.append(test[i]) + # estimate prediction error + error = measure_rmse(test, predictions) + return error + +# score a model, return None on failure +def score_model(data, n_test, cfg, debug=False): + result = None + # convert config to a key + key = str(cfg) + # show all warnings and fail on exception if debugging + if debug: + result = walk_forward_validation(data, n_test, cfg) + else: + # one failure during model validation suggests an unstable config + try: + # never show warnings when grid searching, too noisy + with catch_warnings(): + filterwarnings("ignore") + result = walk_forward_validation(data, n_test, cfg) + except: + error = None + # check for an interesting result + if result is not None: + print(' > Model[%s] %.3f' % (key, result)) + return (key, result) + +# grid search configs +def grid_search(data, cfg_list, n_test, parallel=True): + scores = None + if parallel: + # execute configs in parallel + executor = Parallel(n_jobs=cpu_count(), backend='multiprocessing') + tasks = (delayed(score_model)(data, n_test, cfg) for cfg in cfg_list) + scores = executor(tasks) + else: + scores = [score_model(data, n_test, cfg) for cfg in cfg_list] + # remove empty results + scores = [r for r in scores if r[1] != None] + # sort configs by error, asc + scores.sort(key=lambda tup: tup[1]) + return scores + +# create a set of sarima configs to try +def sarima_configs(seasonal=[0]): + models = list() + # define config lists + p_params = [0, 1, 2] + d_params = [0, 1] + q_params = [0, 1, 2] + t_params = ['n','c','t','ct'] + P_params = [0, 1, 2] + D_params = [0, 1] + Q_params = [0, 1, 2] + m_params = seasonal + # create config instances + for p in p_params: + for d in d_params: + for q in q_params: + for t in t_params: + for P in P_params: + for D in D_params: + for Q in Q_params: + for m in m_params: + cfg = [(p,d,q), (P,D,Q,m), t] + models.append(cfg) + return models + +if __name__ == '__main__': + # load dataset + series = read_csv('daily-total-female-births.csv', header=0, index_col=0) + data = series.values + print(data.shape) + # data split + n_test = 165 + # model configs + cfg_list = sarima_configs() + # grid search + scores = grid_search(data, cfg_list, n_test) + print('done') + # list top 3 configs + for cfg, error in scores[:3]: + print(cfg, error) +``` + +在现代硬件上运行该示例可能需要几分钟。 + +在评估模型时打印模型配置和 RMSE 在运行结束时报告前三个模型配置及其错误。 + +我们可以看到最好的结果是大约 6.77 个出生的 RMSE,具有以下配置: + +* **订单** :( 1,0,2) +* **季节性命令** :( 1,0,1,0) +* **趋势参数**:'t'表示线性趋势 + +令人惊讶的是,具有一些季节性元素的配置导致最低的错误。我不会猜到这种配置,可能会坚持使用 ARIMA 模型。 + +``` +... +> Model[[(2, 1, 2), (1, 0, 1, 0), 'ct']] 6.905 +> Model[[(2, 1, 2), (2, 0, 0, 0), 'ct']] 7.031 +> Model[[(2, 1, 2), (2, 0, 1, 0), 'ct']] 6.985 +> Model[[(2, 1, 2), (1, 0, 2, 0), 'ct']] 6.941 +> Model[[(2, 1, 2), (2, 0, 2, 0), 'ct']] 7.056 +done + +[(1, 0, 2), (1, 0, 1, 0), 't'] 6.770349800255089 +[(0, 1, 2), (1, 0, 2, 0), 'ct'] 6.773217122759515 +[(2, 1, 1), (2, 0, 2, 0), 'ct'] 6.886633191752254 +``` + +## 案例研究 2:趋势 + +“洗发水”数据集总结了三年内洗发水的月销售额。 + +数据集包含明显的趋势,但没有明显的季节性成分。 + +![Line Plot of the Monthly Shampoo Sales Dataset](img/334f0b617304a565e1d93a31bdd1c50d.jpg) + +月度洗发水销售数据集的线图 + +您可以从 [DataMarket](https://datamarket.com/data/set/22r0/sales-of-shampoo-over-a-three-year-period#!ds=22r0&display=line) 了解有关数据集的更多信息。 + +直接从这里下载数据集: + +* [shampoo.csv](https://raw.githubusercontent.com/jbrownlee/Datasets/master/shampoo.csv) + +在当前工作目录中使用文件名“shampoo.csv”保存文件。 + +我们可以使用函数 _read_csv()_ 将此数据集作为 Pandas 系列加载。 + +``` +# parse dates +def custom_parser(x): + return datetime.strptime('195'+x, '%Y-%m') + +# load dataset +series = read_csv('shampoo.csv', header=0, index_col=0, date_parser=custom_parser) +``` + +数据集有三年,或 36 个观测值。我们将使用前 24 个用于训练,其余 12 个用作测试集。 + +下面列出了搜索洗发水销售单变量时间序列预测问题的完整示例网格。 + +``` +# grid search sarima hyperparameters for monthly shampoo sales dataset +from math import sqrt +from multiprocessing import cpu_count +from joblib import Parallel +from joblib import delayed +from warnings import catch_warnings +from warnings import filterwarnings +from statsmodels.tsa.statespace.sarimax import SARIMAX +from sklearn.metrics import mean_squared_error +from pandas import read_csv +from pandas import datetime + +# one-step sarima forecast +def sarima_forecast(history, config): + order, sorder, trend = config + # define model + model = SARIMAX(history, order=order, seasonal_order=sorder, trend=trend, enforce_stationarity=False, enforce_invertibility=False) + # fit model + model_fit = model.fit(disp=False) + # make one step forecast + yhat = model_fit.predict(len(history), len(history)) + return yhat[0] + +# root mean squared error or rmse +def measure_rmse(actual, predicted): + return sqrt(mean_squared_error(actual, predicted)) + +# split a univariate dataset into train/test sets +def train_test_split(data, n_test): + return data[:-n_test], data[-n_test:] + +# walk-forward validation for univariate data +def walk_forward_validation(data, n_test, cfg): + predictions = list() + # split dataset + train, test = train_test_split(data, n_test) + # seed history with training dataset + history = [x for x in train] + # step over each time-step in the test set + for i in range(len(test)): + # fit model and make forecast for history + yhat = sarima_forecast(history, cfg) + # store forecast in list of predictions + predictions.append(yhat) + # add actual observation to history for the next loop + history.append(test[i]) + # estimate prediction error + error = measure_rmse(test, predictions) + return error + +# score a model, return None on failure +def score_model(data, n_test, cfg, debug=False): + result = None + # convert config to a key + key = str(cfg) + # show all warnings and fail on exception if debugging + if debug: + result = walk_forward_validation(data, n_test, cfg) + else: + # one failure during model validation suggests an unstable config + try: + # never show warnings when grid searching, too noisy + with catch_warnings(): + filterwarnings("ignore") + result = walk_forward_validation(data, n_test, cfg) + except: + error = None + # check for an interesting result + if result is not None: + print(' > Model[%s] %.3f' % (key, result)) + return (key, result) + +# grid search configs +def grid_search(data, cfg_list, n_test, parallel=True): + scores = None + if parallel: + # execute configs in parallel + executor = Parallel(n_jobs=cpu_count(), backend='multiprocessing') + tasks = (delayed(score_model)(data, n_test, cfg) for cfg in cfg_list) + scores = executor(tasks) + else: + scores = [score_model(data, n_test, cfg) for cfg in cfg_list] + # remove empty results + scores = [r for r in scores if r[1] != None] + # sort configs by error, asc + scores.sort(key=lambda tup: tup[1]) + return scores + +# create a set of sarima configs to try +def sarima_configs(seasonal=[0]): + models = list() + # define config lists + p_params = [0, 1, 2] + d_params = [0, 1] + q_params = [0, 1, 2] + t_params = ['n','c','t','ct'] + P_params = [0, 1, 2] + D_params = [0, 1] + Q_params = [0, 1, 2] + m_params = seasonal + # create config instances + for p in p_params: + for d in d_params: + for q in q_params: + for t in t_params: + for P in P_params: + for D in D_params: + for Q in Q_params: + for m in m_params: + cfg = [(p,d,q), (P,D,Q,m), t] + models.append(cfg) + return models + +# parse dates +def custom_parser(x): + return datetime.strptime('195'+x, '%Y-%m') + +if __name__ == '__main__': + # load dataset + series = read_csv('shampoo.csv', header=0, index_col=0, date_parser=custom_parser) + data = series.values + print(data.shape) + # data split + n_test = 12 + # model configs + cfg_list = sarima_configs() + # grid search + scores = grid_search(data, cfg_list, n_test) + print('done') + # list top 3 configs + for cfg, error in scores[:3]: + print(cfg, error) +``` + +在现代硬件上运行该示例可能需要几分钟。 + +在评估模型时打印模型配置和 RMSE 在运行结束时报告前三个模型配置及其错误。 + +我们可以看到最好的结果是 RMSE 约为 54.76,具有以下配置: + +* **趋势订单** :( 0,1,2) +* **季节性命令** :( 2,0,2,0) +* **趋势参数**:'t'(线性趋势) + +``` +... +> Model[[(2, 1, 2), (1, 0, 1, 0), 'ct']] 68.891 +> Model[[(2, 1, 2), (2, 0, 0, 0), 'ct']] 75.406 +> Model[[(2, 1, 2), (1, 0, 2, 0), 'ct']] 80.908 +> Model[[(2, 1, 2), (2, 0, 1, 0), 'ct']] 78.734 +> Model[[(2, 1, 2), (2, 0, 2, 0), 'ct']] 82.958 +done +[(0, 1, 2), (2, 0, 2, 0), 't'] 54.767582003072874 +[(0, 1, 1), (2, 0, 2, 0), 'ct'] 58.69987083057107 +[(1, 1, 2), (0, 0, 1, 0), 't'] 58.709089340600094 +``` + +## 案例研究 3:季节性 + +“月平均温度”数据集总结了 1920 至 1939 年华氏诺丁汉城堡的月平均气温,以华氏度为单位。 + +数据集具有明显的季节性成分,没有明显的趋势。 + +![Line Plot of the Monthly Mean Temperatures Dataset](img/72a4721b5d8d493844936208df537995.jpg) + +月平均气温数据集的线图 + +您可以从 [DataMarket](https://datamarket.com/data/set/22li/mean-monthly-air-temperature-deg-f-nottingham-castle-1920-1939#!ds=22li&display=line) 了解有关数据集的更多信息。 + +直接从这里下载数据集: + +* [monthly-mean-temp.csv](https://raw.githubusercontent.com/jbrownlee/Datasets/master/monthly-mean-temp.csv) + +在当前工作目录中使用文件名“ _monthly-mean-temp.csv_ ”保存文件。 + +我们可以使用函数 _read_csv()_ 将此数据集作为 Pandas 系列加载。 + +``` +series = read_csv('monthly-mean-temp.csv', header=0, index_col=0) +``` + +数据集有 20 年,或 240 个观测值。我们将数据集修剪为过去五年的数据(60 个观测值),以加快模型评估过程,并使用去年或 12 个观测值来测试集。 + +``` +# trim dataset to 5 years +data = data[-(5*12):] +``` + +季节性成分的周期约为一年,或 12 个观测值。在准备模型配置时,我们将此作为调用 _sarima_configs()_ 函数的季节性时段。 + +``` +# model configs +cfg_list = sarima_configs(seasonal=[0, 12]) +``` + +下面列出了搜索月平均温度时间序列预测问题的完整示例网格。 + +``` +# grid search sarima hyperparameters for monthly mean temp dataset +from math import sqrt +from multiprocessing import cpu_count +from joblib import Parallel +from joblib import delayed +from warnings import catch_warnings +from warnings import filterwarnings +from statsmodels.tsa.statespace.sarimax import SARIMAX +from sklearn.metrics import mean_squared_error +from pandas import read_csv + +# one-step sarima forecast +def sarima_forecast(history, config): + order, sorder, trend = config + # define model + model = SARIMAX(history, order=order, seasonal_order=sorder, trend=trend, enforce_stationarity=False, enforce_invertibility=False) + # fit model + model_fit = model.fit(disp=False) + # make one step forecast + yhat = model_fit.predict(len(history), len(history)) + return yhat[0] + +# root mean squared error or rmse +def measure_rmse(actual, predicted): + return sqrt(mean_squared_error(actual, predicted)) + +# split a univariate dataset into train/test sets +def train_test_split(data, n_test): + return data[:-n_test], data[-n_test:] + +# walk-forward validation for univariate data +def walk_forward_validation(data, n_test, cfg): + predictions = list() + # split dataset + train, test = train_test_split(data, n_test) + # seed history with training dataset + history = [x for x in train] + # step over each time-step in the test set + for i in range(len(test)): + # fit model and make forecast for history + yhat = sarima_forecast(history, cfg) + # store forecast in list of predictions + predictions.append(yhat) + # add actual observation to history for the next loop + history.append(test[i]) + # estimate prediction error + error = measure_rmse(test, predictions) + return error + +# score a model, return None on failure +def score_model(data, n_test, cfg, debug=False): + result = None + # convert config to a key + key = str(cfg) + # show all warnings and fail on exception if debugging + if debug: + result = walk_forward_validation(data, n_test, cfg) + else: + # one failure during model validation suggests an unstable config + try: + # never show warnings when grid searching, too noisy + with catch_warnings(): + filterwarnings("ignore") + result = walk_forward_validation(data, n_test, cfg) + except: + error = None + # check for an interesting result + if result is not None: + print(' > Model[%s] %.3f' % (key, result)) + return (key, result) + +# grid search configs +def grid_search(data, cfg_list, n_test, parallel=True): + scores = None + if parallel: + # execute configs in parallel + executor = Parallel(n_jobs=cpu_count(), backend='multiprocessing') + tasks = (delayed(score_model)(data, n_test, cfg) for cfg in cfg_list) + scores = executor(tasks) + else: + scores = [score_model(data, n_test, cfg) for cfg in cfg_list] + # remove empty results + scores = [r for r in scores if r[1] != None] + # sort configs by error, asc + scores.sort(key=lambda tup: tup[1]) + return scores + +# create a set of sarima configs to try +def sarima_configs(seasonal=[0]): + models = list() + # define config lists + p_params = [0, 1, 2] + d_params = [0, 1] + q_params = [0, 1, 2] + t_params = ['n','c','t','ct'] + P_params = [0, 1, 2] + D_params = [0, 1] + Q_params = [0, 1, 2] + m_params = seasonal + # create config instances + for p in p_params: + for d in d_params: + for q in q_params: + for t in t_params: + for P in P_params: + for D in D_params: + for Q in Q_params: + for m in m_params: + cfg = [(p,d,q), (P,D,Q,m), t] + models.append(cfg) + return models + +if __name__ == '__main__': + # load dataset + series = read_csv('monthly-mean-temp.csv', header=0, index_col=0) + data = series.values + # trim dataset to 5 years + data = data[-(5*12):] + # data split + n_test = 12 + # model configs + cfg_list = sarima_configs(seasonal=[0, 12]) + # grid search + scores = grid_search(data, cfg_list, n_test) + print('done') + # list top 3 configs + for cfg, error in scores[:3]: + print(cfg, error) +``` + +在现代硬件上运行该示例可能需要几分钟。 + +在评估模型时打印模型配置和 RMSE 在运行结束时报告前三个模型配置及其错误。 + +我们可以看到最好的结果是大约 1.5 度的 RMSE,具有以下配置: + +* **趋势订单** :( 0,0,0) +* **季节性命令** :( 1,0,1,12) +* **趋势参数**:'n'(无趋势) + +正如我们所料,该模型没有趋势组件和 12 个月的季节性 ARMA 组件。 + +``` +... +> Model[[(2, 1, 2), (2, 1, 0, 12), 't']] 4.599 +> Model[[(2, 1, 2), (1, 1, 0, 12), 'ct']] 2.477 +> Model[[(2, 1, 2), (2, 0, 0, 12), 'ct']] 2.548 +> Model[[(2, 1, 2), (2, 0, 1, 12), 'ct']] 2.893 +> Model[[(2, 1, 2), (2, 1, 0, 12), 'ct']] 5.404 +done + +[(0, 0, 0), (1, 0, 1, 12), 'n'] 1.5577613610905712 +[(0, 0, 0), (1, 1, 0, 12), 'n'] 1.6469530713847962 +[(0, 0, 0), (2, 0, 0, 12), 'n'] 1.7314448163607488 +``` + +## 案例研究 4:趋势和季节性 + +“月度汽车销售”数据集总结了 1960 年至 1968 年间加拿大魁北克省的月度汽车销量。 + +数据集具有明显的趋势和季节性成分。 + +![Line Plot of the Monthly Car Sales Dataset](img/6ff80f643e5ca7c00df5ce9e8b51cbc5.jpg) + +月度汽车销售数据集的线图 + +您可以从 [DataMarket](https://datamarket.com/data/set/22n4/monthly-car-sales-in-quebec-1960-1968#!ds=22n4&display=line) 了解有关数据集的更多信息。 + +直接从这里下载数据集: + +* [month-car-sales.csv](https://raw.githubusercontent.com/jbrownlee/Datasets/master/monthly-car-sales.csv) + +在当前工作目录中使用文件名“ _monthly-car-sales.csv_ ”保存文件。 + +我们可以使用函数 _read_csv()_ 将此数据集作为 Pandas 系列加载。 + +``` +series = read_csv('monthly-car-sales.csv', header=0, index_col=0) +``` + +数据集有 9 年或 108 个观测值。我们将使用去年或 12 个观测值作为测试集。 + +季节性成分的期限可能是六个月或 12 个月。在准备模型配置时,我们将尝试将两者作为调用 _sarima_configs()_ 函数的季节性时段。 + +``` +# model configs +cfg_list = sarima_configs(seasonal=[0,6,12]) +``` + +下面列出了搜索月度汽车销售时间序列预测问题的完整示例网格。 + +``` +# grid search sarima hyperparameters for monthly car sales dataset +from math import sqrt +from multiprocessing import cpu_count +from joblib import Parallel +from joblib import delayed +from warnings import catch_warnings +from warnings import filterwarnings +from statsmodels.tsa.statespace.sarimax import SARIMAX +from sklearn.metrics import mean_squared_error +from pandas import read_csv + +# one-step sarima forecast +def sarima_forecast(history, config): + order, sorder, trend = config + # define model + model = SARIMAX(history, order=order, seasonal_order=sorder, trend=trend, enforce_stationarity=False, enforce_invertibility=False) + # fit model + model_fit = model.fit(disp=False) + # make one step forecast + yhat = model_fit.predict(len(history), len(history)) + return yhat[0] + +# root mean squared error or rmse +def measure_rmse(actual, predicted): + return sqrt(mean_squared_error(actual, predicted)) + +# split a univariate dataset into train/test sets +def train_test_split(data, n_test): + return data[:-n_test], data[-n_test:] + +# walk-forward validation for univariate data +def walk_forward_validation(data, n_test, cfg): + predictions = list() + # split dataset + train, test = train_test_split(data, n_test) + # seed history with training dataset + history = [x for x in train] + # step over each time-step in the test set + for i in range(len(test)): + # fit model and make forecast for history + yhat = sarima_forecast(history, cfg) + # store forecast in list of predictions + predictions.append(yhat) + # add actual observation to history for the next loop + history.append(test[i]) + # estimate prediction error + error = measure_rmse(test, predictions) + return error + +# score a model, return None on failure +def score_model(data, n_test, cfg, debug=False): + result = None + # convert config to a key + key = str(cfg) + # show all warnings and fail on exception if debugging + if debug: + result = walk_forward_validation(data, n_test, cfg) + else: + # one failure during model validation suggests an unstable config + try: + # never show warnings when grid searching, too noisy + with catch_warnings(): + filterwarnings("ignore") + result = walk_forward_validation(data, n_test, cfg) + except: + error = None + # check for an interesting result + if result is not None: + print(' > Model[%s] %.3f' % (key, result)) + return (key, result) + +# grid search configs +def grid_search(data, cfg_list, n_test, parallel=True): + scores = None + if parallel: + # execute configs in parallel + executor = Parallel(n_jobs=cpu_count(), backend='multiprocessing') + tasks = (delayed(score_model)(data, n_test, cfg) for cfg in cfg_list) + scores = executor(tasks) + else: + scores = [score_model(data, n_test, cfg) for cfg in cfg_list] + # remove empty results + scores = [r for r in scores if r[1] != None] + # sort configs by error, asc + scores.sort(key=lambda tup: tup[1]) + return scores + +# create a set of sarima configs to try +def sarima_configs(seasonal=[0]): + models = list() + # define config lists + p_params = [0, 1, 2] + d_params = [0, 1] + q_params = [0, 1, 2] + t_params = ['n','c','t','ct'] + P_params = [0, 1, 2] + D_params = [0, 1] + Q_params = [0, 1, 2] + m_params = seasonal + # create config instances + for p in p_params: + for d in d_params: + for q in q_params: + for t in t_params: + for P in P_params: + for D in D_params: + for Q in Q_params: + for m in m_params: + cfg = [(p,d,q), (P,D,Q,m), t] + models.append(cfg) + return models + +if __name__ == '__main__': + # load dataset + series = read_csv('monthly-car-sales.csv', header=0, index_col=0) + data = series.values + print(data.shape) + # data split + n_test = 12 + # model configs + cfg_list = sarima_configs(seasonal=[0,6,12]) + # grid search + scores = grid_search(data, cfg_list, n_test) + print('done') + # list top 3 configs + for cfg, error in scores[:3]: + print(cfg, error) +``` + +在现代硬件上运行该示例可能需要几分钟。 + +在评估模型时打印模型配置和 RMSE 在运行结束时报告前三个模型配置及其错误。 + +我们可以看到最好的结果是 RMSE 大约 1,551 销售,具有以下配置: + +* **趋势订单** :( 0,0,0) +* **季节性命令** :( 1,1,0,12) +* **趋势参数**:'t'(线性趋势) + +``` +> Model[[(2, 1, 2), (2, 1, 1, 6), 'ct']] 2246.248 +> Model[[(2, 1, 2), (2, 0, 2, 12), 'ct']] 10710.462 +> Model[[(2, 1, 2), (2, 1, 2, 6), 'ct']] 2183.568 +> Model[[(2, 1, 2), (2, 1, 0, 12), 'ct']] 2105.800 +> Model[[(2, 1, 2), (2, 1, 1, 12), 'ct']] 2330.361 +> Model[[(2, 1, 2), (2, 1, 2, 12), 'ct']] 31580326686.803 +done +[(0, 0, 0), (1, 1, 0, 12), 't'] 1551.8423920342414 +[(0, 0, 0), (2, 1, 1, 12), 'c'] 1557.334614575545 +[(0, 0, 0), (1, 1, 0, 12), 'c'] 1559.3276311282675 +``` + +## 扩展 + +本节列出了一些扩展您可能希望探索的教程的想法。 + +* **数据转换**。更新框架以支持可配置的数据转换,例如规范化和标准化。 +* **地块预测**。更新框架以重新拟合具有最佳配置的模型并预测整个测试数据集,然后将预测与测试集中的实际观察值进行比较。 +* **调整历史数量**。更新框架以调整用于拟合模型的历史数据量(例如,在 10 年最高温度数据的情况下)。 + +如果你探索任何这些扩展,我很想知道。 + +## 进一步阅读 + +如果您希望深入了解,本节将提供有关该主题的更多资源。 + +### 帖子 + +* [如何使用 Python 创建用于时间序列预测的 ARIMA 模型](https://machinelearningmastery.com/arima-for-time-series-forecasting-with-python/) +* [如何使用 Python 网格搜索 ARIMA 模型超参数](https://machinelearningmastery.com/grid-search-arima-hyperparameters-with-python/) +* [自相关和部分自相关的温和介绍](https://machinelearningmastery.com/gentle-introduction-autocorrelation-partial-autocorrelation/) + +### 图书 + +* 第 8 章 ARIMA 模型,[预测:原则和实践](https://amzn.to/2xlJsfV),2013。 +* 第 7 章,非平稳模型, [R](https://amzn.to/2smB9LR) 的入门时间序列,2009。 + +### API + +* [Statsmodels 状态空间方法的时间序列分析](http://www.statsmodels.org/dev/statespace.html) +* [statsmodels.tsa.statespace.sarimax.SARIMAX API](http://www.statsmodels.org/dev/generated/statsmodels.tsa.statespace.sarimax.SARIMAX.html) +* [statsmodels.tsa.statespace.sarimax.SARIMAXResults API](http://www.statsmodels.org/dev/generated/statsmodels.tsa.statespace.sarimax.SARIMAXResults.html) +* [Statsmodels SARIMAX 笔记本](http://www.statsmodels.org/dev/examples/notebooks/generated/statespace_sarimax_stata.html) +* [Joblib:运行 Python 函数作为管道作业](https://pythonhosted.org/joblib/) + +### 用品 + +* [维基百科上的自回归综合移动平均线](https://en.wikipedia.org/wiki/Autoregressive_integrated_moving_average) + +## 摘要 + +在本教程中,您了解了如何开发网格搜索所有 SARIMA 模型超参数的框架,以进行单变量时间序列预测。 + +具体来说,你学到了: + +* 如何使用前向验证从头开始开发网格搜索 SARIMA 模型的框架。 +* 如何为出生日常时间序列数据网格搜索 SARIMA 模型超参数。 +* 如何针对洗发水销售,汽车销售和温度的月度时间序列数据网格搜索 SARIMA 模型超参数。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/how-to-grid-search-triple-exponential-smoothing-for-time-series-forecasting-in-python.md b/docs/dl-ts/how-to-grid-search-triple-exponential-smoothing-for-time-series-forecasting-in-python.md new file mode 100644 index 0000000000000000000000000000000000000000..ff899e16514d3301261b2a0d21a199ba930b3486 --- /dev/null +++ b/docs/dl-ts/how-to-grid-search-triple-exponential-smoothing-for-time-series-forecasting-in-python.md @@ -0,0 +1,1472 @@ +# 如何在 Python 中进行时间序列预测的网格搜索三次指数平滑 + +> 原文: [https://machinelearningmastery.com/how-to-grid-search-triple-exponential-smoothing-for-time-series-forecasting-in-python/](https://machinelearningmastery.com/how-to-grid-search-triple-exponential-smoothing-for-time-series-forecasting-in-python/) + +指数平滑是单变量数据的时间序列预测方法,可以扩展为支持具有系统趋势或季节性成分的数据。 + +通常的做法是使用优化过程来查找模型超参数,这些参数导致指数平滑模型具有给定时间序列数据集的最佳表现。此实践仅适用于模型用于描述水平,趋势和季节性的指数结构的系数。 + +还可以自动优化指数平滑模型的其他超参数,例如是否对趋势和季节性分量建模,如果是,是否使用加法或乘法方法对它们进行建模。 + +在本教程中,您将了解如何开发一个框架,用于网格搜索所有指数平滑模型超参数,以进行单变量时间序列预测。 + +完成本教程后,您将了解: + +* 如何使用前向验证从头开始开发网格搜索 ETS 模型的框架。 +* 如何为女性出生日常时间序列数据网格搜索 ETS 模型超参数。 +* 如何针对洗发水销售,汽车销售和温度的月度时间序列数据网格搜索 ETS 模型超参数。 + +让我们开始吧。 + +* **Oct8 / 2018** :更新了 ETS 模型的拟合,以使用 NumPy 阵列修复乘法趋势/季节性问题(感谢 Amit Amola)。 + +![How to Grid Search Triple Exponential Smoothing for Time Series Forecasting in Python](img/ffdeee56297777b792330716ee3eddd5.jpg) + +如何在 Python 中进行时间序列预测的网格搜索三次指数平滑 +照片由 [john mcsporran](https://www.flickr.com/photos/127130111@N06/16375806988/) 拍摄,保留一些权利。 + +## 教程概述 + +本教程分为六个部分;他们是: + +1. 时间序列预测的指数平滑 +2. 开发网格搜索框架 +3. 案例研究 1:没有趋势或季节性 +4. 案例研究 2:趋势 +5. 案例研究 3:季节性 +6. 案例研究 4:趋势和季节性 + +## 时间序列预测的指数平滑 + +指数平滑是单变量数据的时间序列预测方法。 + +像 Box-Jenkins ARIMA 系列方法这样的时间序列方法开发了一种模型,其中预测是近期过去观察或滞后的加权线性和。 + +指数平滑预测方法的类似之处在于预测是过去观察的加权和,但模型明确地使用指数减小的权重用于过去的观察。 + +具体而言,过去的观察以几何减小的比率加权。 + +> 使用指数平滑方法产生的预测是过去观测的加权平均值,随着观测结果的变化,权重呈指数衰减。换句话说,观察越近,相关重量越高。 + +- 第 171 页,[预测:原则和实践](https://amzn.to/2xlJsfV),2013。 + +指数平滑方法可以被视为对等,并且是流行的 Box-Jenkins ARIMA 类时间序列预测方法的替代方法。 + +总的来说,这些方法有时被称为 ETS 模型,参考 _ 错误 _,_ 趋势 _ 和 _ 季节性 _ 的显式建模。 + +指数平滑有三种类型;他们是: + +* **单指数平滑**或 SES,用于没有趋势或季节性的单变量数据。 +* **双指数平滑**用于支持趋势的单变量数据。 +* **三重指数平滑**,或 Holt-Winters 指数平滑,支持趋势和季节性。 + +三指数平滑模型通过趋势性质(加法,乘法或无)的性质和季节性的性质(加法,乘法或无)来表示单指数和双指数平滑,以及任何阻尼趋势。 + +## 开发网格搜索框架 + +在本节中,我们将为给定的单变量时间序列预测问题开发一个网格搜索指数平滑模型超参数的框架。 + +我们将使用 statsmodels 库提供的 [Holt-Winters 指数平滑](http://www.statsmodels.org/dev/generated/statsmodels.tsa.holtwinters.ExponentialSmoothing.html)的实现。 + +该模型具有超参数,可控制为系列,趋势和季节性执行的指数的性质,具体为: + +* **smoothing_level** ( _alpha_ ):该级别的平滑系数。 +* **smoothing_slope** ( _beta_ ):趋势的平滑系数。 +* **smoothing_seasonal** ( _gamma_ ):季节性成分的平滑系数。 +* **damping_slope** ( _phi_ ):阻尼趋势的系数。 + +在定义模型时,可以指定所有这四个超参数。如果未指定它们,库将自动调整模型并找到这些超参数的最佳值(例如 _optimized = True_ )。 + +还有其他超参数,模型不会自动调整您可能想要指定的;他们是: + +* **趋势**:趋势分量的类型,作为加法的“_ 加 _”或乘法的“ _mul_ ”。可以通过将趋势设置为“无”来禁用对趋势建模。 +* **阻尼**:趋势分量是否应该被阻尼,无论是真还是假。 +* **季节性**:季节性成分的类型,为“_ 添加 _”为添加剂或“ _mul_ ”为乘法。可以通过将季节性组件设置为“无”来禁用它。 +* **seasonal_periods** :季节性时间段内的时间步数,例如在一年一度的季节性结构中 12 个月 12 个月。 +* **use_boxcox** :是否执行系列的幂变换(True / False)或指定变换的 lambda。 + +如果您对问题了解得足以指定其中一个或多个参数,则应指定它们。如果没有,您可以尝试网格搜索这些参数。 + +我们可以通过定义一个适合具有给定配置的模型的函数来开始,并进行一步预测。 + +下面的 _exp_smoothing_forecast()_ 实现了这种行为。 + +该函数采用连续先前观察的数组或列表以及用于配置模型的配置参数列表。 + +配置参数依次为:趋势类型,阻尼类型,季节性类型,季节周期,是否使用 Box-Cox 变换,以及在拟合模型时是否消除偏差。 + +``` +# one-step Holt Winter's Exponential Smoothing forecast +def exp_smoothing_forecast(history, config): + t,d,s,p,b,r = config + # define model model + history = array(history) + model = ExponentialSmoothing(history, trend=t, damped=d, seasonal=s, seasonal_periods=p) + # fit model + model_fit = model.fit(optimized=True, use_boxcox=b, remove_bias=r) + # make one step forecast + yhat = model_fit.predict(len(history), len(history)) + return yhat[0] +``` + +接下来,我们需要建立一些函数,通过[前向验证](https://machinelearningmastery.com/backtest-machine-learning-models-time-series-forecasting/)重复拟合和评估模型,包括将数据集拆分为火车和测试集并评估一步预测。 + +我们可以使用给定指定大小的分割的切片来分割列表或 NumPy 数据数组,例如,从测试集中的数据中使用的时间步数。 + +下面的 _train_test_split()_ 函数为提供的数据集和要在测试集中使用的指定数量的时间步骤实现此功能。 + +``` +# split a univariate dataset into train/test sets +def train_test_split(data, n_test): + return data[:-n_test], data[-n_test:] +``` + +在对测试数据集中的每个步骤进行预测之后,需要将它们与测试集进行比较以计算错误分数。 + +时间序列预测有许多流行的错误分数。在这种情况下,我们将使用均方根误差(RMSE),但您可以将其更改为您的首选度量,例如 MAPE,MAE 等 + +下面的 _measure_rmse()_ 函数将根据实际(测试集)和预测值列表计算 RMSE。 + +``` +# root mean squared error or rmse +def measure_rmse(actual, predicted): + return sqrt(mean_squared_error(actual, predicted)) +``` + +我们现在可以实现前向验证方案。这是评估尊重观测时间顺序的时间序列预测模型的标准方法。 + +首先,使用 _train_test_split()_ 函数将提供的单变量时间序列数据集分成训练集和测试集。然后枚举测试集中的观察数。对于每一个,我们在所有历史记录中拟合模型并进行一步预测。然后将对时间步骤的真实观察添加到历史中,并重复该过程。调用 _exp_smoothing_forecast()_ 函数以适合模型并进行预测。最后,通过调用 _measure_rmse()_ 函数,将所有一步预测与实际测试集进行比较,计算错误分数。 + +下面的 _walk_forward_validation()_ 函数实现了这一点,采用了单变量时间序列,在测试集中使用的一些时间步骤,以及一组模型配置。 + +``` +# walk-forward validation for univariate data +def walk_forward_validation(data, n_test, cfg): + predictions = list() + # split dataset + train, test = train_test_split(data, n_test) + # seed history with training dataset + history = [x for x in train] + # step over each time-step in the test set + for i in range(len(test)): + # fit model and make forecast for history + yhat = exp_smoothing_forecast(history, cfg) + # store forecast in list of predictions + predictions.append(yhat) + # add actual observation to history for the next loop + history.append(test[i]) + # estimate prediction error + error = measure_rmse(test, predictions) + return error +``` + +如果您对进行多步预测感兴趣,可以在 _exp_smoothing_forecast()_ 函数中更改 _predict()_ 的调用,并更改 _ 中的错误计算 measure_rmse()_ 功能。 + +我们可以使用不同的模型配置列表重复调用 _walk_forward_validation()_。 + +一个可能的问题是,可能不会为模型调用模型配置的某些组合,并且会抛出异常,例如,指定数据中季节性结构的一些但不是所有方面。 + +此外,某些型号还可能会对某些数据发出警告,例如:来自 statsmodels 库调用的线性代数库。 + +我们可以在网格搜索期间捕获异常并忽略警告,方法是将所有调用包含在 _walk_forward_validation()_ 中,并使用 try-except 和 block 来忽略警告。我们还可以添加调试支持来禁用这些保护,以防我们想要查看实际情况。最后,如果确实发生错误,我们可以返回 _ 无 _ 结果;否则,我们可以打印一些关于评估的每个模型的技能的信息。当评估大量模型时,这很有用。 + +下面的 _score_model()_ 函数实现了这个并返回(键和结果)的元组,其中键是测试模型配置的字符串版本。 + +``` +# score a model, return None on failure +def score_model(data, n_test, cfg, debug=False): + result = None + # convert config to a key + key = str(cfg) + # show all warnings and fail on exception if debugging + if debug: + result = walk_forward_validation(data, n_test, cfg) + else: + # one failure during model validation suggests an unstable config + try: + # never show warnings when grid searching, too noisy + with catch_warnings(): + filterwarnings("ignore") + result = walk_forward_validation(data, n_test, cfg) + except: + error = None + # check for an interesting result + if result is not None: + print(' > Model[%s] %.3f' % (key, result)) + return (key, result) +``` + +接下来,我们需要一个循环来测试不同模型配置的列表。 + +这是驱动网格搜索过程的主要功能,并将为每个模型配置调用 _score_model()_ 函数。 + +通过并行评估模型配置,我们可以大大加快网格搜索过程。一种方法是使用 [Joblib 库](https://pythonhosted.org/joblib/)。 + +我们可以定义一个 _Parallel_ 对象,其中包含要使用的核心数,并将其设置为硬件中检测到的 CPU 核心数。 + +``` +executor = Parallel(n_jobs=cpu_count(), backend='multiprocessing') +``` + +然后我们可以创建一个并行执行的任务列表,这将是对我们拥有的每个模型配置的 _score_model()_ 函数的一次调用。 + +``` +tasks = (delayed(score_model)(data, n_test, cfg) for cfg in cfg_list) +``` + +最后,我们可以使用 _Parallel_ 对象并行执行任务列表。 + +``` +scores = executor(tasks) +``` + +而已。 + +我们还可以提供评估所有模型配置的非并行版本,以防我们想要调试某些内容。 + +``` +scores = [score_model(data, n_test, cfg) for cfg in cfg_list] +``` + +评估配置列表的结果将是元组列表,每个元组都有一个名称,该名称总结了特定的模型配置,并且使用该配置评估的模型的错误为 RMSE,如果出现错误则为 None。 + +我们可以使用“无”过滤掉所有分数。 + +``` +scores = [r for r in scores if r[1] != None] +``` + +然后我们可以按照升序排列列表中的所有元组(最好是第一个),然后返回此分数列表以供审阅。 + +给定单变量时间序列数据集,模型配置列表(列表列表)以及在测试集中使用的时间步数,下面的 _grid_search()_ 函数实现此行为。可选的并行参数允许对所有内核的模型进行开启或关闭调整,默认情况下处于打开状态。 + +``` +# grid search configs +def grid_search(data, cfg_list, n_test, parallel=True): + scores = None + if parallel: + # execute configs in parallel + executor = Parallel(n_jobs=cpu_count(), backend='multiprocessing') + tasks = (delayed(score_model)(data, n_test, cfg) for cfg in cfg_list) + scores = executor(tasks) + else: + scores = [score_model(data, n_test, cfg) for cfg in cfg_list] + # remove empty results + scores = [r for r in scores if r[1] != None] + # sort configs by error, asc + scores.sort(key=lambda tup: tup[1]) + return scores +``` + +我们差不多完成了。 + +剩下要做的唯一事情是定义模型配置列表以尝试数据集。 + +我们可以一般地定义它。我们可能想要指定的唯一参数是系列中季节性组件的周期性(如果存在)。默认情况下,我们假设没有季节性组件。 + +下面的 _exp_smoothing_configs()_ 函数将创建要评估的模型配置列表。 + +可以指定季节性时段的可选列表,您甚至可以更改该功能以指定您可能了解的有关时间序列的其他元素。 + +从理论上讲,有 72 种可能的模型配置需要评估,但在实践中,许多模型配置无效并会导致我们将陷入和忽略的错误。 + +``` +# create a set of exponential smoothing configs to try +def exp_smoothing_configs(seasonal=[None]): + models = list() + # define config lists + t_params = ['add', 'mul', None] + d_params = [True, False] + s_params = ['add', 'mul', None] + p_params = seasonal + b_params = [True, False] + r_params = [True, False] + # create config instances + for t in t_params: + for d in d_params: + for s in s_params: + for p in p_params: + for b in b_params: + for r in r_params: + cfg = [t,d,s,p,b,r] + models.append(cfg) + return models +``` + +我们现在有一个网格搜索三重指数平滑模型超参数的框架,通过一步前进验证。 + +它是通用的,适用于作为列表或 NumPy 数组提供的任何内存中单变量时间序列。 + +我们可以通过在人为设计的 10 步数据集上进行测试来确保所有部分协同工作。 + +下面列出了完整的示例。 + +``` +# grid search holt winter's exponential smoothing +from math import sqrt +from multiprocessing import cpu_count +from joblib import Parallel +from joblib import delayed +from warnings import catch_warnings +from warnings import filterwarnings +from statsmodels.tsa.holtwinters import ExponentialSmoothing +from sklearn.metrics import mean_squared_error +from numpy import array + +# one-step Holt Winter’s Exponential Smoothing forecast +def exp_smoothing_forecast(history, config): + t,d,s,p,b,r = config + # define model + history = array(history) + model = ExponentialSmoothing(history, trend=t, damped=d, seasonal=s, seasonal_periods=p) + # fit model + model_fit = model.fit(optimized=True, use_boxcox=b, remove_bias=r) + # make one step forecast + yhat = model_fit.predict(len(history), len(history)) + return yhat[0] + +# root mean squared error or rmse +def measure_rmse(actual, predicted): + return sqrt(mean_squared_error(actual, predicted)) + +# split a univariate dataset into train/test sets +def train_test_split(data, n_test): + return data[:-n_test], data[-n_test:] + +# walk-forward validation for univariate data +def walk_forward_validation(data, n_test, cfg): + predictions = list() + # split dataset + train, test = train_test_split(data, n_test) + # seed history with training dataset + history = [x for x in train] + # step over each time-step in the test set + for i in range(len(test)): + # fit model and make forecast for history + yhat = exp_smoothing_forecast(history, cfg) + # store forecast in list of predictions + predictions.append(yhat) + # add actual observation to history for the next loop + history.append(test[i]) + # estimate prediction error + error = measure_rmse(test, predictions) + return error + +# score a model, return None on failure +def score_model(data, n_test, cfg, debug=False): + result = None + # convert config to a key + key = str(cfg) + # show all warnings and fail on exception if debugging + if debug: + result = walk_forward_validation(data, n_test, cfg) + else: + # one failure during model validation suggests an unstable config + try: + # never show warnings when grid searching, too noisy + with catch_warnings(): + filterwarnings("ignore") + result = walk_forward_validation(data, n_test, cfg) + except: + error = None + # check for an interesting result + if result is not None: + print(' > Model[%s] %.3f' % (key, result)) + return (key, result) + +# grid search configs +def grid_search(data, cfg_list, n_test, parallel=True): + scores = None + if parallel: + # execute configs in parallel + executor = Parallel(n_jobs=cpu_count(), backend='multiprocessing') + tasks = (delayed(score_model)(data, n_test, cfg) for cfg in cfg_list) + scores = executor(tasks) + else: + scores = [score_model(data, n_test, cfg) for cfg in cfg_list] + # remove empty results + scores = [r for r in scores if r[1] != None] + # sort configs by error, asc + scores.sort(key=lambda tup: tup[1]) + return scores + +# create a set of exponential smoothing configs to try +def exp_smoothing_configs(seasonal=[None]): + models = list() + # define config lists + t_params = ['add', 'mul', None] + d_params = [True, False] + s_params = ['add', 'mul', None] + p_params = seasonal + b_params = [True, False] + r_params = [True, False] + # create config instances + for t in t_params: + for d in d_params: + for s in s_params: + for p in p_params: + for b in b_params: + for r in r_params: + cfg = [t,d,s,p,b,r] + models.append(cfg) + return models + +if __name__ == '__main__': + # define dataset + data = [10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0] + print(data) + # data split + n_test = 4 + # model configs + cfg_list = exp_smoothing_configs() + # grid search + scores = grid_search(data, cfg_list, n_test) + print('done') + # list top 3 configs + for cfg, error in scores[:3]: + print(cfg, error) +``` + +首先运行该示例打印设计的时间序列数据集。 + +接下来,在评估模型配置及其错误时报告它们。 + +最后,报告前三种配置的配置和错误。 + +``` +[10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0] + + > Model[[None, False, None, None, True, True]] 1.380 + > Model[[None, False, None, None, True, False]] 10.000 + > Model[[None, False, None, None, False, True]] 2.563 + > Model[[None, False, None, None, False, False]] 10.000 +done + +[None, False, None, None, True, True] 1.379824445857423 +[None, False, None, None, False, True] 2.5628662672606612 +[None, False, None, None, False, False] 10.0 +``` + +我们不报告模型本身优化的模型参数。假设您可以通过指定更广泛的超参数来再次获得相同的结果,并允许库找到相同的内部参数。 + +您可以通过重新配置具有相同配置的独立模型并在模型拟合上打印' _params_ '属性的内容来访问这些内部参数;例如: + +``` +print(model_fit.params) +``` + +现在我们有了一个强大的网格搜索框架来搜索 ETS 模型超参数,让我们在一套标准的单变量时间序列数据集上进行测试。 + +选择数据集用于演示目的;我并不是说 ETS 模型是每个数据集的最佳方法,在某些情况下,SARIMA 或其他东西可能更合适。 + +## 案例研究 1:没有趋势或季节性 + +“每日女性分娩”数据集总结了 1959 年美国加利福尼亚州每日女性总分娩数。 + +数据集没有明显的趋势或季节性成分。 + +![Line Plot of the Daily Female Births Dataset](img/dabb24b02643324fb9d69586be439808.jpg) + +每日女性出生数据集的线图 + +您可以从 [DataMarket](https://datamarket.com/data/set/235k/daily-total-female-births-in-california-1959#!ds=235k&display=line) 了解有关数据集的更多信息。 + +直接从这里下载数据集: + +* [每日总数 - 女性分娩.sv](https://raw.githubusercontent.com/jbrownlee/Datasets/master/daily-total-female-births.csv) + +在当前工作目录中使用文件名“ _daily-total-female-births.csv_ ”保存文件。 + +我们可以使用函数 _read_csv()_ 将此数据集作为 Pandas 系列加载。 + +``` +series = read_csv('daily-total-female-births.csv', header=0, index_col=0) +``` + +数据集有一年或 365 个观测值。我们将使用前 200 个进行训练,将剩余的 165 个作为测试集。 + +下面列出了搜索每日女性单变量时间序列预测问题的完整示例网格。 + +``` +# grid search ets models for daily female births +from math import sqrt +from multiprocessing import cpu_count +from joblib import Parallel +from joblib import delayed +from warnings import catch_warnings +from warnings import filterwarnings +from statsmodels.tsa.holtwinters import ExponentialSmoothing +from sklearn.metrics import mean_squared_error +from pandas import read_csv +from numpy import array + +# one-step Holt Winter’s Exponential Smoothing forecast +def exp_smoothing_forecast(history, config): + t,d,s,p,b,r = config + # define model + history = array(history) + model = ExponentialSmoothing(history, trend=t, damped=d, seasonal=s, seasonal_periods=p) + # fit model + model_fit = model.fit(optimized=True, use_boxcox=b, remove_bias=r) + # make one step forecast + yhat = model_fit.predict(len(history), len(history)) + return yhat[0] + +# root mean squared error or rmse +def measure_rmse(actual, predicted): + return sqrt(mean_squared_error(actual, predicted)) + +# split a univariate dataset into train/test sets +def train_test_split(data, n_test): + return data[:-n_test], data[-n_test:] + +# walk-forward validation for univariate data +def walk_forward_validation(data, n_test, cfg): + predictions = list() + # split dataset + train, test = train_test_split(data, n_test) + # seed history with training dataset + history = [x for x in train] + # step over each time-step in the test set + for i in range(len(test)): + # fit model and make forecast for history + yhat = exp_smoothing_forecast(history, cfg) + # store forecast in list of predictions + predictions.append(yhat) + # add actual observation to history for the next loop + history.append(test[i]) + # estimate prediction error + error = measure_rmse(test, predictions) + return error + +# score a model, return None on failure +def score_model(data, n_test, cfg, debug=False): + result = None + # convert config to a key + key = str(cfg) + # show all warnings and fail on exception if debugging + if debug: + result = walk_forward_validation(data, n_test, cfg) + else: + # one failure during model validation suggests an unstable config + try: + # never show warnings when grid searching, too noisy + with catch_warnings(): + filterwarnings("ignore") + result = walk_forward_validation(data, n_test, cfg) + except: + error = None + # check for an interesting result + if result is not None: + print(' > Model[%s] %.3f' % (key, result)) + return (key, result) + +# grid search configs +def grid_search(data, cfg_list, n_test, parallel=True): + scores = None + if parallel: + # execute configs in parallel + executor = Parallel(n_jobs=cpu_count(), backend='multiprocessing') + tasks = (delayed(score_model)(data, n_test, cfg) for cfg in cfg_list) + scores = executor(tasks) + else: + scores = [score_model(data, n_test, cfg) for cfg in cfg_list] + # remove empty results + scores = [r for r in scores if r[1] != None] + # sort configs by error, asc + scores.sort(key=lambda tup: tup[1]) + return scores + +# create a set of exponential smoothing configs to try +def exp_smoothing_configs(seasonal=[None]): + models = list() + # define config lists + t_params = ['add', 'mul', None] + d_params = [True, False] + s_params = ['add', 'mul', None] + p_params = seasonal + b_params = [True, False] + r_params = [True, False] + # create config instances + for t in t_params: + for d in d_params: + for s in s_params: + for p in p_params: + for b in b_params: + for r in r_params: + cfg = [t,d,s,p,b,r] + models.append(cfg) + return models + +if __name__ == '__main__': + # load dataset + series = read_csv('daily-total-female-births.csv', header=0, index_col=0) + data = series.values + # data split + n_test = 165 + # model configs + cfg_list = exp_smoothing_configs() + # grid search + scores = grid_search(data[:,0], cfg_list, n_test) + print('done') + # list top 3 configs + for cfg, error in scores[:3]: + print(cfg, error) +``` + +运行该示例可能需要几分钟,因为在现代硬件上安装每个 ETS 模型大约需要一分钟。 + +在评估模型时打印模型配置和 RMSE 在运行结束时报告前三个模型配置及其错误。 + +我们可以看到最好的结果是大约 6.96 个出生的 RMSE,具有以下配置: + +* **趋势**:乘法 +* **阻尼**:错误 +* **季节性**:无 +* **季节性时期**:无 +* **Box-Cox 变换**:是的 +* **删除偏差**:是的 + +令人惊讶的是,假设乘法趋势的模型比不具有乘法趋势的模型表现得更好。 + +除非我们抛弃假设和网格搜索模型,否则我们不会知道情况就是这样。 + +``` + > Model[['add', False, None, None, True, True]] 7.081 + > Model[['add', False, None, None, True, False]] 7.113 + > Model[['add', False, None, None, False, True]] 7.112 + > Model[['add', False, None, None, False, False]] 7.115 + > Model[['add', True, None, None, True, True]] 7.118 + > Model[['add', True, None, None, True, False]] 7.170 + > Model[['add', True, None, None, False, True]] 7.113 + > Model[['add', True, None, None, False, False]] 7.126 + > Model[['mul', True, None, None, True, True]] 7.118 + > Model[['mul', True, None, None, True, False]] 7.170 + > Model[['mul', True, None, None, False, True]] 7.113 + > Model[['mul', True, None, None, False, False]] 7.126 + > Model[['mul', False, None, None, True, True]] 6.961 + > Model[['mul', False, None, None, True, False]] 6.985 + > Model[[None, False, None, None, True, True]] 7.169 + > Model[[None, False, None, None, True, False]] 7.212 + > Model[[None, False, None, None, False, True]] 7.117 + > Model[[None, False, None, None, False, False]] 7.126 +done + +['mul', False, None, None, True, True] 6.960703917145126 +['mul', False, None, None, True, False] 6.984513598720297 +['add', False, None, None, True, True] 7.081359856193836 +``` + +## 案例研究 2:趋势 + +“洗发水”数据集总结了三年内洗发水的月销售额。 + +数据集包含明显的趋势,但没有明显的季节性成分。 + +![Line Plot of the Monthly Shampoo Sales Dataset](img/334f0b617304a565e1d93a31bdd1c50d.jpg) + +月度洗发水销售数据集的线图 + +您可以从 [DataMarket](https://datamarket.com/data/set/22r0/sales-of-shampoo-over-a-three-year-period#!ds=22r0&display=line) 了解有关数据集的更多信息。 + +直接从这里下载数据集: + +* [shampoo.csv](https://raw.githubusercontent.com/jbrownlee/Datasets/master/shampoo.csv) + +在当前工作目录中使用文件名“shampoo.csv”保存文件。 + +我们可以使用函数 _read_csv()_ 将此数据集作为 Pandas 系列加载。 + +``` +# parse dates +def custom_parser(x): + return datetime.strptime('195'+x, '%Y-%m') + +# load dataset +series = read_csv('shampoo.csv', header=0, index_col=0, date_parser=custom_parser) +``` + +数据集有三年,或 36 个观测值。我们将使用前 24 个用于训练,其余 12 个用作测试集。 + +下面列出了搜索洗发水销售单变量时间序列预测问题的完整示例网格。 + +``` +# grid search ets models for monthly shampoo sales +from math import sqrt +from multiprocessing import cpu_count +from joblib import Parallel +from joblib import delayed +from warnings import catch_warnings +from warnings import filterwarnings +from statsmodels.tsa.holtwinters import ExponentialSmoothing +from sklearn.metrics import mean_squared_error +from pandas import read_csv +from numpy import array + +# one-step Holt Winter’s Exponential Smoothing forecast +def exp_smoothing_forecast(history, config): + t,d,s,p,b,r = config + # define model + history = array(history) + model = ExponentialSmoothing(history, trend=t, damped=d, seasonal=s, seasonal_periods=p) + # fit model + model_fit = model.fit(optimized=True, use_boxcox=b, remove_bias=r) + # make one step forecast + yhat = model_fit.predict(len(history), len(history)) + return yhat[0] + +# root mean squared error or rmse +def measure_rmse(actual, predicted): + return sqrt(mean_squared_error(actual, predicted)) + +# split a univariate dataset into train/test sets +def train_test_split(data, n_test): + return data[:-n_test], data[-n_test:] + +# walk-forward validation for univariate data +def walk_forward_validation(data, n_test, cfg): + predictions = list() + # split dataset + train, test = train_test_split(data, n_test) + # seed history with training dataset + history = [x for x in train] + # step over each time-step in the test set + for i in range(len(test)): + # fit model and make forecast for history + yhat = exp_smoothing_forecast(history, cfg) + # store forecast in list of predictions + predictions.append(yhat) + # add actual observation to history for the next loop + history.append(test[i]) + # estimate prediction error + error = measure_rmse(test, predictions) + return error + +# score a model, return None on failure +def score_model(data, n_test, cfg, debug=False): + result = None + # convert config to a key + key = str(cfg) + # show all warnings and fail on exception if debugging + if debug: + result = walk_forward_validation(data, n_test, cfg) + else: + # one failure during model validation suggests an unstable config + try: + # never show warnings when grid searching, too noisy + with catch_warnings(): + filterwarnings("ignore") + result = walk_forward_validation(data, n_test, cfg) + except: + error = None + # check for an interesting result + if result is not None: + print(' > Model[%s] %.3f' % (key, result)) + return (key, result) + +# grid search configs +def grid_search(data, cfg_list, n_test, parallel=True): + scores = None + if parallel: + # execute configs in parallel + executor = Parallel(n_jobs=cpu_count(), backend='multiprocessing') + tasks = (delayed(score_model)(data, n_test, cfg) for cfg in cfg_list) + scores = executor(tasks) + else: + scores = [score_model(data, n_test, cfg) for cfg in cfg_list] + # remove empty results + scores = [r for r in scores if r[1] != None] + # sort configs by error, asc + scores.sort(key=lambda tup: tup[1]) + return scores + +# create a set of exponential smoothing configs to try +def exp_smoothing_configs(seasonal=[None]): + models = list() + # define config lists + t_params = ['add', 'mul', None] + d_params = [True, False] + s_params = ['add', 'mul', None] + p_params = seasonal + b_params = [True, False] + r_params = [True, False] + # create config instances + for t in t_params: + for d in d_params: + for s in s_params: + for p in p_params: + for b in b_params: + for r in r_params: + cfg = [t,d,s,p,b,r] + models.append(cfg) + return models + +if __name__ == '__main__': + # load dataset + series = read_csv('shampoo.csv', header=0, index_col=0) + data = series.values + # data split + n_test = 12 + # model configs + cfg_list = exp_smoothing_configs() + # grid search + scores = grid_search(data[:,0], cfg_list, n_test) + print('done') + # list top 3 configs + for cfg, error in scores[:3]: + print(cfg, error) +``` + +鉴于存在少量观察,运行该示例很快。 + +在评估模型时打印模型配置和 RMSE。在运行结束时报告前三个模型配置及其错误。 + +我们可以看到最好的结果是 RMSE 约为 83.74 销售,具有以下配置: + +* **趋势**:乘法 +* **阻尼**:错误 +* **季节性**:无 +* **季节性时期**:无 +* **Box-Cox 变换**:错误 +* **删除偏差**:错误 + +``` + > Model[['add', False, None, None, False, True]] 106.431 + > Model[['add', False, None, None, False, False]] 104.874 + > Model[['add', True, None, None, False, False]] 103.069 + > Model[['add', True, None, None, False, True]] 97.918 + > Model[['mul', True, None, None, False, True]] 95.337 + > Model[['mul', True, None, None, False, False]] 102.152 + > Model[['mul', False, None, None, False, True]] 86.406 + > Model[['mul', False, None, None, False, False]] 83.747 + > Model[[None, False, None, None, False, True]] 99.416 + > Model[[None, False, None, None, False, False]] 108.031 +done + +['mul', False, None, None, False, False] 83.74666940175238 +['mul', False, None, None, False, True] 86.40648953786152 +['mul', True, None, None, False, True] 95.33737598817238 +``` + +## 案例研究 3:季节性 + +“月平均温度”数据集总结了 1920 至 1939 年华氏诺丁汉城堡的月平均气温,以华氏度为单位。 + +数据集具有明显的季节性成分,没有明显的趋势。 + +![Line Plot of the Monthly Mean Temperatures Dataset](img/72a4721b5d8d493844936208df537995.jpg) + +月平均气温数据集的线图 + +您可以从 [DataMarket](https://datamarket.com/data/set/22li/mean-monthly-air-temperature-deg-f-nottingham-castle-1920-1939#!ds=22li&display=line) 了解有关数据集的更多信息。 + +直接从这里下载数据集: + +* [monthly-mean-temp.csv](https://raw.githubusercontent.com/jbrownlee/Datasets/master/monthly-mean-temp.csv) + +在当前工作目录中使用文件名“monthly-mean-temp.csv”保存文件。 + +我们可以使用函数 _read_csv()_ 将此数据集作为 Pandas 系列加载。 + +``` +series = read_csv('monthly-mean-temp.csv', header=0, index_col=0) +``` + +数据集有 20 年,或 240 个观测值。 + +我们将数据集修剪为过去五年的数据(60 个观测值),以加快模型评估过程,并使用去年或 12 个观测值来测试集。 + +``` +# trim dataset to 5 years +data = data[-(5*12):] +``` + +季节性成分的周期约为一年,或 12 个观测值。 + +在准备模型配置时,我们将此作为调用 _exp_smoothing_configs()_ 函数的季节性时段。 + +``` +# model configs +cfg_list = exp_smoothing_configs(seasonal=[0, 12]) +``` + +下面列出了搜索月平均温度时间序列预测问题的完整示例网格。 + +``` +# grid search ets hyperparameters for monthly mean temp dataset +from math import sqrt +from multiprocessing import cpu_count +from joblib import Parallel +from joblib import delayed +from warnings import catch_warnings +from warnings import filterwarnings +from statsmodels.tsa.holtwinters import ExponentialSmoothing +from sklearn.metrics import mean_squared_error +from pandas import read_csv +from numpy import array + +# one-step Holt Winter’s Exponential Smoothing forecast +def exp_smoothing_forecast(history, config): + t,d,s,p,b,r = config + # define model + history = array(history) + model = ExponentialSmoothing(history, trend=t, damped=d, seasonal=s, seasonal_periods=p) + # fit model + model_fit = model.fit(optimized=True, use_boxcox=b, remove_bias=r) + # make one step forecast + yhat = model_fit.predict(len(history), len(history)) + return yhat[0] + +# root mean squared error or rmse +def measure_rmse(actual, predicted): + return sqrt(mean_squared_error(actual, predicted)) + +# split a univariate dataset into train/test sets +def train_test_split(data, n_test): + return data[:-n_test], data[-n_test:] + +# walk-forward validation for univariate data +def walk_forward_validation(data, n_test, cfg): + predictions = list() + # split dataset + train, test = train_test_split(data, n_test) + # seed history with training dataset + history = [x for x in train] + # step over each time-step in the test set + for i in range(len(test)): + # fit model and make forecast for history + yhat = exp_smoothing_forecast(history, cfg) + # store forecast in list of predictions + predictions.append(yhat) + # add actual observation to history for the next loop + history.append(test[i]) + # estimate prediction error + error = measure_rmse(test, predictions) + return error + +# score a model, return None on failure +def score_model(data, n_test, cfg, debug=False): + result = None + # convert config to a key + key = str(cfg) + # show all warnings and fail on exception if debugging + if debug: + result = walk_forward_validation(data, n_test, cfg) + else: + # one failure during model validation suggests an unstable config + try: + # never show warnings when grid searching, too noisy + with catch_warnings(): + filterwarnings("ignore") + result = walk_forward_validation(data, n_test, cfg) + except: + error = None + # check for an interesting result + if result is not None: + print(' > Model[%s] %.3f' % (key, result)) + return (key, result) + +# grid search configs +def grid_search(data, cfg_list, n_test, parallel=True): + scores = None + if parallel: + # execute configs in parallel + executor = Parallel(n_jobs=cpu_count(), backend='multiprocessing') + tasks = (delayed(score_model)(data, n_test, cfg) for cfg in cfg_list) + scores = executor(tasks) + else: + scores = [score_model(data, n_test, cfg) for cfg in cfg_list] + # remove empty results + scores = [r for r in scores if r[1] != None] + # sort configs by error, asc + scores.sort(key=lambda tup: tup[1]) + return scores + +# create a set of exponential smoothing configs to try +def exp_smoothing_configs(seasonal=[None]): + models = list() + # define config lists + t_params = ['add', 'mul', None] + d_params = [True, False] + s_params = ['add', 'mul', None] + p_params = seasonal + b_params = [True, False] + r_params = [True, False] + # create config instances + for t in t_params: + for d in d_params: + for s in s_params: + for p in p_params: + for b in b_params: + for r in r_params: + cfg = [t,d,s,p,b,r] + models.append(cfg) + return models + +if __name__ == '__main__': + # load dataset + series = read_csv('monthly-mean-temp.csv', header=0, index_col=0) + data = series.values + # trim dataset to 5 years + data = data[-(5*12):] + # data split + n_test = 12 + # model configs + cfg_list = exp_smoothing_configs(seasonal=[0,12]) + # grid search + scores = grid_search(data[:,0], cfg_list, n_test) + print('done') + # list top 3 configs + for cfg, error in scores[:3]: + print(cfg, error) +``` + +鉴于大量数据,运行示例相对较慢。 + +在评估模型时打印模型配置和 RMSE。在运行结束时报告前三个模型配置及其错误。 + +我们可以看到最好的结果是大约 1.50 度的 RMSE,具有以下配置: + +* **趋势**:无 +* **阻尼**:错误 +* **季节性**:添加剂 +* **季节性时期**:12 +* **Box-Cox 变换**:错误 +* **删除偏差**:错误 + +``` + > Model[['add', True, 'mul', 12, True, False]] 1.659 + > Model[['add', True, 'mul', 12, True, True]] 1.663 + > Model[['add', True, 'mul', 12, False, True]] 1.603 + > Model[['add', True, 'mul', 12, False, False]] 1.609 + > Model[['mul', False, None, 0, True, True]] 4.920 + > Model[['mul', False, None, 0, True, False]] 4.881 + > Model[['mul', False, None, 0, False, True]] 4.838 + > Model[['mul', False, None, 0, False, False]] 4.813 + > Model[['add', True, 'add', 12, False, True]] 1.568 + > Model[['mul', False, None, 12, True, True]] 4.920 + > Model[['add', True, 'add', 12, False, False]] 1.555 + > Model[['add', True, 'add', 12, True, False]] 1.638 + > Model[['add', True, 'add', 12, True, True]] 1.646 + > Model[['mul', False, None, 12, True, False]] 4.881 + > Model[['mul', False, None, 12, False, True]] 4.838 + > Model[['mul', False, None, 12, False, False]] 4.813 + > Model[['add', True, None, 0, True, True]] 4.654 + > Model[[None, False, 'add', 12, True, True]] 1.508 + > Model[['add', True, None, 0, True, False]] 4.597 + > Model[['add', True, None, 0, False, True]] 4.800 + > Model[[None, False, 'add', 12, True, False]] 1.507 + > Model[['add', True, None, 0, False, False]] 4.760 + > Model[[None, False, 'add', 12, False, True]] 1.502 + > Model[['add', True, None, 12, True, True]] 4.654 + > Model[[None, False, 'add', 12, False, False]] 1.502 + > Model[['add', True, None, 12, True, False]] 4.597 + > Model[[None, False, 'mul', 12, True, True]] 1.507 + > Model[['add', True, None, 12, False, True]] 4.800 + > Model[[None, False, 'mul', 12, True, False]] 1.507 + > Model[['add', True, None, 12, False, False]] 4.760 + > Model[[None, False, 'mul', 12, False, True]] 1.502 + > Model[['add', False, 'add', 12, True, True]] 1.859 + > Model[[None, False, 'mul', 12, False, False]] 1.502 + > Model[[None, False, None, 0, True, True]] 5.188 + > Model[[None, False, None, 0, True, False]] 5.143 + > Model[[None, False, None, 0, False, True]] 5.187 + > Model[[None, False, None, 0, False, False]] 5.143 + > Model[[None, False, None, 12, True, True]] 5.188 + > Model[[None, False, None, 12, True, False]] 5.143 + > Model[[None, False, None, 12, False, True]] 5.187 + > Model[[None, False, None, 12, False, False]] 5.143 + > Model[['add', False, 'add', 12, True, False]] 1.825 + > Model[['add', False, 'add', 12, False, True]] 1.706 + > Model[['add', False, 'add', 12, False, False]] 1.710 + > Model[['add', False, 'mul', 12, True, True]] 1.882 + > Model[['add', False, 'mul', 12, True, False]] 1.739 + > Model[['add', False, 'mul', 12, False, True]] 1.580 + > Model[['add', False, 'mul', 12, False, False]] 1.581 + > Model[['add', False, None, 0, True, True]] 4.980 + > Model[['add', False, None, 0, True, False]] 4.900 + > Model[['add', False, None, 0, False, True]] 5.203 + > Model[['add', False, None, 0, False, False]] 5.151 + > Model[['add', False, None, 12, True, True]] 4.980 + > Model[['add', False, None, 12, True, False]] 4.900 + > Model[['add', False, None, 12, False, True]] 5.203 + > Model[['add', False, None, 12, False, False]] 5.151 + > Model[['mul', True, 'add', 12, True, True]] 19.353 + > Model[['mul', True, 'add', 12, True, False]] 9.807 + > Model[['mul', True, 'add', 12, False, True]] 11.696 + > Model[['mul', True, 'add', 12, False, False]] 2.847 + > Model[['mul', True, None, 0, True, True]] 4.607 + > Model[['mul', True, None, 0, True, False]] 4.570 + > Model[['mul', True, None, 0, False, True]] 4.630 + > Model[['mul', True, None, 0, False, False]] 4.596 + > Model[['mul', True, None, 12, True, True]] 4.607 + > Model[['mul', True, None, 12, True, False]] 4.570 + > Model[['mul', True, None, 12, False, True]] 4.630 + > Model[['mul', True, None, 12, False, False]] 4.593 + > Model[['mul', False, 'add', 12, True, True]] 4.230 + > Model[['mul', False, 'add', 12, True, False]] 4.157 + > Model[['mul', False, 'add', 12, False, True]] 1.538 + > Model[['mul', False, 'add', 12, False, False]] 1.520 +done + +[None, False, 'add', 12, False, False] 1.5015527325330889 +[None, False, 'add', 12, False, True] 1.5015531225114707 +[None, False, 'mul', 12, False, False] 1.501561363221282 +``` + +## 案例研究 4:趋势和季节性 + +“月度汽车销售”数据集总结了 1960 年至 1968 年间加拿大魁北克省的月度汽车销量。 + +数据集具有明显的趋势和季节性成分。 + +![Line Plot of the Monthly Car Sales Dataset](img/6ff80f643e5ca7c00df5ce9e8b51cbc5.jpg) + +月度汽车销售数据集的线图 + +您可以从 [DataMarket](https://datamarket.com/data/set/22n4/monthly-car-sales-in-quebec-1960-1968#!ds=22n4&display=line) 了解有关数据集的更多信息。 + +直接从这里下载数据集: + +* [month-car-sales.csv](https://raw.githubusercontent.com/jbrownlee/Datasets/master/monthly-car-sales.csv) + +在当前工作目录中使用文件名“monthly-car-sales.csv”保存文件。 + +我们可以使用函数 _read_csv()_ 将此数据集作为 Pandas 系列加载。 + +``` +series = read_csv('monthly-car-sales.csv', header=0, index_col=0) +``` + +数据集有九年,或 108 个观测值。我们将使用去年或 12 个观测值作为测试集。 + +季节性成分的期限可能是六个月或 12 个月。在准备模型配置时,我们将尝试将两者作为调用 _exp_smoothing_configs()_ 函数的季节性时段。 + +``` +# model configs +cfg_list = exp_smoothing_configs(seasonal=[0,6,12]) +``` + +下面列出了搜索月度汽车销售时间序列预测问题的完整示例网格。 + +``` +# grid search ets models for monthly car sales +from math import sqrt +from multiprocessing import cpu_count +from joblib import Parallel +from joblib import delayed +from warnings import catch_warnings +from warnings import filterwarnings +from statsmodels.tsa.holtwinters import ExponentialSmoothing +from sklearn.metrics import mean_squared_error +from pandas import read_csv +from numpy import array + +# one-step Holt Winter’s Exponential Smoothing forecast +def exp_smoothing_forecast(history, config): + t,d,s,p,b,r = config + # define model + history = array(history) + model = ExponentialSmoothing(history, trend=t, damped=d, seasonal=s, seasonal_periods=p) + # fit model + model_fit = model.fit(optimized=True, use_boxcox=b, remove_bias=r) + # make one step forecast + yhat = model_fit.predict(len(history), len(history)) + return yhat[0] + +# root mean squared error or rmse +def measure_rmse(actual, predicted): + return sqrt(mean_squared_error(actual, predicted)) + +# split a univariate dataset into train/test sets +def train_test_split(data, n_test): + return data[:-n_test], data[-n_test:] + +# walk-forward validation for univariate data +def walk_forward_validation(data, n_test, cfg): + predictions = list() + # split dataset + train, test = train_test_split(data, n_test) + # seed history with training dataset + history = [x for x in train] + # step over each time-step in the test set + for i in range(len(test)): + # fit model and make forecast for history + yhat = exp_smoothing_forecast(history, cfg) + # store forecast in list of predictions + predictions.append(yhat) + # add actual observation to history for the next loop + history.append(test[i]) + # estimate prediction error + error = measure_rmse(test, predictions) + return error + +# score a model, return None on failure +def score_model(data, n_test, cfg, debug=False): + result = None + # convert config to a key + key = str(cfg) + # show all warnings and fail on exception if debugging + if debug: + result = walk_forward_validation(data, n_test, cfg) + else: + # one failure during model validation suggests an unstable config + try: + # never show warnings when grid searching, too noisy + with catch_warnings(): + filterwarnings("ignore") + result = walk_forward_validation(data, n_test, cfg) + except: + error = None + # check for an interesting result + if result is not None: + print(' > Model[%s] %.3f' % (key, result)) + return (key, result) + +# grid search configs +def grid_search(data, cfg_list, n_test, parallel=True): + scores = None + if parallel: + # execute configs in parallel + executor = Parallel(n_jobs=cpu_count(), backend='multiprocessing') + tasks = (delayed(score_model)(data, n_test, cfg) for cfg in cfg_list) + scores = executor(tasks) + else: + scores = [score_model(data, n_test, cfg) for cfg in cfg_list] + # remove empty results + scores = [r for r in scores if r[1] != None] + # sort configs by error, asc + scores.sort(key=lambda tup: tup[1]) + return scores + +# create a set of exponential smoothing configs to try +def exp_smoothing_configs(seasonal=[None]): + models = list() + # define config lists + t_params = ['add', 'mul', None] + d_params = [True, False] + s_params = ['add', 'mul', None] + p_params = seasonal + b_params = [True, False] + r_params = [True, False] + # create config instances + for t in t_params: + for d in d_params: + for s in s_params: + for p in p_params: + for b in b_params: + for r in r_params: + cfg = [t,d,s,p,b,r] + models.append(cfg) + return models + +if __name__ == '__main__': + # load dataset + series = read_csv('monthly-car-sales.csv', header=0, index_col=0) + data = series.values + # data split + n_test = 12 + # model configs + cfg_list = exp_smoothing_configs(seasonal=[0,6,12]) + # grid search + scores = grid_search(data[:,0], cfg_list, n_test) + print('done') + # list top 3 configs + for cfg, error in scores[:3]: + print(cfg, error) +``` + +鉴于大量数据,运行示例很慢。 + +在评估模型时打印模型配置和 RMSE。在运行结束时报告前三个模型配置及其错误。 + +我们可以看到最好的结果是具有以下配置的约 1,672 销售额的 RMSE: + +* **趋势**:添加剂 +* **阻尼**:错误 +* **季节性**:添加剂 +* **季节性时期**:12 +* **Box-Cox 变换**:错误 +* **删除偏差**:是的 + +这有点令人惊讶,因为我猜想六个月的季节性模型将是首选方法。 + +``` + > Model[['add', True, 'add', 6, False, True]] 3240.433 + > Model[['add', True, 'add', 6, False, False]] 3226.384 + > Model[['add', True, 'add', 6, True, False]] 2836.535 + > Model[['add', True, 'add', 6, True, True]] 2784.852 + > Model[['add', True, 'add', 12, False, False]] 1696.173 + > Model[['add', True, 'add', 12, False, True]] 1721.746 + > Model[[None, False, 'add', 6, True, True]] 3204.874 + > Model[['add', True, 'add', 12, True, False]] 2064.937 + > Model[['add', True, 'add', 12, True, True]] 2098.844 + > Model[[None, False, 'add', 6, True, False]] 3190.972 + > Model[[None, False, 'add', 6, False, True]] 3147.623 + > Model[[None, False, 'add', 6, False, False]] 3126.527 + > Model[[None, False, 'add', 12, True, True]] 1834.910 + > Model[[None, False, 'add', 12, True, False]] 1872.081 + > Model[[None, False, 'add', 12, False, True]] 1736.264 + > Model[[None, False, 'add', 12, False, False]] 1807.325 + > Model[[None, False, 'mul', 6, True, True]] 2993.566 + > Model[[None, False, 'mul', 6, True, False]] 2979.123 + > Model[[None, False, 'mul', 6, False, True]] 3025.876 + > Model[[None, False, 'mul', 6, False, False]] 3009.999 + > Model[['add', True, 'mul', 6, True, True]] 2956.728 + > Model[[None, False, 'mul', 12, True, True]] 1972.547 + > Model[[None, False, 'mul', 12, True, False]] 1989.234 + > Model[[None, False, 'mul', 12, False, True]] 1925.010 + > Model[[None, False, 'mul', 12, False, False]] 1941.217 + > Model[[None, False, None, 0, True, True]] 3801.741 + > Model[[None, False, None, 0, True, False]] 3783.966 + > Model[[None, False, None, 0, False, True]] 3801.560 + > Model[[None, False, None, 0, False, False]] 3783.966 + > Model[[None, False, None, 6, True, True]] 3801.741 + > Model[[None, False, None, 6, True, False]] 3783.966 + > Model[[None, False, None, 6, False, True]] 3801.560 + > Model[[None, False, None, 6, False, False]] 3783.966 + > Model[[None, False, None, 12, True, True]] 3801.741 + > Model[[None, False, None, 12, True, False]] 3783.966 + > Model[[None, False, None, 12, False, True]] 3801.560 + > Model[[None, False, None, 12, False, False]] 3783.966 + > Model[['add', True, 'mul', 6, True, False]] 2932.827 + > Model[['mul', True, 'mul', 12, True, True]] 1953.405 + > Model[['add', True, 'mul', 6, False, True]] 2997.259 + > Model[['mul', True, 'mul', 12, True, False]] 1960.242 + > Model[['add', True, 'mul', 6, False, False]] 2979.248 + > Model[['mul', True, 'mul', 12, False, True]] 1907.792 + > Model[['add', True, 'mul', 12, True, True]] 1972.550 + > Model[['add', True, 'mul', 12, True, False]] 1989.236 + > Model[['mul', True, None, 0, True, True]] 3951.024 + > Model[['mul', True, None, 0, True, False]] 3930.394 + > Model[['mul', True, None, 0, False, True]] 3947.281 + > Model[['mul', True, None, 0, False, False]] 3926.082 + > Model[['mul', True, None, 6, True, True]] 3951.026 + > Model[['mul', True, None, 6, True, False]] 3930.389 + > Model[['mul', True, None, 6, False, True]] 3946.654 + > Model[['mul', True, None, 6, False, False]] 3926.026 + > Model[['mul', True, None, 12, True, True]] 3951.027 + > Model[['mul', True, None, 12, True, False]] 3930.368 + > Model[['mul', True, None, 12, False, True]] 3942.037 + > Model[['mul', True, None, 12, False, False]] 3920.756 + > Model[['add', True, 'mul', 12, False, True]] 1750.480 + > Model[['mul', False, 'add', 6, True, False]] 5043.557 + > Model[['mul', False, 'add', 6, False, True]] 7425.711 + > Model[['mul', False, 'add', 6, False, False]] 7448.455 + > Model[['mul', False, 'add', 12, True, True]] 2160.794 + > Model[['mul', False, 'add', 12, True, False]] 2346.478 + > Model[['mul', False, 'add', 12, False, True]] 16303.868 + > Model[['mul', False, 'add', 12, False, False]] 10268.636 + > Model[['mul', False, 'mul', 12, True, True]] 3012.036 + > Model[['mul', False, 'mul', 12, True, False]] 3005.824 + > Model[['add', True, 'mul', 12, False, False]] 1774.636 + > Model[['mul', False, 'mul', 12, False, True]] 14676.476 + > Model[['add', True, None, 0, True, True]] 3935.674 + > Model[['mul', False, 'mul', 12, False, False]] 13988.754 + > Model[['mul', False, None, 0, True, True]] 3804.906 + > Model[['mul', False, None, 0, True, False]] 3805.342 + > Model[['mul', False, None, 0, False, True]] 3778.444 + > Model[['mul', False, None, 0, False, False]] 3798.003 + > Model[['mul', False, None, 6, True, True]] 3804.906 + > Model[['mul', False, None, 6, True, False]] 3805.342 + > Model[['mul', False, None, 6, False, True]] 3778.456 + > Model[['mul', False, None, 6, False, False]] 3798.007 + > Model[['add', True, None, 0, True, False]] 3915.499 + > Model[['mul', False, None, 12, True, True]] 3804.906 + > Model[['mul', False, None, 12, True, False]] 3805.342 + > Model[['mul', False, None, 12, False, True]] 3778.457 + > Model[['mul', False, None, 12, False, False]] 3797.989 + > Model[['add', True, None, 0, False, True]] 3924.442 + > Model[['add', True, None, 0, False, False]] 3905.627 + > Model[['add', True, None, 6, True, True]] 3935.658 + > Model[['add', True, None, 6, True, False]] 3913.420 + > Model[['add', True, None, 6, False, True]] 3924.287 + > Model[['add', True, None, 6, False, False]] 3913.618 + > Model[['add', True, None, 12, True, True]] 3935.673 + > Model[['add', True, None, 12, True, False]] 3913.428 + > Model[['add', True, None, 12, False, True]] 3924.487 + > Model[['add', True, None, 12, False, False]] 3913.529 + > Model[['add', False, 'add', 6, True, True]] 3220.532 + > Model[['add', False, 'add', 6, True, False]] 3199.766 + > Model[['add', False, 'add', 6, False, True]] 3243.478 + > Model[['add', False, 'add', 6, False, False]] 3226.955 + > Model[['add', False, 'add', 12, True, True]] 1833.481 + > Model[['add', False, 'add', 12, True, False]] 1833.511 + > Model[['add', False, 'add', 12, False, True]] 1672.554 + > Model[['add', False, 'add', 12, False, False]] 1680.845 + > Model[['add', False, 'mul', 6, True, True]] 3014.447 + > Model[['add', False, 'mul', 6, True, False]] 3016.207 + > Model[['add', False, 'mul', 6, False, True]] 3025.870 + > Model[['add', False, 'mul', 6, False, False]] 3010.015 + > Model[['add', False, 'mul', 12, True, True]] 1982.087 + > Model[['add', False, 'mul', 12, True, False]] 1981.089 + > Model[['add', False, 'mul', 12, False, True]] 1898.045 + > Model[['add', False, 'mul', 12, False, False]] 1894.397 + > Model[['add', False, None, 0, True, True]] 3815.765 + > Model[['add', False, None, 0, True, False]] 3813.234 + > Model[['add', False, None, 0, False, True]] 3805.649 + > Model[['add', False, None, 0, False, False]] 3809.864 + > Model[['add', False, None, 6, True, True]] 3815.765 + > Model[['add', False, None, 6, True, False]] 3813.234 + > Model[['add', False, None, 6, False, True]] 3805.619 + > Model[['add', False, None, 6, False, False]] 3809.846 + > Model[['add', False, None, 12, True, True]] 3815.765 + > Model[['add', False, None, 12, True, False]] 3813.234 + > Model[['add', False, None, 12, False, True]] 3805.638 + > Model[['add', False, None, 12, False, False]] 3809.837 + > Model[['mul', True, 'add', 6, True, False]] 4099.032 + > Model[['mul', True, 'add', 6, False, True]] 3818.567 + > Model[['mul', True, 'add', 6, False, False]] 3745.142 + > Model[['mul', True, 'add', 12, True, True]] 2203.354 + > Model[['mul', True, 'add', 12, True, False]] 2284.172 + > Model[['mul', True, 'add', 12, False, True]] 2842.605 + > Model[['mul', True, 'add', 12, False, False]] 2086.899 +done + +['add', False, 'add', 12, False, True] 1672.5539372356582 +['add', False, 'add', 12, False, False] 1680.845043013083 +['add', True, 'add', 12, False, False] 1696.1734099400082 +``` + +## 扩展 + +本节列出了一些扩展您可能希望探索的教程的想法。 + +* **数据转换**。更新框架以支持可配置的数据转换,例如规范化和标准化。 +* **地块预测**。更新框架以重新拟合具有最佳配置的模型并预测整个测试数据集,然后将预测与测试集中的实际观察值进行比较。 +* **调整历史数量**。更新框架以调整用于拟合模型的历史数据量(例如,在 10 年最高温度数据的情况下)。 + +如果你探索任何这些扩展,我很想知道。 + +## 进一步阅读 + +如果您希望深入了解,本节将提供有关该主题的更多资源。 + +### 图书 + +* 第 7 章指数平滑,[预测:原则和实践](https://amzn.to/2xlJsfV),2013。 +* 第 6.4 节。时间序列分析简介,[工程统计手册](https://www.itl.nist.gov/div898/handbook/),2012。 +* [实际时间序列预测与 R](https://amzn.to/2LGKzKm) ,2016 年。 + +### 蜜蜂 + +* [statsmodels.tsa.holtwinters.ExponentialSmoothing API](http://www.statsmodels.org/dev/generated/statsmodels.tsa.holtwinters.ExponentialSmoothing.html) +* [statsmodels.tsa.holtwinters.HoltWintersResults API](http://www.statsmodels.org/dev/generated/statsmodels.tsa.holtwinters.HoltWintersResults.html) +* [Joblib:运行 Python 函数作为管道作业](https://pythonhosted.org/joblib/) + +### 用品 + +* [维基百科上的指数平滑](https://en.wikipedia.org/wiki/Exponential_smoothing) + +## 摘要 + +在本教程中,您了解了如何开发一个框架,用于网格搜索所有指数平滑模型超参数,以进行单变量时间序列预测。 + +具体来说,你学到了: + +* 如何使用前向验证从头开始开发网格搜索 ETS 模型的框架。 +* 如何为出生日常时间序列数据网格搜索 ETS 模型超参数。 +* 如何为洗发水销售,汽车销售和温度的月度时间序列数据网格搜索 ETS 模型超参数。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/how-to-load-and-explore-a-standard-human-activity-recognition-problem.md b/docs/dl-ts/how-to-load-and-explore-a-standard-human-activity-recognition-problem.md new file mode 100644 index 0000000000000000000000000000000000000000..e9fcbfaed04b0b162b8671df05bbaa9207d4769c --- /dev/null +++ b/docs/dl-ts/how-to-load-and-explore-a-standard-human-activity-recognition-problem.md @@ -0,0 +1,652 @@ +# 一个标准的人类活动识别问题的温和介绍 + +> 原文: [https://machinelearningmastery.com/how-to-load-and-explore-a-standard-human-activity-recognition-problem/](https://machinelearningmastery.com/how-to-load-and-explore-a-standard-human-activity-recognition-problem/) + +人类活动识别是将由专用线束或智能电话记录的加速度计数据序列分类为已知的明确定义的运动的问题。 + +鉴于每秒产生大量观测结果,观测的时间性质以及缺乏将加速度计数据与已知运动联系起来的明确方法,这是一个具有挑战性的问题。 + +该问题的经典方法涉及来自基于固定大小窗口的时间序列数据的手工制作特征和训练机器学习模型,例如决策树的集合。困难在于此功能工程需要该领域的深厚专业知识。 + +最近,诸如循环神经网络和一维卷积神经网络或 CNN 的深度学习方法已经被证明在很少或没有数据特征工程的情况下提供具有挑战性的活动识别任务的最新结果。 + +在本教程中,您将发现用于时间序列分类的标准人类活动识别数据集以及如何在建模之前探索数据集。 + +完成本教程后,您将了解: + +* 如何加载和准备人类活动识别时间序列分类数据。 +* 如何探索和可视化时间序列分类数据,以生成建模的想法。 +* 一套用于构建问题,准备数据,建模和评估人类活动识别模型的方法。 + +让我们开始吧。 + +![A Gentle Introduction to a Standard Human Activity Recognition Problem](img/6ea5274c621fd3926200a0df643f1e1b.jpg) + +标准人类活动识别问题的温和介绍 +[tgraham](https://www.flickr.com/photos/tgraham/2546626192/) 的照片,保留一些权利。 + +## 教程概述 + +本教程分为 8 个部分;他们是: + +1. 人类活动识别 +2. 问题描述 +3. 加载数据集 +4. 绘制单个主题的跟踪 +5. 绘制总活动持续时间 +6. 绘制每个主题的痕迹 +7. 绘制痕量观测的直方图 +8. 建模问题的方法 + +## 人类活动识别 + +[人类活动识别](https://en.wikipedia.org/wiki/Activity_recognition)或简称 HAR,是基于使用传感器的移动痕迹来预测人正在做什么的问题。 + +运动通常是正常的室内活动,例如站立,坐着,跳跃和上楼梯。传感器通常位于主体上,例如智能手机或背心,并且经常以三维(x,y,z)记录加速度计数据。 + +这个想法是,一旦主体的活动被识别和知道,智能计算机系统就可以提供帮助。 + +这是一个具有挑战性的问题,因为没有明确的分析方法将传感器数据与一般方式的特定动作联系起来。由于收集了大量的传感器数据(例如,每秒数十或数百次观察),并且在开发预测模型时从这些数据中经典使用手工制作的特征和启发式,因此在技术上具有挑战性。 + +最近,深度学习方法已经在 HAR 问题上取得了成功,因为它们能够自动学习更高阶的特征。 + +> 基于传感器的活动识别从大量低水平传感器读数中寻找关于人类活动的深刻的高级知识。传统的模式识别方法在过去几年中取得了巨大的进步。然而,这些方法通常严重依赖于启发式手工特征提取,这可能会妨碍它们的泛化表现。 [...]最近,深度学习的最新进展使得可以执行自动高级特征提取,从而在许多领域实现了有希望的表现。 + +- [基于传感器的活动识别深度学习:调查](https://arxiv.org/abs/1707.03502) + +## 问题描述 + +收集数据集“来自单个胸部安装的加速度计数据集的 _ 活动识别”并由 Casale,Pujol 等人提供。来自西班牙巴塞罗那大学。_ + +它可以从 UCI 机器学习库免费获得: + +* [来自单个胸部加速计数据集](https://archive.ics.uci.edu/ml/datasets/Activity+Recognition+from+Single+Chest-Mounted+Accelerometer),UCI 机器学习库的活动识别。 + +在 2011 年论文“[使用可穿戴设备的加速度计数据](https://link.springer.com/chapter/10.1007/978-3-642-21257-4_36)[人类活动识别](https://link.springer.com/chapter/10.1007/978-3-642-21257-4_36)”中描述并使用该数据集作为序列分类模型的基础。 + +数据集由来自 15 个不同科目的未校准加速度计数据组成,每个科目执行 7 项活动。每个受试者佩戴定制的胸部加速度计,并以 52Hz(每秒 52 次观察)收集数据。 + +![Photographs of the custom chest-mounted systems warn by each subject](img/af953cebdb4b9f7e3af4bdc35e49bdae.jpg) + +每个主题佩戴的定制胸部系统的照片。 +取自“使用可穿戴设备从加速度计数据中识别人体活动”。 + +每个受试者以连续的顺序进行和记录七项活动。 + +所开展的具体活动包括: + +* 1:在计算机工作(workingPC)。 +* 2:站起来,走路,上下楼梯。 +* 3:站立(站立)。 +* 4:散步(醒来)。 +* 5:上/下楼梯(楼梯)。 +* 6:与某人散步和交谈。 +* 7:站立时说话(说话)。 + +该论文使用了数据的一个子集,特别是 14 个主题和 5 个活动。目前尚不清楚为什么没有使用其他 2 项活动(2 和 6)。 + +> 数据来自 14 名测试者,3 名女性和 11 名年龄在 27 岁至 35 岁之间的男性。[...]所收集的数据由上下楼梯步行 33 分钟,步行 82 分钟,说话 115 分钟组成。保持站立的分钟数和 86 分钟的电脑工作时间。 + +- [使用可穿戴设备从加速计数据识别人类活动](https://link.springer.com/chapter/10.1007/978-3-642-21257-4_36),2011。 + +本文的重点是从数据开发手工制作的功能,以及机器学习算法的开发。 + +数据被分成一秒钟的观察窗口(52),窗口之间有 50%的重叠。 + +> 我们使用 52 个样本的窗口从数据中提取特征,对应于 1 秒的加速度计数据,窗口之间有 50%的重叠。从每个窗口,我们建议提取以下特征:窗口中加速度积分的均方根值,以及 Minmax 和的平均值。 [...]尽管如此,为了完成这组特征,我们添加了已被证明对人类活动识别有用的特征,如:平均值,标准偏差,偏度,峰度,加速度计轴的每对成对之间的相关性(不包括幅度) ,七级小波分解系数的能量。通过这种方式,我们获得了 319 维特征向量。 + +- [使用可穿戴设备从加速计数据识别人类活动](https://link.springer.com/chapter/10.1007/978-3-642-21257-4_36),2011。 + +使用 5 倍交叉验证拟合和评估一套机器学习模型,并获得 94%的准确度。 + +![Confusion Matrix of Random Forest evaluated on the dataset](img/64602bce1a186a714ee2d6b845c8171c.jpg) + +随机森林的混淆矩阵在数据集上进行评估。 +取自“使用可穿戴设备从加速度计数据中识别人体活动”。 + +## 加载数据集 + +数据集可以直接从 UCI 机器学习库下载。 + +* [下载数据集](https://archive.ics.uci.edu/ml/machine-learning-databases/00287/Activity%20Recognition%20from%20Single%20Chest-Mounted%20Accelerometer.zip) + +将数据集解压缩到名为“ _HAR_ ”的新目录中。 + +该目录包含 CSV 文件列表,每个主题一个(1-15)和自述文件。 + +每个文件包含 5 列,行号,x,y 和 z 加速度计读数以及 0 到 7 的类号,其中类 0 表示“无活动”,类 1-7 对应于上一节中列出的活动。 + +例如,下面是文件“ _1.csv_ ”的前 5 行: + +``` +0,1502,2215,2153,1 +1,1667,2072,2047,1 +2,1611,1957,1906,1 +3,1601,1939,1831,1 +4,1643,1965,1879,1 +... +``` + +首先,我们可以将每个文件作为单个 NumPy 数组加载并删除第一列。 + +下面名为 _load_dataset()_ 的函数将加载 HAR 目录中的所有 CSV 文件,删除第一列并返回 15 个 NumPy 数组的列表。 + +``` +# load sequence for each subject, returns a list of numpy arrays +def load_dataset(prefix=''): + subjects = list() + directory = prefix + 'HAR/' + for name in listdir(directory): + filename = directory + '/' + name + if not filename.endswith('.csv'): + continue + df = read_csv(filename, header=None) + # drop row number + values = df.values[:, 1:] + subjects.append(values) + return subjects +``` + +下面列出了完整的示例。 + +``` +# load dataset +from os import listdir +from pandas import read_csv + +# load sequence for each subject, returns a list of numpy arrays +def load_dataset(prefix=''): + subjects = list() + directory = prefix + 'HAR/' + for name in listdir(directory): + filename = directory + '/' + name + if not filename.endswith('.csv'): + continue + df = read_csv(filename, header=None) + # drop row number + values = df.values[:, 1:] + subjects.append(values) + return subjects + +# load +subjects = load_dataset() +print('Loaded %d subjects' % len(subjects)) +``` + +运行该示例将加载所有数据并打印已加载主题的数量。 + +``` +Loaded 15 subjects +``` + +注意,目录中的文件是按文件顺序导航的,这可能与主题顺序不同,例如 _10.csv_ 以文件顺序出现在 _2.csv_ 之前。我相信这在本教程中无关紧要。 + +现在我们知道了如何加载数据,我们可以通过一些可视化来探索它。 + +## 绘制单个主题的跟踪 + +良好的第一个可视化是绘制单个主题的数据。 + +我们可以为给定主题的每个变量创建一个图形,包括 x,y 和 z 加速度计数据,以及相关的类类值。 + +下面的函数 _plot_subject()_ 将绘制给定主题的数据。 + +``` +# plot the x, y, z acceleration and activities for a single subject +def plot_subject(subject): + pyplot.figure() + # create a plot for each column + for col in range(subject.shape[1]): + pyplot.subplot(subject.shape[1], 1, col+1) + pyplot.plot(subject[:,col]) + pyplot.show() +``` + +我们可以将它与上一节中的数据加载结合起来,并绘制第一个加载主题的数据。 + +``` +# plot a subject +from os import listdir +from pandas import read_csv +from matplotlib import pyplot + +# load sequence for each subject, returns a list of numpy arrays +def load_dataset(prefix=''): + subjects = list() + directory = prefix + 'HAR/' + for name in listdir(directory): + filename = directory + '/' + name + if not filename.endswith('.csv'): + continue + df = read_csv(filename, header=None) + # drop row number + values = df.values[:, 1:] + subjects.append(values) + return subjects + +# plot the x, y, z acceleration and activities for a single subject +def plot_subject(subject): + pyplot.figure() + # create a plot for each column + for col in range(subject.shape[1]): + pyplot.subplot(subject.shape[1], 1, col+1) + pyplot.plot(subject[:,col]) + pyplot.show() + +# load +subjects = load_dataset() +print('Loaded %d subjects' % len(subjects)) +# plot activities for a single subject +plot_subject(subjects[0]) +``` + +运行该示例为第一个加载主题的每个变量创建一个线图。 + +我们可以在序列的开头看到一些非常大的运动,这可能是一个可以被删除的异常或异常行为。 + +我们还可以看到主题多次执行某些操作。例如,仔细查看类变量的图(底部图)表明受试者按以下顺序执行活动:1,2,0,3,0,4,3,5,3,6,7。活动 3 进行了两次。 + +![Line plots of x, y, z and class for the first loaded subject.](img/7e2d8d30ddff5a21eee385b400303004.jpg) + +第一个加载主题的 x,y,z 和类的线图。 + +我们可以重新运行此代码并绘制第二个加载的主题(可能是 _10.csv_ )。 + +``` +... +# plot activities for a single subject +plot_subject(subjects[1]) +``` + +运行该示例会创建一个类似的图。 + +我们可以看到更多细节,这表明在上一个情节开头看到的大异常值可能是从该主题的痕迹中清除了值。 + +我们看到类似的活动序列,活动 3 再次发生两次。 + +我们还可以看到,某些活动的执行时间比其他活动长得多。这可能会影响模型区分活动的能力,例如:两个受试者的活动 3(站立)相对于所进行的其他活动具有非常少的数据。 + +![Line plots of x, y, z and class for the second loaded subject.](img/af152fb607033c313c016f47bb8de186.jpg) + +第二个加载主题的 x,y,z 和类的线图。 + +## 绘制总活动持续时间 + +上一节提出了一个问题,即我们对所有主题的每项活动进行了多长时间或多少次观察。 + +如果一项活动的数据多于另一项活动,这可能很重要,这表明不太好的活动可能难以建模。 + +我们可以通过按活动和主题对所有观察进行分组来研究这一点,并绘制分布图。这将了解每个主题在跟踪过程中花费多长时间执行每项活动。 + +首先,我们可以为每个主题分组活动。 + +我们可以通过为每个主题创建字典并按活动存储所有跟踪数据来完成此操作。下面的 _group_by_activity()_ 功能将为每个主题执行此分组。 + +``` +# returns a list of dict, where each dict has one sequence per activity +def group_by_activity(subjects, activities): + grouped = [{a:s[s[:,-1]==a] for a in activities} for s in subjects] + return grouped +``` + +接下来,我们可以计算每个主题的每个活动的总持续时间。 + +我们知道加速度计数据是以 52Hz 记录的,因此我们可以将每个活动的每个跟踪的长度除以 52,以便以秒为单位总结持续时间。 + +以下名为 _plot_durations()_ 的函数将计算每个主题的每个活动的持续时间,并将结果绘制为箱线图。盒状和须状图是总结每个活动的 15 个持续时间的有用方式,因为它描述了持续时间的扩展而不假设分布。 + +``` +# calculate total duration in sec for each activity per subject and plot +def plot_durations(grouped, activities): + # calculate the lengths for each activity for each subject + freq = 52 + durations = [[len(s[a])/freq for s in grouped] for a in activities] + pyplot.boxplot(durations, labels=activities) + pyplot.show() +``` + +下面列出了绘制活动持续时间分布的完整示例。 + +``` +# durations by activity +from os import listdir +from pandas import read_csv +from matplotlib import pyplot + +# load sequence for each subject, returns a list of numpy arrays +def load_dataset(prefix=''): + subjects = list() + directory = prefix + 'HAR/' + for name in listdir(directory): + filename = directory + '/' + name + if not filename.endswith('.csv'): + continue + df = read_csv(filename, header=None) + # drop row number + values = df.values[:, 1:] + subjects.append(values) + return subjects + +# returns a list of dict, where each dict has one sequence per activity +def group_by_activity(subjects, activities): + grouped = [{a:s[s[:,-1]==a] for a in activities} for s in subjects] + return grouped + +# calculate total duration in sec for each activity per subject and plot +def plot_durations(grouped, activities): + # calculate the lengths for each activity for each subject + freq = 52 + durations = [[len(s[a])/freq for s in grouped] for a in activities] + pyplot.boxplot(durations, labels=activities) + pyplot.show() + +# load +subjects = load_dataset() +print('Loaded %d subjects' % len(subjects)) +# group traces by activity for each subject +activities = [i for i in range(0,8)] +grouped = group_by_activity(subjects, activities) +# plot durations +plot_durations(grouped, activities) +``` + +运行该示例绘制了每个主题的活动持续时间的分布。 + +我们可以看到,对于活动 0(无活动),2(站立,行走和上下楼梯),5(上/下楼梯)和 6(步行和说话)的观察相对较少。 + +这可能表明为什么活动 2 和 6 被排除在原始论文的实验之外。 + +我们还可以看到每个主题在活动 1(站立,行走和上下楼梯)和活动 7(站立时说话)上花费了大量时间。这些活动可能过多。准备模型数据可能有益于对这些活动进行欠采样或对其他活动进行采样。 + +![Boxplot of the distribution of activity durations per subject](img/c686debb385b47ff6fcd27805e595b10.jpg) + +每个受试者活动持续时间分布的箱线图 + +## 绘制每个主题的痕迹 + +接下来,查看每个主题的跟踪数据可能会很有趣。 + +一种方法是在单个图形上绘制单个主题的所有迹线,然后垂直排列所有图形。这将允许跨主题和主题内的痕迹进行比较。 + +以下名为 _plot_subjects()_ 的函数将在单独的图上绘制 15 个主题中每个主题的加速度计数据。每个 x,y 和 z 数据的迹线分别绘制为橙色,绿色和蓝色。 + +``` +# plot the x, y, z acceleration for each subject +def plot_subjects(subjects): + pyplot.figure() + # create a plot for each subject + for i in range(len(subjects)): + pyplot.subplot(len(subjects), 1, i+1) + # plot each of x, y and z + for j in range(subjects[i].shape[1]-1): + pyplot.plot(subjects[i][:,j]) + pyplot.show() +``` + +下面列出了完整的示例。 + +``` +# plot accelerometer data for all subjects +from os import listdir +from pandas import read_csv +from matplotlib import pyplot + +# load sequence for each subject, returns a list of numpy arrays +def load_dataset(prefix=''): + subjects = list() + directory = prefix + 'HAR/' + for name in listdir(directory): + filename = directory + '/' + name + if not filename.endswith('.csv'): + continue + df = read_csv(filename, header=None) + # drop row number + values = df.values[:, 1:] + subjects.append(values) + return subjects + +# plot the x, y, z acceleration for each subject +def plot_subjects(subjects): + pyplot.figure() + # create a plot for each subject + for i in range(len(subjects)): + pyplot.subplot(len(subjects), 1, i+1) + # plot each of x, y and z + for j in range(subjects[i].shape[1]-1): + pyplot.plot(subjects[i][:,j]) + pyplot.show() + +# load +subjects = load_dataset() +print('Loaded %d subjects' % len(subjects)) +# plot trace data for each subject +plot_subjects(subjects) +``` + +运行该示例将创建一个包含 15 个图的图形。 + +我们正在研究一般而非具体的趋势。 + +* 我们可以看到很多橙色和绿色以及非常小的蓝色,这表明 z 数据在建模这个问题时可能不太重要。 +* 我们可以看到跟踪数据在 x 和 y 跟踪的相同时间发生相同的一般变化,这表明可能只需要一个数据轴来拟合预测模型。 +* 我们可以看到每个受试者在序列开始时(前 60 秒)在迹线中具有相同的大尖峰,可能与实验的启动有关。 +* 我们可以在跟踪数据中看到跨主题的类似结构,尽管某些迹线看起来比其他迹线更柔和,例如比较第一和第二个图上的幅度。 + +每个受试者可具有不同的完整迹线长度,因此通过 x 轴的直接比较可能是不合理的(例如,同时执行类似的活动)。无论如何,我们并不真正关心这个问题。 + +![Line plots of accelerometer trace data for all 15 subjects.](img/0314ec1585d398ad3046c0ee97a8a6b4.jpg) + +所有 15 名受试者的加速度计跟踪数据的线图。 + +痕迹似乎具有相同的一般比例,但受试者之间的幅度差异表明,每个受试者重新缩放数据可能比跨受试者缩放更有意义。 + +这对于训练数据可能是有意义的,但对于缩放测试对象的数据可能在方法上具有挑战性。它需要或假设在预测活动之前可以获得整个跟踪。这对于模型的离线使用很好,但不能在线使用模型。它还表明,使用预先校准的跟踪数据(例如以固定规模进入的数据)可以更容易地在线使用模型。 + +## 绘制痕量观测的直方图 + +上一节中关于跨不同主题的显着不同尺度可能性的观点可能会给这个数据集的建模带来挑战。 + +我们可以通过绘制加速度计数据的每个轴的观测分布的直方图来探索这一点。 + +与上一节一样,我们可以为每个主题创建一个绘图,然后将所有主题的绘图与相同的 x 轴垂直对齐,以帮助发现展开的明显差异。 + +更新的 _plot_subjects()_ 函数用于绘制直方图而不是线图,如下所示。 _hist()_ 函数用于为加速度计数据的每个轴创建直方图,并且使用大量箱(100)来帮助展开图中的数据。子图也都共享相同的 x 轴以帮助进行比较。 + +``` +# plot the x, y, z acceleration for each subject +def plot_subjects(subjects): + pyplot.figure() + # create a plot for each subject + xaxis = None + for i in range(len(subjects)): + ax = pyplot.subplot(len(subjects), 1, i+1, sharex=xaxis) + if i == 0: + xaxis = ax + # plot a histogram of x data + for j in range(subjects[i].shape[1]-1): + pyplot.hist(subjects[i][:,j], bins=100) + pyplot.show() +``` + +下面列出了完整的示例 + +``` +# plot histograms of trace data for all subjects +from os import listdir +from pandas import read_csv +from matplotlib import pyplot + +# load sequence for each subject, returns a list of numpy arrays +def load_dataset(prefix=''): + subjects = list() + directory = prefix + 'HAR/' + for name in listdir(directory): + filename = directory + '/' + name + if not filename.endswith('.csv'): + continue + df = read_csv(filename, header=None) + # drop row number + values = df.values[:, 1:] + subjects.append(values) + return subjects + +# plot the x, y, z acceleration for each subject +def plot_subjects(subjects): + pyplot.figure() + # create a plot for each subject + xaxis = None + for i in range(len(subjects)): + ax = pyplot.subplot(len(subjects), 1, i+1, sharex=xaxis) + if i == 0: + xaxis = ax + # plot a histogram of x data + for j in range(subjects[i].shape[1]-1): + pyplot.hist(subjects[i][:,j], bins=100) + pyplot.show() + +# load +subjects = load_dataset() +print('Loaded %d subjects' % len(subjects)) +# plot trace data for each subject +plot_subjects(subjects) +``` + +运行该示例将创建一个包含 15 个图的单个图形,每个图形对应一个图形,以及每个图表的 3 个加速度计数据的 3 个直方图。 + +蓝色,橙色和绿色三种颜色代表 x,y 和 z 轴。 + +该图表明加速度计的每个轴的分布是高斯分布或者非常接近高斯分布。这可以帮助沿着加速度计数据的每个轴进行简单的离群值检测和移除。 + +该图确实有助于显示主题内的分布与主题之间的分布差异。 + +在每个主题内,共同模式是 x(蓝色)和 z(绿色)一起分组到左边,y 数据(橙色)分开到右边。 y 的分布通常更尖锐,因为 x 和 z 的分布更平坦。 + +在整个主题中,我们可以看到一般的聚类值约为 2,000(无论单位是多少),尽管有很多差异。这种显着的分布差异确实表明在执行任何跨主题建模之前,需要至少标准化(转换为零均值和单位方差)每个轴和每个主体的数据。 + +![Histograms of accelerometer data for each subject](img/3965db2aedd6938d4d3e029c9b2b8503.jpg) + +每个受试者的加速度计数据的直方图 + +## 建模问题的方法 + +在本节中,我们将基于对数据集的上述探索,探索针对该问题的数据准备和建模的一些想法和方法。 + +这些可能有助于特别是对该数据集建模,但也有助于人类活动识别,甚至是一般的时间序列分类问题。 + +### 问题框架 + +尽管所有方法都围绕时间序列分类的思想,但有许多方法可以将数据构建为预测建模问题。 + +要考虑的两种主要方法是: + +* **每个受试者**:每个受试者的痕量数据的模型活动分类。 +* **交叉主题**:跨主题的跟踪数据的模型活动分类。 + +后者,交叉主题,是更理想的,但如果目标是深刻理解给定的主题,例如前者也可能是有趣的。家中的个性化模型。 + +在建模过程中构建数据的两种主要方法包括: + +* **分段活动**:跟踪数据可以按活动预先分段,并且可以针对每个活动对整个跟踪或其特征进行训练的模型。 +* **滑动窗口**:每个主体的连续轨迹被分成滑动窗口,有或没有重叠,窗口的每个活动的模式被视为要预测的活动。 + +就模型的实际使用而言,前一种方法可能不太现实,但可能是更容易建模的问题。后者是原始论文中使用的问题的框架,其中制备了具有 50%重叠的 1 秒窗口。 + +我没有看到问题框架中的重叠的好处,除了加倍训练数据集的大小,这可能有益于深度神经网络。事实上,我预计它可能会导致过度模型。 + +### 数据准备 + +数据表明在建模过程中可能有用的一些准备方案: + +* 将加速度计观测值下采样到几分之一秒可能是有帮助的,例如, 1 / 4,1 / 2,1,2 秒。 +* 截断原始数据的前 60 秒可能是谨慎的,因为它似乎与实验的启动有关,并且所有主体当时正在执行活动 1(在计算机上工作)。 +* 使用简单的离群值检测和去除方法(例如,每个轴的平均值的标准差的 3 到 4 倍的值)可能是有用的。 +* 也许删除具有相对较少观察的活动将是明智的,或者导致对预测方法(例如,活动 0,2 和 6)的更公平的评估。 +* 也许通过对代表性不足的活动进行过度采样或对训练数据集中过度代表的活动进行抽样调整来重新平衡活动可能有助于建模。 +* 尝试不同的窗口尺寸将是有趣的(例如 1,5,10,30 秒),尤其是在对观察的下采样的确证中。 +* 对于任何跨主题模型,几乎肯定需要标准化每个主题的数据。在每个受试者标准化后对受试者的数据进行标准化也可能是有用的。 + +如前一节所述,每个主题的数据标准化确实引入了方法问题,并且无论如何都可以使用,因为需要来自原始硬件系统的校准观察。 + +### 问题建模 + +我建议使用神经网络来探索这个问题。 + +与使用特征工程和特定于域的手工制作特征的论文中使用的方法不同,直接对原始数据进行建模(下采样或其他方式)将是有用且通用的。 + +首先,我建议使用强大的方法(如随机森林或梯度增强机器)发现表现基线。然后探索特别适合时间序列分类问题的神经网络方法。 + +可能适合的两种类型的神经网络架构是: + +* 卷积神经网络或 CNN。 +* 循环神经网络或 RNN,特别是长短期记忆或 LSTM。 + +第三个是两者的混合: + +* CNN LSTMs。 + +CNN 能够从输入序列中提取特征,例如输入加速度计数据的窗口。诸如 LSTM 之类的 RNN 能够直接从长序列的输入数据中学习,并学习数据中的长期关系。 + +我希望序列数据中几乎没有因果关系,除了每个主题看起来他们正在执行相同的人为行动序列,我们不想学习。天真地,这可能表明 CNN 更适合于在给定一系列观察到的加速度计数据的情况下预测活动。 + +一维 CNN 已广泛用于此类问题,其中一个通道用于加速度计数据的每个轴。一个很好的简单起点是直接在序列数据的窗口上拟合 CNN 模型。这是 2014 年题为“[使用移动传感器进行人类活动识别的卷积神经网络](https://ieeexplore.ieee.org/abstract/document/7026300/)”的论文中描述的方法,并且从下面的图中可以看出更清楚。 + +![Example of 1D CNN Architecture for Human Activity Recognition](img/931e91a8d4a05b4ab1a818b3c821e0b5.jpg) + +用于人类活动识别的 1D CNN 架构的示例 +取自“使用移动传感器的用于人类活动识别的卷积神经网络”。 + +CNN LSTM 可用于 CNN 学习观察子序列的表示,然后 LSTM 学习这些子序列。 + +例如,CNN 可以提取一秒钟的加速度计数据,然后可以重复 30 秒,以向 LSTM 提供 30 个 CNN 解释数据的时间步长。 + +我希望这三个方法对这个问题和类似的问题都很有意思。 + +### 模型评估 + +我不认为窗口的重叠是有用的,并且实际上如果在交叉验证期间跟踪数据的部分在列车和测试数据集中都可用,则实际上可能导致轻微的误导性结果。然而,它会增加训练数据的数量。 + +我认为重要的是将数据暴露给模型,同时保持观察的时间顺序。对于给定主题,来自单个活动的多个窗口可能看起来相似,并且随机改组和分离窗口以训练测试数据可能导致误导结果。 + +我不建议在原始论文中使用随机改组的 k-fold 交叉验证。我希望这会带来乐观的结果,每个 15 个主题的痕迹中有一秒钟的数据窗口混合在一起进行训练和测试。 + +也许对这些数据中的模型进行公平评估将是按主题使用留一法交叉验证或 LOOCV。这是模型适合前 14 个主题并在第 15 个主题的所有窗口上进行评估的地方。重复该过程,其中每个受试者有机会被用作保持测试数据集。 + +按主题分割数据集避免了在模型评估期间与各个窗口的时间排序相关的任何问题,因为所有窗口都将保证新的/看不见的数据。 + +如果你探索这些建模思想中的任何一个,我很想知道。 + +## 进一步阅读 + +如果您希望深入了解,本节将提供有关该主题的更多资源。 + +### 文件 + +* [使用加速度计进行活动识别的综合研究](http://mdpi.com/2227-9709/5/2/27),2018 +* [基于传感器的活动识别深度学习:调查](https://arxiv.org/abs/1707.03502),2017。 +* [使用可穿戴设备从加速度计数据识别人类活动](https://link.springer.com/chapter/10.1007/978-3-642-21257-4_36),2011。 +* [用于使用移动传感器识别人类活动的卷积神经网络](https://ieeexplore.ieee.org/abstract/document/7026300/),2014。 + +### API + +* [sklearn.preprocessing.StandardScaler API](http://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.StandardScaler.html) +* [sklearn.preprocessing.MinMaxScaler API](http://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MinMaxScaler.html) +* [keras.utils.to_categorical API](https://keras.io/utils/#to_categorical) +* [Keras 卷积层](https://keras.io/layers/convolutional/) + +### 用品 + +* [活动识别,维基百科](https://en.wikipedia.org/wiki/Activity_recognition) +* [来自单个胸部加速计数据集](https://archive.ics.uci.edu/ml/datasets/Activity+Recognition+from+Single+Chest-Mounted+Accelerometer),UCI 机器学习库的活动识别。 + +## 摘要 + +在本教程中,您发现了一个用于时间序列分类的标准人类活动识别数据集。 + +具体来说,你学到了: + +* 如何加载和准备人类活动识别时间序列分类数据。 +* 如何探索和可视化时间序列分类数据,以生成建模的想法。 +* 一套用于构建问题,准备数据,建模和评估人类活动识别模型的方法。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/how-to-load-and-explore-household-electricity-usage-data.md b/docs/dl-ts/how-to-load-and-explore-household-electricity-usage-data.md new file mode 100644 index 0000000000000000000000000000000000000000..d8a56cb82e01295f04d742addfed374b7380ac0f --- /dev/null +++ b/docs/dl-ts/how-to-load-and-explore-household-electricity-usage-data.md @@ -0,0 +1,644 @@ +# 如何加载和探索家庭用电数据 + +> 原文: [https://machinelearningmastery.com/how-to-load-and-explore-household-electricity-usage-data/](https://machinelearningmastery.com/how-to-load-and-explore-household-electricity-usage-data/) + +鉴于智能电表的兴起以及太阳能电池板等发电技术的广泛采用,可提供大量的用电数据。 + +该数据代表功率相关变量的多变量时间序列,而这些变量又可用于建模甚至预测未来的电力消耗。 + +在本教程中,您将发现用于多步时间序列预测的家庭功耗数据集,以及如何使用探索性分析更好地理解原始数据。 + +完成本教程后,您将了解: + +* 家庭用电量数据集,描述四年内单个房屋的用电量。 +* 如何使用一系列线图来探索和理解数据集,用于数据分布的系列数据和直方图。 +* 如何使用对问题的新理解来考虑预测问题的不同框架,可以准备数据的方式以及可以使用的建模方法。 + +让我们开始吧。 + +![How to Load and Explore Household Electricity Usage Data](img/979571fe0f3efdae33bd28e903ea41b5.jpg) + +如何加载和探索家庭用电数据 +[Sheila Sund](https://www.flickr.com/photos/sheila_sund/24762233519/) 的照片,保留一些权利。 + +## 教程概述 + +本教程分为五个部分;他们是: + +1. 功耗数据集 +2. 加载数据集 +3. 随着时间的推移观察模式 +4. 时间序列数据分布 +5. 关于建模的想法 + +## 家庭用电量数据集 + +[家庭用电量](https://archive.ics.uci.edu/ml/datasets/individual+household+electric+power+consumption)数据集是一个多变量时间序列数据集,描述了四年内单个家庭的用电量。 + +该数据是在 2006 年 12 月至 2010 年 11 月之间收集的,并且每分钟收集家庭内的能耗观察结果。 + +它是一个多变量系列,由七个变量组成(除日期和时间外);他们是: + +* **global_active_power** :家庭消耗的总有功功率(千瓦)。 +* **global_reactive_power** :家庭消耗的总无功功率(千瓦)。 +* **电压**:平均电压(伏特)。 +* **global_intensity** :平均电流强度(安培)。 +* **sub_metering_1** :厨房的有功电能(瓦特小时的有功电能)。 +* **sub_metering_2** :用于洗衣的有功能量(瓦特小时的有功电能)。 +* **sub_metering_3** :气候控制系统的有功电能(瓦特小时的有功电能)。 + +有功和无功电能参考[交流电](https://en.wikipedia.org/wiki/AC_power)的技术细节。 + +一般而言,有功能量是家庭消耗的实际功率,而无功能量是线路中未使用的功率。 + +我们可以看到,数据集通过房屋中的主电路,特别是厨房,洗衣房和气候控制,提供有功功率以及有功功率的某种划分。这些不是家庭中的所有电路。 + +通过首先将有功能量转换为瓦特小时,然后以瓦时为单位减去其他亚计量有功能量,可以从有功能量计算剩余瓦特小时,如下所示: + +``` +sub_metering_remainder = (global_active_power * 1000 / 60) - (sub_metering_1 + sub_metering_2 + sub_metering_3) +``` + +数据集似乎是在没有开创性参考文件的情况下提供的。 + +尽管如此,该数据集已成为评估多步预测的时间序列预测和机器学习方法的标准,特别是用于预测有功功率。此外,尚不清楚数据集中的其他特征是否可以使模型在预测有功功率方面受益。 + +## 加载数据集 + +数据集可以从 UCI 机器学习库下载为单个 20 兆字节的.zip 文件: + +* [household_power_consumption.zip](https://archive.ics.uci.edu/ml/machine-learning-databases/00235/household_power_consumption.zip) + +下载数据集并将其解压缩到当前工作目录中。您现在将拥有大约 127 兆字节的文件“ _household_power_consumption.txt_ ”并包含所有观察结果 + +检查数据文件。 + +以下是原始数据文件中的前五行数据(和标题)。 + +``` +Date;Time;Global_active_power;Global_reactive_power;Voltage;Global_intensity;Sub_metering_1;Sub_metering_2;Sub_metering_3 +16/12/2006;17:24:00;4.216;0.418;234.840;18.400;0.000;1.000;17.000 +16/12/2006;17:25:00;5.360;0.436;233.630;23.000;0.000;1.000;16.000 +16/12/2006;17:26:00;5.374;0.498;233.290;23.000;0.000;2.000;17.000 +16/12/2006;17:27:00;5.388;0.502;233.740;23.000;0.000;1.000;17.000 +16/12/2006;17:28:00;3.666;0.528;235.680;15.800;0.000;1.000;17.000 +... +``` + +我们可以看到数据列用分号分隔('_;_ ')。 + +据报道,该数据在该时间段内每天有一行。 + +数据确实缺少值;例如,我们可以在 28/4/2007 左右看到 2-3 天的缺失数据。 + +``` +... +28/4/2007;00:20:00;0.492;0.208;236.240;2.200;0.000;0.000;0.000 +28/4/2007;00:21:00;?;?;?;?;?;?; +28/4/2007;00:22:00;?;?;?;?;?;?; +28/4/2007;00:23:00;?;?;?;?;?;?; +28/4/2007;00:24:00;?;?;?;?;?;?; +... +``` + +我们可以通过将数据文件作为 Pandas DataFrame 加载并总结加载的数据来启动。 + +我们可以使用 [read_csv()函数](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_csv.html)来加载数据。 + +使用此功能很容易加载数据,但正确加载它有点棘手。 + +具体来说,我们需要做一些自定义的事情: + +* 将列之间的单独值指定为分号(sep =';') +* 指定第 0 行具有列的名称(header = 0) +* 指定我们有大量的 RAM 来避免警告我们将数据作为对象数组而不是数组加载,因为缺少数据的'?'值(low_memory = False)。 +* 指定 Pandas 在解析日期时尝试推断日期时间格式是可以的,这样会更快(infer_datetime_format = True) +* 指定我们要将日期和时间列一起解析为名为“datetime”的新列(parse_dates = {'datetime':[0,1]}) +* 指定我们希望新的“datetime”列成为 DataFrame 的索引(index_col = ['datetime'])。 + +将所有这些放在一起,我们现在可以加载数据并汇总加载的形状和前几行。 + +``` +# load all data +dataset = read_csv('household_power_consumption.txt', sep=';', header=0, low_memory=False, infer_datetime_format=True, parse_dates={'datetime':[0,1]}, index_col=['datetime']) +# summarize +print(dataset.shape) +print(dataset.head()) +``` + +接下来,我们可以使用带有 NaN 值的“?”字符标记所有缺失值,这是一个浮点数。 + +这将允许我们将数据作为一个浮点值数组而不是混合类型来处理,效率较低。 + +``` +# mark all missing values +dataset.replace('?', nan, inplace=True) +``` + +现在,我们可以使用上一节中的计算创建一个包含剩余子计量的新列。 + +``` +# add a column for for the remainder of sub metering +values = dataset.values.astype('float32') +dataset['sub_metering_4'] = (values[:,0] * 1000 / 60) - (values[:,4] + values[:,5] + values[:,6]) +``` + +我们现在可以将清理后的数据集版本保存到新文件中;在这种情况下,我们只需将文件扩展名更改为.csv,并将数据集保存为“ _household_power_consumption.csv_ ”。 + +``` +# save updated dataset +dataset.to_csv('household_power_consumption.csv') +``` + +为了确认我们没有弄乱,我们可以重新加载数据集并汇总前五行。 + +``` +# load the new file +dataset = read_csv('household_power_consumption.csv', header=None) +print(dataset.head()) +``` + +将所有这些结合在一起,下面列出了加载,清理和保存数据集的完整示例。 + +``` +# load and clean-up data +from numpy import nan +from pandas import read_csv +# load all data +dataset = read_csv('household_power_consumption.txt', sep=';', header=0, low_memory=False, infer_datetime_format=True, parse_dates={'datetime':[0,1]}, index_col=['datetime']) +# summarize +print(dataset.shape) +print(dataset.head()) +# mark all missing values +dataset.replace('?', nan, inplace=True) +# add a column for for the remainder of sub metering +values = dataset.values.astype('float32') +dataset['sub_metering_4'] = (values[:,0] * 1000 / 60) - (values[:,4] + values[:,5] + values[:,6]) +# save updated dataset +dataset.to_csv('household_power_consumption.csv') +# load the new dataset and summarize +dataset = read_csv('household_power_consumption.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +print(dataset.head()) +``` + +运行该示例首先加载原始数据并汇总加载数据的形状和前五行。 + +``` +(2075259, 7) + + Global_active_power ... Sub_metering_3 +datetime ... +2006-12-16 17:24:00 4.216 ... 17.0 +2006-12-16 17:25:00 5.360 ... 16.0 +2006-12-16 17:26:00 5.374 ... 17.0 +2006-12-16 17:27:00 5.388 ... 17.0 +2006-12-16 17:28:00 3.666 ... 17.0 +``` + +然后清理数据集并将其保存到新文件中。 + +我们加载这个新文件并再次打印前五行,显示删除日期和时间列以及添加新的子计量列。 + +``` + Global_active_power ... sub_metering_4 +datetime ... +2006-12-16 17:24:00 4.216 ... 52.266670 +2006-12-16 17:25:00 5.360 ... 72.333336 +2006-12-16 17:26:00 5.374 ... 70.566666 +2006-12-16 17:27:00 5.388 ... 71.800000 +2006-12-16 17:28:00 3.666 ... 43.100000 +``` + +我们可以查看新的' _household_power_consumption.csv_ '文件并检查缺失的观察结果是否用空列标记,大熊猫将正确读作 NaN,例如第 190,499 行: + +``` +... +2007-04-28 00:20:00,0.492,0.208,236.240,2.200,0.000,0.000,0.0,8.2 +2007-04-28 00:21:00,,,,,,,, +2007-04-28 00:22:00,,,,,,,, +2007-04-28 00:23:00,,,,,,,, +2007-04-28 00:24:00,,,,,,,, +2007-04-28 00:25:00,,,,,,,, +... +``` + +现在我们已经清理了数据集版本,我们可以使用可视化进一步调查它。 + +## 随着时间的推移观察模式 + +数据是多变量时间序列,理解时间序列的最佳方法是创建线图。 + +我们可以从为八个变量中的每一个创建单独的线图开始。 + +下面列出了完整的示例。 + +``` +# line plots +from pandas import read_csv +from matplotlib import pyplot +# load the new file +dataset = read_csv('household_power_consumption.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +# line plot for each variable +pyplot.figure() +for i in range(len(dataset.columns)): + pyplot.subplot(len(dataset.columns), 1, i+1) + name = dataset.columns[i] + pyplot.plot(dataset[name]) + pyplot.title(name, y=0) +pyplot.show() +``` + +运行该示例将创建一个包含八个子图的单个图像,每个图对应一个变量。 + +这给了我们四分之一分钟观测的真正高水平。我们可以看到' _Sub_metering_3_ '(环境控制)中可能没有直接映射到炎热或寒冷年份的有趣事情。也许安装了新系统。 + +有趣的是,' _sub_metering_4_ '的贡献似乎随着时间的推移而减少,或呈现下降趋势,可能与' _Sub_metering_3_ 系列末尾的稳固增长相匹配”。 + +这些观察确实强调了在拟合和评估任何模型时遵守该数据的子序列的时间顺序的需要。 + +我们或许可以在' _Global_active_power_ '和其他一些变量中看到季节性影响的波动。 + +有一些尖刻的用法可能与特定时期相匹配,例如周末。 + +![Line Plots of Each Variable in the Power Consumption Dataset](img/fe1f95966fa74970b2f080b2b4ac94c0.jpg) + +功耗数据集中每个变量的线图 + +让我们放大并专注于' _Global_active_power_ '或'_ 有功功率 _'。 + +我们可以为每年创建一个新的有效功率图,以查看这些年来是否存在任何共同模式。 2006 年的第一年,有不到一个月的数据,所以将其从情节中删除。 + +下面列出了完整的示例。 + +``` +# yearly line plots +from pandas import read_csv +from matplotlib import pyplot +# load the new file +dataset = read_csv('household_power_consumption.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +# plot active power for each year +years = ['2007', '2008', '2009', '2010'] +pyplot.figure() +for i in range(len(years)): + # prepare subplot + ax = pyplot.subplot(len(years), 1, i+1) + # determine the year to plot + year = years[i] + # get all observations for the year + result = dataset[str(year)] + # plot the active power for the year + pyplot.plot(result['Global_active_power']) + # add a title to the subplot + pyplot.title(str(year), y=0, loc='left') +pyplot.show() +``` + +运行该示例将创建一个包含四个线图的单个图像,一个数据集中的每年全年(或大部分为全年)数据。 + +我们可以看到多年来的一些共同的总体模式,例如 2 月至 3 月左右和 8 月至 9 月左右,我们看到消费明显减少。 + +在夏季月份(北半球的年中),我们似乎也看到了下降的趋势,并且可能在冬季月份向地块的边缘消耗更多。这些可能显示出消费的年度季节性模式。 + +我们还可以在至少第一,第三和第四个图中看到一些缺失数据。 + +![Line Plots of Active Power for Most Years](img/d953c25c6942e3d93fd4dcfe23fca050.jpg) + +大多数年份的有功功率线图 + +我们可以继续放大消费量,并在 2007 年的 12 个月中查看有功功率。 + +这可能有助于梳理整个月的总体结构,例如每日和每周模式。 + +下面列出了完整的示例。 + +``` +# monthly line plots +from pandas import read_csv +from matplotlib import pyplot +# load the new file +dataset = read_csv('household_power_consumption.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +# plot active power for each year +months = [x for x in range(1, 13)] +pyplot.figure() +for i in range(len(months)): + # prepare subplot + ax = pyplot.subplot(len(months), 1, i+1) + # determine the month to plot + month = '2007-' + str(months[i]) + # get all observations for the month + result = dataset[month] + # plot the active power for the month + pyplot.plot(result['Global_active_power']) + # add a title to the subplot + pyplot.title(month, y=0, loc='left') +pyplot.show() +``` + +运行该示例将创建一个包含 12 个线图的单个图像,2007 年每个月一个。 + +我们可以看到每个月内的日耗电的符号波。这很好,因为我们期望在功耗方面有某种日常模式。 + +我们可以看到,有很少的日子消费很少,例如 8 月和 4 月。这些可能代表住宅无人居住且耗电量最小的假期。 + +![Line Plots for Active Power for All Months in One Year](img/2314a324101c61c23134e1f690302c5b.jpg) + +一年内所有月的有功功率线图 + +最后,我们可以放大一个级别,并仔细查看每日级别的功耗。 + +我们预计每天会有一些消费模式,也许一周内的天数差异。 + +下面列出了完整的示例。 + +``` +# daily line plots +from pandas import read_csv +from matplotlib import pyplot +# load the new file +dataset = read_csv('household_power_consumption.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +# plot active power for each year +days = [x for x in range(1, 20)] +pyplot.figure() +for i in range(len(days)): + # prepare subplot + ax = pyplot.subplot(len(days), 1, i+1) + # determine the day to plot + day = '2007-01-' + str(days[i]) + # get all observations for the day + result = dataset[day] + # plot the active power for the day + pyplot.plot(result['Global_active_power']) + # add a title to the subplot + pyplot.title(day, y=0, loc='left') +pyplot.show() +``` + +运行该示例将创建一个包含 20 个线图的单个图像,一个用于 2007 年 1 月的前 20 天。 + +这些日子有共同之处;例如,很多天消费开始于凌晨 6 点到 7 点左右。 + +有些日子显示当天中午消费量下降,如果大多数人都不在家,这可能是有意义的。 + +我们确实看到有些日子有一些强烈的隔夜消费,在北半球,1 月可能与使用的供暖系统相匹配。 + +如预期的那样,一年中的时间,特别是它带来的季节和天气,将是对这些数据进行建模的重要因素。 + +![Line Plots for Active Power for 20 Days in One Month](img/5734511d8648828979dcbdc65010e778.jpg) + +一个月内 20 天的有功功率线图 + +## 时间序列数据分布 + +另一个需要考虑的重要领域是变量的分布。 + +例如,知道观测的分布是高斯分布还是其他分布可能是有趣的。 + +我们可以通过查看直方图来调查数据的分布。 + +我们可以通过为时间序列中的每个变量创建直方图来开始。 + +下面列出了完整的示例。 + +``` +# histogram plots +from pandas import read_csv +from matplotlib import pyplot +# load the new file +dataset = read_csv('household_power_consumption.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +# histogram plot for each variable +pyplot.figure() +for i in range(len(dataset.columns)): + pyplot.subplot(len(dataset.columns), 1, i+1) + name = dataset.columns[i] + dataset[name].hist(bins=100) + pyplot.title(name, y=0) +pyplot.show() +``` + +运行该示例会为 8 个变量中的每个变量创建一个单独的直方图。 + +我们可以看到,有功和无功功率,强度以及分计量功率都是偏向低瓦特小时或千瓦值的分布。 + +我们还可以看到电压数据的分布是强高斯分布的。 + +![Histogram plots for Each Variable in the Power Consumption Dataset](img/a83635284c2a1866e245d14761b72734.jpg) + +功耗数据集中每个变量的直方图 + +有功功率的分布似乎是双模态的,这意味着它看起来像有两组平均观察结果。 + +我们可以通过查看四年全年数据的有功功耗分布来进一步研究。 + +下面列出了完整的示例。 + +``` +# yearly histogram plots +from pandas import read_csv +from matplotlib import pyplot +# load the new file +dataset = read_csv('household_power_consumption.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +# plot active power for each year +years = ['2007', '2008', '2009', '2010'] +pyplot.figure() +for i in range(len(years)): + # prepare subplot + ax = pyplot.subplot(len(years), 1, i+1) + # determine the year to plot + year = years[i] + # get all observations for the year + result = dataset[str(year)] + # plot the active power for the year + result['Global_active_power'].hist(bins=100) + # zoom in on the distribution + ax.set_xlim(0, 5) + # add a title to the subplot + pyplot.title(str(year), y=0, loc='right') +pyplot.show() +``` + +运行该示例将创建一个包含四个数字的单个图,每个图表在 2007 年到 2010 年之间的每一年。 + +我们可以看到这些年来有功功率消耗的分布看起来非常相似。分布确实是双峰的,一个峰值约为 0.3 千瓦,也许另一个峰值约为 1.3 千瓦。 + +分配到更高的千瓦值有一个长尾。它可能为将数据离散化并将其分为峰 1,峰 2 或长尾的概念敞开大门。这些用于一天或一小时的组或群集可能有助于开发预测模型。 + +![Histogram Plots of Active Power for Most Years](img/9efa29dc901b1dbfd650d33c419c3127.jpg) + +大多数年份的有功功率直方图 + +所确定的群体可能在一年中的季节中变化。 + +我们可以通过查看一年中每个月的有功功率分布来研究这一点。 + +下面列出了完整的示例。 + +``` +# monthly histogram plots +from pandas import read_csv +from matplotlib import pyplot +# load the new file +dataset = read_csv('household_power_consumption.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +# plot active power for each year +months = [x for x in range(1, 13)] +pyplot.figure() +for i in range(len(months)): + # prepare subplot + ax = pyplot.subplot(len(months), 1, i+1) + # determine the month to plot + month = '2007-' + str(months[i]) + # get all observations for the month + result = dataset[month] + # plot the active power for the month + result['Global_active_power'].hist(bins=100) + # zoom in on the distribution + ax.set_xlim(0, 5) + # add a title to the subplot + pyplot.title(month, y=0, loc='right') +pyplot.show() +``` + +运行该示例将创建一个包含 12 个图的图像,2007 年每个月一个。 + +我们每个月可以看到相同的数据分布。图中的轴似乎对齐(给定相似的比例),我们可以看到峰值在北半球温暖的月份向下移动,并在较冷的月份向上移动。 + +在 12 月至 3 月的较冷月份,我们还可以看到更大或更突出的尾部朝向更大的千瓦值。 + +![Histogram Plots for Active Power for All Months in One Year](img/146f0e0a431f36c255d670e7d5eeab23.jpg) + +一年内所有月份的有功功率直方图 + +## 关于建模的想法 + +现在我们知道了如何加载和探索数据集,我们可以提出一些关于如何建模数据集的想法。 + +在本节中,我们将在处理数据时仔细研究三个主要方面;他们是: + +* 问题框架 +* 数据准备 +* 建模方法 + +### 问题框架 + +似乎没有关于数据集的开创性出版物来演示在预测建模问题中构建数据的预期方法。 + +因此,我们可能会猜测可能使用这些数据的有用方法。 + +这些数据仅适用于单个家庭,但也许有效的建模方法可以推广到类似的家庭。 + +也许数据集最有用的框架是预测未来有效功耗的间隔。 + +四个例子包括: + +* 预测第二天的每小时消耗量。 +* 预测下周的每日消费量。 +* 预测下个月的每日消费量。 +* 预测下一年的月消费量。 + +通常,这些类型的预测问题称为多步预测。使用所有变量的模型可称为多变量多步预测模型。 + +这些模型中的每一个都不限于预测微小数据,而是可以将问题建模为所选预测分辨率或低于所选预测分辨率。 + +按规模预测消费可以帮助公用事业公司预测需求,这是一个广泛研究和重要的问题。 + +### 数据准备 + +在为建模准备这些数据时有很大的灵活性。 + +具体的数据准备方法及其益处实际上取决于所选择的问题框架和建模方法。不过,下面列出了可能有用的一般数据准备方法: + +* 每日差异可用于调整数据中的每日循环。 +* 年度差异可用于调整数据中的任何年度周期。 +* 归一化可以帮助将具有不同单位的变量减少到相同的比例。 + +有许多简单的人为因素可能有助于数据的工程特征,反过来可能使特定的日子更容易预测。 + +一些例子包括: + +* 指示一天中的时间,以说明人们回家的可能性。 +* 指示一天是工作日还是周末。 +* 指示某一天是否是北美公众假期。 + +这些因素对于预测月度数据可能要少得多,也许在每周数据的程度上要少得多。 + +更一般的功能可能包括: + +* 指示季节,这可能导致使用的环境控制系统的类型或数量。 + +### 建模方法 + +对于这个问题,可能有四类方法可能很有趣;他们是: + +* 天真的方法。 +* 经典线性方法。 +* 机器学习方法。 +* 深度学习方法。 + +#### 天真的方法 + +天真的方法将包括做出非常简单但通常非常有效的假设的方法。 + +一些例子包括: + +* 明天将和今天一样。 +* 明天将与去年的这一天相同。 +* 明天将是过去几天的平均值。 + +#### 经典线性方法 + +经典线性方法包括对单变量时间序列预测非常有效的技术。 + +两个重要的例子包括: + +* SARIMA +* ETS(三指数平滑) + +他们需要丢弃其他变量,并将模型的参数配置或调整到数据集的特定框架。还可以直接支持与调整日常和季节性结构数据相关的问题。 + +#### 机器学习方法 + +机器学习方法要求将问题构成监督学习问题。 + +这将要求将系列的滞后观察框架化为输入特征,从而丢弃数据中的时间关系。 + +可以探索一套非线性和集合方法,包括: + +* k-最近邻居。 +* 支持向量机 +* 决策树 +* 随机森林 +* 梯度增压机 + +需要特别注意确保这些模型的拟合和评估保留了数据中的时间结构。这很重要,因此该方法无法通过利用未来的观测结果来“欺骗”。 + +这些方法通常与大量变量无关,可能有助于弄清楚是否可以利用其他变量并为预测模型增加价值。 + +#### 深度学习方法 + +通常,神经网络在自回归类型问题上未被证明非常有效。 + +然而,诸如卷积神经网络的技术能够从原始数据(包括一维信号数据)自动学习复杂特征。并且诸如长短期存储器网络之类的循环神经网络能够直接学习输入数据的多个并行序列。 + +此外,这些方法的组合,例如 CNN LSTM 和 ConvLSTM,已经证明在时间序列分类任务上是有效的。 + +这些方法可能能够利用大量基于分钟的数据和多个输入变量。 + +## 进一步阅读 + +如果您希望深入了解,本节将提供有关该主题的更多资源。 + +* [个人家庭用电量数据集,UCI 机器学习库](https://archive.ics.uci.edu/ml/datasets/individual+household+electric+power+consumption)。 +* [交流电源,维基百科。](https://en.wikipedia.org/wiki/AC_power#Active,_reactive,_and_apparent_power) +* [pandas.read_csv API](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_csv.html) + +## 摘要 + +在本教程中,您发现了用于多步时间序列预测的家庭功耗数据集,以及如何使用探索性分析更好地理解原始数据。 + +具体来说,你学到了: + +* 家庭用电量数据集,描述四年内单个房屋的用电量。 +* 如何使用一系列线图来探索和理解数据集,用于数据分布的系列数据和直方图。 +* 如何使用对问题的新理解来考虑预测问题的不同框架,可以准备数据的方式以及可以使用的建模方法。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/how-to-load-visualize-and-explore-a-complex-multivariate-multistep-time-series-forecasting-dataset.md b/docs/dl-ts/how-to-load-visualize-and-explore-a-complex-multivariate-multistep-time-series-forecasting-dataset.md new file mode 100644 index 0000000000000000000000000000000000000000..e005edbc4ac297e7d4879479b40180518e8ef237 --- /dev/null +++ b/docs/dl-ts/how-to-load-visualize-and-explore-a-complex-multivariate-multistep-time-series-forecasting-dataset.md @@ -0,0 +1,1453 @@ +# 如何加载,可视化和探索复杂的多变量多步时间序列预测数据集 + +> 原文: [https://machinelearningmastery.com/how-to-load-visualize-and-explore-a-complex-multivariate-multistep-time-series-forecasting-dataset/](https://machinelearningmastery.com/how-to-load-visualize-and-explore-a-complex-multivariate-multistep-time-series-forecasting-dataset/) + +实时世界时间序列预测具有挑战性,其原因不仅限于问题特征,例如具有多个输入变量,需要预测多个时间步骤,以及需要对多个物理站点执行相同类型的预测。 + +EMC Data Science Global Hackathon 数据集或简称“_ 空气质量预测 _”数据集描述了多个站点的天气状况,需要预测随后三天的空气质量测量结果。 + +在本教程中,您将发现并探索空气质量预测数据集,该数据集代表具有挑战性的多变量,多站点和多步骤时间序列预测问题。 + +完成本教程后,您将了解: + +* 如何加载和探索数据集的块结构。 +* 如何探索和可视化数据集的输入和目标变量。 +* 如何使用新的理解来概述一系列方法来构建问题,准备数据和建模数据集。 + +让我们开始吧。 + +* **Update Apr / 2019** :修正了计算总缺失值的错误(感谢 zhangzhe)。 + +![How to Load, Visualize, and Explore a Complex Multivariate Multistep Time Series Forecasting Dataset](img/b66e2ba197d451dab544125787d5de85.jpg) + +如何加载,可视化和探索复杂的多变量多步时间序列预测数据集 +照片由 [H Matthew Howarth](https://www.flickr.com/photos/flatworldsedge/7874109806/) ,保留一些权利。 + +## 教程概述 + +本教程分为七个部分;他们是: + +1. 问题描述 +2. 加载数据集 +3. 块数据结构 +4. 输入变量 +5. 目标变量 +6. 与目标变量的皱痕 +7. 关于建模的思考 + +## 问题描述 + +EMC Data Science Global Hackathon 数据集或简称“_ 空气质量预测 _”数据集描述了多个站点的天气状况,需要预测随后三天的空气质量测量结果。 + +具体而言,对于多个站点,每小时提供 8 天的温度,压力,风速和风向等天气观测。目标是预测未来三天在多个地点的空气质量测量。预测的提前期不是连续的;相反,必须在 72 小时预测期内预测具体的提前期;他们是: + +``` ++1, +2, +3, +4, +5, +10, +17, +24, +48, +72 +``` + +此外,数据集被划分为不相交但连续的数据块,其中 8 天的数据随后是需要预测的 3 天。 + +并非所有站点或块都可以获得所有观察结果,并且并非所有站点和块都可以使用所有输出变量。必须解决大部分缺失数据。 + +该数据集被用作 2012 年 Kaggle 网站上[短期机器学习竞赛](https://www.kaggle.com/c/dsg-hackathon)(或黑客马拉松)的基础。 + +根据从参与者中扣留的真实观察结果评估竞赛的提交,并使用平均绝对误差(MAE)进行评分。提交要求在由于缺少数据而无法预测的情况下指定-1,000,000 的值。实际上,提供了一个插入缺失值的模板,并且要求所有提交都采用(模糊的是什么)。 + +获胜者在滞留测试集([私人排行榜](https://www.kaggle.com/c/dsg-hackathon/leaderboard))上使用随机森林在滞后观察中获得了 0.21058 的 MAE。该帖子中提供了此解决方案的说明: + +* [把所有东西都扔进随机森林:Ben Hamner 赢得空气质量预测黑客马拉松](http://blog.kaggle.com/2012/05/01/chucking-everything-into-a-random-forest-ben-hamner-on-winning-the-air-quality-prediction-hackathon/),2012。 + +在本教程中,我们将探索此数据集,以便更好地了解预测问题的性质,并提出如何建模的方法。 + +## 加载数据集 + +第一步是下载数据集并将其加载到内存中。 + +数据集可以从 Kaggle 网站免费下载。您可能必须创建一个帐户并登录才能下载数据集。 + +下载整个数据集,例如“_ 将所有 _”下载到您的工作站并使用名为' _AirQualityPrediction_ '的文件夹解压缩当前工作目录中的存档 + +* [EMC 数据科学全球黑客马拉松(空气质量预测)数据](https://www.kaggle.com/c/dsg-hackathon/data) + +你应该在 _AirQualityPrediction /_ 文件夹中有五个文件;他们是: + +* SiteLocations.csv +* SiteLocations_with_more_sites.csv +* SubmissionZerosExceptNAs.csv +* TrainingData.csv +* sample_code.r + +我们的重点将是包含训练数据集的' _TrainingData.csv_ ',特别是块中的数据,其中每个块是八个连续的观察日和目标变量。 + +在撰写本文时,测试数据集(每个块的剩余三天)不可用于此数据集。 + +打开' _TrainingData.csv_ '文件并查看内容。解压缩的数据文件相对较小(21 兆字节),很容易适应 RAM。 + +查看文件的内容,我们可以看到数据文件包含标题行。 + +我们还可以看到丢失的数据标有' _NA_ '值,Pandas 将自动转换为 _NumPy.NaN_ 。 + +我们可以看到'_ 工作日 _'列包含作为字符串的日期,而所有其他数据都是数字。 + +以下是数据文件的前几行供参考。 + +``` +"rowID","chunkID","position_within_chunk","month_most_common","weekday","hour","Solar.radiation_64","WindDirection..Resultant_1","WindDirection..Resultant_1018","WindSpeed..Resultant_1","WindSpeed..Resultant_1018","Ambient.Max.Temperature_14","Ambient.Max.Temperature_22","Ambient.Max.Temperature_50","Ambient.Max.Temperature_52","Ambient.Max.Temperature_57","Ambient.Max.Temperature_76","Ambient.Max.Temperature_2001","Ambient.Max.Temperature_3301","Ambient.Max.Temperature_6005","Ambient.Min.Temperature_14","Ambient.Min.Temperature_22","Ambient.Min.Temperature_50","Ambient.Min.Temperature_52","Ambient.Min.Temperature_57","Ambient.Min.Temperature_76","Ambient.Min.Temperature_2001","Ambient.Min.Temperature_3301","Ambient.Min.Temperature_6005","Sample.Baro.Pressure_14","Sample.Baro.Pressure_22","Sample.Baro.Pressure_50","Sample.Baro.Pressure_52","Sample.Baro.Pressure_57","Sample.Baro.Pressure_76","Sample.Baro.Pressure_2001","Sample.Baro.Pressure_3301","Sample.Baro.Pressure_6005","Sample.Max.Baro.Pressure_14","Sample.Max.Baro.Pressure_22","Sample.Max.Baro.Pressure_50","Sample.Max.Baro.Pressure_52","Sample.Max.Baro.Pressure_57","Sample.Max.Baro.Pressure_76","Sample.Max.Baro.Pressure_2001","Sample.Max.Baro.Pressure_3301","Sample.Max.Baro.Pressure_6005","Sample.Min.Baro.Pressure_14","Sample.Min.Baro.Pressure_22","Sample.Min.Baro.Pressure_50","Sample.Min.Baro.Pressure_52","Sample.Min.Baro.Pressure_57","Sample.Min.Baro.Pressure_76","Sample.Min.Baro.Pressure_2001","Sample.Min.Baro.Pressure_3301","Sample.Min.Baro.Pressure_6005","target_1_57","target_10_4002","target_10_8003","target_11_1","target_11_32","target_11_50","target_11_64","target_11_1003","target_11_1601","target_11_4002","target_11_8003","target_14_4002","target_14_8003","target_15_57","target_2_57","target_3_1","target_3_50","target_3_57","target_3_1601","target_3_4002","target_3_6006","target_4_1","target_4_50","target_4_57","target_4_1018","target_4_1601","target_4_2001","target_4_4002","target_4_4101","target_4_6006","target_4_8003","target_5_6006","target_7_57","target_8_57","target_8_4002","target_8_6004","target_8_8003","target_9_4002","target_9_8003" +1,1,1,10,"Saturday",21,0.01,117,187,0.3,0.3,NA,NA,NA,14.9,NA,NA,NA,NA,NA,NA,NA,NA,5.8,NA,NA,NA,NA,NA,NA,NA,NA,747,NA,NA,NA,NA,NA,NA,NA,NA,750,NA,NA,NA,NA,NA,NA,NA,NA,743,NA,NA,NA,NA,NA,2.67923294292042,6.1816228132982,NA,0.114975168664303,0.114975168664303,0.114975168664303,0.114975168664303,0.114975168664303,0.114975168664303,0.114975168664303,NA,2.38965627997991,NA,5.56815355612325,0.690015329704154,NA,NA,NA,NA,NA,NA,2.84349016287551,0.0920223353681394,1.69321097077376,0.368089341472558,0.184044670736279,0.368089341472558,0.276067006104418,0.892616653070952,1.74842437199465,NA,NA,5.1306307034019,1.34160578423204,2.13879182993514,3.01375212399952,NA,5.67928016629218,NA +2,1,2,10,"Saturday",22,0.01,231,202,0.5,0.6,NA,NA,NA,14.9,NA,NA,NA,NA,NA,NA,NA,NA,5.8,NA,NA,NA,NA,NA,NA,NA,NA,747,NA,NA,NA,NA,NA,NA,NA,NA,750,NA,NA,NA,NA,NA,NA,NA,NA,743,NA,NA,NA,NA,NA,2.67923294292042,8.47583334194495,NA,0.114975168664303,0.114975168664303,0.114975168664303,0.114975168664303,0.114975168664303,0.114975168664303,0.114975168664303,NA,1.99138023331659,NA,5.56815355612325,0.923259948195698,NA,NA,NA,NA,NA,NA,3.1011527019063,0.0920223353681394,1.94167127626774,0.368089341472558,0.184044670736279,0.368089341472558,0.368089341472558,1.73922213845783,2.14412041407765,NA,NA,5.1306307034019,1.19577906855465,2.72209869264472,3.88871241806389,NA,7.42675098668978,NA +3,1,3,10,"Saturday",23,0.01,247,227,0.5,1.5,NA,NA,NA,14.9,NA,NA,NA,NA,NA,NA,NA,NA,5.8,NA,NA,NA,NA,NA,NA,NA,NA,747,NA,NA,NA,NA,NA,NA,NA,NA,750,NA,NA,NA,NA,NA,NA,NA,NA,743,NA,NA,NA,NA,NA,2.67923294292042,8.92192983362627,NA,0.114975168664303,0.114975168664303,0.114975168664303,0.114975168664303,0.114975168664303,0.114975168664303,0.114975168664303,NA,1.7524146053186,NA,5.56815355612325,0.680296803933673,NA,NA,NA,NA,NA,NA,3.06434376775904,0.0920223353681394,2.52141198908702,0.460111676840697,0.184044670736279,0.368089341472558,0.368089341472558,1.7852333061419,1.93246904273093,NA,NA,5.13639545700122,1.40965825154816,3.11096993445111,3.88871241806389,NA,7.68373198968942,NA +4,1,4,10,"Sunday",0,0.01,219,218,0.2,1.2,NA,NA,NA,14,NA,NA,NA,NA,NA,NA,NA,NA,4.8,NA,NA,NA,NA,NA,NA,NA,NA,751,NA,NA,NA,NA,NA,NA,NA,NA,754,NA,NA,NA,NA,NA,NA,NA,NA,748,NA,NA,NA,NA,NA,2.67923294292042,5.09824561921501,NA,0.114975168664303,0.114975168664303,0.114975168664303,0.114975168664303,0.114975168664303,0.114975168664303,0.114975168664303,NA,2.38965627997991,NA,5.6776192223642,0.612267123540305,NA,NA,NA,NA,NA,NA,3.21157950434806,0.184044670736279,2.374176252498,0.460111676840697,0.184044670736279,0.368089341472558,0.276067006104418,1.86805340797323,2.08890701285676,NA,NA,5.21710200739181,1.47771071886428,2.04157401948354,3.20818774490271,NA,4.83124285639335,NA +... +``` + +我们可以使用 Pandas [read_csv()函数](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_csv.html)将数据文件加载到内存中,并在第 0 行指定标题行。 + +``` +# load dataset +dataset = read_csv('AirQualityPrediction/TrainingData.csv', header=0) +``` + +我们还可以快速了解数据集中有多少缺失数据。我们可以通过首先修剪前几列来删除字符串工作日数据并将剩余列转换为浮点值来实现。 + +``` +# trim and transform to floats +values = dataset.values +data = values[:, 6:].astype('float32') +``` + +然后,我们可以计算缺失观测的总数和缺失的值的百分比。 + +``` +# summarize amount of missing data +total_missing = count_nonzero(isnan(data)) +percent_missing = total_missing / data.size * 100 +print('Total Missing: %d/%d (%.1f%%)' % (total_missing, data.size, percent_missing)) +``` + +下面列出了完整的示例。 + +``` +# load dataset +from numpy import isnan +from numpy import count_nonzero +from pandas import read_csv +# load dataset +dataset = read_csv('AirQualityPrediction/TrainingData.csv', header=0) +# summarize +print(dataset.shape) +# trim and transform to floats +values = dataset.values +data = values[:, 6:].astype('float32') +# summarize amount of missing data +total_missing = count_nonzero(isnan(data)) +percent_missing = total_missing / data.size * 100 +print('Total Missing: %d/%d (%.1f%%)' % (total_missing, data.size, percent_missing)) +``` + +首先运行该示例将打印已加载数据集的形状。 + +我们可以看到我们有大约 37,000 行和 95 列。我们知道这些数字是误导的,因为数据实际上被分成块,并且列被分成不同站点的相同观察。 + +我们还可以看到有超过 40%的数据丢失。这很多。数据非常不完整,在建模问题之前我们必须很好地理解这一点。 + +``` +(37821, 95) +Total Missing: 1922092/3366069 (57.1%) +``` + +## 块数据结构 + +一个很好的起点是根据块来查看数据。 + +### 块持续时间 + +我们可以通过'chunkID'变量(列索引 1)对数据进行分组。 + +如果每个块是 8 天并且观察是每小时的,那么我们期望每块(8 * 24)或 192 行数据。 + +如果有 37,821 行数据,则必须有大于或小于 192 小时的块,因为 37,821 / 192 大约是 196.9 块。 + +我们首先将数据拆分成块。我们可以先获得唯一的块标识符列表。 + +``` +chunk_ids = unique(values[:, 1]) +``` + +然后,我们可以收集每个块标识符的所有行,并将它们存储在字典中以便于访问。 + +``` +chunks = dict() +# sort rows by chunk id +for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks[chunk_id] = values[selection, :] +``` + +下面定义了一个名为 _to_chunks()_ 的函数,它接受加载数据的 NumPy 数组,并将 _chunk_id_ 的字典返回到块的行。 + +``` +# split the dataset by 'chunkID', return a dict of id to rows +def to_chunks(values, chunk_ix=1): + chunks = dict() + # get the unique chunk ids + chunk_ids = unique(values[:, chunk_ix]) + # group rows by chunk id + for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks[chunk_id] = values[selection, :] + return chunks +``` + +数据文件中的' _position_within_chunk_ '表示块内行的顺序。在这个阶段,我们假设行已经排序,不需要排序。原始数据文件的略读似乎证实了这一假设。 + +一旦数据被分类成块,我们就可以计算每个块中的行数并查看分布,例如使用框和胡须图。 + +``` +# plot distribution of chunk durations +def plot_chunk_durations(chunks): + # chunk durations in hours + chunk_durations = [len(v) for k,v in chunks.items()] + # boxplot + pyplot.subplot(2, 1, 1) + pyplot.boxplot(chunk_durations) + # histogram + pyplot.subplot(2, 1, 2) + pyplot.hist(chunk_durations) + # histogram + pyplot.show() +``` + +下面列出了将所有这些联系在一起的完整示例 + +``` +# split data into chunks +from numpy import unique +from pandas import read_csv +from matplotlib import pyplot + +# split the dataset by 'chunkID', return a dict of id to rows +def to_chunks(values, chunk_ix=1): + chunks = dict() + # get the unique chunk ids + chunk_ids = unique(values[:, chunk_ix]) + # group rows by chunk id + for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks[chunk_id] = values[selection, :] + return chunks + +# plot distribution of chunk durations +def plot_chunk_durations(chunks): + # chunk durations in hours + chunk_durations = [len(v) for k,v in chunks.items()] + # boxplot + pyplot.subplot(2, 1, 1) + pyplot.boxplot(chunk_durations) + # histogram + pyplot.subplot(2, 1, 2) + pyplot.hist(chunk_durations) + # histogram + pyplot.show() + +# load dataset +dataset = read_csv('AirQualityPrediction/TrainingData.csv', header=0) +# group data by chunks +values = dataset.values +chunks = to_chunks(values) +print('Total Chunks: %d' % len(chunks)) +# plot chunk durations +plot_chunk_durations(chunks) +``` + +首先运行该示例将打印数据集中的块数。 + +我们可以看到有 208,这表明每小时观察的数量确实必须在各个块之间变化。 + +``` +Total Chunks: 208 +``` + +创建框和胡须图以及块持续时间的直方图。我们可以看到中位数确实是 192,这意味着大多数块有八天的观察或接近它。 + +我们还可以看到持续时间长达约 25 行的长尾。虽然这些案例并不多,但鉴于缺乏数据,我们预计这将是一个挑战。 + +该分布还提出了关于每个块内的观察结果可能是多么连续的问题。 + +![Box and whisker plot and Histogram plot of chunk duration in hours](img/c8869e1fb26f7832080d090a99c51e27.jpg) + +盒子和须状图以及以小时为单位的块长度的直方图 + +### 大块连续性 + +了解在没有完整八天数据的那些块中观察是否连续(或不连续)可能会有所帮助。 + +考虑这一点的一种方法是为每个不连续的块创建线图并显示观察中的间隙。 + +我们可以在一个地块上做到这一点。每个块都有一个唯一的标识符,从 1 到 208,我们可以使用它作为序列的值,并通过 _NaN_ 值在 8 天间隔内标记缺失的观察值,这些值不会出现在图上。 + +反过来说,我们可以假设我们对一个块中的所有时间步都有 NaN 值,然后使用' _position_within_chunk_ '列(索引 2)来确定具有值的时间步长并用它们标记它们。块 ID。 + +下面的 _plot_discontinuous_chunks()_ 实现了这种行为,在同一个图上为每个缺少行的块创建一个系列或行。期望的是,突破线将帮助我们看到这些不完整的块是多么连续或不连续。 + +``` +# plot chunks that do not have all data +def plot_discontiguous_chunks(chunks, row_in_chunk_ix=2): + n_steps = 8 * 24 + for c_id,rows in chunks.items(): + # skip chunks with all data + if rows.shape[0] == n_steps: + continue + # create empty series + series = [nan for _ in range(n_steps)] + # mark all rows with data + for row in rows: + # convert to zero offset + r_id = row[row_in_chunk_ix] - 1 + # mark value + series[r_id] = c_id + # plot + pyplot.plot(series) + pyplot.show() +``` + +下面列出了完整的示例。 + +``` +# plot discontiguous chunks +from numpy import nan +from numpy import unique +from pandas import read_csv +from matplotlib import pyplot + +# split the dataset by 'chunkID', return a dict of id to rows +def to_chunks(values, chunk_ix=1): + chunks = dict() + # get the unique chunk ids + chunk_ids = unique(values[:, chunk_ix]) + # group rows by chunk id + for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks[chunk_id] = values[selection, :] + return chunks + +# plot chunks that do not have all data +def plot_discontiguous_chunks(chunks, row_in_chunk_ix=2): + n_steps = 8 * 24 + for c_id,rows in chunks.items(): + # skip chunks with all data + if rows.shape[0] == n_steps: + continue + # create empty series + series = [nan for _ in range(n_steps)] + # mark all rows with data + for row in rows: + # convert to zero offset + r_id = row[row_in_chunk_ix] - 1 + # mark value + series[r_id] = c_id + # plot + pyplot.plot(series) + pyplot.show() + +# load dataset +dataset = read_csv('AirQualityPrediction/TrainingData.csv', header=0) +# group data by chunks +values = dataset.values +chunks = to_chunks(values) +# plot discontiguous chunks +plot_discontiguous_chunks(chunks) +``` + +运行该示例会为每个缺少数据的块创建一个带有一行的图形。 + +每个块的行中断的数量和长度给出了每个块中的观察结果是多么不连续的概念。 + +许多块确实有很长的连续数据,这是建模的一个好兆头。 + +在某些情况下,块的观察结果非常少,而且存在的观察结果是小的连续斑块。模型可能具有挑战性。 + +此外,并非所有这些块都在块的末尾有观察结果:需要预测之前的时间段。对于那些寻求坚持最近观察的模型而言,这些将是一个挑战。 + +块内系列数据的不连续性也将使评估模型具有挑战性。例如,人们不能简单地将块数据分成两半,在前半部分进行训练,在观察结果不完整时对第二部分进行测试。至少,当考虑不完整的块数据时。 + +![Line plots of chunks with discontinuous observations](img/331062f1116fa8e1055698ab47cac15e.jpg) + +具有不连续观察的块的线图 + +### 块内的每日覆盖率 + +块的不连续性也表明,查看每个块所覆盖的小时数可能很重要。 + +一天中的时间在环境数据中很重要,并且假设每个块包含相同的每日或每周周期的模型可能会发生绊倒,如果一天的开始和结束时间不同。 + +我们可以通过绘制每个块的第一个小时(每天 24 小时)的分布来快速检查这一点。 + +柱状图中的区间数设置为 24,因此我们可以清楚地看到 24 小时内每天每小时的分布。 + +此外,当收集块的第一个小时时,我们小心只从那些具有所有八天数据的块中收集它,以防丢失数据的块在块的开头没有观察,我们知道发生。 + +``` +# plot distribution of chunk start hour +def plot_chunk_start_hour(chunks, hour_in_chunk_ix=5): + # chunk start hour + chunk_start_hours = [v[0, hour_in_chunk_ix] for k,v in chunks.items() if len(v)==192] + # boxplot + pyplot.subplot(2, 1, 1) + pyplot.boxplot(chunk_start_hours) + # histogram + pyplot.subplot(2, 1, 2) + pyplot.hist(chunk_start_hours, bins=24) + # histogram + pyplot.show() +``` + +下面列出了完整的示例。 + +``` +# plot distribution of chunk start hour +from numpy import nan +from numpy import unique +from pandas import read_csv +from matplotlib import pyplot + +# split the dataset by 'chunkID', return a dict of id to rows +def to_chunks(values, chunk_ix=1): + chunks = dict() + # get the unique chunk ids + chunk_ids = unique(values[:, chunk_ix]) + # group rows by chunk id + for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks[chunk_id] = values[selection, :] + return chunks + +# plot distribution of chunk start hour +def plot_chunk_start_hour(chunks, hour_in_chunk_ix=5): + # chunk start hour + chunk_start_hours = [v[0, hour_in_chunk_ix] for k,v in chunks.items() if len(v)==192] + # boxplot + pyplot.subplot(2, 1, 1) + pyplot.boxplot(chunk_start_hours) + # histogram + pyplot.subplot(2, 1, 2) + pyplot.hist(chunk_start_hours, bins=24) + # histogram + pyplot.show() + +# load dataset +dataset = read_csv('AirQualityPrediction/TrainingData.csv', header=0) +# group data by chunks +values = dataset.values +chunks = to_chunks(values) +# plot distribution of chunk start hour +plot_chunk_start_hour(chunks) +``` + +运行该示例将创建一个框和胡须图以及每个块中第一个小时的直方图。 + +我们可以看到当天 24 小时内合理均匀的开始时间分布。 + +此外,这意味着每个块的预测间隔也将在 24 小时内变化。这为可能期望标准三天预测期(午夜至午夜)的模型增加了皱纹。 + +![Distribution of first hour in observations within each chunk](img/6f8d0f38f2ef720b8c0e0e63878746c6.jpg) + +每个块内观察的第一个小时的分布 + +现在我们已经了解了数据的块结构,让我们仔细研究描述气象观测的输入变量。 + +## 输入变量 + +有 56 个输入变量。 + +前六个(索引 0 到 5)是块的元数据信息和观察的时间。他们是: + +``` +rowID +chunkID +position_within_chunk +month_most_common +weekday +hour +``` + +其余 50 个描述了特定地点的气象信息;他们是: + +``` +Solar.radiation_64 +WindDirection..Resultant_1 +WindDirection..Resultant_1018 +WindSpeed..Resultant_1 +WindSpeed..Resultant_1018 +Ambient.Max.Temperature_14 +Ambient.Max.Temperature_22 +Ambient.Max.Temperature_50 +Ambient.Max.Temperature_52 +Ambient.Max.Temperature_57 +Ambient.Max.Temperature_76 +Ambient.Max.Temperature_2001 +Ambient.Max.Temperature_3301 +Ambient.Max.Temperature_6005 +Ambient.Min.Temperature_14 +Ambient.Min.Temperature_22 +Ambient.Min.Temperature_50 +Ambient.Min.Temperature_52 +Ambient.Min.Temperature_57 +Ambient.Min.Temperature_76 +Ambient.Min.Temperature_2001 +Ambient.Min.Temperature_3301 +Ambient.Min.Temperature_6005 +Sample.Baro.Pressure_14 +Sample.Baro.Pressure_22 +Sample.Baro.Pressure_50 +Sample.Baro.Pressure_52 +Sample.Baro.Pressure_57 +Sample.Baro.Pressure_76 +Sample.Baro.Pressure_2001 +Sample.Baro.Pressure_3301 +Sample.Baro.Pressure_6005 +Sample.Max.Baro.Pressure_14 +Sample.Max.Baro.Pressure_22 +Sample.Max.Baro.Pressure_50 +Sample.Max.Baro.Pressure_52 +Sample.Max.Baro.Pressure_57 +Sample.Max.Baro.Pressure_76 +Sample.Max.Baro.Pressure_2001 +Sample.Max.Baro.Pressure_3301 +Sample.Max.Baro.Pressure_6005 +Sample.Min.Baro.Pressure_14 +Sample.Min.Baro.Pressure_22 +Sample.Min.Baro.Pressure_50 +Sample.Min.Baro.Pressure_52 +Sample.Min.Baro.Pressure_57 +Sample.Min.Baro.Pressure_76 +Sample.Min.Baro.Pressure_2001 +Sample.Min.Baro.Pressure_3301 +Sample.Min.Baro.Pressure_6005 +``` + +真的,只有八个气象输入变量: + +``` +Solar.radiation +WindDirection..Resultant +WindSpeed..Resultant +Ambient.Max.Temperature +Ambient.Min.Temperature +Sample.Baro.Pressure +Sample.Max.Baro.Pressure +Sample.Min.Baro.Pressure +``` + +这些变量记录在 23 个独特的站点中;他们是: + +``` +1, 14, 22, 50, 52, 57, 64, 76, 1018, 2001, 3301, 6005 +``` + +数据非常复杂。 + +并非所有变量都记录在所有站点上。 + +目标变量中使用的站点标识符有一些重叠,例如 1,50,64 等。 + +目标变量中使用的站点标识符未在输入变量中使用,例如 4002.还有在输入中使用的站点标识符,这些站点标识符未在目标标识符中使用,例如 15。 + +这表明,至少并非所有变量都记录在所有位置。这些录制站在各个站点之间是异构的。此外,对于仅收集给定类型的度量或收集所有度量的站点,可能存在一些特殊情况。 + +让我们仔细看看输入变量的数据。 + +### 块的输入的时间结构 + +我们可以从查看每个块的输入结构和分布开始。 + +所有八天观察的前几个块的块大小为 1,3 和 5。 + +我们可以枚举所有输入列并为每个输入列创建一个线图。这将为每个输入变量创建一个时间序列线图,以便大致了解每个输入变量的变化情况。 + +我们可以针对几个块重复这一点,以了解时间结构如何在块之间有所不同。 + +下面名为 _plot_chunk_inputs()_ 的函数获取块格式的数据和要绘制的块 ID 列表。它将创建一个包含 50 个线图的图形,每个输入变量一个,每个图块 n 行,每个块一个。 + +``` +# plot all inputs for one or more chunk ids +def plot_chunk_inputs(chunks, c_ids): + pyplot.figure() + inputs = range(6, 56) + for i in range(len(inputs)): + ax = pyplot.subplot(len(inputs), 1, i+1) + ax.set_xticklabels([]) + ax.set_yticklabels([]) + column = inputs[i] + for chunk_id in c_ids: + rows = chunks[chunk_id] + pyplot.plot(rows[:,column]) + pyplot.show() +``` + +下面列出了完整的示例。 + +``` +# plot inputs for a chunk +from numpy import unique +from pandas import read_csv +from matplotlib import pyplot + +# split the dataset by 'chunkID', return a dict of id to rows +def to_chunks(values, chunk_ix=1): + chunks = dict() + # get the unique chunk ids + chunk_ids = unique(values[:, chunk_ix]) + # group rows by chunk id + for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks[chunk_id] = values[selection, :] + return chunks + +# plot all inputs for one or more chunk ids +def plot_chunk_inputs(chunks, c_ids): + pyplot.figure() + inputs = range(6, 56) + for i in range(len(inputs)): + ax = pyplot.subplot(len(inputs), 1, i+1) + ax.set_xticklabels([]) + ax.set_yticklabels([]) + column = inputs[i] + for chunk_id in c_ids: + rows = chunks[chunk_id] + pyplot.plot(rows[:,column]) + pyplot.show() + +# load data +dataset = read_csv('AirQualityPrediction/TrainingData.csv', header=0) +# group data by chunks +values = dataset.values +chunks = to_chunks(values) +# plot inputs for some chunks +plot_chunk_inputs(chunks, [1]) +``` + +运行该示例将创建一个包含 50 个线图的单个图形,每个图形用于每个气象输入变量。 + +这些图很难看,因此您可能希望增加所创建图形的大小。 + +我们可以看到前五个变量的观察看起来非常完整;这些是太阳辐射,风速和风向。其余变量看起来非常不完整,至少对于这个块而言。 + +![Parallel Time Series Line Plots For All Input Variables for 1 Chunks](img/1f7f3eb681c264276ba101a2323ceeff.jpg) + +1 个块的所有输入变量的并行时间序列线图 + +我们可以更新示例并绘制前三个块的输入变量,并进行完整的八天观察。 + +``` +plot_chunk_inputs(chunks, [1, 3 ,5]) +``` + +运行该示例将创建相同的 50 个线图,每个图每个图有三个系列或线,每个块一个。 + +同样,该图使单个图很难看到,因此您可能需要增加图的大小以更好地查看模式。 + +我们可以看到这三个图在每个线图中都显示出类似的结构。这是有帮助的发现,因为它表明跨多个块建模相同的变量可能是有用的。 + +![Parallel Time Series Line Plots For All Input Variables for 3 Chunks](img/01de4eea59309dd45bc7daaba90bdbb7.jpg) + +3 个块的所有输入变量的并行时间序列线图 + +它确实提出了关于变量的分布是否在不同站点之间差异很大的问题。 + +### 输入数据分布 + +我们可以使用 box 和 whisker 图粗略地查看输入变量的分布。 + +下面的 _plot_chunk_input_boxplots()_ 将为每个输入要素创建一个盒子和胡须,用于一个块的数据。 + +``` +# boxplot for input variables for a chuck +def plot_chunk_input_boxplots(chunks, c_id): + rows = chunks[c_id] + pyplot.boxplot(rows[:,6:56]) + pyplot.show() +``` + +下面列出了完整的示例。 + +``` +# boxplots of inputs for a chunk +from numpy import unique +from numpy import isnan +from numpy import count_nonzero +from pandas import read_csv +from matplotlib import pyplot + +# split the dataset by 'chunkID', return a dict of id to rows +def to_chunks(values, chunk_ix=1): + chunks = dict() + # get the unique chunk ids + chunk_ids = unique(values[:, chunk_ix]) + # group rows by chunk id + for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks[chunk_id] = values[selection, :] + return chunks + +# boxplot for input variables for a chuck +def plot_chunk_input_boxplots(chunks, c_id): + rows = chunks[c_id] + pyplot.boxplot(rows[:,6:56]) + pyplot.show() + +# load data +dataset = read_csv('TrainingData.csv', header=0) +# group data by chunks +values = dataset.values +chunks = to_chunks(values) +# boxplot for input variables +plot_chunk_input_boxplots(chunks, 1) +``` + +运行该示例将创建 50 个箱图,每个输入变量用于训练数据集中第一个块中的观察。 + +我们可以看到相同类型的变量可能具有相同的观察范围,并且每组变量似乎具有不同的单位。也许是风向的度数,压力的百帕斯卡,温度的摄氏度等等。 + +![Box and whisker plots of input variables for one chunk](img/9c859d8a5b02104d272706f629aaacad.jpg) + +一个块的输入变量的框和胡须图 + +对于八种变量类型中的每一种,进一步研究观察的分布和扩散可能是有趣的。这是一个进一步的练习。 + +我们对输入变量有一些粗略的想法,也许它们可能对预测目标变量很有用。我们无法确定。 + +我们现在可以将注意力转向目标变量。 + +## 目标变量 + +预测问题的目标是预测多个站点的多个变量,为期三天。 + +有 39 个时间序列变量可供预测。 + +从列标题中,它们是: + +``` +"target_1_57","target_10_4002","target_10_8003","target_11_1","target_11_32","target_11_50","target_11_64","target_11_1003","target_11_1601","target_11_4002","target_11_8003","target_14_4002","target_14_8003","target_15_57","target_2_57","target_3_1","target_3_50","target_3_57","target_3_1601","target_3_4002","target_3_6006","target_4_1","target_4_50","target_4_57","target_4_1018","target_4_1601","target_4_2001","target_4_4002","target_4_4101","target_4_6006","target_4_8003","target_5_6006","target_7_57","target_8_57","target_8_4002","target_8_6004","target_8_8003","target_9_4002","target_9_8003" +``` + +这些列标题的命名约定是: + +``` +target_[variable identifier]_[site identifier]] +``` + +我们可以使用一点 regex 将这些列标题转换为变量 id 和 site id 的小数据集。 + +结果如下: + +``` +var, site +1,57 +10,4002 +10,8003 +11,1 +11,32 +11,50 +11,64 +11,1003 +11,1601 +11,4002 +11,8003 +14,4002 +14,8003 +15,57 +2,57 +3,1 +3,50 +3,57 +3,1601 +3,4002 +3,6006 +4,1 +4,50 +4,57 +4,1018 +4,1601 +4,2001 +4,4002 +4,4101 +4,6006 +4,8003 +5,6006 +7,57 +8,57 +8,4002 +8,6004 +8,8003 +9,4002 +9,8003 +``` + +有用的是,目标按变量 id 分组。 + +我们可以看到,可能需要跨多个站点预测一个变量;例如,在站点 1,32,50 等处预测的变量 11,等等: + +``` +var, site +11,1 +11,32 +11,50 +11,64 +11,1003 +11,1601 +11,4002 +11,8003 +``` + +我们可以看到,对于给定的站点,可能需要预测不同的变量。例如,站点 50 需要变量 11,3 和 4: + +``` +var, site +11,50 +3,50 +4,50 +``` + +我们可以将目标的小数据集保存到名为“ _targets.txt_ ”的文件中并加载它以进行快速分析。 + +``` +# summarize targets +from numpy import unique +from pandas import read_csv +# load dataset +dataset = read_csv('targets.txt', header=0) +values = dataset.values +# summarize unique +print('Unique Variables: %d' % len(unique(values[:, 0]))) +print('Unique Sites: %d' % len(unique(values[:, 1]))) +``` + +运行该示例将打印唯一变量和站点的数量。 + +如果我们预测所有站点的所有变量,我们可以看到 39 个目标变量远小于(12 * 14)168。 + +``` +Unique Variables: 12 +Unique Sites: 14 +``` + +让我们仔细看看目标变量的数据。 + +### 大块目标的时间结构 + +我们可以从每个块的目标结构和分布开始。 + +所有八天观察的前几个块的块大小为 1,3 和 5。 + +我们可以枚举所有目标列,并为每个列创建一个线图。这将为每个目标变量创建一个时间序列线图,以大致了解它如何随时间变化。 + +我们可以针对几个块重复这一点,以大致了解时间结构如何在块之间变化。 + +下面的函数名为 _plot_chunk_targets()_,以块格式和块 ID 列表绘制。它将创建一个包含 39 个线图的图形,每个目标变量一个,每个图块 n 行,每个块一个。 + +``` +# plot all targets for one or more chunk ids +def plot_chunk_targets(chunks, c_ids): + pyplot.figure() + targets = range(56, 95) + for i in range(len(targets)): + ax = pyplot.subplot(len(targets), 1, i+1) + ax.set_xticklabels([]) + ax.set_yticklabels([]) + column = targets[i] + for chunk_id in c_ids: + rows = chunks[chunk_id] + pyplot.plot(rows[:,column]) + pyplot.show() +``` + +下面列出了完整的示例。 + +``` +# plot targets for a chunk +from numpy import unique +from pandas import read_csv +from matplotlib import pyplot + +# split the dataset by 'chunkID', return a dict of id to rows +def to_chunks(values, chunk_ix=1): + chunks = dict() + # get the unique chunk ids + chunk_ids = unique(values[:, chunk_ix]) + # group rows by chunk id + for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks[chunk_id] = values[selection, :] + return chunks + +# plot all targets for one or more chunk ids +def plot_chunk_targets(chunks, c_ids): + pyplot.figure() + targets = range(56, 95) + for i in range(len(targets)): + ax = pyplot.subplot(len(targets), 1, i+1) + ax.set_xticklabels([]) + ax.set_yticklabels([]) + column = targets[i] + for chunk_id in c_ids: + rows = chunks[chunk_id] + pyplot.plot(rows[:,column]) + pyplot.show() + +# load data +dataset = read_csv('AirQualityPrediction/TrainingData.csv', header=0) +# group data by chunks +values = dataset.values +chunks = to_chunks(values) +# plot targets for some chunks +plot_chunk_targets(chunks, [1]) +``` + +运行该示例将为块标识符“1”创建一个包含 39 个线图的单个图形。 + +这些图很小,但大致了解变量的时间结构。 + +我们可以看到,有多个变量没有这个块的数据。这些不能直接预测,也可能不是间接预测。 + +这表明除了没有所有站点的所有变量之外,甚至列标题中指定的变量也可能不存在于某些块中。 + +我们还可以在一些系列中看到缺失值的中断。这表明即使我们可能对块中的每个时间步进行观察,我们也可能没有块中所有变量的连续序列。 + +许多情节都有一个循环结构。大多数都有八个峰值,很可能对应于块内八天的观察。这种季节性结构可以直接建模,也可以在建模时从数据中删除,并添加回预测的时间间隔。 + +该系列似乎没有任何趋势。 + +![Parallel Time Series Line Plots For All Target Variables for 1 Chunk](img/739116192307ff4400d6586d3935321c.jpg) + +1 个块的所有目标变量的并行时间序列线图 + +我们可以重新运行该示例并使用完整数据绘制前三个块的目标变量。 + +``` +# plot targets for some chunks +plot_chunk_targets(chunks, [1, 3 ,5]) +``` + +运行该示例将创建一个图形,其中包含 39 个图形和每个图形的三个时间序列,一个用于每个块的目标。 + +绘图很忙,您可能希望增加绘图窗口的大小,以便更好地查看目标变量的块之间的比较。 + +对于许多具有循环日常结构的变量,我们可以看到在整个块中重复的结构。 + +这是令人鼓舞的,因为它表明为站点建模变量可能对块有所帮助。 + +此外,曲线 3 至 10 对应于七个不同位置的变量 11。这些图中时间结构的字符串相似性表明,对跨站点使用的每个变量的数据建模可能是有益的。 + +![Parallel Time Series Line Plots For All Target Variables for 3 Chunks](img/c922bca0017be329474b7ccf8c855193.jpg) + +3 个块的所有目标变量的并行时间序列线图 + +### 目标变量的箱线图分布 + +查看目标变量的分布也很有用。 + +我们可以首先通过为每个目标变量创建框和晶须图来查看每个目标变量的分布。 + +可以为每个目标并排创建单独的箱图,允许在相同比例下直接比较值的形状和范围。 + +``` +# boxplot for target variables for a chuck +def plot_chunk_targets_boxplots(chunks, c_id): + rows = chunks[c_id] + pyplot.boxplot(rows[:,56:]) + pyplot.show() +``` + +下面列出了完整的示例。 + +``` +# boxplots of targets for a chunk +from numpy import unique +from numpy import isnan +from numpy import count_nonzero +from pandas import read_csv +from matplotlib import pyplot + +# split the dataset by 'chunkID', return a dict of id to rows +def to_chunks(values, chunk_ix=1): + chunks = dict() + # get the unique chunk ids + chunk_ids = unique(values[:, chunk_ix]) + # group rows by chunk id + for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks[chunk_id] = values[selection, :] + return chunks + +# boxplot for target variables for a chuck +def plot_chunk_targets_boxplots(chunks, c_id): + rows = chunks[c_id] + pyplot.boxplot(rows[:,56:]) + pyplot.show() + +# load data +dataset = read_csv('AirQualityPrediction/TrainingData.csv', header=0) +# group data by chunks +values = dataset.values +chunks = to_chunks(values) +# boxplot for target variables +plot_chunk_targets_boxplots(chunks, 1) +``` + +运行该示例将创建一个包含 39 个箱图的图形,第一个块的 39 个目标变量中的每一个都有一个。 + +我们可以看到许多变量的中位数接近零或一;我们还可以看到大多数变量存在大的不对称差异,这表明变量可能与异常值存在偏差。 + +令人鼓舞的是,7 个站点的变量 11 的 4-10 的箱形图显示了类似的分布。这进一步证明了数据可以按变量分组并用于拟合可跨站点使用的模型。 + +![Box and whisker plots of target variables for one chunk](img/f05a406510a60b1571ed6761220f38c5.jpg) + +一个块的目标变量的框和胡须图 + +我们可以使用跨所有块的数据重新创建此图,以查看数据集范围的模式。 + +下面列出了完整的示例。 + +``` +# boxplots of targets for all chunks +from pandas import read_csv +from matplotlib import pyplot + +# boxplot for all target variables +def plot_target_boxplots(values): + pyplot.boxplot(values[:,56:]) + pyplot.show() + +# load data +dataset = read_csv('AirQualityPrediction/TrainingData.csv', header=0) +# boxplot for target variables +values = dataset.values +plot_target_boxplots(values) +``` + +运行该示例将创建一个新图形,显示整个训练数据集的 39 个框和胡须图,而不管块是什么。 + +这有点乱,圆圈异常值掩盖了主要的数据分布。 + +我们可以看到异常值确实扩展到 5 到 10 个单位。这表明在建模时可能在标准化和/或重新缩放目标方面有一些用处。 + +也许最有用的发现是,有些目标没有任何(或非常多)数据而不管块数。这些列可能应该从数据集中排除。 + +![Box and whisker plots of target variables for all training data](img/38ea068ea399ec9507e619cc66004454.jpg) + +所有训练数据的目标变量的框和胡须图 + +### 显然空目标列 + +我们可以通过创建每列缺失数据比率的条形图来进一步研究明显的缺失数据,不包括开头的元数据列(例如前五列)。 + +下面的 _plot_col_percentage_missing()_ 函数实现了这个功能。 + +``` +# bar chart of the ratio of missing data per column +def plot_col_percentage_missing(values, ix_start=5): + ratios = list() + # skip early columns, with meta data or strings + for col in range(ix_start, values.shape[1]): + col_data = values[:, col].astype('float32') + ratio = count_nonzero(isnan(col_data)) / len(col_data) * 100 + ratios.append(ratio) + if ratio > 90.0: + print(col, ratio) + col_id = [x for x in range(ix_start, values.shape[1])] + pyplot.bar(col_id, ratios) + pyplot.show() +``` + +下面列出了完整的示例。 + +``` +# summarize missing data per column +from numpy import isnan +from numpy import count_nonzero +from pandas import read_csv +from matplotlib import pyplot + +# bar chart of the ratio of missing data per column +def plot_col_percentage_missing(values, ix_start=5): + ratios = list() + # skip early columns, with meta data or strings + for col in range(ix_start, values.shape[1]): + col_data = values[:, col].astype('float32') + ratio = count_nonzero(isnan(col_data)) / len(col_data) * 100 + ratios.append(ratio) + if ratio > 90.0: + print(ratio) + col_id = [x for x in range(ix_start, values.shape[1])] + pyplot.bar(col_id, ratios) + pyplot.show() + +# load data +dataset = read_csv('AirQualityPrediction/TrainingData.csv', header=0) +# plot ratio of missing data per column +values = dataset.values +plot_col_percentage_missing(values) +``` + +如果比率高于 90%,则首先运行示例打印列 id(零偏移)和缺失数据的比率。 + +我们可以看到,实际上没有非 NaN 数据为零的列,但可能有二十多个(12)具有超过 90%的缺失数据。 + +有趣的是,其中七个是目标变量(指数 56 或更高)。 + +``` +11 91.48885539779488 +20 91.48885539779488 +29 91.48885539779488 +38 91.48885539779488 +47 91.48885539779488 +58 95.38880516115385 +66 96.9805134713519 +68 95.38880516115385 +72 97.31630575606145 +86 95.38880516115385 +92 95.38880516115385 +94 95.38880516115385 +``` + +创建列索引号与缺失数据比率的条形图。 + +我们可以看到,对于缺失数据的比率可能存在一些分层,群集低于 10%,群集约为 70%,群集高于 90%。 + +我们还可以看到输入变量和目标变量之间的分离,前者非常规则,因为它们显示了在不同站点测量的相同变量类型。 + +某些目标变量的这些少量数据表明除了过去的观察之外还需要利用其他因素来进行预测。 + +![Bar Chart of Percentage of Missing Data Per Column](img/20b622b485d5f630ae5715ed1ac6c161.jpg) + +每列缺失数据百分比的条形图 + +### 目标变量的直方图分布 + +目标变量的分布不整齐,至少可以是非高斯分布,或者最差的是高度多模态。 + +我们可以通过查看目标变量的直方图来查看单个块的数据。 + +matplotlib 中 _hist()_ 函数的一个问题是它对 NaN 值不稳健。我们可以通过在绘制之前检查每列是否具有非 NaN 值并排除具有 NaN 值的行来克服此问题。 + +下面的函数执行此操作,并为一个或多个块的每个目标变量创建一个直方图。 + +``` +# plot distribution of targets for one or more chunk ids +def plot_chunk_targets_hist(chunks, c_ids): + pyplot.figure() + targets = range(56, 95) + for i in range(len(targets)): + ax = pyplot.subplot(len(targets), 1, i+1) + ax.set_xticklabels([]) + ax.set_yticklabels([]) + column = targets[i] + for chunk_id in c_ids: + rows = chunks[chunk_id] + # extract column of interest + col = rows[:,column].astype('float32') + # check for some data to plot + if count_nonzero(isnan(col)) < len(rows): + # only plot non-nan values + pyplot.hist(col[~isnan(col)], bins=100) + pyplot.show() +``` + +下面列出了完整的示例。 + +``` +# plot distribution of targets for a chunk +from numpy import unique +from numpy import isnan +from numpy import count_nonzero +from pandas import read_csv +from matplotlib import pyplot + +# split the dataset by 'chunkID', return a dict of id to rows +def to_chunks(values, chunk_ix=1): + chunks = dict() + # get the unique chunk ids + chunk_ids = unique(values[:, chunk_ix]) + # group rows by chunk id + for chunk_id in chunk_ids: + selection = values[:, chunk_ix] == chunk_id + chunks[chunk_id] = values[selection, :] + return chunks + +# plot distribution of targets for one or more chunk ids +def plot_chunk_targets_hist(chunks, c_ids): + pyplot.figure() + targets = range(56, 95) + for i in range(len(targets)): + ax = pyplot.subplot(len(targets), 1, i+1) + ax.set_xticklabels([]) + ax.set_yticklabels([]) + column = targets[i] + for chunk_id in c_ids: + rows = chunks[chunk_id] + # extract column of interest + col = rows[:,column].astype('float32') + # check for some data to plot + if count_nonzero(isnan(col)) < len(rows): + # only plot non-nan values + pyplot.hist(col[~isnan(col)], bins=100) + pyplot.show() + +# load data +dataset = read_csv('AirQualityPrediction/TrainingData.csv', header=0) +# group data by chunks +values = dataset.values +chunks = to_chunks(values) +# plot targets for some chunks +plot_chunk_targets_hist(chunks, [1]) +``` + +运行该示例将创建一个包含 39 个直方图的图形,一个用于第一个块的每个目标变量。 + +情节难以阅读,但是大量的垃圾箱会显示变量的分布。 + +可以公平地说,也许没有一个目标变量具有明显的高斯分布。许多人的右侧尾部可能有偏斜的分布。 + +其他变量具有看似非常离散的分布,可能是所选测量设备或测量尺度的人工产物。 + +![Histograms for each target variable for one chunk](img/fd064a0529a055985fa16a766abe0f7c.jpg) + +一个块的每个目标变量的直方图 + +我们可以使用所有块的目标变量重新创建相同的图。 + +下面列出了完整的示例。 + +``` +# plot distribution of all targets +from numpy import isnan +from numpy import count_nonzero +from pandas import read_csv +from matplotlib import pyplot + +# plot histogram for each target variable +def plot_target_hist(values): + pyplot.figure() + targets = range(56, 95) + for i in range(len(targets)): + ax = pyplot.subplot(len(targets), 1, i+1) + ax.set_xticklabels([]) + ax.set_yticklabels([]) + column = targets[i] + # extract column of interest + col = values[:,column].astype('float32') + # check for some data to plot + if count_nonzero(isnan(col)) < len(values): + # only plot non-nan values + pyplot.hist(col[~isnan(col)], bins=100) + pyplot.show() + +# load data +dataset = read_csv('AirQualityPrediction/TrainingData.csv', header=0) +# plot targets for all chunks +values = dataset.values +plot_target_hist(values) +``` + +运行该示例将创建一个包含 39 个直方图的图形,一个用于训练数据集中的每个目标变量。 + +我们可以看到更全面的分布,更具洞察力。 + +第一批情节可能显示出高度倾斜的分布,其核心可能是也可能不是高斯分布。 + +我们可以看到许多具有间隙的类高斯分布,这表明对高斯分布的连续变量施加了离散测量。 + +我们还可以看到一些显示指数分布的变量。 + +总之,这表明使用[功率变换](https://machinelearningmastery.com/power-transform-time-series-forecast-data-python/)来探索将数据重塑为更高斯,和/或使用不依赖于变量的高斯分布的非参数建模方法。例如,可以预期经典线性方法具有困难时间。 + +![Histograms for each target variable for the entire training dataset](img/24af435317c5118bc2bfa90bcebc041a.jpg) + +整个训练数据集的每个目标变量的直方图 + +## 与目标变量的皱痕 + +比赛结束后,提供数据的人 [David Chudzicki](https://www.linkedin.com/in/david-chudzicki-3ba1a92a/) 总结了 12 个输出变量的真实含义。 + +这是在标题为“[目标变量确实是](https://www.kaggle.com/c/dsg-hackathon/discussion/1830)”的表格帖子中提供的,部分复制如下: + +``` +Description Target Variable +Carbon monoxide, 8 +Sulfur dioxide, 4 +SO2 max 5-min avg, 3 +Nitric oxide (NO), 10 +Nitrogen dioxide (NO2), 14 +Oxides of nitrogen (NOx), 9 +Ozone, 11 +PM10 Total 0-10um STP, 5 +OC CSN Unadjusted PM2.5 LC TOT, 15 +Total Nitrate PM2.5 LC, 2 +EC CSN PM2.5 LC TOT, 1 +Total Carbon PM2.5 LC TOT, 7 +Sulfate PM2.5 LC, 8 +PM2.5 Raw Data, 4 +PM2.5 AQI & Speciation Mass, 3 +``` + +这很有意思,因为我们可以看到目标变量本质上是气象的,并且与竞争的名称所暗示的空气质量有关。 + +问题是数据集中有 15 个变量,只有 12 种不同类型的目标变量。 + +此问题的原因是数据集中的相同目标变量可用于表示不同的目标变量。特别: + +* 目标 8 可以是'_ 一氧化碳 _'或'_ 硫酸盐 PM2.5LC_ '的数据。 +* 目标 4 可以是'_ 二氧化硫 _'或' _PM2.5 原始数据 _'的数据。 +* 目标 3 可以是' _SO2 最大 5 分钟平均 _'或' _PM2.5 AQI&amp;形态质量 _'。 + +根据变量的名称,将数据加倍到相同的目标变量,使用具有不同化学特征的变量,甚至可能使用例如测量的变量。它似乎是偶然的而不是战略性的。 + +目前尚不清楚,但很可能目标代表一个块中的一个变量,但可能代表块之间的不同变量。或者,变量可能在每个块内的站点之间不同。在前一种情况下,这意味着期望跨块的这些目标变量的一致性的模型,这是一个非常合理的假设,可能有困难。在后者中,模型可以将变量位点组合视为不同的变量。 + +可以通过比较这些变量在块之间的分布和比例来梳理差异。 + +这是令人失望的,并且取决于模型技能的重要性,可能需要从数据集中删除这些变量,这些变量是很多目标变量(39 个中的 20 个)。 + +## 关于建模的思考 + +在本节中,我们将利用我们发现的有关该问题的方法,并提出一些建模此问题的方法。 + +我喜欢这个数据集;它是凌乱的,现实的,抵制天真的方法。 + +本节分为四个部分;他们是: + +* 取景。 +* 数据准备。 +* 造型。 +* 评价。 + +### 取景 + +该问题通常被视为多变量多步骤时间序列预测问题。 + +此外,需要跨多个站点预测多个变量,这是时间序列预测问题的常见结构细分,例如,预测不同物理位置(例如商店或车站)的可变物。 + +让我们来看看数据的一些可能的框架。 + +#### 按变量和站点单变量 + +第一种方法可能是将每个站点的每个变量视为单变量时间序列预测问题。 + +对模型进行 8 天的每小时观察,并要求预测三天,从中获取并使用或评估预测提前期的特定子集。 + +在一些选择的情况下,这可能是可能的,这可以通过一些进一步的数据分析来确认。 + +然而,数据通常抵抗这种框架,因为并非所有块都对每个目标变量进行了 8 天的观察。此外,目标变量的时间序列可能非常不连续,即使不是大多数(90%到 95%)不完整。 + +我们可以放松对模型所需的先前数据的结构和数量的期望,设计模型以利用可用的任何东西。 + +这种方法每块需要 39 个模型,总共需要(208 * 39)或 8,112 个独立模型。这听起来有可能,但可能比我们从工程角度更喜欢的可扩展性。 + +可变站点组合可以跨块建模,仅需要 39 个模型。 + +#### 单变量变量 + +目标变量可以跨站点聚合。 + +我们还可以放松使用滞后时间进行预测,并使用零填充或输入缺失值来显示可用的内容,或者甚至是滞后观察而忽略前置时间。 + +然后,我们可以根据给定变量的先前观察结果对问题进行框架,预测接下来的三天。 + +这些模型可能有更多可以使用,但会忽略基于站点的任何变量差异。这可能是也可能不是没有理由的,可以通过比较跨站点的变量分布来检查。 + +有 12 个独特的变量。 + +我们可以为每个块建模每个变量,给出(208 * 12)或 2,496 个模型。在块上建模 12 个变量可能更有意义,只需要 12 个模型。 + +#### 多变量模型 + +也许一个或多个目标变量依赖于一个或多个气象变量,甚至依赖于其他目标变量。 + +这可以通过调查每个目标变量和每个输入变量之间的相关性以及其他目标变量来探索。 + +如果存在或可以假设这样的依赖性,则不仅可以预测具有更完整数据的变量,还可以预测具有 90%以上缺失数据的目标变量。 + +这些模型可以使用先前气象观测的一些子集和/或目标变量观测作为输入。数据的不连续性可能需要放宽输入变量的传统滞后时间结构,允许模型使用可用于特定预测的任何内容。 + +## 数据准备 + +根据模型的选择,输入和目标变量可能会受益于某些数据准备,例如: + +* 标准化。 +* 正常化。 +* 功率变换,高斯。 +* 季节性差异,存在季节性结构。 + +为了解决丢失的数据,在某些情况下,可能需要通过简单的持久性或平均来进行估算。 + +在其他情况下,并且取决于模型的选择,可以直接从 NaN 值学习作为观察(例如 XGBoost 可以这样做)或填充 0 值并屏蔽输入(例如 LSTM 可以这样做)。 + +研究降尺度输入到 2,4 或 12 小时数据或类似数据以试图填补不连续数据中的空白,例如,可能是有趣的。每小时从 12 小时数据预测。 + +### 造型 + +建模可能需要一些原型来发现在方法和选择的输入观察方面有效的方法。 + +#### 经典方法 + +可能存在具有完整数据的块的罕见示例,其中诸如 ETS 或 SARIMA 的经典方法可用于单变量预测。 + +通常,该问题抵制经典方法。 + +#### 机器学习 + +一个很好的选择是使用非线性机器学习方法,这些方法与输入数据的时间结构无关,利用任何可用的东西。 + +此类模型可用于递归或直接策略以预测提前期。直接策略可能更有意义,每个所需的提前期有一个模型。 + +有 10 个交付周期和 39 个目标变量,在这种情况下,直接策略需要(39 * 10)或 390 个模型。 + +对问题建模的直接方法的缺点是模型无法利用预测区间中目标变量之间的任何依赖关系,特别是跨站点,跨变量或跨越提前期。如果这些依赖存在(并且肯定会存在),则可以在使用第二层集合模型时添加它们的味道。 + +特征选择可用于发现变量和/或滞后时间,这可以在预测每个目标变量和提前期时提供最大价值。 + +这种方法将提供很大的灵活性,正如竞争中所示,决策树的集合在很少调整的情况下表现良好。 + +#### 深度学习 + +与机器学习方法一样,深度学习方法可能能够使用任何可用的多变量数据来进行预测。 + +对于这个问题,两类神经网络可能值得探索: + +* 卷积神经网络或 CNN。 +* 循环神经网络,特别是长短期记忆网络或 LSTM。 + +CNN 能够将多变量输入时间序列数据的长序列提取到小特征映射中,并且实质上从与预测最相关的序列中学习特征。它们在输入序列中处理噪声和特征不变性的能力可能是有用的。与其他神经网络一样,CNN 可以输出向量以预测预测的提前期。 + +LSTM 旨在处理序列数据,并可通过屏蔽直接支持丢失的数据。它们也能够从长输入序列自动进行特征学习,单独或与 CNN 结合可以很好地解决这个问题。与编码器 - 解码器架构一起,LSTM 网络可用于本地预测多个交付周期。 + +### 评估 + +一种反映竞争中使用的天真方法可能最适合评估模型。 + +也就是说,将每个块分成列车和测试集,在这种情况下使用前五天的数据进行训练,其余三个用于测试。 + +通过在整个数据集上进行训练并将预测提交给 Kaggle 网站以评估所保持的测试数据集,最终确定模型可能是有趣的。 + +## 进一步阅读 + +如果您希望深入了解,本节将提供有关该主题的更多资源。 + +* [标准多变量,多步骤和多站点时间序列预测问题](https://machinelearningmastery.com/standard-multivariate-multi-step-multi-site-time-series-forecasting-problem/) +* [EMC 数据科学全球黑客马拉松(空气质量预测)](https://www.kaggle.com/c/dsg-hackathon/data) +* [将所有东西放入随机森林:Ben Hamner 赢得空气质量预测黑客马拉松](http://blog.kaggle.com/2012/05/01/chucking-everything-into-a-random-forest-ben-hamner-on-winning-the-air-quality-prediction-hackathon/) +* [EMC 数据科学全球黑客马拉松(空气质量预测)的获奖代码](https://github.com/benhamner/Air-Quality-Prediction-Hackathon-Winning-Model) +* [分区模型的一般方法?](https://www.kaggle.com/c/dsg-hackathon/discussion/1821) + +## 摘要 + +在本教程中,您发现并探索了空气质量预测数据集,该数据集代表了具有挑战性的多变量,多站点和多步骤时间序列预测问题。 + +具体来说,你学到了: + +* 如何加载和探索数据集的块结构。 +* 如何探索和可视化数据集的输入和目标变量。 +* 如何使用新的理解来概述一系列方法来构建问题,准备数据和建模数据集。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/how-to-model-human-activity-from-smartphone-data.md b/docs/dl-ts/how-to-model-human-activity-from-smartphone-data.md new file mode 100644 index 0000000000000000000000000000000000000000..a73bfcb5e07565ba48548970bbd6f6a4f1daad06 --- /dev/null +++ b/docs/dl-ts/how-to-model-human-activity-from-smartphone-data.md @@ -0,0 +1,1380 @@ +# 如何从智能手机数据模拟人类活动 + +> 原文: [https://machinelearningmastery.com/how-to-model-human-activity-from-smartphone-data/](https://machinelearningmastery.com/how-to-model-human-activity-from-smartphone-data/) + +人类活动识别是将由专用线束或智能电话记录的加速度计数据序列分类为已知的明确定义的运动的问题。 + +鉴于每秒产生大量观测结果,观测的时间性质以及缺乏将加速度计数据与已知运动联系起来的明确方法,这是一个具有挑战性的问题。 + +该问题的经典方法涉及基于固定大小的窗口和训练机器学习模型(例如决策树的集合)的时间序列数据中的手工制作特征。困难在于此功能工程需要该领域的深厚专业知识。 + +最近,诸如循环神经网络和一维卷积神经网络(CNN)之类的深度学习方法已经被证明在很少或没有数据特征工程的情况下提供具有挑战性的活动识别任务的最新结果。 + +在本教程中,您将发现用于时间序列分类的'_ 活动识别使用智能手机 _'数据集,以及如何加载和探索数据集以使其为预测建模做好准备。 + +完成本教程后,您将了解: + +* 如何下载数据集并将其加载到内存中。 +* 如何使用线图,直方图和箱线图来更好地理解运动数据的结构。 +* 如何建模问题,包括框架,数据准备,建模和评估。 + +让我们开始吧。 + +![How to Model Human Activity From Smartphone Data](img/51aa5daf7dba387bd3b304c26c2f831e.jpg) + +如何从智能手机数据模拟人类活动 +照片由[摄影师](https://www.flickr.com/photos/corker888/23930933844/),保留一些权利。 + +## 教程概述 + +本教程分为 10 个部分;他们是: + +1. 人类活动识别 +2. 使用智能手机数据集进行活动识别 +3. 下载数据集 +4. 加载数据 +5. 活动类平衡 +6. 绘制一个主题的时间序列数据 +7. 绘制每个主题的直方图 +8. 绘制每个活动的直方图 +9. 绘制活动持续时间箱图 +10. 建模方法 + +## 1.人类活动识别 + +[人类活动识别](https://en.wikipedia.org/wiki/Activity_recognition),或简称为 HAR,是基于使用传感器的移动痕迹来预测人正在做什么的问题。 + +运动通常是正常的室内活动,例如站立,坐着,跳跃和上楼梯。 + +传感器通常位于主体上,例如智能手机或背心,并且经常以三维(x,y,z)记录加速度计数据。 + +> 人类活动识别(HAR)旨在识别由一个人对他/她自己和周围环境进行一组观察所执行的动作。识别可以通过利用从各种来源(例如环境或身体佩戴的传感器)检索的信息来实现。 + +- [使用智能手机进行人类活动识别的公共领域数据集](https://upcommons.upc.edu/handle/2117/20897),2013 年。 + +这个想法是,一旦主体的活动被识别和知道,智能计算机系统就可以提供帮助。 + +这是一个具有挑战性的问题,因为没有明确的分析方法将传感器数据与一般方式的特定动作联系起来。由于收集了大量的传感器数据(例如,每秒数十或数百次观察),并且在开发预测模型时从这些数据中经典使用手工制作的特征和启发式,因此在技术上具有挑战性。 + +最近,深度学习方法已经成功地证明了 HAR 问题,因为它们能够自动学习更高阶的特征。 + +> 基于传感器的活动识别从大量低水平传感器读数中寻找关于人类活动的深刻的高级知识。传统的模式识别方法在过去几年中取得了巨大的进步。然而,这些方法通常严重依赖于启发式手工特征提取,这可能会妨碍它们的泛化表现。 [...]最近,深度学习的最新进展使得可以执行自动高级特征提取,从而在许多领域实现了有希望的表现。 + +- [基于传感器的活动识别深度学习:调查](https://arxiv.org/abs/1707.03502) + +## 2.使用智能手机数据集识别活动 + +标准人类活动识别数据集是 2012 年提供的“_ 活动识别使用智能手机 _”数据集。 + +它由 Davide Anguita 等人准备并提供。来自意大利热那亚大学的 2013 年论文“[使用智能手机进行人类活动识别的公共领域数据集](https://upcommons.upc.edu/handle/2117/20897)”中对该数据集进行了全面描述。该数据集在他们的 2012 年论文中用机器学习算法建模,标题为“[使用多类硬件友好支持向量机](https://link.springer.com/chapter/10.1007/978-3-642-35395-6_30)在智能手机上进行人类活动识别。“ + +数据集可用,可以从 UCI 机器学习库免费下载: + +* [使用智能手机数据集进行人类活动识别](https://archive.ics.uci.edu/ml/datasets/human+activity+recognition+using+smartphones) + +该数据来自 30 名年龄在 19 至 48 岁之间的受试者,他们进行 6 项标准活动中的一项,同时佩戴记录运动数据的腰部智能手机。记录执行活动的每个受试者的视频,并从这些视频手动标记移动数据。 + +以下是在记录其移动数据的同时执行活动的主体的示例视频。 + +<iframe allow="autoplay; encrypted-media" allowfullscreen="" frameborder="0" height="375" src="https://www.youtube.com/embed/XOEN9W05_4A?feature=oembed" width="500"></iframe> + +进行的六项活动如下: + +1. 步行 +2. 走上楼 +3. 走楼下 +4. 坐在 +5. 常设 +6. 铺设 + +记录的运动数据是来自智能手机的 x,y 和 z 加速度计数据(线性加速度)和陀螺仪数据(角速度),特别是[三星 Galaxy S II](https://en.wikipedia.org/wiki/Samsung_Galaxy_S_II) 。 + +以 50Hz(即每秒 50 个数据点)记录观察结果。每个受试者进行两次活动,一次是左侧设备,另一次是右侧设备。 + +> 选择了一组 30 名志愿者,年龄从 19 岁到 48 岁不等。每个人都被要求遵循活动协议,同时佩戴腰部安装的三星 Galaxy S II 智能手机。六个选定的 ADL 是站立,坐下,放下,走路,走楼下和楼上。每个受试者执行两次协议:在第一次试验中,智能手机固定在皮带的左侧,而第二次试验由用户自己放置为首选 + +- [使用智能手机进行人类活动识别的公共领域数据集](https://upcommons.upc.edu/handle/2117/20897),2013。 + +原始数据不可用。相反,可以使用预处理版本的数据集。 + +预处理步骤包括: + +* 使用噪声滤波器预处理加速度计和陀螺仪。 +* 将数据拆分为 2.56 秒(128 个数据点)的固定窗口,重叠率为 50%。 +* 将加速度计数据分割为重力(总)和身体运动分量。 + +> 使用中值滤波器和具有 20Hz 截止频率的 3 阶低通巴特沃斯滤波器对这些信号进行预处理以降低噪声。 [...]加速度信号,具有重力和身体运动成分,使用另一个巴特沃斯低通滤波器分离成身体加速度和重力。 + +- [使用智能手机进行人类活动识别的公共领域数据集](https://upcommons.upc.edu/handle/2117/20897),2013 年。 + +特征工程应用于窗口数据,并且提供具有这些工程特征的数据的副本。 + +从每个窗口提取在人类活动识别领域中常用的许多时间和频率特征。结果是 561 元素的特征向量。 + +根据受试者的数据,将数据集分成训练(70%)和测试(30%)组。列车 21 个,测试 9 个。 + +这表明问题的框架,其中一系列运动活动被用作输入来预测当前正在进行的活动的部分(2.56 秒),其中使用已知受试者训练的模型来预测新受试者的运动活动。 。 + +使用旨在用于智能手机的支持向量机(例如定点算术)的早期实验结果导致测试数据集的预测准确度为 89%,实现与未修改的 SVM 实现类似的结果。 + +> 该方法适用于标准支持向量机(SVM)并利用定点算法来降低计算成本。与传统 SVM 的比较表明,计算成本方面有显着改善,同时保持了相似的精度[...] + +- [智能手机上的人类活动识别使用多类硬件友好支持向量机](https://link.springer.com/chapter/10.1007/978-3-642-35395-6_30),2012。 + +现在我们已经熟悉了预测问题,我们将看看加载和探索这个数据集。 + +## 3.下载数据集 + +该数据集是免费提供的,可以从 UCI 机器学习库下载。 + +数据以单个 zip 文件的形式提供,大小约为 58 兆字节。此下载的直接链接如下: + +* [UCI HAR Dataset.zip](https://archive.ics.uci.edu/ml/machine-learning-databases/00240/UCI%20HAR%20Dataset.zip) + +下载数据集并将所有文件解压缩到当前工作目录中名为“ _HARDataset_ ”的新目录中。 + +检查解压缩的内容,您会注意到以下几点: + +* 存在“ _train_ ”和“ _test_ ”文件夹,其包含用于建模的数据的分割部分(例如,70%/ 30%)。 +* 有一个“ _README.txt_ ”文件,其中包含数据集的详细技术说明和解压缩文件的内容。 +* 有一个“ _features.txt_ ”文件,其中包含工程特性的技术说明。 + +“ _train_ ”和“ _test_ ”文件夹的内容类似(例如文件夹和文件名),尽管它们包含的特定数据存在差异。 + +检查“train”文件夹显示了一些重要元素: + +* 包含预处理数据的“ _Inertial Signals_ ”文件夹。 +* “ _X_train.txt_ ”文件,包含用于拟合模型的工程特征。 +* “ _y_train.txt_ ”包含每个观察的类标签(1-6)。 +* “ _subject_train.txt_ ”文件,其中包含数据文件中每一行的映射及其主题标识符(1-30)。 + +每个文件中的行数匹配,表示每行是每个数据文件中的一个记录。 + +“ _Inertial Signals_ ”目录包含 9 个文件。 + +* _x,y 和 z 轴的重力加速度 _ 数据文件: _total_acc_x_train.txt_ , _total_acc_y_train.txt_ , _total_acc_z_train.txt_ 。 +* _x,y 和 z 轴的身体加速度 _ 数据文件: _body_acc_x_train.txt_ , _body_acc_y_train.txt_ ,body_acc_z_train.txt。 +* _x,y 和 z 轴的体陀螺 _ 数据文件: _body_gyro_x_train.txt_ , _body_gyro_y_train.txt_ , _body_gyro_z_train.txt_ 。 + +该结构在“ _test_ ”目录中进行镜像。 + +我们将把注意力集中在“_ 惯性信号 _”中的数据,因为这是开发可以学习合适表示的机器学习模型中最有趣的,而不是使用特定于域的特征工程。 + +检查数据文件显示列由空格分隔,值显示为缩放到-1,1。此缩放可以通过数据集随附的 _README.txt_ 文件中的注释确认。 + +现在我们知道了我们拥有的数据,我们可以弄清楚如何将其加载到内存中。 + +## 4.加载数据 + +在本节中,我们将开发一些代码来将数据集加载到内存中。 + +首先,我们需要加载一个文件。 + +我们可以使用 [read_csv()Pandas 函数](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_csv.html)来加载单个数据文件,并指定该文件没有标题并使用空格分隔列。 + +``` +dataframe = read_csv(filepath, header=None, delim_whitespace=True) +``` + +我们可以将它包装在名为 _load_file()_ 的函数中。下面列出了此功能的完整示例。 + +``` +# load dataset +from pandas import read_csv + +# load a single file as a numpy array +def load_file(filepath): + dataframe = read_csv(filepath, header=None, delim_whitespace=True) + return dataframe.values + +data = load_file('HARDataset/train/Inertial Signals/total_acc_y_train.txt') +print(data.shape) +``` + +运行该示例加载文件' _total_acc_y_train.txt_ ',返回 NumPy 数组,并打印数组的形状。 + +我们可以看到训练数据由 7,352 行或数据窗口组成,其中每个窗口有 128 个观察值。 + +``` +(7352, 128) +``` + +接下来,将一组文件(例如所有身体加速度数据文件)作为单个组加载将是有用的。 + +理想情况下,在处理多变量时间序列数据时,以下列格式构建数据非常有用: + +``` +[samples, timesteps, features] +``` + +这有助于分析,并且是对诸如卷积神经网络和循环神经网络的深度学习模型的期望。 + +我们可以通过多次调用上面的 _load_file()_ 函数来实现这一点,对于组中的每个文件一次。 + +一旦我们将每个文件作为 NumPy 数组加载,我们就可以将所有三个数组组合或堆叠在一起。我们可以使用 [dstack()NumPy 函数](https://www.numpy.org/devdocs/reference/generated/numpy.dstack.html)来确保每个数组的堆叠方式使得要素在第三维中分离,正如我们所希望的那样。 + +函数 _load_group()_ 对文件名列表实现此行为,如下所示。 + +``` +# load a list of files, such as x, y, z data for a given variable +def load_group(filenames, prefix=''): + loaded = list() + for name in filenames: + data = load_file(prefix + name) + loaded.append(data) + # stack group so that features are the 3rd dimension + loaded = dstack(loaded) + return loaded +``` + +我们可以通过加载所有总加速文件来演示此功能。 + +下面列出了完整的示例。 + +``` +# load dataset +from numpy import dstack +from pandas import read_csv + +# load a single file as a numpy array +def load_file(filepath): + dataframe = read_csv(filepath, header=None, delim_whitespace=True) + return dataframe.values + +# load a list of files, such as x, y, z data for a given variable +def load_group(filenames, prefix=''): + loaded = list() + for name in filenames: + data = load_file(prefix + name) + loaded.append(data) + # stack group so that features are the 3rd dimension + loaded = dstack(loaded) + return loaded + +# load the total acc data +filenames = ['total_acc_x_train.txt', 'total_acc_y_train.txt', 'total_acc_z_train.txt'] +total_acc = load_group(filenames, prefix='HARDataset/train/Inertial Signals/') +print(total_acc.shape) +``` + +运行该示例将打印返回的 NumPy 数组的形状,显示数据集的三个要素 x,y 和 z 的预期样本数和时间步长。 + +``` +(7352, 128, 3) +``` + +最后,我们可以使用到目前为止开发的两个函数来加载列车和测试数据集的所有数据。 + +给定列车和测试文件夹中的并行结构,我们可以开发一个新函数来加载给定文件夹的所有输入和输出数据。该函数可以构建要加载的所有 9 个数据文件的列表,将它们作为一个具有 9 个特征的 NumPy 数组加载,然后加载包含输出类的数据文件。 + +下面的 _load_dataset()_ 函数实现了这种行为。它可以被称为“ _train_ ”组或“ _test_ ”组,作为字符串参数传递。 + +``` +# load a dataset group, such as train or test +def load_dataset(group, prefix=''): + filepath = prefix + group + '/Inertial Signals/' + # load all 9 files as a single array + filenames = list() + # total acceleration + filenames += ['total_acc_x_'+group+'.txt', 'total_acc_y_'+group+'.txt', 'total_acc_z_'+group+'.txt'] + # body acceleration + filenames += ['body_acc_x_'+group+'.txt', 'body_acc_y_'+group+'.txt', 'body_acc_z_'+group+'.txt'] + # body gyroscope + filenames += ['body_gyro_x_'+group+'.txt', 'body_gyro_y_'+group+'.txt', 'body_gyro_z_'+group+'.txt'] + # load input data + X = load_group(filenames, filepath) + # load class output + y = load_file(prefix + group + '/y_'+group+'.txt') + return X, y +``` + +下面列出了完整的示例。 + +``` +# load dataset +from numpy import dstack +from pandas import read_csv + +# load a single file as a numpy array +def load_file(filepath): + dataframe = read_csv(filepath, header=None, delim_whitespace=True) + return dataframe.values + +# load a list of files, such as x, y, z data for a given variable +def load_group(filenames, prefix=''): + loaded = list() + for name in filenames: + data = load_file(prefix + name) + loaded.append(data) + # stack group so that features are the 3rd dimension + loaded = dstack(loaded) + return loaded + +# load a dataset group, such as train or test +def load_dataset(group, prefix=''): + filepath = prefix + group + '/Inertial Signals/' + # load all 9 files as a single array + filenames = list() + # total acceleration + filenames += ['total_acc_x_'+group+'.txt', 'total_acc_y_'+group+'.txt', 'total_acc_z_'+group+'.txt'] + # body acceleration + filenames += ['body_acc_x_'+group+'.txt', 'body_acc_y_'+group+'.txt', 'body_acc_z_'+group+'.txt'] + # body gyroscope + filenames += ['body_gyro_x_'+group+'.txt', 'body_gyro_y_'+group+'.txt', 'body_gyro_z_'+group+'.txt'] + # load input data + X = load_group(filenames, filepath) + # load class output + y = load_file(prefix + group + '/y_'+group+'.txt') + return X, y + +# load all train +trainX, trainy = load_dataset('train', 'HARDataset/') +print(trainX.shape, trainy.shape) +# load all test +testX, testy = load_dataset('test', 'HARDataset/') +print(testX.shape, testy.shape) +``` + +运行该示例将加载训练和测试数据集。 + +我们可以看到测试数据集有 2,947 行窗口数据。正如预期的那样,我们可以看到列车和测试装置中的窗口大小匹配,并且每个列车和测试用例中的输出(y)大小与样本数量相匹配。 + +``` +(7352, 128, 9) (7352, 1) +(2947, 128, 9) (2947, 1) +``` + +既然我们知道如何加载数据,我们就可以开始探索它了。 + +## 5.活动类平衡 + +对数据进行良好的首次检查是调查每项活动的平衡。 + +我们相信,30 个科目中的每一个都进行了六项活动。 + +确认此期望将检查数据是否确实平衡,使模型更容易,并确认我们正确加载和解释数据集。 + +我们可以开发一个函数来总结输出变量的细分,例如: y 变量。 + +下面的函数 _class_breakdown()_ 实现了这种行为,首先将提供的 NumPy 数组包装在 DataFrame 中,按类值对行进行分组,并计算每个组的大小(行数)。然后总结结果,包括计数和百分比。 + +``` +# summarize the balance of classes in an output variable column +def class_breakdown(data): + # convert the numpy array into a dataframe + df = DataFrame(data) + # group data by the class value and calculate the number of rows + counts = df.groupby(0).size() + # retrieve raw rows + counts = counts.values + # summarize + for i in range(len(counts)): + percent = counts[i] / len(df) * 100 + print('Class=%d, total=%d, percentage=%.3f' % (i+1, counts[i], percent)) +``` + +总结列车和测试数据集中类的细分以确保它们具有类似的细分可能是有用的,然后将结果与组合数据集的细分进行比较。 + +下面列出了完整的示例。 + +``` +# summarize class balance +from numpy import array +from numpy import vstack +from pandas import read_csv +from pandas import DataFrame + +# load a single file as a numpy array +def load_file(filepath): + dataframe = read_csv(filepath, header=None, delim_whitespace=True) + return dataframe.values + +# summarize the balance of classes in an output variable column +def class_breakdown(data): + # convert the numpy array into a dataframe + df = DataFrame(data) + # group data by the class value and calculate the number of rows + counts = df.groupby(0).size() + # retrieve raw rows + counts = counts.values + # summarize + for i in range(len(counts)): + percent = counts[i] / len(df) * 100 + print('Class=%d, total=%d, percentage=%.3f' % (i+1, counts[i], percent)) + +# load train file +trainy = load_file('HARDataset/train/y_train.txt') +# summarize class breakdown +print('Train Dataset') +class_breakdown(trainy) + +# load test file +testy = load_file('HARDataset/test/y_test.txt') +# summarize class breakdown +print('Test Dataset') +class_breakdown(testy) + +# summarize combined class breakdown +print('Both') +combined = vstack((trainy, testy)) +class_breakdown(combined) +``` + +首先运行该示例总结了训练集的细分。我们可以看到每个类的非常相似的分布在数据集的 13%到 19%之间徘徊。 + +测试集和两个数据集上的结果看起来非常相似。 + +使用数据集可能是安全的,假设每个列车和测试集以及每个主题可以平衡类的分布。 + +``` +Train Dataset +Class=1, total=1226, percentage=16.676 +Class=2, total=1073, percentage=14.595 +Class=3, total=986, percentage=13.411 +Class=4, total=1286, percentage=17.492 +Class=5, total=1374, percentage=18.689 +Class=6, total=1407, percentage=19.138 + +Test Dataset +Class=1, total=496, percentage=16.831 +Class=2, total=471, percentage=15.982 +Class=3, total=420, percentage=14.252 +Class=4, total=491, percentage=16.661 +Class=5, total=532, percentage=18.052 +Class=6, total=537, percentage=18.222 + +Both +Class=1, total=1722, percentage=16.720 +Class=2, total=1544, percentage=14.992 +Class=3, total=1406, percentage=13.652 +Class=4, total=1777, percentage=17.254 +Class=5, total=1906, percentage=18.507 +Class=6, total=1944, percentage=18.876 +``` + +## 6.绘制一个主题的时间序列数据 + +我们正在处理时间序列数据,因此导入检查是创建原始数据的线图。 + +原始数据由每个变量的时间序列数据窗口组成,窗口确实有 50%的重叠。这表明我们可能会在观察中看到一些重复作为线图,除非重叠被删除。 + +我们可以从使用上面开发的函数加载训练数据集开始。 + +``` +# load data +trainX, trainy = load_dataset('train', 'HARDataset/') +``` + +接下来,我们可以在' _train_ '目录中加载' _subject_train.txt_ ',该目录提供行到它所属主题的映射。 + +我们可以使用 _load_file()_ 函数加载这个文件。加载后,我们还可以使用 _unique()_ NumPy 函数来检索训练数据集中的唯一主题列表。 + +``` +sub_map = load_file('HARDataset/train/subject_train.txt') +train_subjects = unique(sub_map) +print(train_subjects) +``` + +接下来,我们需要一种方法来检索单个主题的所有行,例如科目编号 1。 + +我们可以通过查找属于给定主题的所有行号并使用这些行号从训练数据集中加载的 X 和 y 数据中选择样本来完成此操作。 + +下面的 _data_for_subject()_ 函数实现了这种行为。它将获取加载的训练数据,行号到主题的加载映射,以及我们感兴趣的主题的主题标识号,并将返回 _X_ 和 _y_ 仅针对该主题的数据。 + +``` +# get all data for one subject +def data_for_subject(X, y, sub_map, sub_id): + # get row indexes for the subject id + ix = [i for i in range(len(sub_map)) if sub_map[i]==sub_id] + # return the selected samples + return X[ix, :, :], y[ix] +``` + +现在我们有一个主题的数据,我们可以绘制它。 + +数据由具有重叠的窗口组成。我们可以编写一个函数来消除这种重叠,并将窗口向下挤压给定变量,将其压缩成一个可以直接绘制为线图的长序列。 + +下面的 _to_series()_ 函数对给定变量实现此行为,例如窗户阵列。 + +``` +# convert a series of windows to a 1D list +def to_series(windows): + series = list() + for window in windows: + # remove the overlap from the window + half = int(len(window) / 2) - 1 + for value in window[-half:]: + series.append(value) + return series +``` + +最后,我们有足够的情节来绘制数据。我们可以依次绘制主题的九个变量中的每一个,以及活动水平的最终图。 + +每个系列将具有相同数量的时间步长(x 轴的长度),因此,为每个变量创建一个子图并垂直对齐所有图可能很有用,这样我们就可以比较每个变量的运动。 + +下面的 _plot_subject()_ 函数为单个主题的 _X_ 和 _y_ 数据实现此行为。该函数采用与 _load_dataset()_ 函数中加载的变量(第 3 轴)相同的顺序。每个情节都会添加粗略的标题,因此我们不会轻易混淆我们正在看的内容。 + +``` +# plot the data for one subject +def plot_subject(X, y): + pyplot.figure() + # determine the total number of plots + n, off = X.shape[2] + 1, 0 + # plot total acc + for i in range(3): + pyplot.subplot(n, 1, off+1) + pyplot.plot(to_series(X[:, :, off])) + pyplot.title('total acc '+str(i), y=0, loc='left') + off += 1 + # plot body acc + for i in range(3): + pyplot.subplot(n, 1, off+1) + pyplot.plot(to_series(X[:, :, off])) + pyplot.title('body acc '+str(i), y=0, loc='left') + off += 1 + # plot body gyro + for i in range(3): + pyplot.subplot(n, 1, off+1) + pyplot.plot(to_series(X[:, :, off])) + pyplot.title('body gyro '+str(i), y=0, loc='left') + off += 1 + # plot activities + pyplot.subplot(n, 1, n) + pyplot.plot(y) + pyplot.title('activity', y=0, loc='left') + pyplot.show() +``` + +下面列出了完整的示例。 + +``` +# plot all vars for one subject +from numpy import array +from numpy import dstack +from numpy import unique +from pandas import read_csv +from matplotlib import pyplot + +# load a single file as a numpy array +def load_file(filepath): + dataframe = read_csv(filepath, header=None, delim_whitespace=True) + return dataframe.values + +# load a list of files, such as x, y, z data for a given variable +def load_group(filenames, prefix=''): + loaded = list() + for name in filenames: + data = load_file(prefix + name) + loaded.append(data) + # stack group so that features are the 3rd dimension + loaded = dstack(loaded) + return loaded + +# load a dataset group, such as train or test +def load_dataset(group, prefix=''): + filepath = prefix + group + '/Inertial Signals/' + # load all 9 files as a single array + filenames = list() + # total acceleration + filenames += ['total_acc_x_'+group+'.txt', 'total_acc_y_'+group+'.txt', 'total_acc_z_'+group+'.txt'] + # body acceleration + filenames += ['body_acc_x_'+group+'.txt', 'body_acc_y_'+group+'.txt', 'body_acc_z_'+group+'.txt'] + # body gyroscope + filenames += ['body_gyro_x_'+group+'.txt', 'body_gyro_y_'+group+'.txt', 'body_gyro_z_'+group+'.txt'] + # load input data + X = load_group(filenames, filepath) + # load class output + y = load_file(prefix + group + '/y_'+group+'.txt') + return X, y + +# get all data for one subject +def data_for_subject(X, y, sub_map, sub_id): + # get row indexes for the subject id + ix = [i for i in range(len(sub_map)) if sub_map[i]==sub_id] + # return the selected samples + return X[ix, :, :], y[ix] + +# convert a series of windows to a 1D list +def to_series(windows): + series = list() + for window in windows: + # remove the overlap from the window + half = int(len(window) / 2) - 1 + for value in window[-half:]: + series.append(value) + return series + +# plot the data for one subject +def plot_subject(X, y): + pyplot.figure() + # determine the total number of plots + n, off = X.shape[2] + 1, 0 + # plot total acc + for i in range(3): + pyplot.subplot(n, 1, off+1) + pyplot.plot(to_series(X[:, :, off])) + pyplot.title('total acc '+str(i), y=0, loc='left') + off += 1 + # plot body acc + for i in range(3): + pyplot.subplot(n, 1, off+1) + pyplot.plot(to_series(X[:, :, off])) + pyplot.title('body acc '+str(i), y=0, loc='left') + off += 1 + # plot body gyro + for i in range(3): + pyplot.subplot(n, 1, off+1) + pyplot.plot(to_series(X[:, :, off])) + pyplot.title('body gyro '+str(i), y=0, loc='left') + off += 1 + # plot activities + pyplot.subplot(n, 1, n) + pyplot.plot(y) + pyplot.title('activity', y=0, loc='left') + pyplot.show() + +# load data +trainX, trainy = load_dataset('train', 'HARDataset/') +# load mapping of rows to subjects +sub_map = load_file('HARDataset/train/subject_train.txt') +train_subjects = unique(sub_map) +print(train_subjects) +# get the data for one subject +sub_id = train_subjects[0] +subX, suby = data_for_subject(trainX, trainy, sub_map, sub_id) +print(subX.shape, suby.shape) +# plot data for subject +plot_subject(subX, suby) +``` + +运行该示例将打印训练数据集中的唯一主题,第一个主题的数据样本,并创建一个包含 10 个图的图形,每个图形对应九个输入变量和输出类别。 + +``` +[ 1 3 5 6 7 8 11 14 15 16 17 19 21 22 23 25 26 27 28 29 30] +(341, 128, 9) (341, 1) +``` + +在图中,我们可以看到与活动 1,2 和 3 相对应的大运动周期:步行活动。对于编号较高的活动,4,5 和 6(坐,站立和铺设),我们还可以看到更少的活动(即相对直线)。 + +这是我们正确加载解释原始数据集的确认。 + +我们可以看到这个主题已经执行了两次相同的一般活动顺序,并且一些活动执行了两次以上。这表明,对于特定主题,我们不应假设可能已执行的活动或其顺序。 + +我们还可以看到一些固定活动的相对较大的运动,例如铺设。这些可能是异常值或与活动转换相关。可以将这些观察结果平滑或移除为异常值。 + +最后,我们在九个变量中看到了很多共性。很可能只需要这些迹线的一部分来开发预测模型。 + +![Line plot for all variables for a single subject](img/3ca55b2eb8d252bf860cc2444e9f45b1.jpg) + +单个主题的所有变量的线图 + +我们可以通过做一个小的改动来重新运行另一个主题的例子,例如:选择训练数据集中第二个主题的标识符。 + +``` +# get the data for one subject +sub_id = train_subjects[1] +``` + +第二个主题的情节显示出类似的行为,没有任何意外。 + +活动的双重序列确实比第一个主题更加规律。 + +![Line plot for all variables for a second single subject](img/c26883b83ce0b79bd14c102258ea76e6.jpg) + +第二个单个主题的所有变量的线图 + +## 7.绘制每个受试者的直方图 + +由于问题是框架,我们有兴趣使用一些科目的运动数据来预测其他科目的运动活动。 + +这表明跨学科的运动数据必须有规律性。我们知道数据已经在-1 和 1 之间缩放,可能是每个受试者,这表明检测到的运动的幅度将是相似的。 + +我们还期望运动数据的分布在不同主题之间是相似的,因为它们执行相同的动作。 + +我们可以通过绘制和比较受试者的运动数据的直方图来检查这一点。一种有用的方法是为每个受试者创建一个图并绘制给定数据的所有三个轴(例如总加速度),然后对多个受试者重复此操作。可以修改绘图以使用相同的轴并水平对齐,以便可以比较跨主题的每个变量的分布。 + +下面的 _plot_subject_histograms()_ 函数实现了这种行为。该函数采用加载的数据集和行到主题的映射以及要绘制的最大主题数,默认情况下固定为 10。 + +为每个主题创建一个绘图,并将一个数据类型的三个变量绘制为具有 100 个二进制位的直方图,以帮助使分布明显。每个图共享相同的轴,该轴固定在-1 和 1 的边界。 + +``` +# plot histograms for multiple subjects +def plot_subject_histograms(X, y, sub_map, n=10): + pyplot.figure() + # get unique subjects + subject_ids = unique(sub_map[:,0]) + # enumerate subjects + xaxis = None + for k in range(n): + sub_id = subject_ids[k] + # get data for one subject + subX, _ = data_for_subject(X, y, sub_map, sub_id) + # total acc + for i in range(3): + ax = pyplot.subplot(n, 1, k+1, sharex=xaxis) + ax.set_xlim(-1,1) + if k == 0: + xaxis = ax + pyplot.hist(to_series(subX[:,:,i]), bins=100) + pyplot.show() +``` + +下面列出了完整的示例。 + +``` +# plot histograms for multiple subjects +from numpy import array +from numpy import unique +from numpy import dstack +from pandas import read_csv +from matplotlib import pyplot + +# load a single file as a numpy array +def load_file(filepath): + dataframe = read_csv(filepath, header=None, delim_whitespace=True) + return dataframe.values + +# load a list of files, such as x, y, z data for a given variable +def load_group(filenames, prefix=''): + loaded = list() + for name in filenames: + data = load_file(prefix + name) + loaded.append(data) + # stack group so that features are the 3rd dimension + loaded = dstack(loaded) + return loaded + +# load a dataset group, such as train or test +def load_dataset(group, prefix=''): + filepath = prefix + group + '/Inertial Signals/' + # load all 9 files as a single array + filenames = list() + # total acceleration + filenames += ['total_acc_x_'+group+'.txt', 'total_acc_y_'+group+'.txt', 'total_acc_z_'+group+'.txt'] + # body acceleration + filenames += ['body_acc_x_'+group+'.txt', 'body_acc_y_'+group+'.txt', 'body_acc_z_'+group+'.txt'] + # body gyroscope + filenames += ['body_gyro_x_'+group+'.txt', 'body_gyro_y_'+group+'.txt', 'body_gyro_z_'+group+'.txt'] + # load input data + X = load_group(filenames, filepath) + # load class output + y = load_file(prefix + group + '/y_'+group+'.txt') + return X, y + +# get all data for one subject +def data_for_subject(X, y, sub_map, sub_id): + # get row indexes for the subject id + ix = [i for i in range(len(sub_map)) if sub_map[i]==sub_id] + # return the selected samples + return X[ix, :, :], y[ix] + +# convert a series of windows to a 1D list +def to_series(windows): + series = list() + for window in windows: + # remove the overlap from the window + half = int(len(window) / 2) - 1 + for value in window[-half:]: + series.append(value) + return series + +# plot histograms for multiple subjects +def plot_subject_histograms(X, y, sub_map, n=10): + pyplot.figure() + # get unique subjects + subject_ids = unique(sub_map[:,0]) + # enumerate subjects + xaxis = None + for k in range(n): + sub_id = subject_ids[k] + # get data for one subject + subX, _ = data_for_subject(X, y, sub_map, sub_id) + # total acc + for i in range(3): + ax = pyplot.subplot(n, 1, k+1, sharex=xaxis) + ax.set_xlim(-1,1) + if k == 0: + xaxis = ax + pyplot.hist(to_series(subX[:,:,i]), bins=100) + pyplot.show() + +# load training dataset +X, y = load_dataset('train', 'HARDataset/') +# load mapping of rows to subjects +sub_map = load_file('HARDataset/train/subject_train.txt') +# plot histograms for subjects +plot_subject_histograms(X, y, sub_map) +``` + +运行该示例将创建一个包含 10 个绘图的单个图形,其中包含总加速度数据的三个轴的直方图。 + +给定图上的三个轴中的每一个具有不同的颜色,具体地,x,y 和 z 分别是蓝色,橙色和绿色。 + +我们可以看到给定轴的分布确实看起来是高斯分布的,具有大的独立数据组。 + +我们可以看到一些分布对齐(例如,中间的主要组大约为 0.0),这表明对象之间的运动数据可能存在一些连续性,至少对于这些数据而言。 + +![Histograms of the total acceleration data for 10 subjects](img/7630109f8dad0fafa89b8e2ee42fb8a2.jpg) + +10 个受试者的总加速度数据的直方图 + +我们可以更新 _plot_subject_histograms()_ 函数,接下来绘制身体加速度的分布。更新的功能如下所示。 + +``` +# plot histograms for multiple subjects +def plot_subject_histograms(X, y, sub_map, n=10): + pyplot.figure() + # get unique subjects + subject_ids = unique(sub_map[:,0]) + # enumerate subjects + xaxis = None + for k in range(n): + sub_id = subject_ids[k] + # get data for one subject + subX, _ = data_for_subject(X, y, sub_map, sub_id) + # body acc + for i in range(3): + ax = pyplot.subplot(n, 1, k+1, sharex=xaxis) + ax.set_xlim(-1,1) + if k == 0: + xaxis = ax + pyplot.hist(to_series(subX[:,:,3+i]), bins=100) + pyplot.show() +``` + +运行更新的示例会创建具有非常不同结果的相同图表。 + +在这里,我们可以看到所有数据聚集在一个主题内和主题之间的轴上。这表明数据可能是中心的(零均值)。跨受试者的这种强一致性可能有助于建模,并且可能表明总加速度数据中受试者之间的差异可能不那么有用。 + +![Histograms of the body acceleration data for 10 subjects](img/30ce77b6c30759b1c1239872c87b6aaf.jpg) + +10 名受试者的身体加速度数据的直方图 + +最后,我们可以为陀螺仪数据生成一个最终图。 + +更新的功能如下所示。 + +``` +# plot histograms for multiple subjects +def plot_subject_histograms(X, y, sub_map, n=10): + pyplot.figure() + # get unique subjects + subject_ids = unique(sub_map[:,0]) + # enumerate subjects + xaxis = None + for k in range(n): + sub_id = subject_ids[k] + # get data for one subject + subX, _ = data_for_subject(X, y, sub_map, sub_id) + # body acc + for i in range(3): + ax = pyplot.subplot(n, 1, k+1, sharex=xaxis) + ax.set_xlim(-1,1) + if k == 0: + xaxis = ax + pyplot.hist(to_series(subX[:,:,6+i]), bins=100) + pyplot.show() +``` + +运行该示例显示与身体加速度数据非常相似的结果。 + +我们看到每个轴上的每个轴的高斯分布的可能性很高,以 0.0 为中心。分布更宽一些,显示更加丰富的尾巴,但这对于跨主题的运动数据建模是一个令人鼓舞的发现。 + +![Histograms of the body gyroscope data for 10 subjects](img/f3da9bb298e41b80fb0ab03ad4b59933.jpg) + +10 名受试者的身体陀螺仪数据的直方图 + +## 8.绘制每个活动的直方图 + +我们有兴趣根据活动数据区分活动。 + +最简单的情况是区分单个主题的活动。调查此问题的一种方法是按活动审查主题的移动数据分布。我们希望看到单个主题的不同活动的运动数据之间的分布存在一些差异。 + +我们可以通过创建每个活动的直方图来检查这一点,每个图上给定数据类型的三个轴。同样,可以水平排列图以比较每个数据轴的活动分布。我们希望看到各地活动的分布存在差异。 + +首先,我们必须按活动对主题的跟踪进行分组。下面的 _data_by_activity()_ 函数实现了这种行为。 + +``` +# group data by activity +def data_by_activity(X, y, activities): + # group windows by activity + return {a:X[y[:,0]==a, :, :] for a in activities} +``` + +我们现在可以为给定主题的每个活动创建绘图。 + +下面的 _plot_activity_histograms()_ 函数为给定主题的跟踪数据实现此功能。 + +首先,按活动对数据进行分组,然后为每个活动创建一个子图,并将数据类型的每个轴添加为直方图。该函数仅枚举数据的前三个特征,即总加速度变量。 + +``` +# plot histograms for each activity for a subject +def plot_activity_histograms(X, y): + # get a list of unique activities for the subject + activity_ids = unique(y[:,0]) + # group windows by activity + grouped = data_by_activity(X, y, activity_ids) + # plot per activity, histograms for each axis + pyplot.figure() + xaxis = None + for k in range(len(activity_ids)): + act_id = activity_ids[k] + # total acceleration + for i in range(3): + ax = pyplot.subplot(len(activity_ids), 1, k+1, sharex=xaxis) + ax.set_xlim(-1,1) + if k == 0: + xaxis = ax + pyplot.hist(to_series(grouped[act_id][:,:,i]), bins=100) + pyplot.title('activity '+str(act_id), y=0, loc='left') + pyplot.show() +``` + +下面列出了完整的示例。 + +``` +# plot histograms per activity for a subject +from numpy import array +from numpy import dstack +from numpy import unique +from pandas import read_csv +from matplotlib import pyplot + +# load a single file as a numpy array +def load_file(filepath): + dataframe = read_csv(filepath, header=None, delim_whitespace=True) + return dataframe.values + +# load a list of files, such as x, y, z data for a given variable +def load_group(filenames, prefix=''): + loaded = list() + for name in filenames: + data = load_file(prefix + name) + loaded.append(data) + # stack group so that features are the 3rd dimension + loaded = dstack(loaded) + return loaded + +# load a dataset group, such as train or test +def load_dataset(group, prefix=''): + filepath = prefix + group + '/Inertial Signals/' + # load all 9 files as a single array + filenames = list() + # total acceleration + filenames += ['total_acc_x_'+group+'.txt', 'total_acc_y_'+group+'.txt', 'total_acc_z_'+group+'.txt'] + # body acceleration + filenames += ['body_acc_x_'+group+'.txt', 'body_acc_y_'+group+'.txt', 'body_acc_z_'+group+'.txt'] + # body gyroscope + filenames += ['body_gyro_x_'+group+'.txt', 'body_gyro_y_'+group+'.txt', 'body_gyro_z_'+group+'.txt'] + # load input data + X = load_group(filenames, filepath) + # load class output + y = load_file(prefix + group + '/y_'+group+'.txt') + return X, y + +# get all data for one subject +def data_for_subject(X, y, sub_map, sub_id): + # get row indexes for the subject id + ix = [i for i in range(len(sub_map)) if sub_map[i]==sub_id] + # return the selected samples + return X[ix, :, :], y[ix] + +# convert a series of windows to a 1D list +def to_series(windows): + series = list() + for window in windows: + # remove the overlap from the window + half = int(len(window) / 2) - 1 + for value in window[-half:]: + series.append(value) + return series + +# group data by activity +def data_by_activity(X, y, activities): + # group windows by activity + return {a:X[y[:,0]==a, :, :] for a in activities} + +# plot histograms for each activity for a subject +def plot_activity_histograms(X, y): + # get a list of unique activities for the subject + activity_ids = unique(y[:,0]) + # group windows by activity + grouped = data_by_activity(X, y, activity_ids) + # plot per activity, histograms for each axis + pyplot.figure() + xaxis = None + for k in range(len(activity_ids)): + act_id = activity_ids[k] + # total acceleration + for i in range(3): + ax = pyplot.subplot(len(activity_ids), 1, k+1, sharex=xaxis) + ax.set_xlim(-1,1) + if k == 0: + xaxis = ax + pyplot.hist(to_series(grouped[act_id][:,:,i]), bins=100) + pyplot.title('activity '+str(act_id), y=0, loc='left') + pyplot.show() + +# load data +trainX, trainy = load_dataset('train', 'HARDataset/') +# load mapping of rows to subjects +sub_map = load_file('HARDataset/train/subject_train.txt') +train_subjects = unique(sub_map) +# get the data for one subject +sub_id = train_subjects[0] +subX, suby = data_for_subject(trainX, trainy, sub_map, sub_id) +# plot data for subject +plot_activity_histograms(subX, suby) +``` + +运行该示例将创建包含六个子图的图,每个子图用于列车数据集中第一个主题的每个活动。总加速度数据的 x,y 和 z 轴中的每一个分别具有蓝色,橙色和绿色直方图。 + +我们可以看到每个活动都有不同的数据分布,大运动(前三个活动)与固定活动(最后三个活动)之间存在显着差异。前三个活动的数据分布看起来是高斯的,可能有不同的均值和标准偏差。后一活动的分布看起来是多模态的(即多个峰值)。 + +![Histograms of the total acceleration data by activity](img/8e67ae0bcc130d32971f16ff501fe0e7.jpg) + +按活动计算的总加速度数据的直方图 + +我们可以使用 _plot_activity_histograms()_ 的更新版本重新运行相同的示例,该版本将绘制车身加速度数据。 + +更新的功能如下所示。 + +``` +# plot histograms for each activity for a subject +def plot_activity_histograms(X, y): + # get a list of unique activities for the subject + activity_ids = unique(y[:,0]) + # group windows by activity + grouped = data_by_activity(X, y, activity_ids) + # plot per activity, histograms for each axis + pyplot.figure() + xaxis = None + for k in range(len(activity_ids)): + act_id = activity_ids[k] + # total acceleration + for i in range(3): + ax = pyplot.subplot(len(activity_ids), 1, k+1, sharex=xaxis) + ax.set_xlim(-1,1) + if k == 0: + xaxis = ax + pyplot.hist(to_series(grouped[act_id][:,:,3+i]), bins=100) + pyplot.title('activity '+str(act_id), y=0, loc='left') + pyplot.show() +``` + +运行更新的示例会创建一个新的图。 + +在这里,我们可以看到在动作与静止活动之间的活动中有更多类似的分布。在动态活动的情况下数据看起来是双峰的,并且在静止活动的情况下可能是高斯的或指数的。 + +我们通过活动看到的总体对体加速度分布的模式反映了我们在上一节中使用相同数据类型看到的对象。也许总加速度数据是区分活动的关键。 + +![Histograms of the body acceleration data by activity](img/0d234fc3f62edaa2aabe367976c03f68.jpg) + +通过活动的身体加速度数据的直方图 + +最后,我们可以再次更新示例以绘制陀螺仪数据的每个活动的直方图。 + +更新的功能如下所示。 + +``` +# plot histograms for each activity for a subject +def plot_activity_histograms(X, y): + # get a list of unique activities for the subject + activity_ids = unique(y[:,0]) + # group windows by activity + grouped = data_by_activity(X, y, activity_ids) + # plot per activity, histograms for each axis + pyplot.figure() + xaxis = None + for k in range(len(activity_ids)): + act_id = activity_ids[k] + # total acceleration + for i in range(3): + ax = pyplot.subplot(len(activity_ids), 1, k+1, sharex=xaxis) + ax.set_xlim(-1,1) + if k == 0: + xaxis = ax + pyplot.hist(to_series(grouped[act_id][:,:,6+i]), bins=100) + pyplot.title('activity '+str(act_id), y=0, loc='left') + pyplot.show() +``` + +运行该示例将创建具有与身体加速度数据类似的模式的绘图,尽管可能显示胖尾高斯分布而不是动态活动的双峰分布。 + +![Histograms of the body gyroscope data by activity](img/ba75ce0fe72c2fdbb970c8816d94f6e5.jpg) + +通过活动的身体陀螺仪数据的直方图 + +所有这些图都是为第一个主题创建的,我们期望在其他主题的活动中看到类似的运动数据分布和关系。 + +## 9.绘制活动持续时间箱图 + +需要考虑的最后一个方面是受试者在每项活动上花费的时间。 + +这与班级的平衡密切相关。如果活动(类)在数据集中通常是平衡的,那么我们期望特定主题在其跟踪过程中的活动平衡也将相当平衡。 + +我们可以通过计算每个主题在每个活动上花费的时间(样本或行数)并查看每个活动的持续时间分布来确认这一点。 + +审查这些数据的一种方便方法是将分布总结为箱线图,显示中位数(线),中间 50%(方框),数据的一般范围(四分位数间距)和异常值(以点为单位) 。 + +下面的函数 _plot_activity_durations_by_subject()_ 通过首先按主题分割数据集,然后按活动分割主题数据并计算在每个活动上花费的行,然后最终创建持续时间测量的每个活动的箱线图来实现此行为。 + +``` +# plot activity durations by subject +def plot_activity_durations_by_subject(X, y, sub_map): + # get unique subjects and activities + subject_ids = unique(sub_map[:,0]) + activity_ids = unique(y[:,0]) + # enumerate subjects + activity_windows = {a:list() for a in activity_ids} + for sub_id in subject_ids: + # get data for one subject + _, subj_y = data_for_subject(X, y, sub_map, sub_id) + # count windows by activity + for a in activity_ids: + activity_windows[a].append(len(subj_y[subj_y[:,0]==a])) + # organize durations into a list of lists + durations = [activity_windows[a] for a in activity_ids] + pyplot.boxplot(durations, labels=activity_ids) + pyplot.show() +``` + +下面列出了完整的示例。 + +``` +# plot durations of each activity by subject +from numpy import array +from numpy import dstack +from numpy import unique +from pandas import read_csv +from matplotlib import pyplot + +# load a single file as a numpy array +def load_file(filepath): + dataframe = read_csv(filepath, header=None, delim_whitespace=True) + return dataframe.values + +# load a list of files, such as x, y, z data for a given variable +def load_group(filenames, prefix=''): + loaded = list() + for name in filenames: + data = load_file(prefix + name) + loaded.append(data) + # stack group so that features are the 3rd dimension + loaded = dstack(loaded) + return loaded + +# load a dataset group, such as train or test +def load_dataset(group, prefix=''): + filepath = prefix + group + '/Inertial Signals/' + # load all 9 files as a single array + filenames = list() + # total acceleration + filenames += ['total_acc_x_'+group+'.txt', 'total_acc_y_'+group+'.txt', 'total_acc_z_'+group+'.txt'] + # body acceleration + filenames += ['body_acc_x_'+group+'.txt', 'body_acc_y_'+group+'.txt', 'body_acc_z_'+group+'.txt'] + # body gyroscope + filenames += ['body_gyro_x_'+group+'.txt', 'body_gyro_y_'+group+'.txt', 'body_gyro_z_'+group+'.txt'] + # load input data + X = load_group(filenames, filepath) + # load class output + y = load_file(prefix + group + '/y_'+group+'.txt') + return X, y + +# get all data for one subject +def data_for_subject(X, y, sub_map, sub_id): + # get row indexes for the subject id + ix = [i for i in range(len(sub_map)) if sub_map[i]==sub_id] + # return the selected samples + return X[ix, :, :], y[ix] + +# convert a series of windows to a 1D list +def to_series(windows): + series = list() + for window in windows: + # remove the overlap from the window + half = int(len(window) / 2) - 1 + for value in window[-half:]: + series.append(value) + return series + +# group data by activity +def data_by_activity(X, y, activities): + # group windows by activity + return {a:X[y[:,0]==a, :, :] for a in activities} + +# plot activity durations by subject +def plot_activity_durations_by_subject(X, y, sub_map): + # get unique subjects and activities + subject_ids = unique(sub_map[:,0]) + activity_ids = unique(y[:,0]) + # enumerate subjects + activity_windows = {a:list() for a in activity_ids} + for sub_id in subject_ids: + # get data for one subject + _, subj_y = data_for_subject(X, y, sub_map, sub_id) + # count windows by activity + for a in activity_ids: + activity_windows[a].append(len(subj_y[subj_y[:,0]==a])) + # organize durations into a list of lists + durations = [activity_windows[a] for a in activity_ids] + pyplot.boxplot(durations, labels=activity_ids) + pyplot.show() + +# load training dataset +X, y = load_dataset('train', 'HARDataset/') +# load mapping of rows to subjects +sub_map = load_file('HARDataset/train/subject_train.txt') +# plot durations +plot_activity_durations_by_subject(X, y, sub_map) +``` + +运行该示例将创建六个箱形图,每个活动一个。 + +每个箱图总结了训练数据集中每个活动花费的时间(行数或窗口数)。 + +我们可以看到,受试者在静止活动(4,5 和 6)上花费的时间更多,在动作活动中花费的时间更少(1,2 和 3),3 的分布最小,或者时间花费最少。 + +活动的分布并不大,这表明不需要削减较长时间的活动或过度采样活动。尽管如果运动活动的预测模型的技能通常更差,这些方法仍然可用。 + +![Boxplot of activity durations per subject on train set](img/ecd2d7ec902c9f547e5b006036f581d3.jpg) + +火车组上每个受试者的活动持续时间的箱线图 + +我们可以使用以下附加行为训练数据创建类似的箱线图。 + +``` +# load test dataset +X, y = load_dataset('test', 'HARDataset/') +# load mapping of rows to subjects +sub_map = load_file('HARDataset/test/subject_test.txt') +# plot durations +plot_activity_durations_by_subject(X, y, sub_map) +``` + +运行更新的示例显示了活动之间的类似关系。 + +这是令人鼓舞的,这表明测试和训练数据集确实可以合理地代表整个数据集。 + +![Boxplot of activity durations per subject on test set](img/1c23cef438429401b113cc9ebd2bd4b9.jpg) + +测试集上每个受试者的活动持续时间的箱线图 + +现在我们已经探索了数据集,我们可以建议一些关于如何建模的想法。 + +## 10.建模方法 + +在本节中,我们总结了一些建模活动识别数据集的方法。 + +这些想法分为项目的主题。 + +### 问题框架 + +第一个重要的考虑因素是预测问题的框架。 + +原始作品中描述的问题的框架是基于已知主体的运动数据和活动,根据运动数据预测新主体的活动。 + +我们可以总结为: + +* 给出一个移动数据窗口预测活动。 + +这是一个合理而有用的问题框架。 + +将提供的数据构建为预测问题的其他一些可能方法包括: + +* 在给定移动数据的时间步长的情况下预测活动。 +* 给出多个移动数据窗口的预测活动。 +* 给定多个移动数据窗口的预测活动序列。 +* 给出预分段活动的一系列移动数据的预测活动。 +* 给定移动数据的时间步长,预测活动停止或转换。 +* 给定移动数据窗口预测静止或非静止活动 + +其中一些框架可能过于具有挑战性或太容易。 + +然而,这些框架提供了探索和理解数据集的其他方法。 + +### 数据准备 + +在使用原始数据训练模型之前可能需要一些数据准备。 + +数据似乎已经缩放到范围[-1,1]。 + +在建模之前可以执行的一些其他数据转换包括: + +* 跨学科规范化。 +* 每个科目的标准化。 +* 跨学科标准化。 +* 轴功能选择。 +* 数据类型功能选择。 +* 信号异常检测和清除。 +* 删除过多代表活动的窗口。 +* 对代表性不足的活动进行过采样。 +* 将信号数据下采样到 1 / 4,1 / 2,1,2 或其他部分。 + +### 预测建模 + +通常,问题是时间序列多类分类问题。 + +正如我们所看到的,它也可以被构造为二元分类问题和多步时间序列分类问题。 + +最初的论文探讨了在数据集版本中使用经典机器学习算法,其中从每个数据窗口设计了特征。具体地说,是一种改进的支持向量机 + +数据集的特征工程版本上的 SVM 结果可以提供问题表现的基线。 + +从这一点扩展,在该版本的数据集上评估多个线性,非线性和集合机器学习算法可以提供改进的基准。 + +问题的焦点可能是数据集的未设计或原始版本。 + +在这里,可以探索模型复杂性的进展,以确定最合适的问题模型;一些候选模型包括: + +* 常见的线性,非线性和集成机器学习算法。 +* 多层感知器。 +* 卷积神经网络,特别是 1D CNN。 +* 循环神经网络,特别是 LSTM。 +* CNN 和 LSTMS 的杂交体,例如 CNN-LSTM 和 ConvLSTM。 + +### 模型评估 + +在原始论文中对模型的评估涉及使用 70%和 30%比率的受试者对数据进行训练/测试分割。 + +对这种预先定义的数据分割的探索表明,这两组都能够合理地代表整个数据集。 + +另一种替代方法可以是每个受试者使用留一法交叉验证或 LOOCV。除了为每个受试者提供用作保留测试集的机会之外,该方法还将提供可以平均和总结的 30 个分数的群体,这可以提供更稳健的结果。 + +使用分类精度和混淆矩阵表示模型表现,两者都适用于预测问题的多类性质。 + +具体而言,混淆矩阵将有助于确定某些类别比其他类别更容易或更具挑战性,例如静止活动与涉及运动的活动相比。 + +## 进一步阅读 + +如果您希望深入了解,本节将提供有关该主题的更多资源。 + +### 文件 + +* [基于传感器的活动识别的深度学习:一项调查](https://arxiv.org/abs/1707.03502)。 +* [使用智能手机进行人类活动识别的公共领域数据集](https://upcommons.upc.edu/handle/2117/20897),2013 年。 +* [智能手机上的人类活动识别使用多类硬件友好支持向量机](https://link.springer.com/chapter/10.1007/978-3-642-35395-6_30),2012。 + +### API + +* [pandas.read_csv API](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_csv.html) +* [numpy.dstack API](https://www.numpy.org/devdocs/reference/generated/numpy.dstack.html) + +### 用品 + +* [使用智能手机数据集进行人类活动识别,UCI 机器学习库](https://archive.ics.uci.edu/ml/datasets/human+activity+recognition+using+smartphones) +* [活动识别,维基百科](https://en.wikipedia.org/wiki/Activity_recognition) +* [使用智能手机传感器](https://www.youtube.com/watch?v=XOEN9W05_4A),视频的活动识别实验。 + +## 摘要 + +在本教程中,您发现了使用智能手机数据集进行时间序列分类的活动识别,以及如何加载和浏览数据集以使其为预测建模做好准备。 + +具体来说,你学到了: + +* 如何下载数据集并将其加载到内存中。 +* 如何使用线图,直方图和箱线图来更好地理解运动数据的结构。 +* 如何建模问题包括框架,数据准备,建模和评估。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/how-to-predict-room-occupancy-based-on-environmental-factors.md b/docs/dl-ts/how-to-predict-room-occupancy-based-on-environmental-factors.md new file mode 100644 index 0000000000000000000000000000000000000000..ceebf66815a9ed5e581a22ddd44518611b1c79b9 --- /dev/null +++ b/docs/dl-ts/how-to-predict-room-occupancy-based-on-environmental-factors.md @@ -0,0 +1,397 @@ +# 如何根据环境因素预测房间占用率 + +> 原文: [https://machinelearningmastery.com/how-to-predict-room-occupancy-based-on-environmental-factors/](https://machinelearningmastery.com/how-to-predict-room-occupancy-based-on-environmental-factors/) + +诸如 Arduino 设备之类的小型计算机可以在建筑物内用于记录环境变量,从中可以预测简单和有用的属性。 + +一个示例是基于诸如温度,湿度和相关措施的环境措施来预测房间是否被占用。 + +这是一种称为房间占用分类的常见时间序列分类问题。 + +在本教程中,您将发现一个标准的多变量时间序列分类问题,用于使用环境变量的测量来预测房间占用率。 + +完成本教程后,您将了解: + +* 机器学习中的占用检测标准时间序列分类问题。 +* 如何加载和可视化多变量时间序列分类数据。 +* 如何开发简单的幼稚和逻辑回归模型,以达到近乎完美的技能。 + +让我们开始吧。 + +* **更新 Oct / 2018** :更新了数据集来源的描述(我真的搞砸了),谢谢 Luis Candanedo。 + +## 教程概述 + +本教程分为四个部分;他们是: + +1. 占用检测问题描述 +2. 数据可视化 +3. 连接数据集 +4. 简单的预测模型 + +## 占用检测问题描述 + +标准时间序列分类数据集是 UCI 机器学习库中可用的“_ 占用检测 _”问题。 + +* [占用检测数据集,UCI 机器学习库](https://archive.ics.uci.edu/ml/datasets/Occupancy+Detection+) + +这是一个二元分类问题,需要使用诸如温度和湿度的环境因素的观察来分类房间是否被占用或未被占用。 + +该数据集在 2016 年论文“[使用统计学习模型](https://www.sciencedirect.com/science/article/pii/S0378778815304357)”的 Luis M. Candanedo 和 VéroniqueFeldheim 的光,温度,湿度和 CO2 测量的办公室精确占用检测中进行了描述。 + +通过使用一套环境传感器监控办公室并使用摄像机确定房间是否被占用来收集数据集。 + +> 监测办公室房间的以下变量:温度,湿度,光照和二氧化碳水平。采用微控制器来获取数据。 ZigBee 无线电连接到它并用于将信息传输到记录站。数码相机用于确定房间是否被占用。相机时间每分钟标记图片,并且手动研究这些图片以标记数据。 + +- [使用统计学习模型](https://www.sciencedirect.com/science/article/pii/S0378778815304357),2016 年,通过光照,温度,湿度和 CO2 测量精确占用办公室。 + +数据提供日期时间信息和多天每分钟采取的六项环境措施,具体为: + +* 温度以摄氏度为单位。 +* 相对湿度百分比。 +* 以勒克斯测量的光。 +* 二氧化碳以百万分之一计。 +* 湿度比,源自温度和相对湿度,以千克水蒸气/千克空气测量。 +* 占用率为 1 表示占用,0 表示未占用。 + +该数据集已用于许多简单的建模机器学习论文中。例如,参见文章“[基于可见光的占用推断使用集合学习](https://ieeexplore.ieee.org/document/8302496/),”2018 以获得进一步的参考。 + +## 数据可视化 + +这些数据以 CSV 格式提供三个文件,声称是用于训练,验证和测试的数据分割。 + +这三个文件如下: + +* **datatest.txt** (测试):从 2015-02-02 14:19:00 到 2015-02-04 10:43:00 +* **datatraining.txt** (火车):从 2015-02-04 17:51:00 到 2015-02-10 09:33:00 +* **datatest2.txt** (val):从 2015-02-11 14:48:00 到 2015-02-18 09:19:00 + +最初显而易见的是,数据中的分割在时间上并不是连续的,并且存在差距。 + +测试数据集在训练和验证数据集之前及时。也许这是文件命名约定中的错误。我们还可以看到数据从 2 月 2 日延伸到 2 月 18 日,该日期跨越 17 个日历日,而不是 20 个日历日。 + +从这里下载文件并将它们放在当前的工作目录中: + +* [占用检测数据集,UCI 机器学习库](https://archive.ics.uci.edu/ml/datasets/Occupancy+Detection+) + +每个文件都包含一个标题行,但包含一个行号的列,该列不包括标题行中的条目。 + +为了正确加载数据文件,请更新每个文件的标题行,如下所示: + +从: + +``` +"date","Temperature","Humidity","Light","CO2","HumidityRatio","Occupancy" +``` + +至: + +``` +"no","date","Temperature","Humidity","Light","CO2","HumidityRatio","Occupancy" +``` + +下面是带有修改的 _datatraining.txt_ 文件的前五行示例。 + +``` +"no","date","Temperature","Humidity","Light","CO2","HumidityRatio","Occupancy" +"1","2015-02-04 17:51:00",23.18,27.272,426,721.25,0.00479298817650529,1 +"2","2015-02-04 17:51:59",23.15,27.2675,429.5,714,0.00478344094931065,1 +"3","2015-02-04 17:53:00",23.15,27.245,426,713.5,0.00477946352442199,1 +"4","2015-02-04 17:54:00",23.15,27.2,426,708.25,0.00477150882608175,1 +"5","2015-02-04 17:55:00",23.1,27.2,426,704.5,0.00475699293331518,1 +... +``` + +然后我们可以使用 Pandas _read_csv()_ 函数加载数据文件,如下所示: + +``` +# load all data +data1 = read_csv('datatest.txt', header=0, index_col=1, parse_dates=True, squeeze=True) +data2 = read_csv('datatraining.txt', header=0, index_col=1, parse_dates=True, squeeze=True) +data3 = read_csv('datatest2.txt', header=0, index_col=1, parse_dates=True, squeeze=True) +``` + +加载后,我们可以为六个系列中的每一个创建一个图,清楚地显示三个数据集的及时分离。 + +下面列出了完整的示例。 + +``` +from pandas import read_csv +from matplotlib import pyplot +# load all data +data1 = read_csv('datatest.txt', header=0, index_col=1, parse_dates=True, squeeze=True) +data2 = read_csv('datatraining.txt', header=0, index_col=1, parse_dates=True, squeeze=True) +data3 = read_csv('datatest2.txt', header=0, index_col=1, parse_dates=True, squeeze=True) +# determine the number of features +n_features = data1.values.shape[1] +pyplot.figure() +for i in range(1, n_features): + # specify the subpout + pyplot.subplot(n_features, 1, i) + # plot data from each set + pyplot.plot(data1.index, data1.values[:, i]) + pyplot.plot(data2.index, data2.values[:, i]) + pyplot.plot(data3.index, data3.values[:, i]) + # add a readable name to the plot + pyplot.title(data1.columns[i], y=0.5, loc='right') +pyplot.show() +``` + +运行该示例会为每个数据集创建一个具有不同颜色的绘图: + +* _datatest.txt_ (测试):蓝色 +* _datatraining.txt_ (火车):橙色 +* _datatest2.txt_ (val):绿色 + +我们可以看到测试和训练集之间的小差距以及列车和验证集之间的较大差距。 + +我们还可以看到每个变量的系列中相应的结构(峰值)和房间占用率。 + +![Line Plot Showing Time Series Plots for all variables and each dataset](img/0b7c9423c80762c30eafc56d7e79711a.jpg) + +线图显示所有变量和每个数据集的时间序列图 + +## 连接数据集 + +我们可以通过保留数据的时间一致性并将所有三个集合连接成一个数据集来简化数据集,删除“ _no_ ”列。 + +这将允许临时测试问题的简单直接框架(在下一部分中),该临时框架可以在临时一致的方式上使用特殊列车/测试装置尺寸进行测试。 + +**注意**:这种简化不考虑数据中的时间间隙,并且依赖于先前时​​间步骤的一系列观察的算法可能需要不同的数据组织。 + +下面的示例加载数据,将其连接到时间上一致的数据集,并将结果保存到名为“ _combined.csv_ ”的新文件中。 + +``` +from pandas import read_csv +from pandas import concat +# load all data +data1 = read_csv('datatest.txt', header=0, index_col=1, parse_dates=True, squeeze=True) +data2 = read_csv('datatraining.txt', header=0, index_col=1, parse_dates=True, squeeze=True) +data3 = read_csv('datatest2.txt', header=0, index_col=1, parse_dates=True, squeeze=True) +# vertically stack and maintain temporal order +data = concat([data1, data2, data3]) +# drop row number +data.drop('no', axis=1, inplace=True) +# save aggregated dataset +data.to_csv('combined.csv') +``` + +运行该示例将连接的数据集保存到新文件' _combined.csv_ '。 + +## 简单的预测模型 + +该问题最简单的表述是根据当前的环境条件预测占用率。 + +我将此称为直接模型,因为它没有利用先前时间步骤的环境措施的观察结果。从技术上讲,这不是序列分类,它只是一个直接的分类问题,其中观察是在时间上排序的。 + +这似乎是我从文献中略读的问题的标准表达,令人失望的是,论文似乎使用了 UCI 网站上标注的训练/验证/测试数据。 + +我们将使用上一节中描述的组合数据集,并通过将最后 30%的数据作为测试集保留来评估模型技能。例如: + +``` +# load the dataset +data = read_csv('combined.csv', header=0, index_col=0, parse_dates=True, squeeze=True) +values = data.values +# split data into inputs and outputs +X, y = values[:, :-1], values[:, -1] +# split the dataset +trainX, testX, trainy, testy = train_test_split(X, y, test_size=0.3, shuffle=False, random_state=1) +``` + +接下来,我们可以从一个朴素的预测模型开始评估数据集的一些模型。 + +### 天真的模型 + +这个问题的一个简单模型是预测最突出的阶级结果。 + +这称为零规则,或原始预测算法。我们将评估测试集中每个示例的所有 0(未占用)和全 1(占用)的预测,并使用精度度量来评估方法。 + +下面是一个函数,它将根据测试集和选择的结果变量执行这种天真的预测 + +``` +def naive_prediction(testX, value): + return [value for x in range(len(testX))] +``` + +下面列出了完整的示例。 + +``` +# naive prediction model +from pandas import read_csv +from sklearn.metrics import accuracy_score +from sklearn.model_selection import train_test_split +from sklearn.metrics import accuracy_score +# load the dataset +data = read_csv('../datasets/occupancy_data/combined.csv', header=0, index_col=0, parse_dates=True, squeeze=True) +values = data.values +# split data into inputs and outputs +X, y = values[:, :-1], values[:, -1] +# split the dataset +trainX, testX, trainy, testy = train_test_split(X, y, test_size=0.3, shuffle=False, random_state=1) + +# make a naive prediction +def naive_prediction(testX, value): + return [value for x in range(len(testX))] + +# evaluate skill of predicting each class value +for value in [0, 1]: + # forecast + yhat = naive_prediction(testX, value) + # evaluate + score = accuracy_score(testy, yhat) + # summarize + print('Naive=%d score=%.3f' % (value, score)) +``` + +运行该示例打印天真预测和相关分数。 + +通过预测全部 0,我们可以看到基线分数约为 82%的准确度。都没有入住。 + +对于任何被认为对该问题熟练的模型,它必须达到 82%或更高的技能。 + +``` +Naive=0 score=0.822 +Naive=1 score=0.178 +``` + +### Logistic 回归 + +文献的摘要显示了应用于该问题的一系列复杂的神经网络模型。 + +首先,让我们尝试一个简单的逻辑回归分类算法。 + +下面列出了完整的示例。 + +``` +# logistic regression +from pandas import read_csv +from sklearn.metrics import accuracy_score +from sklearn.model_selection import train_test_split +from sklearn.metrics import accuracy_score +from sklearn.linear_model import LogisticRegression +# load the dataset +data = read_csv('combined.csv', header=0, index_col=0, parse_dates=True, squeeze=True) +values = data.values +# split data into inputs and outputs +X, y = values[:, :-1], values[:, -1] +# split the dataset +trainX, testX, trainy, testy = train_test_split(X, y, test_size=0.3, shuffle=False, random_state=1) +# define the model +model = LogisticRegression() +# fit the model on the training set +model.fit(trainX, trainy) +# predict the test set +yhat = model.predict(testX) +# evaluate model skill +score = accuracy_score(testy, yhat) +print(score) +``` + +运行该示例在训练数据集上拟合逻辑回归模型并预测测试数据集。 + +该模型的技能大约 99%准确,显示出天真方法的技巧。 + +通常,我建议在建模之前对数据进行居中和规范化,但是一些试验和错误表明,未缩放数据的模型更加熟练。 + +``` +0.992704280155642 +``` + +乍一看这是一个令人印象深刻的结果。 + +尽管测试设置与研究文献中的测试设置不同,但是报告的非常简单模型的技能优于更复杂的神经网络模型。 + +### 特征选择和 Logistic 回归 + +仔细观察时间序列图可以看出房间被占用的时间与环境措施的峰值之间存在明显的关系。 + +这是有道理的,并解释了为什么这个问题实际上很容易建模。 + +我们可以通过单独测试每个环境度量的简单逻辑回归模型来进一步简化模型。我们的想法是,我们不需要所有数据来预测占用率;或许只是其中一项措施就足够了。 + +这是最简单的特征选择类型,其中创建模型并单独评估每个特征。更高级的方法可以考虑每个特征子组。 + +下面列出了使用五个输入功能中的每一个单独测试逻辑模型的完整示例。 + +``` +# logistic regression feature selection +from pandas import read_csv +from sklearn.metrics import accuracy_score +from sklearn.model_selection import train_test_split +from sklearn.metrics import accuracy_score +from sklearn.linear_model import LogisticRegression +# load the dataset +data = read_csv('combined.csv', header=0, index_col=0, parse_dates=True, squeeze=True) +values = data.values +# basic feature selection +features = [0, 1, 2, 3, 4] +for f in features: + # split data into inputs and outputs + X, y = values[:, f].reshape((len(values), 1)), values[:, -1] + # split the dataset + trainX, testX, trainy, testy = train_test_split(X, y, test_size=0.3, shuffle=False, random_state=1) + # define the model + model = LogisticRegression() + # fit the model on the training set + model.fit(trainX, trainy) + # predict the test set + yhat = model.predict(testX) + # evaluate model skill + score = accuracy_score(testy, yhat) + print('feature=%d, name=%s, score=%.3f' % (f, data.columns[f], score)) +``` + +运行该示例将打印特征索引,名称以及在该特征上训练的逻辑模型的技能,并在测试集上进行评估。 + +我们可以看到只需要“ _Light_ ”变量就可以在此数据集上实现 99%的准确率。 + +记录环境变量的实验室很可能有一个光传感器,当房间被占用时,它会打开内部灯。 + +或者,也许在白天记录光(例如通过窗户的阳光),并且房间在每天或每周工作日被占用。 + +至少,本教程的结果会询问有关使用此数据集的任何研究论文的一些难题,因为显然它不是一个具有挑战性的预测问题。 + +``` +feature=0, name=Temperature, score=0.799 +feature=1, name=Humidity, score=0.822 +feature=2, name=Light, score=0.991 +feature=3, name=CO2, score=0.763 +feature=4, name=HumidityRatio, score=0.822 +``` + +## 扩展 + +这些数据可能仍然有待进一步调查。 + +一些想法包括: + +* 如果移除光柱,问题可能会更具挑战性。 +* 也许问题可以被描述为真正的多变量时间序列分类,其中在模型中使用滞后观察。 +* 也许在预测中可以利用环境变量中的明显峰值。 + +我简单地尝试了这些模型而没有令人兴奋的结果。 + +如果您浏览任何这些扩展或在线查找一些示例,请在下面的评论中告诉我们。 + +## 进一步阅读 + +如果您希望深入了解,本节将提供有关该主题的更多资源。 + +* [占用检测数据集,UCI 机器学习库](https://archive.ics.uci.edu/ml/datasets/Occupancy+Detection+) +* [GitHub 上的占用检测数据](https://github.com/LuisM78/Occupancy-detection-data) +* [支持需求驱动的 HVAC 运行的基于多传感器的占用估计模型](https://dl.acm.org/citation.cfm?id=2339455),2012。 +* [使用统计学习模型](https://www.sciencedirect.com/science/article/pii/S0378778815304357),2016 年对光线,温度,湿度和 CO2 测量的办公室进行精确的占用检测。 +* [基于可见光的占用推断使用集成学习](https://ieeexplore.ieee.org/document/8302496/),2018。 + +## 摘要 + +在本教程中,您发现了一个标准的多变量时间序列分类问题,用于使用环境变量的度量来预测房间占用率。 + +具体来说,你学到了: + +* 机器学习中的占用检测标准时间序列分类问题。 +* 如何加载和可视化多变量时间序列分类数据。 +* 如何开发简单的幼稚和逻辑回归模型,以达到近乎完美的技能。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/how-to-predict-whether-eyes-are-open-or-closed-using-brain-waves.md b/docs/dl-ts/how-to-predict-whether-eyes-are-open-or-closed-using-brain-waves.md new file mode 100644 index 0000000000000000000000000000000000000000..64f1f7c65a4b13194cfcfa6983d43d755e604774 --- /dev/null +++ b/docs/dl-ts/how-to-predict-whether-eyes-are-open-or-closed-using-brain-waves.md @@ -0,0 +1,587 @@ +# 如何使用脑波预测人眼是开放还是闭合 + +> 原文: [https://machinelearningmastery.com/how-to-predict-whether-eyes-are-open-or-closed-using-brain-waves/](https://machinelearningmastery.com/how-to-predict-whether-eyes-are-open-or-closed-using-brain-waves/) + +#### +评估机器学习方法进行时间序列预测时如何避免方法错误的案例研究。 + +评估关于时间序列预测问题的机器学习模型具有挑战性。 + +在问题框架或模型评估中很容易产生一个小错误,这会产生令人印象深刻的结果,但会导致无效的发现。 + +一个有趣的时间序列分类问题是仅基于他们的脑波数据(EEG)来预测受试者的眼睛是开放还是闭合。 + +在本教程中,您将发现在评估时间序列预测模型时,基于脑电波和常见方法陷阱预测眼睛是开放还是闭合的问题。 + +通过本教程,您将了解在评估时间序列预测问题的机器学习算法时如何避免常见陷阱。这些陷阱既吸引了初学者,也吸引了专业从业者和学者。 + +完成本教程后,您将了解: + +* 眼睛状态预测问题和您可以使用的标准机器学习数据集。 +* 如何重现熟练的结果来预测 Python 中脑波的眼睛状态。 +* 如何在评估预测模型时发现一个有趣的方法缺陷。 + +让我们开始吧。 + +## 教程概述 + +本教程分为七个部分;他们是: + +1. 从脑波预测开/闭眼 +2. 数据可视化和删除异常值 +3. 开发预测模型 +4. 模型评估方法的问题 +5. 用时间顺序训练分裂训练 +6. 前瞻性验证 +7. 外卖和重点课程 + +## 从脑波预测开/闭眼 + +在这篇文章中,我们将仔细研究一个问题,即根据脑波数据预测受试者眼睛是开放还是闭合。 + +Oliver Rosler 和 David Suendermann 为他们的 2013 年论文题为“[迈向使用 EEG 的眼睛状态预测的第一步](http://suendermann.com/su/pdf/aihls2013.pdf)”描述了这个问题。 + +我看到了这个数据集,我不得不知道更多。 + +具体地,当对象打开和闭合眼睛时,通过摄像机记录,由单个人进行脑电图(EEG)记录 117 秒(刚好不到两分钟)。然后手动记录 EEG 迹线中每个时间步的打开/关闭状态。 + +使用 [Emotiv EEG Neuroheadset](https://www.emotiv.com/) 记录 EEG,产生 14 条痕迹。 + +![Cartoon of where EEG sensors were located on the subject](img/35e5f363634431dfbd413ab84cb504a3.jpg) + +EEG 传感器位于受试者身上的卡通 +取自“使用 EEG 进行眼状态预测的第一步”,2013 年。 + +输出变量是二进制的,这意味着这是一个两类分类问题。 + +在 117 秒内总共进行了 14,980 次观测(行),这意味着每秒约有 128 次观测。 + +语料库由 14,977 个实例组成,每个实例具有 15 个属性(14 个属性表示电极和眼睛状态的值)。实例按时间顺序存储在语料库中,以便能够分析时间依赖性。语料库的 8,255(55.12%)个实例对应于睁眼,6,722(44.88%)个实例对应于闭眼状态。 + +* [使用脑电图进行眼状态预测的第一步](http://suendermann.com/su/pdf/aihls2013.pdf),2013。 + +还有一些脑电图观察具有远大于预期的幅度。这些可能是异常值,可以使用简单的统计方法识别和删除,例如删除与平均值有 3 到 4 个标准偏差的行。 + +该问题的最简单的框架是在当前时间步长给出 EEG 迹线的情况下预测眼睛状态(打开/关闭)。该问题的更高级框架可以寻求对每个 EEG 迹线的多变量时间序列建模以便预测当前眼睛状态。 + +## 数据可视化和删除异常值 + +数据集可以从 UCI 机器学习库免费下载: + +* [脑电图眼状态数据集](https://archive.ics.uci.edu/ml/datasets/EEG+Eye+State) + +原始数据采用 ARFF 格式(在 Weka 中使用),但可以通过删除 ARFF 标头转换为 CSV。 + +下面是删除了 ARFF 标题的前五行数据的示例。 + +``` +4329.23,4009.23,4289.23,4148.21,4350.26,4586.15,4096.92,4641.03,4222.05,4238.46,4211.28,4280.51,4635.9,4393.85,0 +4324.62,4004.62,4293.85,4148.72,4342.05,4586.67,4097.44,4638.97,4210.77,4226.67,4207.69,4279.49,4632.82,4384.1,0 +4327.69,4006.67,4295.38,4156.41,4336.92,4583.59,4096.92,4630.26,4207.69,4222.05,4206.67,4282.05,4628.72,4389.23,0 +4328.72,4011.79,4296.41,4155.9,4343.59,4582.56,4097.44,4630.77,4217.44,4235.38,4210.77,4287.69,4632.31,4396.41,0 +4326.15,4011.79,4292.31,4151.28,4347.69,4586.67,4095.9,4627.69,4210.77,4244.1,4212.82,4288.21,4632.82,4398.46,0 +... +``` + +我们可以将数据作为 DataFrame 加载,并绘制每个 EEG 轨迹和输出变量(打开/关闭状态)的时间序列。 + +完整的代码示例如下所示。 + +该示例假定您具有 CSV 格式的数据集副本,文件名为“ _EEG_Eye_State.csv_ ”,与代码位于同一目录中。 + +``` +# visualize dataset +from pandas import read_csv +from matplotlib import pyplot +# load the dataset +data = read_csv('EEG_Eye_State.csv', header=None) +# retrieve data as numpy array +values = data.values +# create a subplot for each time series +pyplot.figure() +for i in range(values.shape[1]): + pyplot.subplot(values.shape[1], 1, i+1) + pyplot.plot(values[:, i]) +pyplot.show() +``` + +运行该示例会为每个 EEG 跟踪和输出变量创建一个线图。 + +我们可以看到异常值清除每条迹线中的数据。我们还可以分别以 0/1 看到眼睛的开/关状态。 + +![Line Plot for each EEG trace and the output variable](img/86ec006d50b9fd40e94c7d64b6dbd486.jpg) + +每个 EEG 轨迹和输出变量的线图 + +去除异常值以更好地理解 EEG 痕迹与眼睛的开/闭状态之间的关系是有用的。 + +下面的示例将删除所有具有 EEG 观测值的行,这些行是平均值的四个标准偏差或更多。数据集将保存到名为“ _EEG_Eye_State_no_outliers.csv_ ”的新文件中。 + +这是[离群值检测和删除](https://machinelearningmastery.com/how-to-use-statistics-to-identify-outliers-in-data/)的快速而肮脏的实现,但是完成了工作。我相信你可以设计出更高效的实施方案。 + +``` +# remove outliers from the EEG data +from pandas import read_csv +from numpy import mean +from numpy import std +from numpy import delete +from numpy import savetxt +# load the dataset. +data = read_csv('EEG_Eye_State.csv', header=None) +values = data.values +# step over each EEG column +for i in range(values.shape[1] - 1): + # calculate column mean and standard deviation + data_mean, data_std = mean(values[:,i]), std(values[:,i]) + # define outlier bounds + cut_off = data_std * 4 + lower, upper = data_mean - cut_off, data_mean + cut_off + # remove too small + too_small = [j for j in range(values.shape[0]) if values[j,i] < lower] + values = delete(values, too_small, 0) + print('>deleted %d rows' % len(too_small)) + # remove too large + too_large = [j for j in range(values.shape[0]) if values[j,i] > upper] + values = delete(values, too_large, 0) + print('>deleted %d rows' % len(too_large)) +# save the results to a new file +savetxt('EEG_Eye_State_no_outliers.csv', values, delimiter=',') +``` + +运行该示例总结了删除的行,因为 EEG 数据中的每一列都针对平均值之上和之下的异常值进行处理。 + +``` +>deleted 0 rows +>deleted 1 rows +>deleted 2 rows +>deleted 1 rows +>deleted 0 rows +>deleted 142 rows +>deleted 0 rows +>deleted 48 rows +>deleted 0 rows +>deleted 153 rows +>deleted 0 rows +>deleted 43 rows +>deleted 0 rows +>deleted 0 rows +>deleted 0 rows +>deleted 15 rows +>deleted 0 rows +>deleted 5 rows +>deleted 10 rows +>deleted 0 rows +>deleted 21 rows +>deleted 53 rows +>deleted 0 rows +>deleted 12 rows +>deleted 58 rows +>deleted 53 rows +>deleted 0 rows +>deleted 59 rows +``` + +我们现在可以通过加载新的' _EEG_Eye_State_no_outliers.csv_ '文件来显示没有异常值的数据。 + +``` +# visualize dataset without outliers +from pandas import read_csv +from matplotlib import pyplot +# load the dataset +data = read_csv('EEG_Eye_State_no_outliers.csv', header=None) +# retrieve data as numpy array +values = data.values +# create a subplot for each time series +pyplot.figure() +for i in range(values.shape[1]): + pyplot.subplot(values.shape[1], 1, i+1) + pyplot.plot(values[:, i]) +pyplot.show() +``` + +运行该示例可创建更好的绘图,清晰显示眼睛闭合时的正峰值(1)和眼睛打开时的负峰值(0)。 + +![Line Plot for each EEG trace and the output variable without outliers](img/706e86992524c28cbe9e752abbef54df.jpg) + +每个 EEG 轨迹的线图和没有异常值的输出变量 + +## 开发预测模型 + +最简单的预测模型是基于当前的 EEG 观察来预测眼睛开/闭状态,忽略跟踪信息。 + +直观地说,人们不会期望这是有效的,然而,这是 Rosler 和 Suendermann 2013 年论文中使用的方法。 + +具体来说,他们使用这种问题框架的 10 倍交叉验证评估了 [Weka 软件](https://machinelearningmastery.com/applied-machine-learning-weka-mini-course/)中的一大套分类算法。他们使用多种方法实现了超过 90%的准确度,包括基于实例的方法,如 [k-最近邻](https://machinelearningmastery.com/k-nearest-neighbors-for-machine-learning/)和 KStar。 + +> 然而,基于实例的学习器,如 IB1 和 KStar,再次大大超过了决策树。后者实现了明显的最佳表现,分类错误率仅为 3.2%。 + +- [使用 EEG 进行眼状态预测的第一步](http://suendermann.com/su/pdf/aihls2013.pdf),2013。 + +在许多其他论文中,类似的方法和发现与相同和相似的数据集一起使用。 + +当我读到这篇文章时,我很惊讶,因此转载了结果。 + +完整示例如下所列,k = 3 KNN。 + +``` +# knn for predicting eye state +from pandas import read_csv +from sklearn.metrics import accuracy_score +from sklearn.model_selection import KFold +from sklearn.neighbors import KNeighborsClassifier +from numpy import mean +# load the dataset +data = read_csv('EEG_Eye_State_no_outliers.csv', header=None) +values = data.values +# evaluate knn using 10-fold cross-validation +scores = list() +kfold = KFold(10, shuffle=True, random_state=1) +for train_ix, test_ix in kfold.split(values): + # define train/test X/y + trainX, trainy = values[train_ix, :-1], values[train_ix, -1] + testX, testy = values[test_ix, :-1], values[test_ix, -1] + # define model + model = KNeighborsClassifier(n_neighbors=3) + # fit model on train set + model.fit(trainX, trainy) + # forecast test set + yhat = model.predict(testX) + # evaluate predictions + score = accuracy_score(testy, yhat) + # store + scores.append(score) + print('>%.3f' % score) +# calculate mean score across each run +print('Final Score: %.3f' % (mean(scores))) +``` + +运行该示例打印交叉验证的每个折叠的得分,并且在所有 10 倍中平均得分为 97%。 + +``` +>0.970 +>0.975 +>0.978 +>0.977 +>0.973 +>0.979 +>0.978 +>0.976 +>0.974 +>0.969 +Final Score: 0.975 +``` + +非常令人印象深刻! + +但是感觉有些不对劲。 + +我有兴趣了解在开放到封闭和关闭到开放的每个过渡期间如何考虑数据中清晰峰值的模型。 + +我尝试使用我自己的测试工具来考虑数据的时间顺序的每个模型表现得更糟。 + +为什么? + +提示:考虑所选模型评估策略和表现最佳的算法类型。 + +## 模型评估方法的问题 + +**免责声明**:我没有打电话给论文或相关论文的作者。我不在乎。根据我的经验,大多数发表的论文都无法复制或存在重大的方法缺陷(包括我写的很多东西)。我只对学习和教学感兴趣。 + +时间序列模型的评估方法存在方法上的缺陷。 + +我教导了这个缺陷,但在阅读了论文并重现结果之后,它仍然让我绊倒了。 + +我希望通过这个例子来说明它将帮助你解决自己的预测问题。 + +模型评估中的方法缺陷是使用 k 折交叉验证。具体而言,以不遵守观察的时间顺序的方式评估模型。 + +这个问题的关键是找到基于实例的方法,比如 k-最近邻,因为它对这个问题非常熟练。 KNN 将在数据集中寻找 _k_ 最相似的行,并计算输出状态的模式作为预测。 + +通过在评估模型时不考虑实例的时间顺序,它允许模型使用来自未来的信息进行预测。这在 KNN 算法中特别明显。 + +由于观测频率高(每秒 128 次),最相似的行将是在过去和未来的预测实例中及时相邻的行。 + +我们可以通过一些小实验来更清楚地说明这一点。 + +## 用时间顺序训练分裂训练 + +我们可以做的第一个测试是评估 KNN 模型的技能,当数据集被洗牌时,以及当数据集不是时,列车/测试分割。 + +在分割之前对数据进行混洗的情况下,我们期望结果类似于上一节中的交叉验证结果,特别是如果测试集是数据集的 10%。 + +如果关于时间排序和基于实例的方法在未来使用相邻示例的重要性的理论是正确的,那么我们期望在拆分之前数据集未被洗牌的测试更糟糕。 + +首先,下面的示例将数据集拆分为列车/测试拆分,分别为 90%/ 10%的数据。在拆分之前对数据集进行洗牌。 + +``` +# knn for predicting eye state +from pandas import read_csv +from sklearn.metrics import accuracy_score +from sklearn.model_selection import train_test_split +from sklearn.neighbors import KNeighborsClassifier +# load the dataset +data = read_csv('EEG_Eye_State_no_outliers.csv', header=None) +values = data.values +# split data into inputs and outputs +X, y = values[:, :-1], values[:, -1] +# split the dataset +trainX, testX, trainy, testy = train_test_split(X, y, test_size=0.1, shuffle=True, random_state=1) +# define model +model = KNeighborsClassifier(n_neighbors=3) +# fit model on train set +model.fit(trainX, trainy) +# forecast test set +yhat = model.predict(testX) +# evaluate predictions +score = accuracy_score(testy, yhat) +print(score) +``` + +运行该示例,我们可以看到,确实,该技能与我们在交叉验证示例中看到的或与其接近的技能相匹配,准确率为 96%。 + +``` +0.9699510831586303 +``` + +接下来,我们重复实验,而不是在拆分之前对数据集进行洗牌。 + +这意味着训练数据是关于观测的时间排序的前 90%的数据,并且测试数据集是数据的最后 10%或约 1,400 个观测值。 + +``` +# knn for predicting eye state +from pandas import read_csv +from sklearn.metrics import accuracy_score +from sklearn.model_selection import train_test_split +from sklearn.neighbors import KNeighborsClassifier +# load the dataset +data = read_csv('EEG_Eye_State_no_outliers.csv', header=None) +values = data.values +# split data into inputs and outputs +X, y = values[:, :-1], values[:, -1] +# split the dataset +trainX, testX, trainy, testy = train_test_split(X, y, test_size=0.1, shuffle=False, random_state=1) +# define model +model = KNeighborsClassifier(n_neighbors=3) +# fit model on train set +model.fit(trainX, trainy) +# forecast test set +yhat = model.predict(testX) +# evaluate predictions +score = accuracy_score(testy, yhat) +print(score) +``` + +运行该示例显示模型技能更差,为 52%。 + +``` +0.5269042627533194 +``` + +这是一个好的开始,但不是确定的。 + +考虑到我们可以在结果变量图上看到非常短的开/关间隔,有可能最后 10%的数据集难以预测。 + +我们可以重复实验并及时使用前 10%的数据进行测试,最后 90%的数据用于训练。我们可以通过在使用 [flip()函数](https://docs.scipy.org/doc/numpy/reference/generated/numpy.flip.html)分割数据之前反转行的顺序来实现。 + +``` +# knn for predicting eye state +from pandas import read_csv +from sklearn.metrics import accuracy_score +from sklearn.model_selection import train_test_split +from sklearn.neighbors import KNeighborsClassifier +from numpy import flip +# load the dataset +data = read_csv('EEG_Eye_State_no_outliers.csv', header=None) +values = data.values +# reverse order of rows +values = flip(values, 0) +# split data into inputs and outputs +X, y = values[:, :-1], values[:, -1] +# split the dataset +trainX, testX, trainy, testy = train_test_split(X, y, test_size=0.1, shuffle=False, random_state=1) +# define model +model = KNeighborsClassifier(n_neighbors=3) +# fit model on train set +model.fit(trainX, trainy) +# forecast test set +yhat = model.predict(testX) +# evaluate predictions +score = accuracy_score(testy, yhat) +print(score) +``` + +运行实验产生类似的结果,准确度约为 52%。 + +这提供了更多的证据,证明不是特定的连续观察块导致模型技能差。 + +``` +0.5290006988120196 +``` + +看起来需要立即相邻的观察来做出良好的预测。 + +## 前瞻性验证 + +模型可能需要过去(但不是未来)的相邻观察,以便进行熟练的预测。 + +这听起来很合理,但也有问题。 + +然而,我们可以使用测试集上的前向验证来实现这一点。这是允许模型在预测时间步骤之前使用所有观察的地方,因为我们在测试数据集中的每个新时间步骤验证新模型。 + +有关前进验证的更多信息,请参阅帖子: + +* [如何对时间序列预测的机器学习模型进行反向测试](https://machinelearningmastery.com/backtest-machine-learning-models-time-series-forecasting/) + +下面的示例使用前向验证评估 KNN 的技能,使用最后 10%的数据集(约 10 秒),遵守时间顺序。 + +``` +# knn for predicting eye state +from pandas import read_csv +from sklearn.metrics import accuracy_score +from sklearn.model_selection import train_test_split +from sklearn.neighbors import KNeighborsClassifier +from numpy import array +# load the dataset +data = read_csv('EEG_Eye_State_no_outliers.csv', header=None) +values = data.values +# split data into inputs and outputs +X, y = values[:, :-1], values[:, -1] +# split the dataset +trainX, testX, trainy, testy = train_test_split(X, y, test_size=0.1, shuffle=False, random_state=1) +# walk-forward validation +historyX, historyy = [x for x in trainX], [x for x in trainy] +predictions = list() +for i in range(len(testy)): + # define model + model = KNeighborsClassifier(n_neighbors=3) + # fit model on train set + model.fit(array(historyX), array(historyy)) + # forecast the next time step + yhat = model.predict([testX[i, :]])[0] + # store prediction + predictions.append(yhat) + # add real observation to history + historyX.append(testX[i, :]) + historyy.append(testy[i]) +# evaluate predictions +score = accuracy_score(testy, predictions) +print(score) +``` + +运行该示例可提供令人印象深刻的模型技能,准确率约为 95%。 + +``` +0.9531795946890287 +``` + +我们可以进一步推进此测试,并且在进行预测时仅将先前的 10 个观测值用于模型。 + +下面列出了完整的示例。 + +``` +# knn for predicting eye state +from pandas import read_csv +from sklearn.metrics import accuracy_score +from sklearn.model_selection import train_test_split +from sklearn.neighbors import KNeighborsClassifier +from numpy import array +# load the dataset +data = read_csv('EEG_Eye_State_no_outliers.csv', header=None) +values = data.values +# split data into inputs and outputs +X, y = values[:, :-1], values[:, -1] +# split the dataset +trainX, testX, trainy, testy = train_test_split(X, y, test_size=0.1, shuffle=False, random_state=1) +# walk-forward validation +historyX, historyy = [x for x in trainX], [x for x in trainy] +predictions = list() +for i in range(len(testy)): + # define model + model = KNeighborsClassifier(n_neighbors=3) + # fit model on a small subset of the train set + tmpX, tmpy = array(historyX)[-10:,:], array(historyy)[-10:] + model.fit(tmpX, tmpy) + # forecast the next time step + yhat = model.predict([testX[i, :]])[0] + # store prediction + predictions.append(yhat) + # add real observation to history + historyX.append(testX[i, :]) + historyy.append(testy[i]) +# evaluate predictions +score = accuracy_score(testy, predictions) +print(score) +``` + +运行该示例可以进一步提高模型技能,准确率接近 99%。 + +我预计,当迹线从开放到闭合或从闭合到开放转变时,唯一出现的错误是 EEG 系列拐点处的错误,这是问题的实际难点部分。这方面需要进一步调查。 + +``` +0.9923130677847659 +``` + +实际上,我们已经确认该模型需要相邻的观测结果及其结果才能进行预测,并且它只能在过去而不是未来的相邻观测中做得很好。 + +这是有趣的。但这一发现在实践中没有用。 + +如果部署了该模型,则需要模型知道最近过去的眼睛打开/关闭状态,例如之前的 128 秒。 + +这将无法使用。 + +基于脑波预测眼睛状态的模型的整体思想是让它在没有这种确认的情况下运行。 + +## 外卖和重点课程 + +让我们回顾一下到目前为止我们学到的东西: + +**1.模型评估方法必须考虑到观测的时间顺序。** + +这意味着使用 k-fold 交叉验证在方法上无效,该交叉验证不按时间分层(例如,随机抽取或使用随机选择的行)。 + +这也意味着使用在分割之前混洗数据的训练/测试分割在方法上是无效的。 + +我们在模型的高技能评估中看到了这一点,与模型的低技能相比,在模型的低技能时,在预测时间无法获得直接相邻的观测时间。 + +**2.模型评估方法必须对最终模型的使用有意义。** + +这意味着即使您使用的方法尊重观察的时间顺序,模型也应该只有在实际使用模型时可用的信息。 + +我们在模型的高技能下看到了这一点,这种方法遵循了观察顺序的前瞻性验证方法,但提供了可用的信息,例如眼睛状态,如果模型在实践中使用则无法获得。 + +关键是从使用最终模型的问题框架开始,然后向后工作到可用的数据,以及在框架中评估模型的方法,该框架仅在可用的信息下运行取景。 + +当您试图了解其他人的工作时,这会倍加适用。 + +**前进** + +希望,无论是在评估自己的预测模型时,还是在评估其他模型时,这都会有所帮助。 + +那么,如果提供原始数据,您将如何解决此问题? + +我认为这个问题的关键是在从开眼到闭眼或闭眼到开眼的过渡时 EEG 数据中明显的正/负峰值。我希望有效的模型可以利用这个特征,可能使用半秒钟或类似的先前脑电图观察。 + +甚至可以使用单个迹线而不是 15 个,以及来自信号处理的简单峰值检测方法,而不是机器学习方法。 + +如果你对此有所了解,请告诉我。我很想看看你发现了什么。 + +## 进一步阅读 + +如果您希望深入了解,本节将提供有关该主题的更多资源。 + +* [脑电图眼状态数据集,UCI 机器学习库](https://archive.ics.uci.edu/ml/datasets/EEG+Eye+State) +* [使用脑电图进行眼状态预测的第一步](http://suendermann.com/su/pdf/aihls2013.pdf),2013。 +* [EEG 眼睛状态识别使用增量属性学习和时间序列分类](https://www.hindawi.com/journals/mpe/2014/365101/),2014。 + +## 摘要 + +在本教程中,您发现了在评估时间序列预测模型时基于脑电波和常见方法陷阱预测眼睛是开放还是闭合的问题。 + +具体来说,你学到了: + +* 眼睛状态预测问题和您可以使用的标准机器学习数据集。 +* 如何重现熟练的结果来预测 Python 中脑波的眼睛状态。 +* 如何在评估预测模型时发现一个有趣的方法缺陷。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/how-to-scale-data-for-long-short-term-memory-networks-in-python.md b/docs/dl-ts/how-to-scale-data-for-long-short-term-memory-networks-in-python.md new file mode 100644 index 0000000000000000000000000000000000000000..82d150dae7b637fbc9aceffbac079498415c20d4 --- /dev/null +++ b/docs/dl-ts/how-to-scale-data-for-long-short-term-memory-networks-in-python.md @@ -0,0 +1,342 @@ +# 如何在 Python 中扩展长短期内存网络的数据 + +> 原文: [https://machinelearningmastery.com/how-to-scale-data-for-long-short-term-memory-networks-in-python/](https://machinelearningmastery.com/how-to-scale-data-for-long-short-term-memory-networks-in-python/) + +在训练神经网络时,可能需要缩放序列预测问题的数据,例如长期短期记忆复现神经网络。 + +当网络适合具有一系列值的非缩放数据(例如,数量在 10 到 100 之间)时,大输入可能会减慢网络的学习和收敛速度,并且在某些情况下会妨碍网络有效地学习问题。 + +在本教程中,您将了解如何规范化和标准化序列预测数据,以及如何确定将哪个用于输入和输出变量。 + +完成本教程后,您将了解: + +* 如何在 Python 中规范化和标准化序列数据。 +* 如何为输入和输出变量选择适当的缩放。 +* 缩放序列数据时的实际考虑因素 + +让我们开始吧。 + +![How to Scale Data for Long Short-Term Memory Networks in Python](img/873e95dd71301bfac68e206f7783e0c0.jpg) + +如何在 Python 中扩展长短期内存网络的数据 +图片来自 [Mathias Appel](https://www.flickr.com/photos/mathiasappel/25527849934/) ,保留一些权利。 + +## 教程概述 + +本教程分为 4 个部分;他们是: + +1. 缩放系列数据 +2. 缩放输入变量 +3. 缩放输出变量 +4. 缩放时的实际考虑因素 + +## 在 Python 中扩展系列数据 + +您可能需要考虑两种类型的系列缩放:规范化和标准化。 + +这些都可以使用 scikit-learn 库来实现。 + +### 规范化系列数据 + +归一化是对原始范围内的数据进行重新缩放,以使所有值都在 0 和 1 的范围内。 + +标准化要求您知道或能够准确估计最小和最大可观察值。您可以从可用数据中估算这些值。如果您的时间序列趋势向上或向下,估计这些预期值可能会很困难,并且规范化可能不是用于解决问题的最佳方法。 + +值按如下标准化: + +``` +y = (x - min) / (max - min) +``` + +其中最小值和最大值与值 x 被归一化有关。 + +例如,对于数据集,我们可以将 min 和 max 可观察值猜测为 30 和-10。然后我们可以将任何值标准化,如 18.8,如下所示: + +``` +y = (x - min) / (max - min) +y = (18.8 - (-10)) / (30 - (-10)) +y = 28.8 / 40 +y = 0.72 +``` + +您可以看到,如果提供的 x 值超出最小值和最大值的范围,则结果值将不在 0 和 1 的范围内。您可以在进行预测之前检查这些观察值并删除它们来自数据集或将它们限制为预定义的最大值或最小值。 + +您可以使用 scikit-learn 对象 [MinMaxScaler](http://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MinMaxScaler.html) 来规范化数据集。 + +使用 MinMaxScaler 和其他缩放技术的良好实践用法如下: + +* **使用可用的训练数据**调整定标器。对于归一化,这意味着训练数据将用于估计最小和最大可观察值。这是通过调用 fit()函数完成的。 +* **将比例应用于训练数据**。这意味着您可以使用标准化数据来训练模型。这是通过调用 transform()函数完成的。 +* **将比例应用于前进的数据**。这意味着您可以在将来准备要预测的新数据。 + +如果需要,可以反转变换。这对于将预测转换回其原始比例以进行报告或绘图非常有用。这可以通过调用 inverse_transform()函数来完成。 + +下面是一个标准化 10 个量的人为序列的例子。 + +缩放器对象要求将数据作为行和列的矩阵提供。加载的时间序列数据作为 Pandas 系列加载。 + +``` +from pandas import Series +from sklearn.preprocessing import MinMaxScaler +# define contrived series +data = [10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0] +series = Series(data) +print(series) +# prepare data for normalization +values = series.values +values = values.reshape((len(values), 1)) +# train the normalization +scaler = MinMaxScaler(feature_range=(0, 1)) +scaler = scaler.fit(values) +print('Min: %f, Max: %f' % (scaler.data_min_, scaler.data_max_)) +# normalize the dataset and print +normalized = scaler.transform(values) +print(normalized) +# inverse transform and print +inversed = scaler.inverse_transform(normalized) +print(inversed) +``` + +运行该示例打印序列,打印从序列估计的最小值和最大值,打印相同的标准化序列,然后使用逆变换将值返回到其原始比例。 + +我们还可以看到数据集的最小值和最大值分别为 10.0 和 100.0。 + +``` +0 10.0 +1 20.0 +2 30.0 +3 40.0 +4 50.0 +5 60.0 +6 70.0 +7 80.0 +8 90.0 +9 100.0 + +Min: 10.000000, Max: 100.000000 + +[[ 0\. ] + [ 0.11111111] + [ 0.22222222] + [ 0.33333333] + [ 0.44444444] + [ 0.55555556] + [ 0.66666667] + [ 0.77777778] + [ 0.88888889] + [ 1\. ]] + +[[ 10.] + [ 20.] + [ 30.] + [ 40.] + [ 50.] + [ 60.] + [ 70.] + [ 80.] + [ 90.] + [ 100.]] +``` + +### 标准化系列数据 + +标准化数据集涉及重新调整值的分布,以便观察值的平均值为 0,标准差为 1。 + +这可以被认为是减去平均值或使数据居中。 + +与标准化一样,当您的数据具有不同比例的输入值时,标准化可能很有用,甚至在某些机器学习算法中也是必需的。 + +标准化假定您的观察结果符合高斯分布(钟形曲线),具有良好的平均值和标准偏差。如果不满足此期望,您仍然可以标准化时间序列数据,但可能无法获得可靠的结果。 + +标准化要求您知道或能够准确估计可观察值的均值和标准差。您可以从训练数据中估算这些值。 + +值标准化如下: + +``` +y = (x - mean) / standard_deviation +``` + +平均值计算如下: + +``` +mean = sum(x) / count(x) +``` + +而 standard_deviation 计算如下: + +``` +standard_deviation = sqrt( sum( (x - mean)^2 ) / count(x)) +``` + +我们可以猜测平均值为 10,标准偏差约为 5.使用这些值,我们可以将第一个值 20.7 标准化如下: + +``` +y = (x - mean) / standard_deviation +y = (20.7 - 10) / 5 +y = (10.7) / 5 +y = 2.14 +``` + +数据集的均值和标准差估计值对于新数据可能比最小值和最大值更稳健。 + +您可以使用 scikit-learn 对象 [StandardScaler](http://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.StandardScaler.html) 标准化数据集。 + +``` +from pandas import Series +from sklearn.preprocessing import StandardScaler +from math import sqrt +# define contrived series +data = [1.0, 5.5, 9.0, 2.6, 8.8, 3.0, 4.1, 7.9, 6.3] +series = Series(data) +print(series) +# prepare data for normalization +values = series.values +values = values.reshape((len(values), 1)) +# train the normalization +scaler = StandardScaler() +scaler = scaler.fit(values) +print('Mean: %f, StandardDeviation: %f' % (scaler.mean_, sqrt(scaler.var_))) +# normalize the dataset and print +standardized = scaler.transform(values) +print(standardized) +# inverse transform and print +inversed = scaler.inverse_transform(standardized) +print(inversed) +``` + +运行该示例打印序列,打印从序列估计的平均值和标准差,打印标准化值,然后以原始比例打印值。 + +我们可以看到估计的平均值和标准偏差分别约为 5.3 和 2.7。 + +``` +0 1.0 +1 5.5 +2 9.0 +3 2.6 +4 8.8 +5 3.0 +6 4.1 +7 7.9 +8 6.3 + +Mean: 5.355556, StandardDeviation: 2.712568 + +[[-1.60569456] + [ 0.05325007] + [ 1.34354035] + [-1.01584758] + [ 1.26980948] + [-0.86838584] + [-0.46286604] + [ 0.93802055] + [ 0.34817357]] + +[[ 1\. ] + [ 5.5] + [ 9\. ] + [ 2.6] + [ 8.8] + [ 3\. ] + [ 4.1] + [ 7.9] + [ 6.3]] +``` + +## 缩放输入变量 + +输入变量是网络在输入或可见层上进行的预测。 + +一个好的经验法则是输入变量应该是小值,可能在 0-1 范围内,或者标准化为零均值和标准差为 1。 + +输入变量是否需要缩放取决于问题和每个变量的具体情况。我们来看一些例子。 + +### 分类输入 + +您可能有一系列分类输入,例如字母或状态。 + +通常,分类输入首先是整数编码,然后是一个热编码。也就是说,为每个不同的可能输入分配唯一的整数值,然后使用 1 和 0 的二进制向量来表示每个整数值。 + +根据定义,一个热编码将确保每个输入是一个小的实际值,在这种情况下为 0.0 或 1.0。 + +### 实值输入 + +您可能有一系列数量作为输入,例如价格或温度。 + +如果数量的分布是正常的,则应该标准化,否则系列应该标准化。如果数值范围很大(10s 100s 等)或小(0.01,0.0001),则适用。 + +如果数量值很小(接近 0-1)并且分布有限(例如标准偏差接近 1),那么也许你可以在没有缩放系列的情况下逃脱。 + +### 其他输入 + +问题可能很复杂,如何最好地扩展输入数据可能并不清楚。 + +如果有疑问,请将输入序列标准化。如果您拥有这些资源,请使用原始数据,标准化数据和标准化来探索建模,并查看是否存在有益的差异。 + +> 如果输入变量是线性组合的,就像在 MLP [多层感知器]中一样,那么至少在理论上很少有必要对输入进行标准化。 ......但是,有很多实际的原因可以解释为什么标准化输入可以使训练更快,并减少陷入局部最优的机会。 + +- [我应该规范化/标准化/重新缩放数据吗?](ftp://ftp.sas.com/pub/neural/FAQ2.html#A_std) 神经网络常见问题解答 + +## 缩放输出变量 + +输出变量是网络预测的变量。 + +您必须确保输出变量的比例与网络输出层上的激活功能(传递函数)的比例相匹配。 + +> 如果输出激活函数的范围为[0,1],那么显然您必须确保目标值位于该范围内。但通常最好选择适合目标分布的输出激活函数,而不是强制数据符合输出激活函数。 + +- [我应该规范化/标准化/重新缩放数据吗?](ftp://ftp.sas.com/pub/neural/FAQ2.html#A_std) 神经网络常见问题解答 + +以下启发式方法应涵盖大多数序列预测问题: + +### 二元分类问题 + +如果您的问题是二元分类问题,那么输出将是类值 0 和 1.这最好使用输出层上的 sigmoid 激活函数建模。输出值将是介于 0 和 1 之间的实数值,可以捕捉到清晰的值。 + +### 多类分类问题 + +如果您的问题是多类分类问题,那么输出将是一个介于 0 和 1 之间的二进制类值的向量,每个类值一个输出。这最好使用输出层上的 softmax 激活功能建模。同样,输出值将是介于 0 和 1 之间的实数值,可以捕捉到清晰的值。 + +### 回归问题 + +如果您的问题是回归问题,那么输出将是实际值。这最好使用线性激活功能建模。如果值的分布正常,则可以标准化输出变量。否则,可以对输出变量进行标准化。 + +### 其他问题 + +可以在输出层上使用许多其他激活函数,并且您的问题的细节可能会增加混淆。 + +经验法则是确保网络输出与数据规模相匹配。 + +## 缩放时的实际考虑因素 + +缩放序列数据时有一些实际考虑因素。 + +* **估算系数**。您可以从训练数据中估算系数(标准化的最小值和最大值或标准化的平均值和标准偏差)。检查这些首先估算并使用领域知识或领域专家来帮助改进这些估算,以便将来对所有数据进行有用的更正。 +* **保存系数**。您将需要以与用于训练模型的数据完全相同的方式对未来的新数据进行标准化。保存用于存档的系数,并在需要在进行预测时扩展新数据时加载它们。 +* **数据分析**。使用数据分析可以帮助您更好地了解数据。例如,一个简单的直方图可以帮助您快速了解数量的分布情况,看看标准化是否有意义。 +* **缩放每个系列**。如果您的问题有多个系列,请将每个系列视为单独的变量,然后分别对其进行缩放。 +* **在合适的时间缩放**。在正确的时间应用任何缩放变换非常重要。例如,如果您有一系列非静止的数量,则在首次使数据静止后进行缩放可能是合适的。在将系列转换为监督学习问题后对其进行扩展是不合适的,因为每个列的处理方式不同,这是不正确的。 +* **如果怀疑**则缩放。您可能需要重新调整输入和输出变量。如果有疑问,至少要对数据进行标准化。 + +## 进一步阅读 + +本节列出了扩展时要考虑的一些其他资源。 + +* [我应该规范化/标准化/重新调整数据吗?](ftp://ftp.sas.com/pub/neural/FAQ2.html#A_std) 神经网络常见问题解答 +* [MinMaxScaler scikit-learn API 文档](http://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MinMaxScaler.html) +* [StandardScaler scikit-learn API 文档](http://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.StandardScaler.html) +* [如何使用 Python 从头开始扩展机器学习数据](http://machinelearningmastery.com/scale-machine-learning-data-scratch-python/) +* [如何在 Python 中标准化和标准化时间序列数据](http://machinelearningmastery.com/normalize-standardize-time-series-data-python/) +* [如何使用 Scikit-Learn 为 Python 机器学习准备数据](http://machinelearningmastery.com/prepare-data-machine-learning-python-scikit-learn/) + +## 摘要 + +在本教程中,您了解了在使用长短期记忆复现神经网络时如何扩展序列预测数据。 + +具体来说,你学到了: + +* 如何在 Python 中规范化和标准化序列数据。 +* 如何为输入和输出变量选择适当的缩放。 +* 缩放序列数据时的实际考虑因素 + +您对缩放序列预测数据有任何疑问吗? +在评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/how-to-use-the-timeseriesgenerator-for-time-series-forecasting-in-keras.md b/docs/dl-ts/how-to-use-the-timeseriesgenerator-for-time-series-forecasting-in-keras.md new file mode 100644 index 0000000000000000000000000000000000000000..3385b12944c0b32680e0a5cd3716b70b678775d8 --- /dev/null +++ b/docs/dl-ts/how-to-use-the-timeseriesgenerator-for-time-series-forecasting-in-keras.md @@ -0,0 +1,723 @@ +# 如何使用 TimeseriesGenerator 进行 Keras 中的时间序列预测 + +> 原文: [https://machinelearningmastery.com/how-to-use-the-timeseriesgenerator-for-time-series-forecasting-in-keras/](https://machinelearningmastery.com/how-to-use-the-timeseriesgenerator-for-time-series-forecasting-in-keras/) + +必须将时间序列数据转换为具有输入和输出组件的样本结构,然后才能用于拟合监督学习模型。 + +如果您必须手动执行此转换,这可能具有挑战性。 Keras 深度学习库提供了 TimeseriesGenerator,可以将单变量和多变量时间序列数据自动转换为样本,随时可以训练深度学习模型。 + +在本教程中,您将了解如何使用 Keras TimeseriesGenerator 准备时间序列数据,以便使用深度学习方法进行建模。 + +完成本教程后,您将了解: + +* 如何定义 TimeseriesGenerator 生成器并将其用于适合深度学习模型。 +* 如何为单变量时间序列准备发电机并适合 MLP 和 LSTM 模型。 +* 如何为多变量时间序列准备生成器并适合 LSTM 模型。 + +让我们开始吧。 + +![How to Use the TimeseriesGenerator for Time Series Forecasting in Keras](img/cf21fe77ad8f90bbad3a4061cfddf266.jpg) + +如何在 Keras 中使用 TimeseriesGenerator 进行时间序列预测 +照片由 [Chris Fithall](https://www.flickr.com/photos/chrisfithall/13933989150/) 拍摄,保留一些权利。 + +## 教程概述 + +本教程分为六个部分;他们是: + +1. 监督学习的时间序列问题 +2. 如何使用 TimeseriesGenerator +3. 单变量时间序列示例 +4. 多变量时间序列示例 +5. 多变量输入和相关系列示例 +6. 多步预测示例 + +**注**:本教程假设您使用的是 **Keras v2.2.4** 或更高版本。 + +## 监督学习的时间序列问题 + +时间序列数据需要准备才能用于训练监督学习模型,例如深度学习模型。 + +例如,单变量时间序列表示为观察向量: + +``` +[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +``` + +监督学习算法要求数据作为样本集合提供,其中每个样本具有输入分量( _X_ )和输出分量( _y_ )。 + +``` +X, y +example input, example output +example input, example output +example input, example output +... +``` + +该模型将学习如何将输入映射到所提供示例的输出。 + +``` +y = f(X) +``` + +必须将时间序列转换为具有输入和输出组件的样本。该变换既可以告知模型将要学习什么,也可以在进行预测时告知将来如何使用该模型,例如:做出预测需要什么( _X_ )和做出预测( _y_ )。 + +对于对一步预测感兴趣的单变量时间序列,在先前时间步骤的观察,即所谓的滞后观察,被用作输入,输出是在当前时间步骤的观察。 + +例如,上述 10 步单变量系列可以表示为监督学习问题,其中输入的三个时间步长和输出的一个步骤如下: + +``` +X, y +[1, 2, 3], [4] +[2, 3, 4], [5] +[3, 4, 5], [6] +... +``` + +您可以编写代码来自行执行此转换;例如,看帖子: + +* [如何将时间序列转换为 Python 中的监督学习问题](https://machinelearningmastery.com/convert-time-series-supervised-learning-problem-python/) + +或者,当您对使用 Keras 训练神经网络模型感兴趣时,可以使用 TimeseriesGenerator 类。 + +## 如何使用 TimeseriesGenerator + +Keras 提供 [TimeseriesGenerator](https://keras.io/preprocessing/sequence/) ,可用于将单变量或多变量时间序列数据集自动转换为监督学习问题。 + +使用 TimeseriesGenerator 有两个部分:定义它并使用它来训练模型。 + +### 定义 TimeseriesGenerator + +您可以创建类的实例并指定时间序列问题的输入和输出方面,它将提供[序列类](https://keras.io/utils/#sequence)的实例,然后可以使用它来迭代输入和输出系列。 + +在大多数时间序列预测问题中,输入和输出系列将是同一系列。 + +例如: + +``` +# load data +inputs = ... +outputs = ... +# define generator +generator = TimeseriesGenerator(inputs, outputs, ...) +# iterator generator +for i in range(len(generator)): + ... +``` + +从技术上讲,该类不是一个生成器,因为它不是 [Python 生成器](https://wiki.python.org/moin/Generators),你不能在它上面使用 _next()_ 函数。 + +除了指定时间序列问题的输入和输出方面外,还应该配置一些其他参数;例如: + +* **长度**:在每个样本的输入部分中使用的滞后观察的数量(例如 3)。 +* **batch_size** :每次迭代时返回的样本数(例如 32)。 + +您必须根据设计的问题框架定义长度参数。这是用作输入的所需滞后观察数。 + +您还必须在训练期间将批量大小定义为模型的批量大小。如果数据集中的样本数小于批量大小,则可以通过计算其长度,将生成器和模型中的批量大小设置为生成器中的样本总数;例如: + +``` +print(len(generator)) +``` + +还有其他参数,例如定义数据的开始和结束偏移,采样率,步幅等。您不太可能使用这些功能,但您可以查看[完整 API](https://keras.io/preprocessing/sequence/) 以获取更多详细信息。 + +默认情况下,样本不会被洗牌。这对于像 LSTM 这样的一些循环神经网络很有用,这些神经网络在一批中的样本之间保持状态。 + +它可以使其他神经网络(例如 CNN 和 MLP)在训练时对样本进行混洗。可以通过将' _shuffle_ '参数设置为 True 来启用随机播放。这将具有为每个批次返回的洗样样本的效果。 + +在撰写本文时,TimeseriesGenerator 仅限于一步输出。不支持多步时间序列预测。 + +### 使用 TimeseriesGenerator 训练模型 + +一旦定义了 TimeseriesGenerator 实例,它就可以用于训练神经网络模型。 + +可以使用 TimeseriesGenerator 作为数据生成器来训练模型。这可以通过使用 _fit_generator()_ 函数拟合定义的模型来实现。 + +此函数将生成器作为参数。它还需要 _steps_per_epoch_ 参数来定义每个时期中使用的样本数。可以将此值设置为 TimeseriesGenerator 实例的长度,以使用生成器中的所有样本。 + +例如: + +``` +# define generator +generator = TimeseriesGenerator(...) +# define model +model = ... +# fit model +model.fit_generator(generator, steps_per_epoch=len(generator), ...) +``` + +类似地,生成器可用于通过调用 _evaluate_generator()_ 函数来评估拟合模型,并使用拟合模型使用 _predict_generator()_ 函数对新数据进行预测。 + +与数据生成器匹配的模型不必使用 evaluate 和 predict 函数的生成器版本。只有在您希望数据生成器为模型准备数据时,才能使用它们。 + +## 单变量时间序列示例 + +我们可以使用一个小的设计单变量时间序列数据集的工作示例使 TimeseriesGenerator 具体化。 + +首先,让我们定义我们的数据集。 + +``` +# define dataset +series = array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) +``` + +我们将选择框架问题,其中最后两个滞后观察将用于预测序列中的下一个值。例如: + +``` +X, y +[1, 2] 3 +``` + +现在,我们将使用批量大小为 1,以便我们可以探索生成器中的数据。 + +``` +# define generator +n_input = 2 +generator = TimeseriesGenerator(series, series, length=n_input, batch_size=1) +``` + +接下来,我们可以看到数据生成器将为此时间序列准备多少样本。 + +``` +# number of samples +print('Samples: %d' % len(generator)) +``` + +最后,我们可以打印每个样本的输入和输出组件,以确认数据是按照我们的预期准备的。 + +``` +for i in range(len(generator)): + x, y = generator[i] + print('%s => %s' % (x, y)) +``` + +下面列出了完整的示例。 + +``` +# univariate one step problem +from numpy import array +from keras.preprocessing.sequence import TimeseriesGenerator +# define dataset +series = array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) +# define generator +n_input = 2 +generator = TimeseriesGenerator(series, series, length=n_input, batch_size=1) +# number of samples +print('Samples: %d' % len(generator)) +# print each sample +for i in range(len(generator)): + x, y = generator[i] + print('%s => %s' % (x, y)) +``` + +首先运行该示例打印生成器中的样本总数,即 8。 + +然后我们可以看到每个输入数组的形状为[1,2],每个输出的形状为[1,]。 + +观察结果按照我们的预期进行准备,其中两个滞后观察结果将用作输入,序列中的后续值作为输出。 + +``` +Samples: 8 + +[[1\. 2.]] => [3.] +[[2\. 3.]] => [4.] +[[3\. 4.]] => [5.] +[[4\. 5.]] => [6.] +[[5\. 6.]] => [7.] +[[6\. 7.]] => [8.] +[[7\. 8.]] => [9.] +[[8\. 9.]] => [10.] +``` + +现在我们可以在这个数据上拟合模型,并学习将输入序列映射到输出序列。 + +我们将从一个简单的多层感知器或 MLP 模型开始。 + +将定义发生器,以便在给定少量样品的情况下,将在每批中使用所有样品。 + +``` +# define generator +n_input = 2 +generator = TimeseriesGenerator(series, series, length=n_input, batch_size=8) +``` + +我们可以定义一个简单的模型,其中一个隐藏层有 50 个节点,一个输出层将进行预测。 + +``` +# define model +model = Sequential() +model.add(Dense(100, activation='relu', input_dim=n_input)) +model.add(Dense(1)) +model.compile(optimizer='adam', loss='mse') +``` + +然后我们可以使用 _fit_generator()_ 函数将模型与生成器拟合。我们在生成器中只有一批数据,因此我们将 _steps_per_epoch_ 设置为 1.该模型适用于 200 个时期。 + +``` +# fit model +model.fit_generator(generator, steps_per_epoch=1, epochs=200, verbose=0) +``` + +一旦适合,我们将进行样本预测。 + +给定输入[9,10],我们将进行预测并期望模型预测[11]或接近它。该模型没有调整;这只是如何使用发电机的一个例子。 + +``` +# make a one step prediction out of sample +x_input = array([9, 10]).reshape((1, n_input)) +yhat = model.predict(x_input, verbose=0) +``` + +下面列出了完整的示例。 + +``` +# univariate one step problem with mlp +from numpy import array +from keras.models import Sequential +from keras.layers import Dense +from keras.preprocessing.sequence import TimeseriesGenerator +# define dataset +series = array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) +# define generator +n_input = 2 +generator = TimeseriesGenerator(series, series, length=n_input, batch_size=8) +# define model +model = Sequential() +model.add(Dense(100, activation='relu', input_dim=n_input)) +model.add(Dense(1)) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit_generator(generator, steps_per_epoch=1, epochs=200, verbose=0) +# make a one step prediction out of sample +x_input = array([9, 10]).reshape((1, n_input)) +yhat = model.predict(x_input, verbose=0) +print(yhat) +``` + +运行该示例准备生成器,拟合模型,并进行样本预测,正确预测接近 11 的值。 + +``` +[[11.510406]] +``` + +我们还可以使用生成器来适应循环神经网络,例如长短期内存网络或 LSTM。 + +LSTM 期望数据输入具有[_ 样本,时间步长,特征 _]的形状,而到目前为止描述的生成器提供滞后观察作为特征或形状[_ 样本,特征 _]。 + +我们可以在从[10,]到[10,1]准备发电机之前重新设计单变量时间序列 10 个时间步长和 1 个特征;例如: + +``` +# reshape to [10, 1] +n_features = 1 +series = series.reshape((len(series), n_features)) +``` + +然后,TimeseriesGenerator 将系列分为样品,其形状为[ _batch,n_input,1_ ]或[8,2,1],用于生成器中的所有八个样品,两个滞后观察用作时间步长。 + +下面列出了完整的示例。 + +``` +# univariate one step problem with lstm +from numpy import array +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import LSTM +from keras.preprocessing.sequence import TimeseriesGenerator +# define dataset +series = array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) +# reshape to [10, 1] +n_features = 1 +series = series.reshape((len(series), n_features)) +# define generator +n_input = 2 +generator = TimeseriesGenerator(series, series, length=n_input, batch_size=8) +# define model +model = Sequential() +model.add(LSTM(100, activation='relu', input_shape=(n_input, n_features))) +model.add(Dense(1)) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit_generator(generator, steps_per_epoch=1, epochs=500, verbose=0) +# make a one step prediction out of sample +x_input = array([9, 10]).reshape((1, n_input, n_features)) +yhat = model.predict(x_input, verbose=0) +print(yhat) +``` + +再次,运行该示例准备数据,拟合模型,并预测序列中的下一个样本外值。 + +``` +[[11.092189]] +``` + +## 多变量时间序列示例 + +TimeseriesGenerator 还支持多变量时间序列问题。 + +这些是您有多个并行系列的问题,每个系列中的观察步骤同时进行。 + +我们可以用一个例子来证明这一点。 + +首先,我们可以设计两个并行系列的数据集。 + +``` +# define dataset +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95, 105]) +``` + +具有多变量时间序列格式的标准结构使得每个时间序列是单独的列,行是每个时间步的观察值。 + +我们定义的系列是向量,但我们可以将它们转换为列。我们可以将每个系列重塑为具有 10 个时间步长和 1 个特征的形状[10,1]的数组。 + +``` +# reshape series +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +``` + +我们现在可以通过调用 _hstack()_ NumPy 函数将列水平堆叠到数据集中。 + +``` +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2)) +``` + +我们现在可以直接将此数据集提供给 TimeseriesGenerator。我们将使用每个系列的前两个观测值作为输入,并将每个序列的下一个观测值作为输出。 + +``` +# define generator +n_input = 2 +generator = TimeseriesGenerator(dataset, dataset, length=n_input, batch_size=1) +``` + +然后,对于 1 个样本,2 个时间步长和 2 个特征或平行序列,每个样本将是[1,2,2]的三维阵列。对于 1 个样本和 2 个特征,输出将是[1,2]的二维系列。第一个样本将是: + +``` +X, y +[[10, 15], [20, 25]] [[30, 35]] +``` + +下面列出了完整的示例。 + +``` +# multivariate one step problem +from numpy import array +from numpy import hstack +from keras.preprocessing.sequence import TimeseriesGenerator +# define dataset +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95, 105]) +# reshape series +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2)) +print(dataset) +# define generator +n_input = 2 +generator = TimeseriesGenerator(dataset, dataset, length=n_input, batch_size=1) +# number of samples +print('Samples: %d' % len(generator)) +# print each sample +for i in range(len(generator)): + x, y = generator[i] + print('%s => %s' % (x, y)) +``` + +运行该示例将首先打印准备好的数据集,然后打印数据集中的总样本数。 + +接下来,打印每个样品的输入和输出部分,确认我们的预期结构。 + +``` +[[ 10 15] + [ 20 25] + [ 30 35] + [ 40 45] + [ 50 55] + [ 60 65] + [ 70 75] + [ 80 85] + [ 90 95] + [100 105]] + +Samples: 8 + +[[[10\. 15.] + [20\. 25.]]] => [[30\. 35.]] +[[[20\. 25.] + [30\. 35.]]] => [[40\. 45.]] +[[[30\. 35.] + [40\. 45.]]] => [[50\. 55.]] +[[[40\. 45.] + [50\. 55.]]] => [[60\. 65.]] +[[[50\. 55.] + [60\. 65.]]] => [[70\. 75.]] +[[[60\. 65.] + [70\. 75.]]] => [[80\. 85.]] +[[[70\. 75.] + [80\. 85.]]] => [[90\. 95.]] +[[[80\. 85.] + [90\. 95.]]] => [[100\. 105.]] +``` + +样本的三维结构意味着生成器不能直接用于像 MLP 这样的简单模型。 + +这可以通过首先将时间序列数据集展平为一维向量,然后将其提供给 TimeseriesGenerator 并将长度设置为用作输入的步数乘以系列中的列数( _n_steps 来实现。 * n_features_ )。 + +这种方法的局限性在于生成器只允许您预测一个变量。几乎可以肯定,编写自己的函数来为 MLP 准备多变量时间序列比使用 TimeseriesGenerator 更好。 + +样品的三维结构可以由 CNN 和 LSTM 模型直接使用。下面列出了使用 TimeseriesGenerator 进行多变量时间序列预测的完整示例。 + +``` +# multivariate one step problem with lstm +from numpy import array +from numpy import hstack +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import LSTM +from keras.preprocessing.sequence import TimeseriesGenerator +# define dataset +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95, 105]) +# reshape series +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2)) +# define generator +n_features = dataset.shape[1] +n_input = 2 +generator = TimeseriesGenerator(dataset, dataset, length=n_input, batch_size=8) +# define model +model = Sequential() +model.add(LSTM(100, activation='relu', input_shape=(n_input, n_features))) +model.add(Dense(2)) +model.compile(optimizer='adam', loss='mse') +# fit model +model.fit_generator(generator, steps_per_epoch=1, epochs=500, verbose=0) +# make a one step prediction out of sample +x_input = array([[90, 95], [100, 105]]).reshape((1, n_input, n_features)) +yhat = model.predict(x_input, verbose=0) +print(yhat) +``` + +运行该示例准备数据,拟合模型,并预测每个输入时间序列中的下一个值,我们期望[110,115]。 + +``` +[[111.03207 116.58153]] +``` + +## 多变量输入和相关系列示例 + +存在多变量时间序列问题,其中存在一个或多个输入序列和要预测的单独输出序列,其取决于输入序列。 + +为了使这个具体,我们可以设计一个带有两个输入时间序列和一个输出系列的例子,它是输入系列的总和。 + +``` +# define dataset +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95, 105]) +out_seq = array([25, 45, 65, 85, 105, 125, 145, 165, 185, 205]) +``` + +其中输出序列中的值是输入时间序列中同一时间步长的值的总和。 + +``` +10 + 15 = 25 +``` + +这与先前的示例不同,在给定输入的情况下,我们希望预测下一时间步的目标时间序列中的值,而不是与输入相同的时间步长。 + +例如,我们想要样本: + +``` +X, y +[10, 15], 25 +[20, 25], 45 +[30, 35], 65 +... +``` + +我们不希望样品如下: + +``` +X, y +[10, 15], 45 +[20, 25], 65 +[30, 35], 85 +... +``` + +尽管如此,TimeseriesGenerator 类假定我们正在预测下一个时间步骤,并将提供数据,如上面的第二种情况。 + +例如: + +``` +# multivariate one step problem +from numpy import array +from numpy import hstack +from keras.preprocessing.sequence import TimeseriesGenerator +# define dataset +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95, 105]) +out_seq = array([25, 45, 65, 85, 105, 125, 145, 165, 185, 205]) +# reshape series +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2)) +# define generator +n_input = 1 +generator = TimeseriesGenerator(dataset, out_seq, length=n_input, batch_size=1) +# print each sample +for i in range(len(generator)): + x, y = generator[i] + print('%s => %s' % (x, y)) +``` + +运行该示例打印样本的输入和输出部分,其具有下一时间步的输出值而不是当前时间步,因为我们可能期望这种类型的问题。 + +``` +[[[10\. 15.]]] => [[45.]] +[[[20\. 25.]]] => [[65.]] +[[[30\. 35.]]] => [[85.]] +[[[40\. 45.]]] => [[105.]] +[[[50\. 55.]]] => [[125.]] +[[[60\. 65.]]] => [[145.]] +[[[70\. 75.]]] => [[165.]] +[[[80\. 85.]]] => [[185.]] +[[[90\. 95.]]] => [[205.]] +``` + +因此,我们可以修改目标序列( _out_seq_ )并在开头插入一个额外的值,以便将所有观察值向下推一步。 + +这种人为的转变将允许优选的问题框架。 + +``` +# shift the target sample by one step +out_seq = insert(out_seq, 0, 0) +``` + +下面提供了这种转变的完整示例。 + +``` +# multivariate one step problem +from numpy import array +from numpy import hstack +from numpy import insert +from keras.preprocessing.sequence import TimeseriesGenerator +# define dataset +in_seq1 = array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) +in_seq2 = array([15, 25, 35, 45, 55, 65, 75, 85, 95, 105]) +out_seq = array([25, 45, 65, 85, 105, 125, 145, 165, 185, 205]) +# reshape series +in_seq1 = in_seq1.reshape((len(in_seq1), 1)) +in_seq2 = in_seq2.reshape((len(in_seq2), 1)) +out_seq = out_seq.reshape((len(out_seq), 1)) +# horizontally stack columns +dataset = hstack((in_seq1, in_seq2)) +# shift the target sample by one step +out_seq = insert(out_seq, 0, 0) +# define generator +n_input = 1 +generator = TimeseriesGenerator(dataset, out_seq, length=n_input, batch_size=1) +# print each sample +for i in range(len(generator)): + x, y = generator[i] + print('%s => %s' % (x, y)) +``` + +运行该示例显示问题的首选框架。 + +无论输入样本的长度如何,此方法都将起作用。 + +``` +[[[10\. 15.]]] => [25.] +[[[20\. 25.]]] => [45.] +[[[30\. 35.]]] => [65.] +[[[40\. 45.]]] => [85.] +[[[50\. 55.]]] => [105.] +[[[60\. 65.]]] => [125.] +[[[70\. 75.]]] => [145.] +[[[80\. 85.]]] => [165.] +[[[90\. 95.]]] => [185.] +``` + +## 多步预测示例 + +与许多其他类型的经典和机器学习模型相比,神经网络模型的一个好处是它们可以进行多步预测。 + +也就是说,模型可以学习将一个或多个特征的输入模式映射到多于一个特征的输出模式。这可用于时间序列预测,以直接预测多个未来时间步骤。 + +这可以通过直接从模型输出向量,通过将所需数量的输出指定为输出层中的节点数来实现,或者可以通过诸如编码器 - 解码器模型的专用序列预测模型来实现。 + +TimeseriesGenerator 的一个限制是它不直接支持多步输出。具体而言,它不会创建目标序列中可能需要的多个步骤。 + +然而,如果您准备目标序列有多个步骤,它将尊重并使用它们作为每个样本的输出部分。这意味着你有责任为每个时间步骤准备预期的输出。 + +我们可以通过一个简单的单变量时间序列来证明这一点,在输出序列中有两个时间步长。 + +您可以看到目标序列中的行数必须与输入序列中的行数相同。在这种情况下,我们必须知道输入序列中的值之外的值,或者将输入序列修剪为目标序列的长度。 + +``` +# define dataset +series = array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) +target = array([[1,2],[2,3],[3,4],[4,5],[5,6],[6,7],[7,8],[8,9],[9,10],[10,11]]) +``` + +下面列出了完整的示例。 + +``` +# univariate multi-step problem +from numpy import array +from keras.preprocessing.sequence import TimeseriesGenerator +# define dataset +series = array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) +target = array([[1,2],[2,3],[3,4],[4,5],[5,6],[6,7],[7,8],[8,9],[9,10],[10,11]]) +# define generator +n_input = 2 +generator = TimeseriesGenerator(series, target, length=n_input, batch_size=1) +# print each sample +for i in range(len(generator)): + x, y = generator[i] + print('%s => %s' % (x, y)) +``` + +运行该示例打印样本的输入和输出部分,显示两个滞后观察值作为输入,两个步骤作为多步骤预测问题的输出。 + +``` +[[1\. 2.]] => [[3\. 4.]] +[[2\. 3.]] => [[4\. 5.]] +[[3\. 4.]] => [[5\. 6.]] +[[4\. 5.]] => [[6\. 7.]] +[[5\. 6.]] => [[7\. 8.]] +[[6\. 7.]] => [[8\. 9.]] +[[7\. 8.]] => [[ 9\. 10.]] +[[8\. 9.]] => [[10\. 11.]] +``` + +## 进一步阅读 + +如果您希望深入了解,本节将提供有关该主题的更多资源。 + +* [如何将时间序列转换为 Python 中的监督学习问题](https://machinelearningmastery.com/convert-time-series-supervised-learning-problem-python/) +* [TimeseriesGenerator Keras API](https://keras.io/preprocessing/sequence/) +* [序列 Keras API](https://keras.io/utils/#sequence) +* [顺序模型 Keras API](https://keras.io/models/sequential/) +* [Python Generator](https://wiki.python.org/moin/Generators) + +## 摘要 + +在本教程中,您了解了如何使用 Keras TimeseriesGenerator 准备时间序列数据,以便使用深度学习方法进行建模。 + +具体来说,你学到了: + +* 如何定义 TimeseriesGenerator 生成器并将其用于适合深度学习模型。 +* 如何为单变量时间序列准备发电机并适合 MLP 和 LSTM 模型。 +* 如何为多变量时间序列准备生成器并适合 LSTM 模型。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/img/00332986f38919ccfc2da94100dfef15.jpg b/docs/dl-ts/img/00332986f38919ccfc2da94100dfef15.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ffb436089c71aedf1b54bfcf456b71183a5b0b12 Binary files /dev/null and b/docs/dl-ts/img/00332986f38919ccfc2da94100dfef15.jpg differ diff --git a/docs/dl-ts/img/0159f35c1136f9a0b272a8e110ac3c82.jpg b/docs/dl-ts/img/0159f35c1136f9a0b272a8e110ac3c82.jpg new file mode 100644 index 0000000000000000000000000000000000000000..26a27a66c6534b148953e4a0974e66ba44f0d16d Binary files /dev/null and b/docs/dl-ts/img/0159f35c1136f9a0b272a8e110ac3c82.jpg differ diff --git a/docs/dl-ts/img/01de4eea59309dd45bc7daaba90bdbb7.jpg b/docs/dl-ts/img/01de4eea59309dd45bc7daaba90bdbb7.jpg new file mode 100644 index 0000000000000000000000000000000000000000..97ab1ddbbdae2f871d7f55cce3e62e516a9bcc8c Binary files /dev/null and b/docs/dl-ts/img/01de4eea59309dd45bc7daaba90bdbb7.jpg differ diff --git a/docs/dl-ts/img/02bea64de799681634c6e64c0503d789.jpg b/docs/dl-ts/img/02bea64de799681634c6e64c0503d789.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2321240706ccdf3ce86798218c4d92439f6e6c60 Binary files /dev/null and b/docs/dl-ts/img/02bea64de799681634c6e64c0503d789.jpg differ diff --git a/docs/dl-ts/img/0314ec1585d398ad3046c0ee97a8a6b4.jpg b/docs/dl-ts/img/0314ec1585d398ad3046c0ee97a8a6b4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..eff46fa081d816e1e6c4f49ebf65a2e27f36ae36 Binary files /dev/null and b/docs/dl-ts/img/0314ec1585d398ad3046c0ee97a8a6b4.jpg differ diff --git a/docs/dl-ts/img/0332193c9b766456dd1810d1f257ae32.jpg b/docs/dl-ts/img/0332193c9b766456dd1810d1f257ae32.jpg new file mode 100644 index 0000000000000000000000000000000000000000..881953d4873d628746644b8f67e9e22c2e406d34 Binary files /dev/null and b/docs/dl-ts/img/0332193c9b766456dd1810d1f257ae32.jpg differ diff --git a/docs/dl-ts/img/03aa4be52b1d862ce62324e4606ebe3b.jpg b/docs/dl-ts/img/03aa4be52b1d862ce62324e4606ebe3b.jpg new file mode 100644 index 0000000000000000000000000000000000000000..627572fafe568279251f669e0188da75a9f83565 Binary files /dev/null and b/docs/dl-ts/img/03aa4be52b1d862ce62324e4606ebe3b.jpg differ diff --git a/docs/dl-ts/img/05031ee46bb81b6491c1604cc4553aea.jpg b/docs/dl-ts/img/05031ee46bb81b6491c1604cc4553aea.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a9ac29232524324af4a1b223d0235e1eaf54c32f Binary files /dev/null and b/docs/dl-ts/img/05031ee46bb81b6491c1604cc4553aea.jpg differ diff --git a/docs/dl-ts/img/08e022a87ed50436d814f99435741106.jpg b/docs/dl-ts/img/08e022a87ed50436d814f99435741106.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7017152da66689e310d209445089b0df98109d76 Binary files /dev/null and b/docs/dl-ts/img/08e022a87ed50436d814f99435741106.jpg differ diff --git a/docs/dl-ts/img/0aa87b8d263d3b7495062f0820b93896.jpg b/docs/dl-ts/img/0aa87b8d263d3b7495062f0820b93896.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9a56704d30eff57de059fd79d5471fccedcaf7d1 Binary files /dev/null and b/docs/dl-ts/img/0aa87b8d263d3b7495062f0820b93896.jpg differ diff --git a/docs/dl-ts/img/0b7c9423c80762c30eafc56d7e79711a.jpg b/docs/dl-ts/img/0b7c9423c80762c30eafc56d7e79711a.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f28b38f72105f3216a25f4e0bd6ac582263b82d2 Binary files /dev/null and b/docs/dl-ts/img/0b7c9423c80762c30eafc56d7e79711a.jpg differ diff --git a/docs/dl-ts/img/0bb8244ec91efc61eb58b07ee1fd3c4a.jpg b/docs/dl-ts/img/0bb8244ec91efc61eb58b07ee1fd3c4a.jpg new file mode 100644 index 0000000000000000000000000000000000000000..086148d197acb2cba1e6015db6eec575bc88e63a Binary files /dev/null and b/docs/dl-ts/img/0bb8244ec91efc61eb58b07ee1fd3c4a.jpg differ diff --git a/docs/dl-ts/img/0ca24cdb971f76ccf42b05032b49fa12.jpg b/docs/dl-ts/img/0ca24cdb971f76ccf42b05032b49fa12.jpg new file mode 100644 index 0000000000000000000000000000000000000000..42822a5beb05b88e629d6b8ecbe72fa8c9a25664 Binary files /dev/null and b/docs/dl-ts/img/0ca24cdb971f76ccf42b05032b49fa12.jpg differ diff --git a/docs/dl-ts/img/0d234fc3f62edaa2aabe367976c03f68.jpg b/docs/dl-ts/img/0d234fc3f62edaa2aabe367976c03f68.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1a8d12426ba8263310e3b5c3b541681ca4dce21c Binary files /dev/null and b/docs/dl-ts/img/0d234fc3f62edaa2aabe367976c03f68.jpg differ diff --git a/docs/dl-ts/img/0d5bd91efd1afcce7169f754f1456634.jpg b/docs/dl-ts/img/0d5bd91efd1afcce7169f754f1456634.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ab75db1fec4fa8dc72394be3b2aa4c436ea65aa9 Binary files /dev/null and b/docs/dl-ts/img/0d5bd91efd1afcce7169f754f1456634.jpg differ diff --git a/docs/dl-ts/img/11bcc530e80a517db8f987595cad3d50.jpg b/docs/dl-ts/img/11bcc530e80a517db8f987595cad3d50.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1e8024f380fc7f70991605034b4723ffd46dc2a8 Binary files /dev/null and b/docs/dl-ts/img/11bcc530e80a517db8f987595cad3d50.jpg differ diff --git a/docs/dl-ts/img/1414ee0be9745fea05fdd9ca50d5110a.jpg b/docs/dl-ts/img/1414ee0be9745fea05fdd9ca50d5110a.jpg new file mode 100644 index 0000000000000000000000000000000000000000..31766a30c766996027185108e98d1b3355c75f93 Binary files /dev/null and b/docs/dl-ts/img/1414ee0be9745fea05fdd9ca50d5110a.jpg differ diff --git a/docs/dl-ts/img/146f0e0a431f36c255d670e7d5eeab23.jpg b/docs/dl-ts/img/146f0e0a431f36c255d670e7d5eeab23.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8081dd62f40313ec97247a0b0516707270baa484 Binary files /dev/null and b/docs/dl-ts/img/146f0e0a431f36c255d670e7d5eeab23.jpg differ diff --git a/docs/dl-ts/img/155050bf84791ee8f293a979787b4719.jpg b/docs/dl-ts/img/155050bf84791ee8f293a979787b4719.jpg new file mode 100644 index 0000000000000000000000000000000000000000..01e25796bcbe2cd7109c56109c5b703f0c6af49c Binary files /dev/null and b/docs/dl-ts/img/155050bf84791ee8f293a979787b4719.jpg differ diff --git a/docs/dl-ts/img/172142dd5af355a685628ce26804923d.jpg b/docs/dl-ts/img/172142dd5af355a685628ce26804923d.jpg new file mode 100644 index 0000000000000000000000000000000000000000..104bcdadf858a8b01c9c19b8d2971293dbf692ff Binary files /dev/null and b/docs/dl-ts/img/172142dd5af355a685628ce26804923d.jpg differ diff --git a/docs/dl-ts/img/1749151e6c1706dd44b13fcade3c67cb.jpg b/docs/dl-ts/img/1749151e6c1706dd44b13fcade3c67cb.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5271c6e17872dd4ecadeef9c1722096c8061dbe2 Binary files /dev/null and b/docs/dl-ts/img/1749151e6c1706dd44b13fcade3c67cb.jpg differ diff --git a/docs/dl-ts/img/185d5e71e3e2cef3a4898fdeab98865c.jpg b/docs/dl-ts/img/185d5e71e3e2cef3a4898fdeab98865c.jpg new file mode 100644 index 0000000000000000000000000000000000000000..253480856dc1a3379cb227b3615c35654f401824 Binary files /dev/null and b/docs/dl-ts/img/185d5e71e3e2cef3a4898fdeab98865c.jpg differ diff --git a/docs/dl-ts/img/18b0ff5882a72f460af802769b02dbd8.jpg b/docs/dl-ts/img/18b0ff5882a72f460af802769b02dbd8.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7fc424e57d9f42ca5df34b75312b3a3e8dcdc50e Binary files /dev/null and b/docs/dl-ts/img/18b0ff5882a72f460af802769b02dbd8.jpg differ diff --git a/docs/dl-ts/img/18c16ac3e2a8293f442b4f8f772e04dd.jpg b/docs/dl-ts/img/18c16ac3e2a8293f442b4f8f772e04dd.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5c39c43d5fa07c2d0963688948f6257cb55a8b4f Binary files /dev/null and b/docs/dl-ts/img/18c16ac3e2a8293f442b4f8f772e04dd.jpg differ diff --git a/docs/dl-ts/img/18f93f0283af63bcca8a5887473b3e99.jpg b/docs/dl-ts/img/18f93f0283af63bcca8a5887473b3e99.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d47e44a10bfd058c36ddcf9d98202db68d0fb34e Binary files /dev/null and b/docs/dl-ts/img/18f93f0283af63bcca8a5887473b3e99.jpg differ diff --git a/docs/dl-ts/img/19494fd62b58338fcf37e6cd194b3011.jpg b/docs/dl-ts/img/19494fd62b58338fcf37e6cd194b3011.jpg new file mode 100644 index 0000000000000000000000000000000000000000..85be9cc96c0d891dd211e4853e02210c2aa721e6 Binary files /dev/null and b/docs/dl-ts/img/19494fd62b58338fcf37e6cd194b3011.jpg differ diff --git a/docs/dl-ts/img/19b6c63fd6c2e9d82bba960d447a3c7c.jpg b/docs/dl-ts/img/19b6c63fd6c2e9d82bba960d447a3c7c.jpg new file mode 100644 index 0000000000000000000000000000000000000000..63a0a3d40b5adcc4347fbafe9e0895bfc62a2086 Binary files /dev/null and b/docs/dl-ts/img/19b6c63fd6c2e9d82bba960d447a3c7c.jpg differ diff --git a/docs/dl-ts/img/1a8b7a5deb488b41a712fd45f5790326.jpg b/docs/dl-ts/img/1a8b7a5deb488b41a712fd45f5790326.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6adb26d4a6103c48f0823150c1ed7c556238b83a Binary files /dev/null and b/docs/dl-ts/img/1a8b7a5deb488b41a712fd45f5790326.jpg differ diff --git a/docs/dl-ts/img/1a9d3b9af9ce4c5c0b708e4b5ced542d.jpg b/docs/dl-ts/img/1a9d3b9af9ce4c5c0b708e4b5ced542d.jpg new file mode 100644 index 0000000000000000000000000000000000000000..be8fe32f66f8a76d25747cb5fe92b13bef255cc1 Binary files /dev/null and b/docs/dl-ts/img/1a9d3b9af9ce4c5c0b708e4b5ced542d.jpg differ diff --git a/docs/dl-ts/img/1c23cef438429401b113cc9ebd2bd4b9.jpg b/docs/dl-ts/img/1c23cef438429401b113cc9ebd2bd4b9.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8d291b599cd34c7ff8ad5b023b7103cbd918734d Binary files /dev/null and b/docs/dl-ts/img/1c23cef438429401b113cc9ebd2bd4b9.jpg differ diff --git a/docs/dl-ts/img/1c33c0de491fa073f947e93d52b6b1a7.jpg b/docs/dl-ts/img/1c33c0de491fa073f947e93d52b6b1a7.jpg new file mode 100644 index 0000000000000000000000000000000000000000..70a1a84a718821ff4e6dddfb0677895887bdc3f7 Binary files /dev/null and b/docs/dl-ts/img/1c33c0de491fa073f947e93d52b6b1a7.jpg differ diff --git a/docs/dl-ts/img/1cc831625d4c1a7f1a90a8ecfcb13d10.jpg b/docs/dl-ts/img/1cc831625d4c1a7f1a90a8ecfcb13d10.jpg new file mode 100644 index 0000000000000000000000000000000000000000..07a5e173a72582099316fe5612789e060b044781 Binary files /dev/null and b/docs/dl-ts/img/1cc831625d4c1a7f1a90a8ecfcb13d10.jpg differ diff --git a/docs/dl-ts/img/1d5a159cb9418c7c6d4b7bff4f258dfd.jpg b/docs/dl-ts/img/1d5a159cb9418c7c6d4b7bff4f258dfd.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7b3b634d9a6f6d6eb2dab85e4d759e5abf35bfb1 Binary files /dev/null and b/docs/dl-ts/img/1d5a159cb9418c7c6d4b7bff4f258dfd.jpg differ diff --git a/docs/dl-ts/img/1e69654cc53366212c8636c588eb915f.jpg b/docs/dl-ts/img/1e69654cc53366212c8636c588eb915f.jpg new file mode 100644 index 0000000000000000000000000000000000000000..009728885cca89b636d0aa8744edbd7ac70e79cd Binary files /dev/null and b/docs/dl-ts/img/1e69654cc53366212c8636c588eb915f.jpg differ diff --git a/docs/dl-ts/img/1f7f3eb681c264276ba101a2323ceeff.jpg b/docs/dl-ts/img/1f7f3eb681c264276ba101a2323ceeff.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9d685a26b633fbe8c3594bdf38b3c7958fcf8753 Binary files /dev/null and b/docs/dl-ts/img/1f7f3eb681c264276ba101a2323ceeff.jpg differ diff --git a/docs/dl-ts/img/1ff46115e178dd087ba95e3d279a687b.jpg b/docs/dl-ts/img/1ff46115e178dd087ba95e3d279a687b.jpg new file mode 100644 index 0000000000000000000000000000000000000000..73a57b38df07e95aad33df7a25a9f05e3aed9d74 Binary files /dev/null and b/docs/dl-ts/img/1ff46115e178dd087ba95e3d279a687b.jpg differ diff --git a/docs/dl-ts/img/20b622b485d5f630ae5715ed1ac6c161.jpg b/docs/dl-ts/img/20b622b485d5f630ae5715ed1ac6c161.jpg new file mode 100644 index 0000000000000000000000000000000000000000..027d9dc8288efbd014008bf690ccefdd911769d1 Binary files /dev/null and b/docs/dl-ts/img/20b622b485d5f630ae5715ed1ac6c161.jpg differ diff --git a/docs/dl-ts/img/21b1ac87a3254d76f9bd77bd7f122087.jpg b/docs/dl-ts/img/21b1ac87a3254d76f9bd77bd7f122087.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0b0449ec95b8e64aa998359a8b086a99ad22a0f0 Binary files /dev/null and b/docs/dl-ts/img/21b1ac87a3254d76f9bd77bd7f122087.jpg differ diff --git a/docs/dl-ts/img/21d06aff1fb1e5cd7453455d560e8c1f.jpg b/docs/dl-ts/img/21d06aff1fb1e5cd7453455d560e8c1f.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c6d9a62d12ec0b9bc2865dec731c8ea3a7b7b487 Binary files /dev/null and b/docs/dl-ts/img/21d06aff1fb1e5cd7453455d560e8c1f.jpg differ diff --git a/docs/dl-ts/img/2314a324101c61c23134e1f690302c5b.jpg b/docs/dl-ts/img/2314a324101c61c23134e1f690302c5b.jpg new file mode 100644 index 0000000000000000000000000000000000000000..80e85cd81f1f79083ab1062c8787d053023818e7 Binary files /dev/null and b/docs/dl-ts/img/2314a324101c61c23134e1f690302c5b.jpg differ diff --git a/docs/dl-ts/img/23ca281ff10f02aae3919d3aa204108f.jpg b/docs/dl-ts/img/23ca281ff10f02aae3919d3aa204108f.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fe1f163c5feaa5063f83eb2c9b3f3f2f286ecdc9 Binary files /dev/null and b/docs/dl-ts/img/23ca281ff10f02aae3919d3aa204108f.jpg differ diff --git a/docs/dl-ts/img/24af435317c5118bc2bfa90bcebc041a.jpg b/docs/dl-ts/img/24af435317c5118bc2bfa90bcebc041a.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bf5514a866fa6c4a3b3eae41615fa2442edd0977 Binary files /dev/null and b/docs/dl-ts/img/24af435317c5118bc2bfa90bcebc041a.jpg differ diff --git a/docs/dl-ts/img/2eba536cb4a7e99c8b1cf0575f8f29fd.jpg b/docs/dl-ts/img/2eba536cb4a7e99c8b1cf0575f8f29fd.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0334a3e57029484a0ae1625b00e21395b48caf60 Binary files /dev/null and b/docs/dl-ts/img/2eba536cb4a7e99c8b1cf0575f8f29fd.jpg differ diff --git a/docs/dl-ts/img/30ce77b6c30759b1c1239872c87b6aaf.jpg b/docs/dl-ts/img/30ce77b6c30759b1c1239872c87b6aaf.jpg new file mode 100644 index 0000000000000000000000000000000000000000..45752ef8dcae04635aee168cf5c28af77bb882dc Binary files /dev/null and b/docs/dl-ts/img/30ce77b6c30759b1c1239872c87b6aaf.jpg differ diff --git a/docs/dl-ts/img/31658b9ea259c71ef6d3d30e79a3b647.jpg b/docs/dl-ts/img/31658b9ea259c71ef6d3d30e79a3b647.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e1ac61d2ef5c42a4c6786eadb0a58d61eabf47e8 Binary files /dev/null and b/docs/dl-ts/img/31658b9ea259c71ef6d3d30e79a3b647.jpg differ diff --git a/docs/dl-ts/img/3267a4a29b449a33c12c590c04a714d0.jpg b/docs/dl-ts/img/3267a4a29b449a33c12c590c04a714d0.jpg new file mode 100644 index 0000000000000000000000000000000000000000..543dd39852e1f9f26debb83aba17f8339e4d0093 Binary files /dev/null and b/docs/dl-ts/img/3267a4a29b449a33c12c590c04a714d0.jpg differ diff --git a/docs/dl-ts/img/331062f1116fa8e1055698ab47cac15e.jpg b/docs/dl-ts/img/331062f1116fa8e1055698ab47cac15e.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fe101adfa5f592d4807113d747453b6a97e06bf4 Binary files /dev/null and b/docs/dl-ts/img/331062f1116fa8e1055698ab47cac15e.jpg differ diff --git a/docs/dl-ts/img/334f0b617304a565e1d93a31bdd1c50d.jpg b/docs/dl-ts/img/334f0b617304a565e1d93a31bdd1c50d.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f89cef6e479b3654a22a31829c05c1129f250ca4 Binary files /dev/null and b/docs/dl-ts/img/334f0b617304a565e1d93a31bdd1c50d.jpg differ diff --git a/docs/dl-ts/img/33b4663925b0226858ab248f10d5086d.jpg b/docs/dl-ts/img/33b4663925b0226858ab248f10d5086d.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0fd5b4855fa769e3abea2ec211276a12d45c9373 Binary files /dev/null and b/docs/dl-ts/img/33b4663925b0226858ab248f10d5086d.jpg differ diff --git a/docs/dl-ts/img/35e5f363634431dfbd413ab84cb504a3.jpg b/docs/dl-ts/img/35e5f363634431dfbd413ab84cb504a3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b75fe1c4e18ffb58c351995cfedc6e61f5c3a03a Binary files /dev/null and b/docs/dl-ts/img/35e5f363634431dfbd413ab84cb504a3.jpg differ diff --git a/docs/dl-ts/img/3618a63641ec8ba329ce9ed44aa54d58.jpg b/docs/dl-ts/img/3618a63641ec8ba329ce9ed44aa54d58.jpg new file mode 100644 index 0000000000000000000000000000000000000000..efff3b7d25a05892ec6423f84c6f7a7a690e11eb Binary files /dev/null and b/docs/dl-ts/img/3618a63641ec8ba329ce9ed44aa54d58.jpg differ diff --git a/docs/dl-ts/img/36b4fc3c00fd972bf66949204672c737.jpg b/docs/dl-ts/img/36b4fc3c00fd972bf66949204672c737.jpg new file mode 100644 index 0000000000000000000000000000000000000000..248255af05c2d93a41f3cd602df5cd7b34eea78d Binary files /dev/null and b/docs/dl-ts/img/36b4fc3c00fd972bf66949204672c737.jpg differ diff --git a/docs/dl-ts/img/3845e70194ea7d465b653bbb0d8b993a.jpg b/docs/dl-ts/img/3845e70194ea7d465b653bbb0d8b993a.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2d7864a07e3fdcdb36100394b3ffb4ab3a92790d Binary files /dev/null and b/docs/dl-ts/img/3845e70194ea7d465b653bbb0d8b993a.jpg differ diff --git a/docs/dl-ts/img/38ea068ea399ec9507e619cc66004454.jpg b/docs/dl-ts/img/38ea068ea399ec9507e619cc66004454.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c3b3781a2c6b344cb0d601d7ce2e22be100a2bd4 Binary files /dev/null and b/docs/dl-ts/img/38ea068ea399ec9507e619cc66004454.jpg differ diff --git a/docs/dl-ts/img/3965db2aedd6938d4d3e029c9b2b8503.jpg b/docs/dl-ts/img/3965db2aedd6938d4d3e029c9b2b8503.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e6ce1d91a590981132afbcdef17943b1886c62d1 Binary files /dev/null and b/docs/dl-ts/img/3965db2aedd6938d4d3e029c9b2b8503.jpg differ diff --git a/docs/dl-ts/img/3aabdc4f308e294d62bbec3e96fe6acd.jpg b/docs/dl-ts/img/3aabdc4f308e294d62bbec3e96fe6acd.jpg new file mode 100644 index 0000000000000000000000000000000000000000..387ab05aabe38072dcb8797e7f8d09cd1461cac5 Binary files /dev/null and b/docs/dl-ts/img/3aabdc4f308e294d62bbec3e96fe6acd.jpg differ diff --git a/docs/dl-ts/img/3ca55b2eb8d252bf860cc2444e9f45b1.jpg b/docs/dl-ts/img/3ca55b2eb8d252bf860cc2444e9f45b1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..528ed98a1604ac163426c1fbc15de647880afbf4 Binary files /dev/null and b/docs/dl-ts/img/3ca55b2eb8d252bf860cc2444e9f45b1.jpg differ diff --git a/docs/dl-ts/img/3e0a3895f026fa2bc5f0f9f4d5dacf9a.jpg b/docs/dl-ts/img/3e0a3895f026fa2bc5f0f9f4d5dacf9a.jpg new file mode 100644 index 0000000000000000000000000000000000000000..abffcf74a23c44f8978911b69a37614c84341fe3 Binary files /dev/null and b/docs/dl-ts/img/3e0a3895f026fa2bc5f0f9f4d5dacf9a.jpg differ diff --git a/docs/dl-ts/img/40914afcbafd1e361d7aad734ed47d7d.jpg b/docs/dl-ts/img/40914afcbafd1e361d7aad734ed47d7d.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c0b6106b32ffb0d737a29c6f521672a327e1750e Binary files /dev/null and b/docs/dl-ts/img/40914afcbafd1e361d7aad734ed47d7d.jpg differ diff --git a/docs/dl-ts/img/41ef48ab40dbf477fca8bca64edff999.jpg b/docs/dl-ts/img/41ef48ab40dbf477fca8bca64edff999.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a660940138c083c84e0f546fd24568de94034745 Binary files /dev/null and b/docs/dl-ts/img/41ef48ab40dbf477fca8bca64edff999.jpg differ diff --git a/docs/dl-ts/img/4214554ca614caed83065545f8ffff38.jpg b/docs/dl-ts/img/4214554ca614caed83065545f8ffff38.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6101430efb5bbda4e8543caec791aba0d1476562 Binary files /dev/null and b/docs/dl-ts/img/4214554ca614caed83065545f8ffff38.jpg differ diff --git a/docs/dl-ts/img/42e521ff516042804a46d81ac9f15db1.jpg b/docs/dl-ts/img/42e521ff516042804a46d81ac9f15db1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..39c1b16f65cf97eb2632c1618e8913ffb9a3a794 Binary files /dev/null and b/docs/dl-ts/img/42e521ff516042804a46d81ac9f15db1.jpg differ diff --git a/docs/dl-ts/img/42fc13496734cacdffd9f8460c8637b1.jpg b/docs/dl-ts/img/42fc13496734cacdffd9f8460c8637b1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..20c2fe9620804f26a06fc85a8d820e4db0946e7d Binary files /dev/null and b/docs/dl-ts/img/42fc13496734cacdffd9f8460c8637b1.jpg differ diff --git a/docs/dl-ts/img/4418cff7f2e1ccd0febd2e302d959377.jpg b/docs/dl-ts/img/4418cff7f2e1ccd0febd2e302d959377.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e118a2f20c50246d3e1b693dffc72e0a4375a1ff Binary files /dev/null and b/docs/dl-ts/img/4418cff7f2e1ccd0febd2e302d959377.jpg differ diff --git a/docs/dl-ts/img/45d5a066240e2c0fb8d533810540a6b4.jpg b/docs/dl-ts/img/45d5a066240e2c0fb8d533810540a6b4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1dda162276ad0abd40c205b67f85fcf6e6057c7b Binary files /dev/null and b/docs/dl-ts/img/45d5a066240e2c0fb8d533810540a6b4.jpg differ diff --git a/docs/dl-ts/img/497efce0a73106038dad8e0f4cb3ecc8.jpg b/docs/dl-ts/img/497efce0a73106038dad8e0f4cb3ecc8.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c351d750b949b369a5cfaa85da2d224a35ec23e3 Binary files /dev/null and b/docs/dl-ts/img/497efce0a73106038dad8e0f4cb3ecc8.jpg differ diff --git a/docs/dl-ts/img/4c75b72d676c9dfbd00be1e57b3f5354.jpg b/docs/dl-ts/img/4c75b72d676c9dfbd00be1e57b3f5354.jpg new file mode 100644 index 0000000000000000000000000000000000000000..56c77dd7f58c1d44f21346d4a87e38c3180b8a2a Binary files /dev/null and b/docs/dl-ts/img/4c75b72d676c9dfbd00be1e57b3f5354.jpg differ diff --git a/docs/dl-ts/img/4fd5ceadaf6bf13c76301251b1e6c656.jpg b/docs/dl-ts/img/4fd5ceadaf6bf13c76301251b1e6c656.jpg new file mode 100644 index 0000000000000000000000000000000000000000..eaad89a4304b387a3490bd40261e0e4f5faa0c20 Binary files /dev/null and b/docs/dl-ts/img/4fd5ceadaf6bf13c76301251b1e6c656.jpg differ diff --git a/docs/dl-ts/img/51aa5daf7dba387bd3b304c26c2f831e.jpg b/docs/dl-ts/img/51aa5daf7dba387bd3b304c26c2f831e.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ad9d5276b3cc74f435036b3dd40757c33a45ada5 Binary files /dev/null and b/docs/dl-ts/img/51aa5daf7dba387bd3b304c26c2f831e.jpg differ diff --git a/docs/dl-ts/img/535f79a66d646b285b37f86d6ec1831f.jpg b/docs/dl-ts/img/535f79a66d646b285b37f86d6ec1831f.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0b4221b947e08950e52e484d71a25c97da4b6efc Binary files /dev/null and b/docs/dl-ts/img/535f79a66d646b285b37f86d6ec1831f.jpg differ diff --git a/docs/dl-ts/img/56241ed05333ba50139dffc5fb206a71.jpg b/docs/dl-ts/img/56241ed05333ba50139dffc5fb206a71.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bf4990de8e7468b238c66289a1b5ba48c37b435d Binary files /dev/null and b/docs/dl-ts/img/56241ed05333ba50139dffc5fb206a71.jpg differ diff --git a/docs/dl-ts/img/56873fdcfb7768a4e912b6753aa854ce.jpg b/docs/dl-ts/img/56873fdcfb7768a4e912b6753aa854ce.jpg new file mode 100644 index 0000000000000000000000000000000000000000..66010847aa244026044c20ecc9758f4701935d32 Binary files /dev/null and b/docs/dl-ts/img/56873fdcfb7768a4e912b6753aa854ce.jpg differ diff --git a/docs/dl-ts/img/5734511d8648828979dcbdc65010e778.jpg b/docs/dl-ts/img/5734511d8648828979dcbdc65010e778.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8e46df47a735d2fb8d33d9b0bf193a54273ba974 Binary files /dev/null and b/docs/dl-ts/img/5734511d8648828979dcbdc65010e778.jpg differ diff --git a/docs/dl-ts/img/5749f0ac52452ef48f62d8653a1de766.jpg b/docs/dl-ts/img/5749f0ac52452ef48f62d8653a1de766.jpg new file mode 100644 index 0000000000000000000000000000000000000000..77060a5a6ca80fe5fbb4d00773897681051bd801 Binary files /dev/null and b/docs/dl-ts/img/5749f0ac52452ef48f62d8653a1de766.jpg differ diff --git a/docs/dl-ts/img/581b1e054e46aaa80b4e5cd32dfcbef2.jpg b/docs/dl-ts/img/581b1e054e46aaa80b4e5cd32dfcbef2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..842c2b9502fde745e7cdb9c5b51605d889c33316 Binary files /dev/null and b/docs/dl-ts/img/581b1e054e46aaa80b4e5cd32dfcbef2.jpg differ diff --git a/docs/dl-ts/img/583e09c543b0402e5d1979ca40390521.jpg b/docs/dl-ts/img/583e09c543b0402e5d1979ca40390521.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2613feca11b9de6f9c2bbb1ff7e47efd7067464a Binary files /dev/null and b/docs/dl-ts/img/583e09c543b0402e5d1979ca40390521.jpg differ diff --git a/docs/dl-ts/img/5865df70cf90107cfdab9a23b81198e5.jpg b/docs/dl-ts/img/5865df70cf90107cfdab9a23b81198e5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..df19546e178246a2de5e579e3193cbbd0da6883a Binary files /dev/null and b/docs/dl-ts/img/5865df70cf90107cfdab9a23b81198e5.jpg differ diff --git a/docs/dl-ts/img/59fc895128bf8cad70ff6341dbce8dc2.jpg b/docs/dl-ts/img/59fc895128bf8cad70ff6341dbce8dc2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..dd200c74d1bbe4a5d81fc4c1991bfafa8c59ded1 Binary files /dev/null and b/docs/dl-ts/img/59fc895128bf8cad70ff6341dbce8dc2.jpg differ diff --git a/docs/dl-ts/img/5a3f5d47621916742d5fa05bd4864b88.jpg b/docs/dl-ts/img/5a3f5d47621916742d5fa05bd4864b88.jpg new file mode 100644 index 0000000000000000000000000000000000000000..37f750df8616f7a9c09dbc148887062d3fc91949 Binary files /dev/null and b/docs/dl-ts/img/5a3f5d47621916742d5fa05bd4864b88.jpg differ diff --git a/docs/dl-ts/img/5bfa775f16608668d8981b772cb2d6f5.jpg b/docs/dl-ts/img/5bfa775f16608668d8981b772cb2d6f5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..92a6b1df790d8f34260be7d02cda75430bc22110 Binary files /dev/null and b/docs/dl-ts/img/5bfa775f16608668d8981b772cb2d6f5.jpg differ diff --git a/docs/dl-ts/img/5bff80c488cfe96f08b491d070af89d9.jpg b/docs/dl-ts/img/5bff80c488cfe96f08b491d070af89d9.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3e62f00f4e60383fc7d56c985bfd790f6084c1c5 Binary files /dev/null and b/docs/dl-ts/img/5bff80c488cfe96f08b491d070af89d9.jpg differ diff --git a/docs/dl-ts/img/5e71b25586ac103970bd9f298778c1db.jpg b/docs/dl-ts/img/5e71b25586ac103970bd9f298778c1db.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7ffd8c2887229dfcb01ee54b2d4af2719151e3b8 Binary files /dev/null and b/docs/dl-ts/img/5e71b25586ac103970bd9f298778c1db.jpg differ diff --git a/docs/dl-ts/img/5f0401b725586b6d9a4ef5a56653a653.jpg b/docs/dl-ts/img/5f0401b725586b6d9a4ef5a56653a653.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7f75ae15757dadd02f5575a0879a9e6ff1525789 Binary files /dev/null and b/docs/dl-ts/img/5f0401b725586b6d9a4ef5a56653a653.jpg differ diff --git a/docs/dl-ts/img/60c25c1bdcff46355dd8df396e8b9c17.jpg b/docs/dl-ts/img/60c25c1bdcff46355dd8df396e8b9c17.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0b64f4e24c716c6d65bf3ca1c7b6e3354a2b338f Binary files /dev/null and b/docs/dl-ts/img/60c25c1bdcff46355dd8df396e8b9c17.jpg differ diff --git a/docs/dl-ts/img/6271a0521f567098b8e9404a30eb6560.jpg b/docs/dl-ts/img/6271a0521f567098b8e9404a30eb6560.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a7011303e102d3ef3e0ddef916399e50e55409e0 Binary files /dev/null and b/docs/dl-ts/img/6271a0521f567098b8e9404a30eb6560.jpg differ diff --git a/docs/dl-ts/img/64107e8c8d652ad0e1c8b133c06bcabf.jpg b/docs/dl-ts/img/64107e8c8d652ad0e1c8b133c06bcabf.jpg new file mode 100644 index 0000000000000000000000000000000000000000..021475ae4dd0fd2858d48787a3d754334b20d695 Binary files /dev/null and b/docs/dl-ts/img/64107e8c8d652ad0e1c8b133c06bcabf.jpg differ diff --git a/docs/dl-ts/img/64602bce1a186a714ee2d6b845c8171c.jpg b/docs/dl-ts/img/64602bce1a186a714ee2d6b845c8171c.jpg new file mode 100644 index 0000000000000000000000000000000000000000..57ea5c3a2e01efe1948152bf74034bb1be75da94 Binary files /dev/null and b/docs/dl-ts/img/64602bce1a186a714ee2d6b845c8171c.jpg differ diff --git a/docs/dl-ts/img/646e3de8684355414799cd9964ad1d4f.jpg b/docs/dl-ts/img/646e3de8684355414799cd9964ad1d4f.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9a79ee60ff9965fc83b20ea89809fa7d82338d08 Binary files /dev/null and b/docs/dl-ts/img/646e3de8684355414799cd9964ad1d4f.jpg differ diff --git a/docs/dl-ts/img/64e666aef1f30c62616410d8e1e2a5cc.jpg b/docs/dl-ts/img/64e666aef1f30c62616410d8e1e2a5cc.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7f0895799f57f8f7b7274f469a42dee40a345681 Binary files /dev/null and b/docs/dl-ts/img/64e666aef1f30c62616410d8e1e2a5cc.jpg differ diff --git a/docs/dl-ts/img/6588c295af3e1872fa8dc390b20a1c2b.jpg b/docs/dl-ts/img/6588c295af3e1872fa8dc390b20a1c2b.jpg new file mode 100644 index 0000000000000000000000000000000000000000..58d2f644732cb253dea571bac9b64378494779ed Binary files /dev/null and b/docs/dl-ts/img/6588c295af3e1872fa8dc390b20a1c2b.jpg differ diff --git a/docs/dl-ts/img/667d8a4b2415019c4c974300451c5bc5.jpg b/docs/dl-ts/img/667d8a4b2415019c4c974300451c5bc5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4e98aba04b15781764299088def041f7a285ba4d Binary files /dev/null and b/docs/dl-ts/img/667d8a4b2415019c4c974300451c5bc5.jpg differ diff --git a/docs/dl-ts/img/668e431bcc6a520acc7e016533fe1276.jpg b/docs/dl-ts/img/668e431bcc6a520acc7e016533fe1276.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6ef64e30add55cca20f162e528d423d4a5e14b3d Binary files /dev/null and b/docs/dl-ts/img/668e431bcc6a520acc7e016533fe1276.jpg differ diff --git a/docs/dl-ts/img/6ad416481f984e0a1fb77a16d5a16692.jpg b/docs/dl-ts/img/6ad416481f984e0a1fb77a16d5a16692.jpg new file mode 100644 index 0000000000000000000000000000000000000000..36ae1ae7756e12464478caf3efa904784d7a447a Binary files /dev/null and b/docs/dl-ts/img/6ad416481f984e0a1fb77a16d5a16692.jpg differ diff --git a/docs/dl-ts/img/6ea5274c621fd3926200a0df643f1e1b.jpg b/docs/dl-ts/img/6ea5274c621fd3926200a0df643f1e1b.jpg new file mode 100644 index 0000000000000000000000000000000000000000..136d406fa65ac02cbf2bf489eb085ca591a335b6 Binary files /dev/null and b/docs/dl-ts/img/6ea5274c621fd3926200a0df643f1e1b.jpg differ diff --git a/docs/dl-ts/img/6f8d0f38f2ef720b8c0e0e63878746c6.jpg b/docs/dl-ts/img/6f8d0f38f2ef720b8c0e0e63878746c6.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bd56f16ff1700c80d4977838a092ce840dfd848f Binary files /dev/null and b/docs/dl-ts/img/6f8d0f38f2ef720b8c0e0e63878746c6.jpg differ diff --git a/docs/dl-ts/img/6ff80f643e5ca7c00df5ce9e8b51cbc5.jpg b/docs/dl-ts/img/6ff80f643e5ca7c00df5ce9e8b51cbc5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..399dd64df185df58f603f2a92a10f41f273ac4be Binary files /dev/null and b/docs/dl-ts/img/6ff80f643e5ca7c00df5ce9e8b51cbc5.jpg differ diff --git a/docs/dl-ts/img/706562ef19b5c359fea66d29146de4ef.jpg b/docs/dl-ts/img/706562ef19b5c359fea66d29146de4ef.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0d12d9d88f25b2f98c730fc6c6e1278d31afcaf7 Binary files /dev/null and b/docs/dl-ts/img/706562ef19b5c359fea66d29146de4ef.jpg differ diff --git a/docs/dl-ts/img/706e86992524c28cbe9e752abbef54df.jpg b/docs/dl-ts/img/706e86992524c28cbe9e752abbef54df.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fcedda51c82659160eff13ae902b62bb0ff338bb Binary files /dev/null and b/docs/dl-ts/img/706e86992524c28cbe9e752abbef54df.jpg differ diff --git a/docs/dl-ts/img/708ea207c23e8bbb98e0546431cbfbd8.jpg b/docs/dl-ts/img/708ea207c23e8bbb98e0546431cbfbd8.jpg new file mode 100644 index 0000000000000000000000000000000000000000..51e06c8477aed82177fc6b39d4f23b8356d7744f Binary files /dev/null and b/docs/dl-ts/img/708ea207c23e8bbb98e0546431cbfbd8.jpg differ diff --git a/docs/dl-ts/img/71241dc27d1854fe4b9daedfd2d2436c.jpg b/docs/dl-ts/img/71241dc27d1854fe4b9daedfd2d2436c.jpg new file mode 100644 index 0000000000000000000000000000000000000000..25ed8462482bc1d4577e728641a6b33adc8d411d Binary files /dev/null and b/docs/dl-ts/img/71241dc27d1854fe4b9daedfd2d2436c.jpg differ diff --git a/docs/dl-ts/img/71ec3bdc817faaf2008676a96990d94e.jpg b/docs/dl-ts/img/71ec3bdc817faaf2008676a96990d94e.jpg new file mode 100644 index 0000000000000000000000000000000000000000..026f2afa963f6ce066a186c102c43b3cefb27e57 Binary files /dev/null and b/docs/dl-ts/img/71ec3bdc817faaf2008676a96990d94e.jpg differ diff --git a/docs/dl-ts/img/72a4721b5d8d493844936208df537995.jpg b/docs/dl-ts/img/72a4721b5d8d493844936208df537995.jpg new file mode 100644 index 0000000000000000000000000000000000000000..21cd8d569c4d1bf80168d0da9f53d3c227bd1297 Binary files /dev/null and b/docs/dl-ts/img/72a4721b5d8d493844936208df537995.jpg differ diff --git a/docs/dl-ts/img/72f05b1109bcadd15fd827120e5405fe.jpg b/docs/dl-ts/img/72f05b1109bcadd15fd827120e5405fe.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6fca503841432511e077accad19fb4d9da20713f Binary files /dev/null and b/docs/dl-ts/img/72f05b1109bcadd15fd827120e5405fe.jpg differ diff --git a/docs/dl-ts/img/7385d179339f593157589610b2d196aa.jpg b/docs/dl-ts/img/7385d179339f593157589610b2d196aa.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8e9bdb324fd0b52e6471923750cbae6c0df9de57 Binary files /dev/null and b/docs/dl-ts/img/7385d179339f593157589610b2d196aa.jpg differ diff --git a/docs/dl-ts/img/739116192307ff4400d6586d3935321c.jpg b/docs/dl-ts/img/739116192307ff4400d6586d3935321c.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7a05d29dcb9d3f06c737c28f2fb7de88bbb4446a Binary files /dev/null and b/docs/dl-ts/img/739116192307ff4400d6586d3935321c.jpg differ diff --git a/docs/dl-ts/img/7485fdb11bb815842db4d9c28200a198.jpg b/docs/dl-ts/img/7485fdb11bb815842db4d9c28200a198.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d7124b54eccf5481fd2faf9cfacca333eee12fc7 Binary files /dev/null and b/docs/dl-ts/img/7485fdb11bb815842db4d9c28200a198.jpg differ diff --git a/docs/dl-ts/img/7630109f8dad0fafa89b8e2ee42fb8a2.jpg b/docs/dl-ts/img/7630109f8dad0fafa89b8e2ee42fb8a2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..91266bb8099507b3eb5bd85eb66c7d8f6e1456f6 Binary files /dev/null and b/docs/dl-ts/img/7630109f8dad0fafa89b8e2ee42fb8a2.jpg differ diff --git a/docs/dl-ts/img/7861f24e2b2586d15ce18a175792112a.jpg b/docs/dl-ts/img/7861f24e2b2586d15ce18a175792112a.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f58f3425e5a7d609aca364fd7ac65f7389c8b026 Binary files /dev/null and b/docs/dl-ts/img/7861f24e2b2586d15ce18a175792112a.jpg differ diff --git a/docs/dl-ts/img/78e782b7bf2e6f07146732bbf87480b9.jpg b/docs/dl-ts/img/78e782b7bf2e6f07146732bbf87480b9.jpg new file mode 100644 index 0000000000000000000000000000000000000000..693db4111cee67d39206cb8d585637b95f98ab3a Binary files /dev/null and b/docs/dl-ts/img/78e782b7bf2e6f07146732bbf87480b9.jpg differ diff --git a/docs/dl-ts/img/7abf4eed7ba2492b303ee9875fa6f90a.jpg b/docs/dl-ts/img/7abf4eed7ba2492b303ee9875fa6f90a.jpg new file mode 100644 index 0000000000000000000000000000000000000000..80d5d1dce78f4eee7ff8f793bf07c74d6138aa58 Binary files /dev/null and b/docs/dl-ts/img/7abf4eed7ba2492b303ee9875fa6f90a.jpg differ diff --git a/docs/dl-ts/img/7b9f6eb1255e7761505c2e3351eca482.jpg b/docs/dl-ts/img/7b9f6eb1255e7761505c2e3351eca482.jpg new file mode 100644 index 0000000000000000000000000000000000000000..65a324922519a872a9c072a092f759b4707a8d78 Binary files /dev/null and b/docs/dl-ts/img/7b9f6eb1255e7761505c2e3351eca482.jpg differ diff --git a/docs/dl-ts/img/7bd6f63a5956d95505a62c8ed9aa9ef6.jpg b/docs/dl-ts/img/7bd6f63a5956d95505a62c8ed9aa9ef6.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5126a9880b9f089f5ba1d6329d7164146c888d86 Binary files /dev/null and b/docs/dl-ts/img/7bd6f63a5956d95505a62c8ed9aa9ef6.jpg differ diff --git a/docs/dl-ts/img/7c3b0e642432ad9d24bbbfb806aecad3.jpg b/docs/dl-ts/img/7c3b0e642432ad9d24bbbfb806aecad3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b16fec846fa347c7ed18a98432cc7ab995899c16 Binary files /dev/null and b/docs/dl-ts/img/7c3b0e642432ad9d24bbbfb806aecad3.jpg differ diff --git a/docs/dl-ts/img/7c4475fd7dcc3407bc39a3efbfef4da6.jpg b/docs/dl-ts/img/7c4475fd7dcc3407bc39a3efbfef4da6.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2b6ea9c26a208d8a91eebab8ff08b1b6c60b597f Binary files /dev/null and b/docs/dl-ts/img/7c4475fd7dcc3407bc39a3efbfef4da6.jpg differ diff --git a/docs/dl-ts/img/7c90b7f5d31971ace6f72457f7ab296f.jpg b/docs/dl-ts/img/7c90b7f5d31971ace6f72457f7ab296f.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d2cd0e68f169223e62e93b7b3bbdf12b37676847 Binary files /dev/null and b/docs/dl-ts/img/7c90b7f5d31971ace6f72457f7ab296f.jpg differ diff --git a/docs/dl-ts/img/7d8356930daede3ec1bb4c447180190d.jpg b/docs/dl-ts/img/7d8356930daede3ec1bb4c447180190d.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b41d2578940142b6537c1aeacc900ff93e466cab Binary files /dev/null and b/docs/dl-ts/img/7d8356930daede3ec1bb4c447180190d.jpg differ diff --git a/docs/dl-ts/img/7d927ae1d72d249122952d2334a3a69a.jpg b/docs/dl-ts/img/7d927ae1d72d249122952d2334a3a69a.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e7482ee96fbb7347ec5c8cd05e334d3cd759a52c Binary files /dev/null and b/docs/dl-ts/img/7d927ae1d72d249122952d2334a3a69a.jpg differ diff --git a/docs/dl-ts/img/7e2d8d30ddff5a21eee385b400303004.jpg b/docs/dl-ts/img/7e2d8d30ddff5a21eee385b400303004.jpg new file mode 100644 index 0000000000000000000000000000000000000000..18cd28dab10ba17abbf97ffb294304d239b1413e Binary files /dev/null and b/docs/dl-ts/img/7e2d8d30ddff5a21eee385b400303004.jpg differ diff --git a/docs/dl-ts/img/7f07eb77886209ce3939e969da4cb79a.jpg b/docs/dl-ts/img/7f07eb77886209ce3939e969da4cb79a.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3e7973f0159ef08067f1c05487b8099a18397e9e Binary files /dev/null and b/docs/dl-ts/img/7f07eb77886209ce3939e969da4cb79a.jpg differ diff --git a/docs/dl-ts/img/806ec13a0c47a1bd7a1b2dda7a7716de.jpg b/docs/dl-ts/img/806ec13a0c47a1bd7a1b2dda7a7716de.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e597529b55199c4cad0f7c25f0f29b7fcc6c9bf3 Binary files /dev/null and b/docs/dl-ts/img/806ec13a0c47a1bd7a1b2dda7a7716de.jpg differ diff --git a/docs/dl-ts/img/86ec006d50b9fd40e94c7d64b6dbd486.jpg b/docs/dl-ts/img/86ec006d50b9fd40e94c7d64b6dbd486.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a13234e843b475fcc78b5b86ea4e633c57d9e1e2 Binary files /dev/null and b/docs/dl-ts/img/86ec006d50b9fd40e94c7d64b6dbd486.jpg differ diff --git a/docs/dl-ts/img/873e95dd71301bfac68e206f7783e0c0.jpg b/docs/dl-ts/img/873e95dd71301bfac68e206f7783e0c0.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c7ab071584133c791f2bb2794fc45971ac7dd915 Binary files /dev/null and b/docs/dl-ts/img/873e95dd71301bfac68e206f7783e0c0.jpg differ diff --git a/docs/dl-ts/img/88725ddef4e40866ae97294670c56f11.jpg b/docs/dl-ts/img/88725ddef4e40866ae97294670c56f11.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d3536cda816e9e28140b7553357154bfb43176bb Binary files /dev/null and b/docs/dl-ts/img/88725ddef4e40866ae97294670c56f11.jpg differ diff --git a/docs/dl-ts/img/8a698688e3da2d77f731be3cc3c494de.jpg b/docs/dl-ts/img/8a698688e3da2d77f731be3cc3c494de.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6c0ee492f86abdc571f5065adea481b40ec56d01 Binary files /dev/null and b/docs/dl-ts/img/8a698688e3da2d77f731be3cc3c494de.jpg differ diff --git a/docs/dl-ts/img/8beda69b175d57109f5b4e227d2f9ff8.jpg b/docs/dl-ts/img/8beda69b175d57109f5b4e227d2f9ff8.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fd86149ec2110e32f6aac120ba69daf6f7012673 Binary files /dev/null and b/docs/dl-ts/img/8beda69b175d57109f5b4e227d2f9ff8.jpg differ diff --git a/docs/dl-ts/img/8cd2375501f527cda5b95eb580c969fb.jpg b/docs/dl-ts/img/8cd2375501f527cda5b95eb580c969fb.jpg new file mode 100644 index 0000000000000000000000000000000000000000..77a0bf459d151c95c99317f132ccd2ed10860863 Binary files /dev/null and b/docs/dl-ts/img/8cd2375501f527cda5b95eb580c969fb.jpg differ diff --git a/docs/dl-ts/img/8d6de0d5780ec7a20d08928deef2bf04.jpg b/docs/dl-ts/img/8d6de0d5780ec7a20d08928deef2bf04.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0c4dbf782c0b54996b31b62c23731f62b098feee Binary files /dev/null and b/docs/dl-ts/img/8d6de0d5780ec7a20d08928deef2bf04.jpg differ diff --git a/docs/dl-ts/img/8e67ae0bcc130d32971f16ff501fe0e7.jpg b/docs/dl-ts/img/8e67ae0bcc130d32971f16ff501fe0e7.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7463c71a56b2c6b7dd654fe2b2f77b9f10aac954 Binary files /dev/null and b/docs/dl-ts/img/8e67ae0bcc130d32971f16ff501fe0e7.jpg differ diff --git a/docs/dl-ts/img/8f800f11e45ec50a340d5bf542f4264b.jpg b/docs/dl-ts/img/8f800f11e45ec50a340d5bf542f4264b.jpg new file mode 100644 index 0000000000000000000000000000000000000000..88ef6012d7e27aefd43f0a724033e9681379197f Binary files /dev/null and b/docs/dl-ts/img/8f800f11e45ec50a340d5bf542f4264b.jpg differ diff --git a/docs/dl-ts/img/90fa21bc957277eceb839b305b4ec6b6.jpg b/docs/dl-ts/img/90fa21bc957277eceb839b305b4ec6b6.jpg new file mode 100644 index 0000000000000000000000000000000000000000..57cced2f638eee00652ec360ccba4e977d30f559 Binary files /dev/null and b/docs/dl-ts/img/90fa21bc957277eceb839b305b4ec6b6.jpg differ diff --git a/docs/dl-ts/img/931e91a8d4a05b4ab1a818b3c821e0b5.jpg b/docs/dl-ts/img/931e91a8d4a05b4ab1a818b3c821e0b5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1a88be9a70b817fd7d870462819bbd038462f89a Binary files /dev/null and b/docs/dl-ts/img/931e91a8d4a05b4ab1a818b3c821e0b5.jpg differ diff --git a/docs/dl-ts/img/95474940827213c21c63294561b2a74d.jpg b/docs/dl-ts/img/95474940827213c21c63294561b2a74d.jpg new file mode 100644 index 0000000000000000000000000000000000000000..df05f493d71835405bd520a2fb6d347709e4cd1c Binary files /dev/null and b/docs/dl-ts/img/95474940827213c21c63294561b2a74d.jpg differ diff --git a/docs/dl-ts/img/956c08eb68d1d6a9cb1be14d4c66ae66.jpg b/docs/dl-ts/img/956c08eb68d1d6a9cb1be14d4c66ae66.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b3c202bd95c82dd273500e6903ebe4e95239f2b1 Binary files /dev/null and b/docs/dl-ts/img/956c08eb68d1d6a9cb1be14d4c66ae66.jpg differ diff --git a/docs/dl-ts/img/976779f88c028ae5777f987126429f65.jpg b/docs/dl-ts/img/976779f88c028ae5777f987126429f65.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ff9df20d867643e2a899b03be666fdaac145b684 Binary files /dev/null and b/docs/dl-ts/img/976779f88c028ae5777f987126429f65.jpg differ diff --git a/docs/dl-ts/img/979571fe0f3efdae33bd28e903ea41b5.jpg b/docs/dl-ts/img/979571fe0f3efdae33bd28e903ea41b5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..90d1f9be2e1778b28bb2f8f3ecb372df4cc6a3ad Binary files /dev/null and b/docs/dl-ts/img/979571fe0f3efdae33bd28e903ea41b5.jpg differ diff --git a/docs/dl-ts/img/9b52cda406ff40e6e4fd66182089820c.jpg b/docs/dl-ts/img/9b52cda406ff40e6e4fd66182089820c.jpg new file mode 100644 index 0000000000000000000000000000000000000000..aa297d87704f88e06d14f722edf6762d7ec787ff Binary files /dev/null and b/docs/dl-ts/img/9b52cda406ff40e6e4fd66182089820c.jpg differ diff --git a/docs/dl-ts/img/9c859d8a5b02104d272706f629aaacad.jpg b/docs/dl-ts/img/9c859d8a5b02104d272706f629aaacad.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4e571110002d9ca9d49b8a344473fb76598c06da Binary files /dev/null and b/docs/dl-ts/img/9c859d8a5b02104d272706f629aaacad.jpg differ diff --git a/docs/dl-ts/img/9e5711f15b64b80d86fc7aefd07d5322.jpg b/docs/dl-ts/img/9e5711f15b64b80d86fc7aefd07d5322.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9a9276c8df75890ce0558331d70cf1dc9d314617 Binary files /dev/null and b/docs/dl-ts/img/9e5711f15b64b80d86fc7aefd07d5322.jpg differ diff --git a/docs/dl-ts/img/9ed1db54db87c397d09434b8aa65366e.jpg b/docs/dl-ts/img/9ed1db54db87c397d09434b8aa65366e.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5aef5a91cedf08b37d2db15b3be5f5e4813505ed Binary files /dev/null and b/docs/dl-ts/img/9ed1db54db87c397d09434b8aa65366e.jpg differ diff --git a/docs/dl-ts/img/9efa29dc901b1dbfd650d33c419c3127.jpg b/docs/dl-ts/img/9efa29dc901b1dbfd650d33c419c3127.jpg new file mode 100644 index 0000000000000000000000000000000000000000..617a9eb26d388ab0024fe4d69f5daf6cf9dc24d4 Binary files /dev/null and b/docs/dl-ts/img/9efa29dc901b1dbfd650d33c419c3127.jpg differ diff --git a/docs/dl-ts/img/9f387fe9e2be7d88dcf3a61fb4de878c.jpg b/docs/dl-ts/img/9f387fe9e2be7d88dcf3a61fb4de878c.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d5ff23b914554d4ccf656cb5cb8d48475f8521eb Binary files /dev/null and b/docs/dl-ts/img/9f387fe9e2be7d88dcf3a61fb4de878c.jpg differ diff --git a/docs/dl-ts/img/9f802400a334488bcea84f8c3259967f.jpg b/docs/dl-ts/img/9f802400a334488bcea84f8c3259967f.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d95210657e0c0f105b825cefb323f4376d252215 Binary files /dev/null and b/docs/dl-ts/img/9f802400a334488bcea84f8c3259967f.jpg differ diff --git a/docs/dl-ts/img/a01cdac1fac30a42a3377da41d8f3d90.jpg b/docs/dl-ts/img/a01cdac1fac30a42a3377da41d8f3d90.jpg new file mode 100644 index 0000000000000000000000000000000000000000..47ace6498389a3f0410ca3513a1ce01389c6e813 Binary files /dev/null and b/docs/dl-ts/img/a01cdac1fac30a42a3377da41d8f3d90.jpg differ diff --git a/docs/dl-ts/img/a03973e2ec9d4b0a7f8dcbe33228abae.jpg b/docs/dl-ts/img/a03973e2ec9d4b0a7f8dcbe33228abae.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6eba6aeb49ce7ca3bb80c0cf1f833c82d76ef73e Binary files /dev/null and b/docs/dl-ts/img/a03973e2ec9d4b0a7f8dcbe33228abae.jpg differ diff --git a/docs/dl-ts/img/a106bd114259a2130d22a6a27d22dabc.jpg b/docs/dl-ts/img/a106bd114259a2130d22a6a27d22dabc.jpg new file mode 100644 index 0000000000000000000000000000000000000000..80a1b925cf8526745819e200e9c7670d866a3ee0 Binary files /dev/null and b/docs/dl-ts/img/a106bd114259a2130d22a6a27d22dabc.jpg differ diff --git a/docs/dl-ts/img/a2c6d8510072e11227a234fa5344bab9.jpg b/docs/dl-ts/img/a2c6d8510072e11227a234fa5344bab9.jpg new file mode 100644 index 0000000000000000000000000000000000000000..128bd2c0d87338d5cb0dd8c72f1d9c650ceb4708 Binary files /dev/null and b/docs/dl-ts/img/a2c6d8510072e11227a234fa5344bab9.jpg differ diff --git a/docs/dl-ts/img/a2ef63ebf932393f4617c27a6a0452ae.jpg b/docs/dl-ts/img/a2ef63ebf932393f4617c27a6a0452ae.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2121fd80affb4ddce0edfc6bde95885f42f368c7 Binary files /dev/null and b/docs/dl-ts/img/a2ef63ebf932393f4617c27a6a0452ae.jpg differ diff --git a/docs/dl-ts/img/a3d40bba50bf998fdd0ac5f7625becad.jpg b/docs/dl-ts/img/a3d40bba50bf998fdd0ac5f7625becad.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8bd42a73f90dab598a133ae56c075de567dcce82 Binary files /dev/null and b/docs/dl-ts/img/a3d40bba50bf998fdd0ac5f7625becad.jpg differ diff --git a/docs/dl-ts/img/a591c6a2170e5c1473f37cdda1016d57.jpg b/docs/dl-ts/img/a591c6a2170e5c1473f37cdda1016d57.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0ac9d780d2b492f3b822c118532475cdf2e78612 Binary files /dev/null and b/docs/dl-ts/img/a591c6a2170e5c1473f37cdda1016d57.jpg differ diff --git a/docs/dl-ts/img/a689c5d0eea9de43a80c00e2d219cb9d.jpg b/docs/dl-ts/img/a689c5d0eea9de43a80c00e2d219cb9d.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2f489ebdf104ad776aa849600c266c2041d47f13 Binary files /dev/null and b/docs/dl-ts/img/a689c5d0eea9de43a80c00e2d219cb9d.jpg differ diff --git a/docs/dl-ts/img/a6ea8e6072ec5a377562eae3a0b4a337.jpg b/docs/dl-ts/img/a6ea8e6072ec5a377562eae3a0b4a337.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ed31fc3ad63e0339b02b0db1a9df4549e85ef38e Binary files /dev/null and b/docs/dl-ts/img/a6ea8e6072ec5a377562eae3a0b4a337.jpg differ diff --git a/docs/dl-ts/img/a83635284c2a1866e245d14761b72734.jpg b/docs/dl-ts/img/a83635284c2a1866e245d14761b72734.jpg new file mode 100644 index 0000000000000000000000000000000000000000..70893bb135f56be9c65234d671bffc9de948c1b0 Binary files /dev/null and b/docs/dl-ts/img/a83635284c2a1866e245d14761b72734.jpg differ diff --git a/docs/dl-ts/img/ab56d09b72803271b91fa5bd0ccc3f0f.jpg b/docs/dl-ts/img/ab56d09b72803271b91fa5bd0ccc3f0f.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9a79ee60ff9965fc83b20ea89809fa7d82338d08 Binary files /dev/null and b/docs/dl-ts/img/ab56d09b72803271b91fa5bd0ccc3f0f.jpg differ diff --git a/docs/dl-ts/img/ac0abd4e84df89efc03932a25a23d630.jpg b/docs/dl-ts/img/ac0abd4e84df89efc03932a25a23d630.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b9ddfcd3afc00525d87a6099905ad3eb31c24b74 Binary files /dev/null and b/docs/dl-ts/img/ac0abd4e84df89efc03932a25a23d630.jpg differ diff --git a/docs/dl-ts/img/ae61a9c9b4ef0d956afa6f7b7847557a.jpg b/docs/dl-ts/img/ae61a9c9b4ef0d956afa6f7b7847557a.jpg new file mode 100644 index 0000000000000000000000000000000000000000..db6d6cbd15aa24d740b100e39c13eb68af3ca4fc Binary files /dev/null and b/docs/dl-ts/img/ae61a9c9b4ef0d956afa6f7b7847557a.jpg differ diff --git a/docs/dl-ts/img/af152fb607033c313c016f47bb8de186.jpg b/docs/dl-ts/img/af152fb607033c313c016f47bb8de186.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c6f91aa98d38335c504079c189250f21cc313491 Binary files /dev/null and b/docs/dl-ts/img/af152fb607033c313c016f47bb8de186.jpg differ diff --git a/docs/dl-ts/img/af953cebdb4b9f7e3af4bdc35e49bdae.jpg b/docs/dl-ts/img/af953cebdb4b9f7e3af4bdc35e49bdae.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7022bac9a42398fcab152d6bc1d37cc1f3e068eb Binary files /dev/null and b/docs/dl-ts/img/af953cebdb4b9f7e3af4bdc35e49bdae.jpg differ diff --git a/docs/dl-ts/img/b0483faab77f135fd4f690e151595898.jpg b/docs/dl-ts/img/b0483faab77f135fd4f690e151595898.jpg new file mode 100644 index 0000000000000000000000000000000000000000..83819a3099dc9af42d61b1939c66c6a44f64ee25 Binary files /dev/null and b/docs/dl-ts/img/b0483faab77f135fd4f690e151595898.jpg differ diff --git a/docs/dl-ts/img/b3ecbb4b2558f0998da0419e4426ae26.jpg b/docs/dl-ts/img/b3ecbb4b2558f0998da0419e4426ae26.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f0f020eae62da66bd528e265851205b890523257 Binary files /dev/null and b/docs/dl-ts/img/b3ecbb4b2558f0998da0419e4426ae26.jpg differ diff --git a/docs/dl-ts/img/b41ef0d58cf243dc1ed17ede40d1bed6.jpg b/docs/dl-ts/img/b41ef0d58cf243dc1ed17ede40d1bed6.jpg new file mode 100644 index 0000000000000000000000000000000000000000..03cc754519afbc460b877dea6297b932e0777033 Binary files /dev/null and b/docs/dl-ts/img/b41ef0d58cf243dc1ed17ede40d1bed6.jpg differ diff --git a/docs/dl-ts/img/b49b84d78ed18007a6d0edaa1b71ce11.jpg b/docs/dl-ts/img/b49b84d78ed18007a6d0edaa1b71ce11.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9a79ee60ff9965fc83b20ea89809fa7d82338d08 Binary files /dev/null and b/docs/dl-ts/img/b49b84d78ed18007a6d0edaa1b71ce11.jpg differ diff --git a/docs/dl-ts/img/b66e2ba197d451dab544125787d5de85.jpg b/docs/dl-ts/img/b66e2ba197d451dab544125787d5de85.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fb67ce51cce736af149b90e5c2376742e0fa8429 Binary files /dev/null and b/docs/dl-ts/img/b66e2ba197d451dab544125787d5de85.jpg differ diff --git a/docs/dl-ts/img/b6ddbe35ed062ef6411b43759ddbaf87.jpg b/docs/dl-ts/img/b6ddbe35ed062ef6411b43759ddbaf87.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c03299437433308855504b84b01bea9ca55ed7b7 Binary files /dev/null and b/docs/dl-ts/img/b6ddbe35ed062ef6411b43759ddbaf87.jpg differ diff --git a/docs/dl-ts/img/b933aab347ed22c0a80e593517694d80.jpg b/docs/dl-ts/img/b933aab347ed22c0a80e593517694d80.jpg new file mode 100644 index 0000000000000000000000000000000000000000..31ddbfcf4b454cca5ba3cd4ee4152a49de884ce1 Binary files /dev/null and b/docs/dl-ts/img/b933aab347ed22c0a80e593517694d80.jpg differ diff --git a/docs/dl-ts/img/ba75ce0fe72c2fdbb970c8816d94f6e5.jpg b/docs/dl-ts/img/ba75ce0fe72c2fdbb970c8816d94f6e5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..79325bf665b7b6e603334b6e9f072d36ee47d90a Binary files /dev/null and b/docs/dl-ts/img/ba75ce0fe72c2fdbb970c8816d94f6e5.jpg differ diff --git a/docs/dl-ts/img/bad7a0703d9d3cdaedb9e0861421f92d.jpg b/docs/dl-ts/img/bad7a0703d9d3cdaedb9e0861421f92d.jpg new file mode 100644 index 0000000000000000000000000000000000000000..16716c4c473e3ca1d220d5967462ac9e3d748f5a Binary files /dev/null and b/docs/dl-ts/img/bad7a0703d9d3cdaedb9e0861421f92d.jpg differ diff --git a/docs/dl-ts/img/bc55927bd32a3ad1efe39a6c9602f692.jpg b/docs/dl-ts/img/bc55927bd32a3ad1efe39a6c9602f692.jpg new file mode 100644 index 0000000000000000000000000000000000000000..eeb1d0c9a995d8ad692c72f4703660e6c2e95155 Binary files /dev/null and b/docs/dl-ts/img/bc55927bd32a3ad1efe39a6c9602f692.jpg differ diff --git a/docs/dl-ts/img/bd66ac0d66a3310bdf178d3f5d6b9e14.jpg b/docs/dl-ts/img/bd66ac0d66a3310bdf178d3f5d6b9e14.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ba742ccca86990b1ac2655daeed8944f6c3dba3a Binary files /dev/null and b/docs/dl-ts/img/bd66ac0d66a3310bdf178d3f5d6b9e14.jpg differ diff --git a/docs/dl-ts/img/c1fe9958045eb17f2e1b6aeaeca1dbb8.jpg b/docs/dl-ts/img/c1fe9958045eb17f2e1b6aeaeca1dbb8.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fbe6d14bcbe4a47bcca18b0e2a1fba5c373776f6 Binary files /dev/null and b/docs/dl-ts/img/c1fe9958045eb17f2e1b6aeaeca1dbb8.jpg differ diff --git a/docs/dl-ts/img/c26883b83ce0b79bd14c102258ea76e6.jpg b/docs/dl-ts/img/c26883b83ce0b79bd14c102258ea76e6.jpg new file mode 100644 index 0000000000000000000000000000000000000000..68e56412ab29fde870742f20d76424c9f40c7719 Binary files /dev/null and b/docs/dl-ts/img/c26883b83ce0b79bd14c102258ea76e6.jpg differ diff --git a/docs/dl-ts/img/c37e78ea2872c04c7d60614faae34c64.jpg b/docs/dl-ts/img/c37e78ea2872c04c7d60614faae34c64.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e66955de536b6bc27d4ced171b85a2e34dae2983 Binary files /dev/null and b/docs/dl-ts/img/c37e78ea2872c04c7d60614faae34c64.jpg differ diff --git a/docs/dl-ts/img/c686debb385b47ff6fcd27805e595b10.jpg b/docs/dl-ts/img/c686debb385b47ff6fcd27805e595b10.jpg new file mode 100644 index 0000000000000000000000000000000000000000..59bde9d320cdaee9f9a9901bdc3285976a4a869f Binary files /dev/null and b/docs/dl-ts/img/c686debb385b47ff6fcd27805e595b10.jpg differ diff --git a/docs/dl-ts/img/c81631a61c32b40f6acdb00b05c20949.jpg b/docs/dl-ts/img/c81631a61c32b40f6acdb00b05c20949.jpg new file mode 100644 index 0000000000000000000000000000000000000000..cd0649ca34ed0482f8b8560841b8ffd4a74a16e5 Binary files /dev/null and b/docs/dl-ts/img/c81631a61c32b40f6acdb00b05c20949.jpg differ diff --git a/docs/dl-ts/img/c8869e1fb26f7832080d090a99c51e27.jpg b/docs/dl-ts/img/c8869e1fb26f7832080d090a99c51e27.jpg new file mode 100644 index 0000000000000000000000000000000000000000..76cb0f6093a0c239ab7108e6e6fd5c5179bb7c2c Binary files /dev/null and b/docs/dl-ts/img/c8869e1fb26f7832080d090a99c51e27.jpg differ diff --git a/docs/dl-ts/img/c8ef908f2c8e1207087d46f000c468d7.jpg b/docs/dl-ts/img/c8ef908f2c8e1207087d46f000c468d7.jpg new file mode 100644 index 0000000000000000000000000000000000000000..687c6e109b9bdf8ce0716d6d891ac15e95dbfad1 Binary files /dev/null and b/docs/dl-ts/img/c8ef908f2c8e1207087d46f000c468d7.jpg differ diff --git a/docs/dl-ts/img/c922bca0017be329474b7ccf8c855193.jpg b/docs/dl-ts/img/c922bca0017be329474b7ccf8c855193.jpg new file mode 100644 index 0000000000000000000000000000000000000000..32ca4706a37065e383214f591ac02bcd1a8f2d1e Binary files /dev/null and b/docs/dl-ts/img/c922bca0017be329474b7ccf8c855193.jpg differ diff --git a/docs/dl-ts/img/cad0705f4ea93f292420695b0d6b2fb8.jpg b/docs/dl-ts/img/cad0705f4ea93f292420695b0d6b2fb8.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3c96c8f27a1b8ff940c53247bc977a3efa62800e Binary files /dev/null and b/docs/dl-ts/img/cad0705f4ea93f292420695b0d6b2fb8.jpg differ diff --git a/docs/dl-ts/img/cc240f9ff549b2514525e33149b186ba.jpg b/docs/dl-ts/img/cc240f9ff549b2514525e33149b186ba.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a33abb185392e20088cc2bd0739a3a22df30735b Binary files /dev/null and b/docs/dl-ts/img/cc240f9ff549b2514525e33149b186ba.jpg differ diff --git a/docs/dl-ts/img/cd9dece685075a929591317dbe05b37e.jpg b/docs/dl-ts/img/cd9dece685075a929591317dbe05b37e.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5dde2b085b29cdc5e53ed3bd11722f9acee5882e Binary files /dev/null and b/docs/dl-ts/img/cd9dece685075a929591317dbe05b37e.jpg differ diff --git a/docs/dl-ts/img/ce439e552781e6e49eb401c3d0d62529.jpg b/docs/dl-ts/img/ce439e552781e6e49eb401c3d0d62529.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1b1474416ccd4e333abc2ee26ddd2aad97dabad3 Binary files /dev/null and b/docs/dl-ts/img/ce439e552781e6e49eb401c3d0d62529.jpg differ diff --git a/docs/dl-ts/img/ce64d04b088a9f83e93898d1c644c3e2.jpg b/docs/dl-ts/img/ce64d04b088a9f83e93898d1c644c3e2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..09eb6ea168e8d56becdc553da6c01891fa9cf281 Binary files /dev/null and b/docs/dl-ts/img/ce64d04b088a9f83e93898d1c644c3e2.jpg differ diff --git a/docs/dl-ts/img/cf21fe77ad8f90bbad3a4061cfddf266.jpg b/docs/dl-ts/img/cf21fe77ad8f90bbad3a4061cfddf266.jpg new file mode 100644 index 0000000000000000000000000000000000000000..895124be88ac6f7f9059c7aa5e39373561b499ff Binary files /dev/null and b/docs/dl-ts/img/cf21fe77ad8f90bbad3a4061cfddf266.jpg differ diff --git a/docs/dl-ts/img/cf7c8976614194befd7ba7073555bfd1.jpg b/docs/dl-ts/img/cf7c8976614194befd7ba7073555bfd1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..dc9b6a79bc306a073ac4ab31ca93fca5dde8f614 Binary files /dev/null and b/docs/dl-ts/img/cf7c8976614194befd7ba7073555bfd1.jpg differ diff --git a/docs/dl-ts/img/cfc3b57cc10dc8b0937a34613f5d5f17.jpg b/docs/dl-ts/img/cfc3b57cc10dc8b0937a34613f5d5f17.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ebe5ce8dad536f3160c7953309431cf25acae34e Binary files /dev/null and b/docs/dl-ts/img/cfc3b57cc10dc8b0937a34613f5d5f17.jpg differ diff --git a/docs/dl-ts/img/cfd6e4ff84de46c79ccf71d45a6a5251.jpg b/docs/dl-ts/img/cfd6e4ff84de46c79ccf71d45a6a5251.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4021a4b85e4b290d909dca6a79635dbc30727624 Binary files /dev/null and b/docs/dl-ts/img/cfd6e4ff84de46c79ccf71d45a6a5251.jpg differ diff --git a/docs/dl-ts/img/d31b96331b0a1d029333ff3432b836b1.jpg b/docs/dl-ts/img/d31b96331b0a1d029333ff3432b836b1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..714fb19227d2b5f7216708b00cfe1ee62e988032 Binary files /dev/null and b/docs/dl-ts/img/d31b96331b0a1d029333ff3432b836b1.jpg differ diff --git a/docs/dl-ts/img/d330ca8c16c51dde05533f60b321bc56.jpg b/docs/dl-ts/img/d330ca8c16c51dde05533f60b321bc56.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1ec9e83507c40a5da8d4b14e26a90efbd9452000 Binary files /dev/null and b/docs/dl-ts/img/d330ca8c16c51dde05533f60b321bc56.jpg differ diff --git a/docs/dl-ts/img/d463c92463bbfd80a905df0c8cc129aa.jpg b/docs/dl-ts/img/d463c92463bbfd80a905df0c8cc129aa.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a27822d5fa25d6e9ecf7a607e30b3b69ea3d3896 Binary files /dev/null and b/docs/dl-ts/img/d463c92463bbfd80a905df0c8cc129aa.jpg differ diff --git a/docs/dl-ts/img/d5ae9aad66d72aeda14a8d3568dc4943.jpg b/docs/dl-ts/img/d5ae9aad66d72aeda14a8d3568dc4943.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c6f9caf6e7e2603928c48d9530c6eeba9bc81f92 Binary files /dev/null and b/docs/dl-ts/img/d5ae9aad66d72aeda14a8d3568dc4943.jpg differ diff --git a/docs/dl-ts/img/d64e07469bc222ad87d3880144fb3396.jpg b/docs/dl-ts/img/d64e07469bc222ad87d3880144fb3396.jpg new file mode 100644 index 0000000000000000000000000000000000000000..14480caeb5a569dfd2faee6b80599ab2bb3502e8 Binary files /dev/null and b/docs/dl-ts/img/d64e07469bc222ad87d3880144fb3396.jpg differ diff --git a/docs/dl-ts/img/d6e72b46b3d1124712f8b7e1fb0da1ca.jpg b/docs/dl-ts/img/d6e72b46b3d1124712f8b7e1fb0da1ca.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e0f1ed997cb6468747249a8a4a01e546234f77e4 Binary files /dev/null and b/docs/dl-ts/img/d6e72b46b3d1124712f8b7e1fb0da1ca.jpg differ diff --git a/docs/dl-ts/img/d727c0accd040bb84330b3c24dffd5d7.jpg b/docs/dl-ts/img/d727c0accd040bb84330b3c24dffd5d7.jpg new file mode 100644 index 0000000000000000000000000000000000000000..978f1866892235ca860cdd19c47c4862db7633c7 Binary files /dev/null and b/docs/dl-ts/img/d727c0accd040bb84330b3c24dffd5d7.jpg differ diff --git a/docs/dl-ts/img/d76413e9f5320b1fca77b30e22b91288.jpg b/docs/dl-ts/img/d76413e9f5320b1fca77b30e22b91288.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2a98a5b2b94447c14762916253190f9380c53adc Binary files /dev/null and b/docs/dl-ts/img/d76413e9f5320b1fca77b30e22b91288.jpg differ diff --git a/docs/dl-ts/img/d7d2d16af4ae339a9737e0115e69b418.jpg b/docs/dl-ts/img/d7d2d16af4ae339a9737e0115e69b418.jpg new file mode 100644 index 0000000000000000000000000000000000000000..cd0a9592556ded83635c78b2aba2bad3e5b870aa Binary files /dev/null and b/docs/dl-ts/img/d7d2d16af4ae339a9737e0115e69b418.jpg differ diff --git a/docs/dl-ts/img/d81e423a4166806357562711b5c56d6d.jpg b/docs/dl-ts/img/d81e423a4166806357562711b5c56d6d.jpg new file mode 100644 index 0000000000000000000000000000000000000000..69ed1a4e274e064d9a62c422e87cbdd7f50abd09 Binary files /dev/null and b/docs/dl-ts/img/d81e423a4166806357562711b5c56d6d.jpg differ diff --git a/docs/dl-ts/img/d886deae66a94f53af0a8769f359935c.jpg b/docs/dl-ts/img/d886deae66a94f53af0a8769f359935c.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5c3b02d961f24fc9ec2e9c15e6d07fd695ec6ab6 Binary files /dev/null and b/docs/dl-ts/img/d886deae66a94f53af0a8769f359935c.jpg differ diff --git a/docs/dl-ts/img/d886f5d7d84f1cced775bb6bea87ee3c.jpg b/docs/dl-ts/img/d886f5d7d84f1cced775bb6bea87ee3c.jpg new file mode 100644 index 0000000000000000000000000000000000000000..518a435ffcec8f4716882a55e6006ad1f826b167 Binary files /dev/null and b/docs/dl-ts/img/d886f5d7d84f1cced775bb6bea87ee3c.jpg differ diff --git a/docs/dl-ts/img/d953c25c6942e3d93fd4dcfe23fca050.jpg b/docs/dl-ts/img/d953c25c6942e3d93fd4dcfe23fca050.jpg new file mode 100644 index 0000000000000000000000000000000000000000..487ee180ca52b9969eff0d66d355ac9025acc36c Binary files /dev/null and b/docs/dl-ts/img/d953c25c6942e3d93fd4dcfe23fca050.jpg differ diff --git a/docs/dl-ts/img/da2a831510c95ca2ba65350d21b67c83.jpg b/docs/dl-ts/img/da2a831510c95ca2ba65350d21b67c83.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c71fe21739d9387e38d1641c1803cc58efc8cd46 Binary files /dev/null and b/docs/dl-ts/img/da2a831510c95ca2ba65350d21b67c83.jpg differ diff --git a/docs/dl-ts/img/dabb24b02643324fb9d69586be439808.jpg b/docs/dl-ts/img/dabb24b02643324fb9d69586be439808.jpg new file mode 100644 index 0000000000000000000000000000000000000000..27a68e23ad29201ca061086411f1607e38ec8805 Binary files /dev/null and b/docs/dl-ts/img/dabb24b02643324fb9d69586be439808.jpg differ diff --git a/docs/dl-ts/img/dac762b2375e9931ed3026c5f7a1a456.jpg b/docs/dl-ts/img/dac762b2375e9931ed3026c5f7a1a456.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e9024faea98f8aaff28baf03a9737bc5302701c9 Binary files /dev/null and b/docs/dl-ts/img/dac762b2375e9931ed3026c5f7a1a456.jpg differ diff --git a/docs/dl-ts/img/dd8aef3b6e26536ca82c6e2827f90025.jpg b/docs/dl-ts/img/dd8aef3b6e26536ca82c6e2827f90025.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7de90577a76b6f7138c6e2a6a09350791a622ef4 Binary files /dev/null and b/docs/dl-ts/img/dd8aef3b6e26536ca82c6e2827f90025.jpg differ diff --git a/docs/dl-ts/img/ddabd4eb61af3e12ea5e27191572ab52.jpg b/docs/dl-ts/img/ddabd4eb61af3e12ea5e27191572ab52.jpg new file mode 100644 index 0000000000000000000000000000000000000000..be188f536b405a89751aa0647e70ec3fc35cfd10 Binary files /dev/null and b/docs/dl-ts/img/ddabd4eb61af3e12ea5e27191572ab52.jpg differ diff --git a/docs/dl-ts/img/df1e821df7175857cde68ed9aaea390b.jpg b/docs/dl-ts/img/df1e821df7175857cde68ed9aaea390b.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a412bd4fce6439c6e67a5ebe897522129be6e4fd Binary files /dev/null and b/docs/dl-ts/img/df1e821df7175857cde68ed9aaea390b.jpg differ diff --git a/docs/dl-ts/img/e093b5862dc4ceffded85d788a5e9c20.jpg b/docs/dl-ts/img/e093b5862dc4ceffded85d788a5e9c20.jpg new file mode 100644 index 0000000000000000000000000000000000000000..61d1073ed04a35920c66b0be77954c991fa01c56 Binary files /dev/null and b/docs/dl-ts/img/e093b5862dc4ceffded85d788a5e9c20.jpg differ diff --git a/docs/dl-ts/img/e39fd234348ba17ead30334c32d345ae.jpg b/docs/dl-ts/img/e39fd234348ba17ead30334c32d345ae.jpg new file mode 100644 index 0000000000000000000000000000000000000000..01675698b81c79b52d80ec933f00a2bda9c59215 Binary files /dev/null and b/docs/dl-ts/img/e39fd234348ba17ead30334c32d345ae.jpg differ diff --git a/docs/dl-ts/img/e4749ea8e2d453eb45635bfb57f9622a.jpg b/docs/dl-ts/img/e4749ea8e2d453eb45635bfb57f9622a.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6229bfa7a4bbd62ef605ced6f32c8eff0497abb1 Binary files /dev/null and b/docs/dl-ts/img/e4749ea8e2d453eb45635bfb57f9622a.jpg differ diff --git a/docs/dl-ts/img/e51221ef089e7cfadbc55a61bbc159f0.jpg b/docs/dl-ts/img/e51221ef089e7cfadbc55a61bbc159f0.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3840280981f2e5739b6d6a8aa2fe533cf7cd3b41 Binary files /dev/null and b/docs/dl-ts/img/e51221ef089e7cfadbc55a61bbc159f0.jpg differ diff --git a/docs/dl-ts/img/e5fb4d95be292393f8e27fa5e79a413a.jpg b/docs/dl-ts/img/e5fb4d95be292393f8e27fa5e79a413a.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5a1c0708c28d62c9b5f0483c6dad25d0656b17d3 Binary files /dev/null and b/docs/dl-ts/img/e5fb4d95be292393f8e27fa5e79a413a.jpg differ diff --git a/docs/dl-ts/img/e68f8d95e6fbb3e3218e817e77b59958.jpg b/docs/dl-ts/img/e68f8d95e6fbb3e3218e817e77b59958.jpg new file mode 100644 index 0000000000000000000000000000000000000000..66e08f14b4c15bef8b21da1ae15e8e740a22192c Binary files /dev/null and b/docs/dl-ts/img/e68f8d95e6fbb3e3218e817e77b59958.jpg differ diff --git a/docs/dl-ts/img/e6b3e3acd0f040f57f08da354ba4b3b3.jpg b/docs/dl-ts/img/e6b3e3acd0f040f57f08da354ba4b3b3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ea04611f2f56157fa4b42b6509399f15bfe5c0c5 Binary files /dev/null and b/docs/dl-ts/img/e6b3e3acd0f040f57f08da354ba4b3b3.jpg differ diff --git a/docs/dl-ts/img/e82707712edc7aa1034e873eb9275227.jpg b/docs/dl-ts/img/e82707712edc7aa1034e873eb9275227.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4bb841933e8f2fcd38051a0cc38372b73015a1ff Binary files /dev/null and b/docs/dl-ts/img/e82707712edc7aa1034e873eb9275227.jpg differ diff --git a/docs/dl-ts/img/ea76f465fa78198e50d3dff96634d44b.jpg b/docs/dl-ts/img/ea76f465fa78198e50d3dff96634d44b.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fc8653d56c2bc13d67e24d3f1d91c47644a24bd2 Binary files /dev/null and b/docs/dl-ts/img/ea76f465fa78198e50d3dff96634d44b.jpg differ diff --git a/docs/dl-ts/img/ebee4c19d3410fa4364b1a96ee8f1bd3.jpg b/docs/dl-ts/img/ebee4c19d3410fa4364b1a96ee8f1bd3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..548354ad9c9db1620450e013e46c2b0e567edeae Binary files /dev/null and b/docs/dl-ts/img/ebee4c19d3410fa4364b1a96ee8f1bd3.jpg differ diff --git a/docs/dl-ts/img/ecc843af6c0636cc7c6c7d09a1fc59e6.jpg b/docs/dl-ts/img/ecc843af6c0636cc7c6c7d09a1fc59e6.jpg new file mode 100644 index 0000000000000000000000000000000000000000..380d60ea9b9eaafa17fe5ae0e45548b45de3b99f Binary files /dev/null and b/docs/dl-ts/img/ecc843af6c0636cc7c6c7d09a1fc59e6.jpg differ diff --git a/docs/dl-ts/img/ecd2d7ec902c9f547e5b006036f581d3.jpg b/docs/dl-ts/img/ecd2d7ec902c9f547e5b006036f581d3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6bb7be0aaec6c1a1651e8e88c808548f2227d87f Binary files /dev/null and b/docs/dl-ts/img/ecd2d7ec902c9f547e5b006036f581d3.jpg differ diff --git a/docs/dl-ts/img/eeb9a82272270efeac789d92769736f7.jpg b/docs/dl-ts/img/eeb9a82272270efeac789d92769736f7.jpg new file mode 100644 index 0000000000000000000000000000000000000000..95a1438b555c6104ad11d900dafd074fd41f33fd Binary files /dev/null and b/docs/dl-ts/img/eeb9a82272270efeac789d92769736f7.jpg differ diff --git a/docs/dl-ts/img/efd88d73fb1f9ab51d889537849b1d52.jpg b/docs/dl-ts/img/efd88d73fb1f9ab51d889537849b1d52.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d50321d0b682bf8159e89bf4997829c20b2fb5c9 Binary files /dev/null and b/docs/dl-ts/img/efd88d73fb1f9ab51d889537849b1d52.jpg differ diff --git a/docs/dl-ts/img/f05a406510a60b1571ed6761220f38c5.jpg b/docs/dl-ts/img/f05a406510a60b1571ed6761220f38c5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..45dcf31cd17a1144772209edd18b7bfba91deac7 Binary files /dev/null and b/docs/dl-ts/img/f05a406510a60b1571ed6761220f38c5.jpg differ diff --git a/docs/dl-ts/img/f09677b6d292acb15ace82e901a6a3a0.jpg b/docs/dl-ts/img/f09677b6d292acb15ace82e901a6a3a0.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ad958b241a13f28b6928871d4efa6447d323e6c7 Binary files /dev/null and b/docs/dl-ts/img/f09677b6d292acb15ace82e901a6a3a0.jpg differ diff --git a/docs/dl-ts/img/f164243f9642ad8edbbb2ba67e32a8e9.jpg b/docs/dl-ts/img/f164243f9642ad8edbbb2ba67e32a8e9.jpg new file mode 100644 index 0000000000000000000000000000000000000000..50f5d677cfdb6223620f2f28a874f89ef08a7ae5 Binary files /dev/null and b/docs/dl-ts/img/f164243f9642ad8edbbb2ba67e32a8e9.jpg differ diff --git a/docs/dl-ts/img/f3da9bb298e41b80fb0ab03ad4b59933.jpg b/docs/dl-ts/img/f3da9bb298e41b80fb0ab03ad4b59933.jpg new file mode 100644 index 0000000000000000000000000000000000000000..76e5893e63064c7392b4bf00411aaf5ccc82cf9d Binary files /dev/null and b/docs/dl-ts/img/f3da9bb298e41b80fb0ab03ad4b59933.jpg differ diff --git a/docs/dl-ts/img/f45bf53ff37b10426af7ebe617257bf8.jpg b/docs/dl-ts/img/f45bf53ff37b10426af7ebe617257bf8.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f646260c697b26fcd298b9d8589f3cbe251c1204 Binary files /dev/null and b/docs/dl-ts/img/f45bf53ff37b10426af7ebe617257bf8.jpg differ diff --git a/docs/dl-ts/img/f483fd406f5bb07ab05d0afad48bddfa.jpg b/docs/dl-ts/img/f483fd406f5bb07ab05d0afad48bddfa.jpg new file mode 100644 index 0000000000000000000000000000000000000000..623e02517631a87fd86e8396a6970580adc4470f Binary files /dev/null and b/docs/dl-ts/img/f483fd406f5bb07ab05d0afad48bddfa.jpg differ diff --git a/docs/dl-ts/img/f48debb1f6b1b8f3f780f8dcb7ef230c.jpg b/docs/dl-ts/img/f48debb1f6b1b8f3f780f8dcb7ef230c.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0d4b4177598f41283d2fa777ff00205cc2be5aba Binary files /dev/null and b/docs/dl-ts/img/f48debb1f6b1b8f3f780f8dcb7ef230c.jpg differ diff --git a/docs/dl-ts/img/f4ae1f38157af4e020f209d5dd2b7350.jpg b/docs/dl-ts/img/f4ae1f38157af4e020f209d5dd2b7350.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1be54f692731dba88466a1921a29c48e7eba416b Binary files /dev/null and b/docs/dl-ts/img/f4ae1f38157af4e020f209d5dd2b7350.jpg differ diff --git a/docs/dl-ts/img/f4b0a30a4bf815a7d503b08aa053de7a.jpg b/docs/dl-ts/img/f4b0a30a4bf815a7d503b08aa053de7a.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1731c8a18b20c29d750135a22c568edddeb60d29 Binary files /dev/null and b/docs/dl-ts/img/f4b0a30a4bf815a7d503b08aa053de7a.jpg differ diff --git a/docs/dl-ts/img/f5cb6c201d420ad25daa99d0a2c5478d.jpg b/docs/dl-ts/img/f5cb6c201d420ad25daa99d0a2c5478d.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a11412cfb1504005b56afe7609d00695206ab7a4 Binary files /dev/null and b/docs/dl-ts/img/f5cb6c201d420ad25daa99d0a2c5478d.jpg differ diff --git a/docs/dl-ts/img/f8b3d7cc98a4b1c950164ed026b6aa20.jpg b/docs/dl-ts/img/f8b3d7cc98a4b1c950164ed026b6aa20.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3ed1cc52632bb78e8aedfafc4ac1a7fc5e6cf0c5 Binary files /dev/null and b/docs/dl-ts/img/f8b3d7cc98a4b1c950164ed026b6aa20.jpg differ diff --git a/docs/dl-ts/img/f8b55f0e298912845f002b6f20a5c28d.jpg b/docs/dl-ts/img/f8b55f0e298912845f002b6f20a5c28d.jpg new file mode 100644 index 0000000000000000000000000000000000000000..85066760663be6b81efdcb701b61e3d534ed6928 Binary files /dev/null and b/docs/dl-ts/img/f8b55f0e298912845f002b6f20a5c28d.jpg differ diff --git a/docs/dl-ts/img/f9e27244a1745ade5fcf281ea7b0edea.jpg b/docs/dl-ts/img/f9e27244a1745ade5fcf281ea7b0edea.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9893be03c29d7d43e6a914f0b497adfb9ac4b43f Binary files /dev/null and b/docs/dl-ts/img/f9e27244a1745ade5fcf281ea7b0edea.jpg differ diff --git a/docs/dl-ts/img/fd064a0529a055985fa16a766abe0f7c.jpg b/docs/dl-ts/img/fd064a0529a055985fa16a766abe0f7c.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6a578f28e6e76408b80d5d9a0e12b187e81a734a Binary files /dev/null and b/docs/dl-ts/img/fd064a0529a055985fa16a766abe0f7c.jpg differ diff --git a/docs/dl-ts/img/fda2e2721e4632569246b9c334a37604.jpg b/docs/dl-ts/img/fda2e2721e4632569246b9c334a37604.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8e10b1778368d2da915a7b60d3013ebd7d49f7a4 Binary files /dev/null and b/docs/dl-ts/img/fda2e2721e4632569246b9c334a37604.jpg differ diff --git a/docs/dl-ts/img/fe1f95966fa74970b2f080b2b4ac94c0.jpg b/docs/dl-ts/img/fe1f95966fa74970b2f080b2b4ac94c0.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1c04127e8e6bc811553994f245dcf2efe8e93864 Binary files /dev/null and b/docs/dl-ts/img/fe1f95966fa74970b2f080b2b4ac94c0.jpg differ diff --git a/docs/dl-ts/img/ffdeee56297777b792330716ee3eddd5.jpg b/docs/dl-ts/img/ffdeee56297777b792330716ee3eddd5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7fadde39f88a33de6149cbc98bf7c3cc4f7327b0 Binary files /dev/null and b/docs/dl-ts/img/ffdeee56297777b792330716ee3eddd5.jpg differ diff --git a/docs/dl-ts/indoor-movement-time-series-classification-with-machine-learning-algorithms.md b/docs/dl-ts/indoor-movement-time-series-classification-with-machine-learning-algorithms.md new file mode 100644 index 0000000000000000000000000000000000000000..9041ce37cc5dbdfc88ea4e9db815b921165b2f6f --- /dev/null +++ b/docs/dl-ts/indoor-movement-time-series-classification-with-machine-learning-algorithms.md @@ -0,0 +1,1073 @@ +# 基于机器学习算法的室内运动时间序列分类 + +> 原文: [https://machinelearningmastery.com/indoor-movement-time-series-classification-with-machine-learning-algorithms/](https://machinelearningmastery.com/indoor-movement-time-series-classification-with-machine-learning-algorithms/) + +室内运动预测涉及使用无线传感器强度数据来预测建筑物内的对象的位置和运动。 + +这是一个具有挑战性的问题,因为没有直接的分析模型将来自多个传感器的信号强度数据的可变长度轨迹转换为用户行为。 + +'_ 室内用户移动 _'数据集是标准且免费提供的时间序列分类问题。 + +在本教程中,您将发现室内运动预测时间序列分类问题以及如何设计功能并评估问题的机器学习算法。 + +完成本教程后,您将了解: + +* 基于传感器强度预测房间运动的时间序列分类问题。 +* 如何调查数据以便更好地理解问题以及如何从原始数据中设计特征以进行预测建模。 +* 如何检查一套分类算法并调整一种算法以进一步提高问题的表现。 + +让我们开始吧。 + +* **更新 Sept / 2018** :添加了指向数据集镜像的链接。 + +![Indoor Movement Time Series Classification with Machine Learning Algorithms](img/19494fd62b58338fcf37e6cd194b3011.jpg) + +室内运动时间序列分类与机器学习算法 +照片由 [Nola Tularosa](https://www.flickr.com/photos/nolatularosa/3238977118/) ,保留一些权利。 + +## 教程概述 + +本教程分为五个部分;他们是: + +1. 室内用户运动预测 +2. 室内运动预测数据集 +3. 模型评估 +4. 数据准备 +5. 算法抽查 + +## 室内用户运动预测 + +“_ 室内用户移动 _”预测问题涉及基于由环境中的无线检测器测量的信号强度的变化来确定个体是否已经在房间之间移动。 + +收集数据集并由 Davide Bacciu 等人提供。来自意大利比萨大学的 2011 年论文“[通过油藏计算](https://pdfs.semanticscholar.org/40c2/393e1874c3fd961fdfff02402c24ccf1c3d7.pdf#page=13)预测异构室内环境中的用户运动”作为探索一种类似于反复神经网络称为“油藏计算”的方法论的数据集。 “ + +问题是预测室内用户定位和运动模式的更普遍问题的特殊情况。 + +通过在环境中定位四个无线传感器并在主题上定位一个来收集数据。当四个无线传感器检测到并记录传感器强度的时间序列时,受试者穿过环境。 + +结果是由可变长度时间序列组成的数据集,其中四个变量描述通过明确定义的静态环境的轨迹,以及运动是否导致环境中的主题更衣室的分类。 + +这是一个具有挑战性的问题,因为没有明显和通用的方法将信号强度数据与环境中的主题位置相关联。 + +> RSS 与被跟踪对象的位置之间的关系不能容易地形成分析模型,因为它强烈依赖于环境的特征以及所涉及的无线设备。一世 + +- [通过油藏计算预测异构室内环境中的用户移动](https://pdfs.semanticscholar.org/40c2/393e1874c3fd961fdfff02402c24ccf1c3d7.pdf#page=13),2011。 + +在受控的实验条件下收集数据。 + +传感器被放置在三对两个连接的房间中,其中包含典型的办公家具。两个传感器放置在两个房间的每个房间的角落中,并且受试者走过房间中的六条预定路径中的一条。预测是在每条路径的某个点上进行的,这可能会也可能不会导致房间的变化。 + +下面的动画片清楚地表明了传感器位置(A1-A4),可能行走的六条可能路径,以及将进行预测的两个点(M)。 + +![Overview of two rooms, sensor locations and the 6 pre-defined paths](img/f45bf53ff37b10426af7ebe617257bf8.jpg) + +两个房间概述,传感器位置和 6 个预定义路径。 +取自“通过油藏计算预测异构室内环境中的用户移动”。 + +从三对两个房间收集三个数据集,其中走路径并进行传感器测量,称为数据集 1,数据集 2 和数据集 3。 + +下表摘自论文,总结了三个数据集中每个数据集中走的路径数,房间更改总数和非房间更改(类标签)以及时间序列输入的长度。 + +![Summary of sensor data collected from the three pairs of two rooms](img/1e69654cc53366212c8636c588eb915f.jpg) + +从三对两个房间收集的传感器数据摘要。 +取自“通过油藏计算预测异构室内环境中的用户移动”。 + +从技术上讲,数据由多变量时间序列输入和分类输出组成,可以描述为时间序列分类问题。 + +> 来自四个锚的 RSS 值被组织成对应于从起始点到标记 M 的轨迹测量的不同长度的序列。目标分类标签与每个输入序列相关联以指示用户是否将要改变其位置(房间)或不。 + +- [通过油藏计算预测异构室内环境中的用户移动](https://pdfs.semanticscholar.org/40c2/393e1874c3fd961fdfff02402c24ccf1c3d7.pdf#page=13),2011。 + +## 室内运动预测数据集 + +数据集可从 UCI 机器学习库免费获得: + +* [来自 RSS 数据集的室内用户移动预测,UCI 机器学习库](https://archive.ics.uci.edu/ml/datasets/Indoor+User+Movement+Prediction+from+RSS+data) + +如果上述网站出现故障(可能发生),这里是指向数据集镜像的直接链接: + +* [IndoorMovement.zip](https://raw.githubusercontent.com/jbrownlee/Datasets/master/IndoorMovement.zip) + +数据可以下载为包含以下显着文件的.zip 文件: + +* **dataset / MovementAAL_RSS _ ???。csv** 每个动作的 RSS 曲线,文件名中的'???'标记从 1 到 311 的曲目编号。 +* **dataset / MovementAAL_target.csv** 跟踪号到输出类值或目标的映射。 +* **groups / MovementAAL_DatasetGroup.csv** 跟踪编号到数据集组 1,2 或 3 的映射,标记记录跟踪的一对房间。 +* **groups / MovementAAL_Paths.csv** 跟踪号码到路径类型 1-6 的映射,在上面的卡通图中标记。 + +提供的数据已经标准化。 + +具体来说,每个输入变量被标准化为每个数据集(一对房间)的范围[-1,1],输出类变量标记为-1 表示房间之间没有过渡,而+1 表示通过房间过渡。 + +> [...]放置数据包括 4 维 RSS 测量的时间序列(NU = 4),对应于每个数据集独立地在[-1,1]范围内归一化的 4 个锚点[...] + +- [通过油藏计算预测异构室内环境中的用户移动](https://pdfs.semanticscholar.org/40c2/393e1874c3fd961fdfff02402c24ccf1c3d7.pdf#page=13),2011。 + +如果预标准化分布差异很大,则在跨数据集组合观察时,按数据集缩放数据可能(或可能不)引入额外的挑战。 + +给定跟踪文​​件中的一个跟踪的时间序列按时间顺序提供,其中一行记录单个时间步的观察结果。数据以 8Hz 记录,这意味着数据中的八个时间步长经过了一秒的时钟时间。 + +下面是一个跟踪示例,取自' _dataset / MovementAAL_RSS_1.csv_ ',其输出目标为'1'(发生房间转换),来自第 1 组(第一对房间)和是路径 1(房间之间从左到右的直射)。 + +``` +#RSS_anchor1, RSS_anchor2, RSS_anchor3, RSS_anchor4 +-0.90476,-0.48,0.28571,0.3 +-0.57143,-0.32,0.14286,0.3 +-0.38095,-0.28,-0.14286,0.35 +-0.28571,-0.2,-0.47619,0.35 +-0.14286,-0.2,0.14286,-0.2 +-0.14286,-0.2,0.047619,0 +-0.14286,-0.16,-0.38095,0.2 +-0.14286,-0.04,-0.61905,-0.2 +-0.095238,-0.08,0.14286,-0.55 +-0.047619,0.04,-0.095238,0.05 +-0.19048,-0.04,0.095238,0.4 +-0.095238,-0.04,-0.14286,0.35 +-0.33333,-0.08,-0.28571,-0.2 +-0.2381,0.04,0.14286,0.35 +0,0.08,0.14286,0.05 +-0.095238,0.04,0.095238,0.1 +-0.14286,-0.2,0.14286,0.5 +-0.19048,0.04,-0.42857,0.3 +-0.14286,-0.08,-0.2381,0.15 +-0.33333,0.16,-0.14286,-0.8 +-0.42857,0.16,-0.28571,-0.1 +-0.71429,0.16,-0.28571,0.2 +-0.095238,-0.08,0.095238,0.35 +-0.28571,0.04,0.14286,0.2 +0,0.04,0.14286,0.1 +0,0.04,-0.047619,-0.05 +-0.14286,-0.6,-0.28571,-0.1 +``` + +如第一篇论文所述,数据集以两种特定方式(实验设置或 ES)使用,以评估问题的预测模型,指定为 ES1 和 ES2。 + +* **ES1** :合并数据集 1 和 2,将其分为训练(80%)和测试(20%)集以评估模型。 +* **ES2** :合并用作训练集的数据集 1 和 2(66%),数据集 3 用作测试集(34%)以评估模型。 + +ES1 案例评估模型以概括两对已知房间内的移动,即具有已知几何形状的房间。 ES2 案例试图推广从两个房间到第三个看不见的房间的运动:一个更难的问题。 + +2011 年的论文报告了 ES1 上大约 95%的分类准确率和 ES2 大约 89%的表现,经过对一套算法的一些测试后,我非常令人印象深刻。 + +## 加载和探索数据集 + +在本节中,我们将数据加载到内存中并使用摘要和可视化进行探索,以帮助更好地了解问题的建模方式。 + +首先,下载数据集并将下载的存档解压缩到当前工作目录中。 + +### 加载数据集 + +目标,组和路径文件可以直接作为 Pandas DataFrames 加载。 + +``` +# load mapping files +from pandas import read_csv +target_mapping = read_csv('dataset/MovementAAL_target.csv', header=0) +group_mapping = read_csv('groups/MovementAAL_DatasetGroup.csv', header=0) +paths_mapping = read_csv('groups/MovementAAL_Paths.csv', header=0) +``` + +信号强度曲线存储在 _ 数据集/_ 目录中的单独文件中。 + +这些可以通过迭代目录中的所有文件并直接加载序列来加载。因为每个序列都有一个可变长度(可变行数),我们可以为列表中的每个跟踪存储 NumPy 数组。 + +``` +# load sequences and targets into memory +from pandas import read_csv +from os import listdir +sequences = list() +directory = 'dataset' +target_mapping = None +for name in listdir(directory): + filename = directory + '/' + name + if filename.endswith('_target.csv'): + continue + df = read_csv(filename, header=0) + values = df.values + sequences.append(values) +``` + +我们可以将所有这些绑定到一个名为 _load_dataset()_ 的函数中,并将数据加载到内存中。 + +下面列出了完整的示例。 + +``` +# load user movement dataset into memory +from pandas import read_csv +from os import listdir + +# return list of traces, and arrays for targets, groups and paths +def load_dataset(prefix=''): + grps_dir, data_dir = prefix+'groups/', prefix+'dataset/' + # load mapping files + targets = read_csv(data_dir + 'MovementAAL_target.csv', header=0) + groups = read_csv(grps_dir + 'MovementAAL_DatasetGroup.csv', header=0) + paths = read_csv(grps_dir + 'MovementAAL_Paths.csv', header=0) + # load traces + sequences = list() + target_mapping = None + for name in listdir(data_dir): + filename = data_dir + name + if filename.endswith('_target.csv'): + continue + df = read_csv(filename, header=0) + values = df.values + sequences.append(values) + return sequences, targets.values[:,1], groups.values[:,1], paths.values[:,1] + +# load dataset +sequences, targets, groups, paths = load_dataset() +# summarize shape of the loaded data +print(len(sequences), targets.shape, groups.shape, paths.shape) +``` + +运行该示例加载数据并显示已从磁盘正确加载 314 条跟踪及其相关输出(目标为-1 或+1),数据集编号(组为 1,2 或 3)和路径编号(路径为 1) -6)。 + +``` +314 (314,) (314,) (314,) +``` + +### 基本信息 + +我们现在可以仔细查看加载的数据,以更好地理解或确认我们对问题的理解。 + +我们从论文中了解到,数据集在两个类别方面是合理平衡的。我们可以通过总结所有观察的类别细分来证实这一点。 + +``` +# summarize class breakdown +class1,class2 = len(targets[targets==-1]), len(targets[targets==1]) +print('Class=-1: %d %.3f%%' % (class1, class1/len(targets)*100)) +print('Class=+1: %d %.3f%%' % (class2, class2/len(targets)*100)) +``` + +接下来,我们可以通过绘制原始值的直方图来查看四个锚点中每一个的传感器强度值的分布。 + +这要求我们创建一个包含所有观察行的数组,以便我们可以绘制每列的分布。 _vstack()_ NumPy 函数将为我们完成这项工作。 + +``` +# histogram for each anchor point +all_rows = vstack(sequences) +pyplot.figure() +variables = [0, 1, 2, 3] +for v in variables: + pyplot.subplot(len(variables), 1, v+1) + pyplot.hist(all_rows[:, v], bins=20) +pyplot.show() +``` + +最后,另一个有趣的方面是跟踪长度的分布。 + +我们可以使用直方图来总结这种分布。 + +``` +# histogram for trace lengths +trace_lengths = [len(x) for x in sequences] +pyplot.hist(trace_lengths, bins=50) +pyplot.show() +``` + +综合这些,下面列出了加载和汇总数据的完整示例。 + +``` +# summarize simple information about user movement data +from os import listdir +from numpy import array +from numpy import vstack +from pandas import read_csv +from matplotlib import pyplot + +# return list of traces, and arrays for targets, groups and paths +def load_dataset(prefix=''): + grps_dir, data_dir = prefix+'groups/', prefix+'dataset/' + # load mapping files + targets = read_csv(data_dir + 'MovementAAL_target.csv', header=0) + groups = read_csv(grps_dir + 'MovementAAL_DatasetGroup.csv', header=0) + paths = read_csv(grps_dir + 'MovementAAL_Paths.csv', header=0) + # load traces + sequences = list() + target_mapping = None + for name in listdir(data_dir): + filename = data_dir + name + if filename.endswith('_target.csv'): + continue + df = read_csv(filename, header=0) + values = df.values + sequences.append(values) + return sequences, targets.values[:,1], groups.values[:,1], paths.values[:,1] + +# load dataset +sequences, targets, groups, paths = load_dataset() +# summarize class breakdown +class1,class2 = len(targets[targets==-1]), len(targets[targets==1]) +print('Class=-1: %d %.3f%%' % (class1, class1/len(targets)*100)) +print('Class=+1: %d %.3f%%' % (class2, class2/len(targets)*100)) +# histogram for each anchor point +all_rows = vstack(sequences) +pyplot.figure() +variables = [0, 1, 2, 3] +for v in variables: + pyplot.subplot(len(variables), 1, v+1) + pyplot.hist(all_rows[:, v], bins=20) +pyplot.show() +# histogram for trace lengths +trace_lengths = [len(x) for x in sequences] +pyplot.hist(trace_lengths, bins=50) +pyplot.show() +``` + +首先运行该示例总结了观察的类分布。 + +结果证实了我们对完整数据集的期望在两个阶段结果的观察方面几乎完全平衡。 + +``` +Class=-1: 156 49.682% +Class=+1: 158 50.318% +``` + +接下来,创建每个锚点的传感器强度的直方图,总结数据分布。 + +我们可以看到每个变量的分布接近正常,显示出类似高斯的形状。我们也可以看到围绕-1 的观测数据太多。这可能表示可以标记甚至从序列中过滤掉的通用“无强度”观察结果。 + +调查分布是否按路径类型甚至数据集编号更改可能会很有趣。 + +![Histograms for the sensor strength values for each anchor point](img/4214554ca614caed83065545f8ffff38.jpg) + +每个锚点的传感器强度值的直方图 + +最后,创建序列长度的直方图。 + +我们可以看到长度在 25,40 和 60 之间的序列簇。我们还可以看到,如果我们想要修剪长序列,那么最长约 70 个时间步长可能是合适的。最小长度似乎是 19。 + +![Histogram of sensor strength sequence lengths](img/5a3f5d47621916742d5fa05bd4864b88.jpg) + +传感器强度序列长度的直方图 + +### 时间序列图 + +我们正在处理时间序列数据,因此我们实际查看序列的一些示例非常重要。 + +我们可以按路径对轨迹进行分组,并为每条路径绘制一条轨迹的示例。期望不同路径的迹线在某些方面可能看起来不同。 + +``` +# group sequences by paths +paths = [1,2,3,4,5,6] +seq_paths = dict() +for path in paths: + seq_paths[path] = [sequences[j] for j in range(len(paths)) if paths[j]==path] +# plot one example of a trace for each path +pyplot.figure() +for i in paths: + pyplot.subplot(len(paths), 1, i) + # line plot each variable + for j in [0, 1, 2, 3]: + pyplot.plot(seq_paths[i][0][:, j], label='Anchor ' + str(j+1)) + pyplot.title('Path ' + str(i), y=0, loc='left') +pyplot.show() +``` + +我们还可以绘制一个迹线的每个系列以及线性回归模型预测的趋势。这将使该系列中的任何趋势变得明显。 + +我们可以使用 [lstsq()NumPy 函数](https://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.lstsq.html)对给定系列拟合线性回归。 + +下面的函数 _regress()_ 将一系列作为单个变量,通过最小二乘拟合线性回归模型,并预测每个时间步的输出返回捕获数据趋势的序列。 + +``` +# fit a linear regression function and return the predicted values for the series +def regress(y): + # define input as the time step + X = array([i for i in range(len(y))]).reshape(len(y), 1) + # fit linear regression via least squares + b = lstsq(X, y)[0][0] + # predict trend on time step + yhat = b * X[:,0] + return yhat +``` + +我们可以使用该函数绘制单个迹线中每个变量的时间序列的趋势。 + +``` +# plot series for a single trace with trend +seq = sequences[0] +variables = [0, 1, 2, 3] +pyplot.figure() +for i in variables: + pyplot.subplot(len(variables), 1, i+1) + # plot the series + pyplot.plot(seq[:,i]) + # plot the trend + pyplot.plot(regress(seq[:,i])) +pyplot.show() +``` + +将所有这些结合在一起,下面列出了完整的示例。 + +``` +# plot series data +from os import listdir +from numpy import array +from numpy import vstack +from numpy.linalg import lstsq +from pandas import read_csv +from matplotlib import pyplot + +# return list of traces, and arrays for targets, groups and paths +def load_dataset(prefix=''): + grps_dir, data_dir = prefix+'groups/', prefix+'dataset/' + # load mapping files + targets = read_csv(data_dir + 'MovementAAL_target.csv', header=0) + groups = read_csv(grps_dir + 'MovementAAL_DatasetGroup.csv', header=0) + paths = read_csv(grps_dir + 'MovementAAL_Paths.csv', header=0) + # load traces + sequences = list() + target_mapping = None + for name in listdir(data_dir): + filename = data_dir + name + if filename.endswith('_target.csv'): + continue + df = read_csv(filename, header=0) + values = df.values + sequences.append(values) + return sequences, targets.values[:,1], groups.values[:,1], paths.values[:,1] + +# fit a linear regression function and return the predicted values for the series +def regress(y): + # define input as the time step + X = array([i for i in range(len(y))]).reshape(len(y), 1) + # fit linear regression via least squares + b = lstsq(X, y)[0][0] + # predict trend on time step + yhat = b * X[:,0] + return yhat + +# load dataset +sequences, targets, groups, paths = load_dataset() +# group sequences by paths +paths = [1,2,3,4,5,6] +seq_paths = dict() +for path in paths: + seq_paths[path] = [sequences[j] for j in range(len(paths)) if paths[j]==path] +# plot one example of a trace for each path +pyplot.figure() +for i in paths: + pyplot.subplot(len(paths), 1, i) + # line plot each variable + for j in [0, 1, 2, 3]: + pyplot.plot(seq_paths[i][0][:, j], label='Anchor ' + str(j+1)) + pyplot.title('Path ' + str(i), y=0, loc='left') +pyplot.show() +# plot series for a single trace with trend +seq = sequences[0] +variables = [0, 1, 2, 3] +pyplot.figure() +for i in variables: + pyplot.subplot(len(variables), 1, i+1) + # plot the series + pyplot.plot(seq[:,i]) + # plot the trend + pyplot.plot(regress(seq[:,i])) +pyplot.show() +``` + +运行该示例将创建一个包含六个图形的图表,每个图形对应六个路径中的每一个。给定的图显示了单个迹线的线图,其中包含迹线的四个变量,每个锚点一个。 + +也许所选择的迹线代表每条路径,也许不是。 + +我们可以看到一些明显的差异: + +* **随时间变化的变量分组**。成对变量可以组合在一起,或者所有变量可以在给定时间组合在一起。 +* **随时间变化的趋势**。变量聚集在一起向中间或分散到极端。 + +理想情况下,如果行为的这些变化是预测性的,则预测模型必须提取这些特征,或者将这些特征的摘要作为输入呈现。 + +![Line plots of one trace (4 variables) for each of the six paths](img/8d6de0d5780ec7a20d08928deef2bf04.jpg) + +六条路径中每条路径的一条迹线(4 个变量)的线图。 + +创建第二个图,显示单个迹线中四个系列的线图以及趋势线。 + +我们可以看到,至少对于这种迹线,当用户在环境中移动时,传感器强度数据有明显的趋势。这可能表明有机会在建模之前使数据静止或使用迹线中的每个系列的趋势(观察或系数)作为预测模型的输入。 + +![Line plots for the time series in a single trace with trend lines](img/a03973e2ec9d4b0a7f8dcbe33228abae.jpg) + +具有趋势线的单个迹线中的时间序列的线图 + +## 模型评估 + +有许多方法可以拟合和评估此数据的模型。 + +鉴于类的平衡,分类准确性似乎是一个良好的首先评估指标。通过预测概率和探索 ROC 曲线上的阈值,可以在将来寻求更多的细微差别。 + +我看到使用这些数据的两个主要主题: + +* **同房**:一个房间里的痕迹训练的模型可以预测那个房间里新痕迹的结果吗? +* **不同的房间**:一个或两个房间的痕迹训练模型可以预测不同房间的新痕迹的结果吗? + +本文中描述并总结的 ES1 和 ES2 案例探讨了这些问题,并提供了一个有用的起点。 + +首先,我们必须将加载的跟踪和目标分成三组。 + +``` +# separate traces +seq1 = [sequences[i] for i in range(len(groups)) if groups[i]==1] +seq2 = [sequences[i] for i in range(len(groups)) if groups[i]==2] +seq3 = [sequences[i] for i in range(len(groups)) if groups[i]==3] +print(len(seq1),len(seq2),len(seq3)) +# separate target +targets1 = [targets[i] for i in range(len(groups)) if groups[i]==1] +targets2 = [targets[i] for i in range(len(groups)) if groups[i]==2] +targets3 = [targets[i] for i in range(len(groups)) if groups[i]==3] +print(len(targets1),len(targets2),len(targets3)) +``` + +在 ES1 的情况下,我们可以使用 k 折交叉验证,其中 k = 5,使用与论文相同的比率,并且重复评估为评估提供了一些稳健性。 + +我们可以使用 scikit-learn 中的 [cross_val_score()函数](http://scikit-learn.org/stable/modules/generated/sklearn.model_selection.cross_val_score.html)来评估模型,然后计算得分的均值和标准差。 + +``` +# evaluate model for ES1 +from numpy import mean +from numpy import std +from sklearn.model_selection import cross_val_score +... +scores = cross_val_score(model, X, y, scoring='accuracy', cv=5, n_jobs=-1) +m, s = mean(scores), std(scores) +``` + +在 ES2 的情况下,我们可以将模型拟合到数据集 1 和 2 上,并直接测试数据集 3 上的模型技能。 + +## 数据准备 + +输入数据如何为预测问题构建灵活性。 + +我想到了两种方法: + +* **自动特征学习**。深度神经网络能够自动进行特征学习,而循环神经网络可以直接支持多变量多步输入数据。可以使用循环神经网络,例如 LSTM 或 1D CNN。可以将序列填充为相同的长度,例如 70 个时间步长,并且可以使用掩蔽层来忽略填充的时间步长。 +* **特色工程**。或者,可变长度序列可以概括为单个固定长度向量,并提供给标准机器学习模型用于预测。这需要仔细的特征工程,以便为模型提供足够的跟踪描述,以学习到输出类的映射。 + +两者都是有趣的方法。 + +作为第一步,我们将通过手动特征工程准备更传统的固定长度向量输入。 + +以下是有关可以包含在向量中的功能的一些想法: + +* 变量的第一个,中间或最后 n 个观测值。 +* 变量的第一个,中间或最后 n 个观测值的平均值或标准差。 +* 最后和第 n 个观察结果之间的差异 +* 对变量的第一,中或最后 n 个观察值的差异。 +* 变量的所有,第一,中间或最后 n 个观测值的线性回归系数。 +* 线性回归预测变量的第一,中间或最后 n 个观测值的趋势。 + +此外,原始值可能不需要数据缩放,因为数据已经缩放到-1 到 1 的范围。如果添加了不同单位的新功能,则可能需要缩放。 + +一些变量确实显示出一些趋势,这表明可能差异变量可能有助于梳理信号。 + +每个变量的分布接近高斯分布,因此一些算法可能会受益于标准化,甚至可能是 Box-Cox 变换。 + +## 算法抽查 + +在本节中,我们将对具有不同工程特性集的一套标准机器学习算法的默认配置进行抽查。 + +现场检查是一种有用的技术,可快速清除输入和输出之间的映射是否有任何信号需要学习,因为大多数测试方法都会提取一些东西。该方法还可以提出可能值得进一步研究的方法。 + +缺点是每种方法都没有给出它最好的机会(配置)以显示它可以对问题做什么,这意味着任何进一步研究的方法都会受到第一批结果的偏见。 + +在这些测试中,我们将研究一套六种不同类型的算法,具体来说: + +* Logistic 回归。 +* k-最近邻居。 +* 决策树。 +* 支持向量机。 +* 随机森林。 +* 梯度增压机。 + +我们将在关注时间序列变量末尾的特征上测试这些方法的默认配置,因为它们可能最能预测房间转换是否会发生。 + +### 最后 _n_ 观察 + +最后 _n_ 观察结果可能预示着运动是否会导致房间过渡。 + +跟踪数据中最小的时间步长为 19,因此,我们将使用 _n = 19_ 作为起点。 + +下面名为 _create_dataset()_ 的函数将使用平面一维向量中每条迹线的最后 _n_ 观测值创建一个固定长度向量,然后将目标添加为最后一个元素向量。 + +简单的机器学习算法需要对跟踪数据进行扁平化。 + +``` +# create a fixed 1d vector for each trace with output variable +def create_dataset(sequences, targets): + # create the transformed dataset + transformed = list() + n_vars = 4 + n_steps = 19 + # process each trace in turn + for i in range(len(sequences)): + seq = sequences[i] + vector = list() + # last n observations + for row in range(1, n_steps+1): + for col in range(n_vars): + vector.append(seq[-row, col]) + # add output + vector.append(targets[i]) + # store + transformed.append(vector) + # prepare array + transformed = array(transformed) + transformed = transformed.astype('float32') + return transformed +``` + +我们可以像以前一样加载数据集,并将其分类到数据集 1,2 和 3 中,如“_ 模型评估 _”部分所述。 + +然后我们可以调用 _create_dataset()_ 函数来创建 ES1 和 ES2 案例所需的数据集,特别是 ES1 组合数据集 1 和 2,而 ES2 使用数据集 1 和 2 作为训练集,数据集 3 作为测试集。 + +下面列出了完整的示例。 + +``` +# prepare fixed length vector dataset +from os import listdir +from numpy import array +from numpy import savetxt +from pandas import read_csv + +# return list of traces, and arrays for targets, groups and paths +def load_dataset(prefix=''): + grps_dir, data_dir = prefix+'groups/', prefix+'dataset/' + # load mapping files + targets = read_csv(data_dir + 'MovementAAL_target.csv', header=0) + groups = read_csv(grps_dir + 'MovementAAL_DatasetGroup.csv', header=0) + paths = read_csv(grps_dir + 'MovementAAL_Paths.csv', header=0) + # load traces + sequences = list() + target_mapping = None + for name in listdir(data_dir): + filename = data_dir + name + if filename.endswith('_target.csv'): + continue + df = read_csv(filename, header=0) + values = df.values + sequences.append(values) + return sequences, targets.values[:,1], groups.values[:,1], paths.values[:,1] + +# create a fixed 1d vector for each trace with output variable +def create_dataset(sequences, targets): + # create the transformed dataset + transformed = list() + n_vars = 4 + n_steps = 19 + # process each trace in turn + for i in range(len(sequences)): + seq = sequences[i] + vector = list() + # last n observations + for row in range(1, n_steps+1): + for col in range(n_vars): + vector.append(seq[-row, col]) + # add output + vector.append(targets[i]) + # store + transformed.append(vector) + # prepare array + transformed = array(transformed) + transformed = transformed.astype('float32') + return transformed + +# load dataset +sequences, targets, groups, paths = load_dataset() +# separate traces +seq1 = [sequences[i] for i in range(len(groups)) if groups[i]==1] +seq2 = [sequences[i] for i in range(len(groups)) if groups[i]==2] +seq3 = [sequences[i] for i in range(len(groups)) if groups[i]==3] +# separate target +targets1 = [targets[i] for i in range(len(groups)) if groups[i]==1] +targets2 = [targets[i] for i in range(len(groups)) if groups[i]==2] +targets3 = [targets[i] for i in range(len(groups)) if groups[i]==3] +# create ES1 dataset +es1 = create_dataset(seq1+seq2, targets1+targets2) +print('ES1: %s' % str(es1.shape)) +savetxt('es1.csv', es1, delimiter=',') +# create ES2 dataset +es2_train = create_dataset(seq1+seq2, targets1+targets2) +es2_test = create_dataset(seq3, targets3) +print('ES2 Train: %s' % str(es2_train.shape)) +print('ES2 Test: %s' % str(es2_test.shape)) +savetxt('es2_train.csv', es2_train, delimiter=',') +savetxt('es2_test.csv', es2_test, delimiter=',') +``` + +运行该示例创建三个新的 CSV 文件,特别是' _es1.csv_ ',' _es2_train.csv_ '和' _es2_test.csv_ '用于 ES1 和分别为 ES2 病例。 + +还总结了这些数据集的形状。 + +``` +ES1: (210, 77) +ES2 Train: (210, 77) +ES2 Test: (104, 77) +``` + +接下来,我们可以评估 ES1 数据集上的模型。 + +经过一些测试后,似乎标准化数据集会为那些依赖距离值(KNN 和 SVM)的方法带来更好的模型技能,并且通常对其他方法没有影响。因此,使用管道来评估首先标准化数据集的每个算法。 + +下面列出了新数据集上的抽样检查算法的完整示例。 + +``` +# spot check for ES1 +from numpy import mean +from numpy import std +from pandas import read_csv +from matplotlib import pyplot +from sklearn.model_selection import cross_val_score +from sklearn.pipeline import Pipeline +from sklearn.preprocessing import StandardScaler +from sklearn.linear_model import LogisticRegression +from sklearn.neighbors import KNeighborsClassifier +from sklearn.tree import DecisionTreeClassifier +from sklearn.svm import SVC +from sklearn.ensemble import RandomForestClassifier +from sklearn.ensemble import GradientBoostingClassifier +# load dataset +dataset = read_csv('es1.csv', header=None) +# split into inputs and outputs +values = dataset.values +X, y = values[:, :-1], values[:, -1] +# create a list of models to evaluate +models, names = list(), list() +# logistic +models.append(LogisticRegression()) +names.append('LR') +# knn +models.append(KNeighborsClassifier()) +names.append('KNN') +# cart +models.append(DecisionTreeClassifier()) +names.append('CART') +# svm +models.append(SVC()) +names.append('SVM') +# random forest +models.append(RandomForestClassifier()) +names.append('RF') +# gbm +models.append(GradientBoostingClassifier()) +names.append('GBM') +# evaluate models +all_scores = list() +for i in range(len(models)): + # create a pipeline for the model + s = StandardScaler() + p = Pipeline(steps=[('s',s), ('m',models[i])]) + scores = cross_val_score(p, X, y, scoring='accuracy', cv=5, n_jobs=-1) + all_scores.append(scores) + # summarize + m, s = mean(scores)*100, std(scores)*100 + print('%s %.3f%% +/-%.3f' % (names[i], m, s)) +# plot +pyplot.boxplot(all_scores, labels=names) +pyplot.show() +``` + +运行该示例打印每个算法的估计表现,包括超过 5 倍交叉验证的平均值和标准差。 + +结果表明 SVM 可能值得以 58%的准确度更详细地查看。 + +``` +LR 55.285% +/-5.518 +KNN 50.897% +/-5.310 +CART 50.501% +/-10.922 +SVM 58.551% +/-7.707 +RF 50.442% +/-6.355 +GBM 55.749% +/-5.423 +``` + +结果也显示为显示分数分布的盒须图。 + +同样,SVM 似乎具有良好的平均表现和紧密的方差。 + +![Spot-check Algorithms on ES1 with last 19 observations](img/0159f35c1136f9a0b272a8e110ac3c82.jpg) + +使用最近 19 次观察在 ES1 上进行抽样检查算法 + +### 最后 _n_ 使用填充进行观察 + +我们可以将每条迹线填充到固定长度。 + +这将提供在每个序列中包括更多先前 _n_ 观察结果的灵活性。 _n_ 的选择也必须与添加到较短序列的填充值的增加相平衡,这反过来可能对模型在这些序列上的表现产生负面影响。 + +我们可以通过将 0.0 值添加到每个变量序列的开头来填充每个序列,直到最大长度,例如,达到了 200 个时间步长。我们可以使用 [pad()NumPy 函数](https://docs.scipy.org/doc/numpy/reference/generated/numpy.pad.html)来完成此操作。 + +``` +from numpy import pad +... +# pad sequences +max_length = 200 +seq = pad(seq, ((max_length-len(seq),0),(0,0)), 'constant', constant_values=(0.0)) +``` + +具有填充支持的 _create_dataset()_ 功能的更新版本如下。 + +我们将尝试 _n = 25_ 以包括每个载体中每个序列中的 25 个最后观察结果。虽然您可能想要探索其他配置是否会带来更好的技能,但可以通过一些试验和错误找到此值。 + +``` +# create a fixed 1d vector for each trace with output variable +def create_dataset(sequences, targets): + # create the transformed dataset + transformed = list() + n_vars, n_steps, max_length = 4, 25, 200 + # process each trace in turn + for i in range(len(sequences)): + seq = sequences[i] + # pad sequences + seq = pad(seq, ((max_length-len(seq),0),(0,0)), 'constant', constant_values=(0.0)) + vector = list() + # last n observations + for row in range(1, n_steps+1): + for col in range(n_vars): + vector.append(seq[-row, col]) + # add output + vector.append(targets[i]) + # store + transformed.append(vector) + # prepare array + transformed = array(transformed) + transformed = transformed.astype('float32') + return transformed +``` + +使用新功能再次运行脚本会创建更新的 CSV 文件。 + +``` +ES1: (210, 101) +ES2 Train: (210, 101) +ES2 Test: (104, 101) +``` + +同样,重新运行数据上的抽样检查脚本会导致 SVM 模型技能的小幅提升,并且还表明 KNN 可能值得进一步调查。 + +``` +LR 54.344% +/-6.195 +KNN 58.562% +/-4.456 +CART 52.837% +/-7.650 +SVM 59.515% +/-6.054 +RF 50.396% +/-7.069 +GBM 50.873% +/-5.416 +``` + +KNN 和 SVM 的箱形图显示出良好的表现和相对紧密的标准偏差。 + +![Spot-check Algorithms on ES1 with last 25 observations](img/45d5a066240e2c0fb8d533810540a6b4.jpg) + +使用最近 25 次观察对 ES1 上的抽样检查算法 + +我们可以更新点检查到网格搜索 KNN 算法的一组 k 值,看看是否可以通过一点调整进一步改进模型的技能。 + +下面列出了完整的示例。 + +``` +# spot check for ES1 +from numpy import mean +from numpy import std +from pandas import read_csv +from matplotlib import pyplot +from sklearn.model_selection import cross_val_score +from sklearn.neighbors import KNeighborsClassifier +from sklearn.pipeline import Pipeline +from sklearn.preprocessing import StandardScaler + +# load dataset +dataset = read_csv('es1.csv', header=None) +# split into inputs and outputs +values = dataset.values +X, y = values[:, :-1], values[:, -1] +# try a range of k values +all_scores, names = list(), list() +for k in range(1,22): + # evaluate + scaler = StandardScaler() + model = KNeighborsClassifier(n_neighbors=k) + pipeline = Pipeline(steps=[('s',scaler), ('m',model)]) + names.append(str(k)) + scores = cross_val_score(pipeline, X, y, scoring='accuracy', cv=5, n_jobs=-1) + all_scores.append(scores) + # summarize + m, s = mean(scores)*100, std(scores)*100 + print('k=%d %.3f%% +/-%.3f' % (k, m, s)) +# plot +pyplot.boxplot(all_scores, labels=names) +pyplot.show() +``` + +运行该示例打印精度的均值和标准差,k 值从 1 到 21。 + +我们可以看到 _k = 7_ 导致最佳技能为 62.872%。 + +``` +k=1 49.534% +/-4.407 +k=2 49.489% +/-4.201 +k=3 56.599% +/-6.923 +k=4 55.660% +/-6.600 +k=5 58.562% +/-4.456 +k=6 59.991% +/-7.901 +k=7 62.872% +/-8.261 +k=8 59.538% +/-5.528 +k=9 57.633% +/-4.723 +k=10 59.074% +/-7.164 +k=11 58.097% +/-7.583 +k=12 58.097% +/-5.294 +k=13 57.179% +/-5.101 +k=14 57.644% +/-3.175 +k=15 59.572% +/-5.481 +k=16 59.038% +/-1.881 +k=17 59.027% +/-2.981 +k=18 60.490% +/-3.368 +k=19 60.014% +/-2.497 +k=20 58.562% +/-2.018 +k=21 58.131% +/-3.084 +``` + +_k_ 值的准确度得分的框和胡须图显示, _k_ 值约为 7,例如 5 和 6,也在数据集上产生稳定且表现良好的模型。 + +![Spot-check KNN neighbors on ES1 with last 25 observations](img/56873fdcfb7768a4e912b6753aa854ce.jpg) + +通过最后 25 次观察,对 ES1 上的 KNN 邻居进行抽查 + +### 在 ES2 上评估 KNN + +现在我们已经了解了一个表示( _n = 25_ )和一个模型(KNN, _k = 7_ ),它们具有一定的随机预测技能,我们可以测试该方法在更难的 ES2 数据集上。 + +每个模型都在数据集 1 和 2 的组合上进行训练,然后在数据集 3 上进行评估。不使用 k 折交叉验证程序,因此我们希望得分是有噪声的。 + +下面列出了 ES2 算法的完整抽样检查。 + +``` +# spot check for ES2 +from pandas import read_csv +from matplotlib import pyplot +from sklearn.metrics import accuracy_score +from sklearn.linear_model import LogisticRegression +from sklearn.neighbors import KNeighborsClassifier +from sklearn.tree import DecisionTreeClassifier +from sklearn.svm import SVC +from sklearn.ensemble import RandomForestClassifier +from sklearn.ensemble import GradientBoostingClassifier +from sklearn.pipeline import Pipeline +from sklearn.preprocessing import StandardScaler +# load dataset +train = read_csv('es2_train.csv', header=None) +test = read_csv('es2_test.csv', header=None) +# split into inputs and outputs +trainX, trainy = train.values[:, :-1], train.values[:, -1] +testX, testy = test.values[:, :-1], test.values[:, -1] +# create a list of models to evaluate +models, names = list(), list() +# logistic +models.append(LogisticRegression()) +names.append('LR') +# knn +models.append(KNeighborsClassifier()) +names.append('KNN') +# knn +models.append(KNeighborsClassifier(n_neighbors=7)) +names.append('KNN-7') +# cart +models.append(DecisionTreeClassifier()) +names.append('CART') +# svm +models.append(SVC()) +names.append('SVM') +# random forest +models.append(RandomForestClassifier()) +names.append('RF') +# gbm +models.append(GradientBoostingClassifier()) +names.append('GBM') +# evaluate models +all_scores = list() +for i in range(len(models)): + # create a pipeline for the model + scaler = StandardScaler() + model = Pipeline(steps=[('s',scaler), ('m',models[i])]) + # fit + # model = models[i] + model.fit(trainX, trainy) + # predict + yhat = model.predict(testX) + # evaluate + score = accuracy_score(testy, yhat) * 100 + all_scores.append(score) + # summarize + print('%s %.3f%%' % (names[i], score)) +# plot +pyplot.bar(names, all_scores) +pyplot.show() +``` + +运行该示例报告 ES2 方案的模型准确性。 + +我们可以看到 KNN 表现良好,并且发现在 ES1 上表现良好的七个邻居的 KNN 在 ES2 上也表现良好。 + +``` +LR 45.192% +KNN 54.808% +KNN-7 57.692% +CART 53.846% +SVM 51.923% +RF 53.846% +GBM 52.885% +``` + +精度分数的条形图有助于使方法之间的表现相对差异更加清晰。 + +![Bar chart of model accuracy on ES2](img/b3ecbb4b2558f0998da0419e4426ae26.jpg) + +ES2 上模型精度的条形图 + +所选择的表示和模型配置确实具有超过预测的技能,准确度为 50%。 + +进一步调整可能会使模型具有更好的技能,我们距离 ES1 和 ES2 分别报告的 95%和 89%准确度还有很长的路要走。 + +### 扩展 + +本节列出了一些扩展您可能希望探索的教程的想法。 + +* **数据准备**。有很多机会可以探索更多的数据准备方法,例如归一化,差分和功率变换。 +* **特色工程**。进一步的特征工程可以产生更好的表现模型,例如每个序列的开始,中间和结束的统计以及趋势信息。 +* **调整**。只有 KNN 算法才有机会进行调整;梯度增强等其他模型可以从超参数的微调中受益。 +* **RNNs** 。该序列分类任务似乎非常适合于循环神经网络,例如支持可变长度多变量输入的 LSTM。对该数据集进行的一些初步测试(由我自己)显示出非常不稳定的结果,但更广泛的调查可能会给出更好甚至更好的结果。 + +如果你探索任何这些扩展,我很想知道。 + +## 进一步阅读 + +如果您希望深入了解,本节将提供有关该主题的更多资源。 + +### 文件 + +* [通过油藏计算预测用户在异质室内环境中的移动](https://pdfs.semanticscholar.org/40c2/393e1874c3fd961fdfff02402c24ccf1c3d7.pdf#page=13),2011。 +* [环境辅助生活应用中储层计算的实验表征](https://link.springer.com/article/10.1007/s00521-013-1364-4),2014。 + +### API + +* [sklearn.model_selection.cross_val_score API](http://scikit-learn.org/stable/modules/generated/sklearn.model_selection.cross_val_score.html) +* [numpy.linalg.lstsq API](https://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.lstsq.html) +* [numpy.pad API](https://docs.scipy.org/doc/numpy/reference/generated/numpy.pad.html) + +### 用品 + +* [来自 RSS 数据集的室内用户移动预测,UCI 机器学习库](https://archive.ics.uci.edu/ml/datasets/Indoor+User+Movement+Prediction+from+RSS+data) +* [通过油藏计算预测非均质室内环境中的用户移动,Paolo Barsocchi 主页](http://wnlab.isti.cnr.it/paolo/index.php/dataset/6rooms)。 +* [来自 RSS 数据集](https://github.com/Laurae2/Indoor_Prediction)的室内用户移动预测,Laurae [法语]。 + +## 摘要 + +在本教程中,您发现了室内运动预测时间序列分类问题以及如何设计功能并评估问题的机器学习算法。 + +具体来说,你学到了: + +* 基于传感器强度预测房间运动的时间序列分类问题。 +* 如何调查数据以便更好地理解问题以及如何从原始数据中设计特征以进行预测建模。 +* 如何检查一套分类算法并调整一种算法以进一步提高问题的表现。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/instability-online-learning-stateful-lstm-time-series-forecasting.md b/docs/dl-ts/instability-online-learning-stateful-lstm-time-series-forecasting.md new file mode 100644 index 0000000000000000000000000000000000000000..cfe71004f0a288d34d49d2efc1e84b0e7fc5a803 --- /dev/null +++ b/docs/dl-ts/instability-online-learning-stateful-lstm-time-series-forecasting.md @@ -0,0 +1,497 @@ +# 用于时间序列预测的状态 LSTM 在线学习的不稳定性 + +> 原文: [https://machinelearningmastery.com/instability-online-learning-stateful-lstm-time-series-forecasting/](https://machinelearningmastery.com/instability-online-learning-stateful-lstm-time-series-forecasting/) + +一些神经网络配置可能导致模型不稳定。 + +这可能会使用描述性统计信息难以表征和比较同一问题的其他模型配置。 + +看似不稳定模型的一个很好的例子是使用有状态长短期记忆(LSTM)模型的在线学习(批量大小为 1)。 + +在本教程中,您将了解如何使用在线学习标准时间序列预测问题来探索有状态 LSTM 拟合的结果。 + +完成本教程后,您将了解: + +* 如何设计一个强大的测试工具来评估 LSTM 模型的时间序列预测问题。 +* 如何分析结果的总体,包括摘要统计,传播和结果分布。 +* 如何分析增加实验重复次数的影响。 + +让我们开始吧。 + +![Instability of Online Learning for Stateful LSTM for Time Series Forecasting](img/88725ddef4e40866ae97294670c56f11.jpg) + +用于时间序列预测的有状态 LSTM 在线学习的不稳定性 +[Magnus Brath](https://www.flickr.com/photos/magnusbrath/5396568150/) 的照片,保留一些权利。 + +## 模型不稳定 + +当您在同一数据上多次训练相同的网络时,您可能会得到截然不同的结果。 + +这是因为神经网络是随机初始化的,并且它们如何适合训练数据的优化性质可导致网络内的不同最终权重。在给定相同输入数据的情况下,这些不同的网络可以反过来导致变化的预测。 + +因此,重复在神经网络上重复任何实验以找到平均预期表现是很重要的。 + +有关神经网络等机器学习算法的随机性的更多信息,请参阅帖子: + +* [在机器学习中拥抱随机性](http://machinelearningmastery.com/randomness-in-machine-learning/) + +神经网络中的批量大小定义了在暴露于训练数据集的情况下网络中权重的更新频率。 + +批量大小为 1 意味着在每行训练数据之后更新网络权重。这称为在线学习。结果是一个可以快速学习的网络,但配置可能非常不稳定。 + +在本教程中,我们将探讨用于时间序列预测的有状态 LSTM 配置的在线学习的不稳定性。 + +我们将通过查看 LSTM 配置在标准时间序列预测问题上的平均表现来探索这一问题,该问题涉及可变数量的实验重复。 + +也就是说,我们将多次在相同数据上重新训练相同的模型配置,并在保留数据集上查看模型的表现,并查看模型的不稳定性。 + +## 教程概述 + +本教程分为 6 个部分。他们是: + +1. 洗发水销售数据集 +2. 实验测试线束 +3. 代码和收集结果 +4. 结果基本情况 +5. 重复与测试 RMSE +6. 审查结果 + +### 环境 + +本教程假定您已安装 Python SciPy 环境。您可以在此示例中使用 Python 2 或 3。 + +本教程假设您安装了 TensorFlow 或 Theano 后端的 Keras v2.0 或更高版本。 + +本教程还假设您安装了 scikit-learn,Pandas,NumPy 和 Matplotlib。 + +接下来,让我们看看标准时间序列预测问题,我们可以将其用作此实验的上下文。 + +如果您在设置 Python 环境时需要帮助,请参阅以下帖子: + +* [如何使用 Anaconda 设置用于机器学习和深度学习的 Python 环境](http://machinelearningmastery.com/setup-python-environment-machine-learning-deep-learning-anaconda/) + +## 洗发水销售数据集 + +该数据集描述了 3 年期间每月洗发水的销售数量。 + +单位是销售计数,有 36 个观察。原始数据集归功于 Makridakis,Wheelwright 和 Hyndman(1998)。 + +[您可以在此处下载并了解有关数据集的更多信息](https://datamarket.com/data/set/22r0/sales-of-shampoo-over-a-three-year-period)。 + +下面的示例加载并创建已加载数据集的图。 + +``` +# 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](img/ab56d09b72803271b91fa5bd0ccc3f0f.jpg) + +洗发水销售数据集的线图 + +接下来,我们将了解实验中使用的 LSTM 配置和测试工具。 + +## 实验测试线束 + +本节介绍本教程中使用的测试工具。 + +### 数据拆分 + +我们将 Shampoo Sales 数据集分为两部分:训练和测试集。 + +前两年的数据将用于训练数据集,剩余的一年数据将用于测试集。 + +将使用训练数据集开发模型,并对测试数据集进行预测。 + +测试数据集的持久性预测(天真预测)实现了每月洗发水销售 136.761 的错误。这在测试集上提供了较低的可接受表现限制。 + +### 模型评估 + +将使用滚动预测场景,也称为前进模型验证。 + +测试数据集的每个时间步骤将一次一个地走。将使用模型对时间步长进行预测,然后将获取测试集的实际预期值,并使其可用于下一时间步的预测模型。 + +这模仿了一个真实世界的场景,每个月都会有新的洗发水销售观察结果,并用于下个月的预测。 + +这将通过列车和测试数据集的结构进行模拟。 + +将收集关于测试数据集的所有预测,并计算错误分数以总结模型的技能。将使用均方根误差(RMSE),因为它会对大错误进行处罚,并产生与预测数据相同的分数,即每月洗发水销售额。 + +### 数据准备 + +在我们将 LSTM 模型拟合到数据集之前,我们必须转换数据。 + +在拟合模型和进行预测之前,对数据集执行以下三个数据变换。 + +1. **转换时间序列数据,使其静止**。具体而言,滞后= 1 差分以消除数据中的增加趋势。 +2. **将时间序列转换为监督学习问题**。具体而言,将数据组织成输入和输出模式,其中前一时间步的观察被用作预测当前时间步的观察的输入 +3. **将观察结果转换为具有特定比例**。具体而言,要将数据重新调整为-1 到 1 之间的值,以满足 LSTM 模型的默认双曲正切激活函数。 + +这些变换在预测时反转,在计算和误差分数之前将它们恢复到原始比例。 + +### LSTM 模型 + +我们将使用基础状态 LSTM 模型,其中 1 个神经元适合 1000 个时期。 + +批量大小为 1 是必需的,因为我们将使用前向验证并对最后 12 个月的测试数据进行一步预测。 + +批量大小为 1 意味着该模型将使用在线训练(而不是批量训练或小批量训练)。因此,预计模型拟合将具有一些变化。 + +理想情况下,将使用更多的训练时期(例如 1500),但这被截断为 1000 以保持运行时间合理。 + +使用有效的 ADAM 优化算法和均方误差损失函数来拟合模型。 + +### 实验运行 + +每个实验场景将运行 100 次,并且测试集上的 RMSE 得分将从每次运行结束时记录。 + +所有测试 RMSE 分数都写入文件以供以后分析。 + +让我们深入研究实验。 + +## 代码和收集结果 + +完整的代码清单如下。 + +在现代硬件上运行可能需要几个小时。 + +``` +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): + # 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[1:,:] + # 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, 1000, 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 = 100 + results = DataFrame() + # run experiment + results['results'] = experiment(repeats, series) + # summarize results + print(results.describe()) + # save results + results.to_csv('experiment_stateful.csv', index=False) + + # entry point +run() +``` + +运行实验会在测试数据集中保存拟合模型的 RMSE 分数。 + +结果保存到文件“ _experiment_stateful.csv_ ”。 + +下面提供了截断的结果列表。 + +自己重新运行实验可能会给出不同的结果,因为我们没有为随机数生成器播种。 + +``` +... +116.39769471284067 +105.0459745537738 +93.57827109861229 +128.973001927212 +97.02915084460737 +198.56877142225886 +113.09568645243242 +97.84127724751188 +124.60413895331735 +111.62139008607713 +``` + +## 结果基本情况 + +我们可以从计算 100 个测试 RMSE 分数的整个人口的一些基本统计数据开始。 + +通常,我们希望机器学习结果具有高斯分布。这允许我们报告模型的平均值和标准偏差,并在对看不见的数据进行预测时指示模型的置信区间。 + +下面的代码段会加载结果文件并计算一些描述性统计信息。 + +``` +from pandas import DataFrame +from pandas import read_csv +from numpy import mean +from numpy import std +from matplotlib import pyplot +# load results file +results = read_csv('experiment_stateful.csv', header=0) +# descriptive stats +print(results.describe()) +# box and whisker plot +results.boxplot() +pyplot.show() +``` + +运行该示例将从结果中打印描述性统计信息。 + +我们可以看到,平均而言,该配置实现了约 107 个月洗发水销售的 RMSE,标准偏差约为 17。 + +我们还可以看到观察到的最佳测试 RMSE 大约是 90 个销售,而更差的是不到 200 个,这是相当多的分数。 + +``` + results +count 100.000000 +mean 107.051146 +std 17.694512 +min 90.407323 +25% 96.630800 +50% 102.603908 +75% 111.199574 +max 198.568771 +``` + +为了更好地了解数据的传播,还创建了一个盒子和胡须图。 + +该图显示了中位数(绿线),中间 50%的数据(框)和异常值(点)。我们可以看到数据差异很大,导致 RMSE 评分不佳。 + +![Box and Whisker Plot of 100 Test RMSE Scores on the Shampoo Sales Dataset](img/497efce0a73106038dad8e0f4cb3ecc8.jpg) + +在洗发水销售数据集上的 100 个测试 RMSE 分数的盒子和晶须图 + +还创建原始结果值的直方图。 + +该图表示倾斜甚至是指数分布,其质量在 RMSE 为 100 左右,长尾导向 RMSE 为 200。 + +结果的分布显然不是高斯分布。这是不幸的,因为平均值和标准偏差不能直接用于估计模型的置信区间(例如 [95%置信度为平均值](https://en.wikipedia.org/wiki/68%E2%80%9395%E2%80%9399.7_rule)附近标准偏差的 2 倍)。 + +偏态分布还强调,中位数(第 50 百分位数)将是更好的集中趋势,而不是使用这些结果的均值。对于异常结果,中位数应该比平均值更稳健。 + +![Histogram of Test RMSE Scores on Shampoo Sales Dataset](img/42e521ff516042804a46d81ac9f15db1.jpg) + +洗发水销售数据集中测试 RMSE 评分的直方图 + +## 重复与测试 RMSE + +我们可以开始研究实验的摘要统计数据如何随着重复次数从 1 增加到 100 而改变。 + +我们可以累积测试 RMSE 分数并计算描述性统计数据。例如,来自一次重复的得分,来自第一次和第二次重复的得分,来自前三次重复的得分,等等至 100 次重复。 + +我们可以回顾一下中心趋势如何随着线图的重复次数的增加而变化。我们将看看均值和中位数。 + +一般来说,我们预计随着实验重复次数的增加,分布将越来越好地与基础分布相匹配,包括中心趋势,如均值。 + +完整的代码清单如下。 + +``` +from pandas import DataFrame +from pandas import read_csv +from numpy import median +from numpy import mean +from matplotlib import pyplot +import numpy +# load results file +results = read_csv('experiment_stateful.csv', header=0) +values = results.values +# collect cumulative stats +medians, means = list(), list() +for i in range(1,len(values)+1): + data = values[0:i, 0] + mean_rmse, median_rmse = mean(data), median(data) + means.append(mean_rmse) + medians.append(median_rmse) + print(i, mean_rmse, median_rmse) +# line plot of cumulative values +line1, = pyplot.plot(medians, label='Median RMSE') +line2, = pyplot.plot(means, label='Mean RMSE') +pyplot.legend(handles=[line1, line2]) +pyplot.show() +``` + +随着重复次数的增加,打印分布的累积大小,平均值和中值。截断的输出如下所示。 + +``` +... +90 105.759546832 101.477640071 +91 105.876449555 102.384620485 +92 105.867422653 102.458057114 +93 105.735281239 102.384620485 +94 105.982491033 102.458057114 +95 105.888245347 102.384620485 +96 106.853667494 102.458057114 +97 106.918018205 102.531493742 +98 106.825398399 102.458057114 +99 107.004981637 102.531493742 +100 107.051145721 102.603907965 +``` + +还创建了线图,显示随着重复次数的增加,平均值和中位数如何变化。 + +结果表明,正如预期的那样,平均值受异常值结果的影响大于中位数。 + +我们可以看到中位数在 99-100 附近看起来相当稳定。在情节结束时跳跃到 102,表明在后来的重复中出现了一系列较差的 RMSE 分数。 + +![Line Plots of Mean and Median Test RMSE vs Number of Repeats](img/bad7a0703d9d3cdaedb9e0861421f92d.jpg) + +平均值和中值测试 RMSE 与重复次数的线图 + +## 审查结果 + +我们根据标准时间序列预测问题对有状态 LSTM 的 100 次重复进行了一些有用的观察。 + +特别: + +* 我们观察到结果的分布不是高斯分布。它可能是倾斜的高斯分布或具有长尾和异常值的指数分布。 +* 我们观察到结果的分布不随着重复从 1 增加到 100 而稳定。 + +观察结果表明了一些重要的特性: + +* LSTM 和问题的在线学习的选择导致相对不稳定的模型。 +* 所选择的重复次数(100)可能不足以表征模型的行为。 + +这是一个有用的发现,因为从 100 次或更少的实验重复中对模型做出有力的结论是错误的。 + +在描述您自己的机器学习结果时,这是一个需要考虑的重要注意事项。 + +这表明该实验有一些扩展,例如: + +* 探索重复次数对更稳定模型的影响,例如使用批量或小批量学习的模型。 +* 将重复次数增加到数千或更多,以试图通过在线学习解释模型的一般不稳定性。 + +## 摘要 + +在本教程中,您了解了如何使用在线学习分析 LSTM 模型的实验结果。 + +你了解到: + +* 如何设计一个强大的测试工具来评估 LSTM 模型的时间序列预测问题。 +* 如何分析实验结果,包括汇总统计。 +* 如何分析增加实验重复次数的影响以及如何识别不稳定模型。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/lstm-model-architecture-for-rare-event-time-series-forecasting.md b/docs/dl-ts/lstm-model-architecture-for-rare-event-time-series-forecasting.md new file mode 100644 index 0000000000000000000000000000000000000000..4395ab14a588888e4d30f981953be7e71af8f491 --- /dev/null +++ b/docs/dl-ts/lstm-model-architecture-for-rare-event-time-series-forecasting.md @@ -0,0 +1,225 @@ +# 用于罕见事件时间序列预测的 LSTM 模型体系结构 + +> 原文: [https://machinelearningmastery.com/lstm-model-architecture-for-rare-event-time-series-forecasting/](https://machinelearningmastery.com/lstm-model-architecture-for-rare-event-time-series-forecasting/) + +使用 LSTM 直接进行时间序列预测几乎没有成功。 + +这是令人惊讶的,因为已知神经网络能够学习复杂的非线性关系,并且 LSTM 可能是能够直接支持多变量序列预测问题的最成功的循环神经网络类型。 + +最近在 [Uber AI Labs](http://uber.ai/) 上进行的一项研究表明,LSTM 的自动特征学习功能及其处理输入序列的能力如何在端到端模型中得到利用,可用于驱动需求预测适用于公众假期等罕见事件。 + +在本文中,您将发现一种为时间序列预测开发可扩展的端到端 LSTM 模型的方法。 + +阅读这篇文章后,你会知道: + +* 跨多个站点的多变量,多步骤预测的挑战,在这种情况下是城市。 +* 用于时间序列预测的 LSTM 模型架构,包括单独的自动编码器和预测子模型。 +* 所提出的 LSTM 架构在罕见事件中的技能需求预测以及在不相关的预测问题上重用训练模型的能力。 + +让我们开始吧。 + +## 概观 + +在这篇文章中,我们将通过 [Nikolay Laptev](http://roseyu.com/time-series-workshop/) 等人回顾 2017 年题为“Uber 神经网络的[时间序列极端事件预测”的论文。在 ICML 2017 时间序列研讨会上发表。](http://roseyu.com/time-series-workshop/submissions/TSW2017_paper_3.pdf) + +这篇文章分为四个部分;他们是: + +1. 动机 +2. 数据集 +3. 模型 +4. 发现 + +## 动机 + +该工作的目标是为多步骤时间序列预测开发端到端预测模型,该模型可以处理多变量输入(例如,多输入时间序列)。 + +该模型的目的是预测优步驾驶员对乘车共享的需求,特别是预测具有挑战性日子的需求,例如假期,其中经典模型的不确定性很高。 + +通常,这种类型的假期需求预测属于称为极端事件预测的研究领域。 + +> 极端事件预测已成为估算乘车共享和其他应用的峰值电力需求,交通拥堵严重性和激增定价的热门话题。事实上,有一个称为极值理论(EVT)的统计分支直接处理这一挑战。 + +- [优步神经网络的时间序列极端事件预测](http://roseyu.com/time-series-workshop/submissions/TSW2017_paper_3.pdf),2017。 + +描述了两种现有方法: + +* **经典预测方法**:每个时间序列开发模型时,可能根据需要拟合。 +* **两步法**:经典模型与机器学习模型结合使用。 + +这些现有模型的难度激发了对单个端到端模型的需求。 + +此外,还需要一个可以跨区域推广的模型,特别是针对每个城市收集的数据。这意味着在一些或所有城市训练的模型可用数据并用于在一些或所有城市进行预测。 + +我们可以将此概括为一个模型的一般需求,该模型支持多变量输入,进行多步预测,并在多个站点(在这种情况下为城市)中进行概括。 + +## 数据集 + +该模型适用于 Uber 数据集,该数据集包括美国顶级城市五年的匿名乘车共享数据。 + +> 在人口方面,美国各大城市完成旅行的五年历史记录用于提供美国所有主要假期的预测。 + +- [优步神经网络的时间序列极端事件预测](http://roseyu.com/time-series-workshop/submissions/TSW2017_paper_3.pdf),2017。 + +每个预测的输入包括每个骑行的信息,以及天气,城市和假日变量。 + +> 为了避免缺乏数据,我们使用其他功能,包括天气信息(例如降水,风速,温度)和城市级信息(例如,当前旅行,当前用户,当地假期)。 + +- [优步神经网络的时间序列极端事件预测](http://roseyu.com/time-series-workshop/submissions/TSW2017_paper_3.pdf),2017。 + +下面的图表提供了一年六个变量的样本。 + +![Scaled Multivariate Input for Model](img/3aabdc4f308e294d62bbec3e96fe6acd.jpg) + +模型 +的缩放多变量输入来自“优步神经网络的时间序列极端事件预测”。 + +通过将历史数据拆分为输入和输出变量的滑动窗口来创建训练数据集。 + +本文未指定实验中使用的回顾和预测范围的具体大小。 + +![Sliding Window Approach to Modeling Time Series](img/e68f8d95e6fbb3e3218e817e77b59958.jpg) + +时间序列建模的滑动窗口方法 +取自“优步神经网络的时间序列极端事件预测”。 + +通过对每批样品的观察值进行标准化来缩放时间序列数据,并且每个输入序列被去除趋势,但是没有去季节化。 + +> 神经网络对未缩放的数据很敏感,因此我们将每个小批量标准化。此外,我们发现,与去调味相反,降低数据的趋势可以产生更好的结果。 + +- [优步神经网络的时间序列极端事件预测](http://roseyu.com/time-series-workshop/submissions/TSW2017_paper_3.pdf),2017。 + +## 模型 + +LSTM,例如 Vanilla LSTMs 在问题上进行了评估并表现出相对较差的表现。 + +这并不奇怪,因为它反映了其他地方的发现。 + +> 我们最初的 LSTM 实施相对于最先进的方法没有表现出优越的表现。 + +- [优步神经网络的时间序列极端事件预测](http://roseyu.com/time-series-workshop/submissions/TSW2017_paper_3.pdf),2017。 + +使用了更精细的架构,包括两个 LSTM 模型: + +* **特征提取器**:用于将输入序列提取到特征向量的模型,该特征向量可以用作进行预测的输入。 +* **Forecaster** :使用提取的特征和其他输入进行预测的模型。 + +开发了 LSTM 自动编码器模型用作特征提取模型,并使用 [Stacked LSTM](https://machinelearningmastery.com/stacked-long-short-term-memory-networks/) 作为预测模型。 + +> 我们发现香草 LSTM 模型的表现比我们的基线差。因此,我们提出了一种新架构,它利用自动编码器进行特征提取,与基线相比实现了卓越的表现。 + +- [优步神经网络的时间序列极端事件预测](http://roseyu.com/time-series-workshop/submissions/TSW2017_paper_3.pdf),2017。 + +在进行预测时,首先将时间序列数据提供给自动编码器,自动编码器被压缩为平均和连接的多个特征向量。然后将特征向量作为输入提供给预测模型以进行预测。 + +> ...该模型首先通过自动特征提取对网络进行填充,这对于在大规模特殊事件期间捕获复杂的时间序列动态至关重要。 [...]然后通过集合技术(例如,平均或其他方法)聚合特征向量。然后将最终向量与新输入连接并馈送到 LSTM 预测器进行预测。 + +- [优步神经网络的时间序列极端事件预测](http://roseyu.com/time-series-workshop/submissions/TSW2017_paper_3.pdf),2017。 + +目前尚不清楚在进行预测时究竟是什么给自动编码器提供了什么,尽管我们可能猜测这是一个多变量时间序列,用于预测在预测时间间隔之前观察的城市。 + +作为自动编码器输入的多变量时间序列将导致可以连接的多个编码向量(每个系列一个)。目前尚不清楚平均在这一点上可能采取什么角色,尽管我们可能猜测它是执行自动编码过程的多个模型的平均值。 + +![Overview of Feature Extraction Model and Forecast Model](img/976779f88c028ae5777f987126429f65.jpg) + +特征提取模型和预测模型概述 +取自“优步神经网络的时间序列极端事件预测”。 + +作者评论说,可以将自动编码器作为预测模型的一部分,并对此进行评估,但单独的模型可以提高表现。 + +> 但是,拥有一个单独的自动编码器模块可以在我们的经验中产生更好的结果。 + +- [优步神经网络的时间序列极端事件预测](http://roseyu.com/time-series-workshop/submissions/TSW2017_paper_3.pdf),2017。 + +在展示纸张时使用的幻灯片中提供了所开发模型的更多细节。 + +自动编码器的输入是 512 LSTM 单位,自动编码器中的瓶颈用于创建 32 或 64 LSTM 单位的编码特征向量。 + +![Details of LSTM Autoencoder for Feature Extraction](img/60c25c1bdcff46355dd8df396e8b9c17.jpg) + +用于特征提取的 LSTM 自动编码器的详细信息 +取自“优步神经网络的时间序列极端事件预测”。 + +使用'_ 新输入 _'将编码的特征向量提供给预测模型,尽管未指定此新输入是什么;我们可以猜测这是一个时间序列,也许是预测区间之前的观测预测的城市的多变量时间序列。或者,从这个系列中提取的特征[论文中的博客文章暗示](https://eng.uber.com/neural-networks/)(尽管我对这篇文章和幻灯片与此相矛盾时持怀疑态度)。 + +该模型接受了大量数据的训练,这是堆叠 LSTM 或一般 LSTM 的一般要求。 + +> 所描述的生产神经网络模型在数千个时间序列上进行训练,每个时间序列具有数千个数据点。 + +- [优步神经网络的时间序列极端事件预测](http://roseyu.com/time-series-workshop/submissions/TSW2017_paper_3.pdf),2017。 + +在进行新的预测时,不会对该模型进行再训练。 + +还使用引导程序实现了估算预测不确定性的有趣方法。 + +它分别使用自动编码器和预测模型分别估计模型不确定性和预测不确定性。输入被提供给给定模型并且使用了激活的丢失(如幻灯片中所评论的)。该过程重复 100 次,模型和预测误差项用于预测不确定性的估计。 + +![Overview of Forecast Uncertainty Estimation](img/185d5e71e3e2cef3a4898fdeab98865c.jpg) + +预测不确定性估计概述 +取自“优步神经网络的时间序列极端事件预测”。 + +这种预测不确定性的方法可能更好地描述于 2017 年论文“[优步时间序列的深度和自信预测](https://arxiv.org/abs/1709.01907)”。 + +## 发现 + +对该模型进行了评估,特别关注美国城市对美国假期的需求预测。 + +没有具体说明模型评估的具体情况。 + +新的广义 LSTM 预测模型被发现优于优步使用的现有模型,如果我们假设现有模型得到了很好的调整,这可能会令人印象深刻。 + +> 结果显示,与包含单变量时间序列和机器学习模型的当前专有方法相比,预测精度提高了 2%-18%。 + +- [优步神经网络的时间序列极端事件预测](http://roseyu.com/time-series-workshop/submissions/TSW2017_paper_3.pdf),2017。 + +然后将在 Uber 数据集上训练的模型直接应用于由[约 1,500 个月的单变量时间序列预测数据集]组成的 [M3-竞赛数据集](https://www.sciencedirect.com/science/article/pii/S0169207000000571)的子集。 + +这是一种转移学习,一种非常理想的目标,允许跨问题域重用深度学习模型。 + +令人惊讶的是,该模型表现良好,与表现最佳的方法相比并不是很好,但比许多复杂模型更好。结果表明,可能通过微调(例如在其他转移学习案例研究中完成),该模型可以重复使用并且技巧娴熟。 + +![Performance of LSTM Model Trained on Uber Data and Evaluated on the M3 Datasets Taken from "Time-series Extreme Event Forecasting with Neural Networks at Uber."](img/18c16ac3e2a8293f442b4f8f772e04dd.jpg) + +LSTM 模型在优步数据上的表现和对 M3 数据集的评估 +取自“优步神经网络的时间序列极端事件预测”。 + +重要的是,作者提出,深度 LSTM 模型对时间序列预测的最有益应用可能是: + +* 有大量的时间序列。 +* 每个系列都有大量的观察结果。 +* 时间序列之间存在很强的相关性。 + +> 根据我们的经验,选择时间序列的神经网络模型有三个标准:(a)时间序列的数量(b)时间序列的长度和(c)时间序列之间的相关性。如果(a),(b)和(c)高,则神经网络可能是正确的选择,否则经典的时间序列方法可能效果最好。 + +- [优步神经网络的时间序列极端事件预测](http://roseyu.com/time-series-workshop/submissions/TSW2017_paper_3.pdf),2017。 + +通过本文介绍中使用的幻灯片很好地总结了这一点。 + +![Lessons Learned Applying LSTMs for Time Series Forecasting](img/535f79a66d646b285b37f86d6ec1831f.jpg) + +应用 LSTM 进行时间序列预测的经验教训 +取自“优步神经网络的时间序列极端事件预测”幻灯片。 + +## 进一步阅读 + +如果您希望深入了解,本节将提供有关该主题的更多资源。 + +* [优步神经网络的时间序列极端事件预测](http://roseyu.com/time-series-workshop/submissions/TSW2017_paper_3.pdf),2017。 +* [优步工程极端事件预测与循环神经网络](https://eng.uber.com/neural-networks/),2017 年。 +* [优步神经网络的时间序列建模](https://forecasters.org/wp-content/uploads/gravity_forms/7-c6dd08fee7f0065037affb5b74fec20a/2017/07/Laptev_Nikolay_ISF2017.pdf),Slides,2017。 +* [时间序列极端事件预测案例研究](https://prezi.com/l16la1_bmfii/time-series-extreme-event-forecasting-case-study/),幻灯片 2018。 +* [时间序列研讨会,ICML 2017](http://roseyu.com/time-series-workshop/) +* [优步时间序列的深度和自信预测](https://arxiv.org/abs/1709.01907),2017。 + +## 摘要 + +在这篇文章中,您发现了一个可扩展的端到端 LSTM 模型,用于时间序列预测。 + +具体来说,你学到了: + +* 跨多个站点的多变量,多步骤预测的挑战,在这种情况下是城市。 +* 用于时间序列预测的 LSTM 模型架构,包括单独的自动编码器和预测子模型。 +* 所提出的 LSTM 架构在罕见事件中的技能需求预测以及在不相关的预测问题上重用训练模型的能力。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/machine-learning-data-transforms-for-time-series-forecasting.md b/docs/dl-ts/machine-learning-data-transforms-for-time-series-forecasting.md new file mode 100644 index 0000000000000000000000000000000000000000..56ef7ed6bd6ea70acf457086ec65d30c80045201 --- /dev/null +++ b/docs/dl-ts/machine-learning-data-transforms-for-time-series-forecasting.md @@ -0,0 +1,397 @@ +# 用于时间序列预测的 4 种通用机器学习数据变换 + +> 原文: [https://machinelearningmastery.com/machine-learning-data-transforms-for-time-series-forecasting/](https://machinelearningmastery.com/machine-learning-data-transforms-for-time-series-forecasting/) + +时间序列数据通常需要在使用机器学习算法建模之前进行一些准备。 + +例如,差分运算可用于从序列中去除趋势和季节结构,以简化预测问题。一些算法(例如神经网络)更喜欢在建模之前对数据进行标准化和/或标准化。 + +应用于该系列的任何变换操作也需要在预测上应用类似的逆变换。这是必需的,以便得到的计算表现度量与输出变量的比例相同,并且可以与经典预测方法进行比较。 + +在这篇文章中,您将了解如何在机器学习中对时间序列数据执行和反转四种常见数据转换。 + +阅读这篇文章后,你会知道: + +* 如何在 Python 中转换和反转四种方法的变换。 +* 在训练和测试数据集上使用变换时的重要注意事项。 +* 在数据集上需要多个操作时建议的转换顺序。 + +让我们开始吧。 + +![4 Common Machine Learning Data Transforms for Time Series Forecasting](img/d76413e9f5320b1fca77b30e22b91288.jpg) + +用于时间序列预测的 4 种通用机器学习数据变换 +照片由 [Wolfgang Staudt](https://www.flickr.com/photos/wolfgangstaudt/2200561848/) 拍摄,保留一些权利。 + +## 概观 + +本教程分为三个部分;他们是: + +1. 时间序列数据的变换 +2. 模型评估的考虑因素 +3. 数据转换顺序 + +## 时间序列数据的变换 + +给定单变量时间序列数据集,在使用机器学习方法进行建模和预测时,有四种变换很流行。 + +他们是: + +* 电力转换 +* 差异变换 +* 标准化 +* 正常化 + +让我们依次快速浏览一下以及如何在 Python 中执行这些转换。 + +我们还将审查如何反转变换操作,因为当我们想要以原始比例评估预测时,这是必需的,以便可以直接比较表现度量。 + +您是否希望在时间序列数据上使用其他变换来进行机器学习方法的建模? +请在下面的评论中告诉我。 + +### 电力转换 + +[功率变换](https://en.wikipedia.org/wiki/Power_transform)从数据分布中移除偏移以使分布更正常(高斯分布)。 + +在时间序列数据集上,这可以消除随时间变化的方差。 + +流行的例子是对数变换(正值)或广义版本,例如 Box-Cox 变换(正值)或 Yeo-Johnson 变换(正值和负值)。 + +例如,我们可以使用 SciPy 库中的 [boxcox()函数](https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.boxcox.html)在 Python 中实现 Box-Cox 变换。 + +默认情况下,该方法将以数字方式优化变换的 lambda 值并返回最佳值。 + +``` +from scipy.stats import boxcox +# define data +data = ... +# box-cox transform +result, lmbda = boxcox(data) +``` + +变换可以反转,但需要一个名为 _invert_boxcox()_ 的下面列出的自定义函数,它接受一个变换值和用于执行变换的 lambda 值。 + +``` +from math import log +from math import exp +# invert a boxcox transform for one value +def invert_boxcox(value, lam): + # log case + if lam == 0: + return exp(value) + # all other cases + return exp(log(lam * value + 1) / lam) +``` + +下面列出了将功率变换应用于数据集并反转变换的完整示例。 + +``` +# example of power transform and inversion +from math import log +from math import exp +from scipy.stats import boxcox + +# invert a boxcox transform for one value +def invert_boxcox(value, lam): + # log case + if lam == 0: + return exp(value) + # all other cases + return exp(log(lam * value + 1) / lam) + +# define dataset +data = [x for x in range(1, 10)] +print(data) +# power transform +transformed, lmbda = boxcox(data) +print(transformed, lmbda) +# invert transform +inverted = [invert_boxcox(x, lmbda) for x in transformed] +print(inverted) +``` + +运行该示例将在转换变换后打印原始数据集,幂变换的结果以及原始值(或接近它)。 + +``` +[1, 2, 3, 4, 5, 6, 7, 8, 9] +[0\. 0.89887536 1.67448353 2.37952145 3.03633818 3.65711928 + 4.2494518 4.81847233 5.36786648] 0.7200338588580095 +[1.0, 2.0, 2.9999999999999996, 3.999999999999999, 5.000000000000001, 6.000000000000001, 6.999999999999999, 7.999999999999998, 8.999999999999998] +``` + +### 差异变换 + +差分变换是从时间序列中去除系统结构的简单方法。 + +例如,可以通过从系列中的每个值中减去先前的值来消除趋势。这称为一阶差分。可以重复该过程(例如差异系列)以消除二阶趋势,等等。 + +通过从前一季节中减去观察值,可以以类似的方式去除季节性结构。 12 个步骤之前的月度数据与年度季节性结构。 + +可以使用下面列出的名为 _difference()_ 的自定义函数计算系列中的单个差异值。该函数采用时间序列和差值计算的间隔,例如, 1 表示趋势差异,12 表示季节性差异。 + +``` +# difference dataset +def difference(data, interval): + return [data[i] - data[i - interval] for i in range(interval, len(data))] +``` + +同样,可以使用自定义函数反转此操作,该函数将原始值添加回名为 _invert_difference()_ 的差值,该值采用原始序列和间隔。 + +``` +# invert difference +def invert_difference(orig_data, diff_data, interval): + return [diff_data[i-interval] + orig_data[i-interval] for i in range(interval, len(orig_data))] +``` + +我们可以在下面演示这个功能。 + +``` +# example of a difference transform + +# difference dataset +def difference(data, interval): + return [data[i] - data[i - interval] for i in range(interval, len(data))] + +# invert difference +def invert_difference(orig_data, diff_data, interval): + return [diff_data[i-interval] + orig_data[i-interval] for i in range(interval, len(orig_data))] + +# define dataset +data = [x for x in range(1, 10)] +print(data) +# difference transform +transformed = difference(data, 1) +print(transformed) +# invert difference +inverted = invert_difference(data, transformed, 1) +print(inverted) +``` + +运行该示例将打印原始数据集,差异变换的结果以及转换后的原始值。 + +注意,变换后序列中的第一个“间隔”值将丢失。这是因为它们在“间隔”之前的时间步长没有值,因此无法区分。 + +``` +[1, 2, 3, 4, 5, 6, 7, 8, 9] +[1, 1, 1, 1, 1, 1, 1, 1] +[2, 3, 4, 5, 6, 7, 8, 9] +``` + +### 标准化 + +标准化是具有高斯分布的数据的变换。 + +它减去均值并将结果除以数据样本的标准差。这具有将数据转换为具有零或中心的均值的效果,其标准偏差为 1.这样得到的分布称为标准高斯分布,或标准法线,因此称为变换的名称。 + +我们可以使用 scikit-learn 库中的 Python 中的 [StandardScaler](http://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.StandardScaler.html) 对象执行标准化。 + +此类允许通过调用 _fit()_ 将变换拟合到训练数据集上,通过调用 _transform()_ 应用于一个或多个数据集(例如训练和测试)并且还提供通过调用 _inverse_transform()_ 来反转变换的函数。 + +下面应用完整的示例。 + +``` +# example of standardization +from sklearn.preprocessing import StandardScaler +from numpy import array +# define dataset +data = [x for x in range(1, 10)] +data = array(data).reshape(len(data), 1) +print(data) +# fit transform +transformer = StandardScaler() +transformer.fit(data) +# difference transform +transformed = transformer.transform(data) +print(transformed) +# invert difference +inverted = transformer.inverse_transform(transformed) +print(inverted) +``` + +运行该示例将打印原始数据集,标准化变换的结果以及转换后的原始值。 + +请注意,期望数据作为具有多行的列提供。 + +``` +[[1] + [2] + [3] + [4] + [5] + [6] + [7] + [8] + [9]] + +[[-1.54919334] + [-1.161895 + [-0.77459667] + [-0.38729833] + [ 0\. + [ 0.38729833] + [ 0.77459667] + [ 1.161895 + [ 1.54919334]] + +[[1.] + [2.] + [3.] + [4.] + [5.] + [6.] + [7.] + [8.] + [9.]] +``` + +### 正常化 + +规范化是将数据从原始范围重新缩放到 0 到 1 之间的新范围。 + +与标准化一样,这可以使用 scikit-learn 库中的转换对象来实现,特别是 [MinMaxScaler](http://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MinMaxScaler.html) 类。除了规范化之外,通过在对象的构造函数中指定首选范围,此类可用于将数据重新缩放到您希望的任何范围。 + +它可以以相同的方式用于拟合,变换和反转变换。 + +下面列出了一个完整的例子。 + +``` +# example of normalization +from sklearn.preprocessing import MinMaxScaler +from numpy import array +# define dataset +data = [x for x in range(1, 10)] +data = array(data).reshape(len(data), 1) +print(data) +# fit transform +transformer = MinMaxScaler() +transformer.fit(data) +# difference transform +transformed = transformer.transform(data) +print(transformed) +# invert difference +inverted = transformer.inverse_transform(transformed) +print(inverted) +``` + +运行该示例将打印原始数据集,规范化转换的结果以及转换后的原始值。 + +``` +[[1] + [2] + [3] + [4] + [5] + [6] + [7] + [8] + [9]] + +[[0\. + [0.125] + [0.25 ] + [0.375] + [0.5 + [0.625] + [0.75 ] + [0.875] + [1\. ] + +[[1.] + [2.] + [3.] + [4.] + [5.] + [6.] + [7.] + [8.] + [9.]] +``` + +## 模型评估的考虑因素 + +我们已经提到了能够反转模型预测变换的重要性,以便计算与其他方法直接相当的模型表现统计量。 + +另外,另一个问题是数据泄漏问题。 + +上述三个数据转换来自提供的数据集的估计系数,然后用于转换数据。特别: + +* **Power Transform** :lambda 参数。 +* **标准化**:平均值和标准差统计量。 +* **标准化**:最小值和最大值。 + +必须仅在训练数据集上估计这些系数。 + +估计完成后,可以在评估模型之前使用系数对训练和测试数据集应用变换。 + +如果在分割成训练集和测试集之前使用整个数据集估计系数,则从测试集到训练数据集的信息泄漏很小。这可能导致对乐观偏见的模型技能的估计。 + +因此,您可能希望使用领域知识增强系数的估计值,例如将来所有时间的预期最小值/最大值。 + +通常,差分不会遇到相同的问题。在大多数情况下,例如一步预测,可以使用滞后观察来执行差异计算。如果不是,则可以在任何需要的地方使用滞后预测作为差异计算中真实观察的代理。 + +## 数据转换顺序 + +您可能希望尝试在建模之前将多个数据转换应用于时间序列。 + +这很常见,例如应用幂变换以消除增加的方差,应用季节差异来消除季节性,并应用一步差分来移除趋势。 + +应用转换操作的顺序很重要。 + +直觉上,我们可以思考变换如何相互作用。 + +* 应该在差分之前执行功率变换。 +* 应在一步差分之前进行季节性差异。 +* 标准化是线性的,应在任何非线性变换和差分后对样本进行标准化。 +* 标准化是线性操作,但它应该是为保持首选标度而执行的最终变换。 + +因此,建议的数据转换顺序如下: + +1. 电力转换。 +2. 季节性差异。 +3. 趋势差异。 +4. 标准化。 +5. 正常化。 + +显然,您只能使用特定数据集所需的变换。 + +重要的是,当变换操作被反转时,必须反转逆变换操作的顺序。具体而言,必须按以下顺序执行逆操作: + +1. 正常化。 +2. 标准化。 +3. 趋势差异。 +4. 季节性差异。 +5. 电力转换。 + +## 进一步阅读 + +如果您希望深入了解,本节将提供有关该主题的更多资源。 + +### 帖子 + +* [如何使用 Python 进行时间序列预测数据的电源转换](https://machinelearningmastery.com/power-transform-time-series-forecast-data-python/) +* [如何使用 Python 中的差异变换删除趋势和季节性](https://machinelearningmastery.com/remove-trends-seasonality-difference-transform-python/) +* [如何区分时间序列数据集与 Python](https://machinelearningmastery.com/difference-time-series-dataset-python/) +* [如何在 Python 中标准化和标准化时间序列数据](https://machinelearningmastery.com/normalize-standardize-time-series-data-python/) + +### 蜜蜂 + +* [scipy.stats.boxcox API](https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.boxcox.html) +* [sklearn.preprocessing.MinMaxScaler API](http://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MinMaxScaler.html) +* [sklearn.preprocessing.StandardScaler API](http://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.StandardScaler.html) + +### 用品 + +* [维基百科上的权力转换](https://en.wikipedia.org/wiki/Power_transform) + +## 摘要 + +在这篇文章中,您了解了如何在机器学习中对时间序列数据执行和反转四种常见数据转换。 + +具体来说,你学到了: + +* 如何在 Python 中转换和反转四种方法的变换。 +* 在训练和测试数据集上使用变换时的重要注意事项。 +* 在数据集上需要多个操作时建议的转换顺序。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/multi-step-time-series-forecasting-long-short-term-memory-networks-python.md b/docs/dl-ts/multi-step-time-series-forecasting-long-short-term-memory-networks-python.md new file mode 100644 index 0000000000000000000000000000000000000000..496bd3141524e052c13b54a5b65baa986d951f23 --- /dev/null +++ b/docs/dl-ts/multi-step-time-series-forecasting-long-short-term-memory-networks-python.md @@ -0,0 +1,994 @@ +# Python 中长短期记忆网络的多步时间序列预测 + +> 原文: [https://machinelearningmastery.com/multi-step-time-series-forecasting-long-short-term-memory-networks-python/](https://machinelearningmastery.com/multi-step-time-series-forecasting-long-short-term-memory-networks-python/) + +长期短期记忆网络或 LSTM 是一种可以学习和预测长序列的循环神经网络。 + +除了学习长序列之外,LSTM 的一个好处是它们可以学习进行一次性多步预测,这对于时间序列预测可能是有用的。 + +LSTM 的一个难点是它们配置起来很棘手,需要大量准备才能以正确的格式获取数据进行学习。 + +在本教程中,您将了解如何使用 Keras 在 Python 中开发用于多步骤时间序列预测的 LSTM。 + +完成本教程后,您将了解: + +* 如何为多步时间序列预测准备数据。 +* 如何开发 LSTM 模型进行多步时间序列预测。 +* 如何评估多步时间序列预测。 + +让我们开始吧。 + +![Multi-step Time Series Forecasting with Long Short-Term Memory Networks in Python](img/c37e78ea2872c04c7d60614faae34c64.jpg) + +Python 中长期短期记忆网络的多步时间序列预测 +[Tom Babich](https://www.flickr.com/photos/100308777@N07/10345460005/) 的照片,保留一些权利。 + +## 教程概述 + +本教程分为 4 个部分;他们是: + +1. 洗发水销售数据集 +2. 数据准备和模型评估 +3. 持久性模型 +4. 多步骤 LSTM + +### 环境 + +本教程假定您已安装 Python SciPy 环境。您可以在此示例中使用 Python 2 或 3。 + +本教程假设您安装了 TensorFlow 或 Theano 后端的 Keras v2.0 或更高版本。 + +本教程还假设您安装了 scikit-learn,Pandas,NumPy 和 Matplotlib。 + +如果您在设置 Python 环境时需要帮助,请参阅以下帖子: + +* [如何使用 Anaconda 设置用于机器学习和深度学习的 Python 环境](http://machinelearningmastery.com/setup-python-environment-machine-learning-deep-learning-anaconda/) + +接下来,让我们看看标准时间序列预测问题,我们可以将其用作此实验的上下文。 + +## 洗发水销售数据集 + +该数据集描述了 3 年期间每月洗发水的销售数量。 + +单位是销售计数,有 36 个观察。原始数据集归功于 Makridakis,Wheelwright 和 Hyndman(1998)。 + +[您可以在此处下载并了解有关数据集的更多信息](https://datamarket.com/data/set/22r0/sales-of-shampoo-over-a-three-year-period)。 + +下面的示例加载并创建已加载数据集的图。 + +``` +# 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](img/646e3de8684355414799cd9964ad1d4f.jpg) + +洗发水销售数据集的线图 + +接下来,我们将看一下实验中使用的模型配置和测试工具。 + +## 数据准备和模型评估 + +本节介绍本教程中使用的数据准备和模型评估 + +### 数据拆分 + +我们将 Shampoo Sales 数据集分为两部分:训练和测试集。 + +前两年的数据将用于训练数据集,剩余的一年数据将用于测试集。 + +将使用训练数据集开发模型,并对测试数据集进行预测。 + +作为参考,过去 12 个月的观察如下: + +``` +"3-01",339.7 +"3-02",440.4 +"3-03",315.9 +"3-04",439.3 +"3-05",401.3 +"3-06",437.4 +"3-07",575.5 +"3-08",407.6 +"3-09",682.0 +"3-10",475.3 +"3-11",581.3 +"3-12",646.9 +``` + +### 多步预测 + +我们将设计一个多步预测。 + +对于数据集最后 12 个月的特定月份,我们将需要进行 3 个月的预测。 + +这是历史观察(t-1,t-2,... t-n)预测 t,t + 1 和 t + 2。 + +具体而言,从第 2 年 12​​月开始,我们必须预测 1 月,2 月和 3 月。从 1 月开始,我们必须预测 2 月,3 月和 4 月。一直到 10 月,11 月,12 月预测从 9 月到 3 年。 + +需要总共 10 个 3 个月的预测,如下: + +``` +Dec, Jan, Feb, Mar +Jan, Feb, Mar, Apr +Feb, Mar, Apr, May +Mar, Apr, May, Jun +Apr, May, Jun, Jul +May, Jun, Jul, Aug +Jun, Jul, Aug, Sep +Jul, Aug, Sep, Oct +Aug, Sep, Oct, Nov +Sep, Oct, Nov, Dec +``` + +### 模型评估 + +将使用滚动预测场景,也称为前进模型验证。 + +测试数据集的每个时间步骤将一次一个地走。将使用模型对时间步长进行预测,然后将从测试集中获取下个月的实际预期值,并使其可用于下一时间步的预测模型。 + +这模仿了一个真实世界的场景,每个月都会有新的洗发水销售观察结果,并用于下个月的预测。 + +这将通过列车和测试数据集的结构进行模拟。 + +将收集关于测试数据集的所有预测并计算错误分数以总结每个预测时间步骤的模型技能。将使用均方根误差(RMSE),因为它会对大错误进行处罚,并产生与预测数据相同的分数,即每月洗发水销售额。 + +## 持久性模型 + +时间序列预测的良好基线是持久性模型。 + +这是一个预测模型,其中最后一个观察结果是持续的。由于它的简单性,它通常被称为天真的预测。 + +您可以在帖子中了解有关时间序列预测的持久性模型的更多信息: + +* [如何使用 Python 进行时间序列预测的基线预测](http://machinelearningmastery.com/persistence-time-series-forecasting-with-python/) + +### 准备数据 + +第一步是将数据从一系列转换为监督学习问题。 + +这是从数字列表到输入和输出模式列表。我们可以使用一个名为 _series_to_supervised()_ 的预先准备的函数来实现这一点。 + +有关此功能的更多信息,请参阅帖子: + +* [如何将时间序列转换为 Python 中的监督学习问题](http://machinelearningmastery.com/convert-time-series-supervised-learning-problem-python) + +该功能如下所列。 + +``` +# convert time series into supervised learning problem +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() + # input sequence (t-n, ... t-1) + 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)] + # forecast sequence (t, t+1, ... t+n) + 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)] + # put it all together + agg = concat(cols, axis=1) + agg.columns = names + # drop rows with NaN values + if dropnan: + agg.dropna(inplace=True) + return agg +``` + +可以通过将加载的系列值传入 _n_in_ 值 1 和 n_out 值 3 来调用该函数;例如: + +``` +supervised = series_to_supervised(raw_values, 1, 3) +``` + +接下来,我们可以将监督学习数据集分成训练和测试集。 + +我们知道,在这种形式中,最后 10 行包含最后一年的数据。这些行包含测试集,其余数据构成训练数据集。 + +我们可以将所有这些放在一个新函数中,该函数接受加载的系列和一些参数,并返回准备建模的训练和测试集。 + +``` +# transform series into train and test sets for supervised learning +def prepare_data(series, n_test, n_lag, n_seq): + # extract raw values + raw_values = series.values + raw_values = raw_values.reshape(len(raw_values), 1) + # transform into supervised learning problem X, y + supervised = series_to_supervised(raw_values, n_lag, n_seq) + supervised_values = supervised.values + # split into train and test sets + train, test = supervised_values[0:-n_test], supervised_values[-n_test:] + return train, test +``` + +我们可以使用 Shampoo 数据集对此进行测试。下面列出了完整的示例。 + +``` +from pandas import DataFrame +from pandas import concat +from pandas import read_csv +from pandas import datetime + +# date-time parsing function for loading the dataset +def parser(x): + return datetime.strptime('190'+x, '%Y-%m') + +# convert time series into supervised learning problem +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() + # input sequence (t-n, ... t-1) + 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)] + # forecast sequence (t, t+1, ... t+n) + 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)] + # put it all together + agg = concat(cols, axis=1) + agg.columns = names + # drop rows with NaN values + if dropnan: + agg.dropna(inplace=True) + return agg + +# transform series into train and test sets for supervised learning +def prepare_data(series, n_test, n_lag, n_seq): + # extract raw values + raw_values = series.values + raw_values = raw_values.reshape(len(raw_values), 1) + # transform into supervised learning problem X, y + supervised = series_to_supervised(raw_values, n_lag, n_seq) + supervised_values = supervised.values + # split into train and test sets + train, test = supervised_values[0:-n_test], supervised_values[-n_test:] + return train, test + +# load dataset +series = read_csv('shampoo-sales.csv', header=0, parse_dates=[0], index_col=0, squeeze=True, date_parser=parser) +# configure +n_lag = 1 +n_seq = 3 +n_test = 10 +# prepare data +train, test = prepare_data(series, n_test, n_lag, n_seq) +print(test) +print('Train: %s, Test: %s' % (train.shape, test.shape)) +``` + +首先运行该示例将打印整个测试数据集,即最后 10 行。还打印了列车测试数据集的形状和大小。 + +``` +[[ 342.3 339.7 440.4 315.9] + [ 339.7 440.4 315.9 439.3] + [ 440.4 315.9 439.3 401.3] + [ 315.9 439.3 401.3 437.4] + [ 439.3 401.3 437.4 575.5] + [ 401.3 437.4 575.5 407.6] + [ 437.4 575.5 407.6 682\. ] + [ 575.5 407.6 682\. 475.3] + [ 407.6 682\. 475.3 581.3] + [ 682\. 475.3 581.3 646.9]] +Train: (23, 4), Test: (10, 4) +``` + +我们可以看到测试数据集第一行的单个输入值(第一列)与第二年 12 月的洗发水销售中的观察结果相符: + +``` +"2-12",342.3 +``` + +我们还可以看到每行包含 4 列用于 1 个输入,3 个输出值用于每个观察。 + +### 进行预测 + +下一步是进行持久性预测。 + +我们可以在名为 _persistence()_ 的函数中轻松实现持久性预测,该函数将最后一次观察和预测步骤的数量保持不变。此函数返回包含预测的数组。 + +``` +# make a persistence forecast +def persistence(last_ob, n_seq): + return [last_ob for i in range(n_seq)] +``` + +然后,我们可以在测试数据集中的每个时间步骤调用此函数,从第 2 年的 12 月到第 3 年的 9 月。 + +下面是一个函数 _make_forecasts()_,它执行此操作并将数据集的训练,测试和配置作为参数,并返回预测列表。 + +``` +# evaluate the persistence model +def make_forecasts(train, test, n_lag, n_seq): + forecasts = list() + for i in range(len(test)): + X, y = test[i, 0:n_lag], test[i, n_lag:] + # make forecast + forecast = persistence(X[-1], n_seq) + # store the forecast + forecasts.append(forecast) + return forecasts +``` + +我们可以调用这个函数如下: + +``` +forecasts = make_forecasts(train, test, 1, 3) +``` + +### 评估预测 + +最后一步是评估预测。 + +我们可以通过计算多步预测的每个时间步长的 RMSE 来做到这一点,在这种情况下给出 3 个 RMSE 分数。下面的函数 _evaluate_forecasts()_ 计算并打印每个预测时间步的 RMSE。 + +``` +# evaluate the RMSE for each forecast time step +def evaluate_forecasts(test, forecasts, n_lag, n_seq): + for i in range(n_seq): + actual = test[:,(n_lag+i)] + predicted = [forecast[i] for forecast in forecasts] + rmse = sqrt(mean_squared_error(actual, predicted)) + print('t+%d RMSE: %f' % ((i+1), rmse)) +``` + +我们可以这样称呼它: + +``` +evaluate_forecasts(test, forecasts, 1, 3) +``` + +在原始数据集的上下文中绘制预测图也有助于了解 RMSE 分数如何与上下文中的问题相关联。 + +我们可以首先绘制整个 Shampoo 数据集,然后将每个预测绘制为红线。下面的函数 _plot_forecasts()_ 将创建并显示此图。 + +``` +# plot the forecasts in the context of the original dataset +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 + off_e = off_s + len(forecasts[i]) + xaxis = [x for x in range(off_s, off_e)] + pyplot.plot(xaxis, forecasts[i], color='red') + # show the plot + pyplot.show() +``` + +我们可以按如下方式调用该函数。请注意,12 个月内在测试集上保留的观察数为 12,而上述使用的 10 个监督学习输入/输出模式则为 10。 + +``` +# plot forecasts +plot_forecasts(series, forecasts, 12) +``` + +我们可以通过将持久预测与原始数据集中的实际持久值相关联来使绘图更好。 + +这将需要将最后观察到的值添加到预测的前面。以下是具有此改进的 _plot_forecasts()_ 功能的更新版本。 + +``` +# plot the forecasts in the context of the original dataset +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) - 12 + 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] + pyplot.plot(xaxis, yaxis, color='red') + # show the plot + pyplot.show() +``` + +### 完整的例子 + +我们可以将所有这些部分组合在一起。 + +下面列出了多步持久性预测的完整代码示例。 + +``` +from pandas import DataFrame +from pandas import concat +from pandas import read_csv +from pandas import datetime +from sklearn.metrics import mean_squared_error +from math import sqrt +from matplotlib import pyplot + +# date-time parsing function for loading the dataset +def parser(x): + return datetime.strptime('190'+x, '%Y-%m') + +# convert time series into supervised learning problem +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() + # input sequence (t-n, ... t-1) + 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)] + # forecast sequence (t, t+1, ... t+n) + 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)] + # put it all together + agg = concat(cols, axis=1) + agg.columns = names + # drop rows with NaN values + if dropnan: + agg.dropna(inplace=True) + return agg + +# transform series into train and test sets for supervised learning +def prepare_data(series, n_test, n_lag, n_seq): + # extract raw values + raw_values = series.values + raw_values = raw_values.reshape(len(raw_values), 1) + # transform into supervised learning problem X, y + supervised = series_to_supervised(raw_values, n_lag, n_seq) + supervised_values = supervised.values + # split into train and test sets + train, test = supervised_values[0:-n_test], supervised_values[-n_test:] + return train, test + +# make a persistence forecast +def persistence(last_ob, n_seq): + return [last_ob for i in range(n_seq)] + +# evaluate the persistence model +def make_forecasts(train, test, n_lag, n_seq): + forecasts = list() + for i in range(len(test)): + X, y = test[i, 0:n_lag], test[i, n_lag:] + # make forecast + forecast = persistence(X[-1], n_seq) + # store the forecast + forecasts.append(forecast) + return forecasts + +# evaluate the RMSE for each forecast time step +def evaluate_forecasts(test, forecasts, n_lag, n_seq): + for i in range(n_seq): + actual = test[:,(n_lag+i)] + predicted = [forecast[i] for forecast in forecasts] + rmse = sqrt(mean_squared_error(actual, predicted)) + print('t+%d RMSE: %f' % ((i+1), rmse)) + +# plot the forecasts in the context of the original dataset +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] + pyplot.plot(xaxis, yaxis, color='red') + # show the plot + pyplot.show() + +# load dataset +series = read_csv('shampoo-sales.csv', header=0, parse_dates=[0], index_col=0, squeeze=True, date_parser=parser) +# configure +n_lag = 1 +n_seq = 3 +n_test = 10 +# prepare data +train, test = prepare_data(series, n_test, n_lag, n_seq) +# make forecasts +forecasts = make_forecasts(train, test, n_lag, n_seq) +# evaluate forecasts +evaluate_forecasts(test, forecasts, n_lag, n_seq) +# plot forecasts +plot_forecasts(series, forecasts, n_test+2) +``` + +首先运行该示例为每个预测的时间步骤打印 RMSE。 + +这为我们提供了每个时间步的表现基线,我们希望 LSTM 能够表现出色。 + +``` +t+1 RMSE: 144.535304 +t+2 RMSE: 86.479905 +t+3 RMSE: 121.149168 +``` + +还创建了原始时间序列与多步持久性预测的关系图。这些行连接到每个预测的相应输入值。 + +此上下文显示了持久性预测实际上是多么幼稚。 + +![Line Plot of Shampoo Sales Dataset with Multi-Step Persistence Forecasts](img/956c08eb68d1d6a9cb1be14d4c66ae66.jpg) + +具有多步持续性预测的洗发水销售数据集线图 + +## 多步 LSTM 网络 + +在本节中,我们将使用持久性示例作为起点,并查看将 LSTM 拟合到训练数据所需的更改,并对测试数据集进行多步预测。 + +### 准备数据 + +在我们使用它来训练 LSTM 之前,必须准备好数据。 + +具体而言,还需要进行两项更改: + +1. **固定**。数据显示必须通过差分消除的增加趋势。 +2. **比例**。必须将数据的比例缩小到-1 到 1 之间的值,即 LSTM 单元的激活功能。 + +我们可以引入一个函数使数据静止称为 _difference()_。这会将一系列值转换为一系列差异,这是一种更简单的表示方式。 + +``` +# 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) +``` + +我们可以使用 sklearn 库中的 _MinMaxScaler_ 来缩放数据。 + +将这些放在一起,我们可以更新 _prepare_data()_ 函数以首先区分数据并重新调整它,然后执行转换为监督学习问题并训练测试集,就像我们之前使用持久性示例一样。 + +除了训练和测试数据集之外,该函数现在返回一个缩放器。 + +``` +# transform series into train and test sets for supervised learning +def prepare_data(series, n_test, n_lag, n_seq): + # extract raw values + raw_values = series.values + # transform data to be stationary + diff_series = difference(raw_values, 1) + diff_values = diff_series.values + diff_values = diff_values.reshape(len(diff_values), 1) + # rescale values to -1, 1 + scaler = MinMaxScaler(feature_range=(-1, 1)) + scaled_values = scaler.fit_transform(diff_values) + scaled_values = scaled_values.reshape(len(scaled_values), 1) + # transform into supervised learning problem X, y + supervised = series_to_supervised(scaled_values, n_lag, n_seq) + supervised_values = supervised.values + # split into train and test sets + train, test = supervised_values[0:-n_test], supervised_values[-n_test:] + return scaler, train, test +``` + +我们可以调用这个函数如下: + +``` +# prepare data +scaler, train, test = prepare_data(series, n_test, n_lag, n_seq) +``` + +### 适合 LSTM 网络 + +接下来,我们需要将 LSTM 网络模型与训练数据相匹配。 + +这首先要求将训练数据集从 2D 阵列[_ 样本,特征 _]转换为 3D 阵列[_ 样本,时间步长,特征 _]。我们将时间步长固定为 1,因此这种变化很简单。 + +接下来,我们需要设计一个 LSTM 网络。我们将使用一个简单的结构,其中 1 个隐藏层具有 1 个 LSTM 单元,然后是具有线性激活和 3 个输出值的输出层。网络将使用均方误差丢失函数和有效的 ADAM 优化算法。 + +LSTM 是有状态的;这意味着我们必须在每个训练时代结束时手动重置网络状态。该网络将适合 1500 个时代。 + +必须使用相同的批量大小进行训练和预测,并且我们需要在测试数据集的每个时间步进行预测。这意味着必须使用批量大小为 1。批量大小为 1 也称为在线学习,因为在每个训练模式之后的训练期间将更新网络权重(与小批量或批量更新相反)。 + +我们可以将所有这些放在一个名为 _fit_lstm()_ 的函数中。该函数采用了许多可用于稍后调整网络的关键参数,并且该函数返回适合 LSTM 模型以备预测。 + +``` +# fit an LSTM network to training data +def fit_lstm(train, n_lag, n_seq, n_batch, nb_epoch, n_neurons): + # reshape training into [samples, timesteps, features] + X, y = train[:, 0:n_lag], train[:, n_lag:] + X = X.reshape(X.shape[0], 1, X.shape[1]) + # design network + model = Sequential() + model.add(LSTM(n_neurons, batch_input_shape=(n_batch, X.shape[1], X.shape[2]), stateful=True)) + model.add(Dense(y.shape[1])) + model.compile(loss='mean_squared_error', optimizer='adam') + # fit network + 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 +``` + +该函数可以调用如下: + +``` +# fit model +model = fit_lstm(train, 1, 3, 1, 1500, 1) +``` + +网络配置没有调整;如果你愿意,尝试不同的参数。 + +在下面的评论中报告您的发现。我很想看看你能得到什么。 + +### 进行 LSTM 预测 + +下一步是使用适合的 LSTM 网络进行预测。 + +通过调用 _model.predict()_,可以使用拟合 LSTM 网络进行单个预测。同样,必须将数据格式化为具有[_ 样本,时间步长,特征 _]格式的 3D 阵列。 + +我们可以将它包装成一个名为 _forecast_lstm()_ 的函数。 + +``` +# make one forecast with an LSTM, +def forecast_lstm(model, X, n_batch): + # reshape input pattern to [samples, timesteps, features] + X = X.reshape(1, 1, len(X)) + # make forecast + forecast = model.predict(X, batch_size=n_batch) + # convert to array + return [x for x in forecast[0, :]] +``` + +我们可以从 _make_forecasts()_ 函数调用此函数并更新它以接受模型作为参数。更新版本如下所示。 + +``` +# evaluate the persistence model +def make_forecasts(model, n_batch, train, test, n_lag, n_seq): + forecasts = list() + for i in range(len(test)): + X, y = test[i, 0:n_lag], test[i, n_lag:] + # make forecast + forecast = forecast_lstm(model, X, n_batch) + # store the forecast + forecasts.append(forecast) + return forecasts +``` + +可以按如下方式调用 _make_forecasts()_ 函数的更新版本: + +``` +# make forecasts +forecasts = make_forecasts(model, 1, train, test, 1, 3) +``` + +### 反转变换 + +在做出预测之后,我们需要反转变换以将值返回到原始比例。 + +这是必要的,以便我们可以计算与其他模型相当的错误分数和图,例如上面的持久性预测。 + +我们可以使用提供 _inverse_transform()_ 函数的 _MinMaxScaler_ 对象直接反转预测的比例。 + +我们可以通过将最后一个观察值(前几个月的洗发水销售额)添加到第一个预测值来反转差异,然后将值传播到预测值之下。 + +这有点儿繁琐;我们可以在函数名 _inverse_difference()_ 中包含行为,它将预测之前的最后观察值和预测作为参数,并返回反向预测。 + +``` +# invert differenced forecast +def inverse_difference(last_ob, forecast): + # invert first forecast + inverted = list() + inverted.append(forecast[0] + last_ob) + # propagate difference forecast using inverted first value + for i in range(1, len(forecast)): + inverted.append(forecast[i] + inverted[i-1]) + return inverted +``` + +将它们放在一起,我们可以创建一个 _inverse_transform()_ 函数,该函数可以处理每个预测,首先反转比例,然后反转差异,将预测恢复到原始比例。 + +``` +# inverse data transform on forecasts +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)) + # invert scaling + inv_scale = scaler.inverse_transform(forecast) + inv_scale = inv_scale[0, :] + # invert differencing + index = len(series) - n_test + i - 1 + last_ob = series.values[index] + inv_diff = inverse_difference(last_ob, inv_scale) + # store + inverted.append(inv_diff) + return inverted +``` + +我们可以使用以下预测来调用此函数: + +``` +# inverse transform forecasts and test +forecasts = inverse_transform(series, forecasts, scaler, n_test+2) +``` + +我们还可以反转输出部分测试数据集上的变换,以便我们可以正确计算 RMSE 分数,如下所示: + +``` +actual = [row[n_lag:] for row in test] +actual = inverse_transform(series, actual, scaler, n_test+2) +``` + +我们还可以简化 RMSE 分数的计算,以期望测试数据仅包含输出值,如下所示: + +``` +def evaluate_forecasts(test, forecasts, n_lag, n_seq): + 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)) +``` + +### 完整的例子 + +我们可以将所有这些部分组合在一起,使 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 + +# date-time parsing function for loading the dataset +def parser(x): + return datetime.strptime('190'+x, '%Y-%m') + +# convert time series into supervised learning problem +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() + # input sequence (t-n, ... t-1) + 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)] + # forecast sequence (t, t+1, ... t+n) + 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)] + # put it all together + agg = concat(cols, axis=1) + agg.columns = names + # drop rows with NaN values + if dropnan: + agg.dropna(inplace=True) + return agg + +# 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) + +# transform series into train and test sets for supervised learning +def prepare_data(series, n_test, n_lag, n_seq): + # extract raw values + raw_values = series.values + # transform data to be stationary + diff_series = difference(raw_values, 1) + diff_values = diff_series.values + diff_values = diff_values.reshape(len(diff_values), 1) + # rescale values to -1, 1 + scaler = MinMaxScaler(feature_range=(-1, 1)) + scaled_values = scaler.fit_transform(diff_values) + scaled_values = scaled_values.reshape(len(scaled_values), 1) + # transform into supervised learning problem X, y + supervised = series_to_supervised(scaled_values, n_lag, n_seq) + supervised_values = supervised.values + # split into train and test sets + train, test = supervised_values[0:-n_test], supervised_values[-n_test:] + return scaler, train, test + +# fit an LSTM network to training data +def fit_lstm(train, n_lag, n_seq, n_batch, nb_epoch, n_neurons): + # reshape training into [samples, timesteps, features] + X, y = train[:, 0:n_lag], train[:, n_lag:] + X = X.reshape(X.shape[0], 1, X.shape[1]) + # design network + model = Sequential() + model.add(LSTM(n_neurons, batch_input_shape=(n_batch, X.shape[1], X.shape[2]), stateful=True)) + model.add(Dense(y.shape[1])) + model.compile(loss='mean_squared_error', optimizer='adam') + # fit network + 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 + +# make one forecast with an LSTM, +def forecast_lstm(model, X, n_batch): + # reshape input pattern to [samples, timesteps, features] + X = X.reshape(1, 1, len(X)) + # make forecast + forecast = model.predict(X, batch_size=n_batch) + # convert to array + return [x for x in forecast[0, :]] + +# evaluate the persistence model +def make_forecasts(model, n_batch, train, test, n_lag, n_seq): + forecasts = list() + for i in range(len(test)): + X, y = test[i, 0:n_lag], test[i, n_lag:] + # make forecast + forecast = forecast_lstm(model, X, n_batch) + # store the forecast + forecasts.append(forecast) + return forecasts + +# invert differenced forecast +def inverse_difference(last_ob, forecast): + # invert first forecast + inverted = list() + inverted.append(forecast[0] + last_ob) + # propagate difference forecast using inverted first value + for i in range(1, len(forecast)): + inverted.append(forecast[i] + inverted[i-1]) + return inverted + +# inverse data transform on forecasts +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)) + # invert scaling + inv_scale = scaler.inverse_transform(forecast) + inv_scale = inv_scale[0, :] + # invert differencing + index = len(series) - n_test + i - 1 + last_ob = series.values[index] + inv_diff = inverse_difference(last_ob, inv_scale) + # store + inverted.append(inv_diff) + return inverted + +# evaluate the RMSE for each forecast time step +def evaluate_forecasts(test, forecasts, n_lag, n_seq): + 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)) + +# plot the forecasts in the context of the original dataset +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] + pyplot.plot(xaxis, yaxis, color='red') + # show the plot + pyplot.show() + +# load dataset +series = read_csv('shampoo-sales.csv', header=0, parse_dates=[0], index_col=0, squeeze=True, date_parser=parser) +# configure +n_lag = 1 +n_seq = 3 +n_test = 10 +n_epochs = 1500 +n_batch = 1 +n_neurons = 1 +# prepare data +scaler, train, test = prepare_data(series, n_test, n_lag, n_seq) +# fit model +model = fit_lstm(train, n_lag, n_seq, n_batch, n_epochs, n_neurons) +# make forecasts +forecasts = make_forecasts(model, n_batch, train, test, n_lag, n_seq) +# inverse transform forecasts and test +forecasts = inverse_transform(series, forecasts, scaler, n_test+2) +actual = [row[n_lag:] for row in test] +actual = inverse_transform(series, actual, scaler, n_test+2) +# evaluate forecasts +evaluate_forecasts(actual, forecasts, n_lag, n_seq) +# plot forecasts +plot_forecasts(series, forecasts, n_test+2) +``` + +首先运行该示例为每个预测的时间步骤打印 RMSE。 + +我们可以看到,每个预测时间步长的分数比持久性预测更好,在某些情况下要好得多。 + +这表明配置的 LSTM 确实掌握了问题的技巧。 + +值得注意的是,RMSE 并没有像预期的那样随着预测范围的长度而变得越来越差。这是因为 t + 2 似乎比 t + 1 更容易预测。这可能是因为向下滴答比系列中记录的向上滴答更容易预测(这可以通过对结果进行更深入的分析来确认)。 + +``` +t+1 RMSE: 95.973221 +t+2 RMSE: 78.872348 +t+3 RMSE: 105.613951 +``` + +还创建了系列(蓝色)和预测(红色)的线图。 + +该图显示,尽管模型的技能更好,但有些预测并不是很好,而且还有很大的改进空间。 + +![Line Plot of Shampoo Sales Dataset with Multi-Step LSTM Forecasts](img/9f802400a334488bcea84f8c3259967f.jpg) + +洗发水销售数据集的线图与多步骤 LSTM 预测 + +## 扩展 + +如果您希望超越本教程,可以考虑一些扩展。 + +* **更新 LSTM** 。更改示例以在新数据可用时重新安装或更新 LSTM。一个 10 秒的训练时期应该足以重新训练一个新的观察。 +* **调整 LSTM** 。网格搜索教程中使用的一些 LSTM 参数,例如时期数,神经元数和层数,以查看是否可以进一步提升表现。 +* **Seq2Seq** 。使用 LSTM 的编码器 - 解码器范例来预测每个序列,看看它是否提供任何好处。 +* **时间范围**。尝试预测不同的时间范围,并了解网络的行为在不同的交付周期中如何变化。 + +你尝试过这些扩展吗? +在评论中分享您的结果;我很想听听它。 + +## 摘要 + +在本教程中,您了解了如何为多步时间序列预测开发 LSTM 网络。 + +具体来说,你学到了: + +* 如何开发多步时间序列预测的持久性模型。 +* 如何开发 LSTM 网络进行多步时间序列预测。 +* 如何评估和绘制多步时间序列预测的结果。 + +您对 LSTM 的多步骤时间序列预测有任何疑问吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/multi-step-time-series-forecasting-with-machine-learning-models-for-household-electricity-consumption.md b/docs/dl-ts/multi-step-time-series-forecasting-with-machine-learning-models-for-household-electricity-consumption.md new file mode 100644 index 0000000000000000000000000000000000000000..45eae4faab1649d6300eeb1b152e50f1b0d6e01d --- /dev/null +++ b/docs/dl-ts/multi-step-time-series-forecasting-with-machine-learning-models-for-household-electricity-consumption.md @@ -0,0 +1,1326 @@ +# 家庭用电机器学习的多步时间序列预测 + +> 原文: [https://machinelearningmastery.com/multi-step-time-series-forecasting-with-machine-learning-models-for-household-electricity-consumption/](https://machinelearningmastery.com/multi-step-time-series-forecasting-with-machine-learning-models-for-household-electricity-consumption/) + +鉴于智能电表的兴起以及太阳能电池板等发电技术的广泛采用,可提供大量的用电数据。 + +该数据代表了多变量时间序列的功率相关变量,而这些变量又可用于建模甚至预测未来的电力消耗。 + +机器学习算法预测单个值,不能直接用于多步预测。可以使用机器学习算法进行多步预测的两种策略是递归和直接方法。 + +在本教程中,您将了解如何使用机器学习算法开发递归和直接多步预测模型。 + +完成本教程后,您将了解: + +* 如何开发一个评估线性,非线性和集成机器学习算法的框架,用于多步时间序列预测。 +* 如何使用递归多步时间序列预测策略评估机器学习算法。 +* 如何使用直接的每日和每个引导时间多步时间序列预测策略来评估机器学习算法。 + +让我们开始吧。 + +![Multi-step Time Series Forecasting with Machine Learning Models for Household Electricity Consumption](img/7d8356930daede3ec1bb4c447180190d.jpg) + +用于家庭用电的机器学习模型的多步骤时间序列预测 +照片由 [Sean McMenemy](https://www.flickr.com/photos/seanfx/9827244314/) ,保留一些权利。 + +## 教程概述 + +本教程分为五个部分;他们是: + +1. 问题描述 +2. 加载并准备数据集 +3. 模型评估 +4. 递归多步预测 +5. 直接多步预测 + +## 问题描述 + +'[家庭用电量](https://archive.ics.uci.edu/ml/datasets/individual+household+electric+power+consumption)'数据集是一个多变量时间序列数据集,描述了四年内单个家庭的用电量。 + +该数据是在 2006 年 12 月至 2010 年 11 月之间收集的,并且每分钟收集家庭内的能耗观察结果。 + +它是一个多变量系列,由七个变量组成(除日期和时间外);他们是: + +* **global_active_power** :家庭消耗的总有功功率(千瓦)。 +* **global_reactive_power** :家庭消耗的总无功功率(千瓦)。 +* **电压**:平均电压(伏特)。 +* **global_intensity** :平均电流强度(安培)。 +* **sub_metering_1** :厨房的有功电能(瓦特小时的有功电能)。 +* **sub_metering_2** :用于洗衣的有功能量(瓦特小时的有功电能)。 +* **sub_metering_3** :气候控制系统的有功电能(瓦特小时的有功电能)。 + +有功和无功电能参考[交流电](https://en.wikipedia.org/wiki/AC_power)的技术细节。 + +可以通过从总活动能量中减去三个定义的子计量变量的总和来创建第四个子计量变量,如下所示: + +``` +sub_metering_remainder = (global_active_power * 1000 / 60) - (sub_metering_1 + sub_metering_2 + sub_metering_3) +``` + +## 加载并准备数据集 + +数据集可以从 UCI 机器学习库下载为单个 20 兆字节的.zip 文件: + +* [household_power_consumption.zip](https://archive.ics.uci.edu/ml/machine-learning-databases/00235/household_power_consumption.zip) + +下载数据集并将其解压缩到当前工作目录中。您现在将拥有大约 127 兆字节的文件“ _household_power_consumption.txt_ ”并包含所有观察结果。 + +我们可以使用 _read_csv()_ 函数来加载数据,并将前两列合并到一个日期时间列中,我们可以将其用作索引。 + +``` +# load all data +dataset = read_csv('household_power_consumption.txt', sep=';', header=0, low_memory=False, infer_datetime_format=True, parse_dates={'datetime':[0,1]}, index_col=['datetime']) +``` + +接下来,我们可以用'_ 标记所有[缺失值](https://machinelearningmastery.com/handle-missing-timesteps-sequence-prediction-problems-python/)?_ '具有 _NaN_ 值的字符,这是一个浮点数。 + +这将允许我们将数据作为一个浮点值数组而不是混合类型(效率较低)。 + +``` +# mark all missing values +dataset.replace('?', nan, inplace=True) +# make dataset numeric +dataset = dataset.astype('float32') +``` + +我们还需要填写缺失值,因为它们已被标记。 + +一种非常简单的方法是从前一天的同一时间复制观察。我们可以在一个名为 _fill_missing()_ 的函数中实现它,该函数将从 24 小时前获取数据的 NumPy 数组并复制值。 + +``` +# fill missing values with a value at the same time one day ago +def fill_missing(values): + one_day = 60 * 24 + for row in range(values.shape[0]): + for col in range(values.shape[1]): + if isnan(values[row, col]): + values[row, col] = values[row - one_day, col] +``` + +我们可以将此函数直接应用于 DataFrame 中的数据。 + +``` +# fill missing +fill_missing(dataset.values) +``` + +现在,我们可以使用上一节中的计算创建一个包含剩余子计量的新列。 + +``` +# add a column for for the remainder of sub metering +values = dataset.values +dataset['sub_metering_4'] = (values[:,0] * 1000 / 60) - (values[:,4] + values[:,5] + values[:,6]) +``` + +我们现在可以将清理后的数据集版本保存到新文件中;在这种情况下,我们只需将文件扩展名更改为.csv,并将数据集保存为“ _household_power_consumption.csv_ ”。 + +``` +# save updated dataset +dataset.to_csv('household_power_consumption.csv') +``` + +将所有这些结合在一起,下面列出了加载,清理和保存数据集的完整示例。 + +``` +# load and clean-up data +from numpy import nan +from numpy import isnan +from pandas import read_csv +from pandas import to_numeric + +# fill missing values with a value at the same time one day ago +def fill_missing(values): + one_day = 60 * 24 + for row in range(values.shape[0]): + for col in range(values.shape[1]): + if isnan(values[row, col]): + values[row, col] = values[row - one_day, col] + +# load all data +dataset = read_csv('household_power_consumption.txt', sep=';', header=0, low_memory=False, infer_datetime_format=True, parse_dates={'datetime':[0,1]}, index_col=['datetime']) +# mark all missing values +dataset.replace('?', nan, inplace=True) +# make dataset numeric +dataset = dataset.astype('float32') +# fill missing +fill_missing(dataset.values) +# add a column for for the remainder of sub metering +values = dataset.values +dataset['sub_metering_4'] = (values[:,0] * 1000 / 60) - (values[:,4] + values[:,5] + values[:,6]) +# save updated dataset +dataset.to_csv('household_power_consumption.csv') +``` + +运行该示例将创建新文件' _household_power_consumption.csv_ ',我们可以将其用作建模项目的起点。 + +## 模型评估 + +在本节中,我们将考虑如何开发和评估家庭电力数据集的预测模型。 + +本节分为四个部分;他们是: + +1. 问题框架 +2. 评估指标 +3. 训练和测试集 +4. 前瞻性验证 + +### 问题框架 + +有许多方法可以利用和探索家庭用电量数据集。 + +在本教程中,我们将使用这些数据来探索一个非常具体的问题;那是: + +> 鉴于最近的耗电量,未来一周的预期耗电量是多少? + +这要求预测模型预测未来七天每天的总有功功率。 + +从技术上讲,考虑到多个预测步骤,这个问题的框架被称为多步骤时间序列预测问题。利用多个输入变量的模型可以称为多变量多步时间序列预测模型。 + +这种类型的模型在规划支出方面可能有助于家庭。在供应方面,它也可能有助于规划特定家庭的电力需求。 + +数据集的这种框架还表明,将每分钟功耗的观察结果下采样到每日总数是有用的。这不是必需的,但考虑到我们对每天的总功率感兴趣,这是有道理的。 + +我们可以使用 pandas DataFrame 上的 [resample()函数](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.resample.html)轻松实现这一点。使用参数' _D_ '调用此函数允许按日期时间索引的加载数据按天分组([查看所有偏移别名](http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases))。然后,我们可以计算每天所有观测值的总和,并为八个变量中的每一个创建每日耗电量数据的新数据集。 + +下面列出了完整的示例。 + +``` +# resample minute data to total for each day +from pandas import read_csv +# load the new file +dataset = read_csv('household_power_consumption.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +# resample data to daily +daily_groups = dataset.resample('D') +daily_data = daily_groups.sum() +# summarize +print(daily_data.shape) +print(daily_data.head()) +# save +daily_data.to_csv('household_power_consumption_days.csv') +``` + +运行该示例将创建一个新的每日总功耗数据集,并将结果保存到名为“ _household_power_consumption_days.csv_ ”的单独文件中。 + +我们可以将其用作数据集,用于拟合和评估所选问题框架的预测模型。 + +### 评估指标 + +预测将包含七个值,一个用于一周中的每一天。 + +多步预测问题通常分别评估每个预测时间步长。这有助于以下几个原因: + +* 在特定提前期评论技能(例如+1 天 vs +3 天)。 +* 在不同的交付时间基于他们的技能对比模型(例如,在+1 天的模型和在日期+5 的模型良好的模型)。 + +总功率的单位是千瓦,并且具有也在相同单位的误差度量将是有用的。均方根误差(RMSE)和平均绝对误差(MAE)都符合这个要求,尽管 RMSE 更常用,将在本教程中采用。与 MAE 不同,RMSE 更能预测预测误差。 + +此问题的表现指标是从第 1 天到第 7 天的每个提前期的 RMSE。 + +作为捷径,使用单个分数总结模型的表现以帮助模型选择可能是有用的。 + +可以使用的一个可能的分数是所有预测天数的 RMSE。 + +下面的函数 _evaluate_forecasts()_ 将实现此行为并基于多个七天预测返回模型的表现。 + +``` +# evaluate one or more weekly forecasts against expected values +def evaluate_forecasts(actual, predicted): + scores = list() + # calculate an RMSE score for each day + for i in range(actual.shape[1]): + # calculate mse + mse = mean_squared_error(actual[:, i], predicted[:, i]) + # calculate rmse + rmse = sqrt(mse) + # store + scores.append(rmse) + # calculate overall RMSE + s = 0 + for row in range(actual.shape[0]): + for col in range(actual.shape[1]): + s += (actual[row, col] - predicted[row, col])**2 + score = sqrt(s / (actual.shape[0] * actual.shape[1])) + return score, scores +``` + +运行该函数将首先返回整个 RMSE,无论白天,然后每天返回一系列 RMSE 分数。 + +### 训练和测试集 + +我们将使用前三年的数据来训练预测模型和评估模型的最后一年。 + +给定数据集中的数据将分为标准周。这些是从周日开始到周六结束的周。 + +这是使用所选模型框架的现实且有用的方法,其中可以预测未来一周的功耗。它也有助于建模,其中模型可用于预测特定日期(例如星期三)或整个序列。 + +我们将数据拆分为标准周,从测试数据集向后工作。 + +数据的最后一年是 2010 年,2010 年的第一个星期日是 1 月 3 日。数据于 2010 年 11 月中旬结束,数据中最接近的最后一个星期六是 11 月 20 日。这给出了 46 周的测试数据。 + +下面提供了测试数据集的每日数据的第一行和最后一行以供确认。 + +``` +2010-01-03,2083.4539999999984,191.61000000000055,350992.12000000034,8703.600000000033,3842.0,4920.0,10074.0,15888.233355799992 +... +2010-11-20,2197.006000000004,153.76800000000028,346475.9999999998,9320.20000000002,4367.0,2947.0,11433.0,17869.76663959999 +``` + +每日数据从 2006 年底开始。 + +数据集中的第一个星期日是 12 月 17 日,这是第二行数据。 + +将数据组织到标准周内为训练预测模型提供了 159 个完整的标准周。 + +``` +2006-12-17,3390.46,226.0059999999994,345725.32000000024,14398.59999999998,2033.0,4187.0,13341.0,36946.66673200004 +... +2010-01-02,1309.2679999999998,199.54600000000016,352332.8399999997,5489.7999999999865,801.0,298.0,6425.0,14297.133406600002 +``` + +下面的函数 _split_dataset()_ 将每日数据拆分为训练集和测试集,并将每个数据组织成标准周。 + +使用特定行偏移来使用数据集的知识来分割数据。然后使用 NumPy [split()函数](https://docs.scipy.org/doc/numpy/reference/generated/numpy.split.html)将分割数据集组织成每周数据。 + +``` +# split a univariate dataset into train/test sets +def split_dataset(data): + # split into standard weeks + train, test = data[1:-328], data[-328:-6] + # restructure into windows of weekly data + train = array(split(train, len(train)/7)) + test = array(split(test, len(test)/7)) + return train, test +``` + +我们可以通过加载每日数据集并打印列车和测试集的第一行和最后一行数据来测试此功能,以确认它们符合上述预期。 + +完整的代码示例如下所示。 + +``` +# split into standard weeks +from numpy import split +from numpy import array +from pandas import read_csv + +# split a univariate dataset into train/test sets +def split_dataset(data): + # split into standard weeks + train, test = data[1:-328], data[-328:-6] + # restructure into windows of weekly data + train = array(split(train, len(train)/7)) + test = array(split(test, len(test)/7)) + return train, test + +# load the new file +dataset = read_csv('household_power_consumption_days.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +train, test = split_dataset(dataset.values) +# validate train data +print(train.shape) +print(train[0, 0, 0], train[-1, -1, 0]) +# validate test +print(test.shape) +print(test[0, 0, 0], test[-1, -1, 0]) +``` + +运行该示例表明,列车数据集确实有 159 周的数据,而测试数据集有 46 周。 + +我们可以看到,第一行和最后一行的列车和测试数据集的总有效功率与我们定义为每组标准周界限的特定日期的数据相匹配。 + +``` +(159, 7, 8) +3390.46 1309.2679999999998 +(46, 7, 8) +2083.4539999999984 2197.006000000004 +``` + +### 前瞻性验证 + +将使用称为前向验证的方案来评估模型。 + +这是需要模型进行一周预测的地方,然后该模型的实际数据可用于模型,以便它可以用作在随后一周进行预测的基础。这对于如何在实践中使用模型以及对模型有益,使其能够利用最佳可用数据都是现实的。 + +我们可以通过分离输入数据和输出/预测数据来证明这一点。 + +``` +Input, Predict +[Week1] Week2 +[Week1 + Week2] Week3 +[Week1 + Week2 + Week3] Week4 +... +``` + +评估此数据集上的预测模型的前瞻性验证方法在下面的函数中提供,名为 _evaluate_model()_。 + +提供 scikit-learn 模型对象作为函数的参数,以及训练和测试数据集。提供了另一个参数 _n_input_ ,用于定义模型将用作输入以进行预测的先前观察的数量。 + +关于 scikit-learn 模型如何适合并进行预测的细节将在后面的章节中介绍。 + +然后使用先前定义的 _evaluate_forecasts()_ 函数,针对测试数据集评估模型所做的预测。 + +``` +# evaluate a single model +def evaluate_model(model, train, test, n_input): + # history is a list of weekly data + history = [x for x in train] + # walk-forward validation over each week + predictions = list() + for i in range(len(test)): + # predict the week + yhat_sequence = ... + # store the predictions + predictions.append(yhat_sequence) + # get real observation and add to history for predicting the next week + history.append(test[i, :]) + predictions = array(predictions) + # evaluate predictions days for each week + score, scores = evaluate_forecasts(test[:, :, 0], predictions) + return score, scores +``` + +一旦我们对模型进行评估,我们就可以总结表现。 + +下面的函数名为 _summarize_scores()_,将模型的表现显示为单行,以便与其他模型进行比较。 + +``` +# summarize scores +def summarize_scores(name, score, scores): + s_scores = ', '.join(['%.1f' % s for s in scores]) + print('%s: [%.3f] %s' % (name, score, s_scores)) +``` + +我们现在已经开始评估数据集上的预测模型的所有元素。 + +## 递归多步预测 + +大多数预测建模算法将采用一些观测值作为输入并预测单个输出值。 + +因此,它们不能直接用于进行多步骤时间序列预测。 + +这适用于大多数线性,非线性和整体机器学习算法。 + +可以使用机器学习算法进行多步时间序列预测的一种方法是递归地使用它们。 + +这涉及对一个时间步进行预测,进行预测,并将其作为输入馈送到模型中,以便预测随后的时间步。重复该过程直到预测到所需数量的步骤。 + +例如: + +``` +X = [x1, x2, x3] +y1 = model.predict(X) + +X = [x2, x3, y1] +y2 = model.predict(X) + +X = [x3, y1, y2] +y3 = model.predict(X) + +... +``` + +在本节中,我们将开发一个测试工具,用于拟合和评估 scikit-learn 中提供的机器学习算法,使用递归模型进行多步预测。 + +第一步是将窗口格式的准备好的训练数据转换为单个单变量系列。 + +下面的 _to_series()_ 功能会将每周多变量数据列表转换为每日消耗的单变量单变量系列。 + +``` +# convert windows of weekly multivariate data into a series of total power +def to_series(data): + # extract just the total power from each week + series = [week[:, 0] for week in data] + # flatten into a single series + series = array(series).flatten() + return series +``` + +接下来,需要将每日电力的顺序转换成适合于监督学习问题的输入和输出。 + +预测将是前几天消耗的总功率的一些函数。我们可以选择用作输入的前几天数,例如一周或两周。总会有一个输出:第二天消耗的总功率。 + +该模型将适合先前时间步骤的真实观察结果。我们需要迭代消耗的每日功率序列并将其分成输入和输出。这称为滑动窗口数据表示。 + +下面的 _to_supervised()_ 函数实现了这种行为。 + +它将每周数据列表作为输入,以及用作创建的每个样本的输入的前几天的数量。 + +第一步是将历史记录转换为单个数据系列。然后枚举该系列,每个时间步创建一个输入和输出对。这个问题的框架将允许模型学习根据前几天的观察结果预测一周中的任何一天。该函数返回输入(X)和输出(y),以便训练模型。 + +``` +# convert history into inputs and outputs +def to_supervised(history, n_input): + # convert history to a univariate series + data = to_series(history) + X, y = list(), list() + ix_start = 0 + # step over the entire history one time step at a time + for i in range(len(data)): + # define the end of the input sequence + ix_end = ix_start + n_input + # ensure we have enough data for this instance + if ix_end < len(data): + X.append(data[ix_start:ix_end]) + y.append(data[ix_end]) + # move along one time step + ix_start += 1 + return array(X), array(y) +``` + +scikit-learn 库允许将模型用作管道的一部分。这允许在拟合模型之前自动应用数据变换。更重要的是,变换以正确的方式准备,在那里准备或适合训练数据并应用于测试数据。这可以在评估模型时防止数据泄漏。 + +在通过在训练数据集上拟合每个模型之前创建管道来评估模型时,我们可以使用此功能。在使用模型之前,我们将标准化和标准化数据。 + +下面的 _make_pipeline()_ 函数实现了这种行为,返回一个可以像模型一样使用的 Pipeline,例如它可以适合它,它可以做出预测。 + +每列执行标准化和规范化操作。在 _to_supervised()_ 功能中,我们基本上将一列数据(总功率)分成多个列,例如,七天七天的输入观察。这意味着输入数据中的七列中的每一列将具有用于标准化的不同均值和标准偏差以及用于归一化的不同最小值和最大值。 + +鉴于我们使用了滑动窗口,几乎所有值都会出现在每列中,因此,这可能不是问题。但重要的是要注意,在将数据拆分为输入和输出之前将数据缩放为单个列会更加严格。 + +``` +# create a feature preparation pipeline for a model +def make_pipeline(model): + steps = list() + # standardization + steps.append(('standardize', StandardScaler())) + # normalization + steps.append(('normalize', MinMaxScaler())) + # the model + steps.append(('model', model)) + # create pipeline + pipeline = Pipeline(steps=steps) + return pipeline +``` + +我们可以将这些元素组合成一个名为 _sklearn_predict()_ 的函数,如下所示。 + +该函数采用 scikit-learn 模型对象,训练数据,称为历史记录以及用作输入的指定数量的前几天。它将训练数据转换为输入和输出,将模型包装在管道中,使其适合,并使用它进行预测。 + +``` +# fit a model and make a forecast +def sklearn_predict(model, history, n_input): + # prepare data + train_x, train_y = to_supervised(history, n_input) + # make pipeline + pipeline = make_pipeline(model) + # fit the model + pipeline.fit(train_x, train_y) + # predict the week, recursively + yhat_sequence = forecast(pipeline, train_x[-1, :], n_input) + return yhat_sequence +``` + +模型将使用训练数据集中的最后一行作为输入以进行预测。 + +_forecast()_ 函数将使用该模型进行递归多步预测。 + +递归预测涉及迭代多步预测所需的七天中的每一天。 + +将模型的输入数据作为 _input_data_ 列表的最后几个观察值。此列表附有训练数据最后一行的所有观察结果,当我们使用模型进行预测时,它们会添加到此列表的末尾。因此,我们可以从该列表中获取最后的 _n_input_ 观测值,以实现提供先前输出作为输入的效果。 + +该模型用于对准备好的输入数据进行预测,并将输出添加到我们将返回的实际输出序列的列表和输入数据列表中,我们将从中输出观察值作为模型的输入。下一次迭代。 + +``` +# make a recursive multi-step forecast +def forecast(model, input_x, n_input): + yhat_sequence = list() + input_data = [x for x in input_x] + for j in range(7): + # prepare the input data + X = array(input_data[-n_input:]).reshape(1, n_input) + # make a one-step forecast + yhat = model.predict(X)[0] + # add to the result + yhat_sequence.append(yhat) + # add the prediction to the input + input_data.append(yhat) + return yhat_sequence +``` + +我们现在拥有使用递归多步预测策略来拟合和评估 scikit-learn 模型的所有元素。 + +我们可以更新上一节中定义的 _evaluate_model()_ 函数来调用 _sklearn_predict()_ 函数。更新的功能如下所示。 + +``` +# evaluate a single model +def evaluate_model(model, train, test, n_input): + # history is a list of weekly data + history = [x for x in train] + # walk-forward validation over each week + predictions = list() + for i in range(len(test)): + # predict the week + yhat_sequence = sklearn_predict(model, history, n_input) + # store the predictions + predictions.append(yhat_sequence) + # get real observation and add to history for predicting the next week + history.append(test[i, :]) + predictions = array(predictions) + # evaluate predictions days for each week + score, scores = evaluate_forecasts(test[:, :, 0], predictions) + return score, scores +``` + +一个重要的最终函数是 _get_models()_,它定义了一个 scikit-learn 模型对象的字典,映射到我们可以用于报告的简写名称。 + +我们将从评估一套线性算法开始。我们期望这些表现类似于自回归模型(例如,如果使用七天的输入,则为 AR(7))。 + +具有十个线性模型的 _get_models()_ 函数定义如下。 + +这是一个抽查,我们对各种算法的一般表现感兴趣,而不是优化任何给定的算法。 + +``` +# prepare a list of ml models +def get_models(models=dict()): + # linear models + models['lr'] = LinearRegression() + models['lasso'] = Lasso() + models['ridge'] = Ridge() + models['en'] = ElasticNet() + models['huber'] = HuberRegressor() + models['lars'] = Lars() + models['llars'] = LassoLars() + models['pa'] = PassiveAggressiveRegressor(max_iter=1000, tol=1e-3) + models['ranscac'] = RANSACRegressor() + models['sgd'] = SGDRegressor(max_iter=1000, tol=1e-3) + print('Defined %d models' % len(models)) + return models +``` + +最后,我们可以将所有这些结合在一起。 + +首先,加载数据集并将其拆分为训练集和测试集。 + +``` +# load the new file +dataset = read_csv('household_power_consumption_days.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +# split into train and test +train, test = split_dataset(dataset.values) +``` + +然后,我们可以准备模型字典并定义观察的前几天的数量,以用作模型的输入。 + +``` +# prepare the models to evaluate +models = get_models() +n_input = 7 +``` + +然后枚举字典中的模型,评估每个模型,总结其分数,并将结果添加到线图中。 + +下面列出了完整的示例。 + +``` +# recursive multi-step forecast with linear algorithms +from math import sqrt +from numpy import split +from numpy import array +from pandas import read_csv +from sklearn.metrics import mean_squared_error +from matplotlib import pyplot +from sklearn.preprocessing import StandardScaler +from sklearn.preprocessing import MinMaxScaler +from sklearn.pipeline import Pipeline +from sklearn.linear_model import LinearRegression +from sklearn.linear_model import Lasso +from sklearn.linear_model import Ridge +from sklearn.linear_model import ElasticNet +from sklearn.linear_model import HuberRegressor +from sklearn.linear_model import Lars +from sklearn.linear_model import LassoLars +from sklearn.linear_model import PassiveAggressiveRegressor +from sklearn.linear_model import RANSACRegressor +from sklearn.linear_model import SGDRegressor + +# split a univariate dataset into train/test sets +def split_dataset(data): + # split into standard weeks + train, test = data[1:-328], data[-328:-6] + # restructure into windows of weekly data + train = array(split(train, len(train)/7)) + test = array(split(test, len(test)/7)) + return train, test + +# evaluate one or more weekly forecasts against expected values +def evaluate_forecasts(actual, predicted): + scores = list() + # calculate an RMSE score for each day + for i in range(actual.shape[1]): + # calculate mse + mse = mean_squared_error(actual[:, i], predicted[:, i]) + # calculate rmse + rmse = sqrt(mse) + # store + scores.append(rmse) + # calculate overall RMSE + s = 0 + for row in range(actual.shape[0]): + for col in range(actual.shape[1]): + s += (actual[row, col] - predicted[row, col])**2 + score = sqrt(s / (actual.shape[0] * actual.shape[1])) + return score, scores + +# summarize scores +def summarize_scores(name, score, scores): + s_scores = ', '.join(['%.1f' % s for s in scores]) + print('%s: [%.3f] %s' % (name, score, s_scores)) + +# prepare a list of ml models +def get_models(models=dict()): + # linear models + models['lr'] = LinearRegression() + models['lasso'] = Lasso() + models['ridge'] = Ridge() + models['en'] = ElasticNet() + models['huber'] = HuberRegressor() + models['lars'] = Lars() + models['llars'] = LassoLars() + models['pa'] = PassiveAggressiveRegressor(max_iter=1000, tol=1e-3) + models['ranscac'] = RANSACRegressor() + models['sgd'] = SGDRegressor(max_iter=1000, tol=1e-3) + print('Defined %d models' % len(models)) + return models + +# create a feature preparation pipeline for a model +def make_pipeline(model): + steps = list() + # standardization + steps.append(('standardize', StandardScaler())) + # normalization + steps.append(('normalize', MinMaxScaler())) + # the model + steps.append(('model', model)) + # create pipeline + pipeline = Pipeline(steps=steps) + return pipeline + +# make a recursive multi-step forecast +def forecast(model, input_x, n_input): + yhat_sequence = list() + input_data = [x for x in input_x] + for j in range(7): + # prepare the input data + X = array(input_data[-n_input:]).reshape(1, n_input) + # make a one-step forecast + yhat = model.predict(X)[0] + # add to the result + yhat_sequence.append(yhat) + # add the prediction to the input + input_data.append(yhat) + return yhat_sequence + +# convert windows of weekly multivariate data into a series of total power +def to_series(data): + # extract just the total power from each week + series = [week[:, 0] for week in data] + # flatten into a single series + series = array(series).flatten() + return series + +# convert history into inputs and outputs +def to_supervised(history, n_input): + # convert history to a univariate series + data = to_series(history) + X, y = list(), list() + ix_start = 0 + # step over the entire history one time step at a time + for i in range(len(data)): + # define the end of the input sequence + ix_end = ix_start + n_input + # ensure we have enough data for this instance + if ix_end < len(data): + X.append(data[ix_start:ix_end]) + y.append(data[ix_end]) + # move along one time step + ix_start += 1 + return array(X), array(y) + +# fit a model and make a forecast +def sklearn_predict(model, history, n_input): + # prepare data + train_x, train_y = to_supervised(history, n_input) + # make pipeline + pipeline = make_pipeline(model) + # fit the model + pipeline.fit(train_x, train_y) + # predict the week, recursively + yhat_sequence = forecast(pipeline, train_x[-1, :], n_input) + return yhat_sequence + +# evaluate a single model +def evaluate_model(model, train, test, n_input): + # history is a list of weekly data + history = [x for x in train] + # walk-forward validation over each week + predictions = list() + for i in range(len(test)): + # predict the week + yhat_sequence = sklearn_predict(model, history, n_input) + # store the predictions + predictions.append(yhat_sequence) + # get real observation and add to history for predicting the next week + history.append(test[i, :]) + predictions = array(predictions) + # evaluate predictions days for each week + score, scores = evaluate_forecasts(test[:, :, 0], predictions) + return score, scores + +# load the new file +dataset = read_csv('household_power_consumption_days.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +# split into train and test +train, test = split_dataset(dataset.values) +# prepare the models to evaluate +models = get_models() +n_input = 7 +# evaluate each model +days = ['sun', 'mon', 'tue', 'wed', 'thr', 'fri', 'sat'] +for name, model in models.items(): + # evaluate and get scores + score, scores = evaluate_model(model, train, test, n_input) + # summarize scores + summarize_scores(name, score, scores) + # plot scores + pyplot.plot(days, scores, marker='o', label=name) +# show plot +pyplot.legend() +pyplot.show() +``` + +运行该示例将评估十个线性算法并总结结果。 + +评估每个算法并使用单行摘要报告表现,包括总体 RMSE 以及每次步骤 RMSE。 + +我们可以看到,大多数评估模型表现良好,整周误差低于 400 千瓦,随机随机梯度下降(SGD)回归量表现最佳,总体 RMSE 约为 383。 + +``` +Defined 10 models +lr: [388.388] 411.0, 389.1, 338.0, 370.8, 408.5, 308.3, 471.1 +lasso: [386.838] 403.6, 388.9, 337.3, 371.1, 406.1, 307.6, 471.6 +ridge: [387.659] 407.9, 388.6, 337.5, 371.2, 407.0, 307.7, 471.7 +en: [469.337] 452.2, 451.9, 435.8, 485.7, 460.4, 405.8, 575.1 +huber: [392.465] 412.1, 388.0, 337.9, 377.3, 405.6, 306.9, 492.5 +lars: [388.388] 411.0, 389.1, 338.0, 370.8, 408.5, 308.3, 471.1 +llars: [388.406] 396.1, 387.8, 339.3, 377.8, 402.9, 310.3, 481.9 +pa: [399.402] 410.0, 391.7, 342.2, 389.7, 409.8, 315.9, 508.4 +ranscac: [439.945] 454.0, 424.0, 369.5, 421.5, 457.5, 409.7, 526.9 +sgd: [383.177] 400.3, 386.0, 333.0, 368.9, 401.5, 303.9, 466.9 +``` + +还创建了 10 个分类器中每个分类器的每日 RMSE 的线图。 + +我们可以看到,除了两种方法之外,其他所有方法都会聚集在一起,在七天预测中表现同样出色。 + +![Line Plot of Recursive Multi-step Forecasts With Linear Algorithms](img/40914afcbafd1e361d7aad734ed47d7d.jpg) + +线性算法的递归多步预测线图 + +通过调整一些表现更好的算法的超参数可以获得更好的结果。此外,更新示例以测试一套非线性和集合算法可能会很有趣。 + +一个有趣的实验可能是评估一个或几个表现更好的算法的表现,前一天或多或少作为输入。 + +## 直接多步预测 + +多步预测的递归策略的替代方案是对每个要预测的日子使用不同的模型。 + +这称为直接多步预测策略。 + +因为我们有兴趣预测七天,所以需要准备七个不同的模型,每个模型专门用于预测不同的一天。 + +训练这种模型有两种方法: + +* **预测日**。可以准备模型来预测标准周的特定日期,例如星期一。 +* **预测提前期**。可以准备模型来预测特定的提前期,例如第 1 天 + +预测一天将更具体,但意味着每个模型可以使用更少的训练数据。预测提前期会使用更多的训练数据,但需要模型在一周的不同日期进行推广。 + +我们将在本节中探讨这两种方法。 + +### 直接日方法 + +首先,我们必须更新 _to_supervised()_ 函数以准备数据,例如用作输入的前一周观察数据以及用作输出的下周特定日期的观察结果。 + +下面列出了实现此行为的更新的 _to_supervised()_ 函数。它需要一个参数 _output_ix_ 来定义下一周的日[0,6]以用作输出。 + +``` +# convert history into inputs and outputs +def to_supervised(history, output_ix): + X, y = list(), list() + # step over the entire history one time step at a time + for i in range(len(history)-1): + X.append(history[i][:,0]) + y.append(history[i + 1][output_ix,0]) + return array(X), array(y) +``` + +此功能可以调用七次,对于所需的七种型号中的每一种都可以调用一次。 + +接下来,我们可以更新 _sklearn_predict()_ 函数,为一周预测中的每一天创建一个新数据集和一个新模型。 + +函数的主体大部分没有变化,只是在输出序列中每天在循环中使用它,其中“ _i_ ”的索引被传递给 _to_supervised 的调用( )_ 为了准备一个特定的数据集来训练模型来预测那一天。 + +该函数不再采用 _n_input_ 参数,因为我们已将输入修复为前一周的七天。 + +``` +# fit a model and make a forecast +def sklearn_predict(model, history): + yhat_sequence = list() + # fit a model for each forecast day + for i in range(7): + # prepare data + train_x, train_y = to_supervised(history, i) + # make pipeline + pipeline = make_pipeline(model) + # fit the model + pipeline.fit(train_x, train_y) + # forecast + x_input = array(train_x[-1, :]).reshape(1,7) + yhat = pipeline.predict(x_input)[0] + # store + yhat_sequence.append(yhat) + return yhat_sequence +``` + +下面列出了完整的示例。 + +``` +# direct multi-step forecast by day +from math import sqrt +from numpy import split +from numpy import array +from pandas import read_csv +from sklearn.metrics import mean_squared_error +from matplotlib import pyplot +from sklearn.preprocessing import StandardScaler +from sklearn.preprocessing import MinMaxScaler +from sklearn.pipeline import Pipeline +from sklearn.linear_model import LinearRegression +from sklearn.linear_model import Lasso +from sklearn.linear_model import Ridge +from sklearn.linear_model import ElasticNet +from sklearn.linear_model import HuberRegressor +from sklearn.linear_model import Lars +from sklearn.linear_model import LassoLars +from sklearn.linear_model import PassiveAggressiveRegressor +from sklearn.linear_model import RANSACRegressor +from sklearn.linear_model import SGDRegressor + +# split a univariate dataset into train/test sets +def split_dataset(data): + # split into standard weeks + train, test = data[1:-328], data[-328:-6] + # restructure into windows of weekly data + train = array(split(train, len(train)/7)) + test = array(split(test, len(test)/7)) + return train, test + +# evaluate one or more weekly forecasts against expected values +def evaluate_forecasts(actual, predicted): + scores = list() + # calculate an RMSE score for each day + for i in range(actual.shape[1]): + # calculate mse + mse = mean_squared_error(actual[:, i], predicted[:, i]) + # calculate rmse + rmse = sqrt(mse) + # store + scores.append(rmse) + # calculate overall RMSE + s = 0 + for row in range(actual.shape[0]): + for col in range(actual.shape[1]): + s += (actual[row, col] - predicted[row, col])**2 + score = sqrt(s / (actual.shape[0] * actual.shape[1])) + return score, scores + +# summarize scores +def summarize_scores(name, score, scores): + s_scores = ', '.join(['%.1f' % s for s in scores]) + print('%s: [%.3f] %s' % (name, score, s_scores)) + +# prepare a list of ml models +def get_models(models=dict()): + # linear models + models['lr'] = LinearRegression() + models['lasso'] = Lasso() + models['ridge'] = Ridge() + models['en'] = ElasticNet() + models['huber'] = HuberRegressor() + models['lars'] = Lars() + models['llars'] = LassoLars() + models['pa'] = PassiveAggressiveRegressor(max_iter=1000, tol=1e-3) + models['ranscac'] = RANSACRegressor() + models['sgd'] = SGDRegressor(max_iter=1000, tol=1e-3) + print('Defined %d models' % len(models)) + return models + +# create a feature preparation pipeline for a model +def make_pipeline(model): + steps = list() + # standardization + steps.append(('standardize', StandardScaler())) + # normalization + steps.append(('normalize', MinMaxScaler())) + # the model + steps.append(('model', model)) + # create pipeline + pipeline = Pipeline(steps=steps) + return pipeline + +# convert history into inputs and outputs +def to_supervised(history, output_ix): + X, y = list(), list() + # step over the entire history one time step at a time + for i in range(len(history)-1): + X.append(history[i][:,0]) + y.append(history[i + 1][output_ix,0]) + return array(X), array(y) + +# fit a model and make a forecast +def sklearn_predict(model, history): + yhat_sequence = list() + # fit a model for each forecast day + for i in range(7): + # prepare data + train_x, train_y = to_supervised(history, i) + # make pipeline + pipeline = make_pipeline(model) + # fit the model + pipeline.fit(train_x, train_y) + # forecast + x_input = array(train_x[-1, :]).reshape(1,7) + yhat = pipeline.predict(x_input)[0] + # store + yhat_sequence.append(yhat) + return yhat_sequence + +# evaluate a single model +def evaluate_model(model, train, test): + # history is a list of weekly data + history = [x for x in train] + # walk-forward validation over each week + predictions = list() + for i in range(len(test)): + # predict the week + yhat_sequence = sklearn_predict(model, history) + # store the predictions + predictions.append(yhat_sequence) + # get real observation and add to history for predicting the next week + history.append(test[i, :]) + predictions = array(predictions) + # evaluate predictions days for each week + score, scores = evaluate_forecasts(test[:, :, 0], predictions) + return score, scores + +# load the new file +dataset = read_csv('household_power_consumption_days.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +# split into train and test +train, test = split_dataset(dataset.values) +# prepare the models to evaluate +models = get_models() +# evaluate each model +days = ['sun', 'mon', 'tue', 'wed', 'thr', 'fri', 'sat'] +for name, model in models.items(): + # evaluate and get scores + score, scores = evaluate_model(model, train, test) + # summarize scores + summarize_scores(name, score, scores) + # plot scores + pyplot.plot(days, scores, marker='o', label=name) +# show plot +pyplot.legend() +pyplot.show() +``` + +首先运行该示例总结了每个模型的表现。 + +我们可以看到表现比这个问题的递归模型略差。 + +``` +Defined 10 models +lr: [410.927] 463.8, 381.4, 351.9, 430.7, 387.8, 350.4, 488.8 +lasso: [408.440] 458.4, 378.5, 352.9, 429.5, 388.0, 348.0, 483.5 +ridge: [403.875] 447.1, 377.9, 347.5, 427.4, 384.1, 343.4, 479.7 +en: [454.263] 471.8, 433.8, 415.8, 477.4, 434.4, 373.8, 551.8 +huber: [409.500] 466.8, 380.2, 359.8, 432.4, 387.0, 351.3, 470.9 +lars: [410.927] 463.8, 381.4, 351.9, 430.7, 387.8, 350.4, 488.8 +llars: [406.490] 453.0, 378.8, 357.3, 428.1, 388.0, 345.0, 476.9 +pa: [402.476] 428.4, 380.9, 356.5, 426.7, 390.4, 348.6, 471.4 +ranscac: [497.225] 456.1, 423.0, 445.9, 547.6, 521.9, 451.5, 607.2 +sgd: [403.526] 441.4, 378.2, 354.5, 423.9, 382.4, 345.8, 480.3 +``` + +还创建了每个模型的每日 RMSE 得分的线图,显示了与递归模型所见的类似的模型分组。 + +![Line Plot of Direct Per-Day Multi-step Forecasts With Linear Algorithms](img/b6ddbe35ed062ef6411b43759ddbaf87.jpg) + +线性算法的直接每日多步预测线图 + +### 直接提前期方法 + +直接提前期方法是相同的,除了 _to_supervised()_ 使用更多的训练数据集。 + +该函数与递归模型示例中定义的函数相同,只是它需要额外的 _output_ix_ 参数来定义下一周中用作输出的日期。 + +下面列出了直接每个引导时间策略的更新 _to_supervised()_ 函数。 + +与每日策略不同,此版本的功能支持可变大小的输入(不仅仅是七天),如果您愿意,可以进行实验。 + +``` +# convert history into inputs and outputs +def to_supervised(history, n_input, output_ix): + # convert history to a univariate series + data = to_series(history) + X, y = list(), list() + ix_start = 0 + # step over the entire history one time step at a time + for i in range(len(data)): + # define the end of the input sequence + ix_end = ix_start + n_input + ix_output = ix_end + output_ix + # ensure we have enough data for this instance + if ix_output < len(data): + X.append(data[ix_start:ix_end]) + y.append(data[ix_output]) + # move along one time step + ix_start += 1 + return array(X), array(y) +``` + +下面列出了完整的示例。 + +``` +# direct multi-step forecast by lead time +from math import sqrt +from numpy import split +from numpy import array +from pandas import read_csv +from sklearn.metrics import mean_squared_error +from matplotlib import pyplot +from sklearn.preprocessing import StandardScaler +from sklearn.preprocessing import MinMaxScaler +from sklearn.pipeline import Pipeline +from sklearn.linear_model import LinearRegression +from sklearn.linear_model import Lasso +from sklearn.linear_model import Ridge +from sklearn.linear_model import ElasticNet +from sklearn.linear_model import HuberRegressor +from sklearn.linear_model import Lars +from sklearn.linear_model import LassoLars +from sklearn.linear_model import PassiveAggressiveRegressor +from sklearn.linear_model import RANSACRegressor +from sklearn.linear_model import SGDRegressor + +# split a univariate dataset into train/test sets +def split_dataset(data): + # split into standard weeks + train, test = data[1:-328], data[-328:-6] + # restructure into windows of weekly data + train = array(split(train, len(train)/7)) + test = array(split(test, len(test)/7)) + return train, test + +# evaluate one or more weekly forecasts against expected values +def evaluate_forecasts(actual, predicted): + scores = list() + # calculate an RMSE score for each day + for i in range(actual.shape[1]): + # calculate mse + mse = mean_squared_error(actual[:, i], predicted[:, i]) + # calculate rmse + rmse = sqrt(mse) + # store + scores.append(rmse) + # calculate overall RMSE + s = 0 + for row in range(actual.shape[0]): + for col in range(actual.shape[1]): + s += (actual[row, col] - predicted[row, col])**2 + score = sqrt(s / (actual.shape[0] * actual.shape[1])) + return score, scores + +# summarize scores +def summarize_scores(name, score, scores): + s_scores = ', '.join(['%.1f' % s for s in scores]) + print('%s: [%.3f] %s' % (name, score, s_scores)) + +# prepare a list of ml models +def get_models(models=dict()): + # linear models + models['lr'] = LinearRegression() + models['lasso'] = Lasso() + models['ridge'] = Ridge() + models['en'] = ElasticNet() + models['huber'] = HuberRegressor() + models['lars'] = Lars() + models['llars'] = LassoLars() + models['pa'] = PassiveAggressiveRegressor(max_iter=1000, tol=1e-3) + models['ranscac'] = RANSACRegressor() + models['sgd'] = SGDRegressor(max_iter=1000, tol=1e-3) + print('Defined %d models' % len(models)) + return models + +# create a feature preparation pipeline for a model +def make_pipeline(model): + steps = list() + # standardization + steps.append(('standardize', StandardScaler())) + # normalization + steps.append(('normalize', MinMaxScaler())) + # the model + steps.append(('model', model)) + # create pipeline + pipeline = Pipeline(steps=steps) + return pipeline + +# # convert windows of weekly multivariate data into a series of total power +def to_series(data): + # extract just the total power from each week + series = [week[:, 0] for week in data] + # flatten into a single series + series = array(series).flatten() + return series + +# convert history into inputs and outputs +def to_supervised(history, n_input, output_ix): + # convert history to a univariate series + data = to_series(history) + X, y = list(), list() + ix_start = 0 + # step over the entire history one time step at a time + for i in range(len(data)): + # define the end of the input sequence + ix_end = ix_start + n_input + ix_output = ix_end + output_ix + # ensure we have enough data for this instance + if ix_output < len(data): + X.append(data[ix_start:ix_end]) + y.append(data[ix_output]) + # move along one time step + ix_start += 1 + return array(X), array(y) + +# fit a model and make a forecast +def sklearn_predict(model, history, n_input): + yhat_sequence = list() + # fit a model for each forecast day + for i in range(7): + # prepare data + train_x, train_y = to_supervised(history, n_input, i) + # make pipeline + pipeline = make_pipeline(model) + # fit the model + pipeline.fit(train_x, train_y) + # forecast + x_input = array(train_x[-1, :]).reshape(1,n_input) + yhat = pipeline.predict(x_input)[0] + # store + yhat_sequence.append(yhat) + return yhat_sequence + +# evaluate a single model +def evaluate_model(model, train, test, n_input): + # history is a list of weekly data + history = [x for x in train] + # walk-forward validation over each week + predictions = list() + for i in range(len(test)): + # predict the week + yhat_sequence = sklearn_predict(model, history, n_input) + # store the predictions + predictions.append(yhat_sequence) + # get real observation and add to history for predicting the next week + history.append(test[i, :]) + predictions = array(predictions) + # evaluate predictions days for each week + score, scores = evaluate_forecasts(test[:, :, 0], predictions) + return score, scores + +# load the new file +dataset = read_csv('household_power_consumption_days.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +# split into train and test +train, test = split_dataset(dataset.values) +# prepare the models to evaluate +models = get_models() +n_input = 7 +# evaluate each model +days = ['sun', 'mon', 'tue', 'wed', 'thr', 'fri', 'sat'] +for name, model in models.items(): + # evaluate and get scores + score, scores = evaluate_model(model, train, test, n_input) + # summarize scores + summarize_scores(name, score, scores) + # plot scores + pyplot.plot(days, scores, marker='o', label=name) +# show plot +pyplot.legend() +pyplot.show() +``` + +运行该示例总结了每个评估的线性模型的总体和每日 RMSE。 + +我们可以看到,通常每个引导时间方法产生的表现优于每日版本。这可能是因为该方法使更多的训练数据可用于模型。 + +``` +Defined 10 models +lr: [394.983] 411.0, 400.7, 340.2, 382.9, 385.1, 362.8, 469.4 +lasso: [391.767] 403.6, 394.4, 336.1, 382.7, 384.2, 360.4, 468.1 +ridge: [393.444] 407.9, 397.8, 338.9, 383.2, 383.2, 360.4, 469.6 +en: [461.986] 452.2, 448.3, 430.3, 480.4, 448.9, 396.0, 560.6 +huber: [394.287] 412.1, 394.0, 333.4, 384.1, 383.1, 364.3, 474.4 +lars: [394.983] 411.0, 400.7, 340.2, 382.9, 385.1, 362.8, 469.4 +llars: [390.075] 396.1, 390.1, 334.3, 384.4, 385.2, 355.6, 470.9 +pa: [389.340] 409.7, 380.6, 328.3, 388.6, 370.1, 351.8, 478.4 +ranscac: [439.298] 387.2, 462.4, 394.4, 427.7, 412.9, 447.9, 526.8 +sgd: [390.184] 396.7, 386.7, 337.6, 391.4, 374.0, 357.1, 473.5 +``` + +再次创建了每日 RMSE 分数的线图。 + +![Line Plot of Direct Per-Lead Time Multi-step Forecasts With Linear Algorithms](img/7bd6f63a5956d95505a62c8ed9aa9ef6.jpg) + +线性算法的直接每个引导时间多步预测的线图 + +探索混合使用每日和每时间步骤来模拟问题可能会很有趣。 + +如果增加用作每个引导时间的输入的前几天的数量是否改善了表现,例如,也可能是有趣的。使用两周的数据而不是一周。 + +## 扩展 + +本节列出了一些扩展您可能希望探索的教程的想法。 + +* **调谐模型**。选择一个表现良好的模型并调整模型超参数以进一步提高表现。 +* **调整数据准备**。在拟合每个模型之前,所有数据都被标准化并标准化;探索这些方法是否必要以及数据缩放方法的更多或不同组合是否可以带来更好的表现。 +* **探索输入大小**。输入大小限于先前观察的七天;探索越来越少的观察日作为输入及其对模型表现的影响。 +* **非线性算法**。探索一套非线性和集成机器学习算法,看看它们是否能提升表现,例如 SVM 和随机森林。 +* **多变量直接模型**。开发直接模型,利用前一周的所有输入变量,而不仅仅是每日消耗的总功率。这将需要将八个变量的七天的二维阵列展平为一维向量。 + +如果你探索任何这些扩展,我很想知道。 + +## 进一步阅读 + +如果您希望深入了解,本节将提供有关该主题的更多资源。 + +### API + +* [pandas.read_csv API](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_csv.html) +* [pandas.DataFrame.resample API](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.resample.html) +* [重采样偏移别名](http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases) +* [sklearn.metrics.mean_squared_error API](http://scikit-learn.org/stable/modules/generated/sklearn.metrics.mean_squared_error.html) +* [numpy.split API](https://docs.scipy.org/doc/numpy/reference/generated/numpy.split.html) + +### 用品 + +* [个人家庭用电数据集,UCI 机器学习库。](https://archive.ics.uci.edu/ml/datasets/individual+household+electric+power+consumption) +* [交流电源,维基百科。](https://en.wikipedia.org/wiki/AC_power) +* [多步时间序列预测的 4 种策略](https://machinelearningmastery.com/multi-step-time-series-forecasting/) + +## 摘要 + +在本教程中,您了解了如何使用机器学习算法开发递归和直接多步预测模型。 + +具体来说,你学到了: + +* 如何开发一个评估线性,非线性和集成机器学习算法的框架,用于多步时间序列预测。 +* 如何使用递归多步时间序列预测策略评估机器学习算法。 +* 如何使用直接的每日和每个引导时间多步时间序列预测策略来评估机器学习算法。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/multivariate-time-series-forecasting-lstms-keras.md b/docs/dl-ts/multivariate-time-series-forecasting-lstms-keras.md new file mode 100644 index 0000000000000000000000000000000000000000..772146e5633128d3d0894c91de1c37bb67a16679 --- /dev/null +++ b/docs/dl-ts/multivariate-time-series-forecasting-lstms-keras.md @@ -0,0 +1,688 @@ +# Keras 中 LSTM 的多变量时间序列预测 + +> 原文: [https://machinelearningmastery.com/multivariate-time-series-forecasting-lstms-keras/](https://machinelearningmastery.com/multivariate-time-series-forecasting-lstms-keras/) + +像长短期记忆(LSTM)循环神经网络这样的神经网络能够几乎无缝地模拟多个输入变量的问题。 + +这在时间序列预测中是一个很大的好处,其中经典线性方法难以适应多变量或多输入预测问题。 + +在本教程中,您将了解如何在 Keras 深度学习库中为多变量时间序列预测开发 LSTM 模型。 + +完成本教程后,您将了解: + +* 如何将原始数据集转换为可用于时间序列预测的内容。 +* 如何准备数据并使 LSTM 适合多变量时间序列预测问题。 +* 如何进行预测并将结果重新调整回原始单位。 + +让我们开始吧。 + +* **2017 年 8 月更新**:修正了在计算最终 RMSE 时与前一时间段的 obs 进行比较的错误。谢谢,Songbin Xu 和 David Righart。 +* **2017 年 10 月更新**:添加了一个新示例,显示如何根据大众需求训练多个先前时间步骤。 +* **Update Sep / 2018** :更新了数据集的链接。 + +## 教程概述 + +本教程分为 3 个部分;他们是: + +1. 空气污染预测 +2. 基本数据准备 +3. 多变量 LSTM 预测模型 + +### Python 环境 + +本教程假定您已安装 Python SciPy 环境。您可以在本教程中使用 Python 2 或 3。 + +您必须安装带有 TensorFlow 或 Theano 后端的 Keras(2.0 或更高版本)。 + +本教程还假设您安装了 scikit-learn,Pandas,NumPy 和 Matplotlib。 + +如果您需要有关环境的帮助,请参阅此帖子: + +* [如何使用 Anaconda 设置用于机器学习和深度学习的 Python 环境](http://machinelearningmastery.com/setup-python-environment-machine-learning-deep-learning-anaconda/) + +## 1.空气污染预测 + +在本教程中,我们将使用空气质量数据集。 + +这是一个数据集,在美国驻中国大使馆报告五年来每小时的天气和污染程度。 + +数据包括日期时间,称为 PM2.5 浓度的污染,以及包括露点,温度,压力,风向,风速和累积的下雪小时数的天气信息。原始数据中的完整功能列表如下: + +1. **否**:行号 +2. **年**:此行中的数据年份 +3. **月**:此行中的数据月份 +4. **天**:此行中的数据日 +5. **小时**:此行中的数据小时数 +6. **pm2.5** :PM2.5 浓度 +7. **DEWP** :露点 +8. **TEMP** :温度 +9. **PRES** :压力 +10. **cbwd** :风向相结合 +11. **Iws** :累积风速 +12. :积雪累积了几个小时 +13. **Ir** :累计下雨时间 + +我们可以使用这些数据并构建一个预测问题,考虑到前一个小时的天气条件和污染,我们预测下一个小时的污染。 + +此数据集可用于构建其他预测问题。 +你有好主意吗?请在下面的评论中告诉我。 + +您可以从 UCI 机器学习库下载数据集。 + +**更新**,我在这里镜像了数据集,因为 UCI 变得不可靠: + +* [北京 PM2.5 数据集](https://raw.githubusercontent.com/jbrownlee/Datasets/master/pollution.csv) + +下载数据集并将其放在当前工作目录中,文件名为“ _raw.csv_ ”。 + +## 2.基本数据准备 + +数据尚未准备好使用。我们必须先做好准备。 + +下面是原始数据集的前几行。 + +``` +No,year,month,day,hour,pm2.5,DEWP,TEMP,PRES,cbwd,Iws,Is,Ir +1,2010,1,1,0,NA,-21,-11,1021,NW,1.79,0,0 +2,2010,1,1,1,NA,-21,-12,1020,NW,4.92,0,0 +3,2010,1,1,2,NA,-21,-11,1019,NW,6.71,0,0 +4,2010,1,1,3,NA,-21,-14,1019,NW,9.84,0,0 +5,2010,1,1,4,NA,-20,-12,1018,NW,12.97,0,0 +``` + +第一步是将日期时间信息合并为一个日期时间,以便我们可以将其用作 Pandas 中的索引。 + +快速检查显示前 24 小时内 pm2.5 的 NA 值。因此,我们需要删除第一行数据。在数据集的后面还有一些分散的“NA”值;我们现在可以用 0 值标记它们。 + +下面的脚本加载原始数据集并将日期时间信息解析为 Pandas DataFrame 索引。删除“否”列,然后为每列指定更清晰的名称。最后,将 NA 值替换为“0”值,并删除前 24 小时。 + +删除“否”列,然后为每列指定更清晰的名称。最后,将 NA 值替换为“0”值,并删除前 24 小时。 + +``` +from pandas import read_csv +from datetime import datetime +# load data +def parse(x): + return datetime.strptime(x, '%Y %m %d %H') +dataset = read_csv('raw.csv', parse_dates = [['year', 'month', 'day', 'hour']], index_col=0, date_parser=parse) +dataset.drop('No', axis=1, inplace=True) +# manually specify column names +dataset.columns = ['pollution', 'dew', 'temp', 'press', 'wnd_dir', 'wnd_spd', 'snow', 'rain'] +dataset.index.name = 'date' +# mark all NA values with 0 +dataset['pollution'].fillna(0, inplace=True) +# drop the first 24 hours +dataset = dataset[24:] +# summarize first 5 rows +print(dataset.head(5)) +# save to file +dataset.to_csv('pollution.csv') +``` + +运行该示例将打印转换数据集的前 5 行,并将数据集保存到“ _pollution.csv_ ”。 + +``` + pollution dew temp press wnd_dir wnd_spd snow rain +date +2010-01-02 00:00:00 129.0 -16 -4.0 1020.0 SE 1.79 0 0 +2010-01-02 01:00:00 148.0 -15 -4.0 1020.0 SE 2.68 0 0 +2010-01-02 02:00:00 159.0 -11 -5.0 1021.0 SE 3.57 0 0 +2010-01-02 03:00:00 181.0 -7 -5.0 1022.0 SE 5.36 1 0 +2010-01-02 04:00:00 138.0 -7 -5.0 1022.0 SE 6.25 2 0 +``` + +现在我们以易于使用的形式获得数据,我们可以创建每个系列的快速绘图并查看我们拥有的内容。 + +下面的代码加载新的“ _pollution.csv_ ”文件,并将每个系列绘制为一个单独的子图,除了风速 dir,这是绝对的。 + +``` +from pandas import read_csv +from matplotlib import pyplot +# load dataset +dataset = read_csv('pollution.csv', header=0, index_col=0) +values = dataset.values +# specify columns to plot +groups = [0, 1, 2, 3, 5, 6, 7] +i = 1 +# plot each column +pyplot.figure() +for group in groups: + pyplot.subplot(len(groups), 1, i) + pyplot.plot(values[:, group]) + pyplot.title(dataset.columns[group], y=0.5, loc='right') + i += 1 +pyplot.show() +``` + +运行该示例将创建一个包含 7 个子图的图,显示每个变量的 5 年数据。 + +![Line Plots of Air Pollution Time Series](img/f4ae1f38157af4e020f209d5dd2b7350.jpg) + +空气污染时间序列的线图 + +## 3.多变量 LSTM 预测模型 + +在本节中,我们将使 LSTM 适应问题。 + +### LSTM 数据准备 + +第一步是为 LSTM 准备污染数据集。 + +这涉及将数据集构建为监督学习问题并对输入变量进行标准化。 + +考虑到污染测量和前一时间步的天气条件,我们将监督学习问题定为预测当前小时(t)的污染。 + +这个表述很简单,只是为了这个演示。您可以探索的其他一些秘籍包括: + +* 根据过去 24 小时内的天气状况和污染情况预测下一小时的污染情况。 +* 如上所述预测下一小时的污染,并给出下一小时的“预期”天气状况。 + +我们可以使用博客文章中开发的 _series_to_supervised()_ 函数来转换数据集: + +* [如何将时间序列转换为 Python 中的监督学习问题](http://machinelearningmastery.com/convert-time-series-supervised-learning-problem-python/) + +首先,加载“ _pollution.csv_ ”数据集。风速特征是标签编码的(整数编码)。如果您有兴趣探索它,将来可能会进一步编码。 + +接下来,将所有特征标准化,然后将数据集转换为监督学习问题。然后移除要预测的小时(t)的天气变量。 + +完整的代码清单如下。 + +``` +# convert series to supervised learning +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() + # input sequence (t-n, ... t-1) + 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)] + # forecast sequence (t, t+1, ... t+n) + 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)] + # put it all together + agg = concat(cols, axis=1) + agg.columns = names + # drop rows with NaN values + if dropnan: + agg.dropna(inplace=True) + return agg + +# load dataset +dataset = read_csv('pollution.csv', header=0, index_col=0) +values = dataset.values +# integer encode direction +encoder = LabelEncoder() +values[:,4] = encoder.fit_transform(values[:,4]) +# ensure all data is float +values = values.astype('float32') +# normalize features +scaler = MinMaxScaler(feature_range=(0, 1)) +scaled = scaler.fit_transform(values) +# frame as supervised learning +reframed = series_to_supervised(scaled, 1, 1) +# drop columns we don't want to predict +reframed.drop(reframed.columns[[9,10,11,12,13,14,15]], axis=1, inplace=True) +print(reframed.head()) +``` + +运行该示例将打印转换后的数据集的前 5 行。我们可以看到 8 个输入变量(输入系列)和 1 个输出变量(当前小时的污染水平)。 + +``` + var1(t-1) var2(t-1) var3(t-1) var4(t-1) var5(t-1) var6(t-1) \ +1 0.129779 0.352941 0.245902 0.527273 0.666667 0.002290 +2 0.148893 0.367647 0.245902 0.527273 0.666667 0.003811 +3 0.159960 0.426471 0.229508 0.545454 0.666667 0.005332 +4 0.182093 0.485294 0.229508 0.563637 0.666667 0.008391 +5 0.138833 0.485294 0.229508 0.563637 0.666667 0.009912 + + var7(t-1) var8(t-1) var1(t) +1 0.000000 0.0 0.148893 +2 0.000000 0.0 0.159960 +3 0.000000 0.0 0.182093 +4 0.037037 0.0 0.138833 +5 0.074074 0.0 0.109658 +``` + +这个数据准备很简单,我们可以探索更多。您可以看到的一些想法包括: + +* 单热编码风速。 +* 通过差分和季节性调整使所有系列保持静止。 +* 提供超过 1 小时的输入时间步长。 + +考虑到学习序列预测问题时 LSTM 在时间上使用反向传播,最后一点可能是最重要的。 + +### 定义和拟合模型 + +在本节中,我们将在多变量输入数据上拟合 LSTM。 + +首先,我们必须将准备好的数据集拆分为训练集和测试集。为了加速本演示模型的训练,我们只在数据的第一年拟合模型,然后在剩余的 4 年数据上进行评估。如果您有时间,请考虑探索此测试工具的倒置版本。 + +下面的示例将数据集拆分为训练集和测试集,然后将训练集和测试集拆分为输入和输出变量。最后,输入(X)被重新整形为 LSTM 所期望的 3D 格式,即[样本,时间步长,特征]。 + +``` +# split into train and test sets +values = reframed.values +n_train_hours = 365 * 24 +train = values[:n_train_hours, :] +test = values[n_train_hours:, :] +# split into input and outputs +train_X, train_y = train[:, :-1], train[:, -1] +test_X, test_y = test[:, :-1], test[:, -1] +# reshape input to be 3D [samples, timesteps, features] +train_X = train_X.reshape((train_X.shape[0], 1, train_X.shape[1])) +test_X = test_X.reshape((test_X.shape[0], 1, test_X.shape[1])) +print(train_X.shape, train_y.shape, test_X.shape, test_y.shape) +``` + +运行此示例打印列车的形状并测试输入和输出集,其中大约 9K 小时的数据用于训练,大约 35K 小时用于测试。 + +``` +(8760, 1, 8) (8760,) (35039, 1, 8) (35039,) +``` + +现在我们可以定义和拟合我们的 LSTM 模型。 + +我们将定义 LSTM,在第一个隐藏层中有 50 个神经元,在输出层中有 1 个神经元用于预测污染。输入形状将是 1 个步骤,具有 8 个功能。 + +我们将使用平均绝对误差(MAE)损失函数和随机梯度下降的有效 Adam 版本。 + +该模型适用于批量大小为 72 的 50 个训练时期。请记住,Keras 中 LSTM 的内部状态在每个批次结束时重置,因此内部状态可能是若干天的函数。有帮助(试试这个)。 + +最后,我们通过在 fit()函数中设置 _validation_data_ 参数来跟踪训练期间的训练和测试丢失。在运行结束时,绘制训练和测试损失。 + +``` +# design network +model = Sequential() +model.add(LSTM(50, input_shape=(train_X.shape[1], train_X.shape[2]))) +model.add(Dense(1)) +model.compile(loss='mae', optimizer='adam') +# fit network +history = model.fit(train_X, train_y, epochs=50, batch_size=72, validation_data=(test_X, test_y), verbose=2, shuffle=False) +# plot history +pyplot.plot(history.history['loss'], label='train') +pyplot.plot(history.history['val_loss'], label='test') +pyplot.legend() +pyplot.show() +``` + +### 评估模型 + +在模型拟合后,我们可以预测整个测试数据集。 + +我们将预测与测试数据集结合起来并反转缩放。我们还使用预期的污染数反转测试数据集上的缩放。 + +通过原始比例的预测和实际值,我们可以计算模型的误差分数。在这种情况下,我们计算出均方误差(RMSE),它以与变量本身相同的单位给出误差。 + +``` +# make a prediction +yhat = model.predict(test_X) +test_X = test_X.reshape((test_X.shape[0], test_X.shape[2])) +# invert scaling for forecast +inv_yhat = concatenate((yhat, test_X[:, 1:]), axis=1) +inv_yhat = scaler.inverse_transform(inv_yhat) +inv_yhat = inv_yhat[:,0] +# invert scaling for actual +test_y = test_y.reshape((len(test_y), 1)) +inv_y = concatenate((test_y, test_X[:, 1:]), axis=1) +inv_y = scaler.inverse_transform(inv_y) +inv_y = inv_y[:,0] +# calculate RMSE +rmse = sqrt(mean_squared_error(inv_y, inv_yhat)) +print('Test RMSE: %.3f' % rmse) +``` + +### 完整的例子 + +下面列出了完整的示例。 + +**注**:此示例假设您已正确准备数据,例如将下载的“ _raw.csv_ ”转换为准备好的“ _pollution.csv_ ”。请参阅本教程的第一部分。 + +``` +from math import sqrt +from numpy import concatenate +from matplotlib import pyplot +from pandas import read_csv +from pandas import DataFrame +from pandas import concat +from sklearn.preprocessing import MinMaxScaler +from sklearn.preprocessing import LabelEncoder +from sklearn.metrics import mean_squared_error +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import LSTM + +# convert series to supervised learning +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() + # input sequence (t-n, ... t-1) + 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)] + # forecast sequence (t, t+1, ... t+n) + 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)] + # put it all together + agg = concat(cols, axis=1) + agg.columns = names + # drop rows with NaN values + if dropnan: + agg.dropna(inplace=True) + return agg + +# load dataset +dataset = read_csv('pollution.csv', header=0, index_col=0) +values = dataset.values +# integer encode direction +encoder = LabelEncoder() +values[:,4] = encoder.fit_transform(values[:,4]) +# ensure all data is float +values = values.astype('float32') +# normalize features +scaler = MinMaxScaler(feature_range=(0, 1)) +scaled = scaler.fit_transform(values) +# frame as supervised learning +reframed = series_to_supervised(scaled, 1, 1) +# drop columns we don't want to predict +reframed.drop(reframed.columns[[9,10,11,12,13,14,15]], axis=1, inplace=True) +print(reframed.head()) + +# split into train and test sets +values = reframed.values +n_train_hours = 365 * 24 +train = values[:n_train_hours, :] +test = values[n_train_hours:, :] +# split into input and outputs +train_X, train_y = train[:, :-1], train[:, -1] +test_X, test_y = test[:, :-1], test[:, -1] +# reshape input to be 3D [samples, timesteps, features] +train_X = train_X.reshape((train_X.shape[0], 1, train_X.shape[1])) +test_X = test_X.reshape((test_X.shape[0], 1, test_X.shape[1])) +print(train_X.shape, train_y.shape, test_X.shape, test_y.shape) + +# design network +model = Sequential() +model.add(LSTM(50, input_shape=(train_X.shape[1], train_X.shape[2]))) +model.add(Dense(1)) +model.compile(loss='mae', optimizer='adam') +# fit network +history = model.fit(train_X, train_y, epochs=50, batch_size=72, validation_data=(test_X, test_y), verbose=2, shuffle=False) +# plot history +pyplot.plot(history.history['loss'], label='train') +pyplot.plot(history.history['val_loss'], label='test') +pyplot.legend() +pyplot.show() + +# make a prediction +yhat = model.predict(test_X) +test_X = test_X.reshape((test_X.shape[0], test_X.shape[2])) +# invert scaling for forecast +inv_yhat = concatenate((yhat, test_X[:, 1:]), axis=1) +inv_yhat = scaler.inverse_transform(inv_yhat) +inv_yhat = inv_yhat[:,0] +# invert scaling for actual +test_y = test_y.reshape((len(test_y), 1)) +inv_y = concatenate((test_y, test_X[:, 1:]), axis=1) +inv_y = scaler.inverse_transform(inv_y) +inv_y = inv_y[:,0] +# calculate RMSE +rmse = sqrt(mean_squared_error(inv_y, inv_yhat)) +print('Test RMSE: %.3f' % rmse) +``` + +首先运行该示例创建一个图表,显示训练期间的火车和测试损失。 + +有趣的是,我们可以看到测试损失低于训练损失。该模型可能过度拟合训练数据。在训练期间测量和绘制 RMSE 可能会对此有所了解。 + +![Line Plot of Train and Test Loss from the Multivariate LSTM During Training](img/c81631a61c32b40f6acdb00b05c20949.jpg) + +训练期间多变量 LSTM 的训练线路和试验损失 + +列车和测试损失在每个训练时期结束时打印。在运行结束时,将打印测试数据集上模型的最终 RMSE。 + +我们可以看到该模型实现了 26.496 的可观 RMSE,低于使用持久性模型找到的 30 的 RMSE。 + +``` +... +Epoch 46/50 +0s - loss: 0.0143 - val_loss: 0.0133 +Epoch 47/50 +0s - loss: 0.0143 - val_loss: 0.0133 +Epoch 48/50 +0s - loss: 0.0144 - val_loss: 0.0133 +Epoch 49/50 +0s - loss: 0.0143 - val_loss: 0.0133 +Epoch 50/50 +0s - loss: 0.0144 - val_loss: 0.0133 +Test RMSE: 26.496 +``` + +此模型未调整。你能做得更好吗? +请在下面的评论中告诉我您的问题框架,模型配置和 RMSE。 + +## 更新:训练多个滞后时间步长示例 + +关于如何调整上述示例以在多个先前时间步骤上训练模型,已经有许多关于建议的请求。 + +在编写原始帖子时,我尝试了这个以及无数其他配置,并决定不包括它们,因为他们没有提升模型技能。 + +尽管如此,我已将此示例作为参考模板包含在内,您可以根据自己的问题进行调整。 + +在多个先前时间步骤上训练模型所需的更改非常小,如下所示: + +首先,在调用 series_to_supervised()时必须适当地构建问题。我们将使用 3 小时的数据作为输入。另请注意,我们不再明确地删除 ob(t)处所有其他字段中的列。 + +``` +# specify the number of lag hours +n_hours = 3 +n_features = 8 +# frame as supervised learning +reframed = series_to_supervised(scaled, n_hours, 1) +``` + +接下来,我们需要更加谨慎地指定输入和输出列。 + +我们的框架数据集中有 3 * 8 + 8 列。我们将在前 3 个小时内将 3 * 8 或 24 列作为所有功能的视角输入。我们将在下一个小时将污染变量作为输出,如下所示: + +``` +# split into input and outputs +n_obs = n_hours * n_features +train_X, train_y = train[:, :n_obs], train[:, -n_features] +test_X, test_y = test[:, :n_obs], test[:, -n_features] +print(train_X.shape, len(train_X), train_y.shape) +``` + +接下来,我们可以正确地重塑输入数据以反映时间步骤和功能。 + +``` +# reshape input to be 3D [samples, timesteps, features] +train_X = train_X.reshape((train_X.shape[0], n_hours, n_features)) +test_X = test_X.reshape((test_X.shape[0], n_hours, n_features)) +``` + +拟合模型是一样的。 + +唯一的另一个小变化是如何评估模型。具体来说,我们如何重建具有 8 列的行,这些行适合于反转缩放操作以使 y 和 yhat 返回到原始比例,以便我们可以计算 RMSE。 + +更改的要点是我们将 y 或 yhat 列与测试数据集的最后 7 个特征连接起来,以便反转缩放,如下所示: + +``` +# invert scaling for forecast +inv_yhat = concatenate((yhat, test_X[:, -7:]), axis=1) +inv_yhat = scaler.inverse_transform(inv_yhat) +inv_yhat = inv_yhat[:,0] +# invert scaling for actual +test_y = test_y.reshape((len(test_y), 1)) +inv_y = concatenate((test_y, test_X[:, -7:]), axis=1) +inv_y = scaler.inverse_transform(inv_y) +inv_y = inv_y[:,0] +``` + +我们可以将所有这些修改与上述示例结合在一起。下面列出了具有多个滞后输入的多变量时间序列预测的完整示例: + +``` +from math import sqrt +from numpy import concatenate +from matplotlib import pyplot +from pandas import read_csv +from pandas import DataFrame +from pandas import concat +from sklearn.preprocessing import MinMaxScaler +from sklearn.preprocessing import LabelEncoder +from sklearn.metrics import mean_squared_error +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import LSTM + +# convert series to supervised learning +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() + # input sequence (t-n, ... t-1) + 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)] + # forecast sequence (t, t+1, ... t+n) + 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)] + # put it all together + agg = concat(cols, axis=1) + agg.columns = names + # drop rows with NaN values + if dropnan: + agg.dropna(inplace=True) + return agg + +# load dataset +dataset = read_csv('pollution.csv', header=0, index_col=0) +values = dataset.values +# integer encode direction +encoder = LabelEncoder() +values[:,4] = encoder.fit_transform(values[:,4]) +# ensure all data is float +values = values.astype('float32') +# normalize features +scaler = MinMaxScaler(feature_range=(0, 1)) +scaled = scaler.fit_transform(values) +# specify the number of lag hours +n_hours = 3 +n_features = 8 +# frame as supervised learning +reframed = series_to_supervised(scaled, n_hours, 1) +print(reframed.shape) + +# split into train and test sets +values = reframed.values +n_train_hours = 365 * 24 +train = values[:n_train_hours, :] +test = values[n_train_hours:, :] +# split into input and outputs +n_obs = n_hours * n_features +train_X, train_y = train[:, :n_obs], train[:, -n_features] +test_X, test_y = test[:, :n_obs], test[:, -n_features] +print(train_X.shape, len(train_X), train_y.shape) +# reshape input to be 3D [samples, timesteps, features] +train_X = train_X.reshape((train_X.shape[0], n_hours, n_features)) +test_X = test_X.reshape((test_X.shape[0], n_hours, n_features)) +print(train_X.shape, train_y.shape, test_X.shape, test_y.shape) + +# design network +model = Sequential() +model.add(LSTM(50, input_shape=(train_X.shape[1], train_X.shape[2]))) +model.add(Dense(1)) +model.compile(loss='mae', optimizer='adam') +# fit network +history = model.fit(train_X, train_y, epochs=50, batch_size=72, validation_data=(test_X, test_y), verbose=2, shuffle=False) +# plot history +pyplot.plot(history.history['loss'], label='train') +pyplot.plot(history.history['val_loss'], label='test') +pyplot.legend() +pyplot.show() + +# make a prediction +yhat = model.predict(test_X) +test_X = test_X.reshape((test_X.shape[0], n_hours*n_features)) +# invert scaling for forecast +inv_yhat = concatenate((yhat, test_X[:, -7:]), axis=1) +inv_yhat = scaler.inverse_transform(inv_yhat) +inv_yhat = inv_yhat[:,0] +# invert scaling for actual +test_y = test_y.reshape((len(test_y), 1)) +inv_y = concatenate((test_y, test_X[:, -7:]), axis=1) +inv_y = scaler.inverse_transform(inv_y) +inv_y = inv_y[:,0] +# calculate RMSE +rmse = sqrt(mean_squared_error(inv_y, inv_yhat)) +print('Test RMSE: %.3f' % rmse) +``` + +该模型在一两分钟内就像以前一样合适。 + +``` +... +Epoch 45/50 +1s - loss: 0.0143 - val_loss: 0.0154 +Epoch 46/50 +1s - loss: 0.0143 - val_loss: 0.0148 +Epoch 47/50 +1s - loss: 0.0143 - val_loss: 0.0152 +Epoch 48/50 +1s - loss: 0.0143 - val_loss: 0.0151 +Epoch 49/50 +1s - loss: 0.0143 - val_loss: 0.0152 +Epoch 50/50 +1s - loss: 0.0144 - val_loss: 0.0149 +``` + +绘制了在时期上的列车和测试损失的图。 + +![Plot of Loss on the Train and Test Datasets](img/b0483faab77f135fd4f690e151595898.jpg) + +列车和测试数据集损失情节 + +最后,测试 RMSE 被打印出来,至少在这个问题上并没有真正显示出技能上的任何优势。 + +``` +Test RMSE: 27.177 +``` + +我想补充一点,LSTM [似乎不适合自回归类型问题](https://machinelearningmastery.com/suitability-long-short-term-memory-networks-time-series-forecasting/),你可能最好去探索一个大窗口的 MLP。 + +我希望这个例子可以帮助您进行自己的时间序列预测实验。 + +## 进一步阅读 + +如果您要深入了解,本节将提供有关该主题的更多资源。 + +* [北京 PM2.5 数据集在 UCI 机器学习库](https://archive.ics.uci.edu/ml/datasets/Beijing+PM2.5+Data) +* [Keras 中长期短期记忆模型的 5 步生命周期](http://machinelearningmastery.com/5-step-life-cycle-long-short-term-memory-models-keras/) +* [Python 中长期短期记忆网络的时间序列预测](http://machinelearningmastery.com/time-series-forecasting-long-short-term-memory-network-python/) +* [Python 中长期短期记忆网络的多步时间序列预测](http://machinelearningmastery.com/multi-step-time-series-forecasting-long-short-term-memory-networks-python/) + +## 摘要 + +在本教程中,您了解了如何使 LSTM 适应多变量时间序列预测问题。 + +具体来说,你学到了: + +* 如何将原始数据集转换为可用于时间序列预测的内容。 +* 如何准备数据并使 LSTM 适合多变量时间序列预测问题。 +* 如何进行预测并将结果重新调整回原始单位。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/naive-methods-for-forecasting-household-electricity-consumption.md b/docs/dl-ts/naive-methods-for-forecasting-household-electricity-consumption.md new file mode 100644 index 0000000000000000000000000000000000000000..0ffd80552897454632fe4e747bf8a10be530b863 --- /dev/null +++ b/docs/dl-ts/naive-methods-for-forecasting-household-electricity-consumption.md @@ -0,0 +1,660 @@ +# 如何开发和评估朴素的家庭用电量预测方法 + +> 原文: [https://machinelearningmastery.com/naive-methods-for-forecasting-household-electricity-consumption/](https://machinelearningmastery.com/naive-methods-for-forecasting-household-electricity-consumption/) + +鉴于智能电表的兴起以及太阳能电池板等发电技术的广泛采用,可提供大量的用电数据。 + +该数据代表了多变量时间序列的功率相关变量,而这些变量又可用于建模甚至预测未来的电力消耗。 + +在本教程中,您将了解如何为“家庭功耗”数据集开发测试工具,并评估三种天真的预测策略,为更复杂的算法提供基线。 + +完成本教程后,您将了解: + +* 如何加载,准备和下采样家庭功耗数据集,为开发模型做好准备。 +* 如何为强大的测试工具开发度量标准,数据集拆分和前进验证元素,以评估预测模型。 +* 如何开发,评估和比较一套天真的持久性预测方法的表现。 + +让我们开始吧。 + +![How to Develop and Evaluate Naive Forecast Methods for Forecasting Household Electricity Consumption](img/7c4475fd7dcc3407bc39a3efbfef4da6.jpg) + +如何制定和评估用于预测家庭用电量的朴素预测方法 +照片来自 [Philippe Put](https://www.flickr.com/photos/34547181@N00/5603088218/) ,保留一些权利。 + +## 教程概述 + +本教程分为四个部分;他们是: + +1. 问题描述 +2. 加载并准备数据集 +3. 模型评估 +4. 天真的预测模型 + +## 问题描述 + +'[家庭用电量](https://archive.ics.uci.edu/ml/datasets/individual+household+electric+power+consumption)'数据集是一个多变量时间序列数据集,描述了四年内单个家庭的用电量。 + +该数据是在 2006 年 12 月至 2010 年 11 月之间收集的,并且每分钟收集家庭内的能耗观察结果。 + +它是一个多变量系列,由七个变量组成(除日期和时间外);他们是: + +* **global_active_power** :家庭消耗的总有功功率(千瓦)。 +* **global_reactive_power** :家庭消耗的总无功功率(千瓦)。 +* **电压**:平均电压(伏特)。 +* **global_intensity** :平均电流强度(安培)。 +* **sub_metering_1** :厨房的有功电能(瓦特小时的有功电能)。 +* **sub_metering_2** :用于洗衣的有功能量(瓦特小时的有功电能)。 +* **sub_metering_3** :气候控制系统的有功电能(瓦特小时的有功电能)。 + +有功和无功电能参考[交流电](https://en.wikipedia.org/wiki/AC_power)的技术细节。 + +可以通过从总活动能量中减去三个定义的子计量变量的总和来创建第四个子计量变量,如下所示: + +``` +sub_metering_remainder = (global_active_power * 1000 / 60) - (sub_metering_1 + sub_metering_2 + sub_metering_3) +``` + +## 加载并准备数据集 + +数据集可以从 UCI 机器学习库下载为单个 20 兆字节的.zip 文件: + +* [household_power_consumption.zip](https://archive.ics.uci.edu/ml/machine-learning-databases/00235/household_power_consumption.zip) + +下载数据集并将其解压缩到当前工作目录中。您现在将拥有大约 127 兆字节的文件“ _household_power_consumption.txt_ ”并包含所有观察结果。 + +我们可以使用 _read_csv()_ 函数来加载数据,并将前两列合并到一个日期时间列中,我们可以将其用作索引。 + +``` +# load all data +dataset = read_csv('household_power_consumption.txt', sep=';', header=0, low_memory=False, infer_datetime_format=True, parse_dates={'datetime':[0,1]}, index_col=['datetime']) +``` + +接下来,我们可以用'_ 标记所有[缺失值](https://machinelearningmastery.com/handle-missing-timesteps-sequence-prediction-problems-python/)?_ '具有 _NaN_ 值的字符,这是一个浮点数。 + +这将允许我们将数据作为一个浮点值数组而不是混合类型(效率较低)。 + +``` +# mark all missing values +dataset.replace('?', nan, inplace=True) +# make dataset numeric +dataset = dataset.astype('float32') +``` + +我们还需要填写缺失值,因为它们已被标记。 + +一种非常简单的方法是从前一天的同一时间复制观察。我们可以在一个名为 _fill_missing()_ 的函数中实现它,该函数将从 24 小时前获取数据的 NumPy 数组并复制值。 + +``` +# fill missing values with a value at the same time one day ago +def fill_missing(values): + one_day = 60 * 24 + for row in range(values.shape[0]): + for col in range(values.shape[1]): + if isnan(values[row, col]): + values[row, col] = values[row - one_day, col] +``` + +我们可以将此函数直接应用于 DataFrame 中的数据。 + +``` +# fill missing +fill_missing(dataset.values) +``` + +现在,我们可以使用上一节中的计算创建一个包含剩余子计量的新列。 + +``` +# add a column for for the remainder of sub metering +values = dataset.values +dataset['sub_metering_4'] = (values[:,0] * 1000 / 60) - (values[:,4] + values[:,5] + values[:,6]) +``` + +我们现在可以将清理后的数据集版本保存到新文件中;在这种情况下,我们只需将文件扩展名更改为.csv,并将数据集保存为“ _household_power_consumption.csv_ ”。 + +``` +# save updated dataset +dataset.to_csv('household_power_consumption.csv') +``` + +将所有这些结合在一起,下面列出了加载,清理和保存数据集的完整示例。 + +``` +# load and clean-up data +from numpy import nan +from numpy import isnan +from pandas import read_csv +from pandas import to_numeric + +# fill missing values with a value at the same time one day ago +def fill_missing(values): + one_day = 60 * 24 + for row in range(values.shape[0]): + for col in range(values.shape[1]): + if isnan(values[row, col]): + values[row, col] = values[row - one_day, col] + +# load all data +dataset = read_csv('household_power_consumption.txt', sep=';', header=0, low_memory=False, infer_datetime_format=True, parse_dates={'datetime':[0,1]}, index_col=['datetime']) +# mark all missing values +dataset.replace('?', nan, inplace=True) +# make dataset numeric +dataset = dataset.astype('float32') +# fill missing +fill_missing(dataset.values) +# add a column for for the remainder of sub metering +values = dataset.values +dataset['sub_metering_4'] = (values[:,0] * 1000 / 60) - (values[:,4] + values[:,5] + values[:,6]) +# save updated dataset +dataset.to_csv('household_power_consumption.csv') +``` + +运行该示例将创建新文件' _household_power_consumption.csv_ ',我们可以将其用作建模项目的起点。 + +## 模型评估 + +在本节中,我们将考虑如何开发和评估家庭电力数据集的预测模型。 + +本节分为四个部分;他们是: + +1. 问题框架 +2. 评估指标 +3. 训练和测试集 +4. 前瞻性验证 + +### 问题框架 + +有许多方法可以利用和探索家庭用电量数据集。 + +在本教程中,我们将使用这些数据来探索一个非常具体的问题;那是: + +> 鉴于最近的耗电量,未来一周的预期耗电量是多少? + +这要求预测模型预测未来七天每天的总有功功率。 + +从技术上讲,考虑到多个预测步骤,这个问题的框架被称为多步骤时间序列预测问题。利用多个输入变量的模型可以称为多变量多步时间序列预测模型。 + +这种类型的模型在规划支出方面可能有助于家庭。在供应方面,它也可能有助于规划特定家庭的电力需求。 + +数据集的这种框架还表明,将每分钟功耗的观察结果下采样到每日总数是有用的。这不是必需的,但考虑到我们对每天的总功率感兴趣,这是有道理的。 + +我们可以使用 pandas DataFrame 上的 [resample()函数](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.resample.html)轻松实现这一点。使用参数' _D_ '调用此函数允许按日期时间索引的加载数据按天分组([查看所有偏移别名](http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases))。然后,我们可以计算每天所有观测值的总和,并为八个变量中的每一个创建每日耗电量数据的新数据集。 + +下面列出了完整的示例。 + +``` +# resample minute data to total for each day +from pandas import read_csv +# load the new file +dataset = read_csv('household_power_consumption.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +# resample data to daily +daily_groups = dataset.resample('D') +daily_data = daily_groups.sum() +# summarize +print(daily_data.shape) +print(daily_data.head()) +# save +daily_data.to_csv('household_power_consumption_days.csv') +``` + +运行该示例将创建一个新的每日总功耗数据集,并将结果保存到名为“ _household_power_consumption_days.csv_ ”的单独文件中。 + +我们可以将其用作数据集,用于拟合和评估所选问题框架的预测模型。 + +### 评估指标 + +预测将包含七个值,一个用于一周中的每一天。 + +多步预测问题通常分别评估每个预测时间步长。这有助于以下几个原因: + +* 在特定提前期评论技能(例如+1 天 vs +3 天)。 +* 在不同的交付时间基于他们的技能对比模型(例如,在+1 天的模型和在日期+5 的模型良好的模型)。 + +总功率的单位是千瓦,并且具有也在相同单位的误差度量将是有用的。均方根误差(RMSE)和平均绝对误差(MAE)都符合这个要求,尽管 RMSE 更常用,将在本教程中采用。与 MAE 不同,RMSE 更能预测预测误差。 + +此问题的表现指标是从第 1 天到第 7 天的每个提前期的 RMSE。 + +作为捷径,使用单个分数总结模型的表现以帮助模型选择可能是有用的。 + +可以使用的一个可能的分数是所有预测天数的 RMSE。 + +下面的函数 _evaluate_forecasts()_ 将实现此行为并基于多个七天预测返回模型的表现。 + +``` +# evaluate one or more weekly forecasts against expected values +def evaluate_forecasts(actual, predicted): + scores = list() + # calculate an RMSE score for each day + for i in range(actual.shape[1]): + # calculate mse + mse = mean_squared_error(actual[:, i], predicted[:, i]) + # calculate rmse + rmse = sqrt(mse) + # store + scores.append(rmse) + # calculate overall RMSE + s = 0 + for row in range(actual.shape[0]): + for col in range(actual.shape[1]): + s += (actual[row, col] - predicted[row, col])**2 + score = sqrt(s / (actual.shape[0] * actual.shape[1])) + return score, scores +``` + +运行该函数将首先返回整个 RMSE,无论白天,然后每天返回一系列 RMSE 分数。 + +### 训练和测试集 + +我们将使用前三年的数据来训练预测模型和评估模型的最后一年。 + +给定数据集中的数据将分为标准周。这些是从周日开始到周六结束的周。 + +这是使用所选模型框架的现实且有用的方法,其中可以预测未来一周的功耗。它也有助于建模,其中模型可用于预测特定日期(例如星期三)或整个序列。 + +我们将数据拆分为标准周,从测试数据集向后工作。 + +数据的最后一年是 2010 年,2010 年的第一个星期日是 1 月 3 日。数据于 2010 年 11 月中旬结束,数据中最接近的最后一个星期六是 11 月 20 日。这给出了 46 周的测试数据。 + +下面提供了测试数据集的每日数据的第一行和最后一行以供确认。 + +``` +2010-01-03,2083.4539999999984,191.61000000000055,350992.12000000034,8703.600000000033,3842.0,4920.0,10074.0,15888.233355799992 +... +2010-11-20,2197.006000000004,153.76800000000028,346475.9999999998,9320.20000000002,4367.0,2947.0,11433.0,17869.76663959999 +``` + +每日数据从 2006 年底开始。 + +数据集中的第一个星期日是 12 月 17 日,这是第二行数据。 + +将数据组织到标准周内为训练预测模型提供了 159 个完整的标准周。 + +``` +2006-12-17,3390.46,226.0059999999994,345725.32000000024,14398.59999999998,2033.0,4187.0,13341.0,36946.66673200004 +... +2010-01-02,1309.2679999999998,199.54600000000016,352332.8399999997,5489.7999999999865,801.0,298.0,6425.0,14297.133406600002 +``` + +下面的函数 _split_dataset()_ 将每日数据拆分为训练集和测试集,并将每个数据组织成标准周。 + +使用特定行偏移来使用数据集的知识来分割数据。然后使用 NumPy [split()函数](https://docs.scipy.org/doc/numpy/reference/generated/numpy.split.html)将分割数据集组织成每周数据。 + +``` +# split a univariate dataset into train/test sets +def split_dataset(data): + # split into standard weeks + train, test = data[1:-328], data[-328:-6] + # restructure into windows of weekly data + train = array(split(train, len(train)/7)) + test = array(split(test, len(test)/7)) + return train, test +``` + +我们可以通过加载每日数据集并打印列车和测试集的第一行和最后一行数据来测试此功能,以确认它们符合上述预期。 + +完整的代码示例如下所示。 + +``` +# split into standard weeks +from numpy import split +from numpy import array +from pandas import read_csv + +# split a univariate dataset into train/test sets +def split_dataset(data): + # split into standard weeks + train, test = data[1:-328], data[-328:-6] + # restructure into windows of weekly data + train = array(split(train, len(train)/7)) + test = array(split(test, len(test)/7)) + return train, test + +# load the new file +dataset = read_csv('household_power_consumption_days.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +train, test = split_dataset(dataset.values) +# validate train data +print(train.shape) +print(train[0, 0, 0], train[-1, -1, 0]) +# validate test +print(test.shape) +print(test[0, 0, 0], test[-1, -1, 0]) +``` + +运行该示例表明,列车数据集确实有 159 周的数据,而测试数据集有 46 周。 + +我们可以看到,第一行和最后一行的列车和测试数据集的总有效功率与我们定义为每组标准周界限的特定日期的数据相匹配。 + +``` +(159, 7, 8) +3390.46 1309.2679999999998 +(46, 7, 8) +2083.4539999999984 2197.006000000004 +``` + +### 前瞻性验证 + +将使用称为[前进验证](https://machinelearningmastery.com/backtest-machine-learning-models-time-series-forecasting/)的方案评估模型。 + +这是需要模型进行一周预测的地方,然后该模型的实际数据可用于模型,以便它可以用作在随后一周进行预测的基础。这对于如何在实践中使用模型以及对模型有益而使其能够利用最佳可用数据都是现实的。 + +我们可以通过分离输入数据和输出/预测数据来证明这一点。 + +``` +Input, Predict +[Week1] Week2 +[Week1 + Week2] Week3 +[Week1 + Week2 + Week3] Week4 +... +``` + +评估此数据集上的预测模型的前瞻性验证方法在下面实现,命名为 _evaluate_model()_。 + +为模型提供函数的名称作为参数“ _model_func_ ”。该功能负责定义模型,使模型适合训练数据,并进行一周的预测。 + +然后使用先前定义的 _evaluate_forecasts()_ 函数,针对测试数据集评估模型所做的预测。 + +``` +# evaluate a single model +def evaluate_model(model_func, train, test): + # history is a list of weekly data + history = [x for x in train] + # walk-forward validation over each week + predictions = list() + for i in range(len(test)): + # predict the week + yhat_sequence = model_func(history) + # store the predictions + predictions.append(yhat_sequence) + # get real observation and add to history for predicting the next week + history.append(test[i, :]) + predictions = array(predictions) + # evaluate predictions days for each week + score, scores = evaluate_forecasts(test[:, :, 0], predictions) + return score, scores +``` + +一旦我们对模型进行评估,我们就可以总结表现。 + +以下名为 _summarize_scores()_ 的函数将模型的表现显示为单行,以便与其他模型进行比较。 + +``` +# summarize scores +def summarize_scores(name, score, scores): + s_scores = ', '.join(['%.1f' % s for s in scores]) + print('%s: [%.3f] %s' % (name, score, s_scores)) +``` + +我们现在已经开始评估数据集上的预测模型的所有元素。 + +## 天真的预测模型 + +在任何新的预测问题上测试天真的预测模型是很重要的。 + +来自[幼稚模型](https://machinelearningmastery.com/persistence-time-series-forecasting-with-python/)的结果提供了预测问题有多困难的定量概念,并提供了可以评估更复杂的预测方法的基准表现。 + +在本节中,我们将开发和比较三种用于家庭功率预测问题的天真预测方法;他们是: + +* 每日持续性预测。 +* 每周持续预测。 +* 每周一年的持续预测。 + +### 每日持续性预测 + +我们将开发的第一个天真的预测是每日持久性模型。 + +该模型从预测期间(例如星期六)之前的最后一天获取有效功率,并将其用作预测期间(星期日至星期六)中每天的功率值。 + +下面的 _daily_persistence()_ 函数实现了每日持久性预测策略。 + +``` +# daily persistence model +def daily_persistence(history): + # get the data for the prior week + last_week = history[-1] + # get the total active power for the last day + value = last_week[-1, 0] + # prepare 7 day forecast + forecast = [value for _ in range(7)] + return forecast +``` + +### 每周持续预测 + +预测标准周时的另一个好的天真预测是使用整个前一周作为未来一周的预测。 + +这是基于下周将与本周非常相似的想法。 + +下面的 _weekly_persistence()_ 函数实现了每周持久性预测策略。 + +``` +# weekly persistence model +def weekly_persistence(history): + # get the data for the prior week + last_week = history[-1] + return last_week[:, 0] +``` + +### 每周一年的持续预测 + +类似于上周用于预测下周的想法是使用去年同一周预测下周的想法。 + +也就是说,使用 52 周前的观察周作为预测,基于下周将与一年前的同一周相似的想法。 + +下面的 _week_one_year_ago_persistence()_ 函数实现了一年前的预测策略。 + +``` +# week one year ago persistence model +def week_one_year_ago_persistence(history): + # get the data for the prior week + last_week = history[-52] + return last_week[:, 0] +``` + +### 天真的模型比较 + +我们可以使用上一节中开发的测试工具来比较每个预测策略。 + +首先,可以加载数据集并将其拆分为训练集和测试集。 + +``` +# load the new file +dataset = read_csv('household_power_consumption_days.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +# split into train and test +train, test = split_dataset(dataset.values) +``` + +每个策略都可以根据唯一名称存储在字典中。此名称可用于打印和创建乐谱图。 + +``` +# define the names and functions for the models we wish to evaluate +models = dict() +models['daily'] = daily_persistence +models['weekly'] = weekly_persistence +models['week-oya'] = week_one_year_ago_persistence +``` + +然后,我们可以列举每个策略,使用前向验证对其进行评估,打印分数,并将分数添加到线图中以进行视觉比较。 + +``` +# evaluate each model +days = ['sun', 'mon', 'tue', 'wed', 'thr', 'fri', 'sat'] +for name, func in models.items(): + # evaluate and get scores + score, scores = evaluate_model(func, train, test) + # summarize scores + summarize_scores('daily persistence', score, scores) + # plot scores + pyplot.plot(days, scores, marker='o', label=name) +``` + +将所有这些结合在一起,下面列出了评估三种天真预测策略的完整示例。 + +``` +# naive forecast strategies +from math import sqrt +from numpy import split +from numpy import array +from pandas import read_csv +from sklearn.metrics import mean_squared_error +from matplotlib import pyplot + +# split a univariate dataset into train/test sets +def split_dataset(data): + # split into standard weeks + train, test = data[1:-328], data[-328:-6] + # restructure into windows of weekly data + train = array(split(train, len(train)/7)) + test = array(split(test, len(test)/7)) + return train, test + +# evaluate one or more weekly forecasts against expected values +def evaluate_forecasts(actual, predicted): + scores = list() + # calculate an RMSE score for each day + for i in range(actual.shape[1]): + # calculate mse + mse = mean_squared_error(actual[:, i], predicted[:, i]) + # calculate rmse + rmse = sqrt(mse) + # store + scores.append(rmse) + # calculate overall RMSE + s = 0 + for row in range(actual.shape[0]): + for col in range(actual.shape[1]): + s += (actual[row, col] - predicted[row, col])**2 + score = sqrt(s / (actual.shape[0] * actual.shape[1])) + return score, scores + +# summarize scores +def summarize_scores(name, score, scores): + s_scores = ', '.join(['%.1f' % s for s in scores]) + print('%s: [%.3f] %s' % (name, score, s_scores)) + +# evaluate a single model +def evaluate_model(model_func, train, test): + # history is a list of weekly data + history = [x for x in train] + # walk-forward validation over each week + predictions = list() + for i in range(len(test)): + # predict the week + yhat_sequence = model_func(history) + # store the predictions + predictions.append(yhat_sequence) + # get real observation and add to history for predicting the next week + history.append(test[i, :]) + predictions = array(predictions) + # evaluate predictions days for each week + score, scores = evaluate_forecasts(test[:, :, 0], predictions) + return score, scores + +# daily persistence model +def daily_persistence(history): + # get the data for the prior week + last_week = history[-1] + # get the total active power for the last day + value = last_week[-1, 0] + # prepare 7 day forecast + forecast = [value for _ in range(7)] + return forecast + +# weekly persistence model +def weekly_persistence(history): + # get the data for the prior week + last_week = history[-1] + return last_week[:, 0] + +# week one year ago persistence model +def week_one_year_ago_persistence(history): + # get the data for the prior week + last_week = history[-52] + return last_week[:, 0] + +# load the new file +dataset = read_csv('household_power_consumption_days.csv', header=0, infer_datetime_format=True, parse_dates=['datetime'], index_col=['datetime']) +# split into train and test +train, test = split_dataset(dataset.values) +# define the names and functions for the models we wish to evaluate +models = dict() +models['daily'] = daily_persistence +models['weekly'] = weekly_persistence +models['week-oya'] = week_one_year_ago_persistence +# evaluate each model +days = ['sun', 'mon', 'tue', 'wed', 'thr', 'fri', 'sat'] +for name, func in models.items(): + # evaluate and get scores + score, scores = evaluate_model(func, train, test) + # summarize scores + summarize_scores(name, score, scores) + # plot scores + pyplot.plot(days, scores, marker='o', label=name) +# show plot +pyplot.legend() +pyplot.show() +``` + +首先运行该示例打印每个模型的总分和每日分数。 + +我们可以看到每周策略的表现优于每日策略,而一年前的一周( _week-oya_ )表现稍好一些。 + +我们可以在每个模型的总体 RMSE 分数和每个预测日的每日分数中看到这一点。一个例外是第一天(星期日)的预测误差,其中似乎每日持久性模型的表现优于两周策略。 + +我们可以使用周-oya 策略,总体 RMSE 为 465.294 千瓦作为表现的基准,以便更复杂的模型在这个特定的问题框架中被认为是熟练的。 + +``` +daily: [511.886] 452.9, 596.4, 532.1, 490.5, 534.3, 481.5, 482.0 +weekly: [469.389] 567.6, 500.3, 411.2, 466.1, 471.9, 358.3, 482.0 +week-oya: [465.294] 550.0, 446.7, 398.6, 487.0, 459.3, 313.5, 555.1 +``` + +还会创建每日预测错误的折线图。 + +除了第一天的情况外,我们可以看到每周策略的相同观察模式总体上比日常策略表现更好。 + +令人惊讶的是(对我来说)一年前的一周比前一周表现更好。我原本预计上周的耗电量会更加相关。 + +检查同一图表中的所有策略表明可能导致更好表现的策略的可能组合。 + +![Line Plot Comparing Naive Forecast Strategies for Household Power Forecasting](https://3qeqpr26caki16dnhd19sv6by6v-wpengine.netdna-ssl.com/wp-content/uploads/2018/07/Line-Plot-Comparing-Naive-Forecast-Strategies-for-Household-Power-Forecasting.png) + +线路图比较家庭电力预测的朴素预测策略 + +### 扩展 + +本节列出了一些扩展您可能希望探索的教程的想法。 + +* **额外的天真战略**。提出,开发和评估一种更为天真的策略,用于预测下周的功耗。 +* **朴素合奏策略**。制定集合策略,结合三种提议的天真预测方法的预测。 +* **优化的直接持久性模型**。在直接持久性模型中测试并找到用于每个预测日的最佳相对前一天(例如-1 或-7)。 + +如果你探索任何这些扩展,我很想知道。 + +## 进一步阅读 + +如果您希望深入了解,本节将提供有关该主题的更多资源。 + +### API + +* [pandas.read_csv API](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_csv.html) +* [pandas.DataFrame.resample API](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.resample.html) +* [重采样偏移别名](http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases) +* [sklearn.metrics.mean_squared_error API](http://scikit-learn.org/stable/modules/generated/sklearn.metrics.mean_squared_error.html) +* [numpy.split API](https://docs.scipy.org/doc/numpy/reference/generated/numpy.split.html) + +### 用品 + +* [个人家庭用电量数据集,UCI 机器学习库](https://archive.ics.uci.edu/ml/datasets/individual+household+electric+power+consumption)。 +* [交流电源,维基百科](https://en.wikipedia.org/wiki/AC_power#Active,_reactive,_and_apparent_power)。 + +## 摘要 + +在本教程中,您了解了如何为家庭功耗数据集开发测试工具,并评估三种天真的预测策略,这些策略为更复杂的算法提供基线。 + +具体来说,你学到了: + +* 如何加载,准备和下采样家庭功耗数据集,以便进行建模。 +* 如何为强大的测试工具开发度量标准,数据集拆分和前进验证元素,以评估预测模型。 +* 如何开发,评估和比较一套天真的持久性预测方法的表现。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/prepare-univariate-time-series-data-long-short-term-memory-networks.md b/docs/dl-ts/prepare-univariate-time-series-data-long-short-term-memory-networks.md new file mode 100644 index 0000000000000000000000000000000000000000..ebba7ed9b1661e64ad8f941d66c53874322b56e0 --- /dev/null +++ b/docs/dl-ts/prepare-univariate-time-series-data-long-short-term-memory-networks.md @@ -0,0 +1,204 @@ +# 如何为长短期记忆网络准备单变量时间序列数据 + +> 原文: [https://machinelearningmastery.com/prepare-univariate-time-series-data-long-short-term-memory-networks/](https://machinelearningmastery.com/prepare-univariate-time-series-data-long-short-term-memory-networks/) + +当您刚刚开始深度学习时,很难准备数据。 + +长期短期记忆,或 LSTM,循环神经网络期望在 Keras Python 深度学习库中进行三维输入。 + +如果您的时间序列数据中包含数千个长序列,则必须将时间序列拆分为样本,然后将其重新整形为 LSTM 模型。 + +在本教程中,您将了解如何使用 Keras 在 Python 中为 LSTM 模型准备单变量时间序列数据。 + +让我们开始吧。 + +![How to Prepare Univariate Time Series Data for Long Short-Term Memory Networks](https://3qeqpr26caki16dnhd19sv6by6v-wpengine.netdna-ssl.com/wp-content/uploads/2017/11/How-to-Prepare-Univariate-Time-Series-Data-for-Long-Short-Term-Memory-Networks.jpg) + +如何为长期短期记忆网络准备单变量时间序列数据 +照片来自 [Miguel Mendez](https://www.flickr.com/photos/flynn_nrg/8487128120/) ,保留一些权利。 + +## 如何准备时间序列数据 + +也许我得到的最常见问题是如何为监督学习准备时间序列数据。 + +我写过一些关于这个主题的帖子,例如: + +* [如何将时间序列转换为 Python 中的监督学习问题](https://machinelearningmastery.com/convert-time-series-supervised-learning-problem-python/) +* [时间序列预测作为监督学习](https://machinelearningmastery.com/time-series-forecasting-supervised-learning/) + +但是,这些帖子对每个人都没有帮助。 + +我最近收到了这封邮件: + +> 我的数据文件中有两列有 5000 行,第 1 列是时间(间隔 1 小时),第 2 列是比特/秒,我试图预测比特/秒。在这种情况下,您可以帮我设置[LSTM]的样本,时间步长和功能吗? + +这里几乎没有问题: + +* LSTM 期望 3D 输入,并且第一次了解这个问题可能具有挑战性。 +* LSTM 不喜欢超过 200-400 个时间步长的序列,因此需要将数据拆分为样本。 + +在本教程中,我们将使用此问题作为显示为 Keras 中的 LSTM 网络专门准备数据的一种方法的基础。 + +## 1.加载数据 + +我假设你知道如何将数据加载为 Pandas Series 或 DataFrame。 + +如果没有,请参阅以下帖子: + +* [如何在 Python 中加载和探索时间序列数据](https://machinelearningmastery.com/load-explore-time-series-data-python/) +* [如何在 Python 中加载机器学习数据](https://machinelearningmastery.com/load-machine-learning-data-python/) + +在这里,我们将通过在 5,000 个时间步长内存中定义新数据集来模拟加载。 + +``` +from numpy import array + +# load... +data = list() +n = 5000 +for i in range(n): + data.append([i+1, (i+1)*10]) +data = array(data) +print(data[:5, :]) +print(data.shape) +``` + +运行此片段既会打印前 5 行数据,也会打印已加载数据的形状。 + +我们可以看到我们有 5,000 行和 2 列:标准的单变量时间序列数据集。 + +``` +[[ 1 10] + [ 2 20] + [ 3 30] + [ 4 40] + [ 5 50]] +(5000, 2) +``` + +## 2.停机时间 + +如果您的时间序列数据随着时间的推移是一致的并且没有缺失值,我们可以删除时间列。 + +如果没有,您可能希望查看插入缺失值,将数据重新采样到新的时间刻度,或者开发可以处理缺失值的模型。看帖子如: + +* [如何使用 Python 处理序列预测问题中的缺失时间步长](https://machinelearningmastery.com/handle-missing-timesteps-sequence-prediction-problems-python/) +* [如何使用 Python 处理丢失的数据](https://machinelearningmastery.com/handle-missing-data-python/) +* [如何使用 Python 重新取样和插值您的时间序列数据](https://machinelearningmastery.com/resample-interpolate-time-series-data-python/) + +在这里,我们只删除第一列: + +``` +# drop time +data = data[:, 1] +print(data.shape) +``` + +现在我们有一个 5,000 个值的数组。 + +``` +(5000,) +``` + +## 3.拆分成样品 + +LSTM 需要处理样品,其中每个样品是单个时间序列。 + +在这种情况下,5,000 个时间步长太长;根据我读过的一些论文,LSTM 可以更好地完成 200 到 400 个步骤。因此,我们需要将 5,000 个时间步骤分成多个较短的子序列。 + +我在这里写了更多关于拆分长序列的文章: + +* [如何处理具有长短期记忆循环神经网络的超长序列](https://machinelearningmastery.com/handle-long-sequences-long-short-term-memory-recurrent-neural-networks/) +* [如何准备 Keras 中截断反向传播的序列预测](https://machinelearningmastery.com/truncated-backpropagation-through-time-in-keras/) + +有很多方法可以做到这一点,你可能想根据你的问题探索一些。 + +例如,您可能需要重叠序列,也许非重叠是好的,但您的模型需要跨子序列的状态等等。 + +在这里,我们将 5,000 个时间步骤分成 25 个子序列,每个子序列有 200 个时间步长。我们将采用老式方式,而不是使用 NumPy 或 Python 技巧,以便您可以看到正在发生的事情。 + +``` +# split into samples (e.g. 5000/200 = 25) +samples = list() +length = 200 +# step over the 5,000 in jumps of 200 +for i in range(0,n,length): + # grab from i to i + 200 + sample = data[i:i+length] + samples.append(sample) +print(len(samples)) +``` + +我们现在有 25 个子序列,每个子序列有 200 个时间步长。 + +``` +25 +``` + +如果您更喜欢在一个班轮中这样做,那就去吧。我很想知道你能想出什么。 +在下面的评论中发布您的方法。 + +## 4.重塑子序列 + +LSTM 需要具有[样本,时间步长和特征]格式的数据。 + +在这里,我们有 25 个样本,每个样本 200 个时间步长和 1 个特征。 + +首先,我们需要将我们的数组列表转换为 25 x 200 的 2D NumPy 数组。 + +``` +# convert list of arrays into 2d array +data = array(samples) +print(data.shape) +``` + +运行这件作品,你应该看到: + +``` +(25, 200) +``` + +接下来,我们可以使用 _reshape()_ 函数为我们的单个特征添加一个额外的维度。 + +``` +# reshape into [samples, timesteps, features] +# expect [25, 200, 1] +data = data.reshape((len(samples), length, 1)) +print(data.shape) +``` + +就是这样。 + +现在,数据可用作 LSTM 模型的输入(X)。 + +``` +(25, 200, 1) +``` + +## 进一步阅读 + +如果您希望深入了解,本节将提供有关该主题的更多资源。 + +### 相关文章 + +* [如何将时间序列转换为 Python 中的监督学习问题](https://machinelearningmastery.com/convert-time-series-supervised-learning-problem-python/) +* [时间序列预测作为监督学习](https://machinelearningmastery.com/time-series-forecasting-supervised-learning/) +* [如何在 Python 中加载和探索时间序列数据](https://machinelearningmastery.com/load-explore-time-series-data-python/) +* [如何在 Python 中加载机器学习数据](https://machinelearningmastery.com/load-machine-learning-data-python/) +* [如何使用 Python 处理序列预测问题中的缺失时间步长](https://machinelearningmastery.com/handle-missing-timesteps-sequence-prediction-problems-python/) +* [如何使用 Python 处理丢失的数据](https://machinelearningmastery.com/handle-missing-data-python/) +* [如何使用 Python 重新取样和插值您的时间序列数据](https://machinelearningmastery.com/resample-interpolate-time-series-data-python/) +* [如何处理具有长短期记忆循环神经网络的超长序列](https://machinelearningmastery.com/handle-long-sequences-long-short-term-memory-recurrent-neural-networks/) +* [如何准备 Keras 中截断反向传播的序列预测](https://machinelearningmastery.com/truncated-backpropagation-through-time-in-keras/) + +### API + +* [Keras 的 LSTM API](https://keras.io/layers/recurrent/#lstm) +* [numpy.reshape API](https://docs.scipy.org/doc/numpy/reference/generated/numpy.reshape.html) + +## 摘要 + +在本教程中,您了解了如何将长的单变量时间序列数据转换为可用于在 Python 中训练 LSTM 模型的表单。 + +这篇文章有帮助吗?你有任何问题吗? +请在下面的评论中告诉我。 \ No newline at end of file diff --git a/docs/dl-ts/promise-recurrent-neural-networks-time-series-forecasting.md b/docs/dl-ts/promise-recurrent-neural-networks-time-series-forecasting.md new file mode 100644 index 0000000000000000000000000000000000000000..4f96ffbf9f678fdfc2af43e4942eea302615a76a --- /dev/null +++ b/docs/dl-ts/promise-recurrent-neural-networks-time-series-forecasting.md @@ -0,0 +1,127 @@ +# 循环神经网络在时间序列预测中的应用 + +> 原文: [https://machinelearningmastery.com/promise-recurrent-neural-networks-time-series-forecasting/](https://machinelearningmastery.com/promise-recurrent-neural-networks-time-series-forecasting/) + +循环神经网络是一种神经网络,它在输入观察中添加了对顺序的显式处理。 + +这种能力表明,循环神经网络的前景是学习输入序列的时间背景,以便做出更好的预测。也就是说,进行预测所需的一系列滞后观察不再必须像传统的时间序列预测那样被诊断和指定,甚至不能用经典神经网络进行预测。相反,可以学习时间依赖性,也可以学习对这种依赖性的改变。 + +在这篇文章中,您将发现循环神经网络在时间序列预测中的承诺能力。阅读这篇文章后,你会知道: + +* 传统时间序列预测方法的重点和隐含(如果不是明确的话)限制。 +* 使用传统前馈神经网络进行时间序列预测的能力。 +* 循环神经网络在传统神经网络之上做出的额外承诺以及这在实践中可能意味着什么的暗示。 + +让我们开始吧。 + +![The Promise of Recurrent Neural Networks for Time Series Forecasting](img/8a698688e3da2d77f731be3cc3c494de.jpg) + +时间序列预测的循环神经网络的承诺 +照片由 [Andy Hutchinson](https://www.flickr.com/photos/repomonkey/13909187181/) 拍摄,保留一些权利。 + +## 时间序列预测 + +时间序列预测很困难。 + +与分类和回归的简单问题不同,时间序列问题增加了观察的顺序或时间依赖性的复杂性。 + +这可能很困难,因为在拟合和评估模型时需要专门处理数据。它还有助于建模,提供趋势和季节性等额外结构,可用于提高模型技能。 + +传统上,时间序列预测一直由 ARIMA 等线性方法主导,因为它们对许多问题都有很好的理解和有效性。但是这些传统方法也受到一些限制,例如: + +* **关注完整数据**:通常不支持丢失或损坏的数据。 +* **关注线性关系**:假设线性关系排除了更复杂的关节分布。 +* **关注固定的时间依赖性**:必须诊断和指定不同时间的观察之间的关系,以及作为输入提供的滞后观察的数量。 +* **关注单变量数据**:许多现实问题都有多个输入变量。 +* **关注一步预测**:许多现实问题需要长时间的预测。 + +> 现有技术通常依赖于手工制作的特征,这些特征创建起来很昂贵并且需要该领域的专业知识。 + +- John Gamboa,[深度学习时间序列分析](https://arxiv.org/abs/1701.01887),2017 年 + +请注意,已经开发了一些专门技术来解决其中一些限制。 + +## 时间序列的神经网络 + +神经网络近似于从输入变量到输出变量的映射函数。 + +由于多种原因,这种通用能力对于时间序列是有价值的。 + +* **强健噪音**。神经网络对输入数据和映射函数中的噪声具有鲁棒性,甚至可以在存在缺失值的情况下支持学习和预测。 +* **非线性**。神经网络不会对映射函数做出强有力的假设,并且很容易学习线性和非线性关系。 + +> ......神经网络的一个重要贡献 - 即它们近似任意非线性函数的优雅能力。这个属性在时间序列处理中具有很高的价值,并承诺提供更强大的应用程序,特别是在预测的下层...... + +- Georg Dorffner,[神经网络用于时间序列处理](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.45.5697),1996。 + +更具体地,神经网络可以被配置为在映射函数中支持任意定义但固定数量的输入和输出。这意味着: + +* **多变量输入**。可以指定任意数量的输入要素,为多变量预测提供直接支持。 +* **多步预测**。可以指定任意数量的输出值,为多步骤甚至多变量预测提供直接支持。 + +仅就这些功能而言,前馈神经网络被广泛用于时间序列预测。 + +隐含于神经网络的使用是要求确实存在从输入到输出的有意义的映射以便学习。对随机游走的映射建模将不会比持久性模型表现更好(例如,使用最后看到的观察作为预测)。 + +这种可学习的映射函数的期望也使得其中一个限制变得清晰:映射函数是固定的或静态的。 + +* **固定输入**。滞后输入变量的数量是固定的,与传统的时间序列预测方法相同。 +* **固定输出**。输出变量的数量也是固定的;虽然这是一个更微妙的问题,但这意味着对于每个输入模式,必须产生一个输出。 + +> 序列对[深度神经网络]提出了挑战,因为它们要求输入和输出的维数是已知的并且是固定的。 + +- Ilya Sutskever,Oriol Vinyals,Quoc V. Le,[序列学习与神经网络](https://arxiv.org/abs/1409.3215),2014 + +前馈神经网络确实提供了很好的能力,但仍然受到必须在模型设计中预先指定时间依赖性的这一关键限制。 + +这种依赖性几乎总是未知的,必须通过固定形式的详细分析来发现和梳理。 + +## 时间序列的循环神经网络 + +当从输入到输出学习映射函数时,像长短期记忆网络这样的循环神经网络在观察之间增加了对顺序的显式处理。 + +序列的添加是近似函数的新维度。网络不是单独将输入映射到输出,而是能够随时间学习输入到输出的映射函数。 + +此功能可解锁神经网络的时间序列。 + +> 长期短期记忆(LSTM)能够解决许多时间序列任务,这些任务是由使用固定大小时间窗口的前馈网络无法解决的。 + +- Felix A. Gers,Douglas Eck,JürgenSchmidhuber,[将 LSTM 应用于通过时间窗方法预测的时间序列](https://link.springer.com/chapter/10.1007/3-540-44668-0_93),2001 + +除了使用神经网络进行时间序列预测的一般好处之外,循环神经网络还可以从数据中学习时间依赖性。 + +* **了解时间依赖性**。学习了随时间观察的背景。 + +也就是说,在最简单的情况下,网络从序列中一次显示一个观察点,并且可以了解它之前看到的哪些观察结果是相关的以及它们如何与预测相关。 + +> 由于能够在序列中学习长期相关性,LSTM 网络消除了对预先指定的时间窗口的需要,并且能够精确地建模复杂的多变量序列。 + +- Pankaj Malhotra 等,[用于时间序列异常检测的长短期记忆网络](https://www.elen.ucl.ac.be/Proceedings/esann/esannpdf/es2015-56.pdf),2015 + +循环神经网络的前景是可以学习输入数据中的时间依赖性。不需要指定一组固定的滞后观察。 + +在这个承诺中隐含的是,也可以学习随环境变化的时间依赖性。 + +但是,循环神经网络可能更多。 + +优良作法是从时间序列数据中手动识别和移除这样的系统结构,以使问题更容易建模(例如使系列静止),并且这在使用循环神经网络时仍然是最佳实践。但是,这些网络的一般能力表明,这可能不是熟练模型的要求。 + +从技术上讲,可用的上下文可能允许循环神经网络学习: + +* **趋势**。时间序列的增加或减少水平,甚至是这些变化的变化。 +* **季节性**。随着时间的推移不断重复模式。 + +您认为 LSTM 对时间序列预测问题的承诺是什么? + +## 摘要 + +在这篇文章中,您发现了循环神经网络对时间序列预测的承诺。 + +具体来说,你学到了: + +* 传统的时间序列预测方法侧重于具有线性关系的单变量数据以及固定和手动诊断的时间依赖性。 +* 神经网络增加了学习可能的噪声和非线性关系的能力,其中任意定义但固定数量的输入和输出支持多变量和多步预测。 +* 循环神经网络增加了有序观察的显式处理和从上下文学习时间依赖的承诺。 + +您是否不同意我对 LSTM 对时间序列预测的承诺的看法? +在下面发表评论并加入讨论。 \ No newline at end of file diff --git a/docs/dl-ts/remove-trends-seasonality-difference-transform-python.md b/docs/dl-ts/remove-trends-seasonality-difference-transform-python.md new file mode 100644 index 0000000000000000000000000000000000000000..f4bd477ee997db309b615990bd41f966058d6f24 --- /dev/null +++ b/docs/dl-ts/remove-trends-seasonality-difference-transform-python.md @@ -0,0 +1,282 @@ +# 如何在 Python 中使用差异变换删除趋势和季节性 + +> 原文: [https://machinelearningmastery.com/remove-trends-seasonality-difference-transform-python/](https://machinelearningmastery.com/remove-trends-seasonality-difference-transform-python/) + +时间序列数据集可能包含趋势和季节性,可能需要在建模之前将其删除。 + +趋势可能导致随时间变化的均值,而季节性可能导致随时间变化的方差,两者都将时间序列定义为非平稳。固定数据集是具有稳定均值和方差的数据集,并且反过来更容易建模。 + +差分是一种流行且广泛使用的数据变换,用于使时间序列数据静止。 + +在本教程中,您将了解如何使用 Python 将差异操作应用于时间序列数据。 + +完成本教程后,您将了解: + +* 静止和非静止时间序列之间的对比以及如何使用差分变换使一系列静止。 +* 如何应用差异变换从系列中删除线性趋势。 +* 如何应用差异变换从系列中删除季节性信号。 + +让我们开始吧。 + +![How to Remove Trends and Seasonality with a Difference Transform in Python](img/d7d2d16af4ae339a9737e0115e69b418.jpg) + +如何使用 Python 中的差异变换删除趋势和季节性 +照片由 [NOAA](https://www.flickr.com/photos/noaaphotolib/5015132522/) ,保留一些权利。 + +## 教程概述 + +本教程分为 4 个部分;他们是: + +1. 平稳 +2. 差异变换 +3. 差异化去除趋势 +4. 区别于删除季节性 + +## 平稳 + +时间序列不同于更传统的分类和回归预测建模问题。 + +时间结构为观察增加了顺序。这种强加的顺序意味着需要专门处理关于这些观察的一致性的重要假设。 + +例如,在建模时,假设观测的汇总统计数据是一致的。在时间序列术语中,我们将此期望称为时间序列是静止的。 + +通过添加趋势,季节性和其他依赖于时间的结构,可以在时间序列中容易地违反这些假设。 + +### 固定时间序列 + +静止时间序列中的观察结果不依赖于时间。 + +如果没有趋势或季节性影响,时间序列是固定的。按时间序列计算的汇总统计数据随时间变化是一致的,例如观察值的均值或方差。 + +当时间序列静止时,可以更容易建模。统计建模方法假定或要求时间序列是静止的。 + +### 非定时时间序列 + +来自非平稳时间序列的观测显示了季节性影响,趋势和依赖于时间指数的其他结构。 + +像平均值和方差这样的汇总统计数据会随着时间的推移而发生变化,从而使模型可能尝试捕获的概念发生偏差。 + +经典时间序列分析和预测方法涉及通过识别和消除趋势以及消除静止效应来使非平稳时间序列数据静止。 + +### 使系列数据固定 + +您可以通过查看系列随时间变化的线图来检查您的时间序列是否静止。 + +系列中明显趋势,季节性或其他系统结构的标志是非平稳序列的指标。 + +更准确的方法是使用统计测试,例如 Dickey-Fuller 测试。 + +你应该让你的时间序列固定吗? + +一般来说,是的。 + +如果您的时间序列中有明确的趋势和季节性,那么对这些组件进行建模,将其从观察中移除,然后在残差上训练模型。 + +> 如果我们将静态模型拟合到数据中,我们假设我们的数据是静止过程的实现。因此,我们分析的第一步应该是检查是否有任何趋势或季节性影响的证据,如果有,则删除它们。 + +- 第 122 页,[介绍时间序列与 R](http://www.amazon.com/dp/0387886974?tag=inspiredalgor-20) 。 + +统计时间序列方法甚至现代机器学习方法将受益于数据中更清晰的信号。 + +## 差异变换 + +差分是一种转换时间序列数据集的方法。 + +它可用于消除序列对时间的依赖性,即所谓的时间依赖性。这包括趋势和季节性等结构。 + +> 差异可以通过消除时间序列水平的变化来帮助稳定时间序列的均值,从而消除(或减少)趋势和季节性。 + +- 第 215 页,[预测:原则和实践](http://www.amazon.com/dp/0987507109?tag=inspiredalgor-20)。 + +通过从当前观察中减去先前的观察来执行差分。 + +``` +difference(t) = observation(t) - observation(t-1) +``` + +当必须将预测转换回原始比例时,需要反转该过程。 + +通过将先前时间步骤的观察值添加到差值,可以反转该过程。 + +``` +inverted(t) = differenced(t) + observation(t-1) +``` + +以这种方式,可以计算一系列差异和反转差异。 + +### 滞后差异 + +将连续观察之间的差异称为滞后-1 差异。 + +可以调整滞后差异以适应特定的时间结构。 + +对于具有季节性成分的时间序列,滞后可以预期为季节性的周期(宽度)。 + +### 差异订单 + +在执行差分运算之后,某些时间结构可能仍然存在,例如在非线性趋势的情况下。 + +这样,差分过程可以重复多次,直到所有时间依赖性都被消除。 + +执行差分的次数称为差分顺序。 + +### 计算差分 + +我们可以手动区分数据集。 + +这涉及开发一个创建差异数据集的新功能。该函数将遍历提供的序列并以指定的间隔或滞后计算差异值。 + +名为 difference()的下面的函数实现了这个过程。 + +``` +# 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) +``` + +我们可以看到该函数在指定的时间间隔后小心地开始差异数据集,以确保实际上可以计算差值。定义默认间隔或滞后值 1。这是一个合理的默认值。 + +进一步的改进是还能够指定执行差分操作的次序或次数。 + +下面名为 inverse_difference()的函数会反转单个预测的差异运算。它还要求提供前一时间步的实际观测值。 + +``` +# invert differenced forecast +def inverse_difference(last_ob, value): + return value + last_ob +``` + +## 差异化去除趋势 + +在本节中,我们将介绍使用差异变换来移除趋势。 + +趋势通过提高水平使时间序列非静止。这具有随时间改变平均时间序列值的效果。 + +下面的示例将 difference()函数应用于具有线性增加趋势的人为数据集。 + +``` +# 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 diff + +# invert differenced forecast +def inverse_difference(last_ob, value): + return value + last_ob + +# define a dataset with a linear trend +data = [i+1 for i in range(20)] +print(data) +# difference the dataset +diff = difference(data) +print(diff) +# invert the difference +inverted = [inverse_difference(data[i], diff[i]) for i in range(len(diff))] +print(inverted) +``` + +运行该示例首先使用线性趋势打印设计序列。接下来,打印差异数据集,显示每个时间步长增加一个单位。该序列的长度为 19 而不是 20,因为序列中第一个值的差异无法计算,因为没有先前值。 + +最后,使用来自原始序列的先验值作为每个变换的引物来反转差异序列。 + +``` +[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] +[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] +[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] +``` + +## 区别于删除季节性 + +在本节中,我们将介绍使用差异变换来消除季节性。 + +季节性变化或季节性是随着时间的推移而定期重复的循环。 + +> 每年内的重复模式称为季节变化,尽管该术语更普遍地应用于任何固定时期内的重复模式。 + +- 第 6 页, [R](http://www.amazon.com/dp/0387886974?tag=inspiredalgor-20) 的时间序列介绍。 + +季节性有很多种类。一些明显的例子包括:时间,每日,每周,每月,每年等。因此,确定时间序列问题中是否存在季节性因素是主观的。 + +确定是否存在季节性方面的最简单方法是绘制和检查您的数据,可能是在不同的尺度和添加趋势线。 + +下面的示例将 difference()函数应用于人为的季节性数据集。该数据集包括两个循环,每个循环 360 个单元。 + +``` +from math import sin +from math import radians +from matplotlib import pyplot + +# 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 diff + +# invert differenced forecast +def inverse_difference(last_ob, value): + return value + last_ob + +# define a dataset with a linear trend +data = [sin(radians(i)) for i in range(360)] + [sin(radians(i)) for i in range(360)] +pyplot.plot(data) +pyplot.show() +# difference the dataset +diff = difference(data, 360) +pyplot.plot(diff) +pyplot.show() +# invert the difference +inverted = [inverse_difference(data[i], diff[i]) for i in range(len(diff))] +pyplot.plot(inverted) +pyplot.show() +``` + +首先运行示例创建并绘制 360 时间步长序列的两个循环的数据集。 + +![Line plot of a contrived sesonal dataset](img/d81e423a4166806357562711b5c56d6d.jpg) + +人工数据集的线图 + +接下来,应用差异变换并绘制结果。该图显示了 360 个零值,并删除了所有季节性信号。 + +在上面的去趋势示例中,差值应用滞后为 1,这意味着牺牲了第一个值。这里整个周期用于差分,即 360 个时间步长。结果是牺牲整个第一循环以区分第二循环。 + +![Line plot of the differenced seasonal dataset](img/e39fd234348ba17ead30334c32d345ae.jpg) + +差异季节性数据集的线图 + +最后,转换反转显示第二周期恢复季节性。 + +![Line plot of the differenced dataset with the inverted difference transform](img/1ff46115e178dd087ba95e3d279a687b.jpg) + +差分数据集的线图与反向差分变换 + +## 进一步阅读 + +* 维基百科上的[固定过程](https://en.wikipedia.org/wiki/Stationary_process) +* 维基百科上的[季节性调整](https://en.wikipedia.org/wiki/Seasonal_adjustment) +* [如何使用 Python 检查时间序列数据是否固定](http://machinelearningmastery.com/time-series-data-stationary-python/) +* [如何区分时间序列数据集与 Python](http://machinelearningmastery.com/difference-time-series-dataset-python/) +* [如何使用 Python 识别和删除时间序列数据中的季节性](http://machinelearningmastery.com/time-series-seasonality-with-python/) +* [使用 Python 进行季节性持久性预测](http://machinelearningmastery.com/seasonal-persistence-forecasting-python/) + +## 摘要 + +在本教程中,您发现了静态和非平稳时间序列之间的区别,以及如何使用差异变换来消除 Python 的趋势和季节性。 + +具体来说,你学到了: + +* 静止和非静止时间序列之间的对比以及如何使用差分变换使一系列静止。 +* 如何应用差异变换从系列中删除线性趋势。 +* 如何应用差异变换从系列中删除季节性信号。 + +你对使时间序列固定有任何疑问吗? +在评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/seed-state-lstms-time-series-forecasting-python.md b/docs/dl-ts/seed-state-lstms-time-series-forecasting-python.md new file mode 100644 index 0000000000000000000000000000000000000000..d94e99674d5e39f419543bc1d8d8843e430976c4 --- /dev/null +++ b/docs/dl-ts/seed-state-lstms-time-series-forecasting-python.md @@ -0,0 +1,451 @@ +# 如何在 LSTM 中种子状态用于 Python 中的时间序列预测 + +> 原文: [https://machinelearningmastery.com/seed-state-lstms-time-series-forecasting-python/](https://machinelearningmastery.com/seed-state-lstms-time-series-forecasting-python/) + +长短期记忆网络(LSTM)是一种强大的循环神经网络,能够学习长序列观察。 + +LSTM 的承诺是它们可能在时间序列预测中有效,尽管已知该方法难以配置和用于这些目的。 + +LSTM 的一个关键特征是它们保持内部状态,有助于预测。这提出了在进行预测之前如何最好地使拟合 LSTM 模型的状态播种的问题。 + +在本教程中,您将了解如何设计,执行和解释实验的结果,以探索是否更好地从训练数据集中获取拟合 LSTM 的状态或不使用先前状态。 + +完成本教程后,您将了解: + +* 关于如何最好地初始化适合 LSTM 状态以进行预测的未决问题。 +* 如何开发一个强大的测试工具来评估 LSTM 模型的单变量时间序列预测问题。 +* 如何确定在预测之前是否播种 LSTM 的状态对于您的时间序列预测问题是一个好主意。 + +让我们开始吧。 + +![How to Seed State for LSTMs for Time Series Forecasting in Python](img/05031ee46bb81b6491c1604cc4553aea.jpg) + +如何为 LSTM 制作状态用于 Python 中的时间序列预测 +照片由 [Tony Hisgett](https://www.flickr.com/photos/hisgett/6940877193/) ,保留一些权利。 + +## 教程概述 + +本教程分为 5 个部分;他们是: + +1. 播种 LSTM 国家 +2. 洗发水销售数据集 +3. LSTM 模型和测试线束 +4. 代码清单 +5. 实验结果 + +### 环境 + +本教程假定您已安装 Python SciPy 环境。您可以在此示例中使用 Python 2 或 3。 + +您必须安装带有 TensorFlow 或 Theano 后端的 Keras(2.0 或更高版本)。 + +本教程还假设您安装了 scikit-learn,Pandas,NumPy 和 Matplotlib。 + +如果您在设置 Python 环境时需要帮助,请参阅以下帖子: + +* [如何使用 Anaconda 设置用于机器学习和深度学习的 Python 环境](http://machinelearningmastery.com/setup-python-environment-machine-learning-deep-learning-anaconda/) + +## 播种 LSTM 国家 + +在 Keras 中使用无状态 LSTM 时,您可以在清除模型的内部状态时进行细粒度控制。 + +这是使用 _model.reset_states()_ 函数实现的。 + +在训练有状态 LSTM 时,重要的是清除训练时期之间的模型状态。这使得在时期训练期间建立的国家与时代中的观察序列相称。 + +鉴于我们有这种细粒度控制,在进行预测之前,是否以及如何初始化 LSTM 的状态存在一个问题。 + +选项是: + +* 在预测之前重置状态。 +* 在预测之前使用训练数据集初始化状态。 + +假设使用训练数据初始化模型的状态将是优越的,但这需要通过实验来确认。 + +此外,可能有多种方法来种植这种状态;例如: + +* 完成训练时期,包括体重更新。例如,不要在上一个训练时期结束时重置。 +* 完成训练数据的预测。 + +通常,认为这两种方法在某种程度上是等同的。后者预测训练数据集是首选,因为它不需要对网络权重进行任何修改,并且可以是保存到文件的不可变网络的可重复过程。 + +在本教程中,我们将考虑以下两者之间的区别: + +* 使用没有状态的拟合 LSTM 预测测试数据集(例如,在重置之后)。 +* 在预测了训练数据集之后,使用状态 LSTM 预测测试数据集。 + +接下来,让我们看一下我们将在本实验中使用的标准时间序列数据集。 + +## 洗发水销售数据集 + +该数据集描述了 3 年期间每月洗发水的销售数量。 + +单位是销售计数,有 36 个观察。原始数据集归功于 Makridakis,Wheelwright 和 Hyndman(1998)。 + +[您可以在此处下载并了解有关数据集的更多信息](https://datamarket.com/data/set/22r0/sales-of-shampoo-over-a-three-year-period)。 + +下面的示例加载并创建已加载数据集的图。 + +``` +# 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](img/ab56d09b72803271b91fa5bd0ccc3f0f.jpg) + +洗发水销售数据集的线图 + +接下来,我们将了解实验中使用的 LSTM 配置和测试工具。 + +## LSTM 模型和测试线束 + +### 数据拆分 + +我们将 Shampoo Sales 数据集分为两部分:训练和测试集。 + +前两年的数据将用于训练数据集,剩余的一年数据将用于测试集。 + +将使用训练数据集开发模型,并对测试数据集进行预测。 + +### 模型评估 + +将使用滚动预测场景,也称为前进模型验证。 + +测试数据集的每个时间步骤将一次一个地走。将使用模型对时间步长进行预测,然后将获取测试集的实际预期值,并使其可用于下一时间步的预测模型。 + +这模仿了一个真实世界的场景,每个月都会有新的洗发水销售观察结果,并用于下个月的预测。 + +这将通过列车和测试数据集的结构进行模拟。我们将以一次性方法进行所有预测。 + +将收集关于测试数据集的所有预测,并计算错误分数以总结模型的技能。将使用均方根误差(RMSE),因为它会对大错误进行处罚,并产生与预测数据相同的分数,即每月洗发水销售额。 + +### 数据准备 + +在我们将 LSTM 模型拟合到数据集之前,我们必须转换数据。 + +在拟合模型和进行预测之前,对数据集执行以下三个数据变换。 + +1. **转换时间序列数据,使其静止**。具体地,_ 滞后= 1_ 差异以消除数据中的增加趋势。 +2. **将时间序列转换为监督学习问题**。具体而言,将数据组织成输入和输出模式,其中前一时间步骤的观察被用作在当前时间步长预测观察的输入。 +3. **将观察结果转换为具有特定比例**。具体而言,要将数据重新调整为-1 到 1 之间的值,以满足 LSTM 模型的默认双曲正切激活函数。 + +### LSTM 模型 + +将使用熟练但未调整的 LSTM 模型配置。 + +这意味着模型将适合数据并且能够进行有意义的预测,但不会是数据集的最佳模型。 + +网络拓扑由 1 个输入,4 个单元的隐藏层和 1 个输出值的输出层组成。 + +该模型适用于批量大小为 4 的 3,000 个时期。数据准备后,训练数据集将减少到 20 个观测值。这样,批量大小均匀地分为训练数据集和测试数据集(需求)。 + +### 实验运行 + +每个方案将运行 30 次。 + +这意味着将为每个方案创建和评估 30 个模型。将收集每次运行的 RMSE,提供一组结果,可使用描述性统计数据(如均值和标准差)进行汇总。 + +这是必需的,因为像 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) + 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, seed): + # 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, 3000, 4) + # forecast the entire training dataset to build up state for forecasting + if seed: + 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() +# with seeding +with_seed = experiment(repeats, series, True) +results['with-seed'] = with_seed +# without seeding +without_seed = experiment(repeats, series, False) +results['without-seed'] = without_seed +# summarize results +print(results.describe()) +# save boxplot +results.boxplot() +pyplot.savefig('boxplot.png') +``` + +## 实验结果 + +运行实验需要一些时间或 CPU 或 GPU 硬件。 + +打印每次运行的 RMSE 以了解进度。 + +在运行结束时,将为每个方案计算和打印摘要统计信息,包括平均值和标准差。 + +完整输出如下所示。 + +``` +1) Test RMSE: 86.566 +2) Test RMSE: 300.874 +3) Test RMSE: 169.237 +4) Test RMSE: 167.939 +5) Test RMSE: 135.416 +6) Test RMSE: 291.746 +7) Test RMSE: 220.729 +8) Test RMSE: 222.815 +9) Test RMSE: 236.043 +10) Test RMSE: 134.183 +11) Test RMSE: 145.320 +12) Test RMSE: 142.771 +13) Test RMSE: 239.289 +14) Test RMSE: 218.629 +15) Test RMSE: 208.855 +16) Test RMSE: 187.421 +17) Test RMSE: 141.151 +18) Test RMSE: 174.379 +19) Test RMSE: 241.310 +20) Test RMSE: 226.963 +21) Test RMSE: 126.777 +22) Test RMSE: 197.340 +23) Test RMSE: 149.662 +24) Test RMSE: 235.681 +25) Test RMSE: 200.320 +26) Test RMSE: 92.396 +27) Test RMSE: 169.573 +28) Test RMSE: 219.894 +29) Test RMSE: 168.048 +30) Test RMSE: 141.638 +1) Test RMSE: 85.470 +2) Test RMSE: 151.935 +3) Test RMSE: 102.314 +4) Test RMSE: 215.588 +5) Test RMSE: 172.948 +6) Test RMSE: 114.746 +7) Test RMSE: 205.958 +8) Test RMSE: 89.335 +9) Test RMSE: 183.635 +10) Test RMSE: 173.400 +11) Test RMSE: 116.645 +12) Test RMSE: 133.473 +13) Test RMSE: 155.044 +14) Test RMSE: 153.582 +15) Test RMSE: 146.693 +16) Test RMSE: 95.455 +17) Test RMSE: 104.970 +18) Test RMSE: 127.700 +19) Test RMSE: 189.728 +20) Test RMSE: 127.756 +21) Test RMSE: 102.795 +22) Test RMSE: 189.742 +23) Test RMSE: 144.621 +24) Test RMSE: 132.053 +25) Test RMSE: 238.034 +26) Test RMSE: 139.800 +27) Test RMSE: 202.881 +28) Test RMSE: 172.278 +29) Test RMSE: 125.565 +30) Test RMSE: 103.868 + with-seed without-seed +count 30.000000 30.000000 +mean 186.432143 146.600505 +std 52.559598 40.554595 +min 86.565993 85.469737 +25% 143.408162 115.221000 +50% 180.899814 142.210265 +75% 222.293194 173.287017 +max 300.873841 238.034137 +``` + +还会创建一个框和胡须图并将其保存到文件中,如下所示。 + +![Box and Whisker Plot of LSTM with and Without Seed of State](img/a6ea8e6072ec5a377562eae3a0b4a337.jpg) + +含有和不含种子的 LSTM 的盒子和晶须图 + +结果令人惊讶。 + +他们建议通过在预测测试数据集之前不播种 LSTM 的状态来获得更好的结果。 + +这可以通过每月洗发水销售额 146.600505 的平均误差较低来看出,而播种的平均误差为 186.432143。分布的盒子和须状图更加清晰。 + +也许所选择的模型配置导致模型太小而不依赖于序列和内部状态以在预测之前受益于播种。也许需要更大的实验。 + +### 扩展 + +令人惊讶的结果为进一步的实验打开了大门。 + +* 评估在最后一个训练时期结束后清除与不清除状态的影响。 +* 评估一次预测训练和测试集的效果,一次评估一个时间步。 +* 评估在每个迭代结束时重置和不重置 LSTM 状态的效果。 + +你尝试过其中一种扩展吗?在下面的评论中分享您的发现。 + +## 摘要 + +在本教程中,您了解了如何通过实验确定在单变量时间序列预测问题上为 LSTM 模型的状态设定种子的最佳方法。 + +具体来说,你学到了: + +* 关于在预测之前播种 LSTM 状态的问题及解决方法。 +* 如何开发一个强大的测试工具来评估 LSTM 模型的时间序列预测。 +* 如何在预测之前确定是否使用训练数据对 LSTM 模型的状态进行种子化。 + +您是否运行了实验或运行了实验的修改版本? +在评论中分享您的结果;我很想见到他们。 + +你对这篇文章有任何疑问吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/stateful-stateless-lstm-time-series-forecasting-python.md b/docs/dl-ts/stateful-stateless-lstm-time-series-forecasting-python.md new file mode 100644 index 0000000000000000000000000000000000000000..d18def3b4377f4a7b40bd66a74345e40ef99bd2f --- /dev/null +++ b/docs/dl-ts/stateful-stateless-lstm-time-series-forecasting-python.md @@ -0,0 +1,771 @@ +# 使用 Python 进行时间序列预测的有状态和无状态 LSTM + +> 原文: [https://machinelearningmastery.com/stateful-stateless-lstm-time-series-forecasting-python/](https://machinelearningmastery.com/stateful-stateless-lstm-time-series-forecasting-python/) + +Keras Python 深度学习库支持有状态和无状态长短期内存(LSTM)网络。 + +使用有状态 LSTM 网络时,我们可以对 LSTM 网络的内部状态何时重置进行细粒度控制。因此,在拟合和使用 LSTM 网络进行预测影响网络技能时,了解管理此内部状态的不同方法非常重要。 + +在本教程中,您将探索 Keras 中有状态和无状态 LSTM 网络在时间序列预测中的表现。 + +完成本教程后,您将了解: + +* 如何比较和对比状态和无状态 LSTM 网络的时间序列预测。 +* 无状态 LSTM 中的批量大小如何与有状态 LSTM 网络相关。 +* 如何评估和比较有状态 LSTM 网络的不同状态重置方案。 + +让我们开始吧。 + +![Stateful and Stateless LSTM for Time Series Forecasting with Python](img/f8b3d7cc98a4b1c950164ed026b6aa20.jpg) + +使用 Python 进行时间序列预测的有状态和无状态 LSTM +照片由 [m01229](https://www.flickr.com/photos/39908901@N06/33414952872/) ,保留一些权利。 + +## 教程概述 + +本教程分为 7 个部分。他们是: + +1. 洗发水销售数据集 +2. 实验测试线束 +3. A 与 A 测试 +4. 有状态与无国籍 +5. 大批量与无状态无国籍 +6. 有状态重置与无状态重置 +7. 审查结果 + +### 环境 + +本教程假定您已安装 Python SciPy 环境。您可以在此示例中使用 Python 2 或 3。 + +本教程假设您安装了 TensorFlow 或 Theano 后端的 Keras v2.0 或更高版本。 + +本教程还假设您安装了 scikit-learn,Pandas,NumPy 和 Matplotlib。 + +如果您在设置 Python 环境时需要帮助,请参阅以下帖子: + +* [如何使用 Anaconda 设置用于机器学习和深度学习的 Python 环境](http://machinelearningmastery.com/setup-python-environment-machine-learning-deep-learning-anaconda/) + +## 洗发水销售数据集 + +该数据集描述了 3 年期间每月洗发水的销售数量。 + +单位是销售计数,有 36 个观察。原始数据集归功于 Makridakis,Wheelwright 和 Hyndman(1998)。 + +[您可以在此处下载并了解有关数据集的更多信息](https://datamarket.com/data/set/22r0/sales-of-shampoo-over-a-three-year-period)。 + +下面的示例加载并创建已加载数据集的图。 + +``` +# 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](img/646e3de8684355414799cd9964ad1d4f.jpg) + +洗发水销售数据集的线图 + +接下来,我们将了解实验中使用的 LSTM 配置和测试工具。 + +## 实验测试线束 + +本节介绍本教程中使用的测试工具。 + +### 数据拆分 + +我们将 Shampoo Sales 数据集分为两部分:训练和测试集。 + +前两年的数据将用于训练数据集,剩余的一年数据将用于测试集。 + +将使用训练数据集开发模型,并对测试数据集进行预测。 + +测试数据集的持久性预测(天真预测)实现了每月洗发水销售 136.761 的错误。这在测试集上提供了较低的可接受表现限制。 + +### 模型评估 + +将使用滚动预测场景,也称为前进模型验证。 + +测试数据集的每个时间步骤将一次一个地走。将使用模型对时间步长进行预测,然后将获取测试集的实际预期值,并使其可用于下一时间步的预测模型。 + +这模仿了一个真实世界的场景,每个月都会有新的洗发水销售观察结果,并用于下个月的预测。 + +这将通过列车和测试数据集的结构进行模拟。 + +将收集关于测试数据集的所有预测,并计算错误分数以总结模型的技能。将使用均方根误差(RMSE),因为它会对大错误进行处罚,并产生与预测数据相同的分数,即每月洗发水销售额。 + +### 数据准备 + +在我们将 LSTM 模型拟合到数据集之前,我们必须转换数据。 + +在拟合模型和进行预测之前,对数据集执行以下三个数据变换。 + +1. **转换时间序列数据,使其静止**。具体而言,滞后= 1 差分以消除数据中的增加趋势。 +2. **将时间序列转换为监督学习问题**。具体而言,将数据组织成输入和输出模式,其中前一时间步的观察被用作预测当前时间步的观察的输入 +3. **将观察结果转换为具有特定比例**。具体而言,要将数据重新调整为-1 到 1 之间的值,以满足 LSTM 模型的默认双曲正切激活函数。 + +这些变换在预测时反转,在计算和误差分数之前将它们恢复到原始比例。 + +### LSTM 模型 + +我们将使用基础状态 LSTM 模型,其中 1 个神经元适合 1000 个时期。 + +批量大小为 1 是必需的,因为我们将使用前向验证并对最后 12 个月的测试数据进行一步预测。 + +批量大小为 1 意味着该模型将使用在线训练(而不是批量训练或小批量训练)。因此,预计模型拟合将具有一些变化。 + +理想情况下,将使用更多的训练时期(例如 1500),但这被截断为 1000 以保持运行时间合理。 + +使用有效的 ADAM 优化算法和均方误差损失函数来拟合模型。 + +### 实验运行 + +每个实验场景将运行 10 次。 + +其原因在于,每次训练给定配置时,LSTM 网络的随机初始条件可能导致非常不同的结果。 + +让我们深入研究实验。 + +## A 与 A 测试 + +一个好的第一个实验是评估我们的测试安全带的噪音或可靠性。 + +这可以通过运行相同的实验两次并比较结果来评估。在 [A / B 测试](https://en.wikipedia.org/wiki/A/B_testing)的世界中,这通常被称为 A 对 A 测试,我觉得这个名字很有用。我们的想法是用实验清除任何明显的错误,并掌握平均值的预期方差。 + +我们将在网络上运行两次有状态 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) + 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[1:,:] + # 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, 1000, 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_stateful.csv', index=False) + + # entry point +run() +``` + +运行实验会将结果保存到名为“ _experiment_stateful.csv_ ”的文件中。 + +再次运行实验并将实验写入的文件名更改为“ _experiment_stateful2.csv_ ”,以便不覆盖第一次运行的结果。 + +您现在应该在文件的当前工作目录中有两组结果: + +* _experiment_stateful.csv_ +* _experiment_stateful2.csv_ + +我们现在可以加载和比较这两个文件。下面列出了执行此操作的脚本。 + +``` +from pandas import DataFrame +from pandas import read_csv +from matplotlib import pyplot +# load results into a dataframe +filenames = ['experiment_stateful.csv', 'experiment_stateful2.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() +``` + +此脚本加载结果文件,并首先计算每次运行的描述性统计信息。 + +我们可以看到平均结果和标准偏差是相对接近的值(分别约为 103-106 和 7-10)。这是一个好兆头,但并不完美。预计将实验的重复次数从 10 增加到 30,100 或甚至 1000 会产生几乎相同的汇总统计数据。 + +``` + stateful stateful2 +count 10.000000 10.000000 +mean 103.142903 106.594624 +std 7.109461 10.687509 +min 94.052380 91.570179 +25% 96.765985 101.015403 +50% 104.376252 102.425406 +75% 107.753516 115.024920 +max 114.958430 125.088436 +``` + +比较还创建了一个框和胡须图来比较两个分布。 + +该图显示了每个实验的 10 个测试 RMSE 结果的第 25 个,第 50 个(中位数)和第 75 个百分位数。该框显示中间 50%的数据,绿线显示中位数。 + +该图显示虽然描述性统计数据相当接近,但分布确实显示出一些差异。 + +然而,分布确实是重叠的,并且不同实验设置的比较均值和标准偏差是合理的,只要我们不对平均值的适度差异进行狡辩。 + +![Box and Whisker Plot of A vs A Experimental Results](img/21b1ac87a3254d76f9bd77bd7f122087.jpg) + +盒子和晶须的 A 与实验结果 + +此分析的一个很好的后续步骤是检查不同样本量的分布的标准误差。这将涉及首先创建一个更大的实验运行池,从中绘制(100 或 1000),并且在比较结果时可以很好地了解重复数的强大数量和平均值的预期误差。 + +## 有状态与无状态 LSTM + +一个好的第一个实验是探索 LSTM 中的维持状态是否增加了不维持状态的价值。 + +在本节中,我们将对比: + +1. 有状态 LSTM(上一节的第一个结果)。 +2. 具有相同配置的无状态 LSTM。 +3. 无状态 LSTM 在训练期间随机改组。 + +LSTM 网络的好处是它们能够维持状态并学习序列。 + +* **期望 1** :期​​望有状态 LSTM 将胜过无状态 LSTM。 + +通常执行每批次或时期的输入模式的混洗以改善训练期间 MLP 网络的普遍性。无状态 LSTM 在训练期间不会改变输入模式,因为网络旨在学习模式序列。我们将测试无状态 LSTM 是否有改组。 + +* **期望 2** :期​​望没有改组的无状态 LSTM 将通过改组跑赢无状态 LSTM。 + +代码更改为上面的有状态 LSTM 示例以使其无状态涉及在 LSTM 层中设置 _ 无状态=假 _ 并使用自动训练时代训练而不是手动。结果将写入名为“ _experiment_stateless.csv_ ”的新文件。更新后的 _fit_lstm()_ 功能如下所示。 + +``` +# 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=False)) + model.add(Dense(1)) + model.compile(loss='mean_squared_error', optimizer='adam') + model.fit(X, y, epochs=nb_epoch, batch_size=batch_size, verbose=0, shuffle=False) + return model +``` + +具有改组实验的无状态涉及在 _fit_lstm()_ 函数中调用 fit 时将 _shuffle_ 参数设置为 _True_ 。该实验的结果写入文件“ _experiment_stateless_shuffle.csv_ ”。 + +完整更新的 _fit_lstm()_ 功能如下所示。 + +``` +# 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=False)) + model.add(Dense(1)) + model.compile(loss='mean_squared_error', optimizer='adam') + model.fit(X, y, epochs=nb_epoch, batch_size=batch_size, verbose=0, shuffle=True) + return model +``` + +运行实验后,您应该有三个结果文件进行比较: + +* _experiment_stateful.csv_ +* _experiment_stateless.csv_ +* _experiment_stateless_shuffle.csv_ + +我们现在可以加载并比较这些结果。下面列出了比较结果的完整示例。 + +``` +from pandas import DataFrame +from pandas import read_csv +from matplotlib import pyplot +# load results into a dataframe +filenames = ['experiment_stateful.csv', 'experiment_stateless.csv', + 'experiment_stateless_shuffle.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() +``` + +首先运行该示例计算并打印每个实验的描述性统计信息。 + +平均结果表明无状态 LSTM 配置可能优于有状态配置。如果强劲,这一发现是非常令人惊讶的,因为它不能满足增加状态改善表现的期望。 + +训练样本的改组似乎对无状态 LSTM 没有太大影响。如果结果是稳健的,那么对无状态 LSTM 的改组训练订单的期望似乎确实提供了一些好处。 + +总之,这些发现可能进一步表明所选择的 LSTM 配置更侧重于学习输入 - 输出对而不是序列内的依赖性。 + +仅从这些有限的结果中,人们就会考虑在这个问题上探索无状态 LSTM。 + +``` + stateful stateless stateless_shuffle +count 10.000000 10.000000 10.000000 +mean 103.142903 95.661773 96.206332 +std 7.109461 1.924133 2.138610 +min 94.052380 94.097259 93.678941 +25% 96.765985 94.290720 94.548002 +50% 104.376252 95.098050 95.804411 +75% 107.753516 96.092609 97.076086 +max 114.958430 100.334725 99.870445 +``` + +还创建了一个盒子和胡须图来比较分布。 + +与无状态情况相比,有状态配置的数据传播显得更大。当我们查看标准偏差分数时,这也存在于描述性统计中。 + +这表明无状态配置可能更稳定。 + +![Box and Whisker Plot of Test RMSE of Stateful vs Stateless LSTM Results](img/0ca24cdb971f76ccf42b05032b49fa12.jpg) + +有状态与无状态 LSTM 结果的测试 RMSE 的盒子和晶须图 + +## 大批量无状态与无状态无状态 + +理解有状态和无状态 LSTM 之间差异的关键是“当内部状态被重置时”。 + +* **无状态**:在无状态 LSTM 配置中,内部状态在每个训练批次或每个批次进行预测后重置。 +* **有状态**:在有状态 LSTM 配置中,只有在调用 _reset_state()_ 功能时才会复位内部状态。 + +如果这是唯一的区别,则可以使用大批量大小来模拟具有无状态 LSTM 的有状态 LSTM。 + +* **期望 3** :当使用相同的批量大小时,无状态和有状态 LSTM 应产生几乎相同的结果。 + +我们可以使用 Shampoo Sales 数据集将训练数据截断到 12 个月,并将测试数据保留为 12 个月。这将允许无状态 LSTM 使用 12 的批量大小。如果以一次性方式(一个函数调用)执行训练和测试,那么“_ 无状态 _”的内部状态可能是 LSTM 不会被重置,两种配置都会产生相同的结果。 + +我们将使用第一个实验的有状态结果作为起点。 _forecast_lstm()_ 功能被修改为在一个步骤中预测一年的观察。 _ 实验()_ 功能被修改为将训练数据集截断为 12 个月的数据,使用批量大小为 12,并处理从 _forecast_lstm()_ 返回的批量预测功能。下面列出了这些更新的功能。结果将写入文件“ _experiment_stateful_batch12.csv_ ”。 + +``` +# 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[1:,:] + # split data into train and test-sets + train, test = supervised_values[-24:-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 + batch_size = 12 + lstm_model = fit_lstm(train_scaled, batch_size, 1000, 1) + # 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 +``` + +我们将使用前一个实验中的无状态 LSTM 配置,将训练模式混洗作为起点。该实验使用与上面列出的相同的 _forecast_lstm()_ 和 _experiment()_ 函数。结果写入文件“ _experiment_stateless_batch12.csv_ ”。 + +运行此实验后,您将有两个结果文件: + +* _experiment_stateful_batch12.csv_ +* _experiment_stateless_batch12.csv_ + +我们现在可以比较这些实验的结果。 + +``` +from pandas import DataFrame +from pandas import read_csv +from matplotlib import pyplot +# load results into a dataframe +filenames = ['experiment_stateful_batch12.csv', 'experiment_stateless_batch12.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() +``` + +运行比较脚本首先计算并打印每个实验的描述性统计信息。 + +每个实验的平均结果表明具有相同批次大小的无状态和有状态配置之间的等效结果。这证实了我们的期望。 + +如果这个结果是稳健的,那么它表明在内部状态重置之后,Keras 中的无状态和有状态 LSTM 网络之间没有进一步的实现 - 详细差异。 + +``` + stateful_batch12 stateless_batch12 +count 10.000000 10.000000 +mean 97.920126 97.450757 +std 6.526297 5.707647 +min 92.723660 91.203493 +25% 94.215807 93.888928 +50% 95.770862 95.640314 +75% 99.338368 98.540688 +max 114.567780 110.014679 +``` + +还创建了一个盒子和胡须图来比较分布。 + +该图证实了描述性统计中的故事,或许只是突出了实验设计的可变性。 + +![Box and Whisker Plot of Test RMSE of Stateful vs Stateless with Large Batch Size LSTM Results](img/ea76f465fa78198e50d3dff96634d44b.jpg) + +具有大批量 LSTM 结果的有状态与无状态测试 RMSE 的框和晶须图 + +## 有状态重置与无状态重置 + +关于有状态 LSTM 的另一个问题是对州进行重置的最佳制度。 + +通常,我们希望在每次呈现序列后重置状态是个好主意。 + +* **期望 4** :在每个训练时期之后重置状态可以获得更好的测试表现。 + +这提出了在进行预测时管理状态的最佳方法的问题。例如,网络是否应该首先对训练数据集进行预测? + +* **期望 5** :通过对训练数据集进行预测,LSTM 中的播种状态可以提高测试表现。 + +我们还希望不在测试集上的一步预测之间重置 LSTM 状态是个好主意。 + +* **期望 6** :在测试集上的一步预测之间不重置状态会导致更好的测试集表现。 + +还有一个问题是,重置状态是否是一个好主意。在本节中,我们尝试梳理这些问题的答案。 + +我们将再次使用所有可用数据和批量大小为 1 进行一步预测。 + +总之,我们将比较以下实验设置: + +没有种子: + +* **noseed_1** :在每个训练时期之后重置状态而不是在测试期间(来自 _experiment_stateful.csv_ 中的第一个实验的有状态结果)。 +* **noseed_2** :在每个训练时期之后和每个一步预测之后复位状态( _experiment_stateful_reset_test.csv_ )。 +* **noseed_3** :训练后无需重置或进行一步预测( _experiment_stateful_noreset.csv_ )。 + +播种: + +* **seed_1** :在每个训练时期之后重置状态,在对测试数据集进行一步预测之前对训练数据集进行一步预测的种子状态( _experiment_stateful_seed_train.csv_ )。 +* **seed_2** :在每个训练时期后重置状态,在训练数据集上进行一步预测的种子状态,然后对测试数据集进行一步预测,并在训练和测试集上的每个一步预测后重置状态( _experiment_stateful_seed_train_resets.csv_ )。 +* **seed_3** :在进行一步预测之前训练数据集上的种子,在预测训练期间没有重置( _experiment_stateful_seed_train_no_resets.csv_ )。 + +来自第一个“A vs A”实验的有状态实验代码用作基础。 + +下面列出了各种重置/不重置和播种/不播种所需的修改。 + +我们可以通过在每次预测之后在模型上添加对 _reset_states()_ 的调用来更新 _forecast_lstm()_ 函数以在每次测试之后更新。更新的 _forecast_lstm()_ 功能如下所示。 + +``` +# 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) + model.reset_states() + return yhat[0,0] +``` + +我们可以通过删除对 _reset_states()_ 的调用来更新 _fit_lstm()_ 函数,使其在每个迭代后不复位。完整的功能如下所列。 + +``` +# 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) + return model +``` + +通过循环训练数据集并进行一步预测,我们可以在训练后通过训练数据集对训练数据集进行训练,使 LSTM 状态成为种子。在对测试数据集进行一步预测之前,可以将其添加到 _run()_ 函数中。更新的 _run()_ 功能如下所示。 + +``` +# 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[1:,:] + # 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, 1000, 1) + # forecast train dataset + for i in range(len(train_scaled)): + X, y = train_scaled[i, 0:-1], train_scaled[i, -1] + yhat = forecast_lstm(lstm_model, 1, X) + # 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 +``` + +这结束了为这 6 个实验创建代码所需的所有分段修改。 + +运行这些实验后,您将获得以下结果文件: + +* _experiment_stateful.csv_ +* _experiment_stateful_reset_test.csv_ +* _experiment_stateful_noreset.csv_ +* _experiment_stateful_seed_train.csv_ +* _experiment_stateful_seed_train_resets.csv_ +* _experiment_stateful_seed_train_no_resets.csv_ + +我们现在可以使用下面的脚本比较结果。 + +``` +from pandas import DataFrame +from pandas import read_csv +from matplotlib import pyplot +# load results into a dataframe +filenames = ['experiment_stateful.csv', 'experiment_stateful_reset_test.csv', + 'experiment_stateful_noreset.csv', 'experiment_stateful_seed_train.csv', + 'experiment_stateful_seed_train_resets.csv', 'experiment_stateful_seed_train_no_resets.csv'] +results = DataFrame() +for name in filenames: + results[name] = read_csv(name, header=0) +results.columns = ['noseed_1', 'noseed_2', 'noseed_3', 'seed_1', 'seed_2', 'seed_3'] +# describe all results +print(results.describe()) +# box and whisker plot +results.boxplot() +pyplot.show() +``` + +运行比较会为每组结果打印描述性统计信息。 + +没有播种的结果表明在测试数据集上的每次预测之后重置之间可能没有什么差别。这表明从预测到预测构建的任何状态都不会增加值,或者此状态由 Keras API 隐式清除。这是一个令人惊讶的结果。 + +无种子案例的结果也表明,在训练期间没有重置导致平均表现更差,方差更大,而不是在每个时期结束时重置状态。这证实了期望在每个训练时期结束时重置状态是一种很好的做法。 + +种子实验的平均结果表明,在对测试数据集进行预测之前,对训练数据集进行预测的播种 LSTM 状态是中性的,如果不是导致表现稍差。 + +在列车和测试装置上的每次预测之后重置状态似乎导致稍微更好的表现,而在训练或测试期间不重置状态似乎导致最佳表现。 + +关于播种的这些结果令人惊讶,但我们应该注意,平均值都在 5 个月洗发水销售的测试 RMSE 内,并且可能是统计噪声。 + +``` + noseed_1 noseed_2 noseed_3 seed_1 seed_2 seed_3 +count 10.000000 10.000000 10.000000 10.000000 10.000000 10.000000 +mean 103.142903 101.757034 110.441021 105.468200 100.093551 98.766432 +std 7.109461 14.584442 24.539690 5.206674 4.157095 11.573366 +min 94.052380 91.264712 87.262549 97.683535 95.913385 90.005843 +25% 96.765985 93.218929 94.610724 100.974693 96.721924 91.203879 +50% 104.376252 96.144883 99.483971 106.036240 98.779770 95.079716 +75% 107.753516 105.657586 121.586508 109.829793 103.082791 100.500867 +max 114.958430 138.752321 166.527902 112.691046 108.070145 128.261354 +``` + +还创建了一个盒子和胡须图来比较分布。 + +该情节与描述性统计数据相同。它突出了在没有播种的情况下无状态 LSTM 上没有使用重置时增加的传播。它还强调了通过对训练数据集进行预测来实现 LSTM 状态的实验的一般紧密传播。 + +![Box and Whisker Plot of Test RMSE of Reset Regimes in Stateful LSTMs](img/7385d179339f593157589610b2d196aa.jpg) + +有状态 LSTM 中重置机制测试 RMSE 的盒子和晶须图 + +## 审查结果 + +在本节中,我们将回顾本教程中的研究结果。 + +* 具有所选配置的 10 次重复实验导致测试 RMSE 的平均值和标准偏差在约 3 个月洗发剂销售中有一些变化。预计会有更多重复收紧。 +* 具有相同配置的无状态 LSTM 可能比有状态版本在此问题上表现更好。 +* 不使用无状态 LSTM 改变训练模式可能会导致稍微更好的表现。 +* 当使用大批量大小时,可以使用无状态 LSTM 模拟有状态 LSTM。 +* 使用有状态 LSTM 进行一步预测时重置状态可以提高测试集的表现。 +* 通过在对测试集进行预测之前对训练数据集进行预测来在有状态 LSTM 中播种状态不会导致测试集上的表现的明显改善。 +* 拟合有状态 LSTM 并将其播种到训练数据集上并且在训练或预测期间不执行任何状态重置可以导致测试集上的更好表现。 + +必须指出的是,通过增加每个实验的重复次数并使用统计显着性检验确认差异是显着的,这些发现应该更加稳健。 + +还应注意,这些结果适用于此特定问题,框架的方式以及所选的 LSTM 配置参数,包括拓扑,批量大小和训练时期。 + +## 摘要 + +在本教程中,您了解了如何使用有状态与无状态 LSTM 网络来研究使用 Keras 在 Python 中进行时间序列预测的影响。 + +具体来说,你学到了: + +* 如何比较无状态与状态 LSTM 网络的时间序列预测。 +* 如何确认无状态 LSTM 和具有大批量大小的有状态 LSTM 的等价性。 +* 如何评估在训练期间重置 LSTM 状态以及使用 LSTM 网络进行时间序列预测预测时的影响。 + +你有任何问题吗?在评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/suitability-long-short-term-memory-networks-time-series-forecasting.md b/docs/dl-ts/suitability-long-short-term-memory-networks-time-series-forecasting.md new file mode 100644 index 0000000000000000000000000000000000000000..87d9d2b9ebdaa27145e420e982d00932fe0f65bf --- /dev/null +++ b/docs/dl-ts/suitability-long-short-term-memory-networks-time-series-forecasting.md @@ -0,0 +1,131 @@ +# 长短时记忆网络在时间序列预测中的适用性 + +> 原文: [https://machinelearningmastery.com/suitability-long-short-term-memory-networks-time-series-forecasting/](https://machinelearningmastery.com/suitability-long-short-term-memory-networks-time-series-forecasting/) + +长短期记忆(LSTM)是一种循环神经网络,可以学习序列中项目之间的顺序依赖性。 + +LSTM 承诺能够学习在时间序列预测问题中进行预测所需的上下文,而不是预先指定和修复此上下文。 + +鉴于有希望,对于 LSTM 是否适合时间序列预测存在疑问。 + +在这篇文章中,我们将研究 LSTMs 在该技术的一些主要开发者的时间序列预测中的应用。 + +让我们开始吧。 + +![On the Suitability of Long Short-Term Memory Networks for Time Series Forecasting](img/1414ee0be9745fea05fdd9ca50d5110a.jpg) + +关于长期短期记忆网络对时间序列预测的适用性 +照片由 [becosky](https://www.flickr.com/photos/becosky/3304801086/) ,保留一些权利。 + +## LSTM 用于时间序列预测 + +我们将仔细研究一篇旨在探讨 LSTM 是否适合时间序列预测的论文。 + +该论文标题为“[将 LSTM 应用于通过时间窗口方法预测的时间序列](https://link.springer.com/chapter/10.1007/3-540-44668-0_93)”([得到 PDF](ftp://ftp.idsia.ch/pub/juergen/icann2001predict.pdf) ,Gers,Eck 和 Schmidhuber,发表于 2001 年。 + +他们首先评论单变量时间序列预测问题实际上比传统上用于演示 LSTM 功能的问题类型更简单。 + +> 文献中发现的时间序列基准问题通常比 LSTM 已经解决的许多任务在概念上更简单。它们通常根本不需要 RNN,因为关于下一个事件的所有相关信息都是通过一个小时间窗内包含的一些最近事件来传达的。 + +本文重点研究了 LSTM 在两个复杂时间序列预测问题中的应用,并将 LSTM 的结果与其他类型的神经网络进行了对比。 + +该研究的重点是两个经典的时间序列问题: + +### 麦基玻璃系列 + +这是根据微分方程计算的人为时间序列。 + +![Plot of the Mackey-Glass Series, Taken from Schoarpedia](img/41ef48ab40dbf477fca8bca64edff999.jpg) + +Mackey-Glass 系列的情节,取自 Schoarpedia + +有关更多信息,请参阅: + +* [Scholarpedia 上的 Mackey-Glass 方程](http://www.scholarpedia.org/article/Mackey-Glass_equation)。 + +### 混沌激光数据(A 组) + +这是一系列来自圣达菲研究所的比赛。 + +集 A 定义为: + +> 清洁的物理实验室实验。远红外激光器中的 1000 个波动点,大致由三个耦合非线性常微分方程描述。 + +![Example of Chaotic Laser Data (Set A), Taken from The Future of Time Series](img/bc55927bd32a3ad1efe39a6c9602f692.jpg) + +混沌激光数据(A 组)的例子,取自时间序列的未来 + +有关更多信息,请参阅: + +* 第 2 节“竞争”[时间序列的未来](http://samoa.santafe.edu/media/workingpapers/93-08-053.pdf),1993。 + +## 自回归 + +使用自回归(AR)方法来模拟这些问题。 + +这意味着下一个时间步骤是作为一些过去(或滞后)观察的函数。 + +这是经典统计时间序列预测的常用方法。 + +LSTM 一次暴露于一个输入,没有固定的滞后变量集,如窗口多层感知器(MLP)。 + +有关时间序列 AR 的更多信息,请参阅帖子: + +* [使用 Python 进行时间序列预测的自回归模型](http://machinelearningmastery.com/autoregression-models-time-series-forecasting-python/) + +## 结果分析 + +一些更为突出的评论是为了回应 LSTM 对 Mackey-Glass 系列问题的糟糕结果。 + +首先,他们评论说增加网络的学习能力没有帮助: + +> 增加内存块的数量并没有显着改善结果。 + +这可能需要进一步增加训练时期的数量。一堆 LSTM 也可能具有改进的结果。 + +他们评论说,为了在 Mackey-Glass 系列上做得好,LSTM 需要记住最近的过去观察结果,而 MLP 则明确地给出了这些数据。 + +> AR-LSTM 方法的结果明显比时间窗方法的结果更差,例如使用 MLP。 AR-LSTM 网络无法访问过去作为其输入的一部分... [为了使 LSTM 做得好]需要记住过去的一个或两个事件,然后在覆盖相同的存储器单元之前使用该信息。 + +他们评论说,总的来说,这对 LSTM 和 RNN 构成了比 MLP 更大的挑战。 + +> 假设任何动态模型都需要来自 t-tau ...的所有输入,我们注意到 AR-RNN 必须存储从 t-tau 到 t 的所有输入并在适当的时间覆盖它们。这需要实现循环缓冲区,这是 RNN 难以模拟的结构。 + +同样,我不禁认为更大的隐藏层(更多的内存单元)和更深的网络(堆叠的 LSTM)更适合学习多个过去的观察。 + +他们后来总结了论文,并根据结果讨论了 LSTM 可能不适合时间序列预测的 AR 类型公式,至少当滞后观测值接近预测时间时。 + +鉴于 LSTM 在测试的单变量问题上的表现与 MLP 相比,这是一个公平的结论。 + +> 基于时间窗的 MLP 在某些时间序列预测基准上优于 LSTM 纯 AR 方法,仅通过查看一些最近的输入来解决。因此,LSTM 的特殊优势,即学习在很长的未知时间内记住单个事件,在这里是不必要的。 +> +> LSTM 学会了调整每个系列的基本振荡,但无法准确地跟踪信号。 + +它们确实强调了 LSTM 学习振荡行为的能力(例如周期或季节性)。 + +> 我们的结果建议仅在传统的基于时间窗口的方法必须失败的任务中使用 LSTM。 +> +> LSTM 跟踪混沌信号中的缓慢振荡的能力可适用于认知领域,例如语音和音乐中的节奏检测。 + +这很有趣,但可能没那么有用,因为这些模式通常会在预测之前尽可能明确地删除。然而,它可能突出了 LSTM 学习在非平稳序列背景下进行预测的可能性。 + +## 最后的话 + +那么,这一切意味着什么呢? + +根据面值,我们可以得出结论,LSTM 不适用于基于 AR 的单变量时间序列预测。我们应首先转向具有固定窗口的 MLP,如果 MLP 无法获得良好结果,则仅转向 LSTM。 + +这听起来很公平。 + +在我们为时间序列预测注销 LSTM 之前,我会争论一些应该考虑的问题: + +* 考虑更复杂的数据准备,例如至少缩放和平稳性。如果周期或趋势明显,则应将其移除,以便模型可以关注基础信号。话虽如此,LSTM 在非静止数据上表现良好或者比其他方法更好的能力是有趣的,但我希望与所需网络容量和训练的增加相称。 +* 考虑使用较大的模型和分层模型(堆叠的 LSTM)来自动学习(或“记住”)更大的时间依赖性。较大的型号可以了解更多。 +* 考虑将模型拟合更长时间,例如数千或数十万个时代,同时利用正规化技术。 LSTM 需要很长时间才能学习复杂的依赖关系。 + +我不会指出我们可以超越基于 AR 的模型;很明显,AR 模型是 LSTM 考虑和采用经典统计方法(如 ARIMA)和表现良好的神经网络(如窗口 MLP)的良好清洁试验场。 + +我相信 LSTM 应用于时间序列预测问题的巨大潜力和机会。 + +你同意吗? +请在下面的评论中告诉我。 \ No newline at end of file diff --git a/docs/dl-ts/taxonomy-of-time-series-forecasting-problems.md b/docs/dl-ts/taxonomy-of-time-series-forecasting-problems.md new file mode 100644 index 0000000000000000000000000000000000000000..c0005daededc9d0b1d5eb9bba43230cc2d59561d --- /dev/null +++ b/docs/dl-ts/taxonomy-of-time-series-forecasting-problems.md @@ -0,0 +1,227 @@ +# 时间序列预测问题的分类 + +> 原文: [https://machinelearningmastery.com/taxonomy-of-time-series-forecasting-problems/](https://machinelearningmastery.com/taxonomy-of-time-series-forecasting-problems/) + +当您遇到新的时间序列预测问题时,有许多事情需要考虑。 + +您所做的选择会直接影响项目的每个步骤,从测试工具的设计到评估预测模型,以及您正在处理的预测问题的基本难度。 + +通过处理有关时间序列预测问题的一系列问题,可以非常快速地缩小选项范围。通过考虑每个主题中的一些主题和问题,您可以缩小问题类型,测试工具,甚至为项目选择算法。 + +在这篇文章中,您将发现一个框架,您可以使用该框架快速了解和构建时间序列预测问题。 + +让我们开始吧。 + +![Taxonomy of Time Series Forecasting Problems](img/1749151e6c1706dd44b13fcade3c67cb.jpg) + +时间序列预测问题的分类 +摄影: [Adam Meek](https://www.flickr.com/photos/adammeek/34093359272/) ,保留一些权利。 + +## 框架概述 + +时间序列预测涉及在观测之间存在有序关系的数据上开发和使用预测模型。 + +您可以在此帖子中了解有关时间序列预测的更多信息: + +* [什么是时间序列预测?](https://machinelearningmastery.com/time-series-forecasting/) + +在开始您的项目之前,您可以回答几个问题,并大大提高您对预测问题结构的理解,模型的结构要求以及如何评估它。 + +本文提出的框架分为七个部分;他们是: + +1. 输入与输出 +2. 内生与外生 +3. 非结构化与结构化 +4. 回归与分类 +5. 单变量与多变量 +6. 单步与多步 +7. 静态与动态 +8. 连续与不连续 + +我建议在开始任何时间序列预测项目之前完成此框架。 + +您的答案在第一时间可能不会很清晰,您可能需要在研究数据,领域以及与专家和利益相关方交谈时提出问题。 + +在了解更多信息时更新您的答案,因为它将帮助您保持正常运行,避免分心,并开发项目所需的实际模型。 + +## 1.输入与输出 + +通常,预测问题涉及使用过去的观察来预测或预测一个或多个可能的未来观察。 + +目标是猜测未来会发生什么。 + +当您需要进行预测时,考虑您可用于进行预测的数据以及您将来会猜测的内容至关重要。 + +我们可以总结一下,在进行单一预测时,模型的输入和输出是什么。 + +* **输入**:提供给模型的历史数据,以便进行单一预测。 +* **输出**:预测或预测未来时间步长超出作为输入提供的数据。 + +输入数据不是用于训练模型的数据。我们还不是那个时候。它是用于进行一次预测的数据,例如销售数据的最后七天,用于预测下一天的销售数据。 + +定义模型的输入和输出会强制您考虑进行预测的确切或可能需要的内容。 + +在输入数据时,您可能无法具体说明。例如,您可能不知道是否需要一个或多个先前时间步骤来进行预测。但是,您将能够识别可用于进行预测的变量。 + +> 预测的输入和输出是什么? + +## 2.内生与外生 + +输入数据可以进一步细分,以便更好地理解它与输出变量的关系。 + +如果输入变量受系统中其他变量的影响且输出变量依赖于它,则它是内生的。 + +在时间序列中,对输入变量的观察取决于彼此。例如,时间 t 的观察取决于 t-1 处的观察; t-1 可能取决于 t-2,依此类推。 + +如果输入变量独立于系统中的其他变量并且输出变量依赖于它,则它是外生变量。 + +简而言之,内生变量受系统中其他变量(包括它们自身)的影响,而外生变量则不被系统视为外部变量。 + +* **内生**:受系统中其他变量影响且输出变量所依赖的输入变量。 +* **外生**:输入变量,不受系统中其他变量影响,输出变量所依赖的变量。 + +通常,时间序列预测问题具有内生变量(例如,输出是某些先前时间步长的函数)并且可能具有或可能不具有外生变量。 + +通常,考虑到对时间序列的强烈关注,外生变量会被忽略。明确地考虑两种变量类型可能有助于识别容易被忽视的外生数据,甚至可以改进模型的工程特征。 + +> 什么是内生和外生变量? + +## 3.回归与分类 + +回归预测建模问题是预测数量的问题。 + +数量是数值;例如价格,计数,数量等。您想要预测一个或多个未来数值的时间序列预测问题是回归类型预测建模问题。 + +分类预测建模问题是预测类别的问题。 + +类别是来自一组明确定义的标签的标签;例如{“hot”,“cold”},{“up”,“down”}和{“buy”,“sell”}是类别。 + +要对输入时间序列数据进行分类的时间序列预测问题是分类预测建模问题。 + +* **回归**:预测数量。 +* **分类**:分类为两个或多个标签之一。 + +> 您正在研究回归或分类预测建模问题吗? + +这些类型之间存在一定的灵活性。 + +例如,回归问题可以重新定义为分类,分类问题可以重新定义为回归。一些问题,如预测序数值,可以被定义为分类和回归。重构您的时间序列预测问题可能会简化它。 + +> 有哪些方法来构建时间序列预测问题? + +## 4.非结构化与结构化 + +在时间序列中绘制每个变量并检查绘图以查找可能的模式非常有用。 + +单个变量的时间序列可能没有任何明显的模式。 + +我们可以把没有模式的系列想象成非结构化的,因为没有可辨别的时间依赖结构。 + +或者,时间序列可能具有明显的模式,例如趋势或季节性周期。 + +我们通常可以通过识别和删除数据中的明显结构来简化建模过程,例如增加趋势或重复循环。一些经典方法甚至允许您指定参数来直接处理这些系统结构。 + +* **非结构化**:时间序列变量中没有明显的系统时间依赖模式。 +* **结构化**:时间序列变量中的系统时间依赖模式(例如趋势和/或季节性)。 + +> 时间序列变量是非结构化的还是结构化的? + +## 5.单变量与多变量 + +随时间测量的单个变量被称为单变量时间序列。单变量意味着一个变量或一个变量。 + +随时间测量的多个变量被称为多变量时间序列:多个变量或多个变量。 + +* **单变量**:随时间变化测量的一个变量。 +* **多变量**:随时间测量的多个变量。 + +> 您是在处理单变量或多变量时间序列问题吗? + +考虑到关于投入和产出的这个问题可能会进一步加以区分。输入和输出之间的变量数可能不同,例如数据可能不对称。 + +例如,您可能有多个变量作为模型的输入,并且只对将其中一个变量预测为输出感兴趣。在这种情况下,模型中假设多个输入变量有助于预测单个输出变量。 + +* **单变量和多变量输入**:随时间测量的一个或多个输入变量。 +* **单变量和多变量输出**:要预测的一个或多个输出变量。 + +## 6.单步与多步 + +需要预测下一个时间步长的预测问题称为一步预测模型。 + +而需要预测多个时间步长的预测问题称为多步预测模型。 + +预测到未来的时间步骤越多,问题就越具挑战性,因为每个预测时间步长的不确定性的复合性质。 + +* **一步**:预测下一步。 +* **多步**:预测多个未来时间步骤。 + +> 您需要单步骤还是多步骤预测? + +## 7.静态与动态 + +可以开发一次模型并重复使用它来进行预测。 + +鉴于模型未在预测之间更新或更改,我们可以将此模型视为静态模型。 + +相反,我们可能会在进行后续预测之前收到新的观察结果,这些预测可用于创建新模型或更新现有模型。我们可以考虑在每个预测之前开发一个新的或更新的模型作为动态问题。 + +例如,如果问题需要在未来一周的一周开始时进行预测,我们可能会收到本周末的真实观察结果,我们可以在下周预测之前更新模型。这将是一个动态模型。如果我们在本周末没有得到真实的观察结果,或者我们选择不重新拟合模型,那么这将是一个静态模型。 + +我们可能更喜欢动态模型,但是域的约束或所选算法的限制可能会产生使这种难以处理的约束。 + +* **静态**。预测模型适合一次并用于进行预测。 +* **动态**。在每次预测之前,预测模型适合新的可用数据。 + +> 您需要静态或动态更新的模型吗? + +## 8.连续与不连续 + +观察值随时间均匀的时间序列可以被描述为连续的。 + +许多时间序列问题都有连续的观察结果,例如每小时,每天,每月或每年观察一次。 + +观察结果随时间不均匀的时间序列可被描述为不连续的。 + +观察的不均匀性可能是由于缺失或损坏的值造成的。这也可能是问题的一个特征,其中观察仅偶尔可用或者以逐渐或逐渐间隔的时间间隔提供。 + +在非均匀观测的情况下,在拟合某些模型以使观察随时间均匀时可能需要特定的数据格式化。 + +* **连续**。随着时间的推移,观察是统一的。 +* **不连续**。随着时间的推移,观察结果不一致。 + +> 您的观察结果是连续的还是不连续的? + +## 进一步阅读 + +本节列出了一些可供进一步阅读的资源。 + +* [什么是时间序列预测?](https://machinelearningmastery.com/time-series-forecasting/) +* [如何通过时间序列预测项目](https://machinelearningmastery.com/work-time-series-forecast-project/) + +## 摘要 + +要查看,您可以询问有关您的问题的主题和问题如下: + +1. 输入与输出 + 1. 预测的输入和输出是什么? +2. 内生与外生 + 1. 什么是内生和外生变量? +3. 非结构化与结构化 + 1. 时间序列变量是非结构化的还是结构化的? +4. 回归与分类 + 1. 您正在研究回归或分类预测建模问题吗? + 2. 有哪些方法来构建时间序列预测问题? +5. 单变量与多变量 + 1. 您是在处理单变量或多变量时间序列问题吗? +6. 单步与多步 + 1. 您需要单步骤还是多步骤预测? +7. 静态与动态 + 1. 您需要静态或动态更新的模型吗? +8. 连续与不连续 + 1. 您的观察结果是连续的还是不连续的? + +您是否发现此框架对您的时间序列预测问题有用? +请在下面的评论中告诉我。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/time-series-forecasting-long-short-term-memory-network-python.md b/docs/dl-ts/time-series-forecasting-long-short-term-memory-network-python.md new file mode 100644 index 0000000000000000000000000000000000000000..bd3c6591ce7f3d97641455d7a03fbdefe3d4cf91 --- /dev/null +++ b/docs/dl-ts/time-series-forecasting-long-short-term-memory-network-python.md @@ -0,0 +1,1085 @@ +# Python 中长短期记忆网络的时间序列预测 + +> 原文: [https://machinelearningmastery.com/time-series-forecasting-long-short-term-memory-network-python/](https://machinelearningmastery.com/time-series-forecasting-long-short-term-memory-network-python/) + +长期短期记忆复发神经网络有望学习长序列的观察。 + +它似乎是时间序列预测的完美匹配,事实上,它可能是。 + +在本教程中,您将了解如何为单步单变量时间序列预测问题开发 LSTM 预测模型。 + +完成本教程后,您将了解: + +* 如何为预测问题制定绩效基准。 +* 如何为一步时间序列预测设计一个强大的测试工具。 +* 如何为时间序列预测准备数据,开发和评估 LSTM 循环神经网络。 + +让我们开始吧。 + +* **2017 年 5 月更新**:修正了 invert_scale()函数中的错误,谢谢 Max。 + +![Time Series Forecasting with the Long Short-Term Memory Network in Python](img/42fc13496734cacdffd9f8460c8637b1.jpg) + +使用 Python 中的长短期记忆网络进行时间序列预测 +照片由 [Matt MacGillivray](https://www.flickr.com/photos/qmnonic/179791867/) 拍摄,保留一些权利。 + +## 教程概述 + +这是一个很大的话题,我们将涉及很多方面。带子。 + +本教程分为 9 个部分;他们是: + +1. 洗发水销售数据集 +2. 测试设置 +3. 持久性模型预测 +4. LSTM 数据准备 +5. LSTM 模型开发 +6. LSTM 预测 +7. 完整的 LSTM 示例 +8. 制定稳健的结果 +9. 教程扩展 + +### Python 环境 + +本教程假定您已安装 Python SciPy 环境。您可以在本教程中使用 Python 2 或 3。 + +您必须安装带有 TensorFlow 或 Theano 后端的 Keras(2.0 或更高版本)。 + +本教程还假设您安装了 scikit-learn,Pandas,NumPy 和 Matplotlib。 + +如果您需要有关环境的帮助,请参阅此帖子: + +* [如何使用 Anaconda 设置用于机器学习和深度学习的 Python 环境](http://machinelearningmastery.com/setup-python-environment-machine-learning-deep-learning-anaconda/) + +## 洗发水销售数据集 + +该数据集描述了 3 年期间每月洗发水的销售数量。 + +单位是销售计数,有 36 个观察。原始数据集归功于 Makridakis,Wheelwright 和 Hyndman(1998)。 + +[您可以在此处下载并了解有关数据集的更多信息](https://datamarket.com/data/set/22r0/sales-of-shampoo-over-a-three-year-period)。 + +**更新**:这是数据集的直接链接,可以使用: [shampoo.csv](https://raw.githubusercontent.com/jbrownlee/Datasets/master/shampoo.csv) + +将数据集下载到当前工作目录,名称为“ _shampoo-sales.csv_ ”。请注意,您可能需要删除 DataMarket 添加的页脚信息。 + +下面的示例加载并创建已加载数据集的图。 + +``` +# 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 Monthly Shampoo Sales Dataset](img/b49b84d78ed18007a6d0edaa1b71ce11.jpg) + +每月洗发水销售数据集的线图 + +## 实验测试设置 + +我们将 Shampoo Sales 数据集分为两部分:训练和测试集。 + +前两年的数据将用于训练数据集,剩余的一年数据将用于测试集。 + +例如: + +``` +# split data into train and test +X = series.values +train, test = X[0:-12], X[-12:] +``` + +将使用训练数据集开发模型,并对测试数据集进行预测。 + +将使用滚动预测场景,也称为前进模型验证。 + +测试数据集的每个时间步骤将一次一个地走。将使用模型对时间步长进行预测,然后将获取测试集的实际预期值,并使其可用于下一时间步的预测模型。 + +例如: + +``` +# walk-forward validation +history = [x for x in train] +predictions = list() +for i in range(len(test)): + # make prediction... +``` + +这模仿了一个真实世界的场景,每个月都会有新的洗发水销售观察结果,并用于下个月的预测。 + +最后,将收集测试数据集的所有预测,并计算错误分数以总结模型的技能。将使用均方根误差(RMSE),因为它会对大错误进行处罚,并产生与预测数据相同的分数,即每月洗发水销售额。 + +例如: + +``` +from sklearn.metrics import mean_squared_error +rmse = sqrt(mean_squared_error(test, predictions)) +print('RMSE: %.3f' % rmse) +``` + +## 持久性模型预测 + +具有线性增长趋势的时间序列的良好基线预测是持久性预测。 + +持续性预测是使用先前时间步骤(t-1)的观察来预测当前时间步骤(t)的观察。 + +我们可以通过从训练数据和前进验证累积的历史记录中进行最后一次观察并使用它来预测当前时间步长来实现这一点。 + +例如: + +``` +# make prediction +yhat = history[-1] +``` + +我们将在数组中累积所有预测,以便可以直接将它们与测试数据集进行比较。 + +下面列出了 Shampoo Sales 数据集上的持久性预测模型的完整示例。 + +``` +from pandas import read_csv +from pandas import datetime +from sklearn.metrics import mean_squared_error +from math import sqrt +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) +# split data into train and test +X = series.values +train, test = X[0:-12], X[-12:] +# walk-forward validation +history = [x for x in train] +predictions = list() +for i in range(len(test)): + # make prediction + predictions.append(history[-1]) + # observation + history.append(test[i]) +# report performance +rmse = sqrt(mean_squared_error(test, predictions)) +print('RMSE: %.3f' % rmse) +# line plot of observed vs predicted +pyplot.plot(test) +pyplot.plot(predictions) +pyplot.show() +``` + +运行该示例将打印出大约 136 个月洗发水销售额的 RMSE,用于测试数据集的预测。 + +``` +RMSE: 136.761 +``` + +还创建了测试数据集(蓝色)与预测值(橙色)的线图,显示了上下文中的持久性模型预测。 + +![Persistence Forecast of Observed vs Predicted for Shampoo Sales Dataset](img/5865df70cf90107cfdab9a23b81198e5.jpg) + +洗发水销售数据集的观察与预测持续性预测 + +有关时间序列预测的持久性模型的更多信息,请参阅此帖子: + +* [如何使用 Python 进行时间序列预测的基线预测](http://machinelearningmastery.com/persistence-time-series-forecasting-with-python/) + +现在我们已经在数据集上有了表现基准,我们可以开始为数据开发 LSTM 模型。 + +## LSTM 数据准备 + +在我们将 LSTM 模型拟合到数据集之前,我们必须转换数据。 + +本节分为三个步骤: + +1. 将时间序列转换为监督学习问题 +2. 转换时间序列数据,使其静止不动。 +3. 将观察结果转换为具有特定比例。 + +### 将时间序列转换为监督学习 + +Keras 中的 LSTM 模型假设您的数据分为输入(X)和输出(y)组件。 + +对于时间序列问题,我们可以通过使用从最后时间步骤(t-1)作为输入的观察和在当前时间步骤(t)的观察作为输出来实现这一点。 + +我们可以使用 Pandas 中的 [shift()](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.shift.html)函数来实现这一点,它会将一系列中的所有值按下指定的数字位置。我们需要移位 1 个位置,这将成为输入变量。现在的时间序列将是输出变量。 + +然后,我们可以将这两个系列连接在一起,为监督学习创建一个 DataFrame。推下的系列将在顶部有一个新的位置,没有任何价值。在此位置将使用 NaN(非数字)值。我们将这些 NaN 值替换为 0 值,LSTM 模型必须将其作为“系列的开头”或“我这里没有数据”来学习,因为没有观察到该数据集零销售的月份。 + +下面的代码定义了一个名为 _timeseries_to_supervised()_ 的辅助函数。它需要一个原始时间序列数据的 NumPy 数组和一个滞后或移位序列的数量来创建和用作输入。 + +``` +# 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.fillna(0, inplace=True) + return df +``` + +我们可以使用我们加载的 Shampoo Sales 数据集测试此功能,并将其转换为监督学习问题。 + +``` +from pandas import read_csv +from pandas import datetime +from pandas import DataFrame +from pandas import concat + +# 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.fillna(0, inplace=True) + return df + +# 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) +# transform to supervised learning +X = series.values +supervised = timeseries_to_supervised(X, 1) +print(supervised.head()) +``` + +运行该示例将打印新监督学习问题的前 5 行。 + +``` + 0 0 +0 0.000000 266.000000 +1 266.000000 145.899994 +2 145.899994 183.100006 +3 183.100006 119.300003 +4 119.300003 180.300003 +``` + +有关将时间序列问题转换为监督学习问题的更多信息,请参阅帖子: + +* [时间序列预测作为监督学习](http://machinelearningmastery.com/time-series-forecasting-supervised-learning/) + +### 将时间序列转换为静止 + +Shampoo Sales 数据集不是固定的。 + +这意味着数据中的结构取决于时间。具体而言,数据呈上升趋势。 + +固定数据更易于建模,很可能会产生更加熟练的预测。 + +可以从观察中移除趋势,然后将其添加回预测以将预测返回到原始比例并计算可比较的误差分数。 + +消除趋势的标准方法是区分数据。即,从当前观察(t)中减去前一时间步骤(t-1)的观察结果。这消除了趋势,我们留下了差异序列,或者从一个时间步到下一个步骤的观察结果的变化。 + +我们可以使用 pandas 中的 [diff()函数](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.diff.html)自动实现。或者,我们可以获得更精细的控制并编写我们自己的函数来执行此操作,这在这种情况下是灵活的首选。 + +下面是一个名为 _difference()_ 的函数,用于计算差分序列。请注意,系列中的第一个观察值被跳过,因为没有先前的观察值来计算差值。 + +``` +# 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) +``` + +我们还需要反转此过程,以便将差异系列的预测恢复到原始比例。 + +以下函数称为 _inverse_difference()_,反转此操作。 + +``` +# invert differenced value +def inverse_difference(history, yhat, interval=1): + return yhat + history[-interval] +``` + +我们可以通过对整个系列进行差分来测试这些函数,然后将其返回到原始比例,如下所示: + +``` +from pandas import read_csv +from pandas import datetime +from pandas import Series + +# 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] + +# 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) +print(series.head()) +# transform to be stationary +differenced = difference(series, 1) +print(differenced.head()) +# invert transform +inverted = list() +for i in range(len(differenced)): + value = inverse_difference(series, differenced[i], len(series)-i) + inverted.append(value) +inverted = Series(inverted) +print(inverted.head()) +``` + +运行该示例打印加载数据的前 5 行,然后打印差异系列的前 5 行,最后打印差异操作的前 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 +0 -120.1 +1 37.2 +2 -63.8 +3 61.0 +4 -11.8 +dtype: float64 + +0 145.9 +1 183.1 +2 119.3 +3 180.3 +4 168.5 +dtype: float64 +``` + +有关使时间序列固定和差分的更多信息,请参阅帖子: + +* [如何使用 Python 检查时间序列数据是否固定](http://machinelearningmastery.com/time-series-data-stationary-python/) +* [如何区分时间序列数据集与 Python](http://machinelearningmastery.com/difference-time-series-dataset-python/) + +### 将时间序列转换为比例 + +与其他神经网络一样,LSTM 期望数据在网络使用的激活函数的范围内。 + +LSTM 的默认激活函数是双曲正切( _tanh_ ),它输出介于-1 和 1 之间的值。这是时间序列数据的首选范围。 + +为了使实验公平,必须在训练数据集上计算缩放系数(最小和最大)值,并应用于缩放测试数据集和任何预测。这是为了避免使用测试数据集中的知识污染实验,这可能会给模型带来小的优势。 + +我们可以使用 [MinMaxScaler 类](http://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MinMaxScaler.html)将数据集转换为[-1,1]范围。与其他 scikit-learn 变换类一样,它需要以行和列的矩阵格式提供数据。因此,我们必须在转换之前重塑我们的 NumPy 数组。 + +例如: + +``` +# transform scale +X = series.values +X = X.reshape(len(X), 1) +scaler = MinMaxScaler(feature_range=(-1, 1)) +scaler = scaler.fit(X) +scaled_X = scaler.transform(X) +``` + +同样,我们必须反转预测的比例,以将值返回到原始比例,以便可以解释结果并计算可比较的误差分数。 + +``` +# invert transform +inverted_X = scaler.inverse_transform(scaled_X) +``` + +将所有这些放在一起,下面的例子改变了 Shampoo Sales 数据的规模。 + +``` +from pandas import read_csv +from pandas import datetime +from pandas import Series +from sklearn.preprocessing import MinMaxScaler +# 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) +print(series.head()) +# transform scale +X = series.values +X = X.reshape(len(X), 1) +scaler = MinMaxScaler(feature_range=(-1, 1)) +scaler = scaler.fit(X) +scaled_X = scaler.transform(X) +scaled_series = Series(scaled_X[:, 0]) +print(scaled_series.head()) +# invert transform +inverted_X = scaler.inverse_transform(scaled_X) +inverted_series = Series(inverted_X[:, 0]) +print(inverted_series.head()) +``` + +运行该示例首先打印加载数据的前 5 行,然后打印缩放数据的前 5 行,然后是缩放比例变换的前 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 +0 -0.478585 +1 -0.905456 +2 -0.773236 +3 -1.000000 +4 -0.783188 +dtype: float64 + +0 266.0 +1 145.9 +2 183.1 +3 119.3 +4 180.3 +dtype: float64 +``` + +现在我们知道如何为 LSTM 网络准备数据,我们可以开始开发我们的模型了。 + +## LSTM 模型开发 + +长短期记忆网络(LSTM)是一种循环神经网络(RNN)。 + +这种类型的网络的一个好处是它可以学习和记住长序列,并且不依赖于预先指定的窗口滞后观察作为输入。 + +在 Keras 中,这被称为有状态,并且涉及在定义 LSTM 层时将“_ 有状态 _”参数设置为“ _True_ ”。 + +默认情况下,Keras 中的 LSTM 层维护一批数据之间的状态。一批数据是来自训练数据集的固定大小的行数,用于定义在更新网络权重之前要处理的模式数。默认情况下,批次之间的 LSTM 层中的状态被清除,因此我们必须使 LSTM 成为有状态。通过调用 _reset_states()_ 函数,这可以精确控制 LSTM 层的状态何时被清除。 + +LSTM 层期望输入位于具有以下尺寸的矩阵中:[_ 样本,时间步长,特征 _]。 + +* **样本**:这些是来自域的独立观察,通常是数据行。 +* **时间步长**:这些是给定观察的给定变量的单独时间步长。 +* **特征**:这些是在观察时观察到的单独测量。 + +我们在如何为网络构建 Shampoo Sales 数据集方面具有一定的灵活性。我们将保持简单并构建问题,因为原始序列中的每个步骤都是一个单独的样本,具有一个时间步长和一个特征。 + +鉴于训练数据集定义为 X 输入和 y 输出,必须将其重新整形为 Samples / TimeSteps / Features 格式,例如: + +``` +X, y = train[:, 0:-1], train[:, -1] +X = X.reshape(X.shape[0], 1, X.shape[1]) +``` + +必须在 LSTM 层中使用“ _batch_input_shape_ ”参数指定输入数据的形状作为元组,该元组指定读取每批的预期观察数,时间步数和数量特征。 + +批量大小通常远小于样本总数。它与时期的数量一起定义了网络学习数据的速度(权重更新的频率)。 + +定义 LSTM 层的最后一个导入参数是神经元的数量,也称为内存单元或块的数量。这是一个相当简单的问题,1 到 5 之间的数字就足够了。 + +下面的行创建了一个 LSTM 隐藏层,它还通过“ _batch_input_shape_ ”参数指定输入层的期望值。 + +``` +layer = LSTM(neurons, batch_input_shape=(batch_size, X.shape[1], X.shape[2]), stateful=True) +``` + +网络需要输出层中的单个神经元具有线性激活,以预测下一时间步骤的洗发水销售数量。 + +一旦指定了网络,就必须使用后端数学库(例如 TensorFlow 或 Theano)将其编译成有效的符号表示。 + +在编译网络时,我们必须指定一个损失函数和优化算法。我们将使用“ _mean_squared_error_ ”作为损失函数,因为它与我们感兴趣的 RMSE 紧密匹配,以及有效的 ADAM 优化算法。 + +使用 Sequential Keras API 定义网络,下面的代码片段创建并编译网络。 + +``` +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') +``` + +编译后,它可以适合训练数据。由于网络是有状态的,我们必须控制何时重置内部状态。因此,我们必须在所需数量的时期内一次手动管理一个时期的训练过程。 + +默认情况下,一个迭代内的样本在暴露给网络之前进行混洗。同样,这对于 LSTM 来说是不合需要的,因为我们希望网络在整个观察序列中学习时建立状态。我们可以通过将“ _shuffle_ ”设置为“ _False_ ”来禁用样本的混洗。 + +此外,默认情况下,网络会在每个时代结束时报告有关模型学习进度和技能的大量调试信息。我们可以通过将“ _verbose_ ”参数设置为“ _0_ ”的级别来禁用它。 + +然后我们可以在训练时期结束时重置内部状态,为下一次训练迭代做好准备。 + +下面是一个手动使网络适合训练数据的循环。 + +``` +for i in range(nb_epoch): + model.fit(X, y, epochs=1, batch_size=batch_size, verbose=0, shuffle=False) + model.reset_states() +``` + +综上所述,我们可以定义一个名为 _fit_lstm()_ 的函数来训练并返回一个 LSTM 模型。作为参数,它将训练数据集置于监督学习格式,批量大小,多个时期和许多神经元中。 + +``` +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 +``` + +batch_size 必须设置为 1.这是因为它必须是训练和测试数据集大小的因子。 + +模型上的 _predict()_ 函数也受批量大小的限制;它必须设置为 1,因为我们有兴趣对测试数据进行一步预测。 + +我们不会在本教程中调整网络参数;相反,我们将使用以下配置,通过一些试验和错误找到: + +* 批量大小:1 +* 时代:3000 +* 神经元:4 + +作为本教程的扩展,您可能希望探索不同的模型参数,看看是否可以提高表现。 + +* **更新**:考虑尝试 1500 个迭代和 1 个神经元,表现可能更好! + +接下来,我们将了解如何使用适合的 LSTM 模型进行一步预测。 + +## LSTM 预测 + +一旦 LSTM 模型适合训练数据,它就可用于进行预测。 + +我们再次具有一定的灵活性。我们可以决定在所有训练数据上拟合一次模型,然后从测试数据中一次预测每个新时间步骤(我们称之为固定方法),或者我们可以重新拟合模型或更新模型的每一步作为测试数据的新观察结果的模型都可用(我们称之为动态方法)。 + +在本教程中,我们将采用固定方法来实现其简单性,但我们希望动态方法能够带来更好的模型技能。 + +为了进行预测,我们可以在模型上调用 _predict()_ 函数。这需要 3D NumPy 数组输入作为参数。在这种情况下,它将是一个值的数组,即前一时间步的观察值。 + +_predict()_ 函数返回一个预测数组,每个输入行一个。因为我们提供单个输入,所以输出将是具有一个值的 2D NumPy 数组。 + +我们可以在下面列出的名为 _forecast()_ 的函数中捕获此行为。给定拟合模型,在拟合模型时使用的批量大小(例如 1)和测试数据中的行,该函数将从测试行中分离输入数据,重新整形,并将预测作为单个返回浮点值。 + +``` +def forecast(model, batch_size, row): + X = row[0:-1] + X = X.reshape(1, 1, len(X)) + yhat = model.predict(X, batch_size=batch_size) + return yhat[0,0] +``` + +在训练期间,内部状态在每个时期之后被重置。在预测时,我们不希望重置预测之间的内部状态。事实上,我们希望模型在我们预测测试数据集中的每个时间步骤时建立状态。 + +这就提出了一个问题,即在预测测试数据集之前,网络的初始状态是什么。 + +在本教程中,我们将通过对训练数据集中的所有样本进行预测来播种状态。从理论上讲,应该建立内部状态,以便预测下一个时间步骤。 + +我们现在拥有适合 Shampoo Sales 数据集的 LSTM 网络模型并评估其表现的所有部分。 + +在下一节中,我们将把所有这些部分放在一起。 + +## 完整的 LSTM 示例 + +在本节中,我们将 LSTM 适用于 Shampoo Sales 数据集并评估模型。 + +这将涉及将前面部分中的所有元素汇总在一起。其中有很多,所以让我们回顾一下: + +1. 从 CSV 文件加载数据集。 +2. 转换数据集以使其适用于 LSTM 模型,包括: + 1. 将数据转换为监督学习问题。 + 2. 将数据转换为静止。 + 3. 转换数据使其具有-1 到 1 的比例。 +3. 将有状态 LSTM 网络模型拟合到训练数据。 +4. 评估测试数据上的静态 LSTM 模型。 +5. 报告预测的表现。 + +有关示例的一些注意事项: + +* 为简洁起见,缩放和反缩放行为已移至函数 _scale()_ 和 _invert_scale()_。 +* 使用缩放器在训练数据上的拟合来缩放测试数据,这是确保测试数据的最小值/最大值不影响模型所需的。 +* 调整数据变换的顺序以便于首先使数据静止,然后是监督学习问题,然后进行缩放。 +* 为了方便起见,在分成列车和测试集之前对整个数据集执行差分。我们可以在前进验证过程中轻松收集观察结果,并在我们前进时区分它们。为了便于阅读,我决定反对它。 + +下面列出了完整的示例。 + +``` +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 +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.fillna(0, inplace=True) + 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, value): + new_row = [x for x in X] + [value] + 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] + +# 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 the model +lstm_model = fit_lstm(train_scaled, 1, 3000, 4) +# forecast the entire training dataset to build up state for forecasting +train_reshaped = train_scaled[:, 0].reshape(len(train_scaled), 1, 1) +lstm_model.predict(train_reshaped, batch_size=1) + +# walk-forward validation on the test data +predictions = list() +for i in range(len(test_scaled)): + # make one-step forecast + 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) + expected = raw_values[len(train) + i + 1] + print('Month=%d, Predicted=%f, Expected=%f' % (i+1, yhat, expected)) + +# report performance +rmse = sqrt(mean_squared_error(raw_values[-12:], predictions)) +print('Test RMSE: %.3f' % rmse) +# line plot of observed vs predicted +pyplot.plot(raw_values[-12:]) +pyplot.plot(predictions) +pyplot.show() +``` + +运行该示例将在测试数据集中打印 12 个月中每个月的预期值和预测值。 + +该示例还打印所有预测的 RMSE。该模型显示每月洗发水销售量为 71.721 的 RMSE,优于达到 136.761 洗发水销售 RMSE 的持久性模型。 + +随机数用于播种 LSTM,因此,您可能会从模型的单次运行中获得不同的结果。我们将在下一节进一步讨论这个问题。 + +``` +Month=1, Predicted=351.582196, Expected=339.700000 +Month=2, Predicted=432.169667, Expected=440.400000 +Month=3, Predicted=378.064505, Expected=315.900000 +Month=4, Predicted=441.370077, Expected=439.300000 +Month=5, Predicted=446.872627, Expected=401.300000 +Month=6, Predicted=514.021244, Expected=437.400000 +Month=7, Predicted=525.608903, Expected=575.500000 +Month=8, Predicted=473.072365, Expected=407.600000 +Month=9, Predicted=523.126979, Expected=682.000000 +Month=10, Predicted=592.274106, Expected=475.300000 +Month=11, Predicted=589.299863, Expected=581.300000 +Month=12, Predicted=584.149152, Expected=646.900000 +Test RMSE: 71.721 +``` + +还创建了测试数据(蓝色)与预测值(橙色)的线图,为模型技能提供了上下文。 + +![Line Plot of LSTM Forecast vs Expected Values](img/23ca281ff10f02aae3919d3aa204108f.jpg) + +LSTM 预测和预期值的线图 + +作为 afternote,您可以进行快速实验,以建立您对测试工具以及所有变换和逆变换的信任。 + +在前进验证中注释掉适合 LSTM 模型的行: + +``` +yhat = forecast_lstm(lstm_model, 1, X) +``` + +并将其替换为以下内容: + +``` +yhat = y +``` + +这应该产生具有完美技能的模型(例如,将预期结果预测为模型输出的模型)。 + +结果应如下所示,表明如果 LSTM 模型能够完美地预测该系列,则逆变换和误差计算将正确显示。 + +``` +Month=1, Predicted=339.700000, Expected=339.700000 +Month=2, Predicted=440.400000, Expected=440.400000 +Month=3, Predicted=315.900000, Expected=315.900000 +Month=4, Predicted=439.300000, Expected=439.300000 +Month=5, Predicted=401.300000, Expected=401.300000 +Month=6, Predicted=437.400000, Expected=437.400000 +Month=7, Predicted=575.500000, Expected=575.500000 +Month=8, Predicted=407.600000, Expected=407.600000 +Month=9, Predicted=682.000000, Expected=682.000000 +Month=10, Predicted=475.300000, Expected=475.300000 +Month=11, Predicted=581.300000, Expected=581.300000 +Month=12, Predicted=646.900000, Expected=646.900000 +Test RMSE: 0.000 +``` + +## 制定稳健的结果 + +神经网络的一个难点是它们在不同的起始条件下给出不同的结果。 + +一种方法可能是修复 Keras 使用的随机数种子,以确保结果可重复。另一种方法是使用不同的实验设置来控制随机初始条件。 + +有关机器学习中随机性的更多信息,请参阅帖子: + +* [在机器学习中拥抱随机性](http://machinelearningmastery.com/randomness-in-machine-learning/) + +我们可以多次重复上一节中的实验,然后将平均 RMSE 作为预期配置在平均看不见的数据上的表现的指示。 + +这通常称为多次重复或多次重启。 + +我们可以在固定数量的重复循环中包装模型拟合和前进验证。每次迭代都可以记录运行的 RMSE。然后我们可以总结 RMSE 分数的分布。 + +``` +# repeat experiment +repeats = 30 +error_scores = list() +for r in range(repeats): + # fit the model + lstm_model = fit_lstm(train_scaled, 1, 3000, 4) + # forecast the entire training dataset to build up state for forecasting + train_reshaped = train_scaled[:, 0].reshape(len(train_scaled), 1, 1) + lstm_model.predict(train_reshaped, batch_size=1) + # walk-forward validation on the test data + predictions = list() + for i in range(len(test_scaled)): + # make one-step forecast + 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) +``` + +数据准备与以前相同。 + +我们将使用 30 次重复,因为这足以提供 RMSE 分数的良好分布。 + +下面列出了完整的示例。 + +``` +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 +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.fillna(0, inplace=True) + 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, value): + new_row = [x for x in X] + [value] + 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] + +# 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) + +# repeat experiment +repeats = 30 +error_scores = list() +for r in range(repeats): + # fit the model + lstm_model = fit_lstm(train_scaled, 1, 3000, 4) + # forecast the entire training dataset to build up state for forecasting + train_reshaped = train_scaled[:, 0].reshape(len(train_scaled), 1, 1) + lstm_model.predict(train_reshaped, batch_size=1) + # walk-forward validation on the test data + predictions = list() + for i in range(len(test_scaled)): + # make one-step forecast + 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) + +# summarize results +results = DataFrame() +results['rmse'] = error_scores +print(results.describe()) +results.boxplot() +pyplot.show() +``` + +运行该示例每次重复打印 RMSE 分数。运行结束提供收集的 RMSE 分数的摘要统计。 + +我们可以看到平均值和标准差 RMSE 分数分别为 138.491905 和 46.313783 月洗发水销售额。 + +这是一个非常有用的结果,因为它表明上面报告的结果可能是一个统计上的侥幸。实验表明,该模型可能与持久性模型一样好(136.761),如果不是稍差的话。 + +这表明,至少需要进一步的模型调整。 + +``` +1) Test RMSE: 136.191 +2) Test RMSE: 169.693 +3) Test RMSE: 176.553 +4) Test RMSE: 198.954 +5) Test RMSE: 148.960 +6) Test RMSE: 103.744 +7) Test RMSE: 164.344 +8) Test RMSE: 108.829 +9) Test RMSE: 232.282 +10) Test RMSE: 110.824 +11) Test RMSE: 163.741 +12) Test RMSE: 111.535 +13) Test RMSE: 118.324 +14) Test RMSE: 107.486 +15) Test RMSE: 97.719 +16) Test RMSE: 87.817 +17) Test RMSE: 92.920 +18) Test RMSE: 112.528 +19) Test RMSE: 131.687 +20) Test RMSE: 92.343 +21) Test RMSE: 173.249 +22) Test RMSE: 182.336 +23) Test RMSE: 101.477 +24) Test RMSE: 108.171 +25) Test RMSE: 135.880 +26) Test RMSE: 254.507 +27) Test RMSE: 87.198 +28) Test RMSE: 122.588 +29) Test RMSE: 228.449 +30) Test RMSE: 94.427 + rmse +count 30.000000 +mean 138.491905 +std 46.313783 +min 87.198493 +25% 104.679391 +50% 120.456233 +75% 168.356040 +max 254.507272 +``` + +根据下面显示的分布创建一个盒子和胡须图。这将捕获数据的中间以及范围和异常值结果。 + +![LSTM Repeated Experiment Box and Whisker Plot](img/5bff80c488cfe96f08b491d070af89d9.jpg) + +LSTM 重复实验箱和晶须图 + +这是一个实验设置,可用于比较 LSTM 模型的一个配置或设置到另一个配置。 + +## 教程扩展 + +我们可能会考虑本教程的许多扩展。 + +也许您可以自己探索其中一些,并在下面的评论中发布您的发现。 + +* **多步预测**。可以改变实验设置以预测下一个 _n_ - 时间步骤而不是下一个单一时间步骤。这也将允许更大的批量和更快的训练。请注意,我们基本上在本教程中执行了一种 12 步一步预测,因为模型未更新,尽管有新的观察结果并且可用作输入变量。 +* **调谐 LSTM 模型**。该模型没有调整;相反,配置被发现有一些快速的试验和错误。我相信通过至少调整神经元的数量和训练时期的数量可以获得更好的结果。我还认为通过回调提前停止可能在训练期间很有用。 +* **种子状态实验**。目前尚不清楚在预测之前通过预测所有训练数据来播种系统是否有益。理论上这似乎是一个好主意,但这需要得到证明。此外,也许在预测之前播种模型的其他方法也是有益的。 +* **更新模型**。可以在前进验证的每个时间步骤中更新模型。需要进行实验以确定从头开始重新修改模型是否更好,或者使用包括新样本在内的更多训练时期更新权重。 +* **输入时间步**。 LSTM 输入支持样本的多个时间步长。需要进行实验以查看是否包括滞后观察作为时间步骤提供任何益处。 +* **输入延迟功能**。可以包括滞后观察作为输入特征。需要进行实验以查看包含滞后特征是否提供任何益处,与 AR(k)线性模型不同。 +* **输入错误系列**。可以构造误差序列(来自持久性模型的预测误差)并且用作附加输入特征,与 MA(k)线性模型不同。需要进行实验以确定这是否会带来任何好处。 +* **学习非固定**。 LSTM 网络可能能够了解数据中的趋势并做出合理的预测。需要进行实验以了解 LSTM 是否可以学习和有效预测数据中剩余的时间依赖结构,如趋势和季节性。 +* **对比无状态**。本教程中使用了有状态 LSTM。应将结果与无状态 LSTM 配置进行比较。 +* **统计学意义**。可以进一步扩展多重复实验方案以包括统计显着性检验,以证明具有不同构型的 RMSE 结果群体之间的差异是否具有统计学显着性。 + +## 摘要 + +在本教程中,您了解了如何为时间序列预测开发 LSTM 模型。 + +具体来说,你学到了: + +* 如何准备用于开发 LSTM 模型的时间序列数据。 +* 如何开发 LSTM 模型进行时间序列预测。 +* 如何使用强大的测试工具评估 LSTM 模型。 + +你能得到更好的结果吗? +在下面的评论中分享您的发现。 \ No newline at end of file diff --git a/docs/dl-ts/time-series-prediction-lstm-recurrent-neural-networks-python-keras.md b/docs/dl-ts/time-series-prediction-lstm-recurrent-neural-networks-python-keras.md new file mode 100644 index 0000000000000000000000000000000000000000..f910d04f1991745f3dbbdc1334fdbd17143eaf36 --- /dev/null +++ b/docs/dl-ts/time-series-prediction-lstm-recurrent-neural-networks-python-keras.md @@ -0,0 +1,883 @@ +# 基于 Keras 的 Python 中 LSTM 循环神经网络的时间序列预测 + +> 原文: [https://machinelearningmastery.com/time-series-prediction-lstm-recurrent-neural-networks-python-keras/](https://machinelearningmastery.com/time-series-prediction-lstm-recurrent-neural-networks-python-keras/) + +时间序列预测问题是一种难以预测的建模问题。 + +与回归预测建模不同,时间序列还增加了输入变量之间序列依赖性的复杂性。 + +设计用于处理序列依赖性的强大类型的神经网络称为[循环神经网络](http://machinelearningmastery.com/crash-course-recurrent-neural-networks-deep-learning/)。长短期内存网络或 LSTM 网络是一种用于深度学习的循环神经网络,因为可以成功训练非常大的架构。 + +在本文中,您将了解如何使用 Keras 深度学习库在 Python 中开发 LSTM 网络,以解决演示时间序列预测问题。 + +完成本教程后,您将了解如何为您自己的时间序列预测问题和其他更常见的序列问题实现和开发 LSTM 网络。你会知道: + +* 关于国际航空公司乘客时间序列预测问题。 +* 如何开发 LSTM 网络用于基于回归,窗口和时间步长的时间序列预测问题的框架。 +* 如何使用 LSTM 网络开发和预测,这些网络在很长的序列中维护状态(内存)。 + +在本教程中,我们将针对标准时间序列预测问题开发许多 LSTM。 LSTM 网络的问题和所选配置仅用于演示目的,仅用于优化。 + +这些示例将向您展示如何为时间序列预测建模问题开发自己的不同结构的 LSTM 网络。 + +让我们开始吧。 + +* **2016 年 10 月更新**:在每个示例中计算 RMSE 的方式出错。报告的 RMSE 是完全错误的。现在,RMSE 直接根据预测计算,RMSE 和预测图都以原始数据集为单位。使用 Keras 1.1.0,TensorFlow 0.10.0 和 scikit-learn v0.18 评估模型。感谢所有指出这个问题的人,以及 Philip O'Brien 帮助指出修复的问题。 +* **2017 年 3 月更新**:更新了 Keras 2.0.2,TensorFlow 1.0.1 和 Theano 0.9.0 的示例。 +* **2017 年 4 月更新**:有关时间序列预测的更完整和更好解释的 LSTM 教程,请参阅 Python 中的长期短期记忆网络[时间序列预测。](http://machinelearningmastery.com/time-series-forecasting-long-short-term-memory-network-python/) + +### 更新了 LSTM 时间序列预测帖子: + +这篇文章中的例子非常陈旧,我有更好的例子可用于在时间序列中使用 LSTM,请参阅: + +1. [用于单变量时间序列预测的 LSTM](https://machinelearningmastery.com/time-series-forecasting-long-short-term-memory-network-python/) +2. [多变量时间序列预测的 LSTM](https://machinelearningmastery.com/multivariate-time-series-forecasting-lstms-keras/) +3. [用于多步时间序列预测的 LSTM](https://machinelearningmastery.com/multi-step-time-series-forecasting-long-short-term-memory-networks-python/) + +![Time Series Prediction with LSTM Recurrent Neural Networks in Python with Keras](img/cfd6e4ff84de46c79ccf71d45a6a5251.jpg) + +时间序列预测与 LSTM 循环神经网络在 Python 与 Keras +照片由 [Margaux-Marguerite Duquesnoy](https://www.flickr.com/photos/124559226@N08/15792381395/) ,保留一些权利。 + +## 问题描述 + +我们将在这篇文章中讨论的问题是国际航空公司乘客预测问题。 + +这是一个问题,在一年零一个月的情况下,任务是以 1,000 为单位预测国际航空公司乘客的数量。数据范围从 1949 年 1 月到 1960 年 12 月,或 12 年,有 144 个观测值。 + +该数据集可从 [DataMarket 网页免费下载,作为 CSV 下载](https://datamarket.com/data/set/22u3/international-airline-passengers-monthly-totals-in-thousands-jan-49-dec-60#!ds=22u3&display=line),文件名为“ _international-airline-passengers.csv_ ”。 + +下面是该文件前几行的示例。 + +``` +"Month","International airline passengers: monthly totals in thousands. Jan 49 ? Dec 60" +"1949-01",112 +"1949-02",118 +"1949-03",132 +"1949-04",129 +"1949-05",121 +``` + +我们可以使用 Pandas 库轻松加载此数据集。鉴于每个观察以一个月的相同间隔分开,我们对日期不感兴趣。因此,当我们加载数据集时,我们可以排除第一列。 + +下载的数据集还有页脚信息,我们可以将 **skipfooter** 参数排除到 **pandas.read_csv()**为 3 页脚行设置为 3。加载后,我们可以轻松绘制整个数据集。下面列出了加载和绘制数据集的代码。 + +``` +import pandas +import matplotlib.pyplot as plt +dataset = pandas.read_csv('international-airline-passengers.csv', usecols=[1], engine='python', skipfooter=3) +plt.plot(dataset) +plt.show() +``` + +随着时间的推移,您可以看到数据集中的上升趋势。 + +您还可以看到数据集的某些周期性,可能与北半球度假期相对应。 + +![Plot of the Airline Passengers Dataset](img/9e5711f15b64b80d86fc7aefd07d5322.jpg) + +航空公司乘客数据集的情节 + +我们将保持简单并按原样处理数据。 + +通常,研究各种数据准备技术以重新调整数据并使其静止是一个好主意。 + +## 长短期记忆网络 + +长期短期记忆网络或 LSTM 网络是一种循环神经网络,使用反向传播时间训练并克服消失的梯度问题。 + +因此,它可用于创建大型循环网络,这些网络又可用于解决机器学习中的困难序列问题并实现最先进的结果。 + +LSTM 网络具有通过层连接的存储块,而不是神经元。 + +块具有使其比经典神经元更聪明的组件和用于最近序列的存储器。块包含管理块状态和输出的门。块对输入序列进行操作,并且块内的每个门使用 S 形激活单元来控制它们是否被触发,使状态的改变和流过块的信息的添加成为条件。 + +一个单元内有三种类型的门: + +* **忘记门**:有条件地决定从块中丢弃的信息。 +* **输入门**:有条件地决定输入中的哪些值来更新存储器状态。 +* **输出门**:根据输入和块的存储器有条件地决定输出内容。 + +每个单元就像一个小型状态机,其中单元的门具有在训练过程中学习的权重。 + +您可以看到如何通过一层 LSTM 实现复杂的学习和记忆,并且不难想象高阶抽象如何与多个这样的层分层。 + +## LSTM 回归网络 + +我们可以将问题表述为回归问题。 + +也就是说,考虑到本月乘客人数(以千人为单位),下个月的乘客人数是多少? + +我们可以编写一个简单的函数将我们的单列数据转换为两列数据集:第一列包含本月的(t)乘客数,第二列包含下个月的(t + 1)乘客数,可以预测。 + +在我们开始之前,让我们首先导入我们打算使用的所有函数和类。这假设一个工作的 SciPy 环境安装了 Keras 深度学习库。 + +``` +import numpy +import matplotlib.pyplot as plt +import pandas +import math +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import LSTM +from sklearn.preprocessing import MinMaxScaler +from sklearn.metrics import mean_squared_error +``` + +在我们做任何事情之前,最好修复随机数种子以确保我们的结果可重复。 + +``` +# fix random seed for reproducibility +numpy.random.seed(7) +``` + +我们还可以使用上一节中的代码将数据集作为 Pandas 数据帧加载。然后,我们可以从数据帧中提取 NumPy 数组,并将整数值转换为浮点值,这些值更适合使用神经网络进行建模。 + +``` +# load the dataset +dataframe = pandas.read_csv('international-airline-passengers.csv', usecols=[1], engine='python', skipfooter=3) +dataset = dataframe.values +dataset = dataset.astype('float32') +``` + +LSTM 对输入数据的比例敏感,特别是在使用 sigmoid(默认)或 tanh 激活函数时。将数据重新调整到 0 到 1 的范围是一种很好的做法,也称为标准化。我们可以使用 scikit-learn 库中的 **MinMaxScaler** 预处理类轻松地规范化数据集。 + +``` +# normalize the dataset +scaler = MinMaxScaler(feature_range=(0, 1)) +dataset = scaler.fit_transform(dataset) +``` + +在我们对数据建模并估计模型在训练数据集上的技能之后,我们需要了解模型在新的看不见的数据上的技能。对于正常的分类或回归问题,我们将使用交叉验证来执行此操作。 + +对于时间序列数据,值的序列很重要。我们可以使用的一种简单方法是将有序数据集拆分为训练和测试数据集。下面的代码计算分裂点的索引,并将数据分成训练数据集,其中 67%的观测值可用于训练我们的模型,剩余的 33%用于测试模型。 + +``` +# split into train and test sets +train_size = int(len(dataset) * 0.67) +test_size = len(dataset) - train_size +train, test = dataset[0:train_size,:], dataset[train_size:len(dataset),:] +print(len(train), len(test)) +``` + +现在我们可以定义一个函数来创建一个新的数据集,如上所述。 + +该函数有两个参数:**数据集**,它是我们想要转换为数据集的 NumPy 数组,以及 **look_back** ,它是之前使用的时间步数输入变量来预测下一个时间段 - 在这种情况下默认为 1。 + +此默认值将创建一个数据集,其中 X 是给定时间(t)的乘客数量,Y 是下次乘客的数量(t + 1)。 + +它可以配置,我们将在下一节中构建一个不同形状的数据集。 + +``` +# convert an array of values into a dataset matrix +def create_dataset(dataset, look_back=1): + dataX, dataY = [], [] + for i in range(len(dataset)-look_back-1): + a = dataset[i:(i+look_back), 0] + dataX.append(a) + dataY.append(dataset[i + look_back, 0]) + return numpy.array(dataX), numpy.array(dataY) +``` + +让我们看一下这个函数对数据集第一行的影响(为了清晰起见,以非标准化形式显示)。 + +``` +X Y +112 118 +118 132 +132 129 +129 121 +121 135 +``` + +如果将前 5 行与上一节中列出的原始数据集样本进行比较,则可以在数字中看到 X = t 和 Y = t + 1 模式。 + +让我们使用这个函数来准备训练和测试数据集以进行建模。 + +``` +# reshape into X=t and Y=t+1 +look_back = 1 +trainX, trainY = create_dataset(train, look_back) +testX, testY = create_dataset(test, look_back) +``` + +LSTM 网络期望输入数据(X)以以下形式提供特定的阵列结构: _[样本,时间步长,特征]_ 。 + +目前,我们的数据形式为:[_ 样本,特征 _],我们将问题定为每个样本的一个时间步长。我们可以使用 **numpy.reshape()**将准备好的列车和测试输入数据转换为预期结构,如下所示: + +``` +# reshape input to be [samples, time steps, features] +trainX = numpy.reshape(trainX, (trainX.shape[0], 1, trainX.shape[1])) +testX = numpy.reshape(testX, (testX.shape[0], 1, testX.shape[1])) +``` + +我们现在准备设计并适应我们的 LSTM 网络来解决这个问题。 + +网络具有带有 1 个输入的可见层,带有 4 个 LSTM 块或神经元的隐藏层,以及进行单个值预测的输出层。默认的 sigmoid 激活函数用于 LSTM 块。对网络进行 100 个迭代的训练,并使用 1 的批量大小。 + +``` +# create and fit the LSTM network +model = Sequential() +model.add(LSTM(4, input_shape=(1, look_back))) +model.add(Dense(1)) +model.compile(loss='mean_squared_error', optimizer='adam') +model.fit(trainX, trainY, epochs=100, batch_size=1, verbose=2) +``` + +一旦模型拟合,我们就可以估计模型在列车和测试数据集上的表现。这将为我们提供新模型的比较点。 + +请注意,我们在计算错误分数之前反转预测,以确保以与原始数据相同的单位报告表现(每月数千名乘客)。 + +``` +# make predictions +trainPredict = model.predict(trainX) +testPredict = model.predict(testX) +# invert predictions +trainPredict = scaler.inverse_transform(trainPredict) +trainY = scaler.inverse_transform([trainY]) +testPredict = scaler.inverse_transform(testPredict) +testY = scaler.inverse_transform([testY]) +# calculate root mean squared error +trainScore = math.sqrt(mean_squared_error(trainY[0], trainPredict[:,0])) +print('Train Score: %.2f RMSE' % (trainScore)) +testScore = math.sqrt(mean_squared_error(testY[0], testPredict[:,0])) +print('Test Score: %.2f RMSE' % (testScore)) +``` + +最后,我们可以使用模型为列车和测试数据集生成预测,以获得模型技能的直观指示。 + +由于数据集的准备方式,我们必须改变预测,使它们在 x 轴上与原始数据集对齐。准备好后,绘制数据,以蓝色显示原始数据集,以绿色显示训练数据集的预测,以及以红色显示未见测试数据集的预测。 + +``` +# shift train predictions for plotting +trainPredictPlot = numpy.empty_like(dataset) +trainPredictPlot[:, :] = numpy.nan +trainPredictPlot[look_back:len(trainPredict)+look_back, :] = trainPredict +# shift test predictions for plotting +testPredictPlot = numpy.empty_like(dataset) +testPredictPlot[:, :] = numpy.nan +testPredictPlot[len(trainPredict)+(look_back*2)+1:len(dataset)-1, :] = testPredict +# plot baseline and predictions +plt.plot(scaler.inverse_transform(dataset)) +plt.plot(trainPredictPlot) +plt.plot(testPredictPlot) +plt.show() +``` + +我们可以看到该模型在拟合训练和测试数据集方面做得非常出色。 + +![LSTM Trained on Regression Formulation of Passenger Prediction Problem](img/4418cff7f2e1ccd0febd2e302d959377.jpg) + +LSTM 对乘客预测问题的回归制定进行了训练 + +为了完整起见,下面是整个代码示例。 + +``` +# LSTM for international airline passengers problem with regression framing +import numpy +import matplotlib.pyplot as plt +from pandas import read_csv +import math +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import LSTM +from sklearn.preprocessing import MinMaxScaler +from sklearn.metrics import mean_squared_error +# convert an array of values into a dataset matrix +def create_dataset(dataset, look_back=1): + dataX, dataY = [], [] + for i in range(len(dataset)-look_back-1): + a = dataset[i:(i+look_back), 0] + dataX.append(a) + dataY.append(dataset[i + look_back, 0]) + return numpy.array(dataX), numpy.array(dataY) +# fix random seed for reproducibility +numpy.random.seed(7) +# load the dataset +dataframe = read_csv('international-airline-passengers.csv', usecols=[1], engine='python', skipfooter=3) +dataset = dataframe.values +dataset = dataset.astype('float32') +# normalize the dataset +scaler = MinMaxScaler(feature_range=(0, 1)) +dataset = scaler.fit_transform(dataset) +# split into train and test sets +train_size = int(len(dataset) * 0.67) +test_size = len(dataset) - train_size +train, test = dataset[0:train_size,:], dataset[train_size:len(dataset),:] +# reshape into X=t and Y=t+1 +look_back = 1 +trainX, trainY = create_dataset(train, look_back) +testX, testY = create_dataset(test, look_back) +# reshape input to be [samples, time steps, features] +trainX = numpy.reshape(trainX, (trainX.shape[0], 1, trainX.shape[1])) +testX = numpy.reshape(testX, (testX.shape[0], 1, testX.shape[1])) +# create and fit the LSTM network +model = Sequential() +model.add(LSTM(4, input_shape=(1, look_back))) +model.add(Dense(1)) +model.compile(loss='mean_squared_error', optimizer='adam') +model.fit(trainX, trainY, epochs=100, batch_size=1, verbose=2) +# make predictions +trainPredict = model.predict(trainX) +testPredict = model.predict(testX) +# invert predictions +trainPredict = scaler.inverse_transform(trainPredict) +trainY = scaler.inverse_transform([trainY]) +testPredict = scaler.inverse_transform(testPredict) +testY = scaler.inverse_transform([testY]) +# calculate root mean squared error +trainScore = math.sqrt(mean_squared_error(trainY[0], trainPredict[:,0])) +print('Train Score: %.2f RMSE' % (trainScore)) +testScore = math.sqrt(mean_squared_error(testY[0], testPredict[:,0])) +print('Test Score: %.2f RMSE' % (testScore)) +# shift train predictions for plotting +trainPredictPlot = numpy.empty_like(dataset) +trainPredictPlot[:, :] = numpy.nan +trainPredictPlot[look_back:len(trainPredict)+look_back, :] = trainPredict +# shift test predictions for plotting +testPredictPlot = numpy.empty_like(dataset) +testPredictPlot[:, :] = numpy.nan +testPredictPlot[len(trainPredict)+(look_back*2)+1:len(dataset)-1, :] = testPredict +# plot baseline and predictions +plt.plot(scaler.inverse_transform(dataset)) +plt.plot(trainPredictPlot) +plt.plot(testPredictPlot) +plt.show() +``` + +运行该示例将生成以下输出。 + +``` +... +Epoch 95/100 +0s - loss: 0.0020 +Epoch 96/100 +0s - loss: 0.0020 +Epoch 97/100 +0s - loss: 0.0020 +Epoch 98/100 +0s - loss: 0.0020 +Epoch 99/100 +0s - loss: 0.0020 +Epoch 100/100 +0s - loss: 0.0020 +Train Score: 22.93 RMSE +Test Score: 47.53 RMSE +``` + +我们可以看到该模型在训练数据集上的平均误差约为 23 名乘客(以千计),在测试数据集上的平均误差约为 52 名乘客(以千计)。没那么糟。 + +## 使用窗口方法进行回归的 LSTM + +我们还可以对问题进行短语,以便可以使用多个最近时间步骤来对下一个时间步进行预测。 + +这称为窗口,窗口的大小是可以针对每个问题进行调整的参数。 + +例如,给定当前时间(t)我们想要预测序列中下一次的值(t + 1),我们可以使用当前时间(t),以及前两次(t-1)和 t-2)作为输入变量。 + +当表达为回归问题时,输入变量为 t-2,t-1,t,输出变量为 t + 1。 + +我们在上一节中创建的 **create_dataset()**函数允许我们通过将 **look_back** 参数从 1 增加到 3 来创建时间序列问题的这个公式。 + +具有此秘籍的数据集样本如下所示: + +``` +X1 X2 X3 Y +112 118 132 129 +118 132 129 121 +132 129 121 135 +129 121 135 148 +121 135 148 148 +``` + +我们可以使用更大的窗口大小重新运行上一节中的示例。为了完整性,下面列出了仅包含窗口大小更改的整个代码清单。 + +``` +# LSTM for international airline passengers problem with window regression framing +import numpy +import matplotlib.pyplot as plt +from pandas import read_csv +import math +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import LSTM +from sklearn.preprocessing import MinMaxScaler +from sklearn.metrics import mean_squared_error +# convert an array of values into a dataset matrix +def create_dataset(dataset, look_back=1): + dataX, dataY = [], [] + for i in range(len(dataset)-look_back-1): + a = dataset[i:(i+look_back), 0] + dataX.append(a) + dataY.append(dataset[i + look_back, 0]) + return numpy.array(dataX), numpy.array(dataY) +# fix random seed for reproducibility +numpy.random.seed(7) +# load the dataset +dataframe = read_csv('international-airline-passengers.csv', usecols=[1], engine='python', skipfooter=3) +dataset = dataframe.values +dataset = dataset.astype('float32') +# normalize the dataset +scaler = MinMaxScaler(feature_range=(0, 1)) +dataset = scaler.fit_transform(dataset) +# split into train and test sets +train_size = int(len(dataset) * 0.67) +test_size = len(dataset) - train_size +train, test = dataset[0:train_size,:], dataset[train_size:len(dataset),:] +# reshape into X=t and Y=t+1 +look_back = 3 +trainX, trainY = create_dataset(train, look_back) +testX, testY = create_dataset(test, look_back) +# reshape input to be [samples, time steps, features] +trainX = numpy.reshape(trainX, (trainX.shape[0], 1, trainX.shape[1])) +testX = numpy.reshape(testX, (testX.shape[0], 1, testX.shape[1])) +# create and fit the LSTM network +model = Sequential() +model.add(LSTM(4, input_shape=(1, look_back))) +model.add(Dense(1)) +model.compile(loss='mean_squared_error', optimizer='adam') +model.fit(trainX, trainY, epochs=100, batch_size=1, verbose=2) +# make predictions +trainPredict = model.predict(trainX) +testPredict = model.predict(testX) +# invert predictions +trainPredict = scaler.inverse_transform(trainPredict) +trainY = scaler.inverse_transform([trainY]) +testPredict = scaler.inverse_transform(testPredict) +testY = scaler.inverse_transform([testY]) +# calculate root mean squared error +trainScore = math.sqrt(mean_squared_error(trainY[0], trainPredict[:,0])) +print('Train Score: %.2f RMSE' % (trainScore)) +testScore = math.sqrt(mean_squared_error(testY[0], testPredict[:,0])) +print('Test Score: %.2f RMSE' % (testScore)) +# shift train predictions for plotting +trainPredictPlot = numpy.empty_like(dataset) +trainPredictPlot[:, :] = numpy.nan +trainPredictPlot[look_back:len(trainPredict)+look_back, :] = trainPredict +# shift test predictions for plotting +testPredictPlot = numpy.empty_like(dataset) +testPredictPlot[:, :] = numpy.nan +testPredictPlot[len(trainPredict)+(look_back*2)+1:len(dataset)-1, :] = testPredict +# plot baseline and predictions +plt.plot(scaler.inverse_transform(dataset)) +plt.plot(trainPredictPlot) +plt.plot(testPredictPlot) +plt.show() +``` + +运行该示例提供以下输出: + +``` +... +Epoch 95/100 +0s - loss: 0.0021 +Epoch 96/100 +0s - loss: 0.0021 +Epoch 97/100 +0s - loss: 0.0021 +Epoch 98/100 +0s - loss: 0.0021 +Epoch 99/100 +0s - loss: 0.0022 +Epoch 100/100 +0s - loss: 0.0020 +Train Score: 24.19 RMSE +Test Score: 58.03 RMSE +``` + +我们可以看到,与上一节相比,误差略有增加。窗口大小和网络架构没有调整:这只是如何构建预测问题的演示。 + +![LSTM Trained on Window Method Formulation of Passenger Prediction Problem](img/0d5bd91efd1afcce7169f754f1456634.jpg) + +LSTM 训练乘客预测问题的窗口方法 + +## 带时间步长的回归 LSTM + +您可能已经注意到 LSTM 网络的数据准备包括时间步骤。 + +一些序列问题可能每个样本具有不同数量的时间步长。例如,您可能会测量导致故障点或喘振点的物理机器。每个事件都是一个样本,导致事件的观察将是时间步骤,观察到的变量将是特征。 + +时间步骤提供了另一种表达时间序列问题的方法。与上面的窗口示例一样,我们可以将时间序列中的先前时间步长作为输入来预测下一时间步的输出。 + +我们可以将它们用作一个输入要素的时间步长,而不是将过去的观察结果作为单独的输入要素,这确实是对问题的更准确的框架。 + +我们可以使用与上一个基于窗口的示例相同的数据表示来执行此操作,除非我们对数据进行整形,我们将列设置为时间步长维并将要素维更改为 1.例如: + +``` +# reshape input to be [samples, time steps, features] +trainX = numpy.reshape(trainX, (trainX.shape[0], trainX.shape[1], 1)) +testX = numpy.reshape(testX, (testX.shape[0], testX.shape[1], 1)) +``` + +下面提供了整个代码清单,以确保完整性。 + +``` +# LSTM for international airline passengers problem with time step regression framing +import numpy +import matplotlib.pyplot as plt +from pandas import read_csv +import math +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import LSTM +from sklearn.preprocessing import MinMaxScaler +from sklearn.metrics import mean_squared_error +# convert an array of values into a dataset matrix +def create_dataset(dataset, look_back=1): + dataX, dataY = [], [] + for i in range(len(dataset)-look_back-1): + a = dataset[i:(i+look_back), 0] + dataX.append(a) + dataY.append(dataset[i + look_back, 0]) + return numpy.array(dataX), numpy.array(dataY) +# fix random seed for reproducibility +numpy.random.seed(7) +# load the dataset +dataframe = read_csv('international-airline-passengers.csv', usecols=[1], engine='python', skipfooter=3) +dataset = dataframe.values +dataset = dataset.astype('float32') +# normalize the dataset +scaler = MinMaxScaler(feature_range=(0, 1)) +dataset = scaler.fit_transform(dataset) +# split into train and test sets +train_size = int(len(dataset) * 0.67) +test_size = len(dataset) - train_size +train, test = dataset[0:train_size,:], dataset[train_size:len(dataset),:] +# reshape into X=t and Y=t+1 +look_back = 3 +trainX, trainY = create_dataset(train, look_back) +testX, testY = create_dataset(test, look_back) +# reshape input to be [samples, time steps, features] +trainX = numpy.reshape(trainX, (trainX.shape[0], trainX.shape[1], 1)) +testX = numpy.reshape(testX, (testX.shape[0], testX.shape[1], 1)) +# create and fit the LSTM network +model = Sequential() +model.add(LSTM(4, input_shape=(look_back, 1))) +model.add(Dense(1)) +model.compile(loss='mean_squared_error', optimizer='adam') +model.fit(trainX, trainY, epochs=100, batch_size=1, verbose=2) +# make predictions +trainPredict = model.predict(trainX) +testPredict = model.predict(testX) +# invert predictions +trainPredict = scaler.inverse_transform(trainPredict) +trainY = scaler.inverse_transform([trainY]) +testPredict = scaler.inverse_transform(testPredict) +testY = scaler.inverse_transform([testY]) +# calculate root mean squared error +trainScore = math.sqrt(mean_squared_error(trainY[0], trainPredict[:,0])) +print('Train Score: %.2f RMSE' % (trainScore)) +testScore = math.sqrt(mean_squared_error(testY[0], testPredict[:,0])) +print('Test Score: %.2f RMSE' % (testScore)) +# shift train predictions for plotting +trainPredictPlot = numpy.empty_like(dataset) +trainPredictPlot[:, :] = numpy.nan +trainPredictPlot[look_back:len(trainPredict)+look_back, :] = trainPredict +# shift test predictions for plotting +testPredictPlot = numpy.empty_like(dataset) +testPredictPlot[:, :] = numpy.nan +testPredictPlot[len(trainPredict)+(look_back*2)+1:len(dataset)-1, :] = testPredict +# plot baseline and predictions +plt.plot(scaler.inverse_transform(dataset)) +plt.plot(trainPredictPlot) +plt.plot(testPredictPlot) +plt.show() +``` + +运行该示例提供以下输出: + +``` +... +Epoch 95/100 +1s - loss: 0.0021 +Epoch 96/100 +1s - loss: 0.0021 +Epoch 97/100 +1s - loss: 0.0021 +Epoch 98/100 +1s - loss: 0.0020 +Epoch 99/100 +1s - loss: 0.0021 +Epoch 100/100 +1s - loss: 0.0020 +Train Score: 23.69 RMSE +Test Score: 58.88 RMSE +``` + +我们可以看到结果略好于前面的例子,尽管输入数据的结构更有意义。 + +![LSTM Trained on Time Step Formulation of Passenger Prediction Problem](img/7abf4eed7ba2492b303ee9875fa6f90a.jpg) + +LSTM 训练乘客预测问题的时间步长形式 + +## LSTM 与批次之间的内存 + +LSTM 网络具有内存,能够记住长序列。 + +通常,在拟合模型时,在每次训练批次之后重置网络内的状态,以及每次调用 **model.predict()**或 **model.evaluate()**。 + +通过使 LSTM 层“有状态”,我们可以更好地控制何时在 Keras 中清除 LSTM 网络的内部状态。这意味着它可以在整个训练序列中构建状态,甚至在需要进行预测时保持该状态。 + +它要求在安装网络时不要改组训练数据。它还要求通过调用 **model.reset_states()**,在每次暴露于训练数据(时期)后明确重置网络状态。这意味着我们必须在每个时代调用 **model.fit()**和 **model.reset_states()**内创建我们自己的时代外环。例如: + +``` +for i in range(100): + model.fit(trainX, trainY, epochs=1, batch_size=batch_size, verbose=2, shuffle=False) + model.reset_states() +``` + +最后,当构造 LSTM 层时,**有状态**参数必须设置为 **True** 而不是指定输入维度,我们必须硬编码批次中的样本数量,通过设置 **batch_input_shape** 参数,样本中的时间步长和时间步长中的要素数量。例如: + +``` +model.add(LSTM(4, batch_input_shape=(batch_size, time_steps, features), stateful=True)) +``` + +然后,在评估模型和进行预测时,必须使用相同的批量大小。例如: + +``` +model.predict(trainX, batch_size=batch_size) +``` + +我们可以调整前一个时间步骤示例以使用有状态 LSTM。完整的代码清单如下。 + +``` +# LSTM for international airline passengers problem with memory +import numpy +import matplotlib.pyplot as plt +from pandas import read_csv +import math +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import LSTM +from sklearn.preprocessing import MinMaxScaler +from sklearn.metrics import mean_squared_error +# convert an array of values into a dataset matrix +def create_dataset(dataset, look_back=1): + dataX, dataY = [], [] + for i in range(len(dataset)-look_back-1): + a = dataset[i:(i+look_back), 0] + dataX.append(a) + dataY.append(dataset[i + look_back, 0]) + return numpy.array(dataX), numpy.array(dataY) +# fix random seed for reproducibility +numpy.random.seed(7) +# load the dataset +dataframe = read_csv('international-airline-passengers.csv', usecols=[1], engine='python', skipfooter=3) +dataset = dataframe.values +dataset = dataset.astype('float32') +# normalize the dataset +scaler = MinMaxScaler(feature_range=(0, 1)) +dataset = scaler.fit_transform(dataset) +# split into train and test sets +train_size = int(len(dataset) * 0.67) +test_size = len(dataset) - train_size +train, test = dataset[0:train_size,:], dataset[train_size:len(dataset),:] +# reshape into X=t and Y=t+1 +look_back = 3 +trainX, trainY = create_dataset(train, look_back) +testX, testY = create_dataset(test, look_back) +# reshape input to be [samples, time steps, features] +trainX = numpy.reshape(trainX, (trainX.shape[0], trainX.shape[1], 1)) +testX = numpy.reshape(testX, (testX.shape[0], testX.shape[1], 1)) +# create and fit the LSTM network +batch_size = 1 +model = Sequential() +model.add(LSTM(4, batch_input_shape=(batch_size, look_back, 1), stateful=True)) +model.add(Dense(1)) +model.compile(loss='mean_squared_error', optimizer='adam') +for i in range(100): + model.fit(trainX, trainY, epochs=1, batch_size=batch_size, verbose=2, shuffle=False) + model.reset_states() +# make predictions +trainPredict = model.predict(trainX, batch_size=batch_size) +model.reset_states() +testPredict = model.predict(testX, batch_size=batch_size) +# invert predictions +trainPredict = scaler.inverse_transform(trainPredict) +trainY = scaler.inverse_transform([trainY]) +testPredict = scaler.inverse_transform(testPredict) +testY = scaler.inverse_transform([testY]) +# calculate root mean squared error +trainScore = math.sqrt(mean_squared_error(trainY[0], trainPredict[:,0])) +print('Train Score: %.2f RMSE' % (trainScore)) +testScore = math.sqrt(mean_squared_error(testY[0], testPredict[:,0])) +print('Test Score: %.2f RMSE' % (testScore)) +# shift train predictions for plotting +trainPredictPlot = numpy.empty_like(dataset) +trainPredictPlot[:, :] = numpy.nan +trainPredictPlot[look_back:len(trainPredict)+look_back, :] = trainPredict +# shift test predictions for plotting +testPredictPlot = numpy.empty_like(dataset) +testPredictPlot[:, :] = numpy.nan +testPredictPlot[len(trainPredict)+(look_back*2)+1:len(dataset)-1, :] = testPredict +# plot baseline and predictions +plt.plot(scaler.inverse_transform(dataset)) +plt.plot(trainPredictPlot) +plt.plot(testPredictPlot) +plt.show() +``` + +运行该示例提供以下输出: + +``` +... +Epoch 1/1 +1s - loss: 0.0017 +Epoch 1/1 +1s - loss: 0.0017 +Epoch 1/1 +1s - loss: 0.0017 +Epoch 1/1 +1s - loss: 0.0017 +Epoch 1/1 +1s - loss: 0.0017 +Epoch 1/1 +1s - loss: 0.0016 +Train Score: 20.74 RMSE +Test Score: 52.23 RMSE +``` + +我们确实看到结果更糟。该模型可能需要更多模块,并且可能需要针对更多时期进行训练以内化问题的结构。 + +![Stateful LSTM Trained on Regression Formulation of Passenger Prediction Problem](img/02bea64de799681634c6e64c0503d789.jpg) + +有状态 LSTM 训练乘客预测问题的回归形式 + +## 堆叠 LSTM 与批次之间的内存 + +最后,我们将了解 LSTM 的一大优势:它们可以在堆叠到深层网络架构中时成功进行训练。 + +LSTM 网络可以像其他层类型堆叠一样堆叠在 Keras 中。对配置的一个补充是每个后续 LSTM 层之前的 LSTM 层必须返回序列。这可以通过将层上的 **return_sequences** 参数设置为 **True** 来完成。 + +我们可以在上一节中扩展有状态 LSTM,使其具有两层,如下所示: + +``` +model.add(LSTM(4, batch_input_shape=(batch_size, look_back, 1), stateful=True, return_sequences=True)) +model.add(LSTM(4, batch_input_shape=(batch_size, look_back, 1), stateful=True)) +``` + +下面提供了整个代码清单,以确保完整性。 + +``` +# Stacked LSTM for international airline passengers problem with memory +import numpy +import matplotlib.pyplot as plt +from pandas import read_csv +import math +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import LSTM +from sklearn.preprocessing import MinMaxScaler +from sklearn.metrics import mean_squared_error +# convert an array of values into a dataset matrix +def create_dataset(dataset, look_back=1): + dataX, dataY = [], [] + for i in range(len(dataset)-look_back-1): + a = dataset[i:(i+look_back), 0] + dataX.append(a) + dataY.append(dataset[i + look_back, 0]) + return numpy.array(dataX), numpy.array(dataY) +# fix random seed for reproducibility +numpy.random.seed(7) +# load the dataset +dataframe = read_csv('international-airline-passengers.csv', usecols=[1], engine='python', skipfooter=3) +dataset = dataframe.values +dataset = dataset.astype('float32') +# normalize the dataset +scaler = MinMaxScaler(feature_range=(0, 1)) +dataset = scaler.fit_transform(dataset) +# split into train and test sets +train_size = int(len(dataset) * 0.67) +test_size = len(dataset) - train_size +train, test = dataset[0:train_size,:], dataset[train_size:len(dataset),:] +# reshape into X=t and Y=t+1 +look_back = 3 +trainX, trainY = create_dataset(train, look_back) +testX, testY = create_dataset(test, look_back) +# reshape input to be [samples, time steps, features] +trainX = numpy.reshape(trainX, (trainX.shape[0], trainX.shape[1], 1)) +testX = numpy.reshape(testX, (testX.shape[0], testX.shape[1], 1)) +# create and fit the LSTM network +batch_size = 1 +model = Sequential() +model.add(LSTM(4, batch_input_shape=(batch_size, look_back, 1), stateful=True, return_sequences=True)) +model.add(LSTM(4, batch_input_shape=(batch_size, look_back, 1), stateful=True)) +model.add(Dense(1)) +model.compile(loss='mean_squared_error', optimizer='adam') +for i in range(100): + model.fit(trainX, trainY, epochs=1, batch_size=batch_size, verbose=2, shuffle=False) + model.reset_states() +# make predictions +trainPredict = model.predict(trainX, batch_size=batch_size) +model.reset_states() +testPredict = model.predict(testX, batch_size=batch_size) +# invert predictions +trainPredict = scaler.inverse_transform(trainPredict) +trainY = scaler.inverse_transform([trainY]) +testPredict = scaler.inverse_transform(testPredict) +testY = scaler.inverse_transform([testY]) +# calculate root mean squared error +trainScore = math.sqrt(mean_squared_error(trainY[0], trainPredict[:,0])) +print('Train Score: %.2f RMSE' % (trainScore)) +testScore = math.sqrt(mean_squared_error(testY[0], testPredict[:,0])) +print('Test Score: %.2f RMSE' % (testScore)) +# shift train predictions for plotting +trainPredictPlot = numpy.empty_like(dataset) +trainPredictPlot[:, :] = numpy.nan +trainPredictPlot[look_back:len(trainPredict)+look_back, :] = trainPredict +# shift test predictions for plotting +testPredictPlot = numpy.empty_like(dataset) +testPredictPlot[:, :] = numpy.nan +testPredictPlot[len(trainPredict)+(look_back*2)+1:len(dataset)-1, :] = testPredict +# plot baseline and predictions +plt.plot(scaler.inverse_transform(dataset)) +plt.plot(trainPredictPlot) +plt.plot(testPredictPlot) +plt.show() +``` + +运行该示例将生成以下输出。 + +``` +... +Epoch 1/1 +1s - loss: 0.0017 +Epoch 1/1 +1s - loss: 0.0017 +Epoch 1/1 +1s - loss: 0.0017 +Epoch 1/1 +1s - loss: 0.0017 +Epoch 1/1 +1s - loss: 0.0016 +Train Score: 20.49 RMSE +Test Score: 56.35 RMSE +``` + +对测试数据集的预测再次恶化。这是更多证据表明需要额外的训练时期。 + +![Stacked Stateful LSTMs Trained on Regression Formulation of Passenger Prediction Problem](img/ce64d04b088a9f83e93898d1c644c3e2.jpg) + +堆积状态 LSTM 训练对乘客预测问题的回归表达式 + +## 摘要 + +在这篇文章中,您了解了如何使用 Keras 深度学习网络在 Python 中开发用于时间序列预测的 LSTM 循环神经网络。 + +具体来说,你学到了: + +* 关于国际航空公司客运时间序列预测问题。 +* 如何为回归创建 LSTM 以及时间序列问题的窗口公式。 +* 如何使用时间序列问题的时间步长公式创建 LSTM。 +* 如何使用状态和堆叠的 LSTM 创建具有状态的 LSTM 以学习长序列。 + +您对 LSTMs 有关时间序列预测或关于这篇文章的任何疑问吗? +在下面的评论中提出您的问题,我会尽力回答。 + +### 更新了 LSTM 时间序列预测帖子: + +这篇文章中的例子非常陈旧,我有更好的例子可用于在时间序列中使用 LSTM,请参阅: + +1. [用于单变量时间序列预测的 LSTM](https://machinelearningmastery.com/time-series-forecasting-long-short-term-memory-network-python/) +2. [多变量时间序列预测的 LSTM](https://machinelearningmastery.com/multivariate-time-series-forecasting-lstms-keras/) +3. [用于多步时间序列预测的 LSTM](https://machinelearningmastery.com/multi-step-time-series-forecasting-long-short-term-memory-networks-python/) \ No newline at end of file diff --git a/docs/dl-ts/time-series-prediction-with-deep-learning-in-python-with-keras.md b/docs/dl-ts/time-series-prediction-with-deep-learning-in-python-with-keras.md new file mode 100644 index 0000000000000000000000000000000000000000..2c3ff2a51a623f5215019655fa9f7ba0589d0571 --- /dev/null +++ b/docs/dl-ts/time-series-prediction-with-deep-learning-in-python-with-keras.md @@ -0,0 +1,421 @@ +# Keras 中深度学习的时间序列预测 + +> 原文: [https://machinelearningmastery.com/time-series-prediction-with-deep-learning-in-python-with-keras/](https://machinelearningmastery.com/time-series-prediction-with-deep-learning-in-python-with-keras/) + +时间序列预测是框架和机器学习解决的难题。 + +在这篇文章中,您将了解如何使用 Keras 深度学习库在 Python 中开发用于时间序列预测的神经网络模型。 + +阅读这篇文章后你会知道: + +* 关于航空公司乘客单变量时间序列预测问题。 +* 如何将时间序列预测作为回归问题,并为其开发神经网络模型。 +* 如何构建具有时滞的时间序列预测,并为其开发神经网络模型。 + +让我们开始吧。 + +* **2016 年 10 月更新**:用更准确的版本替换图表,评论了第一种方法的有限表现。 +* **2017 年 3 月更新**:更新了 Keras 2.0.2,TensorFlow 1.0.1 和 Theano 0.9.0 的示例。 + +## 问题描述 + +我们将在这篇文章中看到的问题是国际航空公司乘客预测问题。 + +这是一个问题,给定一年零一个月,任务是以 1,000 为单位预测国际航空公司乘客的数量。数据范围从 1949 年 1 月到 1960 年 12 月或 12 年,共有 144 个观测值。 + +该数据集可从 [DataMarket 网页免费下载,作为 CSV 下载](https://datamarket.com/data/set/22u3/international-airline-passengers-monthly-totals-in-thousands-jan-49-dec-60#!ds=22u3&display=line),文件名为“ _international-airline-passengers.csv_ ”。 + +下面是该文件前几行的示例。 + +``` +"Month","International airline passengers: monthly totals in thousands. Jan 49 ? Dec 60" +"1949-01",112 +"1949-02",118 +"1949-03",132 +"1949-04",129 +"1949-05",121 +``` + +我们可以使用 Pandas 库轻松加载此数据集。鉴于每个观察以一个月的相同间隔分开,我们对日期不感兴趣。因此,当我们加载数据集时,我们可以排除第一列。 + +下载的数据集还有页脚信息,我们可以将 **skipfooter** 参数排除到 **pandas.read_csv()**为 3 页脚行设置为 3。加载后,我们可以轻松绘制整个数据集。下面列出了加载和绘制数据集的代码。 + +``` +import pandas +import matplotlib.pyplot as plt +dataset = pandas.read_csv('international-airline-passengers.csv', usecols=[1], engine='python', skipfooter=3) +plt.plot(dataset) +plt.show() +``` + +您可以在情节中看到上升趋势。 + +您还可以看到数据集的某些周期性,这可能与北半球夏季假期相对应。 + +![Plot of the Airline Passengers Dataset](img/9e5711f15b64b80d86fc7aefd07d5322.jpg) + +航空公司乘客数据集的情节 + +我们将保持简单并按原样处理数据。 + +通常,研究各种数据准备技术以重新调整数据并使其静止是一个好主意。 + +## 多层感知器回归 + +我们想将时间序列预测问题表述为回归问题。 + +也就是说,考虑到本月的乘客人数(以千人为单位),下个月的乘客数量是多少。 + +我们可以编写一个简单的函数将我们的单列数据转换为两列数据集。第一列包含本月的(t)乘客数,第二列包含下个月的(t + 1)乘客数,需要预测。 + +在我们开始之前,让我们首先导入我们打算使用的所有函数和类。这假设一个工作的 SciPy 环境安装了 Keras 深度学习库。 + +``` +import numpy +import matplotlib.pyplot as plt +import pandas +from keras.models import Sequential +from keras.layers import Dense +``` + +在我们做任何事情之前,最好修复随机数种子以确保我们的结果可重复。 + +``` +# fix random seed for reproducibility +numpy.random.seed(7) +``` + +我们还可以使用上一节中的代码将数据集作为 Pandas 数据帧加载。然后,我们可以从数据帧中提取 NumPy 数组,并将整数值转换为更适合使用神经网络建模的浮点值。 + +``` +# load the dataset +dataframe = pandas.read_csv('international-airline-passengers.csv', usecols=[1], engine='python', skipfooter=3) +dataset = dataframe.values +dataset = dataset.astype('float32') +``` + +在我们对数据建模并估计模型在训练数据集上的技能之后,我们需要了解模型在新的看不见的数据上的技能。对于正常的分类或回归问题,我们将使用交叉验证来完成此操作。 + +对于时间序列数据,值的序列很重要。我们可以使用的一种简单方法是将有序数据集拆分为训练和测试数据集。下面的代码计算分裂点的索引,并将数据分成训练数据集,其中 67%的观测值可用于训练我们的模型,剩余的 33%用于测试模型。 + +``` +# split into train and test sets +train_size = int(len(dataset) * 0.67) +test_size = len(dataset) - train_size +train, test = dataset[0:train_size,:], dataset[train_size:len(dataset),:] +print(len(train), len(test)) +``` + +现在我们可以定义一个函数来创建一个新的数据集,如上所述。该函数有两个参数,数据集是我们想要转换成数据集的 NumPy 数组,而 **look_back** 是用作预测下一个时间段的输入变量的先前时间步数,在这种情况下,默认为 1。 + +此默认值将创建一个数据集,其中 X 是给定时间(t)的乘客数量,Y 是下次乘客的数量(t + 1)。 + +它可以配置,我们将在下一节中介绍构建不同形状的数据集。 + +``` +# convert an array of values into a dataset matrix +def create_dataset(dataset, look_back=1): + dataX, dataY = [], [] + for i in range(len(dataset)-look_back-1): + a = dataset[i:(i+look_back), 0] + dataX.append(a) + dataY.append(dataset[i + look_back, 0]) + return numpy.array(dataX), numpy.array(dataY) +``` + +让我们看看这个函数对数据集的前几行的影响。 + +``` +X Y +112 118 +118 132 +132 129 +129 121 +121 135 +``` + +如果将前 5 行与上一节中列出的原始数据集样本进行比较,则可以在数字中看到 X = t 和 Y = t + 1 模式。 + +让我们使用这个函数来准备准备建模的训练和测试数据集。 + +``` +# reshape into X=t and Y=t+1 +look_back = 1 +trainX, trainY = create_dataset(train, look_back) +testX, testY = create_dataset(test, look_back) +``` + +我们现在可以在训练数据中使用多层感知器模型。 + +我们使用一个简单的网络,1 个输入,1 个隐藏层,8 个神经元和一个输出层。使用均方误差拟合模型,如果我们取平方根,则以数据集为单位给出误差分数。 + +我尝试了一些粗略的参数并确定了下面的配置,但绝不是网络列出了优化。 + +``` +# create and fit Multilayer Perceptron model +model = Sequential() +model.add(Dense(8, input_dim=look_back, activation='relu')) +model.add(Dense(1)) +model.compile(loss='mean_squared_error', optimizer='adam') +model.fit(trainX, trainY, epochs=200, batch_size=2, verbose=2) +``` + +一旦模型拟合,我们就可以估计模型在列车和测试数据集上的表现。这将为我们提供新模型的比较点。 + +``` +# Estimate model performance +trainScore = model.evaluate(trainX, trainY, verbose=0) +print('Train Score: %.2f MSE (%.2f RMSE)' % (trainScore, math.sqrt(trainScore))) +testScore = model.evaluate(testX, testY, verbose=0) +print('Test Score: %.2f MSE (%.2f RMSE)' % (testScore, math.sqrt(testScore))) +``` + +最后,我们可以使用模型为列车和测试数据集生成预测,以获得模型技能的直观指示。 + +由于数据集是如何准备的,我们必须改变预测,以便它们在 x 轴上与原始数据集一致。准备好后,绘制数据,以蓝色显示原始数据集,以绿色显示火车数据集的预测,以红色显示未见测试数据集的预测。 + +``` +# generate predictions for training +trainPredict = model.predict(trainX) +testPredict = model.predict(testX) + +# shift train predictions for plotting +trainPredictPlot = numpy.empty_like(dataset) +trainPredictPlot[:, :] = numpy.nan +trainPredictPlot[look_back:len(trainPredict)+look_back, :] = trainPredict + +# shift test predictions for plotting +testPredictPlot = numpy.empty_like(dataset) +testPredictPlot[:, :] = numpy.nan +testPredictPlot[len(trainPredict)+(look_back*2)+1:len(dataset)-1, :] = testPredict + +# plot baseline and predictions +plt.plot(dataset) +plt.plot(trainPredictPlot) +plt.plot(testPredictPlot) +plt.show() +``` + +我们可以看到该模型在拟合训练和测试数据集方面做得很差。它基本上预测了与输出相同的输入值。 + +![Naive Time Series Predictions With Neural Network](img/668e431bcc6a520acc7e016533fe1276.jpg) + +神经网络的原始时间序列预测 +蓝色=整个数据集,绿色=训练,红色=预测 + +为完整起见,以下是整个代码清单。 + +``` +# Multilayer Perceptron to Predict International Airline Passengers (t+1, given t) +import numpy +import matplotlib.pyplot as plt +import pandas +import math +from keras.models import Sequential +from keras.layers import Dense +# fix random seed for reproducibility +numpy.random.seed(7) +# load the dataset +dataframe = pandas.read_csv('international-airline-passengers.csv', usecols=[1], engine='python', skipfooter=3) +dataset = dataframe.values +dataset = dataset.astype('float32') +# split into train and test sets +train_size = int(len(dataset) * 0.67) +test_size = len(dataset) - train_size +train, test = dataset[0:train_size,:], dataset[train_size:len(dataset),:] +print(len(train), len(test)) +# convert an array of values into a dataset matrix +def create_dataset(dataset, look_back=1): + dataX, dataY = [], [] + for i in range(len(dataset)-look_back-1): + a = dataset[i:(i+look_back), 0] + dataX.append(a) + dataY.append(dataset[i + look_back, 0]) + return numpy.array(dataX), numpy.array(dataY) +# reshape into X=t and Y=t+1 +look_back = 1 +trainX, trainY = create_dataset(train, look_back) +testX, testY = create_dataset(test, look_back) +# create and fit Multilayer Perceptron model +model = Sequential() +model.add(Dense(8, input_dim=look_back, activation='relu')) +model.add(Dense(1)) +model.compile(loss='mean_squared_error', optimizer='adam') +model.fit(trainX, trainY, epochs=200, batch_size=2, verbose=2) +# Estimate model performance +trainScore = model.evaluate(trainX, trainY, verbose=0) +print('Train Score: %.2f MSE (%.2f RMSE)' % (trainScore, math.sqrt(trainScore))) +testScore = model.evaluate(testX, testY, verbose=0) +print('Test Score: %.2f MSE (%.2f RMSE)' % (testScore, math.sqrt(testScore))) +# generate predictions for training +trainPredict = model.predict(trainX) +testPredict = model.predict(testX) +# shift train predictions for plotting +trainPredictPlot = numpy.empty_like(dataset) +trainPredictPlot[:, :] = numpy.nan +trainPredictPlot[look_back:len(trainPredict)+look_back, :] = trainPredict +# shift test predictions for plotting +testPredictPlot = numpy.empty_like(dataset) +testPredictPlot[:, :] = numpy.nan +testPredictPlot[len(trainPredict)+(look_back*2)+1:len(dataset)-1, :] = testPredict +# plot baseline and predictions +plt.plot(dataset) +plt.plot(trainPredictPlot) +plt.plot(testPredictPlot) +plt.show() +``` + +运行模型会生成以下输出。 + +``` +Epoch 195/200 +0s - loss: 536.7014 +Epoch 196/200 +0s - loss: 555.4216 +Epoch 197/200 +0s - loss: 552.2841 +Epoch 198/200 +0s - loss: 541.2220 +Epoch 199/200 +0s - loss: 542.3288 +Epoch 200/200 +0s - loss: 534.2096 +Train Score: 532.59 MSE (23.08 RMSE) +Test Score: 2358.07 MSE (48.56 RMSE) +``` + +根据表现估计的平方根,我们可以看到该模型在训练数据集上有 23 名乘客(以千计)的平均误差,在测试数据集上有 48 名乘客(以千计)。 + +## 多层感知器使用窗口方法 + +我们还可以对问题进行短语,以便可以使用多个最近的时间步骤来对下一个时间步进行预测。 + +这称为窗口方法,窗口的大小是可以针对每个问题调整的参数。 + +例如,给定当前时间(t)我们想要预测序列中下一次的值(t + 1),我们可以使用当前时间(t)以及前两次(t-1 和 T-2)。 + +当表达为回归问题时,输入变量为 t-2,t-1,t,输出变量为 t + 1。 + +我们在上一节中编写的 **create_dataset()**函数允许我们通过将 **look_back** 参数从 1 增加到 3 来创建时间序列问题的这个公式。 + +具有此秘籍的数据集样本如下所示: + +``` +X1 X2 X3 Y +112 118 132 129 +118 132 129 121 +132 129 121 135 +129 121 135 148 +121 135 148 148 +``` + +我们可以使用更大的窗口大小重新运行上一节中的示例。我们将增加网络容量来处理附加信息。第一个隐藏层增加到 14 个神经元,第二个隐藏层增加了 8 个神经元。时代数也增加到 400。 + +为了完整性,下面列出了仅包含窗口大小更改的整个代码清单。 + +``` +# Multilayer Perceptron to Predict International Airline Passengers (t+1, given t, t-1, t-2) +import numpy +import matplotlib.pyplot as plt +from pandas import read_csv +import math +from keras.models import Sequential +from keras.layers import Dense + +# convert an array of values into a dataset matrix +def create_dataset(dataset, look_back=1): + dataX, dataY = [], [] + for i in range(len(dataset)-look_back-1): + a = dataset[i:(i+look_back), 0] + dataX.append(a) + dataY.append(dataset[i + look_back, 0]) + return numpy.array(dataX), numpy.array(dataY) + +# fix random seed for reproducibility +numpy.random.seed(7) +# load the dataset +dataframe = read_csv('international-airline-passengers.csv', usecols=[1], engine='python', skipfooter=3) +dataset = dataframe.values +dataset = dataset.astype('float32') +# split into train and test sets +train_size = int(len(dataset) * 0.67) +test_size = len(dataset) - train_size +train, test = dataset[0:train_size,:], dataset[train_size:len(dataset),:] +# reshape dataset +look_back = 3 +trainX, trainY = create_dataset(train, look_back) +testX, testY = create_dataset(test, look_back) +# create and fit Multilayer Perceptron model +model = Sequential() +model.add(Dense(12, input_dim=look_back, activation='relu')) +model.add(Dense(8, activation='relu')) +model.add(Dense(1)) +model.compile(loss='mean_squared_error', optimizer='adam') +model.fit(trainX, trainY, epochs=400, batch_size=2, verbose=2) +# Estimate model performance +trainScore = model.evaluate(trainX, trainY, verbose=0) +print('Train Score: %.2f MSE (%.2f RMSE)' % (trainScore, math.sqrt(trainScore))) +testScore = model.evaluate(testX, testY, verbose=0) +print('Test Score: %.2f MSE (%.2f RMSE)' % (testScore, math.sqrt(testScore))) +# generate predictions for training +trainPredict = model.predict(trainX) +testPredict = model.predict(testX) +# shift train predictions for plotting +trainPredictPlot = numpy.empty_like(dataset) +trainPredictPlot[:, :] = numpy.nan +trainPredictPlot[look_back:len(trainPredict)+look_back, :] = trainPredict +# shift test predictions for plotting +testPredictPlot = numpy.empty_like(dataset) +testPredictPlot[:, :] = numpy.nan +testPredictPlot[len(trainPredict)+(look_back*2)+1:len(dataset)-1, :] = testPredict +# plot baseline and predictions +plt.plot(dataset) +plt.plot(trainPredictPlot) +plt.plot(testPredictPlot) +plt.show() +``` + +运行该示例提供以下输出。 + +``` +Epoch 395/400 +0s - loss: 485.3482 +Epoch 396/400 +0s - loss: 479.9485 +Epoch 397/400 +0s - loss: 497.2707 +Epoch 398/400 +0s - loss: 489.5670 +Epoch 399/400 +0s - loss: 490.8099 +Epoch 400/400 +0s - loss: 493.6544 +Train Score: 564.03 MSE (23.75 RMSE) +Test Score: 2244.82 MSE (47.38 RMSE) +``` + +我们可以看到,与上一节相比,误差没有明显减少。 + +查看图表,我们可以在预测中看到更多结构。 + +同样,窗口大小和网络架构没有调整,这只是如何构建预测问题的演示。 + +根据绩效分数的平方根,我们可以看到训练数据集的平均误差为 23 名乘客(每月数千人),未见测试集的平均误差为 47 名乘客(每月数千人)。 + +![Window Method For Time Series Predictions With Neural Networks](img/a591c6a2170e5c1473f37cdda1016d57.jpg) + +使用神经网络的时间序列预测的窗口方法 +蓝色=整个数据集,绿色=训练,红色=预测 + +## 摘要 + +在这篇文章中,您了解了如何使用 Keras 深度学习库为时间序列预测问题开发神经网络模型。 + +完成本教程后,您现在知道: + +* 关于国际航空公司旅客预测时间序列数据集。 +* 如何将时间序列预测问题构建为回归问题并开发神经网络模型。 +* 如何使用窗口方法构建时间序列预测问题并开发神经网络模型。 + +您对神经网络或此帖的时间序列预测有任何疑问吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/tune-lstm-hyperparameters-keras-time-series-forecasting.md b/docs/dl-ts/tune-lstm-hyperparameters-keras-time-series-forecasting.md new file mode 100644 index 0000000000000000000000000000000000000000..0df3a688f53cf3b0e6943a840d61ce5e99334fdb --- /dev/null +++ b/docs/dl-ts/tune-lstm-hyperparameters-keras-time-series-forecasting.md @@ -0,0 +1,1023 @@ +# 如何用 Keras 调整 LSTM 超参数进行时间序列预测 + +> 原文: [https://machinelearningmastery.com/tune-lstm-hyperparameters-keras-time-series-forecasting/](https://machinelearningmastery.com/tune-lstm-hyperparameters-keras-time-series-forecasting/) + +配置神经网络很困难,因为没有关于如何做到这一点的好理论。 + +您必须系统地从探索的动态和客观结果点探索不同的配置,以尝试理解给定预测建模问题的发生情况。 + +在本教程中,您将了解如何在时间序列预测问题上探索如何配置 LSTM 网络。 + +完成本教程后,您将了解: + +* 如何调整和解释训练时期数的结果。 +* 如何调整和解释训练批次大小的结果。 +* 如何调整和解释神经元数量的结果。 + +让我们开始吧。 + +![How to Tune LSTM Hyperparameters with Keras for Time Series Forecasting](img/5f0401b725586b6d9a4ef5a56653a653.jpg) + +如何用 Keras 调整 LSTM 超参数用于时间序列预测 +照片由 [David Saddler](https://www.flickr.com/photos/80502454@N00/6585205675/) 保留,保留一些权利。 + +## 教程概述 + +本教程分为 6 个部分;他们是: + +1. 洗发水销售数据集 +2. 实验测试线束 +3. 调整时代数量 +4. 调整批量大小 +5. 调整神经元数量 +6. 结果摘要 + +### 环境 + +本教程假定您已安装 Python SciPy 环境。您可以在此示例中使用 Python 2 或 3。 + +本教程假设您安装了 TensorFlow 或 Theano 后端的 Keras v2.0 或更高版本。 + +本教程还假设您安装了 scikit-learn,Pandas,NumPy 和 Matplotlib。 + +如果您在设置 Python 环境时需要帮助,请参阅以下帖子: + +* [如何使用 Anaconda 设置用于机器学习和深度学习的 Python 环境](http://machinelearningmastery.com/setup-python-environment-machine-learning-deep-learning-anaconda/) + +## 洗发水销售数据集 + +该数据集描述了 3 年期间每月洗发水的销售数量。 + +单位是销售计数,有 36 个观察。原始数据集归功于 Makridakis,Wheelwright 和 Hyndman(1998)。 + +[您可以在此处下载并了解有关数据集的更多信息](https://datamarket.com/data/set/22r0/sales-of-shampoo-over-a-three-year-period)。 + +下面的示例加载并创建已加载数据集的图。 + +``` +# 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](img/646e3de8684355414799cd9964ad1d4f.jpg) + +洗发水销售数据集的线图 + +接下来,我们将了解实验中使用的 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](img/d31b96331b0a1d029333ff3432b836b1.jpg) + +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](img/33b4663925b0226858ab248f10d5086d.jpg) + +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](img/8f800f11e45ec50a340d5bf542f4264b.jpg) + +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](img/f5cb6c201d420ad25daa99d0a2c5478d.jpg) + +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](img/e51221ef089e7cfadbc55a61bbc159f0.jpg) + +框和晶须图总结时代结果 + +接下来,我们将看看批量大小的影响。 + +## 调整批量大小 + +批量大小控制更新网络权重的频率。 + +重要的是在 Keras 中,批量大小必须是测试大小和训练数据集的一个因子。 + +在上一节探索训练时期的数量时,批量大小固定为 4,它完全分为测试数据集(大小为 12)和测试数据集的截断版本(大小为 20)。 + +在本节中,我们将探讨改变批量大小的效果。我们将训练时期的数量保持在 1000。 + +### 1000 个时期的诊断和 4 的批量大小 + +作为提醒,上一节在第二个实验中评估了批量大小为 4,其中一些时期为 1000。 + +结果显示出错误的下降趋势,大多数运行一直持续到最后的训练时期。 + +![Diagnostic Results with 1000 Epochs](img/33b4663925b0226858ab248f10d5086d.jpg) + +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](img/f4b0a30a4bf815a7d503b08aa053de7a.jpg) + +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](img/efd88d73fb1f9ab51d889537849b1d52.jpg) + +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](img/d5ae9aad66d72aeda14a8d3568dc4943.jpg) + +框和晶须图总结批量大小结果 + +## 调整神经元数量 + +在本节中,我们将研究改变网络中神经元数量的影响。 + +神经元的数量会影响网络的学习能力。通常,更多的神经元能够以更长的训练时间为代价从问题中学习更多的结构。更多的学习能力也会产生可能过度拟合训练数据的问题。 + +我们将使用批量大小为 4 和 1000 的训练时期。 + +### 1000 个时期和 1 个神经元的诊断 + +我们将从 1 个神经元开始。 + +提醒一下,这是从时代实验中测试的第二个配置。 + +![Diagnostic Results with 1000 Epochs](img/33b4663925b0226858ab248f10d5086d.jpg) + +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](img/71ec3bdc817faaf2008676a96990d94e.jpg) + +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](img/a2ef63ebf932393f4617c27a6a0452ae.jpg) + +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](img/ac0abd4e84df89efc03932a25a23d630.jpg) + +框和晶须图总结神经元结果 + +## 所有结果摘要 + +我们在本教程的 Shampoo Sales 数据集上完成了很多 LSTM 实验。 + +通常,似乎配置有 1 个神经元,批量大小为 4 并且训练 1000 个迭代的有状态 LSTM 可能是一个很好的配置。 + +结果还表明,批量大小为 1 并且适合更多时期的这种配置可能值得进一步探索。 + +调整神经网络是一项困难的实证研究,LSTM 也不例外。 + +本教程展示了配置行为随时间推移的诊断研究的好处,以及测试 RMSE 的客观研究。 + +然而,总会有更多的研究可以进行。下一节列出了一些想法。 + +### 扩展 + +本节列出了本教程中对实验进行扩展的一些想法。 + +如果您探索其中任何一项,请在评论中报告您的结果;我很想看看你想出了什么。 + +* **dropout**。使用正则化方法减慢学习速度,例如在重复的 LSTM 连接上丢失。 +* **层**。通过在每层中添加更多层和不同数量的神经元来探索额外的分层学习能力。 +* **正规化**。探索权重正则化(如 L1 和 L2)如何用于减慢某些配置上网络的学习和过度拟合。 +* **优化算法**。探索[替代优化算法](https://keras.io/optimizers/)的使用,例如经典的梯度下降,以查看加速或减慢学习的特定配置是否可以带来好处。 +* **损失函数**。探索[替代损失函数](https://keras.io/objectives/)的使用,看看它们是否可用于提升表现。 +* **功能和时间步**。探索使用滞后观察作为输入特征和特征的输入时间步骤,以查看它们作为输入的存在是否可以改善模型的学习和/或预测能力。 +* **批量大**。探索大于 4 的批量大小,可能需要进一步操纵训练和测试数据集的大小。 + +## 摘要 + +在本教程中,您了解了如何系统地研究 LSTM 网络的配置以进行时间序列预测。 + +具体来说,你学到了: + +* 如何设计用于评估模型配置的系统测试工具。 +* 如何使用模型诊断随着时间的推移,以及客观预测误差来解释模型行为。 +* 如何探索和解释训练时期,批量大小和神经元数量的影响。 + +您对调整 LSTM 或本教程有任何疑问吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/update-lstm-networks-training-time-series-forecasting.md b/docs/dl-ts/update-lstm-networks-training-time-series-forecasting.md new file mode 100644 index 0000000000000000000000000000000000000000..43210e261a38d30db624899d2c0f52eb875f3576 --- /dev/null +++ b/docs/dl-ts/update-lstm-networks-training-time-series-forecasting.md @@ -0,0 +1,562 @@ +# 如何在时间序列预测训练期间更新 LSTM 网络 + +> 原文: [https://machinelearningmastery.com/update-lstm-networks-training-time-series-forecasting/](https://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](img/f48debb1f6b1b8f3f780f8dcb7ef230c.jpg) + +如何在时间序列预测训练期间更新 LSTM 网络 +照片由 [Esteban Alvarez](https://www.flickr.com/photos/alvaretz/8427810143/) ,保留一些权利。 + +## 教程概述 + +本教程分为 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 环境时需要帮助,请参阅以下帖子: + +* [如何使用 Anaconda 设置用于机器学习和深度学习的 Python 环境](http://machinelearningmastery.com/setup-python-environment-machine-learning-deep-learning-anaconda/) + +## 洗发水销售数据集 + +该数据集描述了 3 年期间每月洗发水的销售数量。 + +单位是销售计数,有 36 个观察。原始数据集归功于 Makridakis,Wheelwright 和 Hyndman(1998)。 + +[您可以在此处下载并了解有关数据集的更多信息](https://datamarket.com/data/set/22r0/sales-of-shampoo-over-a-three-year-period)。 + +下面的示例加载并创建已加载数据集的图。 + +``` +# 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](img/646e3de8684355414799cd9964ad1d4f.jpg) + +洗发水销售数据集的线图 + +接下来,我们将了解实验中使用的 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](img/a2c6d8510072e11227a234fa5344bab9.jpg) + +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 模型的更新可以产生更有效的预测模型,但是需要仔细校准预测问题。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/use-dropout-lstm-networks-time-series-forecasting.md b/docs/dl-ts/use-dropout-lstm-networks-time-series-forecasting.md new file mode 100644 index 0000000000000000000000000000000000000000..4d658679fdc96dec6a0a274f2ade1db2a8a804d7 --- /dev/null +++ b/docs/dl-ts/use-dropout-lstm-networks-time-series-forecasting.md @@ -0,0 +1,887 @@ +# 如何使用 LSTM 网络的 Dropout 进行时间序列预测 + +> 原文: [https://machinelearningmastery.com/use-dropout-lstm-networks-time-series-forecasting/](https://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](img/7d927ae1d72d249122952d2334a3a69a.jpg) + +如何使用 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 环境时需要帮助,请参阅以下帖子: + +* [如何使用 Anaconda 设置用于机器学习和深度学习的 Python 环境](http://machinelearningmastery.com/setup-python-environment-machine-learning-deep-learning-anaconda/) + +## 洗发水销售数据集 + +该数据集描述了 3 年期间每月洗发水的销售数量。 + +单位是销售计数,有 36 个观察。原始数据集归功于 Makridakis,Wheelwright 和 Hyndman(1998)。 + +[您可以在此处下载并了解有关数据集的更多信息](https://datamarket.com/data/set/22r0/sales-of-shampoo-over-a-three-year-period)。 + +下面的示例加载并创建已加载数据集的图。 + +``` +# 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](img/646e3de8684355414799cd9964ad1d4f.jpg) + +洗发水销售数据集的线图 + +接下来,我们将看一下实验中使用的模型配置和测试工具。 + +## 实验测试线束 + +本节介绍本教程中使用的测试工具。 + +### 数据拆分 + +我们将 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](img/a689c5d0eea9de43a80c00e2d219cb9d.jpg) + +洗发水销售数据集中基线表现的盒子和晶须图 + +网络配置需要考虑的另一个角度是模型适应时的行为方式。 + +我们可以在每个训练时期之后评估训练和测试数据集上的模型,以了解配置是否过度拟合或不适合问题。 + +我们将在每组实验的最佳结果上使用此诊断方法。将运行总共 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](img/71241dc27d1854fe4b9daedfd2d2436c.jpg) + +洗发水销售数据集基线模型的诊断线图 + +## 输入 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](img/1cc831625d4c1a7f1a90a8ecfcb13d10.jpg) + +洗发水销售数据集中输入 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](img/ce439e552781e6e49eb401c3d0d62529.jpg) + +洗发水销售数据集中输入 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](img/3267a4a29b449a33c12c590c04a714d0.jpg) + +洗发水销售数据集中反复 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](img/1a8b7a5deb488b41a712fd45f5790326.jpg) + +洗发水销售数据集中经常性 dropout 表现的诊断线图 + +## 扩展 + +本节列出了在完成本教程后您可能希望考虑进一步实验的一些想法。 + +* **输入层丢失**。可能值得探讨在输入层上使用压差以及它如何影响 LSTM 的表现和过度拟合。 +* **组合输入和循环**。可能值得探索输入和重复丢失的组合,以查看是否可以提供任何额外的好处。 +* **其他正则化方法**。使用 LSTM 网络探索其他正则化方法可能是值得的,例如各种输入,循环和偏置权重正则化函数。 + +## 进一步阅读 + +有关在 Keras 中使用 MLP 模型退出的更多信息,请参阅帖子: + +* [具有 Keras 的深度学习模型中的丢失正则化](http://machinelearningmastery.com/dropout-regularization-deep-learning-models-keras/) + +以下是一些关于 LSTM 网络 dropout 的论文,您可能会发现这些论文对于进一步阅读非常有用。 + +* [循环神经网络正则化](https://arxiv.org/abs/1409.2329) +* [dropout 在循环神经网络中的理论基础应用](https://arxiv.org/abs/1512.05287) +* [Dropout 改进了手写识别的循环神经网络](https://arxiv.org/abs/1312.4569) + +## 摘要 + +在本教程中,您了解了如何将 dropout 与 LSTM 一起用于时间序列预测。 + +具体来说,你学到了: + +* 如何设计一个强大的测试工具来评估 LSTM 网络的时间序列预测。 +* 如何在 LSTM 上配置输入权重丢失以进行时间序列预测。 +* 如何在 LSTM 上配置循环重量丢失以进行时间序列预测。 + +您对使用 LSTM 网络的丢失有任何疑问吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/use-features-lstm-networks-time-series-forecasting.md b/docs/dl-ts/use-features-lstm-networks-time-series-forecasting.md new file mode 100644 index 0000000000000000000000000000000000000000..713ed3db7d78cc44916f281fe45143b6bdb60155 --- /dev/null +++ b/docs/dl-ts/use-features-lstm-networks-time-series-forecasting.md @@ -0,0 +1,549 @@ +# 如何使用 LSTM 网络中的特征进行时间序列预测 + +> 原文: [https://machinelearningmastery.com/use-features-lstm-networks-time-series-forecasting/](https://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](img/2eba536cb4a7e99c8b1cf0575f8f29fd.jpg) + +如何使用 LSTM 网络中的功能进行时间​​序列预测 +[Tom Hodgkinson](https://www.flickr.com/photos/hodgers/117655250/in/photostream/) 的照片,保留一些权利。 + +## 教程概述 + +本教程分为 4 个部分。他们是: + +1. 洗发水销售数据集 +2. 实验测试线束 +3. 使用 Timesteps 的实验 +4. 时间步和神经元的实验 + +### 环境 + +本教程假定您已安装 Python SciPy 环境。您可以在此示例中使用 Python 2 或 3。 + +本教程假设您安装了 TensorFlow 或 Theano 后端的 Keras v2.0 或更高版本。 + +本教程还假设您安装了 scikit-learn,Pandas,NumPy 和 Matplotlib。 + +如果您在设置 Python 环境时需要帮助,请参阅以下帖子: + +* [如何使用 Anaconda 设置用于机器学习和深度学习的 Python 环境](http://machinelearningmastery.com/setup-python-environment-machine-learning-deep-learning-anaconda/) + +## 洗发水销售数据集 + +该数据集描述了 3 年期间每月洗发水的销售数量。 + +单位是销售计数,有 36 个观察。原始数据集归功于 Makridakis,Wheelwright 和 Hyndman(1998)。 + +[您可以在此处下载并了解有关数据集的更多信息](https://datamarket.com/data/set/22r0/sales-of-shampoo-over-a-three-year-period)。 + +下面的示例加载并创建已加载数据集的图。 + +``` +# 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](img/646e3de8684355414799cd9964ad1d4f.jpg) + +洗发水销售数据集的线图 + +接下来,我们将了解实验中使用的 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.csv_ , _experiment_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](img/e4749ea8e2d453eb45635bfb57f9622a.jpg) + +测试 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](img/4c75b72d676c9dfbd00be1e57b3f5354.jpg) + +测试 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](img/806ec13a0c47a1bd7a1b2dda7a7716de.jpg) + +测试 RMSE 的盒子和晶须图与神经元和输入特征的数量以及 1000 个时期 + +## 扩展 + +本节列出了您可能考虑探索的一些进一步调查的领域。 + +* **诊断运行图**。对于给定的实验,在多次运行的情况下查看列车和测试 RMSE 的图可能是有帮助的。这可能有助于梳理过度拟合或过度拟合是否正在发生,反过来又是解决它的方法。 +* **增加重复次数**。使用 10 次重复导致相对少量的测试 RMSE 结果。将重复增加至 30 或 100(或甚至更高)可能导致更稳定的结果。 + +你有没有探索过这些扩展? +在下面的评论中分享您的发现;我很想听听你发现了什么。 + +## 摘要 + +在本教程中,您了解了如何使用滞后观察作为 LSTM 网络中的输入要素进行调查。 + +具体来说,你学到了: + +* 如何开发一个强大的测试工具来尝试使用 LSTM 进行输入表示。 +* 如何使用滞后观测作为 LSTM 时间序列预测的输入特征。 +* 如何通过增加输入功能来增加网络的学习能力。 + +您发现“_ 使用滞后观察作为输入功能可以提高模型技能 _”并未降低所选问题和 LSTM 配置的测试 RMSE。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/use-timesteps-lstm-networks-time-series-forecasting.md b/docs/dl-ts/use-timesteps-lstm-networks-time-series-forecasting.md new file mode 100644 index 0000000000000000000000000000000000000000..9fdeb231a478eaead36281106862f9c13f9a590e --- /dev/null +++ b/docs/dl-ts/use-timesteps-lstm-networks-time-series-forecasting.md @@ -0,0 +1,474 @@ +# 如何在 LSTM 网络中使用时间序列进行时间序列预测 + +> 原文: [https://machinelearningmastery.com/use-timesteps-lstm-networks-time-series-forecasting/](https://machinelearningmastery.com/use-timesteps-lstm-networks-time-series-forecasting/) + +Keras 的长短期记忆(LSTM)网络支持时间步骤。 + +这提出了一个问题,即单变量时间序列的滞后观察是否可以用作 LSTM 的时间步长以及这是否会改善预测表现。 + +在本教程中,我们将研究使用滞后观察作为 Python 中 LSTMs 模型的时间步长。 + +完成本教程后,您将了解: + +* 如何开发测试工具以系统地评估 LSTM 时间序列的时间序列预测。 +* 使用不同数量的滞后观测值作为 LSTM 模型的输入时间步长的影响。 +* 对 LSTM 模型使用不同数量的滞后观察和匹配数量的神经元的影响。 + +让我们开始吧。 + +![How to Use Timesteps in LSTM Networks for Time Series Forecasting](img/18b0ff5882a72f460af802769b02dbd8.jpg) + +如何在 LSTM 网络中使用时间序列进行时间序列预测 +照片来自 [YoTuT](https://www.flickr.com/photos/yotut/326537449/) ,保留一些权利。 + +## 教程概述 + +本教程分为 4 个部分。他们是: + +1. 洗发水销售数据集 +2. 实验测试线束 +3. 时间步骤的实验 +4. 时间步和神经元的实验 + +### 环境 + +本教程假定您已安装 Python SciPy 环境。您可以在此示例中使用 Python 2 或 3。 + +本教程假设您安装了 TensorFlow 或 Theano 后端的 Keras v2.0 或更高版本。 + +本教程还假设您安装了 scikit-learn,Pandas,NumPy 和 Matplotlib。 + +如果您在设置 Python 环境时需要帮助,请参阅以下帖子: + +* [如何使用 Anaconda 设置用于机器学习和深度学习的 Python 环境](http://machinelearningmastery.com/setup-python-environment-machine-learning-deep-learning-anaconda/) + +## 洗发水销售数据集 + +该数据集描述了 3 年期间每月洗发水的销售数量。 + +单位是销售计数,有 36 个观察。原始数据集归功于 Makridakis,Wheelwright 和 Hyndman(1998)。 + +[您可以在此处下载并了解有关数据集的更多信息](https://datamarket.com/data/set/22r0/sales-of-shampoo-over-a-three-year-period)。 + +下面的示例加载并创建已加载数据集的图。 + +``` +# 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](img/646e3de8684355414799cd9964ad1d4f.jpg) + +洗发水销售数据集的线图 + +接下来,我们将了解实验中使用的 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 个步骤。希望是滞后观测的附加背景可以改善预测模型的表现。 + +在训练模型之前,单变量时间序列被转换为监督学习问题。指定的时间步数定义了用于预测下一个时间步长( _y_ )的输入变量( _X_ )的数量。因此,对于表示中使用的每个时间步,必须从数据集的开头删除许多行。这是因为没有先前的观察结果可用作数据集中第一个值的时间步长。 + +下面列出了测试 1 个时间步的完整代码清单。 + +对于 5 个实验中的每一个, _run()_ 函数中的时间步长参数从 1 到 5 变化。此外,结果将在实验结束时保存到文件中,并且还必须针对每个不同的实验运行更改此文件名;例如: _experiment_timesteps_1.csv_ , _experiment_timesteps_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, timesteps): + X, y = train[:, 0:-1], train[:, -1] + X = X.reshape(X.shape[0], timesteps, 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, len(X), 1) + yhat = model.predict(X, batch_size=batch_size) + return yhat[0,0] + +# run a repeated experiment +def experiment(repeats, series, timesteps): + # 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, timesteps) + supervised_values = supervised.values[timesteps:,:] + # 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, timesteps) + # 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 + timesteps = 1 + results['results'] = experiment(repeats, series, timesteps) + # summarize results + print(results.describe()) + # save results + results.to_csv('experiment_timesteps_1.csv', index=False) + + # entry point +run() +``` + +针对 5 个不同数量的时间步长运行 5 个不同的实验。 + +如果有足够的内存和 CPU 资源,可以并行运行它们。这些实验不需要 GPU 资源,实验应该在几分钟到几十分钟内完成。 + +运行实验后,您应该有 5 个包含结果的文件,如下所示: + +``` +experiment_timesteps_1.csv +experiment_timesteps_2.csv +experiment_timesteps_3.csv +experiment_timesteps_4.csv +experiment_timesteps_5.csv +``` + +我们可以编写一些代码来加载和汇总这些结果。 + +具体而言,查看每次运行的描述性统计数据并使用方框和胡须图比较每次运行的结果非常有用。 + +下面列出了总结结果的代码。 + +``` +from pandas import DataFrame +from pandas import read_csv +from matplotlib import pyplot +# load results into a dataframe +filenames = ['experiment_timesteps_1.csv', 'experiment_timesteps_2.csv', + 'experiment_timesteps_3.csv','experiment_timesteps_4.csv','experiment_timesteps_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 百分位数)时也会显示这一点。 + +``` + timesteps_1 timesteps_2 timesteps_3 timesteps_4 timesteps_5 +count 10.000000 10.000000 10.000000 10.000000 10.000000 +mean 102.785197 127.308725 136.182907 146.277122 142.631684 +std 6.299329 22.171668 7.760161 5.609412 6.611638 +min 92.603903 106.124901 124.724903 138.845314 137.359503 +25% 98.979692 114.100891 130.719154 141.906083 138.354265 +50% 103.904185 114.519986 137.055840 145.865171 141.409855 +75% 108.434727 144.328534 139.615541 150.729938 143.604275 +max 110.270559 164.880226 150.497130 155.603461 159.948033 +``` + +还创建了比较结果分布的盒子和胡须图。 + +该情节与描述性统计数据相同。随着时间步数的增加,存在增加测试 RMSE 的一般趋势。 + +![Box and Whisker Plot of Timesteps vs RMSE](img/7c3b0e642432ad9d24bbbfb806aecad3.jpg) + +时间步长与 RMSE 的盒子和晶须图 + +至少在使用数据集和 LSTM 配置的情况下,没有观察到随着时间步长的增加而提高表现的期望。 + +这就提出了一个问题,即网络的容量是否是一个限制因素。我们将在下一节中看到这一点。 + +## 时间步和神经元的实验 + +LSTM 网络中的神经元(也称为块)的数量定义了其学习能力。 + +在先前的实验中,可能使用一个神经元限制了网络的学习能力,使得它不能有效地使用滞后观察作为时间步长。 + +我们可以重复上述实验,并随着时间步长的增加增加 LSTM 中神经元的数量,看看它是否会导致表现提高。 + +这可以通过更改实验函数中的行来实现: + +``` +lstm_model = fit_lstm(train_scaled, 1, 500, 1, timesteps) +``` + +至 + +``` +lstm_model = fit_lstm(train_scaled, 1, 500, timesteps, timesteps) +``` + +此外,我们可以通过在文件名中添加“ __neurons_ ”后缀来保持写入文件的结果与第一个实验中创建的结果分开,例如,更改: + +``` +results.to_csv('experiment_timesteps_1.csv', index=False) +``` + +至 + +``` +results.to_csv('experiment_timesteps_1_neurons.csv', index=False) +``` + +用这些变化重复相同的 5 个实验。 + +运行这些实验后,您应该有 5 个结果文件。 + +``` +experiment_timesteps_1_neurons.csv +experiment_timesteps_2_neurons.csv +experiment_timesteps_3_neurons.csv +experiment_timesteps_4_neurons.csv +experiment_timesteps_5_neurons.csv +``` + +与前一个实验一样,我们可以加载结果,计算描述性统计数据,并创建一个盒子和须状图。完整的代码清单如下。 + +``` +from pandas import DataFrame +from pandas import read_csv +from matplotlib import pyplot +# load results into a dataframe +filenames = ['experiment_timesteps_1_neurons.csv', 'experiment_timesteps_2_neurons.csv', + 'experiment_timesteps_3_neurons.csv','experiment_timesteps_4_neurons.csv','experiment_timesteps_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 显得最低。 + +``` + timesteps_1 timesteps_2 timesteps_3 timesteps_4 timesteps_5 +count 10.000000 10.000000 10.000000 10.000000 10.000000 +mean 109.484374 133.195856 133.432933 145.843701 149.854229 +std 9.663732 36.328757 19.347675 19.389278 30.194324 +min 91.803241 91.791014 87.739484 113.808683 103.612424 +25% 104.757265 119.269854 127.937277 137.417983 131.278548 +50% 108.464050 129.775765 134.076721 147.222168 151.999097 +75% 114.265381 132.796259 147.557091 159.518828 164.741625 +max 126.581011 226.396127 156.019616 171.570206 208.030615 +``` + +创建框和胡须图以比较分布。 + +随着神经元数量和时间步长的增加,扩散和中位表现的趋势几乎表明测试 RMSE 呈线性增加。 + +线性趋势可能表明网络容量的增加没有足够的时间来拟合数据。也许还需要增加时代数量。 + +![Box and Whisker Plot of Timesteps and Neurons vs RMSE](img/ecc843af6c0636cc7c6c7d09a1fc59e6.jpg) + +时间步和神经元的盒子和晶须图与 RMSE + +## 扩展 + +本节列出了您可能考虑探索的一些进一步调查的领域。 + +* **作为特征**。使用滞后观测作为时间步长也提出了滞后观测是否可以用作输入特征的问题。目前尚不清楚 Keras LSTM 实施是否在内部以相同的方式处理时间步骤和特征。 +* **诊断运行图**。对于给定的实验,在多次运行的情况下查看列车和测试 RMSE 的图可能是有帮助的。这可能有助于梳理过度拟合或过度拟合是否正在发生,反过来又是解决它的方法。 +* **增加训练时期**。第二组实验中 LSTM 中神经元的增加可受益于训练时期数量的增加。这可以通过一些后续实验来探索。 +* **增加重复次数**。使用 10 次重复导致相对少量的测试 RMSE 结果。将重复增加至 30 或 100(或甚至更高)可能导致更稳定的结果。 + +你有没有探索过这些扩展? +在下面的评论中分享您的发现;我很想听听你发现了什么。 + +## 摘要 + +在本教程中,您了解了如何使用滞后观察作为 LSTM 网络中的输入时间步骤进行调查。 + +具体来说,你学到了: + +* 如何开发一个强大的测试工具来尝试使用 LSTM 进行输入表示。 +* 如何使用滞后观测值作为 LSTM 时间序列预测的输入时间步长。 +* 如何随着时间步长的增加提高网络的学习能力。 + +您发现使用滞后观察作为输入时间步长的期望并未降低所选问题和 LSTM 配置的测试 RMSE。 + +你有任何问题吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file diff --git a/docs/dl-ts/use-weight-regularization-lstm-networks-time-series-forecasting.md b/docs/dl-ts/use-weight-regularization-lstm-networks-time-series-forecasting.md new file mode 100644 index 0000000000000000000000000000000000000000..21e55b98f8e38204ae3bf3edd2ad5767e7df3796 --- /dev/null +++ b/docs/dl-ts/use-weight-regularization-lstm-networks-time-series-forecasting.md @@ -0,0 +1,703 @@ +# 如何利用 LSTM 网络进行权重正则化进行时间序列预测 + +> 原文: [https://machinelearningmastery.com/use-weight-regularization-lstm-networks-time-series-forecasting/](https://machinelearningmastery.com/use-weight-regularization-lstm-networks-time-series-forecasting/) + +长短期记忆(LSTM)模型是能够学习观察序列的循环神经网络。 + +这可能使它们成为一个非常适合时间序列预测的网络。 + +LSTM 的一个问题是他们可以轻松地过度训练训练数据,降低他们的预测技巧。 + +权重正则化是一种对 LSTM 节点内的权重施加约束(例如 L1 或 L2)的技术。这具有减少过度拟合和改善模型表现的效果。 + +在本教程中,您将了解如何使用 LSTM 网络进行权重正则化,并设计实验来测试其对时间序列预测的有效性。 + +完成本教程后,您将了解: + +* 如何设计一个强大的测试工具来评估 LSTM 网络的时间序列预测。 +* 如何设计,执行和解释使用 LSTM 的偏置权重正则化的结果。 +* 如何设计,执行和解释使用 LSTM 的输入和循环权重正则化的结果。 + +让我们开始吧。 + +![How to Use Weight Regularization with LSTM Networks for Time Series Forecasting](https://3qeqpr26caki16dnhd19sv6by6v-wpengine.netdna-ssl.com/wp-content/uploads/2017/03/How-to-Use-Weight-Regularization-with-LSTM-Networks-for-Time-Series-Forecasting.jpg) + +如何使用 LSTM 网络进行权重正则化进行时间序列预测 +摄影:Julian Fong,保留一些权利。 + +## 教程概述 + +本教程分为 6 个部分。他们是: + +1. 洗发水销售数据集 +2. 实验测试线束 +3. 偏差权重正则化 +4. 输入权重正则化 +5. 复发性体重正则化 +6. 审查结果 + +### 环境 + +本教程假定您已安装 Python SciPy 环境。您可以在此示例中使用 Python 2 或 3。 + +本教程假设您安装了 TensorFlow 或 Theano 后端的 Keras v2.0 或更高版本。 + +本教程还假设您安装了 scikit-learn,Pandas,NumPy 和 Matplotlib。 + +如果您在设置 Python 环境时需要帮助,请参阅以下帖子: + +* [如何使用 Anaconda 设置用于机器学习和深度学习的 Python 环境](http://machinelearningmastery.com/setup-python-environment-machine-learning-deep-learning-anaconda/) + +接下来,让我们看看标准时间序列预测问题,我们可以将其用作此实验的上下文。 + +## 洗发水销售数据集 + +该数据集描述了 3 年期间每月洗发水的销售数量。 + +单位是销售计数,有 36 个观察。原始数据集归功于 Makridakis,Wheelwright 和 Hyndman(1998)。 + +[您可以在此处下载并了解有关数据集的更多信息](https://datamarket.com/data/set/22r0/sales-of-shampoo-over-a-three-year-period)。 + +下面的示例加载并创建已加载数据集的图。 + +``` +# 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](img/646e3de8684355414799cd9964ad1d4f.jpg) + +洗发水销售数据集的线图 + +接下来,我们将看一下实验中使用的模型配置和测试工具。 + +## 实验测试线束 + +本节介绍本教程中使用的测试工具。 + +### 数据拆分 + +我们将 Shampoo Sales 数据集分为两部分:训练和测试集。 + +前两年的数据将用于训练数据集,剩余的一年数据将用于测试集。 + +将使用训练数据集开发模型,并对测试数据集进行预测。 + +测试数据集的持久性预测(天真预测)实现了每月洗发水销售 136.761 的错误。这在测试集上提供了较低的可接受表现限制。 + +### 模型评估 + +将使用滚动预测场景,也称为前进模型验证。 + +测试数据集的每个时间步骤将一次一个地走。将使用模型对时间步长进行预测,然后将获取测试集的实际预期值,并使其可用于下一时间步的预测模型。 + +这模仿了一个真实世界的场景,每个月都会有新的洗发水销售观察结果,并用于下个月的预测。 + +这将通过列车和测试数据集的结构进行模拟。 + +将收集关于测试数据集的所有预测,并计算错误分数以总结模型的技能。将使用均方根误差(RMSE),因为它会对大错误进行处罚,并产生与预测数据相同的分数,即每月洗发水销售额。 + +### 数据准备 + +在我们将模型拟合到数据集之前,我们必须转换数据。 + +在拟合模型和进行预测之前,对数据集执行以下三个数据变换。 + +1. 转换时间序列数据,使其静止不动。具体而言,滞后= 1 差分以消除数据中的增加趋势。 +2. 将时间序列转换为监督学习问题。具体而言,将数据组织成输入和输出模式,其中前一时间步的观察被用作预测当前时间步的观察的输入 +3. 将观察结果转换为具有特定比例。具体而言,将数据重新调整为-1 到 1 之间的值。 + +这些变换在预测时反转,在计算和误差分数之前将它们恢复到原始比例。 + +### LSTM 模型 + +我们将使用基础状态 LSTM 模型,其中 1 个神经元适合 1000 个时期。 + +理想情况下,批量大小为 1 将用于步行前导验证。我们将假设前进验证并预测全年的速度。因此,我们可以使用任何可以按样本数量分割的批量大小,在这种情况下,我们将使用值 4。 + +理想情况下,将使用更多的训练时期(例如 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 keras.regularizers import L1L2 +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](img/a689c5d0eea9de43a80c00e2d219cb9d.jpg) + +洗发水销售数据集中基线表现的盒子和晶须图 + +## 偏差权重正则化 + +权重正则化可以应用于 LSTM 节点内的偏置连接。 + +在 Keras 中,在创建 LSTM 层时使用 _bias_regularizer_ 参数指定。正则化器被定义为 L1,L2 或 L1L2 类之一的实例。 + +更多细节在这里: + +* [Keras 用于规范制定者](https://keras.io/regularizers/) + +在本实验中,我们将 L1,L2 和 L1L2 与基线模型的默认值 0.01 进行比较。我们可以使用 L1L2 类指定所有配置,如下所示: + +* L1L2(0.0,0.0)[例如基线] +* L1L2(0.01,0.0)[例如 L1] +* L1L2(0.0,0.01)[例如 L2] +* L1L2(0.01,0.01)[例如 L1L2 或弹性网] + +下面列出了更新的 _fit_lstm()_,_ 实验()_ 和 _run()_ 函数,用于使用 LSTM 的偏置权重正则化。 + +``` +# fit an LSTM network to training data +def fit_lstm(train, n_batch, nb_epoch, n_neurons, reg): + 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, bias_regularizer=reg)) + 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, reg): + # 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, reg) + # 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 + regularizers = [L1L2(l1=0.0, l2=0.0), L1L2(l1=0.01, l2=0.0), L1L2(l1=0.0, l2=0.01), L1L2(l1=0.01, l2=0.01)] + # run the experiment + results = DataFrame() + for reg in regularizers: + name = ('l1 %.2f,l2 %.2f' % (reg.l1, reg.l2)) + results[name] = experiment(series, n_lag, n_repeats, n_epochs, n_batch, n_neurons, reg) + # summarize results + print(results.describe()) + # save boxplot + results.boxplot() + pyplot.savefig('experiment_reg_bias.png') +``` + +运行此实验会打印每个已评估配置的描述性统计信息。 + +结果表明,与所考虑的所有其他配置相比,平均而言,无偏差正则化的默认值导致更好的表现。 + +``` + l1 0.00,l2 0.00 l1 0.01,l2 0.00 l1 0.00,l2 0.01 l1 0.01,l2 0.01 +count 30.000000 30.000000 30.000000 30.000000 +mean 92.821489 95.520003 93.285389 92.901021 +std 4.894166 3.637022 3.906112 5.082358 +min 81.394504 89.477398 82.994480 78.729224 +25% 89.356330 93.017723 90.907343 91.210105 +50% 92.822871 95.502700 93.837562 94.525965 +75% 95.899939 98.195980 95.426270 96.882378 +max 101.194678 101.750900 100.650130 97.766301 +``` + +还会创建一个框和胡须图来比较每个配置的结果分布。 + +该图显示所有配置具有大约相同的扩展,并且均匀地添加偏置正则化对该问题没有帮助。 + +![Box and Whisker Plots of Bias Weight Regularization Performance on the Shampoo Sales Dataset](img/ae61a9c9b4ef0d956afa6f7b7847557a.jpg) + +在洗发水销售数据集中偏差重量正规化的盒子和晶须图 + +## 输入权重正则化 + +我们还可以将正则化应用于每个 LSTM 单元上的输入连接。 + +在 Keras 中,这是通过将 _kernel_regularizer_ 参数设置为正则化类来实现的。 + +我们将测试与前一节中使用的相同的正则化器配置,具体为: + +* L1L2(0.0,0.0)[例如基线] +* L1L2(0.01,0.0)[例如 L1] +* L1L2(0.0,0.01)[例如 L2] +* L1L2(0.01,0.01)[例如 L1L2 或弹性网] + +下面列出了更新的 _fit_lstm()_,_ 实验()_ 和 _run()_ 函数,用于使用 LSTM 的偏置权重正则化。 + +``` +# fit an LSTM network to training data +def fit_lstm(train, n_batch, nb_epoch, n_neurons, reg): + 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, kernel_regularizer=reg)) + 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, reg): + # 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, reg) + # 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 + regularizers = [L1L2(l1=0.0, l2=0.0), L1L2(l1=0.01, l2=0.0), L1L2(l1=0.0, l2=0.01), L1L2(l1=0.01, l2=0.01)] + # run the experiment + results = DataFrame() + for reg in regularizers: + name = ('l1 %.2f,l2 %.2f' % (reg.l1, reg.l2)) + results[name] = experiment(series, n_lag, n_repeats, n_epochs, n_batch, n_neurons, reg) + # summarize results + print(results.describe()) + # save boxplot + results.boxplot() + pyplot.savefig('experiment_reg_input.png') +``` + +运行此实验会打印每个已评估配置的描述性统计信息。 + +结果表明,在输入连接中增加重量正则化确实在这种设置上提供了全面的好处。 + +我们可以看到,对于所有配置,测试 RMSE 大约低 10 个单位,当 L1 和 L2 都组合成弹性网类型约束时,可能会带来更多好处。 + +``` + l1 0.00,l2 0.00 l1 0.01,l2 0.00 l1 0.00,l2 0.01 l1 0.01,l2 0.01 +count 30.000000 30.000000 30.000000 30.000000 +mean 91.640028 82.118980 82.137198 80.471685 +std 6.086401 4.116072 3.378984 2.212213 +min 80.392310 75.705210 76.005173 76.550909 +25% 88.025135 79.237822 79.698162 78.780802 +50% 91.843761 81.235433 81.463882 80.283913 +75% 94.860117 85.510177 84.563980 82.443390 +max 105.820586 94.210503 90.823454 85.243135 +``` + +还会创建一个框和胡须图来比较每个配置的结果分布。 + +该图显示了输入正则化的一般较低的误差分布。结果还表明,随着 L1L2 配置获得更好的结果,正则化结果可能更加明显。 + +这是一个令人鼓舞的发现,表明用于输入正则化的具有不同 L1L2 值的额外实验将是值得研究的。 + +![Box and Whisker Plots of Input Weight Regularization Performance on the Shampoo Sales Dataset](img/56241ed05333ba50139dffc5fb206a71.jpg) + +洗发水销售数据集中输入重量正规化表现的盒子和晶须图 + +## 复发性体重正则化 + +最后,我们还可以将正则化应用于每个 LSTM 单元上的循环连接。 + +在 Keras 中,这是通过将 _recurrent_regularizer_ 参数设置为正则化类来实现的。 + +我们将测试与前一节中使用的相同的正则化器配置,具体为: + +* L1L2(0.0,0.0)[例如基线] +* L1L2(0.01,0.0)[例如 L1] +* L1L2(0.0,0.01)[例如 L2] +* L1L2(0.01,0.01)[例如 L1L2 或弹性网] + +下面列出了更新的 _fit_lstm()_,_ 实验()_ 和 _run()_ 函数,用于使用 LSTM 的偏置权重正则化。 + +``` +# fit an LSTM network to training data +def fit_lstm(train, n_batch, nb_epoch, n_neurons, reg): + 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_regularizer=reg)) + 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, reg): + # 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, reg) + # 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 + regularizers = [L1L2(l1=0.0, l2=0.0), L1L2(l1=0.01, l2=0.0), L1L2(l1=0.0, l2=0.01), L1L2(l1=0.01, l2=0.01)] + # run the experiment + results = DataFrame() + for reg in regularizers: + name = ('l1 %.2f,l2 %.2f' % (reg.l1, reg.l2)) + results[name] = experiment(series, n_lag, n_repeats, n_epochs, n_batch, n_neurons, reg) + # summarize results + print(results.describe()) + # save boxplot + results.boxplot() + pyplot.savefig('experiment_reg_recurrent.png') +``` + +运行此实验会打印每个已评估配置的描述性统计信息。 + +结果表明,对 LSTM 的复发连接使用正则化对此问题没有明显的好处。 + +尝试的所有变化的平均表现导致比基线模型更差的表现。 + +``` + l1 0.00,l2 0.00 l1 0.01,l2 0.00 l1 0.00,l2 0.01 l1 0.01,l2 0.01 +count 30.000000 30.000000 30.000000 30.000000 +mean 92.918797 100.675386 101.302169 96.820026 +std 7.172764 3.866547 5.228815 3.966710 +min 72.967841 93.789854 91.063592 89.367600 +25% 90.311185 98.014045 98.222732 94.787647 +50% 92.327824 100.546756 101.903350 95.727549 +75% 95.989761 103.491192 104.918266 98.240613 +max 110.529422 108.788604 110.712064 111.185747 +``` + +还会创建一个框和胡须图来比较每个配置的结果分布。 + +该图显示了与摘要统计相同的故事,表明使用复发体重正则化几乎没有益处。 + +![Box and Whisker Plots of Recurrent Weight Regularization Performance on the Shampoo Sales Dataset](img/7b9f6eb1255e7761505c2e3351eca482.jpg) + +洗发水销售数据集中复发重量正常化的盒子和晶须图 + +## 扩展 + +本节列出了后续实验的想法,以扩展本教程中的工作。 + +* **输入权重正则化**。输入权重正则化对该问题的实验结果表明了上市绩效的巨大希望。这可以通过网格搜索不同的 L1 和 L2 值来进一步研究,以找到最佳配置。 +* **行为动态**。可以通过在训练时期上绘制训练和测试 RMSE 来研究每个权重正则化方案的动态行为,以获得对过度拟合或欠拟合行为模式的权重正则化的想法。 +* **结合正则化**。可以设计实验来探索组合不同重量正则化方案的效果。 +* **激活正则化**。 Keras 还支持激活正则化,这可能是探索对 LSTM 施加约束并减少过度拟合的另一种途径。 + +## 摘要 + +在本教程中,您了解了如何将权重正则化与 LSTM 一起用于时间序列预测。 + +具体来说,你学到了: + +* 如何设计一个强大的测试工具来评估 LSTM 网络的时间序列预测。 +* 如何在 LSTM 上配置偏差权重正则化用于时间序列预测。 +* 如何在 LSTM 上配置输入和循环权重正则化以进行时间序列预测。 + +您对使用 LSTM 网络的权重正则化有任何疑问吗? +在下面的评论中提出您的问题,我会尽力回答。 \ No newline at end of file