dl-tf-2e-zh-merge-0

64 阅读1小时+

TensorFlow 深度学习中文第二版(一)

原文:Deep Learning with TensorFlow Second Edition

协议:CC BY-NC-SA 4.0

一、人工神经网络

人工神经网络利用了 DL 的概念 。它们是人类神经系统的抽象表示,其中包含一组神经元,这些神经元通过称为轴突的连接相互通信。

Warren McCulloch 和 Walter Pitts 在 1943 年根据神经活动的计算模型提出了第一个人工神经元模型。这个模型之后是 John von Neumann,Marvin Minsky,Frank Rosenblatt(所谓的感知器)和其他许多人提出的另一个模型。

生物神经元

看一下大脑的架构灵感。大脑中的神经元称为生物神经元。它们是看起来不寻常的细胞,主要存在于动物大脑中,由皮质组成。皮质本身由细胞体组成,细胞体包含细胞核和细胞的大部分复杂成分。有许多称为树突的分支延伸,加上一个称为轴突的非常长的延伸。

在它的极端附近,轴突分裂成许多分支称为终树突,并且在这些分支的顶部是称为突触末端(或简单的突触)的微小结构,连接到其他神经元的树突。生物神经元接收称为来自其他神经元的信号的短电脉冲,作为回应,它们发出自己的信号:

The biological neurons

图 7:生物神经元的工作原理。

在生物学中,神经元由以下组成:

  • 细胞体或体细胞
  • 一个或多个树突,其职责是接收来自其他神经元的信号
  • 轴突,反过来将同一神经元产生的信号传递给其他连接的神经元

神经元的活动在发送/接收来自其他神经元的信号(活动状态)和休息(非活动状态)之间交替。从一个相到另一个相的转变是由外部刺激引起的,由树枝状晶体拾取的信号表示。每个信号具有兴奋或抑制作用,在概念上由与刺激相关的权重表示。

处于空闲状态的神经元累积它收到的所有信号,直到达到某个激活阈值。

人工神经元

基于生物神经元的概念,出现了人工神经元的术语和思想,它们已被用于构建基于 DL 的预测分析的智能机器。这是启发人工神经网络的关键理念。与生物神经元类似,人工神经元由以下部分组成:

  • 一个或多个传入连接,其任务是从其他神经元收集数字信号:为每个连接分配一个权重,用于考虑发送的每个信号

  • 一个或多个输出连接,将信号传递给其他神经元

  • 激活函数,基于一些信号确定输出信号的数值,信号从和其他神经元的输入连接接受,并从权重和神经元本身的激活阈值中适当地收集,权重与每个接收信号相关:

    The artificial neuron

    图 8:人工神经元模型。

通过将激活函数(也称为传递函数)应用于输入的加权和来计算输出,即神经元传输的信号。这些函数的动态范围介于 -1 和 1 之间,或介于 0 和 1 之间。许多激活函数在复杂性和输出方面有所不同。在这里,我们简要介绍三种最简单的形式:

  • 阶跃函数:一旦我们确定阈值x(例如,x = 10),如果输入之和高于阈值,该函数将返回 1,否则则返回 0。
  • 线性组合:不管理阈值,而是从默认值中减去输入值的加权和。我们将得到二元结果,该结果将由减法的正(+b)或负(-b)输出表示。
  • Sigmoid:这会产生 Sigmoid 曲线,这是一条具有 S 趋势的曲线。通常,sigmoid 函数指的是逻辑函数的特殊情况。

从第一个人工神经元原型制作中使用的最简单的形式,我们转向更复杂的形式,可以更好地表征神经元的功能:

  • 双曲正切函数

  • 径向基函数

  • 圆锥截面函数

  • Softmax 函数

    The artificial neuron

    图 9:最常用的人工神经元模型传递函数。(a)阶梯函数(b)线性函数(c)sigmoid 函数,计算值介于 0 和 1 之间(d)sigmoid 函数,计算值介于 -1 和 1 之间。

选择适当的激活函数(也是权重初始化)是使网络发挥最佳表现并获得良好训练的关键。这些主题正在进行大量研究,如果训练阶段正确进行,研究表明在产出质量方面存在微小差异。

注意

在神经网络领域没有经验法则。这一切都取决于您的数据以及在通过激活函数后希望数据转换的形式。如果要选择特定的激活函数,则需要研究函数的图形,以查看结果如何根据给定的值进行更改。

ANN 如何学习?

神经网络的学习过程被配置为权重优化的迭代过程,因此是监督类型。由于网络在属于训练集的一组示例上的表现(即,您知道示例所属的类的集合),因此修改权重。

目的是最小化损失函数,其表示网络行为偏离期望行为的程度。然后在由除了训练集中的对象之外的对象(例如,图像分类问题中的图像)组成的测试集上验证网络的表现。

人工神经网络和反向传播算法

常用的监督学习算法是反向传播算法。训练程序的基本步骤如下:

  1. 用随机权重初始化网络
  2. 对于所有训练案例,请按照下列步骤操作:
    • 正向传播:计算网络的误差,即所需输出与实际输出之间的差值

    • 向后传递:对于所有层,从输出层回到输入层:

      i:使用正确的输入显示网络层的输出(误差函数)。

      ii:调整当前层中的权重以最小化误差函数。这是反向传播的优化步骤。

当验证集上的误差开始增加时,训练过程结束,因为这可能标志着阶段过拟合的开始,即网络倾向于以牺牲训练数据为代价来内插训练数据的阶段。普遍性。

权重优化

因此, 优化权重的有效算法的可用性构成了构建神经网络的必要工具。该问题可以通过称为梯度下降(GD)的迭代数值技术来解决。该技术根据以下算法工作:

  1. 随机选择模型参数的初始值
  2. 根据模型的每个参数计算误差函数的梯度 G.
  3. 更改模型的参数,使它们朝着减小误差的方向移动,即沿 -G 方向移动
  4. 重复步骤 2 和 3,直到 G 的值接近零

误差函数 E 的梯度(G)提供了误差函数与当前值具有更陡斜率的方向;所以为了减少 E,我们必须在相反的方向上做一些小步骤,-G。

通过以迭代方式多次重复此操作,我们向下移动到 E 的最小值,以达到G = 0的点,从而无法进一步进展:

Weight optimization

图 10:搜索误差函数 E 的最小值。我们沿着函数 E 的梯度 G 最小的方向移动。

随机梯度下降

在 GD 优化中,我们基于完整的训练集计算成本梯度,因此我们有时也将其称为批量 GD。在非常大的数据集的情况下,使用 GD 可能非常昂贵,因为我们在训练集上只进行一次传递。训练集越大,我们的算法更新权重的速度就越慢,并且在收敛到全局成本最小值之前可能需要的时间越长。

最快的梯度下降方法是随机梯度下降(SGD),因此,它被广泛应用于深度神经网络。在 SGD 中,我们仅使用来自训练集的一个训练样本来对特定迭代中的参数进行更新。

这里,术语随机来自这样的事实:基于单个训练样本的梯度是真实成本梯度的随机近似。由于其随机性,通向全局最小成本的路径并不像 GD 那样直接,但如果我们可视化 2D 空间中的成本表面,则可能会出现锯齿形:

Stochastic gradient descent

图 11:GD 与 SGD:梯度下降(左图)确保权重中的每次更新都在正确的方向上完成:最小化成本函数的方向。随着数据集大小的增长以及每个步骤中更复杂的计算,SGD(右图)在这些情况下是首选。这里,在处理每个样本时完成权重的更新,因此,后续计算已经使用了改进的权重。尽管如此,这个原因导致了在最小化误差函数方面的一些误导。

神经网络架构

我们连接节点的方式和存在的层数(即输入和输出之间的节点级别以及每层神经元的数量)定义了神经网络的架构。

神经网络中存在各种类型的架构。我们可以将 DL 架构,分为四组:深度神经网络(DNN),卷积神经网络(CNN),循环神经网络(RNN)和紧急架构(EA)。本章的以下部分将简要介绍这些架构。更多详细分析,以及应用实例,将成为本书后续章节的主题。

深度神经网络(DNN)

DNN 是人工神经网络,它们强烈地面向 DL。在正常分析程序不适用的情况下,由于要处理的数据的复杂性,因此这种网络是一种极好的建模工具。 DNN 是与我们讨论过的神经网络非常相似的神经网络,但它们必须实现更复杂的模型(更多的神经元,隐藏层和连接),尽管它们遵循适用于所有 ML 问题的学习原则(例如作为监督学习)。每层中的计算将下面层中的表示转换为稍微更抽象的表示。

我们将使用术语 DNN 将具体指代多层感知器(MLP),堆叠自编码器(SAE)和深度信任网络(DBN)。 SAE 和 DBN 使用自编码器(AEs)和 RBM 作为架构的块。它们与 MLP 之间的主要区别在于,训练分两个阶段执行:无监督的预训练和监督微调:

Deep Neural Networks (DNNs)

图 12:分别使用 AE 和 RBM 的 SAE 和 DBN。

在无监督预训练中,如上图所示,这些层按顺序堆叠并以分层方式进行训练,如使用未标记数据的 AE 或 RBM。然后,在有监督的微调中,堆叠输出分类器层,并通过用标记数据重新训练来优化完整的神经网络。

在本章中,我们不讨论 SAE(详见第 5 章,优化 TensorFlow 自编码器),但将坚持使用 MLP 和 DBN 并使用这两种 DNN 架构。我们将看到如何开发预测模型来处理高维数据集。

多层感知器

在多层网络中,可以识别层的人工神经元,以便每个神经元连接到下一层中的所有神经元,确保:

  • 属于同一层的神经元之间没有连接
  • 属于非相邻层的神经元之间没有连接
  • 每层的层数和神经元数取决于要解决的问题

输入和输出层定义输入和输出,并且存在隐藏层,其复杂性实现网络的不同行为。最后,神经元之间的连接由与相邻层对相同的矩阵表示。

每个数组包含两个相邻层的节点对之间的连接的权重。前馈网络是层内没有环路的网络。

我们将在第 3 章,使用 TensorFlow 的前馈神经网络中更详细地描述前馈网络:

Multilayer perceptron

图 13:MLP 架构

深度信念网络(DBNs)

为了克服 MLP 中的过拟合问题,我们建立了一个 DBN,做了无监督预训练,为输入获得了一组不错的特征表示,然后微调训练集从网络获得实际预测。虽然 MLP 的权重是随机初始化的,但 DBN 使用贪婪的逐层预训练算法通过概率生成模型初始化网络权重。模型由可见层和多层随机和潜在变量组成,称为隐藏单元或特征检测器。

DBN 是深度生成模型,它们是神经网络模型,可以复制您提供的数据分布。这允许您从实际数据点生成“虚假但逼真”的数据点。

DBN 由可见层和多层随机潜在变量组成,这些变量称为隐藏单元或特征检测器。前两层在它们之间具有无向的对称连接并形成关联存储器,而较低层从前一层接收自上而下的有向连接。 DBN 的构建块是受限玻尔兹曼机(RBM)。如下图所示,几个 RBM 一个接一个地堆叠形成 DBN:

Deep Belief Networks (DBNs)

图 14:配置用于半监督学习的 DBN

单个 RBM 由两层组成。第一层由可见神经元组成,第二层由隐藏神经元组成。下图显示了简单 RBM 的结构。可见单元接受输入,隐藏单元是非线性特征检测器。每个可见神经元都连接到所有隐藏的神经元,但同一层中的神经元之间没有内部连接。

RBM 由可见层节点和隐藏层节点组成,但没有可见 - 隐藏和隐藏 - 隐藏连接,因此项受限制。它们允许更有效的网络训练,可以监督或监督。这种类型的神经网络能够表示输入的大量特征,然后隐藏的节点可以表示多达 2n 个特征。可以训练网络回答单个问题(例如,问题是或否:它是猫吗?),直到它能够(再次以二元的方式)响应总共 2n 个问题(它是猫吗? ,这是暹罗人?,它是白色的吗?)。

RBM 的架构如下,神经元根据对称的二分图排列:

Deep Belief Networks (DBNs)

图 15:RBM 架构。

由于无法对变量之间的关系进行建模,因此单个隐藏层 RBM 无法从输入数据中提取所有特征。因此,一层接一层地使用多层 RBM 来提取非线性特征。在 DBN 中,首先使用输入数据训练 RBM,并且隐藏层表示使用贪婪学习方法学习的特征。这些第一 RBM 的学习特征,即第一 RBM 的隐藏层,被用作第二 RBM 的输入,作为 DBN 中的另一层。

类似地,第二层的学习特征用作另一层的输入。这样,DBN 可以从输入数据中提取深度和非线性特征。最后一个 RBM 的隐藏层代表整个网络的学习特征。

卷积神经网络(CNNs)

CNN 已经专门用于图像识别。学习中使用的每个图像被分成紧凑的拓扑部分,每个部分将由过滤器处理以搜索特定模式。形式上,每个图像被表示为像素的三维矩阵(宽度,高度和颜色),并且每个子部分可以与滤波器组卷积在一起。换句话说,沿着图像滚动每个滤镜计算相同滤镜和输入的内积。

此过程为各种过滤器生成一组特征图(激活图)。将各种特征图叠加到图像的相同部分上,我们得到输出量。这种类型的层称为卷积层。下图是 CNN 架构的示意图:

Convolutional Neural Networks (CNNs)

图 16:CNN 架构。

虽然常规 DNN 适用于小图像(例如,MNIST 和 CIFAR-10),但由于需要大量参数,它们会因较大的图像而崩溃。例如,100×100图像具有 10,000 个像素,并且如果第一层仅具有 1,000 个神经元(其已经严格限制传输到下一层的信息量),则这意味着 1000 万个连接。另外,这仅适用于第一层。

CNN 使用部分连接的层解决了这个问题。由于相邻层仅部分连接,并且因为它重复使用其权重,因此 CNN 的参数远远少于完全连接的 DNN,这使得训练速度更快。这降低了过拟合的风险,并且需要更少的训练数据。此外,当 CNN 已经学习了可以检测特定特征的内核时,它可以在图像上的任何地方检测到该特征。相反,当 DNN 在一个位置学习一个特征时,它只能在该特定位置检测到它。由于图像通常具有非常重复的特征,因此 CNN 在图像处理任务(例如分类)和使用较少的训练示例方面能够比 DNN 更好地推广。

重要的是,DNN 没有关于如何组织像素的先验知识;它不知道附近的像素是否接近。 CNN 的架构嵌入了这一先验知识。较低层通常识别图像的单元域中的特征,而较高层将较低层特征组合成较大特征。这适用于大多数自然图像,使 CNN 在 DNN 上具有决定性的先机:

Convolutional Neural Networks (CNNs)

图 17:常规 DNN 与 CNN。

例如,在上图中,在左侧,您可以看到常规的三层神经网络。在右侧,CNN 以三维(宽度,高度和深度)排列其神经元,如在其中一个层中可视化。 CNN 的每一层都将 3D 输入音量转换为神经元激活的 3D 输出音量。红色输入层保持图像,因此其宽度和高度将是图像的尺寸,深度将是三个(红色,绿色和蓝色通道)。

因此,我们所看到的所有多层神经网络都有由长线神经元组成的层,我们不得不将输入图像或数据平铺到 1D,然后再将它们馈送到神经网络。但是,当您尝试直接为它们提供 2D 图像时会发生什么?答案是在 CNN 中,每个层都用 2D 表示,这样可以更容易地将神经元与其相应的输入进行匹配。我们将在接下来的部分中看到这方面的示例。

自编码器

AE 是具有三层或更多层的网络 ,其中输入层和输出具有相同数量的神经元,并且那些中间(隐藏层)具有较少数量的神经元。对网络进行训练,以便在输出中简单地为每条输入数据再现输入中相同的活动模式。

AE 是能够在没有任何监督的情况下学习输入数据的有效表示的 ANN(即,训练集是未标记的)。它们通常具有比输入数据低得多的维度,使得 AE 可用于降低维数。更重要的是,AE 作为强大的特征检测器,它们可用于 DNN 的无监督预训练。

该问题的显着方面在于,由于隐藏层中神经元的数量较少,如果网络可以从示例中学习并推广到可接受的程度,则它执行数据压缩;对于每个示例,隐藏神经元的状态为输入和输出公共状态的压缩版本提供。 AEs 的有用应用是数据可视化的数据去噪和降维。

下图显示了 AE 通常如何工作;它通过两个阶段重建接收的输入:编码阶段,其对应于原始输入的尺寸减小;以及解码阶段,其能够从编码(压缩)表示重建原始输入:

AutoEncoders

图 18:自编码器的编码和解码阶段。

作为无监督神经网络,自编码器的主要特征是其对称结构。 自编码器有两个组件:将输入转换为内部表示的编码器,然后是将内部表示转换为输出的解码器。

换句话说, 自编码器可以看作是编码器的组合,其中我们将一些输入编码为代码,以及解码器,其中我们将代码解码/重建为其原始输入作为输出。因此,MLP 通常具有与自编码器相同的架构,除了输出层中的神经元的数量必须等于输入的数量。

如前所述,训练自编码器的方法不止一种。第一种方法是一次训练整个层,类似于 MLP。但是,在计算成本函数时,不像在监督学习中使用某些标记输出,我们使用输入本身。因此,成本函数显示实际输入和重建输入之间的差异。

循环神经网络(RNNs)

RNN 的基本特征是网络包含至少一个反馈连接,因此激活可以在循环中流动。它使网络能够进行时间处理和学习序列,例如执行序列识别/再现或时间关联/预测。

RNN 架构可以有许多不同的形式。一种常见类型包括标准 MLP 加上添加的循环。这些可以利用 MLP 强大的非线性映射功能,并具有某种形式的内存。其他人具有更均匀的结构,可能与每个神经元连接到所有其他神经元,并且可能具有随机激活函数:

Recurrent Neural Networks (RNNs)

图 19:RNN 架构。

对于简单的架构和确定性激活函数,可以使用类似的 GD 过程来实现学习,这些过程导致用于前馈网络的反向传播算法。

上图查看了 RNN 的一些最重要的类型和功能。 RNN 被设计成利用输入数据的顺序信息,与诸如感知器,长短期存储器单元(LSTM)或门控循环单元(GRU)之类的构件块之间的循环连接。后两者用于消除常规 RNN 的缺点,例如梯度消失/爆炸问题和长短期依赖性。我们将在后面的章节中讨论这些架构。

前沿架构

已经提出了许多其他前沿 DL 架构 ,例如深度时空神经网络(DST-NN),多维循环神经网络(MD-RNN),和卷积自编码器(CAE)。

然而,人们正在谈论和使用其他新兴网络,例如 CapsNets(CNN 的改进版本,旨在消除常规 CNN 的缺点),用于个性化的分解机和深度强化学习。

深度学习框架

在本节中,我们介绍了一些最流行的 DL 框架。简而言之,几乎所有的库都提供了使用图形处理器加速学习过程的可能性,在开放许可下发布,并且是大学研究小组的结果。

TensorFlow 是数学软件,是一个开源软件库,用 Python 和 C++ 编写,用于机器智能。 Google Brain 团队在 2011 年开发了它,它可以用来帮助我们分析数据,预测有效的业务成果。构建神经网络模型后,在必要的特征工程之后,您可以使用绘图或 TensorBoard 以交互方式执行训练。

最新版 TensorFlow 提供的主要功能包括更快的计算,灵活性,可移植性,易于调试,统一的 API,GPU 计算的透明使用,易用性和可扩展性。其他好处包括它被广泛使用,支持,并且可以大规模生产。

Keras 是一个深度学习库,位于 TensorFlow 和 Theano 之上,提供了一个直观的 API,受到了 Torch(可能是现有的最佳 Python API)的启发。 Deeplearning4j 依赖于 Keras 作为其 Python API,并从 Keras 和 Keras,Theano 和 TensorFlow 导入模型。

Google 的软件工程师 FrançoisChollet 创建了 Keras。它可以在 CPU 和 GPU 上无缝运行。这样可以通过用户友好性,模块化和可扩展性轻松快速地进行原型设计。 Keras 可能是增长最快的框架之一,因为构建 NN 层太容易了。因此,Keras 很可能成为 NN 的标准 Python API。

Theano 可能是最常见的库。 Theano 是用 Python 编写的,它是 ML 领域中使用最广泛的语言之一(Python 也用于 TensorFlow)。此外,Theano 允许使用 GPU,比单 CPU 快 24 倍。 Theano 允许您有效地定义,优化和求值复杂的数学表达式,例如多维数组。不幸的是,Yoshua Bengio 于 2017 年 9 月 28 日宣布,Theano 的发展将停止。这意味着 Theano 实际上已经死了。

Neon 是由 Nirvana 开发的基于 Python 的深度学习框架。 Neon 的语法类似于 Theano 的高级框架(例如,Keras)。目前,Neon 被认为是基于 GPU 的最快工具,特别是对于 CNN。虽然它的基于 CPU 的实现比大多数其他库相对更差。

Torch 是 ML 的巨大生态系统,提供大量算法和函数,包括 DL 和处理各种类型的多媒体数据,特别关注并行计算。它为 C 语言提供了出色的接口, 拥有庞大的用户社区。 Torch 是一个扩展脚本语言 Lua 的库,旨在为设计和训练 ML 系统提供灵活的环境。 Torch 是各种平台(Windows,Mac,Linux 和 Android)上的独立且高度可移植的框架,脚本可以在这些平台上运行而无需修改。 Torch 为不同的应用提供了许多用途。

Caffe,主要由伯克利远景和学习中心(BVLC)开发 ,是一个框架 ,因其表达,速度和模块性而脱颖而出。其独特的架构鼓励应用和创新,使计算更容易从 CPU 转换到 GPU。庞大的用户群意味着最近发生了相当大的发展。它是用 Python 编写的,但由于需要编译的众多支持库,安装过程可能很长。

MXNet 是一个支持多种语言的 DL 框架,例如 R,Python,C++ 和 Julia。这很有帮助,因为如果你知道这些语言中的任何一种,你根本不需要走出自己的舒适区来训练你的 DL 模型。它的后端用 C++ 和 CUDA 编写,它能够以与 Theano 类似的方式管理自己的内存。

MXNet 也很受欢迎,因为它可以很好地扩展,并且可以与多个 GPU 和计算机一起使用,这使它对企业非常有用。这就是为什么亚马逊将 MXNet 作为 DL 的参考库。 2017 年 11 月,AWS 宣布推出 ONNX-MXNet,这是一个开源 Python 包,用于将开放式神经网络交换(ONNX) DL 模型导入 Apache MXNet。

Microsoft Cognitive Toolkit(CNTK)是 Microsoft Research 的统一 DL 工具包,可以轻松训练, 将多种 GPU 和服务器中的流行模型类型组合在一起。 CNTK 为语音,图像和文本数据实现高效的 CNN 和 RNN 训练。它支持 cuDNN v5.1 进行 GPU 加速。 CNTK 还支持 Python,C++ ,C#和命令行接口。

这是一个总结这些框架的表:

框架支持的编程语言训练教材和社区CNN 建模能力RNN 建模能力可用性多 GPU 支持
TheanoPython,C++++丰富的 CNN 教程和预建模型丰富的 RNN 教程和预建模型模块化架构
NeonPython,+CNN 最快的工具资源最少模块化架构
TorchLua,Python+资源最少丰富的 RNN 教程和预建模型模块化架构
CaffeC++++丰富的 CNN 教程和预建模型资源最少创建层需要时间
MXNetR,Python,Julia,Scala++丰富的 CNN 教程和预建模型资源最少模块化架构
CNTKC+++丰富的 CNN 教程和预建模型丰富的 RNN 教程和预建模型模块化架构
TensorFlowPython,C+++++丰富的 RNN 教程和预建模型丰富的 RNN 教程和预建模型模块化架构
DeepLearning4jJava,Scala+++丰富的 RNN 教程和预建模型丰富的 RNN 教程和预建模型模块化架构
KerasPython+++丰富的 RNN 教程和预建模型丰富的 RNN 教程和预建模型模块化架构

除了前面的库之外,最近还有一些关于云计算的 DL 项目。这个想法是将 DL 功能带到大数据,拥有数十亿个数据点和高维数据。例如,Amazon Web Services(AWS),Microsoft Azure,Google Cloud Platform 和 NVIDIA GPU Cloud(NGC)都提供机器和深度学习服务,它们是公共云的原生。

2017 年 10 月,AWS 针对 Amazon Elastic Compute Cloud(EC2)P3 实例发布了深度学习 AMI(亚马逊机器映像) 。这些 AMI 预装了深度学习框架,如 TensorFlow,Gluon 和 Apache MXNet,这些框架针对 Amazon EC2 P3 实例中的 NVIDIA Volta V100 GPU 进行了优化。深度学习服务目前提供三种类型的 AMI:Conda AMI,Base AMI 和带源代码的 AMI。

Microsoft Cognitive Toolkit 是 Azure 的开源深度学习服务。与 AWS 的产品类似,它侧重于可以帮助开发人员构建和部署深度学习应用的工具。该工具包安装在 Python 2.7 的根环境中。 Azure 还提供了一个模型库,其中包含代码示例等资源,以帮助企业开始使用该服务。

另一方面,NGC 为 AI 科学家和研究人员提供 GPU 加速容器。 NGC 采用容器化的深度学习框架,如 TensorFlow,PyTorch 和 MXNet,经过 NVIDIA 的调整,测试和认证,可在参与的云服务提供商的最新 NVIDIA GPU 上运行。尽管如此,还有通过各自市场提供的第三方服务。

总结

在本章中,我们介绍了 DL 的一些基本主题。 DL 由一组方法组成,这些方法允许 ML 系统获得多个级别上的数据的分层表示。这是通过组合简单单元来实现的,每个简单单元从输入级别开始,以更高和抽象级别的表示,在其自己的级别上转换表示。

最近,这些技术提供了许多应用中从未见过的结果,例如图像识别和语音识别。这些技术普及的主要原因之一是 GPU 架构的发展,这大大减少了 DNN 的训练时间。

有不同的 DNN 架构,每个架构都是针对特定问题而开发的。我们将在后面的章节中更多地讨论这些架构,并展示使用 TensorFlow 框架创建的应用示例。本章最后简要介绍了最重要的 DL 框架。

在下一章中,我们将开始我们的 DL 之旅,介绍 TensorFlow 软件库。我们将介绍 TensorFlow 的主要功能,并了解如何安装它并设置我们的第一个工作再营销数据集。

二、TensorFlow v1.6 的新功能是什么?

2015 年,Google 开源了 TensorFlow,包括其所有参考实现。所有源代码都是在 Apache 2.0 许可下在 GitHub 上提供的。从那以后,TensorFlow 已经在学术界和工业研究中被广泛采用,最稳定的版本 1.6 最近已经发布了统一的 API。

值得注意的是,TensorFlow 1.6(及更高版本)中的 API 并非都与 v1.5 之前的代码完全向后兼容。这意味着一些在 v1.5 之前工作的程序不一定适用于 TensorFlow 1.6。

现在让我们看看 TensorFlow v1.6 具有的新功能和令人兴奋的功能。

Nvidia GPU 支持的优化

从 TensorFlow v1.5 开始,预构建的二进制文件现在针对 CUDA 9.0 和 cuDNN 7 构建。但是,从 v1.6 版本开始,TensorFlow 预构建的二进制文件使用 AVX 指令,这可能会破坏旧 CPU 上的 TensorFlow。尽管如此,自 v1.5 以来,已经可以在 NVIDIA Tegra 设备上增加对 CUDA 的支持。

TensorFlow Lite

TensorFlow Lite 是 TensorFlow 针对移动和嵌入式设备的轻量级解决方案。它支持具有小二进制大小和支持硬件加速的快速表现的设备上机器学习模型的低延迟推理。

TensorFlow Lite 使用许多技术来实现低延迟,例如优化特定移动应用的内核,预融合激活,允许更小和更快(定点数学)模型的量化内核,以及将来利用杠杆专用机器学习硬件在特定设备上获得特定模型的最佳表现。

Introducing TensorFlow Lite

图 1:使用 TensorFlow Lite 在 Android 和 iOS 设备上使用训练模型的概念视图

机器学习正在改变计算范式,我们看到了移动和嵌入式设备上新用例的新趋势。在相机和语音交互模型的推动下,消费者的期望也趋向于与其设备进行自然的,类似人的交互。

因此,用户的期望不再局限于计算机,并且移动设备的计算能力也因硬件加速以及诸如 Android 神经​​网络 API 和 iOS 的 C++ API 之类的框架而呈指数级增长。如上图所示,预训练模型可以转换为较轻的版本,以便作为 Android 或 iOS 应用运行。

因此,广泛使用的智能设备为设备智能创造了新的可能性。这些允许我们使用我们的智能手机来执行实时计算机视觉和自然语言处理(NLP)。

急切执行

急切执行是 TensorFlow 的一个接口,它提供了一种命令式编程风格。启用预先执行时,TensorFlow 操作(在程序中定义)立即执行。

需要注意的是,从 TensorFlow v1.7 开始,急切执行将被移出contrib。这意味着建议使用tf.enable_eager_execution()。我们将在后面的部分中看到一个例子。

优化加速线性代数(XLA)

v1.5 之前的 XLA 不稳定并且具有非常有限的特性。但是,v1.6 对 XLA 的支持更多。这包括以下内容:

  • 添加了对 XLA 编译器的 Complex64 支持
  • 现在为 CPU 和 GPU 添加了快速傅里叶变换(FFT)支持
  • bfloat支持现已添加到 XLA 基础结构中
  • 已启用 ClusterSpec 传播与 XLA 设备的工作
  • Android TF 现在可以在兼容的 Tegra 设备上使用 CUDA 加速构建
  • 已启用对添加确定性执行器以生成 XLA 图的支持

开源社区报告的大量错误已得到修复,并且此版本已集成了大量 API 级别的更改。

但是,由于我们尚未使用 TensorFlow 进行任何研究,我们将在后面看到如何利用这些功能开发真实的深度学习应用。在此之前,让我们看看如何准备您的编程环境。

安装和配置 TensorFlow

您可以在许多平台上安装和使用 TensorFlow,例如 Linux, macOS 和 Windows。此外,您还可以从 TensorFlow 的最新 GitHub 源构建和安装 TensorFlow。此外,如果您有 Windows 机器,您可以通过原生点或 Anacondas 安装 TensorFlow。 TensorFlow 在 Windows 上支持 Python 3.5.x 和 3.6.x.

此外,Python 3 附带了 PIP3 包管理器,它是用于安装 TensorFlow 的程序。因此,如果您使用此 Python 版本,则无需安装 PIP。根据我们的经验,即使您的计算机上集成了 NVIDIA GPU 硬件,也值得安装并首先尝试仅使用 CPU 的版本,如果您没有获得良好的表现,那么您应该切换到 GPU 支持。

支持 GPU 的 TensorFlow 版本有几个要求,例如 64 位 Linux,Python 2.7(或 Python 3 的 3.3+),NVIDIACUDA®7.5 或更高版本(Pascal GPU 需要 CUDA 8.0)和 NVIDIA cuDNN(这是 GPU 加速深度学习)v5.1(建议使用更高版本)。有关详情,请参阅此链接

更具体地说,TensorFlow 的当前开发仅支持使用 NVIDIA 工具包和软件的 GPU 计算。因此,必须在您的计算机上安装以下软件才能获得预测分析应用的 GPU 支持:

  • NVIDIA 驱动程序
  • 具有计算能力的CUDA >= 3.0
  • CudNN

NVIDIA CUDA 工具包包括(详见此链接):

  • GPU 加速库,例如用于 FFT 的 cuFFT
  • 基本线性代数子程序(BLAS)的 cuBLAS
  • cuSPARSE 用于稀疏矩阵例程
  • cuSOLVER 用于密集和稀疏的直接求解器
  • 随机数生成的 cuRAND,图像的 NPP 和视频处理原语
  • 适用于 NVIDIA Graph Analytics 库的 nvGRAPH
  • 对模板化并行算法和数据结构以及专用 CUDA 数学库的推动

但是,我们不会介绍 TensorFlow 的安装和配置,因为 TensorFlow 上提供的文档非常丰富,可以遵循并采取相应的措施。另一个原因是该版本将定期更改。因此,使用 TensorFlow 网站保持自己更新将是一个更好的主意。

如果您已经安装并配置了编程环境,那么让我们深入了解 TensorFlow 计算图。

TensorFlow 计算图

在考虑执行 TensorFlow 程序时,我们应该熟悉图创建和会话执行的概念。基本上,第一个用于构建模型,第二个用于提供数据并获得结果。

有趣的是,TensorFlow 在 C++ 引擎上执行所有操作,这意味着在 Python 中甚至不会执行一些乘法或添加操作。 Python 只是一个包装器。从根本上说,TensorFlow C++ 引擎包含以下两件事:

  • 有效的操作实现,例如 CNN 的卷积,最大池化和 sigmoid
  • 前馈模式操作的衍生物

TensorFlow 库在编码方面是一个非凡的库,它不像传统的 Python 代码(例如,你可以编写语句并执行它们)。 TensorFlow 代码由不同的操作组成。甚至变量初始化在 TensorFlow 中也很特殊。当您使用 TensorFlow 执行复杂操作(例如训练线性回归)时,TensorFlow 会在内部使用数据流图表示其计算。该图称为计算图,它是由以下组成的有向图:

  • 一组节点,每个节点代表一个操作
  • 一组有向弧,每个弧代表执行操作的数据

TensorFlow 有两种类型的边:

  • 正常:它们携带节点之间的数据结构。一个操作的输出,即来自一个节点的输出,成为另一个操作的输入。连接两个节点的边缘带有值。
  • 特殊:此边不携带值,但仅表示两个节点之间的控制依赖关系,例如 X 和 Y。这意味着只有在 X 中的操作已经执行时才会执行节点 Y,但之前关于数据的操作之间的关系。

TensorFlow 实现定义控制依赖性,以强制规定执行其他独立操作的顺序,作为控制峰值内存使用的方式。

计算图基本上类似于数据流图。图 2 显示了简单计算的计算图,如z = d × c = (a + b) × c

TensorFlow computational graph

图 2:一个计算简单方程的非常简单的执行图

在上图中,图中的圆圈表示操作,而矩形表示计算图。如前所述,TensorFlow 图包含以下内容:

  • tf.Operation对象:这些是图中的节点。这些通常简称为操作。 操作仅为 TITO(张量 - 张量 - 张量)。一个或多个张量输入和一个或多个张量输出。
  • tf.Tensor对象:这些是图的边缘。这些通常简称为张量。

张量对象在图中的各种操作之间流动。在上图中,d也是操作。它可以是“常量”操作,其输出是张量,包含分配给d的实际值。

也可以使用 TensorFlow 执行延迟执行。简而言之,一旦您在计算图的构建阶段中编写了高度复合的表达式,您仍然可以在运行会话阶段对其进行求值。从技术上讲,TensorFlow 安排工作并以​​有效的方式按时执行。

例如,使用 GPU 并行执行代码的独立部分如下图所示:

TensorFlow computational graph

图 3:要在 CPU 或 GPU 等设备上的会话上执行的 TensorFlow 图中的边和节点

在创建计算图之后,TensorFlow 需要具有以分布式方式由多个 CPU(以及 GPU,如果可用)执行的活动会话。通常,您实际上不需要明确指定是使用 CPU 还是 GPU,因为 TensorFlow 可以选择使用哪一个。

默认情况下,将选择 GPU 以进行尽可能多的操作;否则,将使用 CPU。然而,通常,它会分配所有 GPU 内存,即使它不消耗它。

以下是 TensorFlow 图的主要组成部分:

  • 变量:用于 TensorFlow 会话之间的值,包含权重和偏置。
  • 张量:一组值,在节点之间传递以执行操作(也称为操作)。
  • 占位符:用于在程序和 TensorFlow 图之间发送数据。
  • 会话:当会话启动时,TensorFlow 会自动计算图中所有操作的梯度,并在链式规则中使用它们。实际上,在执行图时会调用会话。

不用担心,前面这些组件中的每一个都将在后面的章节中讨论。从技术上讲,您将要编写的程序可以被视为客户。然后,客户端用于以符号方式在 C/C++ 或 Python 中创建执行图,然后您的代码可以请求 TensorFlow 执行此图。整个概念从下图中变得更加清晰:

TensorFlow computational graph

图 4:使用客户端主架构来执行 TensorFlow 图

计算图有助于使用 CPU 或 GPU 在多个计算节点上分配工作负载。这样,神经网络可以等同于复合函数,其中每个层(输入,隐藏或输出层)可以表示为函数。要了解在张量上执行的操作,需要了解 TensorFlow 编程模型的良好解决方法。

TensorFlow 代码结构

TensorFlow 编程模型表示如何构建预测模型。导入 TensorFlow 库时, TensorFlow 程序通常分为四个阶段:

  • 构建涉及张量运算的计算图(我们将很快看到张量)
  • 创建会话
  • 运行会话;执行图中定义的操作
  • 执行数据收集和分析

这些主要阶段定义了 TensorFlow 中的编程模型。请考虑以下示例,其中我们要将两个数相乘:

import tensorflow as tf # Import TensorFlow

x = tf.constant(8) # X op
y = tf.constant(9) # Y op
z = tf.multiply(x, y) # New op Z

sess = tf.Session() # Create TensorFlow session

out_z = sess.run(z) # execute Z op
sess.close() # Close TensorFlow session
print('The multiplication of x and y: %d' % out_z)# print result

前面的代码段可以用下图表示:

TensorFlow code structure

图 5:在客户端主架构上执行并返回的简单乘法

为了使前面的程序更有效,TensorFlow 还允许通过占位符交换图​​变量中的数据(稍后讨论)。现在想象一下代码段之后的可以做同样的事情,但效率更高:

import tensorflow as tf

# Build a graph and create session passing the graph
with tf.Session() as sess:
    x = tf.placeholder(tf.float32, name="x")
    y = tf.placeholder(tf.float32, name="y")
    z = tf.multiply(x,y)

# Put the values 8,9 on the placeholders x,y and execute the graph
z_output = sess.run(z,feed_dict={x: 8, y:9})
print(z_output)

TensorFlow 不是乘以两个数字所必需的。此外,这个简单的操作有很多行代码。该示例的目的是阐明如何构造代码,从最简单的(如在本例中)到最复杂的。此外,该示例还包含一些基本指令,我们将在本书中给出的所有其他示例中找到这些指令。

第一行中的单个导入为您的命令导入 TensorFlow;如前所述,它可以用tf实例化。然后,TensorFlow 运算符将由tf和要使用的运算符的名称表示。在下一行中,我们通过tf.Session()指令构造session对象:

with tf.Session() as sess:

提示

会话对象(即sess)封装了 TensorFlow 的环境,以便执行所有操作对象,并求值Tensor对象。我们将在接下来的部分中看到它们。

该对象包含计算图,如前所述,它包含要执行的计算。以下两行使用placeholder定义变量xy。通过placeholder,您可以定义输入(例如我们示例的变量x)和输出变量(例如变量y):

x = tf.placeholder(tf.float32, name="x")
y = tf.placeholder(tf.float32, name="y")

提示

占位符提供图元素和问题计算数据之间的接口。它们允许我们创建我们的操作并构建我们的计算图而不需要数据,而不是使用它的引用。

要通过placeholder函数定义数据或张量(我们将很快向您介绍张量的概念),需要三个参数:

  • 数据类型是要定义的张量中的元素类型。
  • 占位符的形状是要进给的张量的形状(可选)。如果未指定形状,则可以提供任何形状的张量。
  • 名称对于调试和代码分析非常有用,但它是可选的。

注意

有关张量的更多信息,请参阅此链接

因此,我们可以使用先前定义的两个参数(占位符和常量)来引入我们想要计算的模型。接下来,我们定义计算模型。

会话内的以下语句构建xy的乘积的数据结构,并随后将操作结果分配给张量z。然后它如下:

     z = tf.multiply(x, y)

由于结果已由占位符z保存,我们通过sess.run语句执行图。在这里,我们提供两个值来将张量修补为图节点。它暂时用张量值替换操作的输出:

z_output = sess.run(z,feed_dict={x: 8, y:9})

在最后的指令中,我们打印结果:

     print(z_output)

这打印输出72.0

用 TensorFlow 急切执行

如前所述,在启用 TensorFlow 的急切执行时,我们可以立即执行 TensorFlow 操作,因为它们是以命令方式从 Python 调用的。

启用急切执行后,TensorFlow 函数会立即执行操作并返回具体值。这与tf.Session相反,函数添加到图并创建计算图中的节点的符号引用。

TensorFlow 通过tf.enable_eager_execution提供急切执行的功能,其中包含以下别名:

  • tf.contrib.eager.enable_eager_execution
  • tf.enable_eager_execution

tf.enable_eager_execution具有以下签名:

tf.enable_eager_execution(
        config=None,
        device_policy=None
)

在上面的签名中,configtf.ConfigProto,用于配置执行操作的环境,但这是一个可选参数。另一方面,device_policy也是一个可选参数,用于控制关于特定设备(例如 GPU0)上需要输入的操作如何处理不同设备(例如,GPU1 或 CPU)上的输入的策略。

现在调用前面的代码将启用程序生命周期的急切执行。例如,以下代码在 TensorFlow 中执行简单的乘法运算:

import tensorflow as tf

x = tf.placeholder(tf.float32, shape=[1, 1]) # a placeholder for variable x
y = tf.placeholder(tf.float32, shape=[1, 1]) # a placeholder for variable y
m = tf.matmul(x, y)

with tf.Session() as sess:
    print(sess.run(m, feed_dict={x: [[2.]], y: [[4.]]}))

以下是上述代码的输出:

>>>
8.

然而,使用急切执行,整体代码看起来更简单:

import tensorflow as tf

# Eager execution (from TF v1.7 onwards):
tf.eager.enable_eager_execution()
x = [[2.]]
y = [[4.]]
m = tf.matmul(x, y)

print(m)

以下是上述代码的输出:

>>>
tf.Tensor([[8.]], shape=(1, 1), dtype=float32)

你能理解在执行前面的代码块时会发生什么吗?好了,在启用了执行后,操作在定义时执行,Tensor对象保存具体值,可以通过numpy()方法作为numpy.ndarray访问。

请注意,在使用 TensorFlow API 创建或执行图后,无法启用急切执行。通常建议在程序启动时调用此函数,而不是在库中调用。虽然这听起来很吸引人,但我们不会在即将到来的章节中使用此功能,因为这是一个新功能,尚未得到很好的探索。

TensorFlow 中的数据模型

TensorFlow 中的数据模型由张量表示。在不使用复杂的数学定义的情况下,我们可以说张量(在 TensorFlow 中)识别多维数值数组。我们将在下一小节中看到有关张量的更多细节。

张量

让我们看一下来自维基百科:张量的形式定义:

“张量是描述几何向量,标量和其他张量之间线性关系的几何对象。这种关系的基本例子包括点积,叉积和线性映射。几何向量,通常用于物理和工程应用,以及标量他们自己也是张量。“

该数据结构的特征在于三个参数:秩,形状和类型,如下图所示:

Tensor

图 6:张量只是具有形状,阶数和类型的几何对象,用于保存多维数组

因此,张量可以被认为是指定具有任意数量的索引的元素的矩阵的推广。张量的语法与嵌套向量大致相同。

提示

张量只定义此值的类型以及在会话期间应计算此值的方法。因此,它们不代表或保留操作产生的任何值。

有些人喜欢比较 NumPy 和 TensorFlow。然而,实际上,TensorFlow 和 NumPy 在两者都是 Nd 数组库的意义上非常相似!

嗯,NumPy 确实有 n 维数组支持,但它不提供方法来创建张量函数并自动计算导数(并且它没有 GPU 支持)。下图是 NumPy 和 TensorFlow 的简短一对一比较:

Tensor

图 7:NumPy 与 TensorFlow:一对一比较

现在让我们看一下在 TensorFlow 图之前创建张量的替代方法(我们将在后面看到其他的进给机制):

>>> X = [[2.0, 4.0],
        [6.0, 8.0]] # X is a list of lists
>>> Y = np.array([[2.0, 4.0],
                 [6.0, 6.0]], dtype=np.float32)#Y is a Numpy array
>>> Z = tf.constant([[2.0, 4.0],
                    [6.0, 8.0]]) # Z is a tensor

这里,X是列表,Y是来自 NumPy 库的 n 维数组,Z是 TensorFlow 张量对象。现在让我们看看他们的类型:

>>> print(type(X))
>>> print(type(Y))
>>> print(type(Z))

#Output
<class 'list'>
<class 'numpy.ndarray'>
<class 'tensorflow.python.framework.ops.Tensor'>

好吧,他们的类型打印正确。但是,与其他类型相比,我们正式处理张量的更方便的函数是tf.convert_to_tensor()函数如下:

t1 = tf.convert_to_tensor(X, dtype=tf.float32)
t2 = tf.convert_to_tensor(Z, dtype=tf.float32)

现在让我们使用以下代码查看它们的类型:

>>> print(type(t1))
>>> print(type(t2))

#Output:
<class 'tensorflow.python.framework.ops.Tensor'>
<class 'tensorflow.python.framework.ops.Tensor'>

太棒了!关于张量的讨论已经足够了。因此,我们可以考虑以术语秩为特征的结构。

秩和形状

称为秩的维度单位描述每个张量。它识别张量的维数。因此,秩被称为张量的阶数或维度。阶数零张量是标量,阶数 1 张量是向量,阶数 2 张量是矩阵。

以下代码定义了 TensorFlow scalarvectormatrixcube_matrix。在下一个示例中,我们将展示秩如何工作:

import tensorflow as tf
scalar = tf.constant(100)
vector = tf.constant([1,2,3,4,5])
matrix = tf.constant([[1,2,3],[4,5,6]])

cube_matrix = tf.constant([[[1],[2],[3]],[[4],[5],[6]],[[7],[8],[9]]])

print(scalar.get_shape())
print(vector.get_shape())
print(matrix.get_shape())
print(cube_matrix.get_shape())

结果打印在这里:

>>>
()
(5,)
(2, 3)
(3, 3, 1)
>>>

张量的形状是它具有的行数和列数。现在我们将看看如何将张量的形状与其阶数联系起来:

>>scalar.get_shape()
TensorShape([])

>>vector.get_shape()
TensorShape([Dimension(5)])

>>matrix.get_shape()
TensorShape([Dimension(2), Dimension(3)])

>>cube.get_shape()
TensorShape([Dimension(3), Dimension(3), Dimension(1)])

数据类型

除了阶数和形状,张量具有数据类型。以下是数据类型列表:

数据类型Python 类型描述
DT_FLOATtf.float3232 位浮点
DT_DOUBLEtf.float6464 位浮点
DT_INT8tf.int88 位有符号整数
DT_INT16tf.int1616 位有符号整数
DT_INT32tf.int3232 位有符号整数
DT_INT64tf.int6464 位有符号整数
DT_UINT8tf.uint88 位无符号整数
DT_STRINGtf.string可变长度字节数组。张量的每个元素都是一个字节数组
DT_BOOLtf.bool布尔
DT_COMPLEX64tf.complex64由两个 32 位浮点组成的复数:实部和虚部
DT_COMPLEX128tf.complex128由两个 64 位浮点组成的复数:实部和虚部
DT_QINT8tf.qint8量化操作中使用的 8 位有符号整数
DT_QINT32tf.qint32量化操作中使用的 32 位有符号整数
DT_QUINT8tf.quint8量化操作中使用的 8 位无符号整数

上表是不言自明的,因此我们没有提供有关数据类型的详细讨论。 TensorFlow API 用于管理与 NumPy 数组之间的数据。

因此,要构建具有常量值的张量,将 NumPy 数组传递给tf.constant()运算符,结果将是具有该值的张量:

import tensorflow as tf

import numpy as np
array_1d = np.array([1,2,3,4,5,6,7,8,9,10])
tensor_1d = tf.constant(array_1d)

with tf.Session() as sess:
    print(tensor_1d.get_shape())
    print(sess.run(tensor_1d))

运行该示例,我们获得以下内容:

>>>
 (10,)
 [ 1  2  3  4  5  6  7  8  9 10]

要构建具有变量值的张量,请使用 NumPy 数组并将其传递给tf.Variable构造器。结果将是具有该初始值的变量张量:

import tensorflow as tf
import numpy as np

# Create a sample NumPy array
array_2d = np.array([(1,2,3),(4,5,6),(7,8,9)])

# Now pass the preceding array to tf.Variable()
tensor_2d = tf.Variable(array_2d)

# Execute the preceding op under an active session
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    print(tensor_2d.get_shape())
    print sess.run(tensor_2d)
# Finally, close the TensorFlow session when you're done
sess.close()

在前面的代码块中,tf.global_variables_initializer()用于初始化我们之前创建的所有操作。如果需要创建一个初始值取决于另一个变量的变量,请使用另一个变量的initialized_value()。这可确保以正确的顺序初始化变量。

结果如下:

>>>
 (3, 3)
 [[1 2 3]
 [4 5 6]
 [7 8 9]]

为了便于在交互式 Python 环境中使用,我们可以使用InteractiveSession类,然后将该会话用于所有Tensor.eval()Operation.run()调用:

import tensorflow as tf # Import TensorFlow
import numpy as np # Import numpy

# Create an interactive TensorFlow session
interactive_session = tf.InteractiveSession()

# Create a 1d NumPy array 
array1 = np.array([1,2,3,4,5]) # An array

# Then convert the preceding array into a tensor
tensor = tf.constant(array1) # convert to tensor
print(tensor.eval()) # evaluate the tensor op

interactive_session.close() # close the session

提示

tf.InteractiveSession()只是方便的语法糖,用于在 IPython 中保持默认会话打开。

结果如下:

>>>
   [1 2 3 4 5]

在交互式设置中,例如 shell 或 IPython 笔记本,这可能会更容易,因为在任何地方传递会话对象都很繁琐。

注意

IPython 笔记本现在称为 Jupyter 笔记本。它是一个交互式计算环境,您可以在其中组合代码执行,富文本,数学,绘图和富媒体。有关更多信息,感兴趣的读者请参阅此链接

定义张量的另一种方法是使用tf.convert_to_tensor语句:

import tensorflow as tf
import numpy as np
tensor_3d = np.array([[[0, 1, 2], [3, 4, 5], [6, 7, 8]],
                      [[9, 10, 11], [12, 13, 14], [15, 16, 17]],
                      [[18, 19, 20], [21, 22, 23], [24, 25, 26]]])
tensor_3d = tf.convert_to_tensor(tensor_3d, dtype=tf.float64)

with tf.Session() as sess:
    print(tensor_3d.get_shape())
    print(sess.run(tensor_3d))
# Finally, close the TensorFlow session when you're done
sess.close()

以下是上述代码的输出:

>>>
(3, 3, 3)
[[[  0\.   1\.   2.]
  [  3\.   4\.   5.]
  [  6\.   7\.   8.]]
 [[  9\.  10\.  11.]
  [ 12\.  13\.  14.]
  [ 15\.  16\.  17.]]
 [[ 18\.  19\.  20.]
  [ 21\.  22\.  23.]
  [ 24\.  25\.  26.]]]

变量

变量是用于保存和更新参数的 TensorFlow 对象。必须初始化变量,以便您可以保存并恢复它以便稍后分析代码。使用tf.Variable()tf.get_variable()语句创建变量。而tf.get_varaiable()被推荐但tf.Variable()是低标签抽象。

在下面的示例中,我们要计算从 1 到 10 的数字,但让我们先导入 TensorFlow:

import tensorflow as tf

我们创建了一个将初始化为标量值 0 的变量:

value = tf.get_variable("value", shape=[], dtype=tf.int32, initializer=None, regularizer=None, trainable=True, collections=None)

assign()add()运算符只是计算图的节点,因此在会话运行之前它们不会执行赋值:

one = tf.constant(1)
update_value = tf.assign_add(value, one)
initialize_var = tf.global_variables_initializer()

我们可以实例化计算图:

with tf.Session() as sess:
    sess.run(initialize_var)
    print(sess.run(value))
    for _ in range(5):
        sess.run(update_value)
        print(sess.run(value))
# Close the session

让我们回想一下,张量对象是操作结果的符号句柄,但它实际上并不保存操作输出的值:

>>>
0
1
2
3
4
5

运行

要获取操作的输出,可以通过调用会话对象上的run()并传入张量来执行图。除了获取单个张量节点,您还可以获取多个张量。

在下面的示例中,使用run()调用一起提取summultiply张量:

import tensorflow as tf
constant_A = tf.constant([100.0])
constant_B = tf.constant([300.0])
constant_C = tf.constant([3.0])

sum_ = tf.add(constant_A,constant_B)
mul_ = tf.multiply(constant_A,constant_C)

with tf.Session() as sess:
    result = sess.run([sum_,mul_])# _ means throw away afterwards
    print(result)

输出如下:

>>>
[array(400.],dtype=float32),array([ 300.],dtype=float32)]

应该注意的是,所有需要执行的操作(即,为了产生张量值)都运行一次(每个请求的张量不是一次)。

馈送和占位符

有四种方法将数据输入 TensorFlow 程序(更多信息,请参阅此链接):

  • 数据集 API:这使您能够从简单和可重用的分布式文件系统构建复杂的输入管道,并执行复杂的操作。如果您要处理不同数据格式的大量数据,建议使用数据集 API。数据集 API 为 TensorFlow 引入了两个新的抽象,用于创建可馈送数据集:tf.contrib.data.Dataset(通过创建源或应用转换操作)和tf.contrib.data.Iterator
  • 馈送:这允许我们将数据注入计算图中的任何张量。
  • 从文件中读取:这允许我们使用 Python 的内置机制开发输入管道,用于从图开头的数据文件中读取数据。
  • 预加载数据:对于小数据集,我们可以使用 TensorFlow 图中的常量或变量来保存所有数据。

在本节中,我们将看到馈送机制的例子。我们将在接下来的章节中看到其他方法。 TensorFlow 提供了一种馈送机制,允许我们将数据注入计算图中的任何张量。您可以通过feed_dict参数将源数据提供给启动计算的run()eval()调用。

提示

使用feed_dict参数进行馈送是将数据提供到 TensorFlow 执行图中的最低效方法,并且仅应用于需要小数据集的小型实验。它也可以用于调试。

我们还可以用馈送数据(即变量和常量)替换任何张量。最佳做法是使用tf.placeholder()使用 TensorFlow 占位符节点。占位符专门用作馈送的目标。空占位符未初始化,因此不包含任何数据。

因此,如果在没有馈送的情况下执行它,它将始终生成错误,因此您不会忘记提供它。以下示例显示如何提供数据以构建随机2×3矩阵:

import tensorflow as tf
import numpy as np

a = 3
b = 2
x = tf.placeholder(tf.float32,shape=(a,b))
y = tf.add(x,x)

data = np.random.rand(a,b)
sess = tf.Session()
print(sess.run(y,feed_dict={x:data}))

sess.close()# close the session

输出如下:

>>>
[[ 1.78602004  1.64606333]
 [ 1.03966308  0.99269408]
 [ 0.98822606  1.50157797]]
>>>

通过 TensorBoard 可视化计算

TensorFlow 包含函数 ,允许您在名为 TensorBoard 的可视化工具中调试和优化程序。使用 TensorBoard,您可以以图形方式观察有关图任何部分的参数和详细信息的不同类型的统计数据。

此外,在使用复杂的 DNN 进行预测建模时,图可能很复杂且令人困惑。为了更容易理解,调试和优化 TensorFlow 程序,您可以使用 TensorBoard 可视化 TensorFlow 图,绘制有关图执行的量化指标,并显示其他数据,例如通过它的图像。

因此,TensorBoard 可以被认为是一个用于分析和调试预测模型的框架。 TensorBoard 使用所谓的摘要来查看模型的参数:一旦执行了 TensorFlow 代码,我们就可以调用 TensorBoard 来查看 GUI 中的摘要。

TensorBoard 如何运作?

TensorFlow 使用计算图来执行应用。在计算图中,节点表示操作,弧是操作之间的数据。

TensorBoard 的主要思想是将摘要与图上的节点(操作)相关联。代码运行时,摘要操作将序列化节点的数据并将数据输出到文件中。稍后,TensorBoard 将可视化汇总操作。有关更详细的讨论,读者可以参考此链接

简而言之,TensorBoard 是一套 Web 应用,用于检查和理解您的 TensorFlow 运行和图。使用 TensorBoard 时的工作流程如下:

  1. 构建计算图/代码

  2. 将摘要操作附加到您要检查的节点

  3. 像往常一样开始运行图

  4. 运行摘要操作

  5. 执行完成后,运行 TensorBoard 以显示摘要输出

    file_writer = tf.summary.FileWriter('/path/to/logs', sess.graph)
    

对于步骤 2(即,在运行 TensorBoard 之前),请确保通过创建摘要编写器在日志目录中生成摘要数据:

sess.graph包含图定义;启用图可视化工具

现在,如果你在终端中键入$ which tensorboard,如果你用 pip 安装它,它应该存在:

root@ubuntu:~$ which tensorboard
/usr/local/bin/tensorboard

你需要给它一个日志目录。当您在运行图的目录中时,可以使用以下内容从终端启动它:

tensorboard --logdir path/to/logs

当 TensorBoard 配置完全时,可以通过发出以下命令来访问它:

# Make sure there's no space before or after '="
$ tensorboard –logdir=<trace_file_name>

现在您只需输入http://localhost:6006/即可从浏览器访问localhost:6006。然后它应该是这样的:

How does TensorBoard work?

图 8:在浏览器上使用 TensorBoard

注意

TensorBoard 可用于谷歌浏览器或 Firefox。其他浏览器可能有效,但可能存在错误或表现问题。

这已经太过分了吗?不要担心,在上一节中,我们将结合前面解释的所有想法构建单个输入神经元模型并使用 TensorBoard 进行分析。

线性回归及更多

在本节中,我们将仔细研究 TensorFlow 和 TensorBoard 的主要概念,并尝试做一些基本操作来帮助您入门。我们想要实现的模型模拟线性回归。

在统计和 ML 中,线性回归是一种经常用于衡量变量之间关系的技术。这是一种非常简单但有效的算法,也可用于预测建模。

线性回归模拟因变量y[i],自变量x[i],和随机项b。这可以看作如下:

Linear regression and beyond

使用 TensorFlow 的典型线性回归问题具有以下工作流程,该工作流程更新参数以最小化给定成本函数(参见下图):

Linear regression and beyond

图 9:在 TensorFlow 中使用线性回归的学习算法

现在,让我们尝试按照前面的图,通过概念化前面的等式将其重现为线性回归。为此,我们将编写一个简单的 Python 程序,用于在 2D 空间中创建数据。然后我们将使用 TensorFlow 来寻找最适合数据点的线(如下图所示):

# Import libraries (Numpy, matplotlib)

import numpy as np
import matplotlib.pyplot as plot

# Create 1000 points following a function y=0.1 * x + 0.4z
(i.e. # y = W * x + b) with some normal random distribution:

num_points = 1000
vectors_set = []

# Create a few random data points 
for i in range(num_points):
    W = 0.1 # W
   b = 0.4 # b
    x1 = np.random.normal(0.0, 1.0)#in: mean, standard deviation
    nd = np.random.normal(0.0, 0.05)#in:mean,standard deviation
    y1 = W * x1 + b

 # Add some impurity with normal distribution -i.e. nd 
    y1 = y1 + nd

 # Append them and create a combined vector set:
    vectors_set.append([x1, y1])

# Separate the data point across axises:
x_data = [v[0] for v in vectors_set]
y_data = [v[1] for v in vectors_set]

# Plot and show the data points in a 2D space
plot.plot(x_data, y_data, 'ro', label='Original data')
plot.legend()
plot.show()

如果您的编译器没有报错,您应该得到以下图表:

Linear regression and beyond

图 10:随机生成(但原始)数据

好吧,到目前为止,我们刚刚创建了一些数据点而没有可以通过 TensorFlow 执行的相关模型。因此,下一步是创建一个线性回归模型,该模型可以获得从输入数据点估计的输出值y,即x_data。在这种情况下,我们只有两个相关参数,Wb

现在的目标是创建一个图,允许我们根据输入数据x_data,通过将它们调整为y_data来找到这两个参数的值。因此,我们的目标函数如下:

Linear regression and beyond

如果你还记得,我们在 2D 空间中创建数据点时定义了W = 0.1b = 0.4。 TensorFlow 必须优化这两个值,使W趋于 0.1 和b为 0.4。

解决此类优化问题的标准方法是迭代数据点的每个值并调整Wb的值,以便为每次迭代获得更精确的答案。要查看值是否确实在改善,我们需要定义一个成本函数来衡量某条线的优质程度。

在我们的例子中,成本函数是均方误差,这有助于我们根据实际数据点与每次迭代的估计距离函数之间的距离函数找出误差的平均值。我们首先导入 TensorFlow 库:

import tensorflow as tf
W = tf.Variable(tf.zeros([1]))
b = tf.Variable(tf.zeros([1]))
y = W * x_data + b

在前面的代码段中,我们使用不同的策略生成一个随机点并将其存储在变量W中。现在,让我们定义一个损失函数loss = mean[(y - y_data)^2],这将返回一个标量值,其中包含我们之间所有距离的均值。数据和模型预测。就 TensorFlow 约定而言,损失函数可表示如下:

loss = tf.reduce_mean(tf.square(y - y_data))

前一行实际上计算均方误差(MSE)。在不进一步详述的情况下,我们可以使用一些广泛使用的优化算法,例如 GD。在最低级别,GD 是一种算法,它对我们已经拥有的一组给定参数起作用。

它以一组初始参数值开始,并迭代地移向一组值,这些值通过采用另一个称为学习率的参数来最小化函数。通过在梯度函数的负方向上采取步骤来实现这种迭代最小化:

optimizer = tf.train.GradientDescentOptimizer(0.6)
train = optimizer.minimize(loss)

在运行此优化函数之前,我们需要初始化到目前为止所有的变量。让我们使用传统的 TensorFlow 技术,如下所示:

init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)

由于我们已经创建了 TensorFlow 会话,我们已准备好进行迭代过程,帮助我们找到Wb的最佳值:

for i in range(6):
  sess.run(train)
  print(i, sess.run(W), sess.run(b), sess.run(loss))

您应该观察以下输出:

>>>
0 [ 0.18418592] [ 0.47198644] 0.0152888
1 [ 0.08373772] [ 0.38146532] 0.00311204
2 [ 0.10470386] [ 0.39876288] 0.00262051
3 [ 0.10031486] [ 0.39547175] 0.00260051
4 [ 0.10123629] [ 0.39609471] 0.00259969
5 [ 0.1010423] [ 0.39597753] 0.00259966
6 [ 0.10108326] [ 0.3959994] 0.00259966
7 [ 0.10107458] [ 0.39599535] 0.00259966

你可以看到算法从W = 0.18418592b = 0.47198644的初始值开始,损失非常高。然后,算法通过最小化成本函数来迭代地调整值。在第八次迭代中,所有值都倾向于我们期望的值。

现在,如果我们可以绘制它们怎么办?让我们通过在for循环下添加绘图线来实现 ,如下所示:

for i in range(6):
       sess.run(train)
       print(i, sess.run(W), sess.run(b), sess.run(loss))
       plot.plot(x_data, y_data, 'ro', label='Original data')
       plot.plot(x_data, sess.run(W)*x_data + sess.run(b))
       plot.xlabel('X')
       plot.xlim(-2, 2)
       plot.ylim(0.1, 0.6)
       plot.ylabel('Y')
       plot.legend()
       plot.show()

前面的代码块应该生成下图(虽然合并在一起):

Linear regression and beyond

图 11:在第六次迭代后优化损失函数的线性回归

现在让我们进入第 16 次迭代:

>>>
0 [ 0.23306453] [ 0.47967502] 0.0259004
1 [ 0.08183448] [ 0.38200468] 0.00311023
2 [ 0.10253634] [ 0.40177572] 0.00254209
3 [ 0.09969243] [ 0.39778906] 0.0025257
4 [ 0.10008509] [ 0.39859086] 0.00252516
5 [ 0.10003048] [ 0.39842987] 0.00252514
6 [ 0.10003816] [ 0.39846218] 0.00252514
7 [ 0.10003706] [ 0.39845571] 0.00252514
8 [ 0.10003722] [ 0.39845699] 0.00252514
9 [ 0.10003719] [ 0.39845672] 0.00252514
10 [ 0.1000372] [ 0.39845678] 0.00252514
11 [ 0.1000372] [ 0.39845678] 0.00252514
12 [ 0.1000372] [ 0.39845678] 0.00252514
13 [ 0.1000372] [ 0.39845678] 0.00252514
14 [ 0.1000372] [ 0.39845678] 0.00252514
15 [ 0.1000372] [ 0.39845678] 0.00252514

好多了,我们更接近优化的值,对吧?现在,如果我们通过 TensorFlow 进一步改进我们的可视化分析,以帮助可视化这些图中发生的事情,该怎么办? TensorBoard 提供了一个网页,用于调试图并检查变量,节点,边缘及其相应的连接。

此外,我们需要使用变量标注前面的图,例如损失函数,Wby_datax_data等。然后,您需要通过调用tf.summary.merge_all()函数生成所有摘要。

现在,我们需要对前面的代码进行如下更改。但是,最好使用tf.name_scope()函数对图上的相关节点进行分组。因此,我们可以使用tf.name_scope()来组织 TensorBoard 图表视图中的内容,但让我们给它一个更好的名称:

with tf.name_scope("LinearRegression") as scope:
   W = tf.Variable(tf.zeros([1]))
   b = tf.Variable(tf.zeros([1]))
   y = W * x_data + b

然后,让我们以类似的方式标注损失函数,但使用合适的名称,例如LossFunction

with tf.name_scope("LossFunction") as scope:
  loss = tf.reduce_mean(tf.square(y - y_data))

让我们标注 TensorBoard 所需的损失,权重和偏置:

loss_summary = tf.summary.scalar("loss", loss)
w_ = tf.summary.histogram("W", W)
b_ = tf.summary.histogram("b", b)

一旦您标注了图,就可以通过合并来配置摘要了:

merged_op = tf.summary.merge_all()

在运行训练之前(初始化之后),使用tf.summary.FileWriter() API 编写摘要,如下所示:

writer_tensorboard = tf.summary.FileWriter('logs/', tf.get_default_graph())

然后按如下方式启动 TensorBoard:

$ tensorboard –logdir=<trace_dir_name>

在我们的例子中,它可能类似于以下内容:

$ tensorboard --logdir=/home/root/LR/

现在让我们转到http://localhost:6006并单击 GRAPH 选项卡。您应该看到以下图表:

Linear regression and beyond

图 12:TensorBoard 上的主图和辅助节点

提示

请注意,Ubuntu 可能会要求您安装python-tk包。您可以通过在 Ubuntu 上执行以下命令来执行此操作:

$ sudo apt-get install python-tk
# For Python 3.x, use the following
$ sudo apt-get install python3-tk

真实数据集的线性回归回顾

在上一节中,我们看到线性回归的一个例子。我们看到了如何将 TensorFlow 与随机生成的数据集一起使用,即假数据。我们已经看到回归是一种用于预测连续(而非离散)输出的监督机器学习。

然而,对假数据进行线性回归就像买一辆新车但从不开车。这个令人敬畏的机器需要在现实世界中使用!幸运的是,许多数据集可在线获取,以测试您新发现的回归知识。

其中一个是波士顿住房数据集,可以从 UCI 机器学习库下载。它也可以作为 scikit-learn 的预处理数据集使用。

所以,让我们开始导入所有必需的库,包括 TensorFlow,NumPy,Matplotlib 和 scikit-learn:

import matplotlib.pyplot as plt
import tensorflow as tf
import numpy as np
from numpy import genfromtxt
from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split

接下来,我们需要准备由波士顿住房数据集中的特征和标签组成的训练集。read_boston_data ()方法从 scikit-learn 读取并分别返回特征和标签:

def read_boston_data():
    boston = load_boston()
    features = np.array(boston.data)
    labels = np.array(boston.target)
    return features, labels

现在我们已经拥有特征和标签,我们还需要使用normalizer()方法对特征进行标准化。这是方法的签名:

def normalizer(dataset):
    mu = np.mean(dataset,axis=0)
    sigma = np.std(dataset,axis=0)
    return(dataset - mu)/sigma

bias_vector()用于将偏差项(即全 1)附加到我们在前一步骤中准备的标准化特征。它对应于前一个例子中直线方程中的b项:

def bias_vector(features,labels):
    n_training_samples = features.shape[0]
    n_dim = features.shape[1]
    f = np.reshape(np.c_[np.ones(n_training_samples),features],[n_training_samples,n_dim + 1])
    l = np.reshape(labels,[n_training_samples,1])
    return f, l

我们现在将调用这些方法并将数据集拆分为训练和测试,75% 用于训练和休息用于测试:

features,labels = read_boston_data()
normalized_features = normalizer(features)
data, label = bias_vector(normalized_features,labels)
n_dim = data.shape[1]
# Train-test split
train_x, test_x, train_y, test_y = train_test_split(data,label,test_size = 0.25,random_state = 100)

现在让我们使用 TensorFlow 的数据结构(例如占位符,标签和权重):

learning_rate = 0.01
training_epochs = 100000
log_loss = np.empty(shape=[1],dtype=float)
X = tf.placeholder(tf.float32,[None,n_dim]) #takes any number of rows but n_dim columns
Y = tf.placeholder(tf.float32,[None,1]) # #takes any number of rows but only 1 continuous column
W = tf.Variable(tf.ones([n_dim,1])) # W weight vector

做得好!我们已经准备好构建 TensorFlow 图所需的数据结构。现在是构建线性回归的时候了,这非常简单:

y_ = tf.matmul(X, W)
cost_op = tf.reduce_mean(tf.square(y_ - Y))
training_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost_op)

在前面的代码段中,第一行将特征矩阵乘以可用于预测的权重矩阵。第二行计算损失,即回归线的平方误差。最后,第三行执行 GD 优化的一步以最小化平方误差。

提示

使用哪种优化器:使用优化器的主要目的是最小化成本;因此,我们必须定义一个优化器。使用最常见的优化器(如 SGD),学习率必须以1 / T进行缩放才能获得收敛,其中T是迭代次数。

Adam 或 RMSProp 尝试通过调整步长来自动克服此限制,以使步长与梯度具有相同的比例。此外,在前面的示例中,我们使用了 Adam 优化器,它在大多数情况下都表现良好。

然而,如果您正在训练神经网络计算梯度是必须的,使用实现 RMSProp 算法的RMSPropOptimizer函数是一个更好的主意,因为它是在小批量设置中学习的更快的方式。研究人员还建议在训练深度 CNN 或 DNN 时使用 Momentum 优化器。

从技术上讲,RMSPropOptimizer 是一种先进的梯度下降形式,它将学习率除以指数衰减的平方梯度平均值。衰减参数的建议设置值为 0.9,而学习率的良好默认值为 0.001。

例如在 TensorFlow 中,tf.train.RMSPropOptimizer()帮助我们轻松使用它:

optimizer = tf.train.RMSPropOptimizer(0.001, 0.9).minimize(cost_op)

现在,在我们开始训练模型之前,我们需要使用initialize_all_variables()方法初始化所有变量,如下所示:

init = tf.initialize_all_variables()

太棒了!现在我们已经设法准备好所有组件,我们已经准备好训练实际的训练了。我们首先创建 TensorFlow 会话,如下所示:

sess = tf.Session()
sess.run(init_op)
for epoch in range(training_epochs):
    sess.run(training_step,feed_dict={X:train_x,Y:train_y})
    log_loss = np.append(log_loss,sess.run(cost_op,feed_dict={X: train_x,Y: train_y}))

训练完成后,我们就可以对看不见的数据进行预测。然而,看到完成训练的直观表示会更令人兴奋。因此,让我们使用 Matplotlib 将成本绘制为迭代次数的函数:

plt.plot(range(len(log_loss)),log_loss)
plt.axis([0,training_epochs,0,np.max(log_loss)])
plt.show()

以下是上述代码的输出:

>>>

Linear regression revisited for a real dataset

图 13:作为迭代次数函数的成本

对测试数据集做一些预测并计算均方误差:

pred_y = sess.run(y_, feed_dict={X: test_x})
mse = tf.reduce_mean(tf.square(pred_y - test_y))
print("MSE: %.4f" % sess.run(mse))

以下是上述代码的输出:

>>>
MSE: 27.3749

最后,让我们展示最佳拟合线:

fig, ax = plt.subplots()
ax.scatter(test_y, pred_y)
ax.plot([test_y.min(), test_y.max()], [test_y.min(), test_y.max()], 'k--', lw=3)
ax.set_xlabel('Measured')
ax.set_ylabel('Predicted')
plt.show()

以下是上述代码的输出:

>>>

Linear regression revisited for a real dataset

图 14:预测值与实际值

总结

TensorFlow 旨在通过 ML 和 DL 轻松地为每个人进行预测分析,但使用它确实需要对一些通用原则和算法有一个正确的理解。 TensorFlow 的最新版本带有许多令人兴奋的新功能,所以我们试图覆盖它们,以便您可以轻松使用它们。总之,这里简要回顾一下本章已经解释过的 TensorFlow 的关键概念:

  • 图:每个 TensorFlow 计算可以表示为数据流图,其中每个图构建为一组操作对象。有三种核心图数据结构:tf.Graphtf.Operationtf.Tensor )。
  • 操作:图节点将一个或多个张量作为输入,并产生一个或多个张量作为输出。节点可以由操作对象表示,用于执行诸如加法,乘法,除法,减法或更复杂操作的计算单元。
  • 张量:它们就像高维数组对象。换句话说,它们可以表示为数据流图的边缘,并且是不同操作的输出。
  • 会话:会话对象是一个实体,它封装了执行操作对象的环境,以便在数据流图上运行计算。结果,在run()eval()调用内部求值张量对象。

在本章的后面部分,我们介绍了 TensorBoard,它是分析和调试神经网络模型的强大工具。最后,我们看到了如何在假数据集和真实数据集上实现最简单的基于 TensorFlow 的线性回归模型之一。

在下一章中,我们将讨论不同 FFNN 架构的理论背景,如深度信念网络(DBN)和多层感知器(MLP)。

然后,我们将展示如何训练和分析评估模型所需的表现指标,然后通过一些方法调整 FFNN 的超参数以优化表现。最后,我们将提供两个使用 MLP 和 DBN 的示例,说明如何为银行营销数据集建立非常强大和准确的预测分析模型。

三、实现前馈神经网络

自动识别手写数字是一个重要的问题,可以在许多实际应用中找到。在本节中,我们将实现一个前馈网络来解决这个问题。

Implementing a feed-forward neural network

图 3:从 MNIST 数据库中提取的数据示例

为了训练和测试已实现的模型,我们将使用一个名为 MNIST 的手写数字最着名的数据集。 MNIST 数据集是一个包含 60,000 个示例的训练集和一个包含 10,000 个示例的测试集。存储在示例文件中的数据示例如上图所示。

源图像最初是黑白的。之后,为了将它们标准化为20×20像素的大小,由于抗混叠滤波器用于调整大小的效果,引入了中间亮度级别。随后,在28×28像素的区域中将图像聚焦在像素的质心中,以便改善学习过程。整个数据库存储在四个文件中:

  • train-images-idx3-ubyte.gz:训练集图像(9912422 字节)
  • train-labels-idx1-ubyte.gz:训练集标签(28881 字节)
  • t10k-images-idx3-ubyte.gz:测试集图像(1648877 字节)
  • t10k-labels-idx1-ubyte.gz:测试集标签(4542 字节)

每个数据库包含两个文件的 ;第一个包含图像,而第二个包含相应的标签。

探索 MNIST 数据集

让我们看一下如何访问 MNIST 数据的简短示例,以及如何显示所选图像。为此,只需执行Explore_MNIST.py脚本。首先,我们必须导入 numpy,因为我们必须进行一些图像处理:

import numpy as np

Matplotlib 中的pyplot函数用于绘制图像:

import matplotlib.pyplot as plt

我们将使用tensorflow.examples.tutorials.mnist中的input_data类,它允许我们下载 MNIST 数据库并构建数据集:

import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

然后我们使用read_data_sets方法加载数据集:

import os
dataPath = "temp/"
if not os.path.exists(dataPath):
    os.makedirs(dataPath)
input = input_data.read_data_sets(dataPath, one_hot=True)

图像将保存在temp/目录中。现在让我们看看图像和标签的形状:

print(input.train.images.shape)
print(input.train.labels.shape)
print(input.test.images.shape)
print(input.test.labels.shape)

以下是上述代码的输出:

>>>
(55000, 784)
(55000, 10)
(10000, 784)
(10000, 10)

使用 Python 库matplotlib,我们想要可视化一个数字:

image_0 =  input.train.images[0]
image_0 = np.resize(image_0,(28,28))
label_0 =  input.train.labels[0]
print(label_0)

以下是上述代码的输出:

>>>
[ 0\.  0\.  0\.  0\.  0\.  0\.  0\.  1\.  0\.  0.]

数字1是数组的第八个位置。这意味着我们图像的数字是数字 7。最后,我们必须验证数字是否真的是 7。我们可以使用导入的plt函数来绘制image_0张量:

plt.imshow(image_0, cmap='Greys_r')
plt.show()

Exploring the MNIST dataset

图 4:从 MNIST 数据集中提取的图像

Softmax 分类器

在上一节中,我们展示了如何访问和操作 MNIST 数据集。在本节中,我们将看到如何使用前面的数据集来解决 TensorFlow 手写数字的分类问题。我们将应用所学的概念来构建更多神经网络模型,以便评估和比较所采用的不同方法的结果。

将要实现的第一个前馈网络架构如下图所示:

Softmax classifier

图 5:softmax 神经网络架构

我们将构建一个五层网络:第一层到第四层是 Sigmoid 结构,第五层是 softmax 激活函数。请记住,定义此网络是为了激活它是一组正值,总和等于 1。这意味着输出的第j个值是与网络输入对应的类j的概率。让我们看看如何实现我们的神经网络模型。

为了确定网络的适当大小(即,层中的神经元或单元的数量),即隐藏层的数量和每层神经元的数量,通常我们依赖于一般的经验标准,个人经验或适当的测试。这些是需要调整的一些超参数。在本章的后面,我们将看到一些超参数优化的例子。

下表总结了已实现的网络架构。它显示了每层神经元的数量,以及相应的激活函数:

神经元数量激活函数
1L = 200Sigmoid
2M = 100Sigmoid
3N = 60Sigmoid
4O = 30Sigmoid
510Softmax

前四层的激活函数是 Sigmoid 函数。激活函数的最后一层始终是 softmax,因为网络的输出必须表示输入数字的概率。通常,中间层的数量和大小会极大地影响的网络表现:

  • 以积极的方式,因为在这些层上是基于网络推广的能力,并检测输入的特殊特征
  • 以负面的方式,因为如果网络是冗余的,那么它会不必要地减轻学习阶段的负担

为此,只需执行five_layers_sigmoid.py脚本。首先,我们将通过导入以下库来开始实现网络:

import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import math
from tensorflow.python.framework import ops
import random
import os

接下来,我们将设置以下配置参数:

logs_path = 'log_sigmoid/' # logging path
batch_size = 100 # batch size while performing training 
learning_rate = 0.003 # Learning rate 
training_epochs = 10 # training epoch
display_epoch = 1

然后,我们将下载图像和标签,并准备数据集:

dataPath = "temp/"
if not os.path.exists(dataPath):
    os.makedirs(dataPath)
mnist = input_data.read_data_sets(dataPath, one_hot=True) # MNIST to be downloaded

从输入层开始,我们现在将看看如何构建网络架构。输入层现在是形状[1×784]的张量 - 即[1,28 * 28],它代表要分类的图像:

X = tf.placeholder(tf.float32, [None, 784], name='InputData') # image shape 28*28=784
XX = tf.reshape(X, [-1, 784]) # reshape input
Y_ = tf.placeholder(tf.float32, [None, 10], name='LabelData') # 0-9 digits => 10 classes

第一层接收要分类的输入图像的像素,与W1权重连接组合,并添加到B1偏差张量的相应值:

W1 = tf.Variable(tf.truncated_normal([784, L], stddev=0.1)) # Initialize random weights for the hidden layer 1
B1 = tf.Variable(tf.zeros([L])) # Bias vector for layer 1

第一层通过 sigmoid 激活函数将其输出发送到第二层:

Y1 = tf.nn.sigmoid(tf.matmul(XX, W1) + B1) # Output from layer 1

第二层从第一层接收Y1输出,将其与W2权重连接组合,并将其添加到B2偏差张量的相应值:

W2 = tf.Variable(tf.truncated_normal([L, M], stddev=0.1)) # Initialize random weights for the hidden layer 2
B2 = tf.Variable(tf.ones([M])) # Bias vector for layer 2

第二层通过 sigmoid 激活函数将其输出发送到第三层:

Y2 = tf.nn.sigmoid(tf.matmul(Y1, W2) + B2) # Output from layer 2

第三层接收来自第二层的Y2输出,将其与W3权重连接组合,并将其添加到B3偏差张量的相应值:

W3 = tf.Variable(tf.truncated_normal([M, N], stddev=0.1)) # Initialize random weights for the hidden layer 3 
B3 = tf.Variable(tf.ones([N])) # Bias vector for layer 3

第三层通过 sigmoid 激活函数将其输出发送到第四层:

Y3 = tf.nn.sigmoid(tf.matmul(Y2, W3) + B3) # Output from layer 3

第四层接收来自第三层的Y3输出,将其与W4权重连接组合,并将其添加到B4偏差张量的相应值:

W4 = tf.Variable(tf.truncated_normal([N, O], stddev=0.1)) # Initialize random weights for the hidden layer 4
B4 = tf.Variable(tf.ones([O])) # Bias vector for layer 4

然后通过 Sigmoid 激活函数将第四层的输出传播到第五层:

Y4 = tf.nn.sigmoid(tf.matmul(Y3, W4) + B4) # Output from layer 4

第五层将在输入中接收来自第四层的激活O = 30,该激活将通过softmax激活函数,转换为每个数字的相应概率类别:

W5 = tf.Variable(tf.truncated_normal([O, 10], stddev=0.1)) # Initialize random weights for the hidden layer 5 
B5 = tf.Variable(tf.ones([10])) # Bias vector for layer 5
Ylogits = tf.matmul(Y4, W5) + B5 # computing the logits
Y = tf.nn.softmax(Ylogits)# output from layer 5

这里,我们的损失函数是目标和softmax激活函数之间的交叉熵,应用于模型的预测:

cross_entropy = tf.nn.softmax_cross_entropy_with_logits_v2(logits=Ylogits, labels=Y) # final outcome using softmax cross entropy
cost_op = tf.reduce_mean(cross_entropy)*100

另外,我们定义correct_prediction和模型的准确率:

correct_prediction = tf.equal(tf.argmax(Y, 1), tf.argmax(Y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

现在我们需要使用优化器来减少训练误差。与简单的GradientDescentOptimizer相比,AdamOptimizer具有几个优点。实际上,它使用更大的有效步长,算法将收敛到此步长而不进行微调:

# Optimization op (backprop)
train_op = tf.train.AdamOptimizer(learning_rate).minimize(cost_op)

Optimizer基类提供了计算损失梯度的方法,并将梯度应用于变量。子类集合实现了经典的优化算法,例如GradientDescentAdagrad。在 TensorFlow 中训练 NN 模型时,我们从不实例化Optimizer类本身,而是实例化以下子类之一

此链接tf.contrib.opt用于更多优化器。

然后让我们构建一个将所有操作封装到范围中的模型,使 TensorBoard 的图可视化更加方便:

# Create a summary to monitor cost tensor
tf.summary.scalar("cost", cost_op)
# Create a summary to monitor accuracy tensor
tf.summary.scalar("accuracy", accuracy)
# Merge all summaries into a single op
summary_op = tf.summary.merge_all()

最后,我们将开始训练:

with tf.Session() as sess:
        # Run the initializer
    sess.run(init_op)

    # op to write logs to TensorBoard
    writer = tf.summary.FileWriter(logs_path, graph=tf.get_default_graph())

    for epoch in range(training_epochs):
        batch_count = int(mnist.train.num_examples/batch_size)
        for i in range(batch_count):
            batch_x, batch_y = mnist.train.next_batch(batch_size)
            _,summary = sess.run([train_op, summary_op], feed_dict={X: batch_x, Y_: batch_y})
            writer.add_summary(summary, epoch * batch_count + i)

        print("Epoch: ", epoch)
    print("Optimization Finished!")

    print("Accuracy: ", accuracy.eval(feed_dict={X: mnist.test.images, Y_: mnist.test.labels}))

定义摘要和会话运行的源代码几乎与前一个相同。我们可以直接转向评估实现的模型。运行模型时,我们有以下输出:

运行此代码后的最终测试设置准确率应约为 97%:

Extracting temp/train-images-idx3-ubyte.gz
Extracting temp/train-labels-idx1-ubyte.gz
Extracting temp/t10k-images-idx3-ubyte.gz
Extracting temp/t10k-labels-idx1-ubyte.gz
Epoch:  0
Epoch:  1
Epoch:  2
Epoch:  3
Epoch:  4
Epoch:  5
Epoch:  6
Epoch:  7
Epoch:  8
Epoch:  9
Optimization Finished!
Accuracy:  0.9
715

现在我们可以通过在运行文件夹中打开终端然后执行以下命令来移动到 TensorBoard:

$> tensorboard --logdir='log_sigmoid/' # if required, provide absolute path

然后我们在localhost上打开浏览器。在下图中,我们显示了成本函数的趋势,作为示例数量的函数,在训练集上,以及测试集的准确率:

Softmax classifier

图 6:测试集上的准确率函数,以及训练集上的成本函数

成本函数随着迭代次数的增加而减少。如果没有发生这种情况,则意味着出现了问题。在最好的情况下,这可能只是因为某些参数未正确设置。在最坏的情况下,构建的数据集中可能存在问题,例如,信息太少或图像质量差。如果发生这种情况,我们必须直接修复数据集。

到目前为止,我们已经看到了 FFNN 的实现。但是,使用真实数据集探索更有用的 FFNN 实现会很棒。我们将从 MLP 开始。

实现多层感知器(MLP)

感知器单层 LTU 组成,每个神经元连接到所有输入。这些连接通常使用称为输入神经元的特殊传递神经元来表示:它们只输出它们被输入的任何输入。此外,通常添加额外的偏置特征(x0 = 1)。

这种偏置特征通常使用称为偏置神经元的特殊类型的神经元来表示,一直输出 1。具有两个输入和三个输出的感知器在图 7 中表示。该感知器可以同时将实例分类为三个不同的二元类,这使其成为多输出分类器:

Implementing a multilayer perceptron (MLP)

图 7:具有两个输入和三个输出的感知器

由于每个输出神经元的决策边界是线性的,因此感知器无法学习复杂模式。然而,如果训练实例是线性可分的,研究表明该算法将收敛于称为“感知器收敛定理”的解决方案。

MLP 是 FFNN,这意味着它是来自不同层的神经元之间的唯一连接。更具体地,MLP 由一个(通过)输入层,一个或多个 LTU 层(称为隐藏层)和一个称为输出层的 LTU 的最后一层组成。除输出层外,每一层都包含一个偏置神经元,并作为完全连接的二分图连接到下一层:

Implementing a multilayer perceptron (MLP)

图 8:MLP 由一个输入层,一个隐藏层和一个输出层组成

训练 MLP

MLP 在 1986 年首次使用反向传播训练算法成功训练。然而,现在这种算法的优化版本被称为梯度下降。在训练阶段期间,对于每个训练实例,算法将其馈送到网络并计算每个连续层中的每个神经元的输出。

训练算法测量网络的输出误差(即,期望输出和网络的实际输出之间的差异),并计算最后隐藏层中每个神经元对每个输出神经元误差的贡献程度。然后,它继续测量这些误差贡献中有多少来自先前隐藏层中的每个神经元,依此类推,直到算法到达输入层。通过在网络中向后传播误差梯度,该反向传递有效地测量网络中所有连接权重的误差梯度。

在技​​术上,通过反向传播方法计算每层的成本函数的梯度。梯度下降的想法是有一个成本函数,显示某些神经网络的预测输出与实际输出之间的差异:

Training an MLP

图 9:用于无监督学习的 ANN 的示例实现

有几种已知类型的代价函数,例如平方误差函数和对数似然函数。该成本函数的选择可以基于许多因素。梯度下降法通过最小化此成本函数来优化网络的权重。步骤如下:

  1. 权重初始化
  2. 计算神经网络的预测输出,通常称为转发传播步骤
  3. 计算成本/损失函数。一些常见的成本/损失函数包括对数似然函数和平方误差函数
  4. 计算成本/损失函数的梯度。对于大多数 DNN 架构,最常见的方法是反向传播
  5. 基于当前权重的权重更新,以及成本/损失函数的梯度
  6. 步骤 2 到 5 的迭代,直到成本函数,达到某个阈值或经过一定量的迭代

图 9 中可以看到梯度下降的图示。该图显示了基于网络权重的神经网络的成本函数。在梯度下降的第一次迭代中,我们将成本函数应用于一些随机初始权重。对于每次迭代,我们在梯度方向上更新权重,这对应于图 9 中的箭头。重复更新直到一定次数的迭代或直到成本函数达到某个阈值。

使用 MLP

多层感知器通常用于以监督方式解决分类和回归问题。尽管 CNN 逐渐取代了它们在图像和视频数据中的实现,但仍然可以有效地使用低维和数字特征 MLP:可以解决二元和多类分类问题。

Using MLPs

图 10:用于分类的现代 MLP(包括 ReLU 和 softmax)

然而,对于多类分类任务和训练,通常通过用共享 softmax 函数替换各个激活函数来修改输出层。每个神经元的输出对应于相应类的估计概率。请注意,信号仅在一个方向上从输入流向输出,因此该架构是 FFNN 的示例。

作为案例研究,我们将使用银行营销数据集。这些数据与葡萄牙银行机构的直接营销活动有关。营销活动基于电话。通常,不止一次联系同一个客户,以评估产品(银行定期存款)是(是)还是不(否)订阅。 目标是使用 MLP 来预测客户是否会订阅定期存款(变量 y),即二元分类问题。

数据集描述

我想在此承认有两个来源。这个数据集被用于 Moro 和其他人发表的一篇研究论文中:一种数据驱动的方法来预测银行电话营销的成功,决策支持系统,Elsevier,2014 年 6 月。后来,它被捐赠给了 UCI 机器学习库,可以从此链接下载。

根据数据集描述,有四个数据集:

  • bank-additional-full.csv:这包括所有示例(41,188)和 20 个输入,按日期排序(从 2008 年 5 月到 2010 年 11 月)。这些数据非常接近 Moro 和其他人分析的数据
  • bank-additional.csv:这包括 10% 的例子(4119),从 1 和 20 个输入中随机选择
  • bank-full.csv:这包括按日期排序的所有示例和 17 个输入(此数据集的较旧版本,输入较少)
  • bank.csv:这包括 10% 的示例和 17 个输入,从 3 中随机选择(此数据集的较旧版本,输入较少)

数据集中有 21 个属性。独立变量可以进一步分类为与客户相关的数据(属性 1 到 7),与当前活动的最后一次联系(属性 8 到 11)相关。其他属性(属性 12 至 15)以及社会和经济背景属性(属性 16 至 20)被分类。因变量由 y 指定,最后一个属性(21):

ID属性说明
1age年龄数字。
2job这是类别格式的职业类型,具有可能的值:adminblue-collarentrepreneurhousemaidmanagementretiredself-employedservicesstudenttechnicianunemployedunknown
3marital这是类别格式的婚姻状态,具有可能的值:divorced(或widowed),marriedsingleunknown
4education这是类别格式的教育背景,具有如下可能的值:basic.4ybasic.6ybasic.9yhigh.schoolilliterateprofessional.courseuniversity.degreeunknown
default这是类别格式的信用,默认情况下可能包含:noyesunknown
6housing客户是否有住房贷款?
7loan类别格式的个人贷款,具有可能的值:noyesunknown
8contact这是类别格式的通信类型,具有可能的值:cellulartelephone
9month这是类别格式的一年中最后一个通话月份,具有可能的值:janfebmar,... novdec
10day_of_week这是类别格式的一周中的最后一个通话日,具有可能的值:montuewedthufri
11duration这是以秒为单位的最后一次通话持续时间(数值)。此属性高度影响输出目标(例如,如果duration = 0,则y = no)。然而,在通话之前不知道持续时间。另外,在通话结束后,y显然是已知的。因此,此输入仅应包括在基准目的中,如果打算采用现实的预测模型,则应将其丢弃。
12campaign这是活动期间此客户的通话数量。
13pdays这是上一个广告系列和客户的上次通话之后经过的天数(数字 -999 表示之前未联系过客户)。
14previous这是之前此广告系列和此客户的通话数量(数字)。
15poutcome上一次营销活动的结果(类别:failurenonexistentsuccess)。
16emp.var.rate这是就业变化率的季度指标(数字)。
17cons.price.idx这是消费者价格指数的月度指标(数字)。
18cons.conf.idx这是消费者信心指数的月度指标(数字)。
19euribor3m这是 3 个月的 euribor 费率的每日指标(数字)。
20nr.employed这是员工数的季度指标(数字)。
21y表示客户是否拥有定期存款,可能值是二元:yesno

预处理

您可以看到数据集尚未准备好直接输入 MLP 或 DBN 分类器,因为该特征与数值和分类值混合在一起。此外,结果变量具有分类值。因此,我们需要将分类值转换为数值,以便特征和结果变量以数字形式。下一步显示了此过程。有关此预处理,请参阅preprocessing_b.py文件。

首先,我们必须加载预处理所需的所需包和库:

import pandas as pd
import numpy as np
from sklearn import preprocessing

然后从上述 URL 下载数据文件并将其放在方便的位置 - 比如说input

然后,我们加载并解析数据集:

data = pd.read_csv('input/bank-additional-full.csv', sep = ";")

接下来,我们将提取变量名称:

var_names = data.columns.tolist()

现在,基于表 1 中的数据集描述,我们将提取分类变量:

categs = ['job','marital','education','default','housing','loan','contact','month','day_of_week','duration','poutcome','y']

然后,我们将提取定量变量:

# Quantitative vars
quantit = [i for i in var_names if i not in categs]

然后让我们得到分类变量的虚拟变量:

job = pd.get_dummies(data['job'])
marital = pd.get_dummies(data['marital'])
education = pd.get_dummies(data['education'])
default = pd.get_dummies(data['default'])
housing = pd.get_dummies(data['housing'])
loan = pd.get_dummies(data['loan'])
contact = pd.get_dummies(data['contact'])
month = pd.get_dummies(data['month'])
day = pd.get_dummies(data['day_of_week'])
duration = pd.get_dummies(data['duration'])
poutcome = pd.get_dummies(data['poutcome'])

现在,是时候映射变量来预测:

dict_map = dict()
y_map = {'yes':1,'no':0}
dict_map['y'] = y_map
data = data.replace(dict_map)
label = data['y']
df_numerical = data[quantit]
df_names = df_numerical .keys().tolist()

一旦我们将分类变量转换为数值变量,下一个任务就是正则化数值变量。因此,使用归一化,我们将单个样本缩放为具有单元规范。如果您计划使用二次形式(如点积或任何其他内核)来量化任何样本对的相似性,则此过程非常有用。该假设是在文本分类和聚类上下文中经常使用的向量空间模型的基础。

那么,让我们来衡量量化变量:

min_max_scaler = preprocessing.MinMaxScaler()
x_scaled = min_max_scaler.fit_transform(df_numerical)
df_temp = pd.DataFrame(x_scaled)
df_temp.columns = df_names

现在我们有(原始)数值变量的临时数据帧,下一个任务是将所有数据帧组合在一起并生成正则化数据帧。我们将使用熊猫:

normalized_df = pd.concat([df_temp,
                      job,
                      marital,
                      education,
                      default,
                      housing,
                      loan,
                      contact,
                      month,
                      day,
                      poutcome,
                      duration,
                      label], axis=1)

最后,我们需要将结果数据帧保存在 CSV 文件中,如下所示:

normalized_df.to_csv('bank_normalized.csv', index = False)

用于客户订阅评估的 TensorFlow 中的 MLP 实现

对于这个例子,我们将使用我们在前面的例子中正则化的银行营销数据集。有几个步骤可以遵循。首先,我们需要导入 TensorFlow,以及其他必要的包和模块:

import tensorflow as tf
import pandas as pd
import numpy as np
import os
from sklearn.cross_validation import train_test_split # for random split of train/test

现在,我们需要加载正则化的银行营销数据集,其中所有特征和标签都是数字。为此,我们使用 pandas 库中的read_csv()方法:

FILE_PATH = 'bank_normalized.csv'         # Path to .csv dataset 
raw_data = pd.read_csv(FILE_PATH)        # Open raw .csv
print("Raw data loaded successfully...\n")

以下是上述代码的输出:

>>>
Raw data loaded successfully...

如前一节所述,调整 DNN 的超参数并不简单。但是,它通常取决于您正在处理的数据集。对于某些数据集,可能的解决方法是根据与数据集相关的统计信息设置这些值,例如,训练实例的数量,输入大小和类的数量。

DNN 不适用于小型和低维数据集。在这些情况下,更好的选择是使用线性模型。首先,让我们放置一个指向标签列本身的指针,计算实例数和类数,并定义训练/测试分流比,如下所示:

Y_LABEL = 'y'    # Name of the variable to be predicted
KEYS = [i for i in raw_data.keys().tolist() if i != Y_LABEL]# Name of predictors
N_INSTANCES = raw_data.shape[0]       # Number of instances
N_INPUT = raw_data.shape[1] - 1            # Input size
N_CLASSES = raw_data[Y_LABEL].unique().shape[0] # Number of classes
TEST_SIZE = 0.25         # Test set size (% of dataset)
TRAIN_SIZE = int(N_INSTANCES * (1 - TEST_SIZE))  # Train size

现在,让我们看一下我们将用于训练 MLP 模型的数据集的统计数据:

print("Variables loaded successfully...\n")
print("Number of predictors \t%s" %(N_INPUT))
print("Number of classes \t%s" %(N_CLASSES))
print("Number of instances \t%s" %(N_INSTANCES))
print("\n")

以下是上述代码的输出:

>>>
Variables loaded successfully...
Number of predictors     1606
Number of classes          2
Number of instances      41188

下一个任务是定义其他参数,例如学习率,训练周期,批量大小和权重的标准偏差。通常,较低的训练率会帮助您的 DNN 学习更慢,但需要集中精力。请注意,我们需要定义更多参数,例如隐藏层数和激活函数。

LEARNING_RATE = 0.001     # learning rate
TRAINING_EPOCHS = 1000     # number of training epoch for the forward pass
BATCH_SIZE = 100     # batch size to be used during training
DISPLAY_STEP = 20   # print the error etc. at each 20 step
HIDDEN_SIZE = 256   # number of neurons in each hidden layer
# We use tanh as the activation function, but you can try using ReLU as well
ACTIVATION_FUNCTION_OUT = tf.nn.tanh
STDDEV = 0.1        # Standard Deviations
RANDOM_STATE = 100

前面的初始化是基于反复试验设置的 。因此,根据您的用例和数据类型,明智地设置它们,但我们将在本章后面提供一些指导。此外,对于前面的代码,RANDOM_STATE用于表示训练的随机状态和测试分割。首先,我们将原始特征和标签分开:

data = raw_data[KEYS].get_values()       # X data
labels = raw_data[Y_LABEL].get_values()  # y data

现在我们有标签,他们必须编码:

labels_ = np.zeros((N_INSTANCES, N_CLASSES))
labels_[np.arange(N_INSTANCES), labels] = 1

最后,我们必须拆分训练和测试集。如前所述,我们将保留 75% 的训练输入,剩下的 25% 用于测试集:

data_train, data_test, labels_train, labels_test = train_test_split(data,labels_,test_size = TEST_SIZE,random_state = RANDOM_STATE)
print("Data loaded and splitted successfully...\n")

以下是上述代码的输出:

>>>
Data loaded and splitted successfully

由于这是一个监督分类问题,我们应该有特征和标签的占位符:

如前所述,MLP 由一个输入层,几个隐藏层和一个称为输出层的最终 LTU 层组成。对于这个例子,我将把训练与四个隐藏层结合起来。因此,我们将分类器称为深度前馈 MLP。请注意,我们还需要在每个层中使用权重(输入层除外),以及每个层中的偏差(输出层除外)。通常,每个隐藏层包括偏置神经元,并且作为从一个隐藏层到另一个隐藏层的完全连接的二分图(前馈)完全连接到下一层。那么,让我们定义隐藏层的大小:

n_input = N_INPUT                   # input n labels 
n_hidden_1 = HIDDEN_SIZE            # 1st layer 
n_hidden_2 = HIDDEN_SIZE            # 2nd layer
n_hidden_3 = HIDDEN_SIZE            # 3rd layer 
n_hidden_4 = HIDDEN_SIZE            # 4th layer 
n_classes = N_CLASSES               # output m classes 

由于这是一个监督分类问题,我们应该有特征和标签的占位符:

# input shape is None * number of input
X = tf.placeholder(tf.float32, [None, n_input])

占位符的第一个维度是None,这意味着我们可以有任意数量的行。第二个维度固定在多个特征上,这意味着每行需要具有该列数量的特征。

# label shape is None * number of classes
y = tf.placeholder(tf.float32, [None, n_classes])

另外,我们需要另一个占位符用于丢弃,这是通过仅以某种可能性保持神经元活动(例如p < 1.0,或者将其设置为零来实现)来实现的。请注意,这也是要调整的超参数和训练时间,而不是测试时间:

dropout_keep_prob = tf.placeholder(tf.float32)

使用此处给出的缩放,可以将相同的网络用于训练(使用dropout_keep_prob < 1.0)和评估(使用dropout_keep_prob == 1.0)。现在,我们可以定义一个实现 MLP 分类器的方法。为此,我们将提供四个参数,如输入,权重,偏置和丢弃概率,如下所示:

def DeepMLPClassifier(_X, _weights, _biases, dropout_keep_prob):
    layer1 = tf.nn.dropout(tf.nn.tanh(tf.add(tf.matmul(_X, _weights['h1']), _biases['b1'])), dropout_keep_prob)
    layer2 = tf.nn.dropout(tf.nn.tanh(tf.add(tf.matmul(layer1, _weights['h2']), _biases['b2'])), dropout_keep_prob)
    layer3 = tf.nn.dropout(tf.nn.tanh(tf.add(tf.matmul(layer2, _weights['h3']), _biases['b3'])), dropout_keep_prob)
    layer4 = tf.nn.dropout(tf.nn.tanh(tf.add(tf.matmul(layer3, _weights['h4']), _biases['b4'])), dropout_keep_prob)
    out = ACTIVATION_FUNCTION_OUT(tf.add(tf.matmul(layer4, _weights['out']), _biases['out']))
return out

上述方法的返回值是激活函数的输出。前面的方法是一个存根实现,它没有告诉任何关于权重和偏置的具体内容,所以在我们开始训练之前,我们应该定义它们:

weights = {
    'w1': tf.Variable(tf.random_normal([n_input, n_hidden_1],stddev=STDDEV)),
    'w2': tf.Variable(tf.random_normal([n_hidden_1, n_hidden_2],stddev=STDDEV)),
    'w3': tf.Variable(tf.random_normal([n_hidden_2, n_hidden_3],stddev=STDDEV)),
    'w4': tf.Variable(tf.random_normal([n_hidden_3, n_hidden_4],stddev=STDDEV)),
    'out': tf.Variable(tf.random_normal([n_hidden_4, n_classes],stddev=STDDEV)),   
}
biases = { 
    'b1': tf.Variable(tf.random_normal([n_hidden_1])), 
    'b2': tf.Variable(tf.random_normal([n_hidden_2])), 
    'b3': tf.Variable(tf.random_normal([n_hidden_3])), 
    'b4': tf.Variable(tf.random_normal([n_hidden_4])), 
    'out': tf.Variable(tf.random_normal([n_classes])) 
}

现在我们可以使用真实参数(输入层,权重,偏置和退出)调用前面的 MLP 实现,保持概率如下:

pred = DeepMLPClassifier(X, weights, biases, dropout_keep_prob)

我们建立了 MLP 模型,是时候训练网络了。首先,我们需要定义成本操作,然后我们将使用 Adam 优化器,它将慢慢学习并尝试尽可能减少训练损失:

cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(logits=pred, labels=y))

# Optimization op (backprop)
optimizer = tf.train.AdamOptimizer(learning_rate = LEARNING_RATE).minimize(cost_op)

接下来,我们需要定义用于计算分类准确率的其他参数:

correct_prediction = tf.equal(tf.argmax(pred, 1), tf.argmax(y, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
print("Deep MLP networks has been built successfully...")
print("Starting training...")

之后,我们需要在启动 TensorFlow 会话之前初始化所有变量和占位符:

init_op = tf.global_variables_initializer() 

现在,我们非常接近开始训练,但在此之前,最后一步是创建 TensorFlow 会话并按如下方式启动它:

sess = tf.Session()
sess.run(init_op)

最后,我们准备开始在训练集上训练我们的 MLP。我们遍历所有批次并使用批量数据拟合以计算平均训练成本。然而,显示每个周期的训练成本和准确率会很棒:

for epoch in range(TRAINING_EPOCHS):
    avg_cost = 0.0
    total_batch = int(data_train.shape[0] / BATCH_SIZE)
    # Loop over all batches
    for i in range(total_batch):
        randidx = np.random.randint(int(TRAIN_SIZE), size = BATCH_SIZE)
        batch_xs = data_train[randidx, :]
        batch_ys = labels_train[randidx, :]
        # Fit using batched data
        sess.run(optimizer, feed_dict={X: batch_xs, y: batch_ys, dropout_keep_prob: 0.9})
        # Calculate average cost
        avg_cost += sess.run(cost, feed_dict={X: batch_xs, y: batch_ys, dropout_keep_prob:1.})/total_batch
    # Display progress
    if epoch % DISPLAY_STEP == 0:
        print("Epoch: %3d/%3d cost: %.9f" % (epoch, TRAINING_EPOCHS, avg_cost))
        train_acc = sess.run(accuracy, feed_dict={X: batch_xs, y: batch_ys, dropout_keep_prob:1.})
        print("Training accuracy: %.3f" % (train_acc))
print("Your MLP model has been trained successfully.")

以下是上述代码的输出:

>>>
Starting training...
Epoch:   0/1000 cost: 0.356494816
Training accuracy: 0.920
…
Epoch: 180/1000 cost: 0.350044933
Training accuracy: 0.860
….
Epoch: 980/1000 cost: 0.358226758
Training accuracy: 0.910

干得好,我们的 MLP 模型已经成功训练!现在,如果我们以图形方式看到成本和准确率怎么办?我们来试试吧:

# Plot loss over time
plt.subplot(221)
plt.plot(i_data, cost_list, 'k--', label='Training loss', linewidth=1.0)
plt.title('Cross entropy loss per iteration')
plt.xlabel('Iteration')
plt.ylabel('Cross entropy loss')
plt.legend(loc='upper right')
                   plt.grid(True)

以下是上述代码的输出 :

>>>

A TensorFlow implementation of MLP for client-subscription assessment

图 11:训练阶段每次迭代的交叉熵损失

上图显示交叉熵损失在 0.34 和 0.36 之间或多或少稳定,但波动很小。现在,让我们看看这对整体训练准确率有何影响:

# Plot train and test accuracy
plt.subplot(222)
plt.plot(i_data, acc_list, 'r--', label='Accuracy on the training set', linewidth=1.0)
plt.title('Accuracy on the training set')
plt.xlabel('Iteration')
plt.ylabel('Accuracy')
plt.legend(loc='upper right')
plt.grid(True)
plt.show()

以下是前面代码的输出:

>>>

A TensorFlow implementation of MLP for client-subscription assessment

图 12:每次迭代时训练集的准确率

我们可以看到训练准确率在 79% 和 96% 之间波动,但不会均匀地增加或减少。解决此问题的一种可能方法是添加更多隐藏层并使用不同的优化器,例如本章前面讨论的梯度下降。我们将丢弃概率提高到 100%,即 1.0。原因是也有相同的网络用于测试:

print("Evaluating MLP on the test set...")
test_acc = sess.run(accuracy, feed_dict={X: data_test, y: labels_test, dropout_keep_prob:1.})
print ("Prediction/classification accuracy: %.3f" % (test_acc))

以下是上述代码的输出:

>>>
Evaluating MLP on the test set...
Prediction/classification accuracy: 0.889
Session closed!

因此,分类准确率约为 89%。一点也不差!现在,如果需要更高的精度,我们可以使用称为深度信任网络(DBN)的另一种 DNN 架构,可以以有监督或无监督的方式进行训练。

这是在其应用中作为分类器观察 DBN 的最简单方法。如果我们有一个 DBN 分类器,那么预训练方法是以类似于自编码器的无监督方式完成的,这将在第 5 章中描述,优化 TensorFlow 自编码器,分类器以受监督的方式训练(微调),就像 MLP 中的那样。

深度信念网络(DBNs)

为了克服 MLP 中的过拟合问题,我们建立了一个 DBN,进行无监督的预训练,为输入获得一组不错的特征表示,然后对训练集进行微调以获得预测。网络。

虽然 MLP 的权重是随机初始化的,但 DBN 使用贪婪的逐层预训练算法通过概率生成模型初始化网络权重。这些模型由可见层和多层随机潜在变量组成,这些变量称为隐藏单元或特征检测器。

堆叠 DBN 中的 RBM,形成无向概率图模型,类似于马尔可夫随机场(MRF):两层由可见神经元和隐藏神经元组成。

堆叠 RBM 中的顶部两层在它们之间具有无向的对称连接并形成关联存储器,而较低层从上面的层接收自上而下的定向连接:

Deep Belief Networks (DBNs)

图 13:RBM 作为构建块的 DBN 的高级视图

顶部两层在它们之间具有无向的对称连接并形成关联存储器,而较低层从前面的层接收自上而下的定向连接。几个 RBM 一个接一个地堆叠以形成 DBN。

受限玻尔兹曼机(RBMs)

RBM 是无向概率图模型,称为马尔科夫随机场。它由两层组成。第一层由可见神经元组成,第二层由隐藏神经元组成。图 14 显示了简单 RBM 的结构。可见单元接受输入,隐藏单元是非线性特征检测器。每个可见神经元都连接到所有隐藏的神经元,但同一层中的神经元之间没有内部连接:

Restricted Boltzmann Machines (RBMs)

图 14:简单 RBM 的结构

图 14 中的 RBM 由m个可见单元组成,V = (v[1] ... v[m])n个隐藏单元,H = (h[1] ... h[n])。可见单元接受 0 到 1 之间的值,隐藏单元的生成值介于 0 和 1 之间。模型的联合概率是由以下等式给出的能量函数:

Restricted Boltzmann Machines (RBMs)

在前面的等式中,i = 1 ... mj = 1 ... nbicj分别是可见和隐藏单元的偏差,并且w[ij]v[i]h[j]之间的权重。模型分配给可见向量v的概率由下式给出:

Restricted Boltzmann Machines (RBMs)

在第二个等式中,Z是分区函数,定义如下:

Restricted Boltzmann Machines (RBMs)

权重的学习可以通过以下等式获得:

Restricted Boltzmann Machines (RBMs)

在等式 4 中,学习率由eps定义。通常,较小的eps值可确保训练更加密集。但是,如果您希望网络快速学习,可以将此值设置得更高。

由于同一层中的单元之间没有连接,因此很容易计算第一项。p(h | v)p(v | h)的条件分布是阶乘的,并且由以下等式中的逻辑函数给出:

Restricted Boltzmann Machines (RBMs)

因此,样本v[i] h[j]是无偏的。然而,计算第二项的对数似然的计算成本是指数级的。虽然有可能得到第二项的无偏样本,但使用马尔可夫链蒙特卡罗(MCMC)的吉布斯采样,这个过程也不具有成本效益。相反,RBM 使用称为对比发散的有效近似方法。

通常,MCMC 需要许多采样步骤才能达到静止的收敛。运行吉布斯采样几步(通常是一步)足以训练一个模型,这称为对比分歧学习。对比分歧的第一步是用训练向量初始化可见单元。

下一步是使用等式 5 计算所有隐藏单元,同时使用可见单元,然后使用等式 4 从隐藏单元重建可见单元。最后,隐藏单元用重建的可见单元更新。因此,代替方程式 4,我们最终得到以下的权重学习模型:

Restricted Boltzmann Machines (RBMs)

简而言之,该过程试图减少输入数据和重建数据之间的重建误差。算法收敛需要多次参数更新迭代。迭代称为周期。输入数据被分成小批量,并且在每个小批量之后更新参数,具有参数的平均值。

最后,如前所述,RBM 最大化可见单元p(v)的概率,其由模式和整体训练数据定义。它相当于最小化模型分布和经验数据分布之间的 KL 散度

对比分歧只是这个目标函数的粗略近似,但它在实践中非常有效。虽然方便,但重建误差实际上是衡量学习进度的一个非常差的指标。考虑到这些方面,RBM 需要一些时间来收敛,但是如果你看到重建是不错的,那么你的算法效果很好。

构建一个简单的 DBN

单个隐藏层 RBM 无法从输入数据中提取所有特征,因为它无法对变量之间的关系进行建模。因此,一层接一个地使用多层 RBM 来提取非线性特征。在 DBN 中,首先使用输入数据训练 RBM,并且隐藏层以贪婪学习方法表示学习的特征。

第一 RBM 的这些学习特征用作第二 RBM 的输入,作为 DBN 中的另一层,如图 15 所示。类似地,第二层的学习特征用作另一层的输入。

这样,DBN 可以从输入数据中提取深度和非线性特征。最后一个 RBM 的隐藏层代表整个网络的学习特征。前面针对所有 RBM 层描述的学习特征的过程称为预训练。

无监督的预训练

假设您要处理复杂任务,您没有多少标记的训练数据。很难找到合适的 DNN 实现或架构来进行训练并用于预测分析。然而,如果您有大量未标记的训练数据,您可以尝试逐层训练层,从最低层开始,然后使用无监督的特征检测器算法向上移动。这就是 RBM(图 15)或自编码器(图 16)的工作原理。

Unsupervised pre-training

图 15:使用自编码器在 DBN 中进行无监督的预训练

当您有一个复杂的任务需要解决时,无监督的预训练仍然是一个不错的选择,没有类似的模型可以重复使用,并且标记很少的训练数据,但是大量未标记的训练数据。目前的趋势是使用自编码器而不是 RBM;但是,对于下一节中的示例,RBM 将用于简化。读者也可以尝试使用自编码器而不是 RBM。

预训练是一种无监督的学习过程。在预训练之后,通过在最后一个 RBM 层的顶部添加标记层来执行网络的微调。此步骤是受监督的学习过程。无监督的预训练步骤尝试查找网络权重:

Unsupervised pre-training

图 16:通过构建具有 RBM 栈的简单 DBN 在 DBN 中进行无监督预训练

监督的微调

在监督学习阶段(也称为监督微调)中,不是随机初始化网络权重,而是使用在预训练步骤中计算的权重来初始化它们。这样,当使用监督梯度下降时,DBN 可以避免收敛到局部最小值。

如前所述,使用 RBM 栈,DBN 可以构造如下:

  • 使用参数W[1]训练底部 RBM(第一个 RBM)
  • 将第二层权重初始化为W[2] = W[1]^T,这可确保 DBN 至少与我们的基础 RBM 一样好

因此,将这些步骤放在一起,图 17 显示了由三个 RBM 组成的简单 DBN 的构造:

Supervised fine-tuning

图 17:使用多个 RBM 构建简单的 DBN

现在,当调整 DBN 以获得更好的预测准确率时,我们应该调整几个超参数,以便 DBN 通过解开和改进W[2]来拟合训练数据。综上所述,我们有了创建基于 DBN 的分类器或回归器的概念工作流程。

现在我们已经有足够的理论背景来介绍如何使用几个 RBM 构建 DBN,现在是时候将我们的理论应用于实践中了。在下一节中,我们将了解如何开发用于预测分析的监督 DBN 分类器。

用于客户订阅评估的 TensorFlow 中的 DBN 实现

在银行营销数据集的前一个示例中,我们使用 MLP 观察到大约 89% 的分类准确率。我们还将原始数据集标准化,然后将其提供给 MLP。在本节中,我们将了解如何为基于 DBN 的预测模型使用相同的数据集。

我们将使用 Md.Rezaul Karim 最近出版的书籍 Predictive Analytics with TensorFlow 的 DBN 实现,可以从 GitHub 下载。

前面提到的实现是基于 RBM 的简单,干净,快速的 DBN 实现,并且基于 NumPy 和 TensorFlow 库,以便利用 GPU 计算。该库基于以下两篇研究论文实现:

  • Geoffrey E. Hinton,Simon Osindero 和 Yee-Whye Teh 的深度信念网快速学习算法。 Neural Computation 18.7(2006):1527-1554。
  • 训练受限制的玻尔兹曼机:简介,Asja Fischer 和 Christian Igel。模式识别 47.1(2014):25-39。

我们将看到如何以无监督的方式训练 RBM,然后我们将以有监督的方式训练网络。简而言之,有几个步骤需要遵循。主分类器是classification_demo.py

提示

虽然在以监督和无监督的方式训练 DBN 时数据集不是那么大或高维度,但是在训练时间中会有如此多的计算,这需要巨大的资源。然而,RBM 需要大量时间来收敛。因此,我建议读者在 GPU 上进行训练,至少拥有 32 GB 的 RAM 和一个 Corei7 处理器。

我们将从加载所需的模块和库开始:

import numpy as np
import pandas as pd
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.metrics.classification import accuracy_score
from sklearn.metrics import precision_recall_fscore_support
from sklearn.metrics import confusion_matrix
import itertools
from tf_models import SupervisedDBNClassification
import matplotlib.pyplot as plt

然后,我们加载前一个 MLP 示例中使​​用的已经正则化的数据集:

FILE_PATH = '../input/bank_normalized.csv'
raw_data = pd.read_csv(FILE_PATH)

在前面的代码中,我们使用了 pandas read_csv()方法并创建了一个DataFrame。现在,下一个任务是按如下方式扩展特征和标签:

Y_LABEL = 'y'
KEYS = [i for i in raw_data.keys().tolist() if i != Y_LABEL]
X = raw_data[KEYS].get_values()
Y = raw_data[Y_LABEL].get_values()
class_names = list(raw_data.columns.values)
print(class_names)

在前面的行中,我们已经分离了特征和标签。这些特征存储在X中,标签位于Y中。接下来的任务是将它们分成训练(75%)和测试集(25%),如下所示:

X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.25, random_state=100)

现在我们已经有了训练和测试集,我们可以直接进入 DBN 训练步骤。但是,首先我们需要实例化 DBN。我们将以受监督的方式进行分类,但我们需要为此 DNN 架构提供超参数:

classifier = SupervisedDBNClassification(hidden_layers_structure=[64, 64],learning_rate_rbm=0.05,learning_rate=0.01,n_epochs_rbm=10,n_iter_backprop=100,batch_size=32,activation_function='relu',dropout_p=0.2)

在前面的代码段中,n_epochs_rbm是预训练(无监督)和n_iter_backprop用于监督微调的周期数。尽管如此,我们已经为这两个阶段定义了两个单独的学习率,分别使用learning_rate_rbmlearning_rate

不过,我们将在本节后面描述SupervisedDBNClassification的这个类实现。

该库具有支持 sigmoid,ReLU 和 tanh 激活函数的实现。此外,它利用 l2 正则化来避免过拟合。我们将按如下方式进行实际拟合:

classifier.fit(X_train, Y_train)

如果一切顺利,你应该在控制台上观察到以下进展:

[START] Pre-training step:
>> Epoch 1 finished     RBM Reconstruction error 1.681226
….
>> Epoch 3 finished     RBM Reconstruction error 4.926415
>> Epoch 5 finished     RBM Reconstruction error 7.185334
…
>> Epoch 7 finished     RBM Reconstruction error 37.734962
>> Epoch 8 finished      RBM Reconstruction error 467.182892
….
>> Epoch 10 finished      RBM Reconstruction error 938.583801
[END] Pre-training step
[START] Fine tuning step:
>> Epoch 0 finished     ANN training loss 0.316619
>> Epoch 1 finished     ANN training loss 0.311203
>> Epoch 2 finished     ANN training loss 0.308707
….
>> Epoch 98 finished     ANN training loss 0.288299
>>        Epoch        99         finished                   ANN         training         loss        0.288900

由于 RBM 的权重是随机初始化的,因此重建和原始输入之间的差异通常很大。

从技术上讲,我们可以将重建误差视为重建值与输入值之间的差异。然后,在迭代学习过程中,将该误差反向传播 RBM 的权重几次,直到达到最小误差。

然而,在我们的情况下,重建达到 938,这不是那么大(即,不是无限),所以我们仍然可以期望良好的准确率。无论如何,经过 100 次迭代后,显示每个周期训练光泽的微调图如下:

Implementing a DBN with TensorFlow for client-subscription assessment

图 18:每次迭代的 SGD 微调损失(仅 100 次迭代)

然而,当我重复前面的训练并微调 1000 个周期时,我没有看到训练损失有任何显着改善:

Implementing a DBN with TensorFlow for client-subscription assessment

图 19:每次迭代的 SGD 微调损失(1000 次迭代)

这是监督的 DBN 分类器的实现。此类为分类问题实现 DBN。它将网络输出转换为原始标签。在对标签映射执行索引之后,它还需要网络参数并返回列表。

然后,该类预测给定数据中每个样本的类的概率分布,并返回字典列表(每个样本一个)。最后,它附加了 softmax 线性分类器作为输出层:

class SupervisedDBNClassification(TensorFlowAbstractSupervisedDBN, ClassifierMixin):
    def _build_model(self, weights=None):
        super(SupervisedDBNClassification, self)._build_model(weights)
        self.output = tf.nn.softmax(self.y)
        self.cost_function = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(logits=self.y, labels=self.y_))
        self.train_step = self.optimizer.minimize(self.cost_function)
    @classmethod

    def _get_param_names(cls):
        return super(SupervisedDBNClassification, cls)._get_param_names() + ['label_to_idx_map', 'idx_to_label_map']

    @classmethod
    def from_dict(cls, dct_to_load):
        label_to_idx_map = dct_to_load.pop('label_to_idx_map')
        idx_to_label_map = dct_to_load.pop('idx_to_label_map')
        instance = super(SupervisedDBNClassification, cls).from_dict(dct_to_load)
        setattr(instance, 'label_to_idx_map', label_to_idx_map)
        setattr(instance, 'idx_to_label_map', idx_to_label_map)
        return instance

    def _transform_labels_to_network_format(self, labels):
        """
        Converts network output to original labels.
        :param indexes: array-like, shape = (n_samples, )
        :return:
        """
        new_labels, label_to_idx_map, idx_to_label_map = to_categorical(labels, self.num_classes)
        self.label_to_idx_map = label_to_idx_map
        self.idx_to_label_map = idx_to_label_map
        return new_labels

    def _transform_network_format_to_labels(self, indexes):
        return list(map(lambda idx: self.idx_to_label_map[idx], indexes))

    def predict(self, X):
        probs = self.predict_proba(X)
        indexes = np.argmax(probs, axis=1)
        return self._transform_network_format_to_labels(indexes)

    def predict_proba(self, X):
        """
        Predicts probability distribution of classes for each sample in the given data.
        :param X: array-like, shape = (n_samples, n_features)
        :return:
        """
        return super(SupervisedDBNClassification, self)._compute_output_units_matrix(X)

    def predict_proba_dict(self, X):
        """
        Predicts probability distribution of classes for each sample in the given data.
        Returns a list of dictionaries, one per sample. Each dict contains {label_1: prob_1, ..., label_j: prob_j}
        :param X: array-like, shape = (n_samples, n_features)
        :return:
        """
        if len(X.shape) == 1:  # It is a single sample
            X = np.expand_dims(X, 0)
        predicted_probs = self.predict_proba(X)
        result = []
        num_of_data, num_of_labels = predicted_probs.shape
        for i in range(num_of_data):
            # key : label
            # value : predicted probability
            dict_prob = {}
            for j in range(num_of_labels):
                dict_prob[self.idx_to_label_map[j]] = predicted_probs[i][j]
            result.append(dict_prob)
        return result
    def _determine_num_output_neurons(self, labels):
        return len(np.unique(labels))

正如我们在前面的例子和运行部分中提到的那样,微调神经网络的参数是一个棘手的过程。有很多不同的方法,但我的知识并没有一刀切的方法。然而,通过前面的组合,我收到了更好的分类结果。另一个要选择的重要参数是学习率。根据您的模型调整学习率是一种可以采取的方法,以减少训练时间,同时避免局部最小化。在这里,我想讨论一些能够帮助我提高预测准确率的技巧,不仅适用于此应用,也适用于其他应用。

现在我们已经建立了模型,现在是评估其表现的时候了。为了评估分类准确率,我们将使用几个表现指标,如precisionrecallf1 score。此外,我们将绘制混淆矩阵,以观察与真实标签相对应的预测标签。首先,让我们计算预测精度如下:

Y_pred = classifier.predict(X_test)
print('Accuracy: %f' % accuracy_score(Y_test, Y_pred))

接下来,我们需要计算分类的precisionrecallf1 score

p, r, f, s = precision_recall_fscore_support(Y_test, Y_pred, average='weighted')
print('Precision:', p)
print('Recall:', r)
print('F1-score:', f)

以下是上述代码的输出:

>>>
Accuracy: 0.900554
Precision: 0.8824140209830381
Recall: 0.9005535592891133
F1-score: 0.8767190584424599

太棒了!使用我们的 DBN 实现,我们解决了与使用 MLP 相同的分类问题。尽管如此,与 MLP 相比,我们设法获得了稍微更好的准确率。

现在,如果要解决回归问题,要预测的标签是连续的,则必须使用SupervisedDBNRegression()函数进行此实现。 DBN 文件夹中的回归脚本(即regression_demo.py)也可用于执行回归操作。

但是,使用专门为回归 y 准备的另一个数据集将是更好的主意。您需要做的就是准备数据集,以便基于 TensorFlow 的 DBN 可以使用它。因此,为了最小化演示,我使用房价:高级回归技术数据集来预测房价。

调整超参数和高级 FFNN

神经网络的灵活性也是它们的主要缺点之一:有很多超参数可以调整。即使在简单的 MLP 中,您也可以更改层数,每层神经元的数量以及每层中使用的激活函数的类型。您还可以更改权重初始化逻辑,退出保持概率等。

另外,FFNN 中的一些常见问题,例如梯度消失问题,以及选择最合适的激活函数,学习率和优化器,是最重要的。

调整 FFNN 超参数

超参数是不在估计器中直接学习的参数。有可能并建议您在超参数空间中搜索最佳交叉验证得分。在构造估计器时提供的任何参数可以以这种方式优化。现在,问题是:您如何知道超参数的哪种组合最适合您的任务?当然,您可以使用网格搜索和交叉验证来为线性机器学习模型找到正确的超参数。

但是,对于 DNN,有许多超参数可供调整。由于在大型数据集上训练神经网络需要花费大量时间,因此您只能在合理的时间内探索超参数空间的一小部分。以下是一些可以遵循的见解。

此外,当然,正如我所说,您可以使用网格搜索或随机搜索,通过交叉验证,为线性机器学习模型找到正确的超参数。我们将在本节后面看到一些可能的详尽和随机网格搜索和交叉验证方法。

隐藏层数

对于许多问题,你可以从一个或两个隐藏层开始,这个设置可以很好地使用两个隐藏层,具有相同的神经元总数(见下文以了解一些神经元),训练时间大致相同。现在让我们看一些关于设置隐藏层数的朴素估计:

  • 0:仅能表示线性可分离函数或决策
  • 1:可以近似包含从一个有限空间到另一个有限空间的连续映射的任何函数
  • 2:可以用任意精度表示任意决策边界,具有合理的激活函数,并且可以近似任何平滑映射到任何精度

但是,对于更复杂的问题,您可以逐渐增加隐藏层的数量,直到您开始过拟合训练集。非常复杂的任务,例如大图像分类或语音识别,通常需要具有数十层的网络,并且它们需要大量的训练数据。

不过,您可以尝试逐渐增加神经元的数量,直到网络开始过拟合。这意味着不会导致过拟合的隐藏神经元数量的上限是:

Number of hidden layers

在上面的等式中:

N[i]为输入神经元的数量

N[o]为输出神经元的数量

N[s]为训练数据集中的样本数

α为任意比例因子,通常为 2-10。

请注意,上述等式不是来自任何研究,而是来自我的个人工作经验。但是,对于自动程序,您将以 2 的α值开始,即训练数据的自由度是模型的两倍,如果训练数据的误差明显小于 10,则可以达到 10。用于交叉验证数据集。

每个隐藏层的神经元数量

显然,输入和输出层中神经元的数量取决于您的任务所需的输入和输出类型。例如,如果您的数据集的形状为28x28,则它应该具有大小为 784 的输入神经元,并且输出神经元应该等于要预测的类的数量。

我们将在下一个例子中看到它如何在实践中工作,使用 MLP,其中将有四个具有 256 个神经元的隐藏层(只有一个超参数可以调整,而不是每层一个)。就像层数一样,您可以尝试逐渐增加神经元的数量,直到网络开始过拟合。

有一些经验导出的经验法则,其中最常用的是:“隐藏层的最佳大小通常在输入的大小和输出层的大小之间。”

总之,对于大多数问题,通过仅使用两个规则设置隐藏层配置,您可能可以获得不错的表现(即使没有第二个优化步骤):

  • 隐藏层的数量等于一
  • 该层中的神经元数量是输入和输出层中神经元的平均值

然而,就像层数一样,你可以尝试逐渐增加神经元的数量,直到网络开始过拟合。

权重和偏置初始化

正如我们将在下一个示例中看到的那样,初始化权重和偏置,隐藏层是一个重要的超参数,需要注意:

  • 不要做所有零初始化:一个听起来合理的想法可能是将所有初始权重设置为零,但它在实践中不起作用。这是因为如果网络中的每个神经元计算相同的输出,如果它们的权重被初始化为相同,则神经元之间将不存在不对称的来源。
  • 小随机数:也可以将神经元的权重初始化为小数,但不能相同为零。或者,可以使用从均匀分布中抽取的小数字。
  • 初始化偏差:通常将偏差初始化为零,因为权重中的小随机数提供不对称性破坏。将偏差设置为一个小的常量值,例如所有偏差的 0.01,确保所有 ReLU 单元都可以传播一些梯度。但是,它既没有表现良好,也没有表现出持续改进因此,建议坚持使用零。

选择最合适的优化器

因为在 FFNN 中, 目标函数之一是最小化评估成本,我们必须定义一个优化器。我们已经看到了如何使用tf.train.AdamOptimizerTensorflow tf.train提供了一组有助于训练模型的类和函数。就个人而言,我发现 Adam 优化器在实践中对我很有效,而不必考虑学习率等等。

对于大多数情况,我们可以利用 Adam,但有时我们可以采用实现的RMSPropOptimizer函数,这是梯度下降的高级形式。RMSPropOptimizer函数实现RMSProp算法。

RMSPropOptimizer函数还将学习率除以指数衰减的平方梯度平均值。衰减参数的建议设置值为0.9,而学习率的良好默认值为0.001

optimizer = tf.train.RMSPropOptimizer(0.001, 0.9).minimize(cost_op)

使用最常见的优化器 SGD,学习率必须随1/T缩放才能获得收敛,其中T是迭代次数。RMSProp尝试通过调整步长来自动克服此限制,以使步长与梯度的比例相同。

因此,如果您正在训练神经网络,但计算梯度是必需的,使用tf.train.RMSPropOptimizer()将是在小批量设置中学习的更快方式。研究人员还建议在训练 CNN 等深层网络时使用动量优化器。

最后,如果您想通过设置这些优化器来玩游戏,您只需要更改一行。由于时间限制,我没有尝试过所有这些。然而,根据 Sebastian Ruder 最近的一篇研究论文(见此链接),自适应学习率方法的优化者,AdagradAdadeltaRMSpropAdam是最合适的,并为这些情况提供最佳收敛。

网格搜索和随机搜索的超参数调整

采样搜索的两种通用候选方法在其他基于 Python 的机器学习库(如 Scikit-learn)中提供。对于给定值,GridSearchCV详尽考虑所有参数组合,而RandomizedSearchCV可以从具有指定分布的参数空间中对给定数量的候选进行采样。

GridSearchCV是自动测试和优化超参数的好方法。我经常在 Scikit-learn 中使用它。然而,TensorFlowEstimator优化learning_ratebatch_size等等还不是那么简单。而且,正如我所说,我们经常调整这么多超参数以获得最佳结果。不过,我发现这篇文章对于学习如何调整上述超参数非常有用

随机搜索和网格搜索探索完全相同的参数空间。参数设置的结果非常相似,而随机搜索的运行时间则大大降低。

一些基准测试(例如此链接)已经报告了随机搜索的表现略差,尽管这很可能是一种噪音效应并且不会延续到坚持不懈的测试集。

正则化

有几种方法可以控制 DNN 的训练,以防止在训练阶段过拟合,例如,L2/L1 正则化,最大范数约束和退出:

  • L2 正则化:这可能是最常见的正则化形式。使用梯度下降参数更新,L2 正则化表示每个权重将线性地向零衰减。
  • L1 正则化:对于每个权重 w,我们将项λ|w|添加到目标。然而, 也可以组合 L1 和 L2 正则化以实现弹性网络正则化。
  • 最大范数约束:这强制了每个隐藏层神经元的权重向量的大小的绝对上限。可以进一步使用投影的梯度下降来强制约束。

消失梯度问题出现在非常深的神经网络(通常是 RNN,它将有一个专门的章节),它使用激活函数,其梯度往往很小(在 0 到 1 的范围内)。

由于这些小梯度在反向传播期间进一步增加,因此它们倾向于在整个层中“消失”,从而阻止网络学习远程依赖性。解决这个问题的常用方法是使用激活函数,如线性单元(又名 ReLU),它不会受到小梯度的影响。我们将看到一种改进的 RNN 变体,称为长短期记忆 (又名 LSTM),它可以解决这个问题。我们将在第 5 章,优化 TensorFlow 自编码器中看到关于该主题的更详细讨论。

尽管如此,我们已经看到最后的架构更改提高了模型的准确率,但我们可以通过使用 ReLU 更改 sigmoid 激活函数来做得更好,如下所示:

Regularization

图 20:ReLU 函数

ReLU 单元计算函数f(x) = max(0, x)。 ReLU 计算速度快,因为它不需要任何指数计算,例如 sigmoid 或 tanh 激活所需的计算。此外,与 sigmoid/tanh 函数相比,发现它大大加速了随机梯度下降的收敛。要使用 ReLU 函数,我们只需在先前实现的模型中更改前四个层的以下定义:

第一层输出:

Y1 = tf.nn.relu(tf.matmul(XX, W1) + B1) # Output from layer 1

第二层输出:

Y2 = tf.nn.relu(tf.matmul(Y1, W2) + B2) # Output from layer 2

第三层输出:

Y3 = tf.nn.relu(tf.matmul(Y2, W3) + B3) # Output from layer 3

第四层输出:

Y4 = tf.nn.relu(tf.matmul(Y3, W4) + B4) # Output from layer 4

输出层:

Ylogits = tf.matmul(Y4, W5) + B5 # computing the logits
Y = tf.nn.softmax(Ylogits) # output from layer 5

当然,tf.nn.relu是 TensorFlow 的 ReLU 实现。模型的准确率几乎达到 98%,您可以看到运行网络:

>>>
Loading data/train-images-idx3-ubyte.mnist 
Loading data/train-labels-idx1-ubyte.mnist Loading data/t10k-images-idx3-ubyte.mnist 
Loading data/t10k-labels-idx1-ubyte.mnist 
Epoch:	0
Epoch:	1
Epoch:	2
Epoch:	
3
Epoch:	4
Epoch:	5
Epoch:	6
Epoch:	7
Epoch:	8
Epoch:	9
Accuracy:0.9789 
done
>>>

关注 TensorBoard 分析,从源文件执行的文件夹中,您应该数字:

$> Tensorboard --logdir = 'log_relu' # Don't put space before or after '='

然后在localhost上打开浏览器以显示 TensorBoard 的起始页面。在下图中,我们显示了趋势对训练集示例数量的准确率:

Regularization

图 21:训练集上的准确率函数

在大约 1000 个示例之后,您可以很容易地看到在不良的初始趋势之后,准确率如何开始快速渐进式改进。

丢弃优化

在使用 DNN 时,我们需要另一个占位符用于丢弃,这是一个需要调整的超参数。它仅通过以某种概率保持神经元活动(比如p > 1.0)或者将其设置为零来实现。这个想法是在测试时使用单个神经网络而不会丢弃。该网络的权重是训练权重的缩小版本。如果在训练期间使用dropout_keep_prob < 1.0保留单元,则在测试时将该单元的输出权重乘以p

在学习阶段,与下一层的连接可以限于神经元的子集,以减少要更新的权重。这种学习优化技术称为丢弃。因此,丢弃是一种用于减少具有许多层和/或神经元的网络中的过拟合的技术。通常,丢弃层位于具有大量可训练神经元的层之后。

该技术允许将前一层的一定百分比的神经元设置为 0,然后排除激活。神经元激活被设置为 0 的概率由层内的丢弃率参数通过 0 和 1 之间的数字表示。实际上,神经元的激活保持等于丢弃率的概率;否则,它被丢弃,即设置为 0。

Dropout optimization

图 22:丢弃表示

通过这种方式,对于每个输入,网络拥有与前一个略有不同的架构。即使这些架构具有相同的权重,一些连接也是有效的,有些连接不是每次都以不同的方式。上图显示了丢弃的工作原理:每个隐藏单元都是从网络中随机省略的,概率为p

但需要注意的是,每个训练实例的选定丢弃单元不同;这就是为什么这更像是一个训练问题。丢弃可以被视为在大量不同的神经网络中执行模型平均的有效方式,其中可以以比架构问题低得多的计算成本来避免过拟合。丢弃降低了神经元依赖于其他神经元存在的可能性。通过这种方式,它被迫更多地了解强大的特征,并且它们与其他不同神经元的联系非常有用。

允许构建丢弃层的 TensorFlow 函数是tf.nn.dropout。此函数的输入是前一层的输出,并且丢弃参数tf.nn.dropout返回与输入张量相同大小的输出张量。该模型的实现遵循与五层网络相同的规则。在这种情况下,我们必须在一层和另一层之间插入丢弃函数:

pkeep = tf.placeholder(tf.float32)

Y1 = tf.nn.relu(tf.matmul(XX, W1) + B1) # Output from layer 1
Y1d = tf.nn.dropout(Y1, pkeep)

Y2 = tf.nn.relu(tf.matmul(Y1, W2) + B2) # Output from layer 2
Y2d = tf.nn.dropout(Y2, pkeep)

Y3 = tf.nn.relu(tf.matmul(Y2, W3) + B3) # Output from layer 3
Y3d = tf.nn.dropout(Y3, pkeep)

Y4 = tf.nn.relu(tf.matmul(Y3, W4) + B4) # Output from layer 4
Y4d = tf.nn.dropout(Y4, pkeep)

Ylogits = tf.matmul(Y4d, W5) + B5 # computing the logits
Y = tf.nn.softmax(Ylogits) # output from layer 5

退出优化产生以下结果:

>>>
Loading data/train-images-idx3-ubyte.mnist Loading data/train-labels-idx1-ubyte.mnist Loading data/t10k-images-idx3-ubyte.mnist Loading data/t10k-labels-idx1-ubyte.mnist Epoch:	0
Epoch:	1
Epoch:	2
Epoch:	3
Epoch:	4
Epoch:	5
Epoch:	6
Epoch:
	7
Epoch:	8
Epoch:	9
Accuracy:	0.9666 done
>>>

尽管有这种实现, 之前的 ReLU 网络仍然更好,但您可以尝试更改网络参数以提高模型的准确率。此外,由于这是一个很小的网络,我们处理的是小规模的数据集,当您处理具有更复杂网络的大规模高维数据集时,您会发现丢弃可能非常重要。我们将在下一章中看到一些动手实例。

现在,要了解丢弃优化的效果,让我们开始 TensorBoard 分析。只需键入以下内容:

$> Tensorboard --logdir=' log_softmax_relu_dropout/'

下图显示了作为训练示例函数的精度成本函数:

Dropout optimization

图 23:a)丢弃优化的准确率,b)训练集的成本函数

在上图中,我们显示成本函数作为训练样例的函数。这两种趋势都是我们所期望的:随着训练样例的增加,准确率会提高,而成本函数会随着迭代次数的增加而减

总结

我们已经了解了如何实现 FFNN 架构,其特征在于一组输入单元,一组输出单元以及一个或多个连接该输出的输入级别的隐藏单元。我们已经看到如何组织网络层,以便级别之间的连接是完全的并且在单个方向上:每个单元从前一层的所有单元接收信号并发送其输出值,适当地权衡到所有单元的下一层。

我们还看到了如何为每个层定义激活函数(例如,sigmoid,ReLU,tanh 和 softmax),其中激活函数的选择取决于架构和要解决的问题。

然后,我们实现了四种不同的 FFNN 模型。第一个模型有一个隐藏层,具有 softmax 激活函数。其他三个更复杂的模型总共有五个隐藏层,但具有不同的激活函数。我们还看到了如何使用 TensorFlow 实现深度 MLP 和 DBN,以解决分类任务。使用这些实现,我们设法达到了 90% 以上的准确率。最后,我们讨论了如何调整 DNN 的超参数以获得更好和更优化的表现。

虽然常规的 FFNN(例如 MLP)适用于小图像(例如,MNIST 或 CIFAR-10),但由于需要大量参数,它会因较大的图像而分解。例如,100×100 图像具有 10,000 个像素,并且如果第一层仅具有 1,000 个神经元(其已经严格限制传输到下一层的信息量),则这意味着 1000 万个连接。另外,这仅适用于第一层。

重要的是,DNN 不知道像素的组织方式,因此不知道附近的像素是否接近。 CNN 的架构嵌入了这种先验知识。较低层通常识别图像的单元域中的特征,而较高层将较低层特征组合成较大特征。这适用于大多数自然图像,与 DNN 相比,CNN 具有决定性的先机性。

在下一章中,我们将进一步探讨神经网络模型的复杂性,引入 CNN,这可能对深度学习技术产生重大影响。我们将研究主要功能并查看一些实现示例。