原文:Deep Learning Quick Reference
译者:飞龙
本文来自【ApacheCN 深度学习 译文集】,采用译后编辑(MTPE)流程来尽可能提升效率。
不要担心自己的形象,只关心如何实现目标。——《原则》,生活原则 2.3.c
一、深度学习的基础
欢迎使用《深度学习快速参考》! 在本书中,我将尝试使需要解决深度学习问题的数据科学家,机器学习工程师和软件工程师更容易使用,实用和使用深度学习技术。 如果您想训练自己的深度神经网络并且陷入困境,那么本指南很有可能会有所帮助。
本书动手了,旨在作为实用指南,可以帮助您快速解决问题。 它主要供需要使用深度学习解决问题的经验丰富的机器学习工程师和数据科学家使用。 除了本章(其中提供了一些我们将要开始使用的术语,框架和背景知识)之外,它并不意味着要按顺序阅读。 每章均包含一个实际示例,并附有代码,一些最佳实践和安全选择。 我们希望您能跳到所需的章节并开始使用。
本书不会深入研究深度学习和神经网络的理论。 有许多可以提供这种背景知识的精彩书籍,我强烈建议您至少阅读其中一本(也许是参考书目,也可以只是建议)。 我们希望提供足够的理论和数学直觉来帮助您入门。
我们将在本章介绍以下主题:
- 深度神经网络架构
- 深度学习的优化算法
- 深度学习框架
- 构建用于深度学习的数据集
深度神经网络架构
深度神经网络架构的结构可能会因网络的应用而有很大差异,但它们都有一些基本组件。 在本节中,我们将简要讨论这些组件。
在本书中,我将深度神经网络定义为一个具有多个隐藏层的网络。 除此之外,我们不会尝试将成员限制为深度学习俱乐部。 因此,我们的网络可能只有不到 100 个神经元,甚至可能有数百万个。 我们可能会使用特殊的神经元层,包括卷积和循环层,但尽管如此,我们仍将所有这些都称为神经元。
神经元
神经元是神经网络的原子单位。 有时这是受到生物学启发的。 但是,这是另一本书的主题。 神经元通常排列成层。 在本书中,如果我指的是特定的神经元,则将使用符号n[k]^l
,其中l
是神经元所在的层, k
是神经元编号 。 由于我们将使用遵循第 0 个表示法的编程语言,因此我的表示法也将基于第 0 个表示法。
大多数神经元的核心是两个共同起作用的函数:线性函数和激活函数。 让我们从较高的角度看一下这两个组成部分。
神经元线性函数
神经元的第一部分是线性函数,其输出是输入的总和,每个输入乘以一个系数。 这个函数实际上或多或少是线性回归。 这些系数通常在神经网络中称为权重。 例如,给定某些神经元,其输入特征为x1
,x2
和x3
,输出z
,则此线性分量或神经元线性函数将简单地为:
在给定数据的情况下,θ[1], θ[2], ..., θ[n]
是权重或系数,b
是偏差项。
神经元激活函数
神经元的第二个函数是激活函数,其任务是在神经元之间引入非线性。 Sigmoid 激活是一种常用的激活,您可能会通过逻辑回归来熟悉它。 它将神经元的输出压缩到输出空间,其中z
的非常大的值被驱动为 1,而z
的非常小的值被驱动为 0。
sigmoid 函数如下所示:
事实证明,激活函数对于中间神经元非常重要。 没有它,可以证明一堆具有线性激活的神经元(实际上不是激活,或更正式地说是z = z
的激活函数)实际上只是一个线性函数。
在这种情况下,单个线性函数是不理想的,因为在许多情况下,我们的网络可能未针对当前问题指定。 也就是说,由于输入特征和目标变量之间的非线性关系(我们正在预测),网络无法很好地对数据建模。
不能用线性函数建模的函数的典型示例是排他的OR
函数,如下图所示:
其他常见的激活函数是tanh
函数和 ReLu 或整流线性激活。
双曲正切或tanh
函数如下所示:
对于中间层,tanh
通常比 Sigmoid 更好。 您可能会看到,tanh
的输出将在[-1, 1]
之间,而 Sigmoid 曲线的输出将为[0, 1]
。 这种额外的宽度可为消失或爆炸的梯度问题提供一定的弹性,我们将在后面详细介绍。 到目前为止,仅需知道消失的梯度问题就可以使网络在早期的层中收敛非常慢(如果有的话)。 因此,使用tanh
的网络趋于比使用 Sigmoid 激活的网络收敛更快。 也就是说,它们仍然不如 ReLu 快。
ReLu,或直线激活,简单定义为:
这是一个安全的赌注,我们在本书中的大部分时间都会使用它。 ReLu 不仅易于计算和微分,而且还可以抵抗消失的梯度问题。 ReLu 的唯一缺点是它的一阶导数未精确定义为 0。包括泄漏的 ReLu 在内的变体在计算上更加困难,但针对此问题更健壮。
为了完整起见,以下是 ReLu 的一些明显图表:
深度学习中的损失和成本函数
每个机器学习模型实际上都是从成本函数开始的。 简单来说,成本函数可让您衡量模型对训练数据的拟合程度。 在本书中,我们将损失函数定义为训练集中单个观测值的拟合正确性。 这样,成本函数通常将是整个训练集中损失的平均值。 稍后,当我们介绍每种类型的神经网络时,我们将重新讨论损失函数。 但是,请快速考虑线性回归的成本函数作为示例:
在这种情况下,损失函数为(y_hat - y)^2
,这实际上是平方误差。 因此,我们的cost
函数J
实际上只是均方误差,或整个数据集的均方误差的平均值。 按照惯例,添加了项 1/2 以使某些微积分更干净。
正向传播过程
正向传播是我们尝试使用单个观测值中存在的特征预测目标变量的过程。 想象一下,我们有一个两层神经网络。 在正向传播过程中,我们将从观察中出现的特征x[1], x[2], ..., x[n]
开始,然后将这些特征乘以它们在第 1 层中的关联系数,并为每个神经元添加一个偏差项。 之后,我们会将输出发送到神经元的激活。 之后,输出将被发送到下一层,依此类推,直到到达网络的末端,然后剩下网络的预测:
反向传播过程
一旦正向传播完成,我们就可以对每个数据点进行网络预测。 我们也知道数据点的实际值。 通常,将预测定义为y_hat
,而将目标变量的实际值定义为y
。
一旦y
和y_hat
都已知,就可以使用成本函数计算网络误差。 回想一下,代价函数是loss
函数的平均值。
为了使学习在网络中发生,网络的误差信号必须从最后一层到最后一层通过网络层向后传播。 我们反向传播的目标是在网络中向后传播该误差信号,同时随着信号的传播使用误差信号来更新网络权重。 在数学上,要做到这一点,我们需要对权重进行微调,以使成本函数最小,从而最小化成本函数。 此过程称为梯度下降。
梯度是误差函数相对于网络内每个权重的偏导数。 可以使用链法则和上面各层的梯度逐层计算每个权重的梯度。
一旦知道了每一层的梯度,我们就可以使用梯度下降算法来最小化cost
函数。
梯度下降将重复此更新,直到网络的误差最小化并且该过程收敛为止:
梯度下降算法将梯度乘以称为alpha
的学习率,然后从每个权重的当前值中减去该值。 学习率是一个超参数。
随机和小批量梯度下降
上一节中描述的算法假定整个数据集都进行正向和相应的反向传递,因此将其称为批梯度下降。
进行梯度下降的另一种可能方法是一次使用一个数据点,并随着我们的更新网络权重。 此方法可能有助于加快网络可能停止收敛的鞍点附近的收敛速度。 当然,仅单个点的误差估计可能无法很好地近似于整个数据集的误差。
解决此问题的最佳解决方案是使用小型批量梯度下降,其中我们将采用称为小型批量的数据的随机子集来计算误差并更新网络权重。 这几乎总是最好的选择。 它还有一个额外的好处,即可以将非常大的数据集自然地拆分为多个块,这些块可以更容易地在计算机的内存中甚至跨计算机的内存中进行管理。
这是对神经网络最重要部分之一的极高层次的描述,我们认为这与本书的实际性质相符。 实际上,大多数现代框架都为我们处理了这些步骤。 但是,至少在理论上,它们无疑是值得了解的。 我们鼓励读者在时间允许的情况下更深入地进行向前和向后传播。
深度学习的优化算法
梯度下降算法不是唯一可用于优化网络权重的优化算法,但它是大多数其他算法的基础。 虽然了解每种优化算法都有可能获得博士学位,但我们将为一些最实用的内容专门介绍几句话。
梯度下降和动量
通过使用具有动量的梯度下降,可以通过增加方向学习的速度来加快梯度下降,从而使梯度在方向上保持恒定,而在方向缓慢学习时,梯度会在方向上波动。 它允许梯度下降的速度增加。
动量的工作原理是引入速度项,并在更新规则中使用该项的加权移动平均值,如下所示:
在动量的情况下,最通常将β
设置为 0.9,通常这不是需要更改的超参数。
RMSProp 算法
RMSProp 是另一种算法,可以通过跨网络权重表示的多维空间,通过在某些方向上加快学习速度,并在其他方向上抑制振荡来加快梯度下降:
这具有在v[t]
大的方向上进一步减少振荡的效果。
Adam 优化器
Adam 是已知表现最好的优化器之一,这是我的首选。 它可以很好地解决各种问题。 它将动量和 RMSProp 的最佳部分组合到一个更新规则中:
其中ε
很小,可以防止被 0 除。
亚当通常是一个不错的选择,当您进行原型设计时,这是一个很好的起点,因此,从亚当开始可以节省一些时间。
深度学习框架
虽然仅使用 Python 的numpy
从头开始构建和训练深度神经网络是绝对可能的,但这将花费大量的时间和代码。 在几乎每种情况下,使用深度学习框架都更加实用。
在本书中,我们将使用 TensorFlow 和 Keras 来使开发深度神经网络变得更加轻松和快捷。
什么是 TensorFlow?
TensorFlow 是一个可用于快速构建深度神经网络的库。 在 TensorFlow 中,我们到目前为止已涵盖的数学运算被表示为节点。 这些节点之间的边缘是张量或多维数据数组。 给定定义为图和损失函数的神经网络,TensorFlow 可以自动计算网络的梯度并优化图以最小化损失函数。
TensorFlow 是 Google 在 2015 年发布的一个开源项目。此后,它已经获得了很大的关注,并拥有庞大的用户社区。 虽然 TensorFlow 提供 Java,C++,Go 和 Python 的 API,但我们仅介绍 Python API。 本书使用了 Python API,因为它既是最常用的,也是开发新模型时最常用的 API。
通过在一个或多个图形处理单元上执行这些计算,TensorFlow 可以大大加快计算速度。 GPU 计算提供的加速已成为现代深度学习中的必要条件。
什么是 Keras?
尽管在 TensorFlow 中构建深度神经网络要比从头开始做起来容易得多,但 TensorFlow 仍然是一个非常底层的 API。 Keras 是一个高级 API,允许我们使用 TensorFlow(或 Theano 或 Microsoft 的 CNTK)快速构建深度学习网络。
用 Keras 和 TensorFlow 构建的模型是便携式的,也可以在本机 TensorFlow 中进行训练或使用。 TensorFlow 中构建的模型可以加载到 Keras 中并在其中使用。
TensorFlow 的流行替代品
那里还有许多其他很棒的深度学习框架。 我们之所以选择 Keras 和 TensorFlow,主要是因为其受欢迎程度,易用性,支持的可用性以及生产部署的准备就绪。 无疑还有其他有价值的选择。
我最喜欢的 TensorFlow 替代品包括:
- Apache MXNet:一个非常高表现的框架,带有一个名为 Gluon 的新命令式接口
- PyTorch:Facebook 最初开发的一种非常新颖且有希望的架构
- CNTK:也可以与 Keras 一起使用的 Microsoft 深度学习框架
尽管我确实坚信 Keras 和 TensorFlow 是本书的正确选择,但我也想承认这些出色的框架以及每个项目对领域做出的贡献。
TensorFlow 和 Keras 的 GPU 要求
在本书的其余部分,我们将使用 Keras 和 TensorFlow。 我们将探索的大多数示例都需要 GPU 来加速。 包括 TensorFlow 在内的大多数现代深度学习框架都使用 GPU 极大地加速了网络训练期间所需的大量计算。 如果没有 GPU,我们讨论的大多数模型的训练时间将过长。
如果您没有安装有 GPU 的计算机,则可以从包括 Amazon 的 Amazon Web Services 和 Google 的 Google Cloud Platform 在内的各种云提供商处租用基于 GPU 的计算实例。 对于本书中的示例,我们将在运行 Ubuntu Server 16.04 的 Amazon EC2 中使用p2.xlarge
实例。 p2.xlarge 实例提供了具有 2,496 个 CUDA 内核的 Nvidia Tesla K80 GPU,这将使我们在本书中显示的模型的运行速度甚至比非常高端的台式计算机所能达到的速度快得多。
安装 Nvidia CUDA 工具包和 cuDNN
由于您可能会在深度学习工作中使用基于云的解决方案,因此我提供了一些说明,这些说明可帮助您在 Ubuntu Linux 上快速启动并运行,Ubuntu Linux 在各个云提供商中普遍可用。 也可以在 Windows 上安装 TensorFlow 和 Keras。 从 TensorFlow v1.2 开始,TensorFlow 不幸地不支持 OSX 上的 GPU。
在使用 GPU 之前,必须先安装 NVidia CUDA 工具包和 cuDNN 。 我们将安装 CUDA Toolkit 8.0 和 cuDNN v6.0,建议与 TensorFlow v1.4 一起使用。 在您阅读完本段之前,很有可能会发布新版本,因此,请访问 www.tensorflow.org 以获取最新的必需版本。
我们将从在 Ubuntu 上安装build-essential
包开始,该包包含编译 C++ 程序所需的大部分内容。 代码在这里给出:
sudo apt-get update
sudo apt-get install build-essential
接下来,我们可以下载并安装 CUDA Toolkit。 如前所述,我们将安装 8.0 版及其相关补丁。 您可以在这个页面中找到最适合您的 CUDA 工具包。
wget https://developer.nvidia.com/compute/cuda/8.0/Prod2/local_installers/cuda_8.0.61_375.26_linux-run
sudo sh cuda_8.0.61_375.26_linux-run # Accept the EULA and choose defaults
wget https://developer.nvidia.com/compute/cuda/8.0/Prod2/patches/2/cuda_8.0.61.2_linux-run
sudo sh cuda_8.0.61.2_linux-run # Accept the EULA and choose defaults
CUDA 工具包现在应该安装在以下路径中:/usr/local/cuda
。 您需要添加一些环境变量,以便 TensorFlow 可以找到它。 您可能应该考虑将这些环境变量添加到~/.bash_profile
,以便在每次登录时进行设置,如以下代码所示:
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/usr/local/cuda/lib64"
export CUDA_HOME="/usr/local/cuda"
此时,您可以通过执行以下命令来测试一切是否正常:nvidia-smi
。 输出应类似于以下内容:
$nvidia-smi
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 375.26 Driver Version: 375.26 |
|-------------------------------+----------------------+----------------------+
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
|===============================+======================+======================|
| 0 Tesla K80 Off | 0000:00:1E.0 Off | 0 |
| N/A 41C P0 57W / 149W | 0MiB / 11439MiB | 99% Default |
+-------------------------------+----------------------+----------------------+
最后,我们需要安装 cuDNN,这是 NVIDIA CUDA 深度神经网络库。
首先,将 cuDNN 下载到本地计算机。 为此,您需要在 NVIDIA 开发人员网络中注册为开发人员。 您可以在 cuDNN 主页 上找到 cuDNN。 将其下载到本地计算机后,可以使用scp
将其移至 EC2 实例。 虽然确切的说明会因云提供商的不同而有所差异,但是您可以在这个页面中找到有关通过 SSH/SCP 连接到 AWS EC2 的其他信息。 。
将 cuDNN 移至 EC2 映像后,可以使用以下代码解压缩文件:
tar -xzvf cudnn-8.0-linux-x64-v6.0.tgz
最后,使用以下代码将解压缩的文件复制到其适当的位置:
sudo cp cuda/include/cudnn.h /usr/local/cuda/include/
sudo cp cuda/lib64/* /usr/local/cuda/lib64
我不清楚为什么 CUDA 和 cuDNN 分别分发,为什么 cuDNN 需要注册。 cuDNN 的下载过程和手动安装过于复杂,这确实是深度学习中最大的谜团之一。
安装 Python
我们将使用virtualenv
创建一个隔离的 Python 虚拟环境。 尽管这不是严格必要的,但这是一种极好的实践。 这样,我们会将该项目的所有 Python 库保存在一个独立的隔离环境中,该环境不会干扰系统 Python 的安装。 此外,virtualenv
环境将使以后打包和部署我们的深度神经网络更加容易。
首先,使用 Ubuntu 中的 aptitude 包管理器安装Python
,pip
和virtualenv
。 以下是代码:
sudo apt-get install python3-pip python3-dev python-virtualenv
现在,我们可以为我们的工作创建虚拟环境。 我们将所有虚拟环境文件保存在名为~/deep-learn
的文件夹中。 您可以自由选择该虚拟环境的任何名称。 以下代码显示了如何创建虚拟环境:
virtualenv --no-site-packages -p python3 ~/deep-learn
如果您是一位经验丰富的 Python 开发人员,您可能已经注意到我已将环境设置为默认为 Python3.x。 肯定不是必须的,并且 TensorFlow/Keras 都支持 Python 2.7。 也就是说,作者感到 Python 社区有道德义务支持现代版本的 Python。
现在已经创建了虚拟环境,您可以按以下方式激活它:
$source ~/deep-learn/bin/activate
(deep-learn)$ # notice the shell changes to indicate the virtualenv
此时,每次登录时都需要激活要使用的虚拟环境。如果您想始终输入刚刚创建的虚拟环境,可以将source
命令添加到~/.bash_profile
。
现在我们已经配置了虚拟环境,我们可以根据需要在其中添加 Python 包。 首先,请确保我们具有 Python 包管理器pip
的最新版本:
easy_install -U pip
最后,我建议安装 IPython,它是一个交互式 Python shell,可简化开发。
pip install ipython
就是这样。 现在我们准备安装 TensorFlow 和 Keras。
安装 TensorFlow 和 Keras
在我们共同完成所有工作之后,您将很高兴看到现在安装 TensorFlow 和 Keras 多么简单。
让我们开始安装 TensorFlow
TensorFlow 的安装可以使用以下代码完成:
pip install --upgrade tensorflow-gpu
确保pip install tensorflow-gpu
。 如果您通过 pip 安装 TensorfFow(不带-gpu
),则将安装仅 CPU 版本。
在安装 Keras 之前,让我们测试一下 TensorFlow 安装。 为此,我将使用 TensorFlow 网站和 IPython 解释器中的一些示例代码。
通过在 bash 提示符下键入 IPython ,启动 IPython 解释程序。 IPython 启动后,让我们尝试导入 TensorFlow。 输出如下所示:
In [1]: import tensorflow as tf
In [2]:
如果导入 TensorFlow 导致错误,请对到目前为止已执行的步骤进行故障排除。 大多数情况下,当无法导入 TensorFlow 时,可能未正确安装 CUDA 或 cuDNN。
现在我们已经成功安装了 TensorFlow,我们将在 IPython 中运行一小段代码,以验证我们可以在 GPU 上运行计算:
a = tf.constant([1.0,</span> 2.0, 3.0, 4.0, 5.0, 6.0], shape=[2, 3], name='a')
b = tf.constant([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], shape=[3, 2], name='b')
c = tf.matmul(a, b)
sess = tf.Session(config=tf.ConfigProto(log_device_placement=True))
print(sess.run(c))
如果一切顺利,我们将看到许多迹象表明正在使用我们的 GPU。 我在此处提供了一些输出,并重点介绍了提请您注意的证据。 根据硬件,您的输出可能会有所不同,但是您应该看到类似的证据,如下所示:
/job:localhost/replica:0/task:0/device:GPU:0 -> device: 0, name: Tesla K80, pci bus id: 0000:00:1e.0, compute capability: 3.7
MatMul: (MatMul): /job:localhost/replica:0/task:0/device:GPU:0
: I tensorflow/core/common_runtime/placer.cc:874] MatMul: (MatMul)/job:localhost/replica:0/task:0/device:GPU:0
b: (Const): /job:localhost/replica:0/task:0/device:GPU:0
: I tensorflow/core/common_runtime/placer.cc:874] b: (Const)/job:localhost/replica:0/task:0/device:GPU:0
a: (Const): /job:localhost/replica:0/task:0/device:GPU:0
: I tensorflow/core/common_runtime/placer.cc:874] a: (Const)/job:localhost/replica:0/task:0/device:GPU:0
[[ 22\. 28.]
[ 49\. 64.]]
在前面的输出中,我们可以看到张量a
和b
以及矩阵乘法运算已分配给 GPU。 如果访问 GPU 出现问题,则输出可能如下所示:
I tensorflow/core/common_runtime/placer.cc:874] b_1: (Const)/job:localhost/replica:0/task:0/device:CPU:0
a_1: (Const): /job:localhost/replica:0/task:0/device:CPU:0
I tensorflow/core/common_runtime/placer.cc:874] a_1: (Const)/job:localhost/replica:0/task:0/device:CPU:0
在这里我们可以看到张量b_1
和a_1
被分配给 CPU 而不是 GPU。 如果发生这种情况,说明您的 TensorFlow,CUDA 或 cuDNN 安装存在问题。
如果到目前为止,您已经安装了 TensorFlow。 剩下的唯一任务是安装 Keras。
可以在以下代码的帮助下完成 Keras 的安装:
pip install keras
就是这样! 现在我们准备在 Keras 和 TensorFlow 中构建深度神经网络。
这可能是创建快照甚至是 EC2 实例的 AMI 的好时机,因此您不必再次进行此安装。
构建用于深度学习的数据集
与您可能已经使用的其他预测模型相比,深度神经网络非常复杂。 考虑一个具有 100 个输入的网络,两个具有 30 个神经元的隐藏层以及一个逻辑输出层。 该网络将具有 3,930 个可学习的参数以及优化所需的超参数,这是一个非常小的例子。 大型卷积神经网络将具有数亿个可学习的参数。 所有这些参数使得深度神经网络在学习结构和模式方面如此惊人。 但是,这也使过度安装成为可能。
深度学习中的偏差和方差误差
您可能熟悉典型预测模型中的所谓偏差/方差折衷。 如果您不在,我们将在此处提供快速提醒。 在传统的预测模型中,当我们尝试从偏差中发现误差并从方差中发现误差时,通常会有一些折衷。 因此,让我们看看这两个误差是什么:
- 偏差误差:偏差误差是模型引入的误差。 例如,如果您尝试使用线性模型对非线性函数建模,则模型将在指定的下为,并且偏差误差会很高。
- 方差误差:方差误差是由训练数据中的随机性引起的误差。 当我们很好地拟合训练分布以至于我们的模型不再泛化时,我们就过拟合或引入了方差误差。
在大多数机器学习应用中,我们寻求找到一些折衷方案,以最小化偏差误差,同时引入尽可能小的方差误差。 我之所以这么说是因为深度神经网络的一大优点是,在很大程度上,偏差和方差可以彼此独立地进行操纵。 但是,这样做时,我们将需要非常谨慎地构造训练数据。
训练,验证和测试数据集
在本书的其余部分中,我将把我的数据分为三个独立的集合,分别称为训练,验证和测试。 从总数据集中抽取为随机样本的这三个单独的数据集的结构和大小将大致如此。
训练数据集将按预期用于训练网络。
验证数据集将用于查找理想的超参数并测量过拟合。 在周期结束时,即网络有机会观察训练集中的每个数据点时,我们将对验证集进行预测。 该预测将用于监视过拟合,并将帮助我们知道网络何时完成训练。 像这样在每个周期末尾使用验证设置与典型用法有些不同。 有关保留验证的更多信息,请参考 Hastie 和 Tibshirani 撰写的《统计学习的特征》。
一旦完成所有训练,就将使用测试数据集,以根据网络未看到的一组数据准确地测量模型表现。
验证和测试数据来自同一数据集非常重要。 训练数据集匹配验证和测试不太重要,尽管那仍然是理想的。 例如,如果使用图像增强(对训练图像进行较小的修改以尝试扩大训练集大小),则训练集分布可能不再与验证集分布匹配。 这是可以接受的,并且只要验证和测试来自同一分布,就可以充分测量网络表现。
在传统的机器学习应用中,习惯上将 10% 到 20% 的可用数据用于验证和测试。 在深度神经网络中,通常情况是我们的数据量很大,以至于我们可以用更小的验证和测试集来充分测量网络表现。 当数据量达到数以千万计的观测值时,将 98%,1%,1% 的拆分完全合适。
在深度神经网络中管理偏差和方差
现在,我们已经定义了如何构造数据并刷新偏差和方差,现在让我们考虑如何控制深度神经网络中的偏差和方差。
- 高偏差:在训练集上进行预测时,具有高偏差的网络将具有非常高的错误率。 该模型在拟合数据方面表现不佳。 为了减少偏差,您可能需要更改网络架构。 您可能需要添加层,神经元或两者。 使用卷积或循环网络可能可以更好地解决您的问题。
当然,有时由于信号不足或非常困难的问题而导致问题偏高,因此请务必以合理的速度校准您的期望(我喜欢从对人的准确率进行校准开始)。
- 高方差:具有低偏差误差的网络很好地拟合了训练数据; 但是,如果验证误差大于测试误差,则网络已开始过拟合训练数据。 减少差异的两种最佳方法是添加数据并向网络添加正则化。
添加数据很简单,但并非总是可能的。 在整本书中,我们将介绍适用的正则化技术。 我们将讨论的最常见的正则化技术是 L2 正则化,丢弃法和批量归一化。
K 折交叉验证
如果您有机器学习的经验,您可能想知道为什么我会选择通过 K 折交叉验证而不是保留(训练/验证/测试)验证。 训练深度神经网络是一项非常昂贵的操作,并且非常简单地讲,针对每个我们想探索的超参数训练 K 个神经网络通常不太实用。
我们可以确信,在给定的验证和测试集足够大的情况下,留出验证会做得很好。 在大多数情况下,我们希望在有大量数据的情况下应用深度学习,从而获得足够的值和测试集。
最终,这取决于您。 稍后我们将看到,Keras 提供了 scikit-learn 接口,该接口可将 Keras 模型集成到 scikit-learn 管道中。 这使我们能够执行 K 折,分层 K 折,甚至使用 K 折进行网格搜索。 有时在训练深层模型时使用 K 折 CV 是可行且适当的。 也就是说,在本书的其余部分中,我们将重点介绍使用留出验证。
总结
希望本章能够使您对深度神经网络架构和优化算法有所了解。 因为这是快速参考,所以我们没有做太多的详细介绍,我鼓励读者对这里可能是新手或陌生的任何材料进行更深入的研究。
我们讨论了 Keras 和 TensorFlow 的基础知识,以及为什么我们在本书中选择了这些框架。 我们还讨论了 CUDA,cuDNN,Keras 和 TensorFlow 的安装和配置。
最后,我们介绍了本书其余部分将使用的留出验证方法,以及为什么对于大多数深度神经网络应用,我们都更喜欢 K 折 CV。
当我们在以后的章节中重新审视这些主题时,我们将大量参考本章。 在下一章中,我们将开始使用 Keras 解决回归问题,这是构建深度神经网络的第一步。
二、使用深度学习解决回归问题
在本章中,我们将构建一个简单的多层感知器(MLP),它是具有单个隐藏层的神经网络的奇特名称,用于解决回归问题。 然后,我们将深入研究具有多个隐藏层的深度神经网络。 在此过程中,我们将探索模型的表现和过拟合。 所以,让我们开始吧!
我们将在本章介绍以下主题:
- 回归分析和深度神经网络
- 将深度神经网络用于回归
- 在 Keras 中建立 MLP
- 在 Keras 中建立深度神经网络
- 保存和加载经过训练的 Keras 模型
回归分析和深度神经网络
在经典回归分析中,我们使用线性模型来学习一组独立变量和因变量之间的关系。 在找到这种关系时,我们希望能够在给定自变量值的情况下预测因变量的值。
进行回归分析的第二个重要原因是要了解当所有其他自变量保持恒定时单个自变量对因变量的影响。 传统多元线性回归的一大优点是线性模型的其他条件不变属性。 我们可以通过使用与该自变量关联的学习权重来解释单个自变量对因变量的影响,而无需考虑其他自变量。 这种解释充其量是具有挑战性的,需要我们对我们的数据和模型做出很多假设。 但是,它通常非常有用。
深度神经网络很难解释,尽管尝试这样做是一个活跃的研究领域。
有关介绍深度神经网络的当前状态的介绍,请查看 Montavon 等人的《解释和理解深度神经网络的方法》。
将神经网络用于回归的好处
在本章的其余部分,我们将重点介绍使用深度神经网络进行预测。 与使用传统的多元线性回归进行比较时,您会很高兴地发现我们的神经网络具有以下优势:
- 我们不需要选择或筛选特征。 神经网络是功能强大的特征工程机器,可以了解哪些特征是相关的,而忽略了无关的特征。
- 给定足够复杂的网络,还可以学习特征交互(例如,除了
x[1]
和x[2]
的独立效应,x[1] * x[2]
的效应)) - 您可能现在已经猜到了,我们还可以学习更高阶的多项式关系(例如
x[2]^3
) - 最后,只要我们确保最终激活可以对分布进行建模,我们就不必只对正态分布建模或对非正态分布使用不同的模型。
将神经网络用于回归时要考虑的缺点
但这并不是所有的彩虹和小猫,使用神经网络解决这些真正简单的问题也有一些弊端。 最明显的缺点是:
- 如前所述,神经网络不容易解释。
- 当具有许多特征和大量数据时,神经网络最有效。 许多简单的回归问题还不够大,无法真正从神经网络中受益。
- 在很多情况下,传统的多元回归或树模型(例如梯度提升树)在此类问题上的表现将优于神经网络。 越复杂,就越适合神经网络。
将深度神经网络用于回归
既然您已经希望了解为什么(不希望)使用深度神经网络进行回归,那么我将向您展示如何做到这一点。 虽然它不像在 scikit-learn 中使用线性回归器那样简单,但我认为使用 Keras 会很容易。 最重要的是,Keras 将允许您快速迭代模型架构而无需更改大量代码。
如何规划机器学习问题
在构建新的神经网络时,我建议每次都遵循相同的基本步骤。
深度神经网络很快就会变得非常复杂。 进行一点计划和组织,大大加快您的工作流程!
以下是构建深度神经网络的步骤:
- 概述您要解决的问题。
- 确定模型的输入和输出。
- 选择
cost
函数和指标。 - 创建一个初始的网络架构。
- 训练和调整网络。
定义示例问题
在我们的示例问题中,我们将使用 P. Cortez 等人创建的葡萄酒质量数据集。 考虑到白酒的其他 10 个化学特性,我们将预测白葡萄酒数据中所含酒精的百分比。
此数据集中总共有 4,898 个观测值或元素,对于经典回归问题而言可能很大,但对于深度学习问题而言却很小。
一些快速的探索性数据分析将告诉我们,我们将用来预测酒精含量的 10 个化学特征在不同尺度上都是连续变量。
加载数据集
虽然可能不是机器学习问题中最有趣的部分,但加载数据是重要的一步。 我将在这里介绍我的数据加载方法,以便您可以了解如何处理数据集。
from sklearn.preprocessing import StandardScaler
import pandas as pd
TRAIN_DATA = "./data/train/train_data.csv"
VAL_DATA = "./data/val/val_data.csv"
TEST_DATA = "./data/test/test_data.csv"
def load_data():
"""Loads train, val, and test datasets from disk"""
train = pd.read_csv(TRAIN_DATA)
val = pd.read_csv(VAL_DATA)
test = pd.read_csv(TEST_DATA)
# we will use sklearn's StandardScaler to scale our data to 0 mean, unit variance.
scaler = StandardScaler()
train = scaler.fit_transform(train)
val = scaler.transform(val)
test = scaler.transform(test)
# we will use a dict to keep all this data tidy.
data = dict()
data["train_y"] = train[:, 10]
data["train_X"] = train[:, 0:9]
data["val_y"] = val[:, 10]
data["val_X"] = val[:, 0:9]
data["test_y"] = test[:, 10]
data["test_X"] = test[:, 0:9]
# it's a good idea to keep the scaler (or at least the mean/variance) so we can unscale predictions
data["scaler"] = scaler
return data
当我从 csv,excel 甚至是 DBMS 中读取数据时,第一步通常是将其加载到 pandas 数据框中。
标准化我们的数据很重要,这样每个特征都应具有可比的范围,并且所有这些范围都应位于激活函数的范围之内。 在这里,我使用了 Scikit-Learn 的StandardScaler
完成此任务。
这为我们提供了一个形状完整的数据集(4898, 10)
。 我们的目标变量alcohol
的百分比介于 8% 和 14.2% 之间。
在加载数据之前,我已经对数据进行了随机采样并将其划分为train
,val
和test
数据集,因此我们在这里不必担心。
最后,load_data()
函数返回一个字典,该字典将所有内容保持整齐并放在一个位置。 如果您以后看到我参考数据[X_train]
,则知道我正在参考训练数据集,该数据集已存储在数据字典中。
。 该项目的代码和数据均可在该书的 GitHub 网站上找到。
定义成本函数
对于回归任务,最常见的成本函数是均方根误差(RMSE)和平均绝对误差(MAE)。 我将在这里使用 MAE。 定义如下:
很简单,MAE 是数据集中所有示例的平均无符号误差。 与 RMSE 非常相似; 但是,我们使用y
和y_hat
之间的差的绝对值代替平均平方误差的平方根:
您可能想知道 MAE 与更熟悉的 RMSE 有何不同。 如果误差在数据集中均匀分布,则 RMSE 和 MAE 将相等。 如果数据集中有非常大的离群值,则 RMSE 将比 MAE 大得多。 您选择的成本函数应适合您的用例。 关于可解释性,MAE 比 RMSE 更具解释性,因为它是实际的平均误差。
在 Keras 中建立 MLP
Keras 使用模型对象的实例来包含神经网络。 对于熟悉 scikit-learn 的人来说,这可能是相当熟悉的。 略有不同的是 Keras 模型包含一组层。 这一组层需要由我们定义。 只需很少的代码,就可以在网络架构中实现惊人的灵活性。
Keras 当前有两个用于构建模型的 API。 在我的示例中,我将使用函数式 API。 它稍微冗长一些,但可以提供更多的灵活性。 我建议尽可能使用函数式 API。
我们的 MLP 将需要一个输入层,一个隐藏层和一个输出层。
输入层形状
由于我们已经确定了输入,因此我们知道输入矩阵的行数等于数据集中的数据元素/观测值的数量,并且列数等于变量/特征的数量。 输入矩阵的形状为(观察数量 x 10 个特征)
。 TensorFlow 和 Keras 可以在定义数据集中元素的数量时使用None
作为占位符,而不是定义数据集中或小批量中的确切记录数。
如果看到 Keras 或 TensorFlow 模型层形状中使用了None
维度,则它实际上表示任意维度,该维度可以采用任何正整数值。
隐藏层形状
我们的隐藏层将从 32 个神经元开始。 在这一点上,我们不知道需要多少神经元。 这确实是一个超参数,以后可以进行探索和调整。 为给定问题确定合适的网络架构是深度学习领域的一个开放问题。
由于隐藏层中这 32 个神经元中的每一个都将其激活输出到输出层,因此隐藏层的形状将为(10, 32)
。
输出层形状
我们的最后一层将由单个神经元组成,使用来自隐藏层的 32 个输入,将为每个观察值预测单个输出值y_hat
。
将所有各层放在一起,我们的 MLP 网络结构将如下所示:
神经网络架构
现在我们已经定义了输入和输出,我们可以看一下网络的代码。
from keras.layers import Input, Dense
from keras.models import Model
def build_network(input_features=None):
inputs = Input(shape=(input_features,), name="input")
x = Dense(32, activation='relu', name="hidden")(inputs)
prediction = Dense(1, activation='linear', name="final")(x)
model = Model(inputs=inputs, outputs=prediction)
model.compile(optimizer='adam', loss='mean_absolute_error')
return model
这里的所有都是它的! 然后,我们可以使用此代码,只需调用它即可构建适合于我们问题的神经网络实例,如下所示:
model = build_network(input_features=10)
但是,在开始之前,让我们回顾一下前面代码中的一些有趣的部分:
- 每层链接到到它上面的层。 每层都是可调用的,并返回张量。 例如,当隐藏层调用它时,我们的隐藏层绑定到输入层:
x = Dense(32, activation='relu', name="hidden")(inputs)
- 我们最后一层的激活函数是线性的。 这与不使用任何激活(这是我们要进行回归)相同。
- Keras 模型需要使用
.compile()
进行编译。 - 在编译调用期间,您需要定义将要使用的成本函数和优化器。 正如我们所讨论的,在此示例中,我已将 MAE 用于成本函数。 我使用具有默认参数的 Adam 作为我的优化程序,我们在第 1 章中已经介绍了这一点。很可能我们最终将希望调整 Adam 的学习速度。 这样做非常简单:您只需要定义一个自定义
adam
实例,然后使用该实例即可:
from keras.optimizers import Adam
adam_optimizer = Adam(lr=0.001, beta_1=0.9, beta_2=0.999, epsilon=1e-08, decay=0.0)
model.compile(optimizer=adam_optimizer, loss='mean_absolute_error')
训练 Keras 模型
现在我们的网络已经构建和编译,剩下的就是训练它了。 就像 Python 的 scikit-learn 一样,您可以通过在模型实例上调用.fit()
来做到这一点,如以下代码所示:
model.fit(x=data["train_X"], y=data["train_y"], batch_size=32, epochs=200, verbose=1, validation_data=(data["val_X"], data["val_y"]))
让我们来看一下 Keras fit
方法所采用的一些重要参数。 我将假设您熟悉小批量梯度下降和训练周期,但如果不熟悉,请查看第 1 章“深度学习的基础知识”, 概述。 Keras 拟合模型中的重要参数如下:
batch_size
:Keras 的默认批次大小为 32。批次大小是 Keras 将使用的迷你批次的大小。 当然,这意味着 Keras 假设您要使用小批量梯度下降。 如果由于某种原因不想使用小批量梯度,可以设置batch_size=None
。epochs
:一个周期只是整个训练集的单次通过。 在实践中,您需要在训练网络时对其进行监视,以了解网络何时收敛,因此epochs
是一个易于学习的超参数。 稍后,我们将看到可以在每个周期甚至比最后一个周期更好的每个周期保存模型的权重。 一旦知道如何做到这一点,我们就可以选择我们认为最好的周期,并实现一种基于人的早期停止。validation_data
:在这里,我们指定验证集。 在每个阶段结束时,Keras 将在验证集上测试模型,并使用损失函数和您指定的任何其他指标输出结果。 另外,您可以将validation_split
设置为浮点值,以指定要用于验证的训练组的百分比。 这两个选项都可以正常工作,但是在数据集拆分方面,我希望讲得很明确。verbose
:这有点不言而喻; 但是,值得一提。verbose=1
输出一个进度条,显示当前周期的状态,在周期结束时,Keras 将输出训练和验证损失。 也可以将verbose
设置为 2(每个小批量输出损失信息),将其设置为 0(使 Keras 保持静音)。
评估模型的表现
现在我们的 MLP 已经过训练,我们可以开始了解它的表现。 为此,我将对Train
,Val
和Test
数据集进行预测。 相同的代码如下:
print("Model Train MAE: " + str(mean_absolute_error(data["train_y"], model.predict(data["train_X"]))))
print("Model Val MAE: " + str(mean_absolute_error(data["val_y"], model.predict(data["val_X"]))))
print("Model Test MAE: " + str(mean_absolute_error(data["test_y"], model.predict(data["test_X"]))))
对于我们的 MLP,这是我们做得如何:
Model Train MAE: 0.190074701809
Model Val MAE: 0.213255747475
Model Test MAE: 0.199885450841
请记住,我们的数据已缩放为 0 均值和单位方差。 Train MAE
是0.19
,而我们的Val MAE
是0.21
。 这两个误差彼此之间非常接近,所以过分适合并不是我太在意的事情。 因为我预计会有一些我看不到的过拟合(通常是更大的问题),所以我认为此模型可能有太多偏差。 换句话说,我们可能无法足够紧密地拟合数据。 发生这种情况时,我们需要为我们的模型添加更多的层,更多的神经元或两者。 我们需要更深入。 让我们接下来做。
我们可以尝试通过以更多神经元的形式向网络添加参数来减少网络偏差。 虽然您可能会开始尝试优化优化器,但通常最好先找到自己熟悉的网络架构。
在 Keras 中建立深度神经网络
更改模型就像重新定义我们先前的build_network()
函数一样容易。 我们的输入层将保持不变,因为我们的输入没有更改。 同样,输出层应保持不变。
我将通过添加其他隐藏层将参数添加到我们的网络中。 我希望通过添加这些隐藏层,我们的网络可以了解输入和输出之间更复杂的关系。 我将从添加四个其他隐藏层开始; 前三个将具有 32 个神经元,第四个将具有 16 个神经元。其外观如下:
以下是在 Keras 中构建模型的相关代码:
def build_network(input_features=None):
inputs = Input(shape=(input_features,), name="input")
x = Dense(32, activation='relu', name="hidden1")(inputs)
x = Dense(32, activation='relu', name="hidden2")(x)
x = Dense(32, activation='relu', name="hidden3")(x)
x = Dense(32, activation='relu', name="hidden4")(x)
x = Dense(16, activation='relu', name="hidden5")(x)
prediction = Dense(1, activation='linear', name="final")(x)
model = Model(inputs=inputs, outputs=prediction)
model.compile(optimizer='adam', loss='mean_absolute_error')
return model
如所承诺的,我们的代码几乎没有改变。 我将其他行加粗了。 我们其余的代码可以保持不变。 但是,随着网络复杂性的增加,您通常必须训练更长的时间(更多的时间)。
测量深度神经网络表现
在这个问题上,深层网络真的比 MLP 好吗? 让我们找出答案! 训练了 500 个周期后,模型的效果如下:
Model Train MAE: 0.0753991873787
Model Val MAE: 0.189703853999
Model Test MAE: 0.190189985043
我们可以看到Train MAE
现在从0.19
减少到0.075
。 我们大大降低了网络的偏差。
但是,我们的差异增加了。 训练误差和验证误差之间的差异要大得多。 我们的Val
集误差确实略有下降,这很好; 但是,训练误差和验证误差之间的巨大差距表明我们开始过度适应训练集。
在这种情况下,减少差异的最直接方法是添加其他训练数据或应用诸如 L2 正则化或丢弃法之类的正则化技术,我们将在下一章中介绍。
对于高方差网络,更多的数据通常是最佳解决方案。 如果有可能收集更多数据,那可能就是花费时间的最佳位置。
建立网络后,我想直观地检查误差,以了解网络对验证集分布进行建模的程度。 这通常会带来见解,这将有助于我改进模型。 对于回归模型,我想绘制验证集的预测值和实际值的直方图。 让我们看看我的表现如何。 该图如下,供您参考:
总体而言,我认为该模型正在相当接近地预测实际分布。 似乎实际的验证数据集比预测的数据集向左移动(较小的值)要多一些,这可能是一个重要的见解。 换句话说,网络可能会预测葡萄酒的酒精含量高于平均水平,尤其是在酒精含量较低的情况下。 更仔细地检查验证数据可能会建议我们如何收集更多的训练数据。
调整模型超参数
现在,我们已经针对该问题训练了 MLP 和六层深度神经网络,现在可以调整和优化模型超参数了。
我们将在第 6 章“超参数优化”中讨论深度模型调整。 您可以使用多种策略为模型选择最佳参数。 您可能已经注意到,我们仍然可以优化许多可能的参数和超参数。
如果要完全调整此模型,则应执行以下操作:
- 试验隐藏层的数量。 看来五个可能太多,而一个可能还不够。
- 试验每个隐藏层相对于层数的神经元数量。
- 尝试添加丢弃或正则化。
- 尝试通过尝试使用 SGD 或 RMS 属性而不是 Adam 或通过对 Adam 使用不同的学习率来进一步减少模型误差。
深度神经网络有许多活动部分,有时要达到最佳状态是一个疲惫的概念。 您必须确定您的模型是否足够好。
保存和加载经过训练的 Keras 模型
您不太可能会训练一个深层的神经网络,然后将其应用到同一脚本中。 最有可能的是,您将需要训练网络,然后保存结构和权重,以便可以将其用于设计用于对新数据进行评分的面向生产的应用中。 为此,您需要能够保存和加载模型。
在 Keras 中保存模型非常简单。 您可以使用模型实例的.save()
方法将网络结构和权重保存到hdf5
文件,如以下代码所示:
model.save("regression_model.h5")
这就是全部。 从磁盘加载模型非常简单。 此处提供了执行此操作的代码供您参考:
from keras.models import load_model
model = load_model("regression_model.h5")
总结
当您考虑深度学习时,您可能会想到令人印象深刻的复杂计算机视觉问题,但是即使对于像这样的简单回归问题,深度神经网络也可能有用。 希望我已经证明了这一点,同时还介绍了 Keras 语法并向您展示了如何构建一个非常简单的网络。
随着我们的继续,我们将遇到更多的复杂性。 更大的网络,更复杂的成本函数以及高维输入数据。 但是,我在本章中使用的过程在大多数情况下将保持不变。 在每种情况下,我们都将概述问题,确定输入和输出,选择成本函数,创建网络架构,最后训练和调整模型。
如果考虑以下因素,则在深度神经网络中通常可以独立地控制和减少偏差和方差:
- 偏差:可以通过增加模型复杂度来减少此偏差。 其他神经元或层将有所帮助。 添加数据并不能真正帮助减少偏差。
- 方差:可以通过添加数据或正则化来减少此变化。
在下一章中,我们将讨论如何使用 TensorBoard 更快地对深度神经网络进行优化和故障排除。
三、使用 TensorBoard 监控网络训练
在本章中,我将向您展示如何使用 TensorBoard 帮助更快更轻松地训练深度神经网络。 我认为 TensorBoard 是一个很棒的工具,经常被忽略,而它又常常被拖到脚注或上一章中。 现在,让我们看一下 TensorBoard,以便我们可以立即开始利用它。
我们将在本章介绍以下主题:
- TensorBoard 的简要概述
- 设置 TensorBoard
- 将 Keras 连接到 TensorBoard
- 使用 TensorBoard
TensorBoard 的简要概述
TensorBoard 是一个基于 Web 的应用,可以帮助您可视化 TensorFlow 中创建的深度神经网络的指标,参数和结构。 它将帮助您更快,更轻松地调试和优化深度神经网络。
正如您现在可能已经猜到的那样,深度神经网络可能变得相当复杂。 不幸的是,这意味着很多事情可能出错。 众所周知,我时不时地会犯一个错误,而当错误发生在一个深度神经网络内部时,该深度神经网络位于一个框架内,该框架在另一个框架上运行,在一个 GPU 上运行,很难找到这些错误。 他们。 TensorBoard 可能是您需要在其他本来很暗的房间中发现问题的手电筒。 TensorBoard 将允许您在训练网络时监视指标和参数的变化,这可以大大加快故障排除速度。
TensorBoard 也非常适合优化。 借助 TensorBoard,您可以直观地比较多个模型运行。 这使您可以试验不断变化的架构和超参数,然后相对于网络的其他运行评估那些变化。 所有这一切都可能在每个周期发生,因此如果您愿意,您可以取消效果不佳的模型运行,从而节省了时间和金钱。 您可以在这个页面上阅读有关 TensorBoard 的更多信息。
设置 TensorBoard
TensorBoard 是一个独立的 Web 应用。 您将通过网络浏览器使用它。 设置需要两个步骤。 首先,我们将设置 TensorBoard 以可视化在 TensorFlow 和 Keras 中构建的网络,然后我们将设置 Keras 与 TensorBoard 共享信息。
本节介绍 TensorBoard 的设置。 接下来的内容将涉及修改 Keras 代码以与 TensorBoard 共享信息。
安装 TensorBoard
如果您已经安装了 TensorFlow,则您的机器上可能已经安装了 Tensorboard。 万一您可以安装和更新 TensorBoard,可以使用pip
进行安装,就像 Keras 和 TensorFlow 一样。 要安装它,只需运行以下命令:
pip install -U tensorboard
TensorBoard 如何与 Keras/TensorFlow 交互
TensorBoard 和 TensorFlow 使用公共日志目录共享信息。 在 Keras 和 TensorFlow 训练中,Keras 将指标和激活直方图(稍后将对此进行详细介绍)写入您指定的日志目录中。 现在,让我们使用以下代码在主目录中为该示例创建一个日志目录:
mkdir ~/ch3_tb_log
运行 TensorBoard
剩下的就是启动 TensorBoard 进程。 我们可以使用以下代码启动 TensorBoard:
tensorboard --logdir ~/ch3_tb_log --port 6006
您可能已经猜到了,--logdir
指定我们刚刚创建的目录,--port 6006
指定 TensorBoard 将在其上运行的端口。 端口6006
是默认端口。 但是,您可以使用所需的任何端口。
现在,您应该可以通过将浏览器指向http://<ip address>:6006
来导航到 TensorBoard URL。
如果使用的是云服务,则可能还需要调整防火墙或安全规则,以允许通过端口6006
连接到服务器。 在 Amazon Web Services(AWS)上,您可以通过编辑与您的 EC2 实例关联的安全组中的入站规则来执行此操作:
您可能不希望像我上面那样允许全世界范围内的开放访问。 这只是一个测试实例,因此我不太关心安全性,无论如何我都喜欢过着危险的生活。
如果一切正常,您应该看到一个空的 TensorBoard,如下所示:
不用担心,我们很快就会填满。
将 Keras 连接到 TensorBoard
现在 TensorBoard 已启动并正在运行,剩下的就是告诉 Keras 将 TensorBoard 日志写入我们上面指定的目录。 幸运的是,这确实很容易实现,它为我们提供了一个很好的机会来了解 Keras 中称为 Keras 回调的特殊函数类。
引入 Keras 回调
Keras 中的回调是可以在训练过程中运行的函数。 他们可以做各种伟大的事情,例如在某个周期之后节省模型权重,记录事情,更改超参数或方便地编写 TensorBoard 日志文件。 您甚至可以创建自己的自定义回调。
在下一节中,我们将使用 TensorBoard 回调。 但是,我鼓励您在这个页面上查看 Keras 中可用的所有回调。
TensorBoard 回调是可以在模型训练之前进行配置和实例化的对象。 我们将创建这些回调的列表。 一旦创建了要用于深度神经网络的回调列表,我们就可以将该列表作为参数传递给模型的.fit()
方法。 然后,将在每个周期或 Keras 适当时使用这些回调。 在我们继续下一个示例时,这将更有意义。
创建一个 TensorBoard 回调
在本章中,我通过复制第 2 章“开始使用深度学习来解决回归问题”的网络和数据。 我们将做一些简单的添加来添加 TensorBoard 回调。 让我们从修改我们首先构建的mlp
开始。
首先,我们需要使用以下代码导入 TensorBoard 回调类:
from keras.callbacks import TensorBoard
然后,我们将启动回调。 我喜欢在创建所有回调的函数中执行此操作,以使事情精心制作和整理。 下面的create_callbacks()
函数将返回我们将传递给.fit()
的所有回调的列表。 在这种情况下,它将返回一个包含一个元素的列表:
def create_callbacks():
tensorboard_callback = TensorBoard(log_dir='~/ch3_tb_log/mlp',
histogram_freq=1, batch_size=32, write_graph=True,
write_grads=False)
return [tensorboard_callback]
在继续之前,我们先介绍一下这里使用的一些参数:
log_dir
:这是我们将为 TensorBoard 写入日志文件的路径。
您可能已经注意到,我正在将 MLP 网络的 TensorBoard 回调的日志写入~/ch_3_tb_log/mlp
,这将在我们为 TensorBoard 指定的目录下创建一个新的目录mlp
。 这是故意的。 我们将配置在第 2 章,“使用深度学习解决回归问题”训练的深度神经网络模型,以登录到单独的目录~/ch_3_tb_log/dnn
。 这样做将使我们能够比较两个模型的运行。
histogram_freq
:这指定我们将多长时间计算一次激活和权重的直方图(以周期为单位)。 它的默认值为 0,这会使日志更小,但不会生成直方图。 我们将介绍为什么以及何时您会对直方图感兴趣。batch_size
:这是用于计算直方图的批量大小。 默认为 32。write_graph
:此函数为布尔值。 这将告诉 TensorBoard 可视化网络图。 这可能非常方便,但也会使日志变得很大。write_grads
:此函数也是布尔值。 这将告诉 TensorBoard 也计算梯度的直方图。
由于 TensorFlow 会自动为您计算梯度,因此很少使用。 但是,如果您要使用自定义激活或费用,它可能是出色的故障排除工具。
TensorBoard 回调可以接受用于在图像上运行神经网络或通过使用嵌入式层的其他参数。 我们将在本书的后面介绍这两个方面。 如果您对这些函数感兴趣,请访问 TensorBoard API 文档。
现在,我们只需要创建回调列表,并将mlp
与callbacks
参数匹配即可。 看起来像这样:
callbacks = create_callbacks()
model.fit(x=data["train_X"], y=data["train_y"], batch_size=32,
epochs=200, verbose=1, validation_data=(data["val_X"],
data["val_y"]), callbacks=callbacks)
为了清楚起见,我将新参数加粗了。
在继续使用 TensorBoard 之前,我将以与检测mlp
相同的方式来检测深度神经网络。 唯一的代码更改是我们将 TensorBoard 日志写入的目录。 下面给出了实现该方法的方法,供您参考:
def create_callbacks():
tensorboard_callback = TensorBoard(log_dir='./ch3_tb_log/dnn',
histogram_freq=1, batch_size=32, write_graph=True, write_grads=False)
return [tensorboard_callback]
其余代码将相同。 现在,让我们再次训练每个网络,看看 TensorBoard。
使用 TensorBoard
现在我们已经完全配置了 TensorBoard 并告诉我们的网络如何向其发送日志数据,我们可以开始利用它了。 在本章的其余部分,我将向您展示一些我最喜欢的使用 TensorBoard 的方式。 TensorBoard 的功能不只此而已,我们将在本书的其余部分中重新讨论其他功能。
可视化训练
由于我们已在第 2 章“使用了深度学习解决回归问题”中使用这两种模型编写了日志数据,因此可以使用 TensorBoard 以图形方式比较这两种模型。 打开 TensorBoard 并转到SCALARS
选项卡。 您应该会看到类似这样的内容。 您可能需要单击loss
和val_loss
来展开图形:
张量板显示模型的损失图和val_loss
图
如果您查看屏幕的左下角,则应注意,我们创建的每个目录都有与之关联的运行。 两者均处于选中状态。 这意味着在我们的图形上,我们将看到两个模型的输出。
TensorBoard 可以容纳许多运行,并且您可以通过正则表达式过滤它们(例如^dnn
将显示所有以dnn
开头的运行)。 这意味着,如果您通过许多实验或运行(例如超参数优化)来搜索最佳模型,则可以在明确并一致地命名运行,并包含有意义的超参数和架构信息的情况下,以这个名字快速浏览它们!
这些图上的默认 X 比例尺是周期。 Y 值是我们选择的损失函数,即 MAE。 您可以单击图形以浏览它们并拖动以缩放。
看到这样的图,我们真的可以看到每个网络的相对偏差和方差。 虽然模型之间在训练损失方面有很好的分离,但深度神经网络在验证集上只得到了一点点改善,这表明我们已经进入了过拟合的领域。
可视化网络图
虽然能够查看我们的训练过程并比较模型显然很不错,但这并不是 TensorBoard 所能做的。 我们还可以使用它来可视化网络结构。 在这里,我导航到GRAPHS
并提出了深度神经网络的结构:
TensorBoard 显示深度神经网络的结构
训练节点代表输入张量,默认情况下,正是这个巨型章鱼以某种无益的方式连接到图的其余部分。 要解决此问题,您只需单击该节点,然后单击从主图中删除。 然后将其移到侧面。
可视化损坏的网络
TensorBoard 是一个出色的故障排除工具。 为了证明这一点,我将复制我们的深度神经网络并将其破坏! 幸运的是,打破神经网络真的很容易。 相信我,我已经无意间做了这件事,以至于我现在基本上是专家。
想象一下,您刚刚训练了一个新的神经网络,并且看到损失看起来像这样:
该网络的损失函数被卡住,并且比我们之前的运行要高得多。 什么地方出了错?
导航到 TensorBoard 的HISTOGRAMS
部分,并可视化第一个隐藏层。 让我们比较两个网络中隐藏层 1 的权重直方图:
显示两个网络中隐藏层 1 的权重直方图的屏幕截图
对于标记为 dnn 的网络的偏差和权重,您将看到权重分布在整个图中。 您甚至可以说每个分布都可能是正态分布。
您也可以在“分布”部分比较权重和偏差。 两者都以略有不同的方式呈现大多数相同的信息。
现在,看看我们破碎的网络的权重和偏置。 并不是这样分散,实际上的权重基本上是相同的。 网络并不是真正的学习。 该层中的每个神经元看起来或多或少都是相同的。 如果您查看其他隐藏层,则会看到更多相同的层。
您可能想知道我是怎么做到的。 您很幸运,我会分享我的秘密。 毕竟,您永远都不知道何时需要断开自己的网络。 为了解决问题,我将网络中的每个神经元初始化为完全相同的值。 发生这种情况时,每个神经元在反向传播期间收到的误差是完全相同的,并且更改的方式也完全相同。 网络然后无法破坏对称性。 以随机方式将权重初始化到深度神经网络非常重要,如果您违反了该规则,就会发生这种情况!
遇到问题时,可以像这样完全使用 TensorBoard。 请记住,我们的深度神经网络有 4033,在深度学习领域中,它仍然可以算作很小的。 使用 TensorBoard,我们能够直观地检查 4033 个参数并确定问题。 TensorBoard 是一个用于深度学习的暗室中的神奇手电筒。
总结
在本章中,我们讨论了如何安装,配置和使用 TensorBoard。 我们讨论了如何使用 TensorBoard 在 TensorBoard 的SCALARS
部分中的每个周期检查模型的损失函数,从而直观地比较模型。 然后,我们使用 TensorsBoard 的GRAPHS
部分来可视化网络结构。 最后,我们通过查看直方图向您展示了如何使用 TensorBoard 进行故障排除。
在下一章中,我们将研究如何使用 Keras 和 TensorFlow 解决二分类问题,从而扩展我们的深度学习技巧。
四、使用深度学习解决二分类问题
在本章中,我们将使用 Keras 和 TensorFlow 解决棘手的二分类问题。 我们将首先讨论深度学习对此类问题的利弊,然后我们将继续使用与第 2 章“学习解决回归问题”中使用的相同框架建立解决方案。 最后,我们将更深入地介绍 Keras 回调,甚至使用自定义回调来实现每个周期的受试者工作特征的曲线下面积(ROC AUC)指标。
我们将在本章介绍以下主题:
- 二分类和深度神经网络
- 案例研究 – 癫痫发作识别
- 在 Keras 中建立二分类器
- 在 Keras 中使用检查点回调
- 在自定义回调中测量 ROC AUC
- 测量精度,召回率和 f1 得分
二分类和深度神经网络
二分类问题(例如回归问题)是非常常见的机器学习任务。 如此之多,以至于任何一本有关深度学习的书都无法完整覆盖。 可以肯定的是,我们还没有真正达到深度神经网络的甜蜜点,但是我们进展顺利。 在开始编写代码之前,让我们谈谈在选择深度神经网络来解决此类问题时应考虑的权衡。
深度神经网络的好处
与更传统的分类器(例如逻辑回归模型)或什至基于树的模型(例如随机森林或梯度提升机)相比,深度神经网络有一些不错的优点。
与回归一样,在第 2 章“使用深度学习解决回归问题”中,我们不需要选择或筛选特征。 在本章选择的问题中,有 178 个输入变量。 每个输入变量都是来自标记为x1..x178
的脑电图(EEG)的特定输入。 即使您是医生,也很难理解这么多特征与目标变量之间的关系。 这些特征中的某些特征很可能是不相关的,而这些变量和目标之间可能存在一些更高级别的交互,这是一个更好的机会。 如果使用传统模型,则经过特征选择步骤后,我们将获得最佳模型表现。 使用深度神经网络时不需要这样做。
深度神经网络的缺点
正如我们在第 2 章“使用深度学习解决回归问题”所述,深度神经网络不容易解释。 虽然深度神经网络是出色的预测器,但要理解它们为何得出自己的预测并不容易。 需要重复的是,当任务是要了解哪些特征与目标的变化最相关时,深度神经网络并不是工作的工具。 但是,如果目标是原始预测能力,则应考虑使用深度神经网络。
我们还应该考虑复杂性。 深度神经网络是具有许多参数的复杂模型。 找到最佳的神经网络可能需要花费时间和实验。 并非所有问题都能确保达到如此复杂的水平。
在现实生活中,我很少使用深度学习作为结构化数据问题的第一个解决方案。 我将从可能可行的最简单模型开始,然后根据问题的需要迭代进行深度学习。 当问题域包含图像,音频或文本时,我更有可能从深度学习开始。
案例研究 – 癫痫发作识别
您可能已经猜到了,我们将要解决二分类问题。 我们将使用与在第 2 章“使用深度学习解决回归问题”建立的框架相同的框架来计划问题,并根据需要对其进行修改。 您可以在本书的 GitHub 存储库中的第 4 章“使用深度学习解决回归问题”,找到本章的完整代码。
定义我们的数据集
我们将在本章中使用的数据集称为癫痫发作识别数据集。 数据最初来自Andrzejak RG 等人在 Phys 上发表的论文《指示脑电活动的时间序列中的非线性确定性和有限维结构:对记录区域和大脑状态的依赖性》。您可以在 UCI 机器学习存储库中找到数据。
我们的目标是创建一个深度神经网络,根据输入特征,该网络可以预测患者是否有癫痫发作。
加载数据
我们可以使用以下函数加载本章中使用的数据。 它与我们在第 2 章中使用的函数非常相似,但是适用于此数据集。
from sklearn.preprocessing import StandardScaler
def load_data():
"""Loads train, val, and test datasets from disk"""
train = pd.read_csv(TRAIN_DATA)
val = pd.read_csv(VAL_DATA)
test = pd.read_csv(TEST_DATA)
# we will use a dict to keep all this data tidy.
data = dict()
data["train_y"] = train.pop('y')
data["val_y"] = val.pop('y')
data["test_y"] = test.pop('y')
# we will use sklearn's StandardScaler to scale our data to 0 mean, unit variance.
scaler = StandardScaler()
train = scaler.fit_transform(train)
val = scaler.transform(val)
test = scaler.transform(test)
data["train_X"] = train
data["val_X"] = val
data["test_X"] = test
# it's a good idea to keep the scaler (or at least the mean/variance) so we can unscale predictions
data["scaler"] = scaler
return data
模型输入和输出
该数据集中有 11,500 行。 数据集的每一行包含 178 个数据点,每个数据点代表 1 秒钟的 EEG 记录样本和相应的患者状态,跨 100 个不同患者生成。
数据集中有五个患者状态。 但是,状态 2 至状态 5 的患者未发生癫痫发作。 状态 1 的患者正在发作。
我已经修改了原始数据集,通过将状态 2-5 更改为 0 级(表示无癫痫发作)和将 1 级(表示有癫痫发作)将状态重新定义为二分类问题。
与第 2 章“使用深度学习解决回归问题”中的回归问题一样,我们将使用 80% 的训练,10% 的 val,10% 的测试分割。
成本函数
我们需要分类器来预测癫痫发作的可能性,即类别 1。这意味着我们的输出将被限制为[0, 1]
,就像在传统的逻辑回归模型中一样。 在这种情况下,我们的成本函数将是二元交叉熵,也称为对数损失。 如果您以前使用过分类器,那么您可能很熟悉此数学运算; 但是,作为复习,我将在这里包括。
对数损失的完整公式如下所示:
这可能更简单地看作是两个函数的集合,对于情况y[i] = 0
和y[i] = 1
,一个函数:
当y[i] = 1
,
当y[i] = 0
。
对数函数在这里用于产生单调函数(一个一直在增加或减少的函数),我们可以轻松微分它。 与所有成本函数一样,我们将调整网络参数以最小化网络成本。
使用指标评估表现
除了loss
函数之外,Keras 还使我们可以使用度量标准来帮助判断模型的表现。 虽然最大程度地降低损失是有好处的,但在给定loss
函数的情况下,我们如何期望模型执行效果并不是特别明显。 度量标准并不用于训练模型,它们只是用来帮助我们了解当前状态。
尽管损失对我们而言并不重要,但准确率却对我们而言意义重大。 我们人类非常了解准确率。
Keras 定义二元精度如下:
def binary_accuracy(y_true, y_pred):
return K.mean(K.equal(y_true, K.round(y_pred)), axis=-1)
这实际上只是将正确答案的数量除以总答案的一种聪明方法,这是我们自从上学初期就可能一直在做的一项工作,目的是计算出考试的成绩。
您可能想知道我们的数据集是否平衡,因为准确率对于不平衡的数据集而言效果很差。 实际上这是不平衡的。 只有五分之一的数据集是类 1。我们将 ROC AUC 分数作为自定义回调来计算,以解决此问题。 在 Keras 中未将 ROC 用作度量标准,因为度量标准是针对每个小型批次计算的,并且 ROC AUC 分数并非真正由小型批次定义。
在 Keras 中建立二分类器
既然我们已经定义了问题,输入,期望的输出和成本函数,我们就可以在 Keras 中快速编写其余代码。 我们唯一缺少的是网络架构。 我们将很快讨论更多。 关于 Keras 的我最喜欢的事情之一是调整网络架构有多么容易。 如您所见,在找到最佳架构之前,可能需要进行大量实验。 如果是这样,那么易于更改的框架会使您的工作变得更加轻松!
输入层
和以前一样,我们的输入层需要知道数据集的维度。 我喜欢在一个函数中构建整个 Keras 模型,并允许该函数传递回已编译的模型。 现在,此函数仅接受一个参数,即特征数。 以下代码用于定义输入层:
def build_network(input_features=None):
# first we specify an input layer, with a shape == features
inputs = Input(shape=(input_features,), name="input")
隐藏层
我们已经定义了输入,这很容易。 现在我们需要确定网络架构。 我们如何知道应该包括多少层以及应该包含多少个神经元? 我想给你一个公式。 我真的会。 不幸的是,它不存在。 实际上,有些人正在尝试构建可以学习其他神经网络的最佳架构的神经网络。 对于我们其余的人,我们将不得不尝试,寻找自己或借用别人的架构。
如果我们使用的神经元过多会怎样?
如果我们使网络架构过于复杂,则会发生两件事:
- 我们可能会开发一个高方差模型
- 该模型将比不太复杂的模型训练得慢
如果我们增加许多层,我们的梯度将变得越来越小,直到前几层几乎没有训练为止,这就是梯度消失问题。 我们离那还很遥远,但是我们稍后会讨论。
用说唱传奇克里斯托弗·华莱士(又名臭名昭著的 B.I.G.)的话来说,我们遇到的神经元越多,看到的问题就越多。 话虽如此,方差可以通过丢弃法,正则化和提早停止进行管理,GPU 计算的进步使更深层次的网络成为可能。
如果我必须在神经元太多或太少的网络之间进行选择,而我只能尝试一个实验,那么我宁愿选择稍微过多的神经元。
如果我们使用的神经元太少会怎样?
想象一下,我们没有隐藏层,只有输入和输出的情况。 我们在第 1 章“深度学习的基础知识”中讨论了该架构,在此我们展示了如何无法为XOR
函数建模。 这样的网络架构无法对数据中的任何非线性进行建模,因此无法通过网络进行建模。 每个隐藏层都为特征工程越来越复杂的交互提供了机会。
如果选择的神经元太少,则结果可能如下:
- 真正快速的神经网络
- 那有很高的偏差,而且预测不是很好
选择隐藏层架构
因此,既然我们了解选择太多参数而不是选择太多参数的价格和行为,那么从哪里开始呢? 据我所知,剩下的只是实验。
测量这些实验可能很棘手。 如果像我们的早期网络一样,您的网络训练很快,那么可以在多种架构中实现诸如交叉验证之类的东西,以评估每种架构的多次运行。 如果您的网络需要很长时间进行训练,则可能会留下一些统计上不太复杂的信息。 我们将在第 6 章“超参数优化”中介绍网络优化。
一些书籍提供了选择神经网络架构的经验法则。 我对此表示怀疑和怀疑,您当然不会在这里找到一个。
为我们的示例编码隐藏层
对于我们的示例问题,我将使用五个隐藏层,因为我认为特征之间存在许多交互。 我的直觉主要基于领域知识。 阅读数据描述后,我知道这是时间序列的横截面切片,并且可能是自动相关的。
我将从第一层的 128 个神经元开始(略小于我的输入大小),然后在接近输出时减半到 16 个神经元。 这完全不是凭经验,它仅基于我自己的经验。 我们将使用以下代码定义隐藏层:
x = Dense(128, activation='relu', name="hidden1")(inputs)
x = Dense(64, activation='relu', name="hidden2")(x)
x = Dense(64, activation='relu', name="hidden3")(x)
x = Dense(32, activation='relu', name="hidden4")(x)
x = Dense(16, activation='relu', name="hidden5")(x)
在每一层中,我都使用relu
激活,因为它通常是最好和最安全的选择,但是要确保这也是可以试验的超参数。
输出层
最后,我们需要网络的输出层。 我们将使用以下代码定义输出层:
prediction = Dense(1, activation='sigmoid', name="final")(x)
在此示例中,我们正在构建一个二分类器,因此我们希望我们的网络输出观察结果属于类 1 的概率。幸运的是,sigmoid
激活将精确地做到这一点,将网络输出限制在 0 到 1 之间。
放在一起
将所有代码放在一起,剩下的就是编译我们的 Keras 模型,将binary_crossentrophy
指定为我们的loss
函数,将accuracy
指定为我们希望在训练过程中监控的指标。 我们将使用以下代码来编译我们的 Keras 模型:
def build_network(input_features=None):
inputs = Input(shape=(input_features,), name="input")
x = Dense(128, activation='relu', name="hidden1")(inputs)
x = Dense(64, activation='relu', name="hidden2")(x)
x = Dense(64, activation='relu', name="hidden3")(x)
x = Dense(32, activation='relu', name="hidden4")(x)
x = Dense(16, activation='relu', name="hidden5")(x)
prediction = Dense(1, activation='sigmoid', name="final")(x)
model = Model(inputs=inputs, outputs=prediction)
model.compile(optimizer='adam', loss='binary_crossentropy',
metrics=["accuracy"])
return model
训练我们的模型
现在我们已经定义了模型,我们都准备对其进行训练。 我们的操作方法如下:
input_features = data["train_X"].shape[1]
model = build_network(input_features=input_features)
model.fit(x=data["train_X"], y=data["train_y"], batch_size=32, epochs=20, verbose=1, validation_data=(data["val_X"], data["val_y"]), callbacks=callbacks)
如果您已经阅读第 2 章“使用深度学习解决回归问题”,则应该看起来很熟悉。 在大多数情况下,实际上是相同的。 回调列表包含 TensorBoard 回调,因此让我们观看我们的网络训练 20 个周期,看看会发生什么:
尽管我们的训练损失继续下降,但我们可以看到val_loss
到处都在跳跃。 大约在第八个周期之后,我们就过拟合了。
有几种方法可以减少网络差异并管理这种过拟合,下一章将介绍大多数方法。 但是,在开始之前,我想向您展示一些有用的东西,称为检查点回调。
在 Keras 中使用检查点回调
在第 2 章“使用深度学习解决回归问题”中,我们看到了.save()
方法,该方法使我们可以在完成训练后保存 Keras 模型。 但是,如果我们可以不时地将权重写入磁盘,以便在上一个示例中及时返回,并在模型开始过拟合之前保存其版本,那不好吗? 然后,我们可以就此停止,并使用网络的最低方差版本。
这正是ModelCheckpoint
回调为我们所做的。 让我们来看看:
checkpoint_callback = ModelCheckpoint(filepath="./model-weights.{epoch:02d}-{val_acc:.6f}.hdf5", monitor='val_acc', verbose=1, save_best_only=True)
ModelCheckpoint
将为我们执行的工作是按计划的时间间隔保存模型。 在这里,我们告诉ModelCheckpoint
每当我们达到新的最佳验证精度(val_acc
)时都要保存模型的副本。 我们也可以监视验证损失或我们指定的任何其他指标。
文件名字符串将包含周期编号和运行的验证准确率。
当我们再次训练模型时,我们可以看到正在创建以下文件:
model-weights.00-0.971304.hdf5
model-weights.02-0.977391.hdf5
model-weights.05-0.985217.hdf5
因此,我们可以看到在第 5 个阶段之后,我们无法达到val_acc
的最佳水平,并且没有编写检查点。 然后,我们可以返回并从检查点 5 加载权重,并使用最佳模型。
这里有一些大的假设,将第 5 期称为最佳。 您可能需要多次运行网络,尤其是在您的数据集相对较小的情况下,就像本书中的早期示例一样。 我们可以肯定地说,这个结果将是不稳定的。
顺便说一下,这是防止过拟合的非常简单的方法。 我们可以选择使用方差太大之前发生的模型检查点。 这是做类似提前停止的一种方法,这意味着当我们看到模型没有改善时,我们会在指定的周期数之前停止训练。
在自定义回调中测量 ROC AUC
让我们再使用一个回调。 这次,我们将构建一个自定义的回调,以在每个周期结束时在训练集和测试集上计算曲线下的接收器工作特征区域(ROC AUC)。
在 Keras 中创建自定义回调实际上非常简单。 我们需要做的就是创建一个固有的Callback
类,并覆盖所需的方法。 由于我们想在每个周期结束时计算 ROC AUC 分数,因此我们将在_epoch_end
上覆盖:
from keras.callbacks import Callback
class RocAUCScore(Callback):
def __init__(self, training_data, validation_data):
self.x = training_data[0]
self.y = training_data[1]
self.x_val = validation_data[0]
self.y_val = validation_data[1]
super(RocAUCScore, self).__init__()
def on_epoch_end(self, epoch, logs={}):
y_pred = self.model.predict(self.x)
roc = roc_auc_score(self.y, y_pred)
y_pred_val = self.model.predict(self.x_val)
roc_val = roc_auc_score(self.y_val, y_pred_val)
print('\n *** ROC AUC Score: %s - roc-auc_val: %s ***' %
(str(roc), str(roc_val)))
return
现在,我们已经创建了新的自定义回调,我们可以将其添加到回调创建器函数中,如以下代码所示:
def create_callbacks(data):
tensorboard_callback = TensorBoard(log_dir=os.path.join(os.getcwd(),
"tb_log", "5h_adam_20epochs"), histogram_freq=1, batch_size=32,
write_graph=True, write_grads=False)
roc_auc_callback = RocAUCScore(training_data=(data["train_X"],
data["train_y"]), validation_data=(data["val_X"], data["val_y"]))
checkpoint_callback = ModelCheckpoint(filepath="./model-weights.
{epoch:02d}-{val_acc:.6f}.hdf5", monitor='val_acc',verbose=1,
save_best_only=True)
return [tensorboard_callback, roc_auc_callback, checkpoint_callback]
这里的所有都是它的! 您可以用相同的方式实现其他任何指标。
测量精度,召回率和 f1 得分
正如您可能对其他二分类器有丰富的经验一样,我认为用几句话讨论如何创建与更传统的二分类器一起使用的一些常规指标是明智的。
Keras 函数式 API 与 scikit-learn 中可能使用的 API 之间的区别是.predict()
方法的行为。 当使用 Keras 时,对于n
个样本中的每个,.predict()
将返回k
类概率的nxk
矩阵。 对于二分类器,将只有一列,即类别 1 的类别概率。这使 Keras .predict()
更像 scikit-learn 中的.predict_proba()
。
在计算精度,召回率或其他基于类的指标时,您需要通过选择一些操作点来转换.predict()
输出,如以下代码所示:
def class_from_prob(x, operating_point=0.5):
x[x >= operating_point] = 1
x[x < operating_point] = 0
return x
完成此操作后,您可以随意重用sklearn.metric
中的典型指标,如以下代码所示:
y_prob_val = model.predict(data["val_X"])
y_hat_val = class_from_prob(y_prob_val)
print(classification_report(data["val_y"], y_hat_val))
总结
在本章中,我们讨论了使用深度神经网络作为二分类器。 我们花了很多时间讨论网络架构的设计选择,并提出了这样的想法,即搜索和试验是当前选择架构的最佳方法。
我们学习了如何在 Keras 中使用检查点回调来使我们能够及时返回并找到具有所需表现特征的模型版本。 然后,我们在训练的模型中创建并使用了自定义回调来衡量 ROC AUC 得分。 我们总结了如何将 Keras .predict()
方法与sklearn.metrics
中的传统指标结合使用。
在下一章中,我们将研究多分类,我们将更多地讨论如何防止过拟合。
五、使用 Keras 解决多分类问题
在本章中,我们将使用 Keras 和 TensorFlow 来处理具有许多自变量的 10 类多分类问题。 和以前一样,我们将讨论使用深度学习解决此问题的利弊; 但是,您不会发现很多缺点。 最后,我们将花费大量时间讨论控制过拟合的方法。
我们将在本章介绍以下主题:
- 多分类和深度神经网络
- 案例研究 – 手写数字分类
- 在 Keras 中建立多分类器
- 通过丢弃控制方差
- 通过正则化控制方差
多分类和深度神经网络
这里是! 我们终于找到了有趣的东西! 在本章中,我们将创建一个深度神经网络,该网络可以将观察结果分类为多个类别,这是神经网络确实发挥出色的地方之一。 让我们再谈一些关于深度神经网络对此类问题的好处。
就像我们都在谈论同一件事一样,让我们在开始之前定义多分类。 想象我们有一个分类器,该分类器将各种水果的权重作为输入,并根据给定的权重来预测水果。 输出可能恰好是一组类(苹果,香蕉,芒果等)中的一个类。 这是多分类,不要与多标签混淆,在这种情况下,模型可能会预测一组标签是否将应用于互不排斥的观察结果。
优点
当我们需要预测大量类时,相对于其他模型,深度神经网络的确是出色的执行者。 当输入向量中的特征数量变大时,神经网络自然适合。 当这两种情况都集中在同一个问题上时,我可能就是从那里开始的。 这正是我们将在本章中要研究的案例研究中看到的问题的类型。
缺点
和以前一样,更简单的模型可能会比深度学习模型做的更好或更好。 在所有其他条件都相同的情况下,您可能应该支持更简单的模型。 但是,随着类数的增加,深度神经网络复杂性的弊端通常会减少。 为了容纳许多类,许多其他模型的实现必须变得非常复杂,有些模型甚至可能需要优化作为超参数用于模型的多类策略。
案例研究 - 手写数字分类
我们将使用多分类网络来识别手写数字的相应类。 与以前一样,如果您想继续阅读,可以在本书的 Git 存储库中的Chapter05
下找到本章的完整代码。
问题定义
MNIST
数据集已成为几乎规范的神经网络数据集。 该数据集由 60,000 个手写数字组成的图像,属于代表它们各自数字(0, 1, 2 ... 9)
的 10 类。 由于此数据集变得如此普遍,因此许多深度学习框架都在 API 中内置了 MNIST 加载方法。 TensorFlow 和 Keras 都拥有一个,我们将使用 Keras MNIST 加载器使我们的生活更轻松。 但是,如果您想从原始数据中获取数据,或者想进一步了解 MNIST 的历史,可以在这个页面中找到更多信息。
模型输入和输出
我们的数据集已被划分为一个训练集,该训练集的大小为 50,000 个观察值,一个测试集为 10,000 个观察值。 我将从训练集中获取最后 5,000 个观察值,并将其用作验证集。
拼合输入
每个输入观察都是一个 28 像素乘 28 像素的黑白图像。 像这样的一幅图像在磁盘上表示为28x28
的矩阵,其值介于 0 到 255 之间,其中每个值都是该像素中黑色的强度。 至此,我们只知道如何在二维向量上训练网络(稍后我们将学习一种更好的方法); 因此我们将这个28x28
矩阵展平为1 x 784
输入向量。
一旦我们堆叠了所有这些1x784
向量,就剩下50,000 x 784
训练集。
如果您对卷积神经网络有丰富的经验,那么您可能现在正在翻白眼,如果您还没有,那么很快就会有更好的方法,但是不要太快地跳过本章。 我认为扁平化的MNIST
是一个非常好的数据集,因为它的外观和行为与我们在许多投入领域(例如,物联网,制造业,生物,制药和医疗用例)中遇到的许多复杂的现实生活问题非常相似)。
类别输出
我们的输出层将为每个类包含一个神经元。 每个类别的关联神经元将经过训练,以将该类别的概率预测为介于 0 和 1 之间的值。我们将使用一种称为 softmax 的特殊激活,以确保所有这些输出总和为 1,我们将介绍 softmax 的详细信息。
这意味着我们将需要为我们的类创建一个二元/分类编码。 例如,如果我们使y = [0, 3, 2, 1]
并对其进行分类编码,则将具有如下矩阵y
:
幸运的是,Keras 为我们提供了方便的功能来进行这种转换。
成本函数
我们将使用的成本函数称为多项式交叉熵。 多项式交叉熵实际上只是在第 4 章“使用 Keras 进行二分类”中看到的二元交叉熵函数的概括。
让我们一起看看它们,而不只是显示分类交叉熵。 我要断言它们是平等的,然后解释原因:
前面的等式是正确的(m = 2
时)
好吧,别害怕。 我知道,这是一堆数学。 绝对交叉熵方程是一直存在于右边的方程。 二元交叉熵紧随其后。 现在,设想m = 2
的情况。 在这种情况下,您可能会发现,j = 0
和j = 1
的y[ij]log(p[ij])
的和,对于i
中的每个值,等于来自二元交叉熵的结果。 希望这种减少足以使分类交叉熵有意义。 如果没有,我建议选择一些值并进行编码。 只需一秒钟,稍后您将感谢我!
指标
分类交叉熵是一个很好的成本函数,但实际上并不能告诉我们很多我们可以从网络中获得的预测质量。 不幸的是,像 ROC AUC 这样的二分类指标也对我们没有太大帮助,因为我们超越了二分类 AUC 的定义并没有。
鉴于缺少更好的指标,我将使用准确率作为人类可以理解的训练指标。 幸运的是,在这种情况下,我的数据集是平衡的。 正如您所期望的那样,准确率是指真实值与预测值的匹配次数除以数据集的总大小。
训练结束后,我将使用 scikit-learn 的分类报告向我们显示每个类的精确度和召回率。 如果您愿意,也可以为此使用混淆矩阵。
在 Keras 中建立多分类器
由于我们现在有一个定义明确的问题,因此可以开始对其进行编码。 如前所述,这次我们必须对输入和输出进行一些转换。 在我们建立网络的过程中,我将向您展示这些内容。
载入 MNIST
对我们来说幸运的是,在 Keras 中内置了一个 MNIST 加载函数,该函数可以检索 MNIST 数据并为我们加载。 我们需要做的就是导入keras.datasets.mnist
并使用load_data()
方法,如以下代码所示:
(train_X, train_y), (test_X, test_y) = mnist.load_data()
train_X
的形状为50,000 x 28 x 28
。正如我们在“模型输入和输出”部分中所述,我们将需要将28x28
矩阵展平为 784 个元素向量。 NumPy 使这变得非常容易。 以下代码说明了此技术:
train_X = train_X.reshape(-1, 784)
有了这种方式,我们应该考虑扩展输入。 以前,我们使用 scikit-learn 的StandardScaler
。 MNIST 不需要这样做。 由于我们知道每个像素都在 0 到 255 的相同范围内,因此我们可以通过除以255
轻松地将值转换为 0 和 1 之间的值,然后在执行操作之前将数据类型显式转换为float32
,如以下代码所示:
train_X = train_X.astype('float32')
train_X /= 255
正如我们在“模型输入和输出”部分中所述,在加载数据时,我们可能应该将因变量向量转换为分类向量。 为此,我们将在以下代码的帮助下使用keras.utils.to_categorical()
:
train_y = to_categorical(train_y)
这样,我们的数据就可以进行训练了!
输入层
我们的输入层实际上与之前的示例保持不变,但我将在此处包括它以使其成为适当的快速参考:
def build_network(input_features=None):
inputs = Input(shape=(input_features,), name="input")
隐藏层
我将使用带有512
神经元的第一个隐藏层。 这比输入向量的 784 个元素略小,但这完全不是规则。 同样,此架构只是一个开始,并不一定是最好的。 然后,我将在第二和第三隐藏层中浏览大小,如以下代码所示:
x = Dense(512, activation='relu', name="hidden1")(inputs)
x = Dense(256, activation='relu', name="hidden2")(x)
x = Dense(128, activation='relu', name="hidden3")(x)
输出层
我们的输出层将包含 10 个神经元,一个观察值可能属于其中的每个可能的类。 这对应于我们在y
向量上使用to_categorical()
时施加的编码:
prediction = Dense(10, activation='softmax', name="output")(x)
如您所见,我们正在使用的激活称为 softmax。 让我们讨论一下softmax
是什么,以及为什么有用。
Softmax 激活
想象一下,如果不是使用深层神经网络,而是使用k
个逻辑回归,其中每个回归都预测单个类中的成员。 逻辑回归的集合(每个类一个)如下所示:
使用这组逻辑回归的问题是每个逻辑回归的输出都是独立的。 想象一下,在我们的集合中,这些逻辑回归中的一些不确定其所属类别的成员资格,从而导致多个答案在P(Y = k) = 0.5
附近。 这使我们无法将这些输出用作k
类中类成员资格的总体概率,因为它们不一定总和为 1。
Softmax 压缩所有这些逻辑回归的输出,使它们的总和为 1,从而将其用作整体类成员的概率,从而为我们提供了帮助。
softmax
函数如下所示:
(对于j = 1
至k
类,其中zj / zk
是属于k
的逻辑回归)
因此,如果将softmax
函数放在我们先前的回归集的前面,我们将得到一组类别概率,它们合计为 1,可以用作 k 个类别中成员资格的概率。 这改变了我们的整体函数,如下所示:
先前的函数通常称为多项式逻辑回归。 它有点像一层,仅输出和神经网络。 我们不再频繁使用多项式逻辑回归。 但是,我们当然可以一直使用softmax
函数。 对于本书中的大多数多分类问题,我们将使用softmax
,因此值得理解。
如果您像我一样,并且发现所有数学知识都难以阅读,那么在代码中查看softmax
可能会更容易。 因此,在继续操作之前,请使用以下代码段进行操作:
def softmax(z):
z_exp = [math.exp(x) for x in z]
sum_z_exp = sum(z_exp)
softmax = [round(i / sum_z_exp, 3) for i in z_exp]
return softmax
让我们快速尝试一个例子。 想象一下,我们有一组逻辑输出,如下所示:
z = np.array([0.9, 0.8, 0.2, 0.1, 0.5])
如果应用softmax
,我们可以轻松地将这些输出转换为相对的类概率,如下所示:
print(softmax(z))
[0.284, 0.257, 0.141, 0.128, 0.19]
放在一起
现在我们已经涵盖了各个部分,让我们看一下我们的整个网络。 这看起来与我们之前在本书中介绍的模型相似。 但是,我们使用的损失函数categorical_crossentropy
在本章的“成本函数”部分中介绍了。
我们将使用以下代码定义网络:
def build_network(input_features=None):
# first we specify an input layer, with a shape == features
inputs = Input(shape=(input_features,), name="input")
x = Dense(512, activation='relu', name="hidden1")(inputs)
x = Dense(256, activation='relu', name="hidden2")(x)
x = Dense(128, activation='relu', name="hidden3")(x)
prediction = Dense(10, activation='softmax', name="output")(x)
model = Model(inputs=inputs, outputs=prediction)
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=["accuracy"])
return model
训练
现在我们已经定义了神经网络并加载了数据,剩下的就是训练它了。
在本书中以及本书的其他几个示例中,我使用的是称为数据的字典,以绕过train_X
,val_X
和test_X
等各种数据集。 我使用这种表示法来保持代码的可读性,并且因为传递整个字典的必要性经常高于没有。
这是我将如何训练我们刚刚建立的模型的方法。
model = build_network(data["train_X"].shape[1])
model.fit(x=data["train_X"], y=data["train_y"],
batch_size=30,
epochs=50,
validation_data=(data["val_X"], data["val_y"]),
verbose=1,
callbacks=callbacks)
我正在使用与以前相同的回调。 我没有使用我们在第 4 章“使用 Keras 进行二分类”中构建的 ROC AUC 回调,因为 ROC AUC 没有为多分类器明确定义。
存在一些针对该问题的创造性解决方案。 例如,通过成对分析近似多类 ROC 和 ROC 表面下体积都是出色的论文,都可以解决这个问题。 但是,实际上,这些方法及其度量标准很少在 R 中使用,最常在 R 中实现。因此,到目前为止,让我们坚持使用多类准确率,并且远离 R。
让我们观看 TensorBoard 在我们的模型训练中:
在阅读下一段之前,请花点时间思考一下这些图形在告诉我们什么。 得到它了? 好的,让我们继续。
因此,这是一个熟悉的情况。 我们的训练损失正在继续下降,而我们的验证损失正在上升。 我们过拟合。 虽然当然可以选择提前停止,但让我向您展示一些处理过拟合的新技巧。 让我们在下一部分中查看丢弃法和 l2 正则化。 但是,在进行此操作之前,我们应该研究如何使用多类网络来测量准确率和进行预测。
在多类模型中使用 scikit-learn 指标
和以前一样,我们可以借鉴 scikit-learn 的指标来衡量我们的模型。 但是,为此,我们需要从模型的y
的分类输出中进行一些简单的转换,因为 scikit-learn 需要使用类标签,而不是二元类指示器。
为了取得飞跃,我们将使用以下代码开始进行预测:
y_softmax = model.predict(data["test_X"])
然后,我们将选择概率最大的类的索引,使用以下代码将其方便地作为该类:
y_hat = y_softmax.argmax(axis=-1)
然后,我们可以像以前一样使用 scikit-learn 的分类报告。 相同的代码如下:
from sklearn.metrics import classification_report
print(classification_report(test_y, y_hat))
现在,我们实际上可以查看所有 10 个类的精度,召回率和 f1 得分。 下图说明了sklearn.metrics.classification_report()
的输出:
通过丢弃控制方差
减少深度神经网络过拟合的一种非常好的方法是采用一种称为丢弃法的技术。 丢弃法完全按照其说的去做,它使神经元脱离隐藏层。 运作方式如下。
通过每个小批量,我们将随机选择关闭每个隐藏层中的节点。 想象一下,我们在某个隐藏层中实现了丢弃,并且我们选择了丢弃率为 0.5。 这意味着,对于每个小批量,对于每个神经元,我们都掷硬币以查看是否使用该神经元。 这样,您可能会随机关闭该隐藏层中大约一半的神经元:
如果我们一遍又一遍地执行此操作,就好像我们正在训练许多较小的网络。 模型权重保持相对较小,每个较小的网络不太可能过拟合数据。 这也迫使每个神经元减少对其他神经元的依赖。
丢弃法效果惊人,可以很好地解决您可能遇到的许多(如果不是大多数)深度学习问题的过拟合问题。 如果您具有高方差模型,则丢弃是减少过拟合的好选择。
Keras 包含一个内置的Dropout
层,我们可以轻松地在网络中使用它来实现Dropout
。 Dropout
层将简单地随机关闭前一层神经元的输出,以使我们轻松地改造网络以使用Dropout
。 要使用它,除了我们正在使用的其他层类型之外,我们还需要首先导入新层,如以下代码所示:
from keras.layers import Input, Dense, Dropout
然后,我们只需将Dropout
层插入模型,如以下代码所示:
def build_network(input_features=None):
# first we specify an input layer, with a shape == features
inputs = Input(shape=(input_features,), name="input")
x = Dense(512, activation='relu', name="hidden1")(inputs)
x = Dropout(0.5)(x)
x = Dense(256, activation='relu', name="hidden2")(x)
x = Dropout(0.5)(x)
x = Dense(128, activation='relu', name="hidden3")(x)
x = Dropout(0.5)(x)
prediction = Dense(10, activation='softmax', name="output")(x)
model = Model(inputs=inputs, outputs=prediction)
model.compile(optimizer='adam', loss='categorical_crossentropy',
metrics=["accuracy"])
return model
这是我们先前使用的确切模型; 但是,我们在每个Dense
层之后都插入了Dropout
层,这是我通常在实现丢弃时开始的方式。 像其他模型架构决策一样,您可以选择仅在某些层,所有层或没有层中实现丢弃。 您还可以选择更改退出/保留概率; 但是,我确实建议从 0.5 开始,因为它通常效果很好。
一个安全的选择是在每一层都退出,保持概率为 0.5。 不错的第二种尝试是仅在第一层使用丢弃。
让我们用丢弃法训练我们的新模型,看看它与我们的第一次尝试相比如何:
首先让我们看一下验证准确率。 使用丢弃模型的训练速度与未规范模型的训练速度一样快,但是在这种情况下,它的确似乎很快就开始加速。 看看在第 44 个周期的验证准确率。它比非正规模型略好。
现在,让我们看看验证损失。 您可以看到丢弃法对模型过拟合的影响,而且确实非常明显。 虽然仅转换为最终产品的少量改进,但丢弃法表现相当不错,可以防止我们的验证损失提升。
通过正则化控制方差
正则化是控制过拟合的另一种方法,当模型中的各个权重增大时会对其进行惩罚。 如果您熟悉线性模型(例如线性和逻辑回归),那么它与在神经元级别应用的技术完全相同。 可以使用两种形式的正则化,称为 L1 和 L2,来对神经网络进行正则化。 但是,由于 L2 正则化计算效率更高,因此几乎总是在神经网络中使用它。
快速地,我们需要首先规范化成本函数。 如果我们将C[0]
,分类交叉熵作为原始成本函数,则正规化的cost
函数将如下所示:
这里,λ
是可以增加或减少以更改应用的正则化量的正则化参数。 此正则化参数会惩罚较大的权重值,从而使网络总体上希望具有较小的权重。
要更深入地了解神经网络中的正则化,请查看 Michael Nielsen 的《神经网络和深度学习》的第 3 章。
可以将正则化应用于 Keras 层中的权重,偏差和激活。 我将使用带有默认参数的 L2 演示此技术。 在以下示例中,我将正则化应用于每个隐藏层:
def build_network(input_features=None):
# first we specify an input layer, with a shape == features
inputs = Input(shape=(input_features,), name="input")
x = Dense(512, activation='relu', name="hidden1", kernel_regularizer='l2') \
(inputs)
x = Dense(256, activation='relu', name="hidden2", kernel_regularizer='l2')(x)
x = Dense(128, activation='relu', name="hidden3", kernel_regularizer='l2')(x)
prediction = Dense(10, activation='softmax', name="output")(x)
model = Model(inputs=inputs, outputs=prediction)
model.compile(optimizer='adam', loss='categorical_crossentropy',
metrics=["accuracy"])
return model
因此,让我们将默认的 L2 正则化与其他两个模型进行比较。 下图显示了比较:
不幸的是,我们的新 L2 正则化网络很容易找到。 在这种情况下,似乎 L2 正则化效果很好。 我们的网络现在偏差严重,对其他两个方面的了解还不够。
如果我真的确定要使用正则化来解决此问题,那么我将首先更改正则化率并尝试找到更合适的值,但我们相距甚远,我对此表示怀疑,我们会做得比我们更好 dropout
模型。
总结
在本章中,我们实际上已经开始了解深度神经网络在进行多分类时的威力。 我们详细介绍了softmax
函数,然后我们构建并训练了一个网络来将手写数字分为 10 个各自的类别。
最后,当我们注意到模型过拟合时,我们尝试同时使用丢弃和 L2 正则化来减少模型的方差。
到目前为止,您已经看到深度神经网络需要很多选择,关于架构的选择,学习率,甚至是正则化率。 我们将在下一章中学习如何优化这些选择。
标签:Keras,模型,参考,使用,学习,神经网络,深度,我们 From: https://www.cnblogs.com/apachecn/p/17322053.html