谷歌云平台机器学习实用指南(三)
原文:
annas-archive.org/md5/0c231e89c375d109731af45617713934译者:飞龙
第十章:使用 TensorBoard 评估结果
在上一章中,我们了解了神经网络的工作原理,神经网络中的各种超参数是什么,以及如何进一步调整它们以提高我们模型的准确性。
Google 提供了 TensorBoard,它是模型训练日志的可视化。在本章中,我们展示了如何使用 TensorBoard 进行 TensorFlow 和 Keras。我们解释 TensorBoard 生成的可视化,以了解我们模型的性能,并了解 TensorBoard 中的其他功能,这些功能可以帮助我们更好地可视化数据集。
如前一章所述,Keras 作为一个框架,是 TensorFlow 或 Theano 之上的包装器。你将使用 TensorFlow 进行的一些计算,例如训练一个大规模的深度神经网络,可能很复杂且令人困惑。为了使其更容易理解、调试和优化 TensorFlow 程序,TensorFlow 的创建者包括了一套名为 TensorBoard 的可视化工具。
你可以使用 TensorBoard 来可视化你的 TensorFlow 图,绘制关于图执行的定量指标,还可以看到输入的图像等额外数据。当 TensorBoard 完全配置后,它看起来像这样:
从这张截图,你可以注意到图表显示了随着 epoch 数量的增加,平均交叉熵误差的减少。在章节的后续部分,我们将介绍以下内容:
-
安装 TensorBoard
-
TensorBoard 捕获的各种总结操作的概述
-
调试代码的方法
设置 TensorBoard
在上一章中,我们了解了如何设置 Datalab。在 Datalab 中安装 TensorBoard 与指定以下代码一样简单:
注意,我们不需要为 TensorBoard 进行任何单独的安装,它包含在google.datalab.ml包中预构建的。
一旦导入包,我们需要通过指定包含模型拟合过程写入的总结的日志位置来启动 TensorBoard。
tb.start方法的工作原理如下:
注意,在第一步中,它检查用户是否有权执行计算。接下来,它选择一个未使用的端口来打开 TensorBoard,最后它启动 TensorBoard 并打印打开 TensorBoard 的链接。
我们将在下一节中学习更多关于写入日志的内容。
总结操作的概述
总结提供了一种导出关于模型压缩信息的方法,然后可以在 TensorBoard 等工具中访问。
一些常用的总结函数包括:
-
标量 -
直方图 -
音频 -
图像 -
合并 -
merge_all
一个标量总结操作返回一个标量,即随着 epoch 数量的增加,某个度量值的值。
histogram 汇总操作返回各种值的直方图——可能是每一层的权重和偏差。
image 和 audio 汇总操作返回图像和音频,可以在 TensorBoard 中分别进行可视化和播放。
merge 操作返回所有输入汇总值的并集,而 merge_all 返回模型规范中包含的所有汇总的并集。
下一个部分将提供一些讨论的汇总的可视化。
调试代码的方法
为了理解 TensorBoard 如何帮助,让我们初始化一个如下所示的模式结构,一个注定不会工作的结构:
注意,在这段代码片段中,验证准确率仅为约 19%。
验证准确率如此低的原因是输入数据集未进行缩放,并且我们在未缩放的数据集上执行了 ReLU 激活。
注意,在前面的代码中,我们将在目录 logs/tensor_new6 中存储模型运行的日志(子目录可以命名为任何名称)。
一旦日志存储在这个位置,我们就可以按照以下步骤启动 TensorBoard:
前面的代码启动了 TensorBoard,其外观如下:
注意,默认情况下,输出给出标量的度量,即训练和测试数据集的准确率和损失值。
可以使用正则表达式 .* 在 Filter 标签中相邻可视化输出,如下所示:
注意,这张截图中的前两个图表表示训练数据集的准确率和损失,而接下来的两个图表表示验证数据集的准确率和损失。
当我们查看各个层的权重和偏差的直方图时,我们了解到权重和偏差在各个时期没有变化:
这表明网络架构中没有发生学习。
当我们在不同的标签页查看权重和偏差在各个时期的分布时,也可以注意到相同的情况:
从这张截图,我们可以得出模型准确率如此低的原因;这是因为模型无法更新权重。
现在,通过点击“GRAPHS”标签,让我们探索模型是否初始化正确:
你应该注意到训练块连接到图中的每个其他块。这是因为,为了计算梯度,需要连接到图中的每个变量(因为每个变量都包含需要调整的权重)。
现在,让我们从图中删除训练块。这可以通过如下方式右键单击训练块来完成:
移除训练块后的结果图如下:
注意,输入层连接到隐藏层,隐藏层又连接到输出层,从输出层计算指标和损失。让我们通过双击各个块来探索这些连接,如下所示:
放大这些连接有助于我们理解各个块中的形状:
输入层在维度上是(784),因为可能有任意数量的输入样本,但每个样本都是 784 维的。同样,内核(权重矩阵)是 784 x 784 维的,偏差将有 784 个初始化值,依此类推。
注意,在上面的图中,我们使用输入层的值并使用random_normal初始化的内核进行矩阵乘法。同时注意,random_normal初始化与训练块没有连接,而内核块与训练块相连。
让我们也找出输出层是否按照预期连接到所有相关块。鉴于图看起来非常复杂,我们可以使用 TensorBoard 提供的另一个功能:跟踪输入。跟踪输入有助于突出显示仅与任何感兴趣的块相连的块。通过在左侧面板中选择感兴趣的块并切换开关来激活,如下所示:
现在所有连接看起来都很好,但梯度仍然没有得到更新;让我们将激活函数更改为 sigmoid,然后检查权重直方图:
我们以下面的方式构建一个具有 sigmoid 激活的神经网络:
一旦定义并编译了神经网络结构,让我们按照以下步骤拟合模型:
为了打开 TensorBoard,我们将执行以下代码:
from google.datalab.ml import TensorBoard as tb
tb.start('./logs/tensor_neww3')
我们将收到以下输出:
同时,我们应该注意到准确率和损失指标有了相当大的改进:
也可以通过在 TensorBoard 函数中指定 write_grads=True 来可视化梯度的直方图。输出将如下所示:
从 TensorFlow 设置 TensorBoard
在上一章中,我们了解到在 TensorFlow 中定义模型有两种方式:
-
预制评估器
-
构建自定义评估器
在以下代码中,我们将考虑一个额外的代码片段,它将使我们能够可视化各种摘要操作:
注意,我们只需要在预制的估计器中指定 model_dir 来存储由 TensorFlow 操作生成的各种日志文件。
然后,可以通过引用模型目录来初始化 TensorBoard,如下所示:
上述代码将导致 TensorBoard 可视化,其中将包含所有内置的摘要。
自定义估计器的摘要
在上一节中,我们探讨了在 TensorBoard 中从预制的估计器获取预定义摘要的方法。在本节中,我们将了解如何在自定义估计器中获取摘要,以便它们可以在 TensorBoard 中进行可视化。
需要捕获的摘要操作应在自定义估计器函数中指定,如下所示:
注意,模型函数与我们之前在了解自定义估计器时定义的非常相似;然而,添加了一些将摘要写入日志文件的代码行。
tf.summary.scalar 添加了准确度指标。同样,我们可能还想将损失(另一个标量)添加到日志中;然而,它默认添加(注意,当训练模型时损失会被显示)。
tf.summary.histogram 提供了网络中权重的分布。
一旦模型训练完成,我们应该注意 TensorBoard 输出中的标量和直方图/分布。训练模型并启动 TensorBoard 的代码如下:
在上述代码片段中,我们指定了模型函数和参数以及日志文件将被写入的目录:
model.train(input_fn=train_input_fn, steps=1000)
上述代码片段训练了包含 1,024(批大小)数据点的 1,000 批次的模型:
from google.datalab.ml import TensorBoard as tb
tb.start('/content/datalab/docs/log10/')
此代码片段通过使用给定文件夹中编写的日志文件启动 TensorBoard。
摘要
在本章中,我们了解了在 TensorBoard 中可视化神经网络模型的方法,包括从 Keras 和 TensorFlow 中进行可视化。我们还考虑了如何在预制的估计器和自定义定义的估计器中可视化模型、权重分布以及损失/准确度指标。以及神经网络中的各种指标。
第十一章:通过超参数调整优化模型
神经网络包含多个参数,这些参数可以影响预测事件或标签的最终准确度。典型的参数包括:
-
训练时使用的批量大小
-
周期数量
-
学习率
-
隐藏层数量
-
每个隐藏层中的隐藏单元数量
-
隐藏层中应用的激活函数
-
使用的优化器
从前面的列表中,我们可以看到可以调整的参数数量非常高。这使得找到最佳的超参数组合具有挑战性。在这种情况下,Cloud ML Engine 提供的超参数调整服务非常有用。
在本章中,我们将介绍:
-
为什么需要超参数调整
-
超参数调整工作概述
-
在云端实现超参数调整
超参数调整的直觉
为了获得超参数调整的实用直觉,让我们通过以下场景来预测给定神经网络架构在 MNIST 数据集上的准确率:
-
场景 1:大量周期和低学习率
-
场景 2:少量周期和高的学习率
让我们在 Google Cloud 环境中创建训练集和测试集,如下所示:
- 下载数据集:
mkdir data
curl -O https://s3.amazonaws.com/img-datasets/mnist.pkl.gz
gzip -d mnist.pkl.gz
mv mnist.pkl data/
前面的代码创建了一个名为 data 的新文件夹,下载了 MNIST 数据集,并将其移动到 data 文件夹中。
- 在终端中打开 Python 并导入所需的包:
from __future__ import print_function
import tensorflow as tf
import pickle # for handling the new data source
import numpy as np
from datetime import datetime # for filename conventions
from tensorflow.python.lib.io import file_io # for better file I/O
import sys
- 导入 MNIST 数据集:
f = file_io.FileIO('data/mnist.pkl', mode='r')
data = pickle.load(f)
- 提取训练集和测试集:
(x_train, y_train), (x_test, y_test) = data
# Converting the data from a 28 x 28 shape to 784 columns
x_train = x_train.reshape(60000, 784)
x_train = x_train.astype('float32')
# Scaling the train dataset
x_train /= 255
# Reshaping the test dataset
x_test = x_test.reshape(10000, 784)
x_test = x_test.astype('float32')
# Scaling the test dataset
x_test /= 255
# Specifying the type of labels
y_train = y_train.astype(np.int32)
y_test = y_test.astype(np.int32)
- 创建估算函数:
# Creating the estimator input functions for train and test datasets
train_input_fn = tf.estimator.inputs.numpy_input_fn(
x={"x2": np.array(x_train)},
y=np.array(y_train),
num_epochs=None,
batch_size=1024,
shuffle=True)
test_input_fn = tf.estimator.inputs.numpy_input_fn(
x={"x2": np.array(x_test)},
y=np.array(y_test),
num_epochs=1,
shuffle=False)
- 指定列的类型:
feature_x = tf.feature_column.numeric_column("x2", shape=(784))
feature_columns = [feature_x]
- 使用场景 1 中的参数构建 DNN 分类器;即学习率为
0.1,步数为200:
num_hidden_units = [1000]
lr=0.1
num_steps=200
# Building the estimator using DNN classifier
# This is where the learning rate hyper parameter is passed
model = tf.estimator.DNNClassifier(feature_columns=feature_columns,
hidden_units=num_hidden_units,
activation_fn=tf.nn.relu,
n_classes=10,
optimizer=tf.train.AdagradOptimizer(learning_rate = lr))
model.train(input_fn=train_input_fn, steps=num_steps)
# Fetching the model results
result = model.evaluate(input_fn=test_input_fn)
print('Test loss:', result['average_loss'])
print('Test accuracy:', result['accuracy'])
在这种情况下,测试准确率达到 96.49%。
在场景 2 中,我们将使用不同的参数构建另一个 DNN 分类器;现在学习率为 0.01,步数为 2000:
num_hidden_units = [1000]
lr=0.01
num_steps=2000
# Building the estimator using DNN classifier
# This is where the learning rate hyper parameter is passed
model = tf.estimator.DNNClassifier(feature_columns=feature_columns,
hidden_units=num_hidden_units,
activation_fn=tf.nn.relu,
n_classes=10,
optimizer=tf.train.AdagradOptimizer(learning_rate = lr))
model.train(input_fn=train_input_fn, steps=num_steps)
# Fetching the model results
result = model.evaluate(input_fn=test_input_fn)
print('Test loss:', result['average_loss'])
print('Test accuracy:', result['accuracy'])
场景 2 中测试集上的准确率接近 98.2%。
前面的两个场景表明了不同超参数值如何影响最终结果的重要性。
在这种情况下,Google Cloud ML 引擎非常有用,我们可以更智能地选择更优的超参数集。
超参数调整概述
超参数调整通过在一个训练作业中运行多个试验来实现。每个试验是您训练应用程序的完整执行,其中选择的超参数值在您指定的范围内设置。Cloud ML Engine 训练服务会跟踪每个试验的结果,并为后续试验进行调整。作业完成后,您可以获取所有试验的摘要,以及根据您指定的标准确定的最有效配置值。
我们希望选择那些能给出最佳性能的超参数。这相当于一个优化问题,具体来说,是优化一个函数 f(x)(即性能作为超参数值的函数)在紧致集 A 上的问题。我们可以用数学公式表示为:
让我们以函数 (1-x)^(ex) 为例,它在 x = 0 处达到最大值 f(x) = 1,因此 arg max 是 0。
许多优化设置,如这个例子,假设目标函数 f(x) 有已知的数学形式,是凸的,或者容易评估。但这些特征不适用于寻找超参数的问题,其中函数是未知的且难以评估。这就是贝叶斯优化发挥作用的地方。
为了实现超参数调整,Google 使用了一种称为高斯过程探险家的算法,这是一种贝叶斯优化的形式。
当函数的数学形式未知或计算成本高昂时,贝叶斯优化是一种极其强大的技术。其背后的主要思想是基于数据(使用著名的贝叶斯定理)计算目标函数的后验分布,然后根据这个分布选择好的点进行尝试。
要使用贝叶斯优化,我们需要一种灵活地建模目标函数分布的方法。这比建模实数分布要复杂一些,因为我们需要一个这样的分布来表示我们对每个 x 的 f(x) 的信念。如果 x 包含连续超参数,那么将会有无限多个 x 需要建模 f(x),即为其构建一个分布。对于这个问题,高斯过程是一种特别优雅的技术。实际上,它们推广了多维高斯分布,并且确实存在足够灵活以建模任何目标函数的版本。
上述过程通常如以下图所示:
我们通过迭代的结果更新高斯模型,这有助于进一步确定要测试的正确下一组超参数集;结果进一步提高了我们的高斯模型,以识别要选择正确的超参数集。
高斯分布的细节超出了本书的范围,但为了这个练习,我们将采用 Google 的方法(作为一个黑盒)并使用 Google Cloud 实现超参数调整。
Google Cloud 中的超参数调整
为了让刚刚设置的高斯过程运行,我们必须允许我们的模型构建在 Google Cloud 上运行,以便进行超参数调整。
为了运行超参数调整,以下组件是必不可少的:
-
数据文件及其位置
-
模型文件
-
超参数配置文件
-
设置文件
-
__init__文件
由于我们正在 Google Cloud ML 引擎上运行模型,数据应位于云存储桶中,以便 ML 引擎可以访问。
这可以通过在云壳中执行以下操作来完成:
gsutil mb gs://my-mnist-bucket
gsutil cp -r data/mnist.pkl gs://my-mnist-bucket/data/mnist.pkl
注意,使用前面的步骤,我们已经创建了一个名为my-mnist-bucket的存储桶,并将我们的数据复制到该存储桶中。前面的代码应该会在该目录中创建一个名为data的目录和该目录中的mnist.pkl文件:
模型文件
模型文件应位于包含__init__.py文件的文件夹中。
让我们创建一个名为trainer的文件夹,其中包含模型文件和__init__文件:
mkdir trainer
cd trainer
之前的代码创建了trainer文件夹并将目录更改为新创建的文件夹。
让我们继续创建模型文件,如下所示:
vim mnist_mlp_lr_numsteps.py
将以下代码插入到之前创建的文件中:
from __future__ import print_function
import argparse
import pickle
from datetime import datetime
import numpy as np
from tensorflow.python.lib.io import file_io # for better file I/O
import sys
import tensorflow as tf
def train_model(train_file='data/mnist.pkl',job_dir='./tmp/mnist_mlp', num_steps = 1, lr=0.1, **args):
# logs_path gives access to the logs that are generated by the previous epochs of model
logs_path = job_dir + '/logs/' + str(datetime.now().isoformat())
print('Using logs_path located at {}'.format(logs_path))
# by default floats are considered as string
# Good idea to convert them back into floats
lr=float(lr)
num_steps=float(num_steps)
batch_size = 1024
num_classes = 10
# Reading in the pickle file. Pickle works differently with Python 2 vs 3
# In Python 2 the following code would be:
# f = file_io.FileIO(train_file, mode='r')
# data = pickle.load(f)
f = file_io.FileIO(train_file, mode='rb')
data = pickle.load(f,encoding='bytes')
(x_train, y_train), (x_test, y_test) = data
# Converting the data from a 28X28 shape to 784 columns
x_train = x_train.reshape(60000, 784)
x_train = x_train.astype('float32')
x_test = x_test.reshape(10000, 784)
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255
# Specifying the type of following labels
y_train = y_train.astype(np.int32)
y_test = y_test.astype(np.int32)
# Creating the estimator following input functions
train_input_fn = tf.estimator.inputs.numpy_input_fn(
x={"x2": np.array(x_train)},
y=np.array(y_train),
num_epochs=None,
batch_size=batch_size,
shuffle=True)
test_input_fn = tf.estimator.inputs.numpy_input_fn(
x={"x2": np.array(x_test)},
y=np.array(y_test),
num_epochs=1,
shuffle=False)
# Specifying the columns as numeric columns
feature_x = tf.feature_column.numeric_column("x2", shape=(784))
feature_columns = [feature_x]
num_hidden_units = [1000]
# Building the estimator using DNN classifier
# This is where the learning rate hyper parameter is passed
model = tf.estimator.DNNClassifier(feature_columns=feature_columns,
hidden_units=num_hidden_units,
activation_fn=tf.nn.relu,
n_classes=num_classes,
optimizer=tf.train.AdagradOptimizer(learning_rate = lr))
# Passing the other parameter: num_steps
model.train(input_fn=train_input_fn, steps=num_steps)
# Fetching the model results
result = model.evaluate(input_fn=test_input_fn)
print('Test loss:', result['average_loss'])
print('Test accuracy:', result['accuracy'])
if __name__ == '__main__':
# Parse the input arguments for common Cloud ML Engine options
# There are 4 arguments that we need to give, as per the preceding model specification
# training file location, job directory, number of steps and learning rate
parser = argparse.ArgumentParser()
parser.add_argument(
'--train-file',
help='Cloud Storage bucket or local path to training data')
parser.add_argument(
'--job-dir',
help='Cloud storage bucket to export the model and store temp files')
parser.add_argument(
'--num-steps',
help='number of steps')
parser.add_argument(
'--lr',
help='learning rate')
args = parser.parse_args()
arguments = args.__dict__
train_model(**arguments)
配置文件
一旦模型文件设置好,我们需要在同一个trainer文件夹中提供配置文件,以便 ML 引擎知道需要调整的参数以及参数的典型最小和最大值。
我们在trainer文件夹中创建配置文件如下:
vim hptune.yaml
以下代码被插入到前面的文件中:
trainingInput:
pythonVersion: "3.5"
scaleTier: CUSTOM
masterType: standard_gpu
hyperparameters:
goal: MAXIMIZE
hyperparameterMetricTag: accuracy
maxTrials: 10
maxParallelTrials: 1
params:
- parameterName: num-steps
type: INTEGER
minValue: 200
maxValue: 10000
scaleType: UNIT_LINEAR_SCALE
- parameterName: lr
type: DOUBLE
minValue: 0.001
maxValue: 0.1
scaleType: UNIT_LOG_SCALE
在前面的代码块中,我们指定了要运行的 Python 版本,并指定了它是在 CPU 上运行还是在 GPU 上运行。
在hyperparameters部分,我们指定了需要优化的指标是准确率(请注意,model.evaluate的输出是accuracy、loss、average loss和global step);目标是最大化它。
此外,我们还指定了要运行的试验的最大次数以及可以并行运行的试验的最大次数(当云配置与多个核心相关联时,此值会发生变化)。
params部分包含需要修改的参数、变量的类型以及最小和最大值。
ScaleType指示将对参数应用哪种缩放类型:
| Value | 描述 |
|---|---|
UNIT_LINEAR_SCALE | 线性地将可行空间缩放到(0,1)。 |
UNIT_LOG_SCALE | 对数地将可行空间缩放到(0,1)。整个可行空间必须是严格正的。 |
UNIT_REVERSE_LOG_SCALE | 将可行空间反向对数缩放到(0,1)。结果是,接近可行空间顶部的值比接近底部的点分散得更多。整个可行空间必须是严格正的。 |
设置文件
在某些情况下,我们可能需要安装未预构建的包。在这种情况下,setup.py文件很有用:
from setuptools import setup, find_packages
setup(name='mnist_mlp_lr_numsteps',
version='1.0',
packages=find_packages(),
include_package_data=True,
install_requires=[
'keras',
'h5py'],
zip_safe=False)
在前面的代码中,可以包含运行模型文件所需的额外包。
init 文件
为了让 Cloud ML 引擎为我们正在构建的模块创建一个包,它需要在trainer文件夹中创建一个__init__.py文件。
为了做到这一点,我们将运行以下代码:
touch trainer/__init__.py
现在一切设置就绪,我们按照以下方式运行作业:
export BUCKET_NAME=my-mnist-bucket
export JOB_NAME="mnist_mlp_hpt_train_$(date +%Y%m%d_%H%M%S)"
export JOB_DIR=gs://$BUCKET_NAME/$JOB_NAME
export REGION=us-east1
export HPTUNING_CONFIG=hptune.yaml
gcloud ml-engine jobs submit training $JOB_NAME \
--job-dir $JOB_DIR \
--runtime-version 1.6 \
--config $HPTUNING_CONFIG \
--module-name trainer.mnist_mlp_lr_numsteps \
--package-path ./trainer \
--region $REGION \
-- \
--train-file gs://$BUCKET_NAME/data/mnist.pkl \
--num-steps 100 \
--lr 0.01
注意,我们指定了数据存在的数据桶名称,以及需要存储日志的作业名称和目录。需要设置区域,并指定配置文件。
此外,使用你的包的命名空间点表示法将--module-name参数设置为应用程序主模块的名称。
注意,在指定区域后,我们有一个空格,表示现在开始是参数(它们是训练文件位置、步数和学习率)。
在前面的代码中指定的步数和学习率是默认版本,一旦传递给 ML 引擎作业,就会进行更改。
代码的输出可以在我们运行的作业的训练输出中可视化,如下所示:
最优的超参数可以从前面的输出中选择。我们可以看到,学习率设置为0.0149,步数设置为7658,比我们之前测试的两个场景在测试数据集上的准确率更高。
摘要
在本章中,我们了解了不同的参数组合如何影响最终的准确度度量,以及如何使用 Cloud ML 引擎进行超参数调整来进一步提高准确度。
在下一章中,我们将学习如何通过设置正确的参数和定义适当的架构来识别过拟合,并使我们的模型对之前未见过的数据更加鲁棒。
第十二章:使用正则化防止过拟合
到目前为止,在前面的章节中,我们了解了构建神经网络、评估 TensorBoard 结果以及调整神经网络模型的超参数以提高模型准确率。
虽然超参数通常有助于提高模型的准确率,但某些超参数的配置会导致模型过度拟合训练数据,而无法泛化测试数据,这就是过拟合训练数据的问题。
一个关键参数可以帮助我们在泛化未见数据集的同时避免过拟合,那就是正则化技术。以下是一些关键的正则化技术:
-
L2 正则化
-
L1 正则化
-
Dropout
-
缩放
-
批标准化
-
权重初始化
在本章中,我们将讨论以下内容:
-
过度/欠拟合的直觉
-
使用正则化减少过拟合
-
改善欠拟合场景
过度/欠拟合的直觉
在我们了解前面技术如何有用之前,让我们构建一个场景,以便我们了解过拟合的现象。
场景 1:在未见数据集上未泛化的案例
在这个场景中,我们将创建一个数据集,其中输入和输出之间存在清晰的线性可分映射。例如,每当独立变量为正时,输出为 [1,0],而当输入变量为负时,输出为 [0,1]:
为了那个数据集,我们将通过添加一些遵循先前模式相反的数据点来添加少量噪声(前一个数据集的 10%),即当输入变量为正时,输出为 [0,1],而当输入变量为负时,输出为 [1,0]:
将前两个步骤获得的数据集附加在一起,就得到了训练数据集,如下所示:
在下一步中,我们将创建测试数据集,其中它遵循大多数训练数据集的准则,即当输入为正时,输出为 [1,0]:
现在我们已经创建了数据集,让我们继续构建一个模型来预测给定的输入的输出。
这里的直觉是,如果训练准确率提高了超过 90.91%,那么它就是一个典型的过拟合案例,因为模型试图拟合那些不适用于未见数据集的少数观察结果。
为了检查这一点——让我们首先导入所有相关的包来在 keras 中构建一个模型:
我们构建了一个具有三个层的模型,其中每个隐藏层有 1,000、500 和 100 个单元:
下面是训练和测试数据集上损失和准确性的 TensorBoard 可视化:
从前两个图中,我们可以看到,随着训练数据集上的损失减少,其准确性提高。
此外,请注意,训练损失并没有平滑地减少——这可能会向我们表明它正在过度拟合训练数据。
你应该观察到,随着训练数据集准确性的提高,验证准确性(测试准确性)开始下降——再次向我们表明该模型对未见数据集的泛化能力不佳。
这种现象通常发生在模型过于复杂,试图拟合最后几个错误分类以减少训练损失时。
减少过拟合
通常,过拟合会导致一些权重相对于其他权重非常高。为了理解这一点,让我们看看在 场景 1 中通过运行模型在人工创建的数据集上获得的权重直方图:
我们可以看到,有一些权重值很高(> 0.1),而大多数权重则集中在零附近。
现在我们来探索通过 L1 和 L2 正则化对具有高权重值进行惩罚的影响。
正则化的直觉如下:
- 如果将权重值缩小到尽可能小,那么这些权重中的一些对微调我们的模型以适应少数异常情况贡献更大的可能性就较小。
实现 L2 正则化
既然我们已经看到了我们的数据集上过拟合的发生,我们将探讨 L2 正则化在减少数据集上过拟合的影响。
数据集上的 L2 正则化可以定义为如下:
注意,损失函数是传统的损失函数,其中 y 是因变量,x 是自变量,W 是核(权重矩阵)。
正则化项被添加到损失函数中。注意正则化值是权重矩阵所有维度的权重值的平方和。鉴于我们是在最小化权重值的平方和以及损失函数,成本函数确保没有权重值过大——从而确保过拟合现象减少。
Lambda 参数是一个超参数,用于调整我们对正则化项赋予的权重。
让我们探索在 场景 1 中定义的模型中添加 L2 正则化的影响:
注意,我们修改了在 场景 1 中看到的代码,通过添加 kernel_regularizer,在这种情况下,是具有 0.01 Lambda 值的 L2 正则化器。
注意 TensorBoard 的输出,正如我们训练前面的模型:
训练损失持续下降,验证准确率保持稳定,而训练准确率为 90.9%,没有考虑到过拟合的情况。
让我们探索权重的分布,以了解在执行 L2 正则化和没有正则化时权重分布的差异:
你应该注意到,与没有正则化的情况相比,在 L2 正则化的场景下,核(主要是dense_2和dense_3层的核)在零点有一个更尖锐的峰值。
为了进一步了解峰值分布,我们将修改 lambda 值,并将正则化的权重提高至 0.1 而不是 0.001,看看权重看起来如何:
注意,由于给正则化项更高的权重,权重在中心(值为 0)周围的分布要尖锐得多。
还应该注意到,核是dense_4,并且变化不大,因为我们没有在这个层应用正则化。
从前面的点我们可以得出结论,通过实现 L2 正则化,我们可以减少在没有正则化时看到的过拟合问题。
实现 L1 正则化
L1 正则化与 L2 正则化类似;然而,L1 正则化的成本函数与 L2 正则化不同,如下所示:
注意,在上述方程中,所有项都保持不变;只是正则化项是权重绝对值的总和,而不是权重平方值的总和。
让我们在代码中实现 L1 正则化;现在我们看到相应的输出如下:
注意,由于 L1 正则化项不涉及平方,我们可能需要在 L1 中降低 lambda 值,与 L2 相比(考虑到大多数权重小于一,平方它们会使权重值更小)。
在定义模型(这次带有正则化)后,我们拟合它,如下所示:
上述代码拟合结果在训练和测试数据集上的准确率,正如我们所预期,如下所示:
让我们也看看在直方图标签页中各层的权重分布:
我们应该注意,这里的核分布与 L2 正则化 lambda 值高时的核分布相似。
实现 dropout
另一种减少过拟合的方法是实现 dropout 技术。在执行典型反向传播中的权重更新时,我们确保在给定的一轮中,一些随机部分的权重被排除在权重更新之外——因此得名 dropout。
Dropout 作为一种技术,也可以帮助减少过拟合,因为减少单个 epoch 中需要更新的权重数量,从而减少了输出依赖于少数输入值的机会。
Dropout 可以这样实现:
模型拟合的结果如下:
应该注意,与没有正则化的场景相比,给定配置中的 dropout 导致权重分布略宽:
减少欠拟合
当以下情况发生时,通常会出现欠拟合:
-
模型极其复杂,并且运行了较少的 epoch
-
数据未进行归一化
场景 2:MNIST 数据集上的欠拟合实际操作
在以下场景中,我们看到 MNIST 数据集上欠拟合的实际案例:
注意,在前面的代码中,我们没有缩放我们的数据——训练和测试数据集的列值范围在 0 到 255 之间:
前面模型的训练和测试数据集上的 TensorBoard 准确率和损失可视化如下:
注意,在前面的图表中,训练数据集的损失和准确率几乎没有变化(注意两个图表的y轴值)。
这种情况(损失几乎没有变化)通常发生在输入有非常高的数字(通常>5)时。
前面的情况可以通过执行以下任何一项来纠正:
-
数据缩放
-
批标准化
数据缩放就像重复前面的架构一样简单,但需要稍作修改,即缩放训练和测试数据集:
批标准化可以在以下方式下执行(甚至可以在未缩放的 MNIST 数据集上):
训练和测试准确率的可视化如下:
在前面的场景中,我们看到即使在未缩放的数据集上,测试准确率也相当高。
场景 3:错误的权重初始化
就像前面的场景一样,如果权重没有正确初始化(即使数据集是正确缩放的),我们很可能遇到欠拟合的场景。例如,在以下代码中,我们将所有权重(核)初始化为零,然后注意到测试数据集上的准确率:
前述代码的输出结果导致以下 TensorBoard 可视化:
与场景 2类似,前述图表表明,通过先前定义的架构没有发生学习。
由于权重被初始化为零,没有发生学习。
建议将权重初始化为正态初始化。其他可以尝试的初始化方法,以测试是否可以提高准确率的有:
-
glorot_normal -
lecun_uniform -
glorot_uniform -
he_normal
摘要
在本章中,我们看到了过拟合的特征以及如何通过 L1 和 L2 正则化以及 dropout 来处理它们。同样,我们也看到了存在大量欠拟合的场景,以及如何通过缩放或批量归一化来帮助我们改善欠拟合的情况。
第十三章:超越前馈网络——CNN 和 RNN
人工神经网络(ANNs)现在在各种技术中极为广泛地被用作工具。在最简单的应用中,ANNs 为神经元之间的连接提供了一种前馈架构。前馈神经网络是设计出的第一种也是最简单的人工神经网络。在存在与某些问题相互作用的假设的情况下,前馈网络的内向结构具有强烈的限制性。然而,可以从它开始,创建一个计算一个单元的结果会影响另一个单元计算过程的网络。显然,管理这些网络动态的算法必须满足新的收敛标准。
在本章中,我们将介绍主要的人工神经网络架构,例如卷积神经网络(CNN)、循环神经网络(RNN)和长短期记忆(LSTM)。我们将解释每种类型 NN 背后的概念,并告诉您它们应该应用于哪些问题。每种类型的 NN 都使用 TensorFlow 在真实数据集上实现。
涵盖的主题包括:
-
卷积网络及其应用
-
循环网络
-
LSTM 架构
在本章结束时,我们将了解训练、测试和评估卷积神经网络(CNN)。我们将学习如何在 Google Cloud Platform 上训练和测试 CNN 模型。我们将涵盖 CNN 和 RNN 架构的概念。我们还将能够训练一个 LSTM 模型。读者将学习将哪种类型的神经网络应用于不同的问题,以及如何在 Google Cloud Platform 上定义和实现它们。
卷积神经网络
ANN 是从生物神经网络(人脑)中受到启发的模型系列,它从调节自然神经网络的机制开始,计划模拟人类思维。它们用于估计或近似可能依赖于大量输入的函数,其中许多通常是未知的。ANN 通常表示为相互连接的神经元系统,其中发生消息交换。每个连接都有一个相关的权重;权重的值可以根据经验进行调整,这使得神经网络成为适应各种类型输入并具有学习能力的工具。
ANNs 将神经元定义为中央处理单元,它通过对一组输入执行数学运算来生成一个输出。神经元的输出是输入加权总和加上偏差的函数。每个神经元执行一个非常简单的操作,如果接收到的总信号量超过激活阈值,则涉及激活。在以下图中,显示了一个简单的 ANN 架构:
实质上,卷积神经网络(CNN)是人工神经网络。事实上,就像后者一样,CNN 由通过加权分支(权重)相互连接的神经元组成;网络的训练参数再次是权重和偏差。
在 CNN 中,神经元之间的连接模式受到动物世界中视觉皮层结构的启发。大脑的这一部分(视觉皮层)中的单个神经元对观察到的狭窄区域内的某些刺激做出反应,该区域称为感受野。不同神经元的感受野部分重叠,以覆盖整个视野。单个神经元对其感受野内发生的刺激的反应可以通过卷积运算进行数学近似。
与神经网络训练相关的所有内容,即前向/反向传播和权重的更新,也适用于此上下文;此外,整个 CNN 总是使用一个可微分的成本函数。然而,CNN 假设它们的输入具有精确的数据结构,例如图像,这使得它们可以在其架构中采用特定的属性以更好地处理此类数据。
使用 FC 架构分层的一般神经网络——其中每一层的每个神经元都与前一层的所有神经元(除偏置神经元外)相连——在输入数据规模增加时通常无法很好地扩展。
让我们举一个实际例子:假设我们想要分析一张图像以检测对象。首先,让我们看看图像是如何处理的。正如我们所知,在图像编码中,它被分成一个小方格的网格,每个小方格代表一个像素。在这个阶段,为了编码彩色图像,我们只需要为每个方格识别一定数量的色调和不同的颜色渐变。然后我们通过适当的位序列对每个方格进行编码。以下是一个简单的图像编码示例:
网格中的方块数量定义了图像的分辨率。例如,宽度为 1,600 像素、高度为 800 像素(1,600 x 800)的图像包含(相乘)1,280,000 个像素,或 1.2 兆像素。因此,我们必须乘以三个颜色通道,最终得到 1,600 x 800 x 3 = 3,840,000。这意味着第一个隐藏层中完全连接的每个神经元都会有 3,840,000 个权重。这只是一个神经元的例子;考虑到整个网络,事情肯定会变得难以管理!
CNN 被设计成直接在由像素表示的图像中识别视觉模式,并且需要零或非常有限的预处理。它们能够识别极其变化的模式,例如自由手写和代表现实世界的图像。
通常,CNN 由几个交替的卷积和子采样级别(池化)组成,在分类的情况下,后面跟着一个或多个 FC 最终级别。以下图显示了经典的图像处理流程:
为了解决现实世界中的问题,这些步骤可以按需组合和堆叠。例如,你可以有两层、三层,甚至更多层的卷积。你可以输入所有你想要的池化来减少数据的大小。
如前所述,在 CNN 中通常使用不同类型的层。在以下章节中,将介绍主要的一些。
卷积层
这是主要类型的层;在 CNN 中使用一个或多个这些层是必不可少的。实际上,卷积层的参数与一组可操作的滤波器相关。每个滤波器在宽度和高度维度上是空间上小的,但它扩展到它所应用的输入体积的整个深度。
与普通神经网络不同,卷积层中的神经元以三维组织:宽度、高度和深度。它们在以下图中展示:
在前向传播过程中,每个滤波器沿着输入体积的宽度和高度进行平移——或者更准确地说,进行卷积——产生该滤波器的二维激活图(或特征图)。当滤波器在输入区域移动时,滤波器的值与它所应用的输入部分的值之间执行标量积操作。
直观地讲,网络的目标是学习在输入的特定空间区域出现某种特定类型的特征时被激活的滤波器。所有这些特征图(对于所有滤波器)沿着深度维度的排队形成了卷积层的输出体积。这个体积的每个元素都可以解释为观察输入的小区域并与其他特征图中的神经元共享其参数的神经元的输出。这是因为这些值都来自应用相同的滤波器。
总结一下,让我们关注以下要点:
-
局部感受野:层的每个神经元都与输入的一个小区域(称为局部感受野)完全连接;每个连接学习一个权重。
-
共享权重:由于有趣的特征(边缘、块等)可以在图像的任何地方找到,同一层的神经元共享权重。这意味着同一层的所有神经元都将识别相同的位置在不同输入点处的特征。
-
卷积:相同的权重图应用于不同的位置。卷积输出称为特征图。
每个滤波器捕捉前一层中存在的特征。因此,为了提取不同的特征,我们需要训练多个卷积滤波器。每个滤波器返回一个突出不同特性的特征图。
矩形线性单元
ReLU(修正线性单元)在神经网络中扮演神经元激活函数的角色。ReLU 层由应用函数 f*(x) = max (0, x)* 的神经元组成。这些层增加了网络的非线性,同时不修改卷积层的接收域。与双曲正切或 Sigmoid 等其他函数相比,ReLU 函数的功能更受青睐,因为它在比较中导致训练过程更快,同时不会显著影响泛化精度。
池化层
这些层定期插入到网络中,以减少当前表示的空间尺寸(宽度和高度)以及特定网络阶段中的体积;这有助于减少网络参数数量和计算时间。它还监控过拟合。池化层独立地对输入体积的每个深度切片进行操作,以在空间上调整其大小。
例如,这种技术将输入图像分割成一系列正方形,并对每个生成的区域,返回最大值作为输出。
CNNs 还使用位于卷积层之后的池化层。池化层将输入划分为区域,并选择单个代表性值(最大池化和平均池化)。使用池化层:
-
减少了后续层的计算量
-
增强了特征对空间位置的抗干扰性
它基于这样的概念,一旦某个特征被识别,其在输入中的精确位置并不像其相对于其他特征的近似位置那样重要。在典型的 CNN 架构中,卷积层和池化层反复交替。
完全连接层
这种类型的层与具有完全连接(FC)架构的任何经典 ANN 层完全相同。简单地说,在 FC 层中,每个神经元都连接到前一层中的所有神经元,特别是它们的激活。
与 CNN 中迄今为止所看到的不同,这种类型的层不使用局部连接性属性。FC 层连接到整个输入体积,因此,正如你可以想象的那样,会有很多连接。这种类型层的唯一可设置参数是构成它的 K 个神经元的数量。基本上定义 FC 层的是以下内容:将其 K 个神经元与所有输入体积连接,并计算其 K 个神经元中的每个神经元的激活。
实际上,其输出将是一个 1 x 1 x K 的单个向量,包含计算出的激活值。使用单个全连接层后,从三维组织输入体积(在单维中组织单个输出向量)切换,这表明在应用全连接层之后,不能再使用卷积层。在 CNN 的上下文中,全连接层的主要功能是对到目前为止获得的信息进行某种分组,用一个单一的数字(其神经元之一的活动)来表示,这将在后续的计算中用于最终的分类。
CNN 的结构
在详细分析了 CNN 的每个组件之后,现在是时候看看 CNN 的整体结构了。例如,从图像作为输入层开始,将会有一系列的卷积层,其中穿插着 ReLU 层,并在必要时使用标准化和池化层。最后,在输出层之前,将有一系列的全连接层。以下是一个 CNN 架构的示例:
基本思想是从一个大的图像开始,逐步减少数据,直到得到一个单一的结果。卷积通道越多,神经网络就越能够理解和处理复杂函数。
TensorFlow 概述
TensorFlow 是由谷歌提供的开源数值计算库,用于机器智能。它隐藏了构建深度学习模型所需的全部编程,并为开发者提供了一个黑盒接口来编程。
在 TensorFlow 中,图中的节点代表数学运算,而图边代表它们之间传递的多维数据数组(张量)。TensorFlow 最初是由谷歌大脑团队在谷歌机器智能研究内部为机器学习和深度神经网络研究开发的,但现在它已进入公共领域。当配置适当的时候,TensorFlow 会利用 GPU 处理。
TensorFlow 的通用用例如下:
-
图像识别
-
计算机视觉
-
语音/声音识别
-
时间序列分析
-
语言检测
-
语言翻译
-
基于文本的处理
-
手写识别
-
许多其他
要使用 TensorFlow,我们首先必须安装 Python。如果你机器上没有 Python 安装,那么是时候安装它了。Python 是一种动态的面向对象编程(OOP)语言,可用于许多类型的软件开发。它提供了与其他语言和程序集成的强大支持,附带一个大型标准库,可以在几天内学会。许多 Python 程序员可以证实生产力的显著提高,并觉得它鼓励开发更高质量的代码和可维护性。
Python 运行在 Windows、Linux/Unix、macOS X、OS/2、Amiga、Palm 手持设备和诺基亚手机上。它还适用于 Java 和.NET 虚拟机。Python 遵循 OSI 批准的开源许可;其使用是免费的,包括商业产品。
Python 是在 20 世纪 90 年代初由荷兰 Stichting Mathematisch Centrum 的 Guido van Rossum 创建的,作为名为ABC的语言的继任者。尽管 Python 包括许多其他人的贡献,但 Guido 仍然是 Python 的主要作者。
如果你不知道要使用哪个版本,有一份英文文档可以帮助你选择。原则上,如果你必须从头开始,我们建议选择 Python 3.6。有关可用版本和如何安装 Python 的所有信息,请参阅www.python.org/。
在正确安装我们的机器的 Python 版本后,我们必须担心安装 TensorFlow。我们可以从以下链接检索所有库信息和操作系统的可用版本:www.tensorflow.org/。
此外,在安装部分,我们可以找到一系列指南,解释如何安装允许我们用 Python 编写应用程序的 TensorFlow 版本。以下操作系统的指南可用:
-
Ubuntu
-
macOS X
-
Windows
例如,要在 Windows 上安装 TensorFlow,我们必须选择以下类型之一:
-
仅支持 CPU 的 TensorFlow
-
支持 GPU 的 TensorFlow
要安装 TensorFlow,以管理员权限启动终端。然后在终端中发出适当的pip3 install命令。要安装仅 CPU 版本,请输入以下命令:
C:\> pip3 install --upgrade tensorflow
一系列代码行将在视频中显示,以使我们了解安装过程的执行情况,如图所示:
在过程结束时,将显示以下代码:
Successfully installed absl-py-0.1.10 markdown-2.6.11 numpy-1.14.0 protobuf-3.5.1 setuptools-38.5.1 tensorflow-1.5.0 tensorflow-tensorboard-1.5.1 werkzeug-0.14.1
要验证安装,从 shell 中调用python,如下所示:
python
在 Python 交互式外壳中输入以下简短程序:
>>> import tensorflow as tf
>>> hello = tf.constant('Hello, TensorFlow!')
>>> sess = tf.Session()
>>> print(sess.run(hello))
如果系统输出以下内容,那么你就可以开始编写 TensorFlow 程序了:
Hello, TensorFlow!
在这种情况下,你将确认库在你的计算机上正确安装。现在你只需要使用它。
使用 CNN 和 TensorFlow 进行手写识别
手写识别(HWR)是现代技术中非常常用的程序。通过光学扫描(光学字符识别或OCR)或智能文字识别,可以从一张纸上离线检测到书写文本的图像。或者,可以在线检测笔尖移动(例如,从笔计算机表面,由于有更多线索,这通常更容易)。
从技术上来说,手写识别是计算机接收和解释来自纸张文件、照片、触摸屏和其他设备等来源的手写可识别输入的能力。HWR 通过需要 OCR 的各种技术来完成。然而,一个完整的脚本识别系统还管理格式,执行正确的字符分割,并找到最可能的单词。
修改后的国家标准与技术研究院(MNIST)是一个包含手写数字的大型数据库。它包含 70,000 个数据示例。它是 NIST 更大数据集的一个子集。这些数字的分辨率为 28 x 28 像素,存储在一个 70,000 行和 785 列的矩阵中;784 列形成 28 x 28 矩阵中的每个像素值,一个值是实际的数字。这些数字已经被尺寸归一化,并居中在一个固定大小的图像中。
MNIST 集中的数字图像最初是由 Chris Burges 和 Corinna Cortes 使用边界框归一化和居中技术选定的。Yann LeCun 的版本使用更大窗口内的质心居中。数据可在 Yann LeCun 的网站上找到:
每个图像都是 28 x 28 像素创建的。以下图显示了 MNIST 数据集中 0-8 的图像样本:
MNIST 包含几个手写数字的样本。这个数据集可以被输入到我们的 Python 程序中,我们的代码可以识别任何作为预测数据呈现的新手写数字。这是一个神经网络架构作为计算机视觉系统在 AI 应用中起作用的案例。以下表格显示了 LeCun 网站上可用的 MNIST 数据集的分布:
| 数字 | 数量 |
|---|---|
| 0 | 5923 |
| 1 | 6742 |
| 2 | 5958 |
| 3 | 6131 |
| 4 | 5842 |
| 5 | 5421 |
| 6 | 5918 |
| 7 | 6265 |
| 8 | 5851 |
| 9 | 5949 |
我们将使用 TensorFlow 库来训练和测试 MNIST 数据集。我们将 70,000 行的数据集分为 60,000 个训练行和 10,000 个测试行。接下来,我们将找到模型的准确率。然后,该模型可以用来预测任何包含 0 到 9 数字的 28 x 28 像素手写数字的输入数据集。在我们的示例 Python 代码中,我们使用一个 100 行的训练数据集和一个 10 行的测试数据集。在这个例子中,我们将学习如何使用 TensorFlow 层模块,它提供了一个高级 API,使得构建神经网络变得容易。它提供了创建密集(FC)层和卷积层、添加激活函数和应用 dropout 正则化的方法。
首先,我们将逐行分析代码,然后我们将了解如何使用 Google Cloud Platform 提供的工具来处理它。现在,让我们从头开始分析代码,学习如何应用卷积神经网络(CNN)来解决手写识别(HWR)问题:
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
这三条线被添加以编写兼容 Python 2/3 的代码库。所以让我们继续导入模块:
import numpy as np
import tensorflow as tf
这样,我们已经导入了numpy和tensorflow模块。让我们分析下一行代码:
tf.logging.set_verbosity(tf.logging.INFO)
这段代码设置了将记录的消息的阈值。在初始阶段之后,我们将定义一个函数,该函数将允许我们构建一个 CNN 模型:
def cnn_model_fn(features, labels, mode):
因此,我们已经定义了函数。现在让我们继续:
input_layer = tf.reshape(features["x"], [-1, 28, 28, 1])
在这一行代码中,我们以(batch_size,image_width,image_height,channels)的形式传递了输入张量,这是从层模块中的方法期望得到的,用于创建二维图像数据的卷积和池化层。让我们继续到第一个卷积层:
conv1 = tf.layers.conv2d(
inputs=input_layer,
filters=32,
kernel_size=[5, 5],
padding="same",
activation=tf.nn.relu)
这一层创建了一个卷积核,该核与层输入卷积以产生输出张量。卷积中的过滤器数量为 32,2D 卷积窗口的高度和宽度为[5,5],激活函数是 ReLU 函数。为此,我们使用了层模块中的conv2d()方法。接下来,我们将我们的第一个池化层连接到我们刚刚创建的卷积层:
pool1 = tf.layers.max_pooling2d(inputs=conv1,
pool_size=[2, 2], strides=2)
我们在层中使用了max_pooling2d()方法来构建一个执行 2x2 过滤器最大池化和步长为2的层。现在我们将第二个卷积层连接到我们的 CNN:
conv2 = tf.layers.conv2d(
inputs=pool1,
filters=64,
kernel_size=[5, 5],
padding="same",
activation=tf.nn.relu)
现在我们将连接第二个池化层到我们的卷积神经网络(CNN):
pool2 = tf.layers.max_pooling2d(inputs=conv2,
pool_size=[2, 2], strides=2)
pool2_flat = tf.reshape(pool2, [-1, 7 * 7 * 64])
接下来,我们将添加一个密集层:
dense = tf.layers.dense(inputs=pool2_flat,
units=1024, activation=tf.nn.relu)
通过这段代码,我们在 CNN 中添加了一个包含 1,024 个神经元的密集层和 ReLU 激活函数,以对卷积/池化层提取的特征进行分类。
记住,ReLU 层由应用函数*f(x) = max (0, x)*的神经元组成。这些层增加了网络的非线性,同时它们不修改卷积层的接收域。
为了提高结果,我们将在密集层上应用 dropout 正则化:
dropout = tf.layers.dropout(inputs=dense,
rate=0.4, training=mode ==
tf.estimator.ModeKeys.TRAIN)
为了做到这一点,我们在层中使用了 dropout 方法。接下来,我们将添加最终的层到我们的神经网络:
logits = tf.layers.dense(inputs=dropout, units=10)
这是logits层,它将返回我们的预测的原始值。通过前面的代码,我们创建了一个包含10个神经元的密集层(每个目标类 0-9 一个),具有线性激活。我们只需生成预测:
predictions = {
"classes": tf.argmax(input=logits, axis=1),
"probabilities": tf.nn.softmax(logits, name="softmax_tensor")
}
if mode == tf.estimator.ModeKeys.PREDICT:
return tf.estimator.EstimatorSpec(mode=mode,
predictions=predictions)
我们将预测生成的原始值转换为两种不同的格式,我们的模型函数可以返回:一个从 0 到 9 的数字以及示例是零、是一、是二等的概率。我们在字典中编译我们的预测并返回一个EstimatorSpec对象。现在,我们将传递定义一个loss函数:
loss = tf.losses.sparse_softmax_cross_entropy(labels=labels, logits=logits)
loss函数衡量模型的预测与目标类之间的匹配程度。此函数用于训练和评估。我们将配置我们的模型,在训练期间优化此损失值:
if mode == tf.estimator.ModeKeys.TRAIN:
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.001)
train_op = optimizer.minimize(
loss=loss,
global_step=tf.train.get_global_step())
return tf.estimator.EstimatorSpec(mode=mode, loss=loss, train_op=train_op)
我们使用0.001的学习率和随机梯度下降作为优化算法。现在,我们将在模型中添加一个准确度指标:
eval_metric_ops = {
"accuracy": tf.metrics.accuracy(
labels=labels, predictions=predictions["classes"])}
return tf.estimator.EstimatorSpec(
mode=mode, loss=loss, eval_metric_ops=eval_metric_ops)
为了做到这一点,我们在EVAL模式下定义了eval_metric_ops字典。因此,我们已经定义了网络的架构;现在有必要定义训练和测试网络的代码。为此,我们将在 Python 代码中添加一个main()函数:
def main(unused_argv):
然后我们将加载训练和评估数据:
mnist = tf.contrib.learn.datasets.load_dataset("mnist")
train_data = mnist.train.images
train_labels = np.asarray(mnist.train.labels, dtype=np.int32)
eval_data = mnist.test.images
eval_labels = np.asarray(mnist.test.labels, dtype=np.int32)
在这段代码中,我们将训练特征数据和训练标签分别存储在train_data和train_labels中的numpy数组中。同样,我们将评估特征数据和评估标签分别存储在eval_data和eval_labels中。接下来,我们将为我们的模型创建一个Estimator:
mnist_classifier = tf.estimator.Estimator(
model_fn=cnn_model_fn, model_dir="/tmp/mnist_convnet_model")
Estimator是 TensorFlow 的一个类,用于执行高级模型训练、评估和推理。以下代码设置了预测的日志记录:
tensors_to_log = {"probabilities": "softmax_tensor"}
logging_hook = tf.train.LoggingTensorHook(
tensors=tensors_to_log, every_n_iter=50)
现在我们已经准备好训练我们的模型:
train_input_fn = tf.estimator.inputs.numpy_input_fn(
x={"x": train_data},
y=train_labels,
batch_size=100,
num_epochs=None,
shuffle=True)
mnist_classifier.train(
input_fn=train_input_fn,
steps=15000,
hooks=[logging_hook])
为了做到这一点,我们创建了train_input_fn并在mnist_classifier上调用train()。在前面的代码中,我们固定了steps=15000,这意味着模型将总共训练 15,000 步。
执行此训练所需的时间取决于我们机器上安装的处理器,但无论如何,它可能将超过 1 小时。为了在更短的时间内进行此类训练,您可以减少传递给train()函数的步骤数;很明显,这种变化将对算法的准确性产生负面影响。
最后,我们将评估模型并打印结果:
eval_input_fn = tf.estimator.inputs.numpy_input_fn(
x={"x": eval_data},
y=eval_labels,
num_epochs=1,
shuffle=False)
eval_results = mnist_classifier.evaluate(input_fn=eval_input_fn)
print(eval_results)
我们调用了evaluate方法,该方法评估了在model_fn的eval_metriced_ops参数中指定的度量标准。我们的 Python 代码以以下行结束:
if __name__ == "__main__":
tf.app.run()
这些行只是一个非常快速的包装器,用于处理标志解析,然后转发到您自己的主函数。在这个阶段,我们只需将整个代码复制到一个具有.py扩展名的文件中,并在安装了 Python 和 TensorFlow 的机器上运行它。
在 Google Cloud Shell 上运行 Python 代码
Google Cloud Shell 直接从您的浏览器提供对云资源的命令行访问。您可以轻松管理项目和服务,无需在系统中安装 Google Cloud SDK 或其他工具。使用 Cloud Shell,当您需要时,Cloud SDK 的gcloud命令行工具和其他必要的实用工具始终可用,更新并完全认证。
以下是一些 Google Cloud Shell 的功能:
-
这是一个用于管理托管在 Google Cloud Platform 上的资源的 shell 环境。
-
我们可以使用 Linux shell 的灵活性来管理我们的 GCP 资源。Cloud Shell 通过在网页控制台中打开的终端窗口,提供了对虚拟机实例的命令行访问。
-
它为访问托管在 Google Cloud Platform 上的项目和资源提供了集成授权。
-
您最喜欢的许多命令行工具,从 bash 和 sh 到 emacs 和 vim,都已经预先安装和更新。如 MySQL 客户端、Kubernetes 和 Docker 等管理工具已配置并就绪。您不再需要担心安装最新版本及其所有依赖项。只需连接到 Cloud Shell。
开发者将能够访问所有喜欢的预配置开发工具。您将找到 Java、Go、Python、Node.js、PHP 和 Ruby 的开发和实现工具。在 Cloud Shell 实例中运行您的网络应用程序,并在浏览器中预览它们。然后使用预配置的 Git 和 Mercurial 客户端再次提交到存储库。
Cloud Shell 为 Cloud Shell 实例分配了 5 GB 的永久磁盘存储空间,挂载为 $ HOME 目录。存储在 $ HOME 目录中的所有文件,包括用户配置脚本和 bashrc、vimrc 等文件,从一个会话持续到另一个会话。
要启动 Cloud Shell,只需在控制台窗口顶部点击激活 Google Cloud Shell 按钮,如下面的截图所示:
在控制台底部的新的框架中打开一个 Cloud Shell 会话,并显示命令行提示符。初始化 shell 会话可能需要几秒钟。现在,我们的 Cloud Shell 会话已准备好使用,如下面的截图所示:
在这一点上,我们需要将包含 Python 代码的 cnn_hwr.py 文件传输到 Google Cloud Platform。我们已看到,为此,我们可以使用 Google Cloud Storage 提供的资源。然后我们打开 Google Cloud Storage 浏览器并创建一个新的存储桶。
记住,存储桶是基本容器,用于存储您的数据。您存储在 Cloud Storage 中的所有内容都必须包含在存储桶中。您可以使用存储桶来组织您的数据并控制对数据的访问,但与目录和文件夹不同,您不能嵌套存储桶。
要将 cnn_hwr.py 文件传输到 Google Storage,请执行以下步骤:
-
只需点击创建存储桶图标
-
在创建存储桶窗口中输入新存储桶的名称(
cnn-hwr) -
之后,在存储桶列表中会出现一个新的存储桶
-
点击
cnn-hwr存储桶 -
在打开的窗口中点击上传文件图标
-
在打开的对话框窗口中选择文件
-
点击打开
在这一点上,我们的文件将出现在新的存储桶中,如下面的图所示:
现在,我们可以从 Cloud Shell 访问文件。为此,我们在 shell 中创建一个新的文件夹。在 shell 提示符中输入以下命令:
mkdir CNN-HWR
现在,要将文件从 Google Storage 存储桶复制到 CNN-HWR 文件夹,只需在 shell 提示符中输入以下命令:
gsutil cp gs://cnn-hwr-mlengine/cnn_hwr.py CNN-HWR
以下代码将显示:
giuseppe_ciaburro@progetto-1-191608:~$ gsutil cp gs://cnn-hwr/cnn_hwr.py CNN-HWR
Copying gs://cnn-hwr/cnn_hwr.py...
/ [1 files][ 5.7 KiB/ 5.7 KiB]
Operation completed over 1 objects/5.7 KiB.
现在,让我们进入文件夹并验证文件的存在:
$cd CNN-HWR
$ls
cnn_hwr.py
我们只需运行该文件:
$ python cnn_hwr.py
显示了一系列初步指令:
Successfully downloaded train-images-idx3-ubyte.gz 9912422 bytes.
Extracting MNIST-data/train-images-idx3-ubyte.gz
Successfully downloaded train-labels-idx1-ubyte.gz 28881 bytes.
Extracting MNIST-data/train-labels-idx1-ubyte.gz
Successfully downloaded t10k-images-idx3-ubyte.gz 1648877 bytes.
Extracting MNIST-data/t10k-images-idx3-ubyte.gz
Successfully downloaded t10k-labels-idx1-ubyte.gz 4542 bytes.
Extracting MNIST-data/t10k-labels-idx1-ubyte.gz
INFO:tensorflow:Using default config.
它们表明数据下载成功,TensorFlow 库的调用也成功了。从这一点开始,网络的训练开始,正如我们预料的,这可能相当长。算法执行结束后,将返回以下信息:
INFO:tensorflow:Saving checkpoints for 15000 into /tmp/mnist_convnet_model/model.ckpt.
INFO:tensorflow:Loss for final step: 2.2751274.INFO:tensorflow:Starting evaluation at 2018-02-19-08:47:04
INFO:tensorflow:Restoring parameters from /tmp/mnist_convnet_model/model.ckpt-15000
INFO:tensorflow:Finished evaluation at 2018-02-19-08:47:56
INFO:tensorflow:Saving dict for global step 15000: accuracy = 0.9723, global_step = 15000, loss = 0.098432
{'loss': 0.098432, 'global_step': 15000, 'accuracy': 0.9723}
在这种情况下,我们在测试数据集上达到了97.2的准确率。
循环神经网络
前馈神经网络基于输入数据,这些数据被提供给网络并转换为输出。如果是一个监督学习算法,输出是一个可以识别输入的标签。基本上,这些算法通过识别模式将原始数据连接到特定的类别。另一方面,循环网络不仅接受被提供给网络的当前输入数据,还包括它们随时间经历过的内容。
一个循环神经网络(RNN)是一种信息双向流动的神经网络模型。换句话说,在前馈网络中,信号的传播仅以连续的方式从输入到输出方向进行,而循环网络则不同。在这些网络中,这种传播也可以从跟随前一个神经层的神经层发生,或者在同一层的神经元之间发生,甚至可以发生在同一个神经元自身之间。
一个循环网络在特定时刻做出的决策会影响它接下来将做出的决策。因此,循环网络有两个输入来源——当前和最近过去——它们结合起来决定如何对新数据进行响应,就像人们在日常生活中所做的那样。
循环网络之所以与前馈网络不同,是因为它们与过去决策相关的反馈回路,因此暂时将它们的输出作为输入接受。这一点可以通过说循环网络具有记忆来强调。将记忆添加到神经网络中有其目的:序列本身包含信息,循环网络使用这些信息执行前馈网络无法执行的任务。
访问记忆是通过内容而不是通过地址或位置来实现的。一种方法是认为记忆内容是 RNN 节点上的激活模式。想法是使用一个激活方案来启动网络,这个方案是请求的记忆内容的部分或噪声表示,并且网络稳定在所需的内容上。
RNN 是一类神经网络,其中至少存在一个神经元之间的反馈连接,形成一个有向循环。以下图示了一个典型的 RNN,其输出层和隐藏层之间存在连接:
在图所示的循环网络中,既使用输入层又使用输出层来定义隐藏层的权重。
最终,我们可以将 RNN 视为 ANN 的一种变体:这些变体可以在不同的隐藏层数量和数据流的不同趋势上进行表征。RNN 的特点是数据流的不同趋势,实际上神经元之间的连接形成了一个循环。与前馈网络不同,RNN 可以使用内部记忆进行其处理。RNN 是一类 ANN,其特征是隐藏层之间的连接,这些连接通过时间传播以学习序列。
数据在内存中保持的方式以及在不同时间周期中的流动使得 RNN 强大且成功。RNN 的应用案例包括以下领域:
-
股市预测
-
图像标题
-
天气预报
-
基于时间序列的预测
-
语言翻译
-
语音识别
-
手写识别
-
音频或视频处理
-
机器人动作序列
循环神经网络被设计成通过数据序列识别模式,并在预测和预测方面非常有用。它们可以在文本、图像、语音和时间序列数据上工作。RNN 是功能强大的 ANN 之一,代表了生物大脑,包括具有处理能力的记忆。
循环神经网络从当前输入(类似于前馈网络)以及之前计算得出的输出中获取输入。在下面的图中,我们比较了前馈神经网络和循环神经网络的单个神经元工作原理:
正如我们在简单、刚刚提出的单个神经元方案中看到的那样,反馈信号被添加到 RNN 的输入信号中。反馈是一个相当重要和显著的特征。与仅限于从输入到输出的单向信号的简单网络相比,反馈网络更有可能更新,并且具有更多的计算能力。反馈网络显示出单向网络未揭示的现象和过程。
为了理解 ANN 和 RNN 之间的差异,我们将 RNN 视为神经网络的网络,并且循环性质以下列方式展开:考虑神经元在不同时间周期(t-1,t,t+1,等等)的状态,直到收敛或达到总 epoch 数。
网络学习阶段可以使用类似于导致前馈网络反向传播算法的梯度下降过程进行。至少在简单架构和确定性激活函数的情况下是有效的。当激活是随机的,模拟退火方法可能更合适。
RNN 架构可以有多种不同的形式。在数据向后流动的方式上有更多变体:
-
完全循环
-
递归
-
跳频
-
Elman 网络
-
LSTM
-
门控循环单元
-
双向
-
循环多层感知器
在接下来的几页中,我们将分析这些网络中的一些架构。
完全循环神经网络
一个全递归神经网络是一个神经元网络,每个神经元都有一个指向其他每个神经元的定向(单向)连接。每个神经元都有一个时变的、实值的激活度。每个连接都有一个可修改的实值权重。期望有输入神经元、输出神经元和隐藏神经元。这种网络是一种多层感知器,其中前一组隐藏单元的激活度与输入一起反馈到网络中,如下面的图所示:
在每一步中,每个非输入单元将其当前激活度计算为连接到它的所有单元激活度的加权和的非线性函数。
递归神经网络
递归网络是循环网络的推广。在循环网络中,权重是共享的,维度在序列长度上保持不变。在递归网络中,权重是共享的,维度在节点上保持不变。以下图显示了递归神经网络的外观:
递归神经网络可用于学习树状结构。它们在解析自然场景和语言方面非常有用。
霍普菲尔德循环神经网络
1982 年,物理学家约翰·J·霍普菲尔德发表了一篇基础文章,在其中引入了一个数学模型,通常被称为霍普菲尔德网络。这个网络突出了从大量简单处理元素的集体行为中产生的新计算能力。霍普菲尔德网络是一种循环人工神经网络(ANN)的形式。
根据霍普菲尔德的观点,每个物理系统都可以被认为是一个潜在的存储设备,如果它具有一定数量的稳定状态,这些状态作为系统本身的吸引子。基于这一考虑,他提出了这样的吸引子的稳定性和位置代表了由大量相互作用的神经元组成的系统的自发性属性的观点。
结构上,霍普菲尔德网络构成一个循环对称神经网络(因此具有对称的突触权重矩阵),它是完全连接的,其中每个神经元都与所有其他神经元相连,如下面的图所示:
如前所述,循环神经网络是一种包含双向信息流的神经网络;换句话说,在前馈网络中,信号的传播仅以连续的方式在从输入到输出的方向上进行,而在循环网络中,这种传播也可以从跟随前一个神经层的神经层或同一层中的神经元(霍普菲尔德网络)之间发生,甚至可以发生在神经元与其自身之间。
跳频网络的动力学由一个非线性微分方程组描述,神经元更新机制可以是:
-
异步:一次更新一个神经元
-
同步:所有神经元同时更新
-
连续:所有神经元持续更新
Elman 神经网络
Elman 神经网络是一种前馈网络,其中除了与输出层相连外,隐藏层还分支到另一个相同的层,称为上下文层,它与权重等于一的上下文层相连。在每一个时刻(每次数据传递到输入层的神经元时),上下文层的神经元保持前一个值并将它们传递给相应的隐藏层神经元。以下图显示了 Elman 网络方案:
与前馈网络类似,Elman 的 RNN 可以使用称为时间反向传播(BPTT)的算法进行训练,这是专门为 RNN 创建的反向传播的变体。实质上,该算法将神经网络展开,将其转换为前馈网络,层数等于要学习的序列长度;随后,应用经典的反向传播算法。或者,可以使用全局优化方法,如遗传算法,特别是在无法应用 BPTT 的 RNN 拓扑结构上。
长短期记忆网络
LSTM 是 RNN 的一种特定架构,最初由 Hochreiter 和 Schmidhuber 在 1997 年构思。这种类型的神经网络最近在深度学习背景下被重新发现,因为它不受梯度消失问题的影响,并且在实践中提供了出色的结果和性能。
梯度消失问题影响了基于梯度的学习方法的 ANN 训练。在梯度方法,如反向传播中,权重按误差梯度的比例进行调整。由于上述梯度的计算方式,我们得到一个效果,即它们的模量呈指数下降,朝着最深的层前进。问题是,在某些情况下,梯度会变得极其小,实际上阻止了权重改变其值。在最坏的情况下,这可能会完全阻止神经网络进一步训练。
基于 LSTM 的网络非常适合预测和分类时间序列,它们正在取代许多经典机器学习方法。事实上,在 2012 年,谷歌替换了其语音识别模型,从表示了 30 多年标准的隐马尔可夫模型(HMM)过渡到深度学习神经网络。在 2015 年,它切换到结合连接主义时间分类(CTC)的 RNN LSTM。
CTC 是一种神经网络输出和相关评分函数,用于训练 RNN。
这是因为 LSTM 网络能够考虑数据之间的长期依赖关系,在语音识别的情况下,这意味着管理句子中的上下文以提高识别能力。
LSTM 网络由相互连接的细胞(LSTM 块)组成。每个细胞又由三种类型的端口组成:输入门、输出门和遗忘门。它们分别实现了对细胞内存的写入、读取和重置功能。端口不是二进制而是模拟的(通常由一个映射到范围(0, 1)的 sigmoid 激活函数管理,其中零表示完全抑制,1 表示完全激活),并且是乘法的。这些端口的存 在使得 LSTM 细胞能够记住信息一段时间。实际上,如果输入门低于激活阈值,细胞将保持之前的状态,而如果它被激活,当前状态将与输入值相结合。正如其名所示,遗忘门将细胞当前状态重置(当其值被带到零时),输出门决定细胞内的值是否必须取出。
下图显示了 LSTM 单元:
基于神经网络的这些方法非常强大,因为它们能够捕捉数据之间的特征和关系。特别是,在实践中,也发现 LSTM 网络提供了高性能和优秀的识别率。一个缺点是神经网络是黑盒模型,因此它们的行为是不可预测的,并且无法追踪它们处理数据的逻辑。
使用 RNN 和 TensorFlow 进行手写识别
为了练习 RNN,我们将使用之前用于构建 CNN 的数据集。我指的是 MNIST 数据集,这是一个包含大量手写数字的大型数据库。它包含 70,000 个数据示例。它是 NIST 更大数据集的一个子集。28 x 28 像素分辨率的图像存储在一个 70,000 行和 785 列的矩阵中;28 x 28 矩阵中的每个像素值和一个值是实际的数字。在固定大小的图像中,数字已经被尺寸归一化。
在此情况下,我们将使用 TensorFlow 库实现一个 RNN(LSTM)来对图像进行分类。我们将把每行图像视为像素序列。由于 MNIST 图像的形状是 28 x 28,我们将为每个样本处理 28 个时间步长的 28 个序列。
首先,我们将逐行分析代码;然后我们将看到如何使用 Google Cloud Platform 提供的工具来处理它。现在,让我们通过代码学习如何将 RNN(LSTM)应用于解决 HWR 问题。让我们从代码的开始部分开始:
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
这三行代码是为了编写一个兼容 Python 2/3 的代码库而添加的。因此,让我们继续导入模块:
import tensorflow as tf
from tensorflow.contrib import rnn
这样,我们已经导入了tensorflow模块,以及来自tensorflow.contrib的rnn模块。《tensorflow.contrib》包含易变或实验性代码。《rnn`模块是一个用于构建 RNN 单元和额外的 RNN 操作的模块。让我们分析下一行代码:
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("/tmp/data/", one_hot=True)
第一行用于从 TensorFlow 库导入mnist数据集;实际上,minist数据集已经作为示例存在于库中。第二行从本地目录读取数据。让我们继续设置训练参数:
learning_rate = 0.001
training_steps = 20000
batch_size = 128
display_step = 1000
学习率是学习算法用来确定权重调整速度的值。它决定了使用该算法训练的权重神经元的获取时间。《训练步数》设置训练过程执行次数。《批量大小》是你输入网络的样本数量。《显示步数》决定显示训练部分结果的步数。现在让我们设置网络参数:
num_input = 28
timesteps = 28
num_hidden = 128
num_classes = 10
第一个参数(num_input)设置 MNIST 数据输入(图像形状:28 x 28)。timesteps参数相当于你运行 RNN 的时间步数。《num_hidden参数设置神经网络隐藏层的数量。最后,《num_classes参数设置 MNIST 总类别(0-9 数字)。让我们分析以下代码行:
X = tf.placeholder("float", [None, timesteps, num_input])
Y = tf.placeholder("float", [None, num_classes])
在这些代码行中,我们使用了tf.placeholder()函数。占位符简单地说是一个我们将在以后日期分配数据的变量。它允许我们创建操作和构建计算图,而无需数据。这样,我们已经设置了tf.Graph输入。《tf.Graph》包含两种相关信息:图结构和图集合。TensorFlow 使用数据流图来表示你的计算,以单个操作之间的依赖关系为依据。这导致了一种低级编程模型,其中你首先定义数据流图,然后创建 TensorFlow 会话,在一系列本地和远程设备上运行图的一部分。让我们继续定义权重:
weights = {
'out': tf.Variable(tf.random_normal([num_hidden, num_classes]))
}
biases = {
'out': tf.Variable(tf.random_normal([num_classes]))
}
网络中的权重是将输入转换为影响输出的最重要的因素。这与线性回归中的斜率类似,其中权重乘以输入以形成输出。权重是决定每个神经元如何影响其他神经元的数值参数。偏差类似于线性方程中添加的截距。它是一个额外的参数,用于调整输出,以及神经元输入的加权总和。现在我们必须通过创建一个新的函数来定义RNN:
def RNN(x, weights, biases):
x = tf.unstack(x, timesteps, 1)
lstm_cell = rnn.BasicLSTMCell(num_hidden, forget_bias=1.0)
outputs, states = rnn.static_rnn(lstm_cell, x, dtype=tf.float32)
return tf.matmul(outputs[-1], weights['out']) + biases['out']
使用 unstack() 函数来获取形状为 (batch_size, n_input) 的 timesteps 张量列表。然后我们使用 TensorFlow 定义了一个 lstm 单元,并得到了 lstm 单元输出。最后,我们在内循环和最后一个输出处放置了一个线性激活,使用 RNN。接下来,让我们继续:
logits = RNN(X, weights, biases)
prediction = tf.nn.softmax(logits)
第一行代码使用新定义的 RNN 函数构建网络,而第二行代码使用 tf.nn.softmax() 函数进行预测,该函数计算 softmax 激活。接下来,我们将定义 loss 和 optimizer:
loss_op = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(
logits=logits, labels=Y))
optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)
train_op = optimizer.minimize(loss_op)
loss 函数将事件或一个或多个变量的值映射到一个实数,直观地表示与事件相关的一些 cost。我们使用了 tf.reduce_mean() 函数,该函数计算张量维度的元素均值。optimizer 基类提供了计算损失梯度并将梯度应用于变量的方法。一系列子类实现了经典的优化算法,如梯度下降和 AdaGrad。让我们继续评估模型:
correct_pred = tf.equal(tf.argmax(prediction, 1), tf.argmax(Y, 1))
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
然后,我们将通过分配它们的默认值来初始化变量:
init = tf.global_variables_initializer()
现在,我们可以开始训练网络:
with tf.Session() as sess:
sess.run(init)
for step in range(1, training_steps+1):
batch_x, batch_y = mnist.train.next_batch(batch_size)
batch_x = batch_x.reshape((batch_size, timesteps, num_input))
sess.run(train_op, feed_dict={X: batch_x, Y: batch_y})
if step % display_step == 0 or step == 1:
loss, acc = sess.run([loss_op, accuracy], feed_dict={X: batch_x,
Y: batch_y})
print("Step " + str(step) + ", Minibatch Loss= " + \
"{:.4f}".format(loss) + ", Training Accuracy= " + \
"{:.3f}".format(acc))
print("End of the optimization process ")
最后,我们将计算 128 个 mnist 测试图像的准确率:
test_len = 128
test_data = mnist.test.images[:test_len].reshape((-1, timesteps, num_input))
test_label = mnist.test.labels[:test_len]
print("Testing Accuracy:", \
sess.run(accuracy, feed_dict={X: test_data, Y: test_label}))
到目前为止,我们只需将整个代码复制到一个以 .py 扩展名的文件中,并在安装了 Python 和 TensorFlow 的机器上运行它。
在 Google Cloud Shell 上使用 LSTM
在彻底分析 Python 代码之后,是时候运行它来对数据集中的图像进行分类了。为此,我们以与 CNN 示例类似的方式进行操作。因此,我们将使用 Google Cloud Shell。Google Cloud Shell 直接从您的浏览器提供对云资源的命令行访问。您可以轻松管理项目和服务,而无需在系统中安装 Google Cloud SDK 或其他工具。使用 Cloud Shell,当您需要时,Cloud SDK 的 gcloud 命令行工具和其他必要的实用工具总是可用、更新并完全认证。
要启动 Cloud Shell,只需在控制窗口顶部单击“激活 Google Cloud Shell”按钮,如下面的截图所示:
Cloud Shell 会话在控制窗口底部的新的框架中打开,并显示一个命令行提示符。初始化 shell 会话可能需要几秒钟。现在,我们的 Cloud Shell 会话已准备好使用,如下面的截图所示:
到目前为止,我们需要将包含 Python 代码的 rnn_hwr.py 文件传输到 Google Cloud Platform。我们已经看到,为此,我们可以使用 Google Cloud Storage 提供的资源。然后我们打开 Google Cloud Storage 浏览器并创建一个新的存储桶。
要将 cnn_hwr.py 文件传输到 Google 存储,请按照以下步骤操作:
-
只需单击 CREATE BUCKET 图标
-
在创建存储桶窗口中输入新存储桶的名称(
rnn-hwr) -
之后,在存储桶列表中会出现一个新的存储桶
-
点击
rnn-hwr存储桶 -
点击打开的窗口中的“上传文件”图标
-
在弹出的对话框中选择文件
-
点击“打开”
在这一点上,我们的文件将出现在新的存储桶中,如下面的截图所示:
现在我们可以从 Cloud Shell 访问该文件。为此,我们在 shell 中创建一个新的文件夹。在 shell 提示符中输入以下命令:
mkdir RNN-HWR
现在,要将文件从 Google Storage 存储桶复制到CNN-HWR文件夹,只需在 shell 提示符中输入以下命令:
gsutil cp gs://rnn-hwr-mlengine/rnn_hwr.py RNN-HWR
显示以下代码:
giuseppe_ciaburro@progetto-1-191608:~$ gsutil cp gs://rnn-hwr/rnn_hwr.py RNN-HWR
Copying gs://rnn-hwr/rnn_hwr.py...
/ [1 files][ 4.0 KiB/ 4.0 KiB]
Operation completed over 1 objects/4.0 KiB.
现在,让我们进入文件夹并验证文件的存在:
$cd RNN-HWR
$ls
rnn_hwr.py
我们只需运行文件:
$ python rnn_hwr.py
显示一系列初步指令:
Extracting /tmp/data/train-images-idx3-ubyte.gz
Extracting /tmp/data/train-labels-idx1-ubyte.gz
Extracting /tmp/data/t10k-images-idx3-ubyte.gz
Extracting /tmp/data/t10k-labels-idx1-ubyte.gz
它们表明数据下载成功,TensorFlow 库的调用也成功。从这一点开始,网络的训练开始,正如我们预期的,可能相当长。算法执行结束后,将返回以下信息:
Step 1, Minibatch Loss= 2.9727, Training Accuracy= 0.117
Step 1000, Minibatch Loss= 1.8381, Training Accuracy= 0.430
Step 2000, Minibatch Loss= 1.4021, Training Accuracy= 0.602
Step 3000, Minibatch Loss= 1.1560, Training Accuracy= 0.672
Step 4000, Minibatch Loss= 0.9748, Training Accuracy= 0.727
Step 5000, Minibatch Loss= 0.8156, Training Accuracy= 0.750
Step 6000, Minibatch Loss= 0.7572, Training Accuracy= 0.758
Step 7000, Minibatch Loss= 0.5930, Training Accuracy= 0.812
Step 8000, Minibatch Loss= 0.5583, Training Accuracy= 0.805
Step 9000, Minibatch Loss= 0.4324, Training Accuracy= 0.914
Step 10000, Minibatch Loss= 0.4227, Training Accuracy= 0.844
Step 11000, Minibatch Loss= 0.2818, Training Accuracy= 0.906
Step 12000, Minibatch Loss= 0.3205, Training Accuracy= 0.922
Step 13000, Minibatch Loss= 0.4042, Training Accuracy= 0.891
Step 14000, Minibatch Loss= 0.2918, Training Accuracy= 0.914
Step 15000, Minibatch Loss= 0.1991, Training Accuracy= 0.938
Step 16000, Minibatch Loss= 0.2815, Training Accuracy= 0.930
Step 17000, Minibatch Loss= 0.1790, Training Accuracy= 0.953
Step 18000, Minibatch Loss= 0.2627, Training Accuracy= 0.906
Step 19000, Minibatch Loss= 0.1616, Training Accuracy= 0.945
Step 20000, Minibatch Loss= 0.1017, Training Accuracy= 0.992
Optimization Finished!
Testing Accuracy: 0.9765625
在这种情况下,我们在测试数据集上达到了97.6的准确率。
摘要
在本章中,我们通过添加功能来扩展标准神经网络的底层概念,以解决更复杂的问题。首先,我们发现了 CNN 的架构。CNN 是一种 ANN,其隐藏层通常由卷积层、池化层、全连接层和归一化层组成。CNN 的底层概念得到了覆盖。
我们通过分析一个真实案例来理解 CNN 的训练、测试和评估。为此,在 Google Cloud Platform 中解决了一个 HWR 问题。
然后,我们探讨了 RNN。循环神经网络不仅接受当前输入数据作为网络输入,还接受它们在一段时间内经历过的数据。分析了几个 RNN 架构。特别是,我们专注于 LSTM 网络。
第十四章:使用 LSTM 的时间序列
在涉及多个现实生活领域的许多情况下,需要规划未来的行动。预测是有效规划的重要工具。此外,这个工具使决策者对意外事件的影响降低,因为它要求对操作环境的知识采取更科学的方法。通常,未来行动的规划源于对随时间积累的数据的分析,以提取对观察现象特征化的信息。
事件的按时间顺序记录产生了新的行为,这恰好被称为时间序列。时间序列是由在连续瞬间或时间间隔内对现象进行的观察序列组成的。通常,即使不是必然的,它们也是均匀分布的或长度相同。时间序列预测需要神经网络对数据序列有一定的记忆。称为长短期记忆(LSTM)的特定架构非常适合时间序列分析。在本章中,我们将展示如何使用 Keras 在 GCD 上创建和训练自己的 LSTM,并将它们应用于预测金融时间序列。我们将发现最常用的建模方法:自回归(AR)、移动平均(MA)、自回归移动平均(ARMA)和自回归积分移动平均(ARIMA)。
本章涵盖的主题包括:
-
时间序列的经典方法
-
时间序列分解
-
时间序列模型
-
LSTM 用于时间序列分析
在本章结束时,我们将能够处理与时间序列相关的问题。我们将了解如何识别时间序列的不同组成部分,包括趋势、季节性和残差,以及消除季节性以使预测更容易理解。最后,我们将通过一个实际例子了解如何实现具有重复功能的 LSTM 网络。
介绍时间序列
时间序列是由在连续瞬间或时间间隔内对现象 y 进行的观察序列组成的,这些瞬间或时间间隔通常是均匀分布的,即使不是必然的,长度也相同。商品价格趋势、股票市场指数、BTP/BUND 利差和失业率只是时间序列的几个例子。
与经典统计学中假设独立观察来自单个随机变量相反,在时间序列中,假设有 n 个观察来自许多相关随机变量。因此,时间序列的推断被配置为一个试图将时间序列恢复到其生成过程的程序。
时间序列可以分为两种类型:
-
确定性:如果变量的值可以根据先前值精确确定
-
随机性:如果变量的值只能根据先前值部分确定
大多数时间序列都是随机的,因此没有错误地绘制预测是不可能的。通常假设观察到的时序是这两个组成部分组成的。这两个序列不能单独观察,但必须基于样本来确定。
我们将这个序列表示为这两个贡献的总和:
Y[t] = f(t) + w(t)
根据时间序列的经典方法,假设存在现象的时间演化规律,由*f(t)表示。随机成分w(t)被假设代表我们不想或不能在Y[t]*中考虑的、每个都微不足道的情形集合。
因此,*Y[t]的残差部分,即未被f(t)解释的部分,被归因于偶然误差的情况,并同化为一系列偶然误差。这相当于假设随机成分w(t)是由一个白噪声过程生成的,即由一系列独立同分布的随机变量序列,均值为零,方差恒定。总之,在经典方法中,注意力集中在f(t)上,而w(t)*被认为是一个具有不相关成分的过程,因此可以忽略不计。
用t = 1…. T表示时间,我们将指示这个序列y[t];时间是决定事件序列的参数,不能被忽略,因此我们还需要知道观察在时间维度上的位置。通常,它用于在笛卡尔图上表示值对*(t, y[t])*,以连续线图的形式,好像现象是连续检测到的。这种图表称为时间序列图。在下面的图表中,我们看到的是从 1871 年到 1970 年阿斯旺尼罗河流量的时间序列图:
时间序列图可以立即揭示趋势或规律性振荡,以及随时间出现的其他系统性趋势。前一个图表显示了长期内系统性地下降的年度数据。特别是,它有一个锯齿状模式;由于数据是按月度计算的,存在称为季节性的现象。可以注意到,在预期有雨的那些月份总是记录到高峰值。
时间序列的单变量分析旨在解释生成序列的动态机制,并预测现象的未来实现。在这些操作中,所利用的信息仅限于*(t; Y[t])这对,其中t = 1,…, T*。基本点是过去和现在都包含与预测现象未来演化相关的信息。
可以认为单变量分析过于限制;我们通常有与要预测的现象相关的信息,这些信息应该适当融入,以提高修订模型的性能。尽管如此,它是一个有用的基准,允许验证更复杂的替代方案。
在时间序列图中,可以根据时间识别出四种类型的模式:
-
水平模式:在这种情况下,系列围绕一个常数值(系列平均值)波动。这种系列在平均意义上被称为平稳的。这是在质量控制中,当过程相对于平均值保持控制时出现的典型情况。
-
季节性模式:当系列受季节性因素影响时存在(例如,每月、半年、季度等)。冰淇淋、软饮料、电力消耗等产品都受到季节性现象的影响。受季节性影响的系列也被称为周期性系列,因为季节周期在固定周期内重复。在年度数据中,季节性不存在。
-
周期模式:当系列有非固定周期的增加和减少时,这种趋势类型就会出现。这是周期性和季节性波动之间的主要区别。此外,周期性振荡的幅度通常大于季节性引起的幅度。在经济系列中,周期模式由经济因猜测现象的扩张和收缩所决定。
-
趋势或潜在趋势:它以长期增加或减少的趋势为特征。世界居民人口系列就是一个增加趋势的例子;另一方面,月度啤酒销售系列没有显示出任何趋势。它有一个水平的背景模式。
许多系列强调了这些模式的组合。正是这种复杂性使得预测操作极其有趣。实际上,预测方法必须能够识别系列的各个组成部分,以便在未来的假设下,在它们的进化特征中继续重复过去的模式。
时间序列的经典方法基于将系列的确定性部分分解成一系列信号成分(这些成分表达系列的结构性信息)相对于可忽略的噪声部分。在实践中,我们将尝试在时间序列趋势中识别我们之前列出的一些模式。以下图显示了一个时间序列,其中已识别出一些成分:
在之前的图中,我们已识别的组成部分是:
-
趋势:这是所考虑现象的潜在趋势,指的是一个长期的时间段。
-
季节性:这包括现象在一年中的运动。由于气候和社会因素的影响,它们往往在相同时期(例如,月份、季度等)以类似的方式重复出现。
-
残差:在时间序列模型中,观察变量与不同成分之间从未有过完美的关系。偶然成分考虑了这一点,以及经济主体、社会等方面的不可预测行为。
最后,我们可以说,采用这种方法,时间序列可以被视为分析过的三个成分的总和(加法方法)。
时间序列的经典方法
到目前为止,我们根据经典方法处理时间序列。在这个视角下,试图模拟现象的经典模型可以分为两种类型:
-
组合模型:已知基本组成部分,通过假设某种聚合形式,得到结果序列
-
分解模型:从观察序列中假设存在一些基本趋势,我们想要确定其特征
分解模型在实践中最常使用,因此我们将详细分析它们。
时间序列的成分可以根据不同类型的方法进行聚合:
-
加法方法:Y(t) = τ(t) + C(t) + S(t) + r(t)
-
乘法方法:Y(t) = τ(t) * C(t) * S(t) * r(t)
-
混合方法:Y(t) = τ(t) * C(t) + S(t) * r(t)
在这些公式中,因子定义为以下:
-
Y(t) 代表时间序列
-
τ(t) 代表趋势成分
-
C(t) 代表周期成分
-
S(t) 代表季节性成分
-
r(t) 代表残差成分
通过对时间序列成分的对数变换,乘法模型可以追溯到加法模型:
Y(t) = τ(t) * C(t) * S(t) * r(t)
通过对所有因子应用对数函数,这个公式变为:
lnY(t) = lnτ(t) + lnC(t) + lnS(t) + lnr(t)
趋势成分的估计
趋势成分的估计可以有两种不同的模式,这取决于线性/非线性特征。
如果序列趋势在参数上通过对数变换是线性的或可线性化的,那么这些趋势可以通过从线性回归中推导出的程序进行估计。我们可以假设一个可以用以下方程表示的多项式趋势:
τ (t) = α[0] + α[1] t + α[2] t[2] + ... + α[q] t[q] + εt
在这个公式中,q 代表多项式的次数。
根据假设的 q 的值,可以表示以下情况:
| q | 情况 |
|---|---|
| 0 | 获得一个常数趋势 |
| 1 | 我们得到一个线性趋势 |
| 2 | 我们得到一个抛物线趋势 |
相反,非线性行为的存在使得难以,如果不是不可能的话,识别出一个已知的函数形式 f(t) 来表达趋势成分。
在这些情况下,使用 MA 工具。MA 是一个移动到每个新迭代(在任何时间 t)从数据序列的开始到结束的算术平均值(简单或加权)。
假设我们有 n 个数据项:
a1, a2, a3, ..., a^((n-1)), a^n
以下程序被采用:
-
首先,我们计算前三个数据的平均值,并用平均值替换中间数据
-
然后,我们用第二组三个数据重复该程序
-
当没有更多数据可用时,程序将耗尽
在考虑的情况下,MA 由仅三个数据组成。MA 的阶数可以扩展到 5,7,9,等等。为了使 MA 与可用数据居中,阶数必须是奇数。
估计季节性成分
研究历史序列的季节性可以有以下目的:
-
简单地估计季节性成分
-
一旦估计出来,就从一般课程中消除它
如果必须比较具有不同季节性的几个时间序列,唯一的方法是对它们进行季节性调整。
有几种方法可以估计季节性成分。其中之一是使用二元辅助变量(虚拟变量)的回归模型。
假设存在一个没有趋势成分的加性模型:
Y(t) = S(t) + r(t)
假设我们按月度测量了该序列。虚拟变量可以按以下方式定义:
-
dj:如果观察值 t 是相对于年份的第 j 个月,则为 1
-
dj:否则为 0
一旦创建了周期性虚拟变量,就可以使用以下回归模型来估计季节性成分:
Y(t) = β[1]D[1] + β[2]D[2] + ... + β[n]D[n] + ε(t)
模型中剩余的 ε(t) 部分代表序列中未被季节性解释的部分。如果序列中存在趋势成分,它将与 ε(t) 完全一致。
时间序列模型
在前面的章节中,我们探讨了时间序列的基本原理。为了根据过去发生的事件正确预测未来事件,有必要构建一个适当的数值模拟模型。选择一个合适的模型非常重要,因为它反映了序列的潜在结构。在实践中,有两种类型的模型可用:线性或非线性(取决于序列的当前值是否是过去观察值的线性或非线性函数)。
以下是最广泛用于预测时间序列数据的模型:
-
AR
-
MA
-
ARMA
-
ARIMA
自回归模型
AR 模型是解决与时间序列相关的预测问题的非常有用的工具。一个序列连续值之间的强相关性通常会被观察到。在这种情况下,当我们考虑相邻值时,我们谈论一阶自相关;如果我们指的是两个周期后序列值之间的关系,我们谈论二阶自相关;在一般情况下,如果考虑的值之间有 p 个周期,我们谈论 p 阶自相关。AR 模型允许利用这些联系来获得序列未来行为的有用预测。
AR 是一种线性预测建模技术。该模型试图根据使用 AR 参数作为系数的先前值来预测时间序列。用于预测的样本数量决定了模型的阶数(p)。正如其名称所示,这是一个变量的自回归;也就是说,使用变量的过去值的线性组合来预测未来值。阶数为 p 的 AR 模型定义为:
在前面的公式中,术语定义如下:
-
Y[t] 是时间周期 t 的实际值
-
c 是一个常数
-
ϕ[i] (i = 1,2,..., p) 是模型参数
-
Y[t-i] 是时间周期 t-i 的过去值
-
ε[t] 是时间周期 t 的随机误差(白噪声)
可能会发生常数项被省略的情况;这样做是为了使模型尽可能简单。
移动平均模型
MA 模型规定输出变量线性依赖于随机项(不可完全预测)的过去和当前过去值。MA 模型不应与我们在前几节中看到的 MA 混淆。这是一个本质上不同的概念,尽管有一些相似之处。与 AR 模型不同,完成的 MA 模型总是平稳的。
正如 AR (p) 模型相对于序列的过去值进行回归一样,MA (q) 模型使用过去误差作为解释变量。
阶数为 q 的 MA 模型定义为:
在前面的公式中,术语定义如下:
-
Y[t] 是时间周期 t 的实际值
-
μ 是序列的均值
-
θ[i] (i = 1,2,..., q) 是模型参数
-
ε[t-i] 是时间周期 t-i 的过去随机误差
-
ε[t] 是时间周期 t 的随机误差(白噪声)
MA 模型本质上是对白噪声应用有限脉冲响应滤波器,并对它附加了一些额外的解释。
自回归移动平均模型
ARMA 是一种线性数学模型,它基于先前输入和输出值即时提供输出值。该系统被视为一个实体,它即时接收输入值(输入)并生成输出(输出),基于内部参数,这些参数根据线性规律变化。因此,每个内部参数在每个瞬间等于前一个瞬间的所有内部参数的线性组合和输入值。输出值反过来又将是内部参数的线性组合,在罕见情况下,也可能是输入值。
更简单地说,ARMA 可以看作是 AR 和 MA 模型的有效组合,形成了一类通用且有用的时序模型。
该模型通常定义为 ARMA 模型 (p, q),其中 p 是自回归部分的阶数,q 是移动平均部分的阶数。ARMA 模型由以下公式定义:
术语定义如下:
-
Y[t] 是时间周期 t 的实际值
-
c 仍然是一个常数
-
ϕ[i] (i = 1,2,..., p) 是自回归模型参数
-
Y[t-i] 是时间周期 t-i 的过去值
-
θ[i] (i = 1,2,..., q) 是移动平均模型参数
-
ε[t-i] 是时间周期 t-i 的过去随机误差
-
ε[t] 是时间周期 t 的随机误差(白噪声)
通常,一旦选择了阶数 (p, q),ARMA 模型 (p, q) 的参数可以通过最大似然估计器进行估计,例如。至于 AR 模型,模型阶数的选择必须满足对数据良好适应和参数估计数量简约性的对立需求。
自回归积分移动平均模型
ARIMA 模型是 ARMA 模型的一种推广。ARIMA 模型适用于数据表现出明显的非平稳趋势的情况。在这些情况下,为了消除非平稳性,在 ARMA 算法(对应于模型的积分部分)中添加了一个初始微分步骤,该步骤应用一次或多次。
因此,该算法本质上由三个部分组成:
-
自回归部分决定了对其自身延迟(即先前)值进行回归的演变变量。
-
MA 部分。它表示回归误差实际上是过去同时发生并在不同时间出现的误差项的线性组合。
-
积分部分;它表示数据值已被替换为它们当前值与先前值之间的差值(并且这种微分过程可能已经执行过多次)。
每个这些特征的目的都是为了以最佳方式使模型适合数据。
为了制定 ARIMA 模型的代表性方程,我们从 ARMA 模型方程开始:
简单地将 AR 部分移到方程的右侧,得到以下方程(小于常数 c):
通过引入滞后算子 (L),我们可以将这个方程重写如下:
记住:滞后算子 (L) 对时间序列的一个元素进行操作,以产生前一个元素,其含义是 LY[t] = Y[t-1]。
假设如下:
这精确地表达了之前为了消除非平稳性而进行的 d 阶因式分解过程。基于这个假设并设置 p = p'-d,我们可以写出以下方程来表示 ARIMA (p,d,q) 模型的数学公式,使用滞后多项式:
d 参数控制着区分的程度。通常情况下,d=1 就足够了。
从时间序列中去除季节性
在经济和金融分析中,这些分析通常基于众多指标,使用季节性调整形式(即净去季节性波动)的数据被广泛使用,以便更清楚地把握所考虑现象的短期演变。
在时间序列的动态中,季节性是每年以固定间隔重复出现的成分,在连续年份的同一时期(月份、季度、学期等)中强度变化或多或少相似;在同年中强度不同。这种类型的典型例子是 8 月份在许多公司假期关闭后工业生产的下降,以及 12 月份由于假日季节零售销售的上升。
季节性波动,伪装其他感兴趣的运动(通常是周期性波动),在分析经济周期时通常被视为一种干扰。季节性的存在会在例如分析历史序列中两个连续时期(月份和季度)之间观察到的变化时造成问题——所谓的经济波动。这些波动通常在很大程度上受到季节性波动的影响,而不是其他原因(例如,经济周期)的影响。另一方面,可以通过在季节性调整后的数据上计算经济波动来正确地突出显示后者。此外,由于每个时间序列都有其特定的季节性特征,使用季节性调整后的数据使得比较不同时间序列的演变成为可能,并且在联合使用不同国家产生的统计数据时得到了广泛应用。
分析时间序列数据集
要了解如何在时间序列上执行季节性移除操作,我们将使用关于月度牛奶产量(每头牛的磅数;1962 年 1 月 – 1975 年 12 月)的数据集。以下是关于此数据集的一些有用信息:
-
单位:每头牛的磅数
-
数据集度量:一个时间序列中的 168 个事实值
-
时间粒度:月份
-
时间范围:1962 年 1 月 – 1975 年 12 月
-
来源:时间序列数据库
时间序列数据库(TSDL)是由澳大利亚莫纳什大学统计学教授 Rob Hyndman 创建的。
数据存储在一个名为 milk-production-pounds.csv 的 .csv 文件中。首先,让我们看看如何将数据导入 Python,然后如何显示它以识别可能存在的季节性。首先要做的事情是导入我们将要使用的库:
import pandas as pd
import matplotlib.pyplot as plt
在第一行中,我们导入了 pandas 库,在第二行中,我们导入了来自 matplotlib 库的 pyplot 模块。
pandas 是一个开源的 BSD 许可库,为 Python 编程语言提供高性能、易于使用的数据结构和数据分析工具。特别是,它提供了用于操作数值表和时间序列的数据结构和操作。
Matplotlib 是一个 Python 2D 绘图库,可以在各种硬拷贝格式和跨平台的交互式环境中生成出版物质量的图形。Matplotlib 可以用于 Python 脚本、Python 和 IPython Shell、Jupyter Notebook、Web 应用服务器以及四个图形用户界面工具包。matplotlib.pyplot 模块包含允许您快速生成许多类型图表的函数。现在让我们看看如何在 Python 中导入数据集中包含的数据:
data = pd.read_csv('milk-production-pounds.csv',
parse_dates=True,index_col='DateTime',
names=['DateTime', 'Milk'], header=None)
要导入数据集,我们使用了 pandas 库的 read_csv 模块。read_csv 方法将数据加载到我们命名为 data 的 Pandas DataFrame 中。为了显示视频导入的 DataFrame 的前五行,我们可以使用 head() 函数如下:
print(data.head())
以下结果返回:
DateTime Milk
1962-01-01 589
1962-02-01 561
1962-03-01 640
1962-04-01 656
1962-05-01 727
head() 函数不带任何参数时,从 DataFrame 中获取前五行数据。现在时间序列已在我们的 Python 环境中可用;为了预览其中包含的数据,我们可以计算一系列基本统计量。为此,我们将使用以下方式的 describe() 函数:
print(data.describe())
以下结果返回:
Milk
count 168.000000
mean 754.708333
std 102.204524
min 553.000000
25% 677.750000
50% 761.000000
75% 824.500000
max 969.000000
describe() 函数生成描述性统计量,用于总结数据集分布的中心趋势、离散程度和形状,排除 NaN 值。它分析数值和对象序列,以及混合数据类型的 DataFrame 列集。输出将根据提供的内容而变化。为了提取更多信息,我们可以按照以下方式调用 info() 函数:
print(data.info())
以下结果返回:
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 168 entries, 1962-01-01 to 1975-12-01
Data columns (total 1 columns):
Milk 168 non-null int64
dtypes: int64(1)
memory usage: 2.6 KB
None
在查看数据集的内容之后,我们将进行初步的视觉探索性分析。Pandas 内置了相对广泛的绘图功能,可用于探索性图表——特别是在数据分析中非常有用。Pandas 的 .plot() 命令本身提供了大量的功能:
data.plot()
plt.show()
data.plot() 命令使用 matplotlib/pylab 绘制 DataFrame 的图表。为了在视频中显示刚刚创建的图表,我们必须使用 plt.show() 函数,如下面的图表所示:
从前一个图表的分析中,我们可以肯定地认识到牛奶产量正在增长(我们注意到一个正向趋势),但同时也表现出一定的可变性(围绕假设趋势线的波动)。这种状态几乎随着时间的推移而持续不变。
识别时间序列中的趋势
如果我们想要尝试预测下一个月的牛奶产量,我们可以这样思考:有了获取到的数据,我们可以追踪趋势线并将其延伸到下一个月。这样,我们就可以对即将到来的未来牛奶产量有一个粗略的估计。
但追踪趋势线意味着追踪回归线。线性回归方法包括精确地确定一条能够代表二维平面上点分布的线。正如容易想象的那样,如果对应于观察点的点靠近这条线,那么选定的模型将能够有效地描述变量之间的联系。在理论上,有无限多条可能近似观察点的线。在实践中,只有一个数学模型能够优化数据的表示。
要拟合线性回归模型,我们首先需要导入两个额外的库:
import numpy
from sklearn.linear_model import LinearRegression
NumPy 是 Python 科学计算的基础包。它包含,但不仅限于:
-
一个强大的 N 维数组对象
-
复杂的(广播)功能
-
用于集成 C/C++ 和 FORTRAN 代码的工具
-
有用的线性代数、傅里叶变换和随机数功能
除了其明显的科学用途外,NumPy 还可以用作高效的多维通用数据容器。可以定义任意数据类型。这使得 NumPy 能够无缝且快速地与各种数据库集成。
sklearn 是一个用于 Python 编程语言的免费软件机器学习库。它包含各种分类、回归和聚类算法,包括支持向量机、随机森林、梯度提升、k-means 和 DBSCAN,并且设计为与 Python 的数值和科学库 NumPy 和 SciPy 兼容。
记住,要导入 Python 初始分布中不存在的库,你必须使用pip install命令后跟库的名称。这个命令应该只使用一次,而不是每次运行代码时都使用。
我们开始准备数据:
X = [i for i in range(0, len(data))]
X = numpy.reshape(X, (len(X), 1))
y = data.values
首先,我们统计了数据;然后我们使用reshape()函数给一个数组赋予新的形状而不改变其数据。最后,我们将时间序列值插入到y变量中。现在我们可以构建线性回归模型:
LModel = LinearRegression()
LinearRegression()函数执行普通最小二乘线性回归。普通最小二乘法是一种优化技术(或回归),它允许我们找到一个函数,该函数由一个最优曲线(或回归曲线)表示,该曲线尽可能接近一组数据。特别是,找到的函数必须是最小化观测数据与代表该函数本身的曲线之间的距离平方和的函数。
给定观察群体中的 n 个点(x[1], y[1]), (x[2], y[2]), ... (x[n], y[n]),最小二乘回归线定义为方程线:
y=αx+β*
对于以下量是最小的:
这个量表示每个实验数据(x[i], y[i])到对应直线上的点(x[i], αx[i]+β)的距离平方和,如下所示:
现在,我们必须应用fit方法来拟合线性模型:
LModel.fit(X, y)
线性回归模型基本上找到截距和斜率的最佳值,从而得到一条最佳拟合数据的直线。为了查看线性回归算法为我们数据集计算出的截距和斜率的值,执行以下代码:
print(LModel.intercept_,LModel.coef_)
返回以下结果:
[613.37496478] [[1.69261519]]
第一个是截距;第二个是回归线的系数。现在我们已经训练了我们的算法,是时候进行一些预测了。为了做到这一点,我们将使用全部数据并查看我们的算法预测百分比分数的准确性。记住,我们的目标是定位时间序列趋势。要对全部数据进行预测,执行以下代码:
trend = LModel.predict(X)
是时候可视化我们所取得的成果了:
plt.plot(y)
plt.plot(trend)
plt.show()
使用这段代码,我们首先追踪了时间序列。因此,我们添加了代表数据趋势的回归线,最后我们将整个图表打印出来,如下所示:
我们回顾一下,这代表了一种长期单调趋势运动,突出了由于在相同方面系统作用的原因而产生的现象的结构演变。从前图的分析中,我们可以注意到这一点:基于表示时间序列趋势的线条对精确时期牛奶产量进行估计,在某些情况下可能是灾难性的。这是因为季节性高点和低点与回归线的距离很重要。很明显,不能使用这条线来估计牛奶产量。
时间序列分解
时间序列经典分析的一个基本目的是将序列分解为其组成部分,以便更好地研究它们。此外,为了能够将随机方法应用于时间序列,通常几乎总是需要消除趋势和季节性,以获得稳定的过程。正如我们在前面的章节中指定的那样,时间序列的组成部分通常是以下这些:趋势、季节性、周期和残差。
如前所述,它们可以通过加法方式分解:
Y(t) = τ(t) + S(t) + r(t)
它们也可以通过乘法方法分解:
Y(t) = τ(t) * S(t) * r(t)
在接下来的章节中,我们将探讨如何使用这两种方法推导出这些成分。
加法方法
要进行时间序列分解,我们可以使用自动化程序。stats模型库提供了一个名为seasonal_decompose()的函数,实现了朴素或经典分解方法的实现。加法或乘法方法都是可用的。
我们开始导入stats模型库:
from statsmodels.tsa.seasonal import seasonal_decompose
尤其是我们导入了seasonal_decompose模块,使用移动平均(MAs)进行季节分解。我们通过应用加法方法进行分解:
DecompDataAdd = seasonal_decompose(data, model='additive', freq=1)
通过对数据进行卷积滤波器处理,首先移除季节成分。每个周期的平滑序列的平均值即为返回的季节成分。让我们通过识别成分的可视化来看看发生了什么:
DecompDataAdd.plot()
plt.show()
下图显示了加法方法的分解结果:
在此图中,时间序列的三个成分被清晰地表示出来:趋势、季节和残差。这些属性包含在seasonal_decompose()方法返回的对象中。这意味着我们可以使用该对象的内容来从时间序列中去除季节性影响。让我们看看如何:
SeasRemov= data-DecompDataAdd.seasonal
通过这一行代码,我们已经简化了seasonal_decompose()方法从数据返回的季节性属性。此时,我们只需可视化结果:
SeasRemov.plot()
plt.show()
下图显示了去除季节性后的月度牛奶产量(1962 年 1 月至 1975 年 12 月每头牛的磅数):
在获得的图表中,由于季节性而产生的成分已被清楚地去除,而由于趋势而产生的成分则清晰可见。
乘法方法
正如我们所说的,seasonal_decompose()执行加法和乘法分解。要运行乘法方法,只需键入以下命令:
DecompDataMult = seasonal_decompose(data, model='multiplicative')
到目前为止,我们只需可视化结果:
DecompDataMult.plot()
plt.show()
下面的图表显示了乘法方法的分解结果:
在上一幅图中,我们可以注意到从时间序列中提取的趋势和季节性信息似乎相当合理。残差显示出有趣的变异;在时间序列的早期和后期,高变异性时期被清楚地识别出来。
用于时间序列分析的 LSTM
LSTM 是一种由 Hochreiter 和 Schmidhuber 在 1997 年最初构思的循环神经网络特定架构。这种类型的神经网络最近在深度学习背景下被重新发现,因为它摆脱了梯度消失的问题,并且在实践中提供了出色的结果和性能。
基于 LSTM 的网络非常适合时间序列的预测和分类,并且正在取代许多经典的机器学习方法。这是因为 LSTM 网络能够考虑数据之间的长期依赖关系,在语音识别的情况下,这意味着管理句子中的上下文以提高识别能力。
时间序列数据集概述
来自美国国家海洋和大气管理局(NOAA)的科学家们从 1965 年到 1980 年在夏威夷莫纳罗亚火山锥顶部(顶部)测量了大气二氧化碳。该数据集覆盖了 317.25 到 341.19 百万分之一(ppm)的二氧化碳浓度,并包含 192 个月的记录。以下是关于此数据集的一些有用信息:
-
单位:ppm
-
数据集指标:一个时间序列中有 192 个事实值
-
时间粒度:月
-
时间范围:1965 年 1 月-1980 年 12 月
来源:TSDL,由澳大利亚莫纳什大学统计学教授 Rob Hyndman 创建。
数据可在名为co2-ppm-mauna-loa-19651980.csv的.csv文件中找到。首先,让我们看看如何将数据导入 Python,然后如何显示它们以识别可能存在的季节性。首先要做的事情是导入我们将要使用的库:
import pandas as pd
import matplotlib.pyplot as plt
在第一行中,我们导入了pandas,在第二行中,我们导入了来自matplotlib库的pyplot模块。现在让我们看看如何在 Python 中导入数据集中的数据:
dataset = pd.read_csv(' co2-ppm-mauna-loa-19651980.csv',
parse_dates=True,index_col='DateTime',
names=['DateTime', 'CO2'], header=None)
要导入数据集,我们使用了pandas库的read_csv模块。read_csv方法将数据加载到我们命名为dataset的 Pandas DataFrame 中。为了在视频中显示导入的 DataFrame 的前五行,我们可以使用以下head()函数:
print(dataset.head())
返回以下结果:
DateTime CO2
1965-01-01 319.32
1965-02-01 320.36
1965-03-01 320.82
1965-04-01 322.06
1965-05-01 322.17
head()函数不带参数时,从 DataFrame 中获取前五行数据。现在时间序列已经存在于我们的 Python 环境中;为了预览其中的数据,我们可以计算一系列基本统计信息。为此,我们将使用以下方式的describe()函数:
print(dataset.describe())
返回以下结果:
CO2
count 192.000000
mean 328.463958
std 5.962682
min 317.250000
25% 323.397500
50% 328.295000
75% 333.095000
max 341.190000
describe()函数生成描述性统计信息,总结数据集分布的中心趋势、离散度和形状,排除 NaN 值。它分析数值和对象序列,以及混合数据类型的 DataFrame 列集。输出将根据提供的内容而变化。为了提取更多信息,我们可以调用info()函数:
print(data.info())
返回以下结果:
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 192 entries, 1965-01-01 to 1980-12-01
Data columns (total 1 columns):
CO2 192 non-null float64
dtypes: float64(1)
memory usage: 3.0 KB
在查看数据集的内容之后,我们将进行初步的视觉探索性分析。Pandas 内置了相对广泛的绘图功能,可用于探索性图表;这在数据分析中特别有用。Pandas 的.plot()命令提供了大量的功能:
dataset.plot()
plt.show()
dataset.plot()命令使用matplotlib/pylab绘制 DataFrame 的图表。为了在视频中显示刚刚创建的图表,我们必须使用plt.show()函数,如下面的图表所示:
从前一个图的分析中,我们可以肯定地认识到大气二氧化碳正在增长。我们注意到一个正向趋势。但这也表明了一定的可变性(围绕假设趋势线的振荡),这种可变性几乎随着时间的推移而持续保持。
数据缩放
数据缩放是一种预处理技术,通常在特征选择和分类之前使用。许多基于人工智能的系统使用由许多不同的特征提取算法生成的特征,这些算法来自不同的来源。这些特征可能具有不同的动态范围。
此外,在具有大量特征和较大动态范围的数据挖掘应用中,特征缩放可能会提高拟合模型的性能。然而,选择这些技术是重要的问题,因为对输入数据进行缩放可能会改变数据的结构,从而影响数据挖掘中使用的多元分析结果。
为了对数据进行缩放,我们将使用最小-最大归一化(通常称为特征缩放);它对原始数据进行线性变换。这种技术将所有缩放数据都转换到(0,1)的范围内。实现这一目标的公式是:
最小-最大归一化保留了原始数据值之间的关系。这种有界范围的代价是我们最终会得到较小的标准差,这可能会抑制异常值的影响。
为了执行最小-最大归一化,我们将使用sklearn.preprocessing类的MinMaxScaler()模块。此模块通过将每个特征缩放到给定范围来转换特征。这个估计器将每个特征单独缩放和转换,使其在训练集上位于给定的范围内,即零到一之间。以下代码显示了如何将此模块应用于我们的数据:
scaler = MinMaxScaler()
dataset = scaler.fit_transform(dataset)
首先,我们使用了MinMaxScaler()函数来设置归一化区间(默认为(0,1))。在代码的第二行中,我们应用了fit_transform()函数;它将转换器拟合到数据集,并返回数据的转换版本。这个函数特别有用,因为它存储了使用的转换参数。这些参数在做出预测后,我们将需要将这些数据以初始形式(归一化之前)报告出来,以便与实际数据进行比较。
数据拆分
现在我们来拆分用于训练和测试模型的数据。训练和测试模型是进一步使用模型进行预测分析的基础。给定 192 行数据的数据集,我们将其拆分为一个方便的比例(比如说 70:30),并将 134 行分配给训练,58 行分配给测试。
通常,在基于人工神经网络的算法中,拆分是通过随机选择行来进行的,以减少偏差。对于时间序列数据,值的顺序很重要,因此这个程序不可行。我们可以使用的一个简单方法是按照顺序将数据集分为训练集和测试集。正如我们所预期的,以下代码计算了分割点索引,并在训练数据集中分离数据,其中 70%的观测值用于训练我们的模型;这留下了剩余的 30%用于测试模型:
train_len = int(len(dataset) * 0.70)
test_len = len(dataset) - train_len
train = dataset[0:train_len,:]
test = dataset[train_len:len(dataset),:]
代码的前两行设置了两组数据长度。接下来的两行将数据集分为两部分:从第 1 行到train_len -1行用于训练集,从train_len行到最后一行用于测试集。为了确认数据的正确拆分,我们可以打印两个数据集的长度:
print(len(train), len(test))
这给出了以下结果:
134 58
正如我们所预期的,这个操作将数据集分为134行(训练集)和58行(测试集)。
构建模型
我们的目标是利用数据集中的数据来进行预测。具体来说,我们希望根据.csv文件中可用的数据预测空气中二氧化碳的存在。我们需要输入和输出数据来训练和测试我们的网络。很明显,输入由数据集中存在的数据表示。然后我们必须构建我们的输出;我们将通过假设我们想要预测时间t + 1时大气中存在的 CO2 相对于时间t时测量的值来做到这一点。所以我们将有:
输入 = data(t)
输出 = data(t + 1)
我们已经说过,循环网络具有记忆功能,并且通过固定所谓的时间步来维持。时间步与在训练期间计算权重更新梯度的反向传播中回溯的时间步数有关。这样,我们设置 时间步 = 1。然后我们定义一个函数,它接受一个数据集和一个时间步返回输入和输出数据:
def dataset_creating(dataset):
Xdata, Ydata = [], []
for i in range(len(dataset)-1):
Xdata.append(dataset[i, 0])
Ydata.append(dataset[i + 1, 0])
return numpy.array(Xdata), numpy.array(Ydata)
在此函数中,Xdata=Input= data(t) 是输入变量,Ydata=output= data(t + 1) 是下一个时间段的预测值。让我们使用这个函数来设置下一阶段(网络建模)中我们将使用的训练和测试数据集:
trainX, trainY = create_dataset(train)
testX, testY = create_dataset(test)
以这种方式,我们创建了网络训练和测试所需的所有数据。此函数将值数组转换为数据集矩阵。现在我们必须准备两个输入数据集(trainX 和 testX),以符合我们打算使用的机器学习算法(LSTM)所需的形式。为此,有必要深化这一概念。
在一个经典的正向传播网络中,如前几章已分析的,输入包含每个观测变量所假设的值。这意味着输入具有以下形状:
(观测数量,特征数量)
在 LSTM/RNN 网络中,每个 LSTM 层的输入必须包含以下信息:
-
观测:收集到的观测数量
-
时间步:样本中的一个观测点
-
特征:每个步骤一个特征
因此,有必要为那些经典网络预见的添加一个时间维度。因此,输入形状变为:
(观测数量,时间步数,每步特征数量)
以这种方式,每个 LSTM 层的输入变为三维。为了将输入数据集转换为 3D 形式,我们将使用以下 numpy.reshape() 函数:
trainX = numpy.reshape(trainX, (trainX.shape[0], 1, 1))
testX = numpy.reshape(testX, (testX.shape[0], 1, 1))
numpy.reshape() 函数在不改变其数据的情况下,为数组赋予新的形状。所使用的函数参数包括:
-
trainX,testX:需要重塑的数组 -
(trainX.shape[0], 1, 1),(testX.shape[0], 1, 1):新形状
新形状应该与原始形状兼容。在我们的例子中,新形状是 trainX 的 (133,1,1) 和 testX 的 (57,1,1)。现在数据已经以正确的格式,是时候创建模型了:
timesteps = 1
model = Sequential()
我们开始定义时间步;然后我们使用一个顺序模型,即层的线性堆叠。要创建一个顺序模型,我们必须将层实例的列表传递给构造函数。我们也可以通过 .add() 方法简单地添加层:
model.add(LSTM(4, input_shape=(1, timesteps)))
model.add(Dense(1))
第一层是一个 LSTM 层,包含四个 LSTM 块的隐藏层。模型需要知道它应该期望的输入形状。因此,我们向这个层传递了一个 input_shape 参数。在下一行,我们添加了一个实现默认 sigmoid 激活函数的密集层。现在,我们必须为训练配置模型:
model.compile(loss='mean_squared_error', optimizer='adam')
为了做到这一点,我们使用了编译模块。传递的参数是一个损失函数mean_squared_error和随机梯度下降optimizer。最后,我们可以拟合模型:
model.fit(trainX, trainY, epochs=1000, batch_size=1, verbose=2)
在训练阶段,使用trainX和trainY数据,共 1,000 个 epoch(在训练集上的完整训练周期)。传递一个批大小为 1(batch_size = 每个梯度更新中的样本数)。最后verbose=2(verbose 参数提供了关于计算机正在做什么的额外细节)打印出每个 epoch 的损失值。
进行预测
我们的模式现在已准备好使用。因此,我们可以用它来执行我们的预测:
trainPred = model.predict(trainX)
testPred = model.predict(testX)
使用了predict()模块,它为输入样本生成输出预测。计算是在批处理中完成的。返回一个预测的 Numpy 数组。之前,当执行数据缩放时,我们使用了fit_transform()函数。正如我们所说的,这个函数特别有用,因为它存储了使用的转换参数。这些参数在做出预测后,当我们必须将数据报告为初始形式(在归一化之前),以便与实际数据进行比较时将是有用的。事实上,现在必须以原始形式报告预测,以便与实际值进行比较:
trainPred = scaler.inverse_transform(trainPred)
trainY = scaler.inverse_transform([trainY])
testPred = scaler.inverse_transform(testPred)
testY = scaler.inverse_transform([testY])
这个代码块仅用于取消归一化的影响,并将数据集恢复到初始形式。为了估计算法的性能,我们将计算均方根误差:
trainScore = math.sqrt(mean_squared_error(trainY[0], trainPred[:,0]))
print('Train Score: %.2f RMSE' % (trainScore))
testScore = math.sqrt(mean_squared_error(testY[0], testPred[:,0]))
print('Test Score: %.2f RMSE' % (testScore))
均方根误差(RMSE)衡量两个数据集之间的误差程度。换句话说,它比较了一个预测值和一个观察值。
返回以下结果:
Train Score: 1.12 RMSE
Test Score: 1.35 RMSE
在评估了方法性能之后,我们现在可以通过绘制适当的图表来可视化结果。为了正确显示时间序列,需要进行预测偏移。这个操作必须在训练集和测试集上执行:
trainPredPlot = numpy.empty_like(dataset)
trainPredPlot[:,:] = numpy.nan
trainPredPlot[1:len(trainPred)+1,:] = trainPred
然后在测试集上执行相同的操作:
testPredPlot = numpy.empty_like(dataset)
testPredPlot[:,:] = numpy.nan
testPredPlot[len(trainPred)+2:len(dataset),:] = testPred
最后,我们必须绘制实际数据和预测:
plt.plot(scaler.inverse_transform(dataset))
plt.plot(trainPredPlot)
plt.plot(testPredPlot)
plt.show()
在以下图表中显示了实际数据和预测:
从前一个图表的分析中,我们可以看到 RMSE 报告的内容得到了图表的证实。事实上,我们可以看到模型在训练集和测试集的拟合方面做得非常出色。
摘要
在本章中,我们探讨了时间序列数据。时间序列构成了一系列现象的观察序列。在一个时间序列中,我们可以识别出几个组成部分:趋势、季节性、周期和残差。我们通过一个实际例子学习了如何从一个时间序列中去除季节性。
然后讨论了表示时间序列的最常用的模型:AR、MA、ARMA 和 ARIMA。对于每一个,我们分析了基本概念,然后提供了模型的数学公式。
最后,提出了一种用于时间序列分析的长短期记忆(LSTM)模型。通过一个实际例子,我们可以看到如何使用 LSTM 类型的循环神经网络模型来处理时间序列回归问题。