app-dl-py-merge-1

35 阅读1小时+

Python 应用深度学习(二)

原文:annas-archive.org/md5/5ba333cb84d93dd1084633034e39693f

译者:飞龙

协议:CC BY-NC-SA 4.0

第四章:神经网络与深度学习简介

MNIST 数据集的图像边缘没有包含数字。因此,两个网络都不会给位于该区域的像素分配相关的值。如果我们将数字画得更靠近指定区域的中心,两个网络在分类数字时的准确率都明显提高。这表明,神经网络的强大程度取决于用来训练它们的数据。如果用于训练的数据与我们尝试预测的数据有很大不同,那么网络很可能会产生令人失望的结果。本章将涵盖神经网络的基础知识以及如何设置深度学习编程环境。我们还将探索神经网络的常见组件及其基本操作。最后,我们将通过探索使用 TensorFlow 创建的训练神经网络来结束本章内容。

本章的内容是理解神经网络能够做什么。我们不会涉及深度学习算法背后的数学概念,而是描述构成深度学习系统的基本元素。我们还将探讨神经网络在解决实际问题中的应用实例。

本章将为你提供一个关于如何设计使用神经网络解决问题的系统的实际直觉——包括如何判断一个给定的问题是否可以用这些算法解决。其核心是,本章挑战你将问题看作是思想的数学表示。到本章结束时,你将能够将问题看作这些表示的集合,并开始识别这些表示如何通过深度学习算法进行学习。

本章结束时,你将能够:

  • 探讨神经网络的基础知识

  • 设置深度学习编程环境

  • 探索神经网络的常见组件及其基本操作

  • 通过探索使用 TensorFlow 创建的训练神经网络,结束本章内容

什么是神经网络?

神经网络——也称为人工神经网络——最早由麻省理工学院教授沃伦·麦卡洛克和沃尔特·皮茨于 20 世纪 40 年代提出。

欲了解更多信息,请参阅《解释:神经网络》。麻省理工学院新闻办公室,2017 年 4 月 14 日。可在以下网址获取:

news.mit.edu/2017/explained-neural-networksdeep-learning-0414

受神经科学进展的启发,他们提议创建一个能够再现大脑工作方式(无论是人类还是其他)的计算机系统。其核心思想是一个作为相互连接网络运作的计算机系统。也就是说,一个由许多简单组件组成的系统,这些组件既解释数据,又相互影响如何解释数据。这个核心思想至今依然存在。

深度学习在很大程度上被认为是现代神经网络的研究。可以把它看作是神经网络的一个现代名称。主要的区别在于,深度学习中使用的神经网络通常要大得多——即,它们有更多的节点和层——相比早期的神经网络。深度学习算法和应用通常需要资源才能取得成功,因此使用“深度”一词来强调它的规模和大量的相互连接的组件。

成功的应用

神经网络自从 20 世纪 40 年代起便开始研究,虽然形式各异。但直到最近,深度学习系统才在大规模工业应用中取得了成功。

当代神经网络的倡导者在语音识别、语言翻译、图像分类和其他领域取得了巨大的成功。它如今的显著地位得益于计算能力的大幅提升以及图形处理单元GPU)和张量处理单元TPU)的出现——它们能够进行比普通 CPU 更多的同时数学运算,并且数据的可用性也大大增加。

不同 AlphaGo 算法的功耗。AlphaGo 是 DeepMind 提出的一个项目,旨在开发一系列击败围棋的算法。它被认为是深度学习强大能力的一个典型例子。TPU 是由 Google 开发的一种芯片组,专门用于深度学习程序。

图示展示了用于训练不同版本 AlphaGo 算法的 GPU 和 TPU 数量。来源:deepmind.com/blog/alphago-zero-learning-scratch/

在本书中,我们不会使用 GPU 来完成我们的活动。使用神经网络并不需要 GPU。在本书提供的一些简单示例中——所有计算都可以通过普通笔记本电脑的 CPU 完成。然而,当处理非常大的数据集时,GPU 会非常有帮助,因为训练神经网络所需的长时间将变得不切实际。

下面是神经网络在一些领域取得巨大影响的例子:

  • 翻译文本:2017 年,Google 宣布推出一种新的翻译算法,称为 Transformer。该算法由一个递归神经网络(LSTM)构成,使用双语文本进行训练。Google 表示,与行业标准(BLEU)相比,该算法在准确性方面表现突出,并且计算效率也很高。在撰写本文时,Transformer 被报告为 Google Translate 的主要翻译算法。

Google Research Blog。Transformer:一种用于语言理解的新型神经网络架构。2017 年 8 月 31 日。网址:research.googleblog.com/2017/08/transformernovel-neural-network.html

  • 自动驾驶车辆:Uber、NVIDIA 和 Waymo 被认为正在使用深度学习模型来控制驾驶相关的各项车辆功能。每家公司都在研究多个可能性,包括利用人类训练网络、在虚拟环境中模拟车辆驾驶,甚至创建一个类似小城市的环境,在其中车辆可以根据预期和意外事件进行训练。

Alexis C. Madrigal:深入 Waymo 的秘密世界,了解自动驾驶汽车的训练。The Atlantic。2017 年 8 月 23 日。网址:https://

www.theatlantic.com/technology/…

NVIDIA:端到端深度学习用于自动驾驶汽车。2016 年 8 月 17 日。网址:devblogs.nvidia.com/

parallelforall/deep-learning-self-driving-cars/

Dave Gershgorn:Uber 的新 AI 团队正在寻找通往自动驾驶汽车的最短路径。Quartz。2016 年 12 月 5 日。网址:qz.com/853236/ubers-new-ai-team-is-looking-for-theshortest-route-to-self-driving-cars/

  • 图像识别:Facebook 和 Google 使用深度学习模型来识别图像中的实体,并自动将这些实体标记为联系人中的人物。在这两种情况下,网络都是通过已标记的图像以及来自目标朋友或联系人的图像来进行训练的。两家公司都报告称,这些模型在大多数情况下能够高精度地推荐朋友或联系人。

虽然在其他行业中还有更多的例子,但深度学习模型的应用仍处于起步阶段。许多成功的应用尚未到来,包括你自己创造的那些。

为什么神经网络工作得如此出色?

为什么神经网络如此强大?神经网络之所以强大,是因为它们可以用来预测任何给定的函数,并给出合理的逼近。如果某人能够将一个问题表示为数学函数,并且拥有正确表示该函数的数据,那么深度学习模型原则上——在有足够资源的情况下——能够逼近该函数。这通常被称为神经网络的普适性原理

欲了解更多信息,请参考 Michael Nielsen 的《神经网络与深度学习:神经网络能够计算任何函数的可视化证明》。可访问:neuralnetworksanddeeplearning.com/chap4.html

本书中不会深入探讨普适性原理的数学证明。然而,神经网络的两个特性应该能够给你正确的直觉,帮助你理解这一原理:表示学习和函数逼近。

欲了解更多信息,请参考 Kai Arulkumaran, Marc Peter Deisenroth, Miles Brundage 和 Anil Anthony Bharath 的文章《深度强化学习简短调查》。arXiv. 2017 年 9 月 28 日。可访问:www.arxiv-vanity.com/papers/1708.05866/

表示学习

用于训练神经网络的数据包含表示(也称为特征),这些表示解释了你试图解决的问题。例如,如果某人想从图像中识别人脸,那么包含人脸的图像中每个像素的颜色值将作为起点。然后,模型将在训练过程中不断地通过组合像素来学习更高层次的表示。

图 1:从输入数据开始的一系列更高级的表示。图像衍生自原始图像,来源于:Yann LeCun, Yoshua Bengio & Geoffrey Hinton. "Deep Learning". Nature 521, 436–444 (2015 年 5 月 28 日) doi:10.1038/nature14539

用正式的语言来说,神经网络是计算图,其中每一步从输入数据中计算出更高层次的抽象表示。

每一步都代表着进入不同抽象层次的进展。数据通过这些层次,逐步构建出更高层次的表示。该过程最终完成时会得到最高层次的表示:即模型试图预测的那个表示。

函数逼近

当神经网络学习数据的新表示时,它们通过将权重和偏差与不同层次的神经元结合来实现这一过程。每次训练周期进行时,它们都会使用一种叫做反向传播的数学技术调整这些连接的权重。每一轮的权重和偏差都会得到改进,直到达到最佳状态。这意味着神经网络可以在每个训练周期中衡量它的错误,调整每个神经元的权重和偏差,并重新尝试。如果它确定某个修改比上一次的结果更好,它会继续强化这个修改,直到达到最佳解决方案。

简而言之,这个过程就是神经网络能够近似函数的原因。然而,有许多原因可能导致神经网络无法完美地预测一个函数,其中最主要的原因是:

  • 许多函数包含随机特性(即随机性)

  • 可能会过拟合训练数据中的特殊性

  • 可能缺乏训练数据

在许多实际应用中,简单的神经网络能够以合理的精度近似一个函数。这类应用将是我们的重点。

深度学习的局限性

深度学习技术最适用于那些可以用正式数学规则定义的问题(即作为数据表示)。如果一个问题很难以这种方式定义,那么深度学习很可能无法提供有用的解决方案。此外,如果用于某个问题的数据有偏差,或者只包含生成该问题的潜在函数的部分表示,那么深度学习技术只能复制该问题,而无法学会如何解决它。

记住,深度学习算法通过学习数据的不同表示来近似给定的函数。如果数据没有恰当地代表一个函数,那么神经网络很可能会错误地表示该函数。考虑以下类比:你正在尝试预测全国的汽油价格(即燃料价格),并创建一个深度学习模型。你使用信用卡账单中关于日常汽油消费的支出数据作为该模型的输入数据。模型可能最终学会你汽油消费的模式,但它很可能无法准确地表示由其他因素引起的汽油价格波动,这些因素在你的数据中每周才出现一次,比如政府政策、市场竞争、国际政治等。最终,模型在生产环境中使用时会得出错误的结果。

为了避免这个问题,确保用于训练模型的数据尽可能准确地代表模型试图解决的问题。

要深入讨论这个话题,请参考 François Chollet 即将出版的书籍《深度学习与 Python》。François 是 Keras 的创始人,Keras 是本书中使用的 Python 库。章节《深度学习的局限性》对理解这一话题尤其重要。该书的工作版本可通过以下链接访问:blog.keras.io/the-limitations-of-deep-learning.html

固有偏见和伦理考虑

研究人员建议,如果在没有考虑训练数据中固有偏见的情况下使用深度学习模型,不仅可能导致效果不佳的解决方案,还可能引发伦理上的复杂问题。

例如,在 2016 年末,来自中国上海交通大学的研究人员创建了一个神经网络,通过仅仅使用面部照片就能够正确分类犯罪分子。研究人员使用了 1,856 张中国男性的图片,其中一半是已被定罪的。

他们的模型以 89.5%的准确率识别出了囚犯。( blog.keras.io/the-limitations-of-deep-learning.html)。《麻省理工科技评论》。神经网络通过面部识别来识别犯罪分子。2016 年 11 月 22 日。可通过以下链接访问:www.technologyreview.com/s/602955/neural-network-learns-to-identify-criminals-by-their-faces/

这篇论文在科学界和大众媒体中引发了极大的争议。该方案的一个关键问题在于未能正确识别输入数据中固有的偏见。也就是说,本研究中使用的数据来自两个不同的来源:一个是犯罪分子,另一个是非犯罪分子。一些研究人员建议,他们的算法识别的是与研究中使用的不同数据来源相关的模式,而不是从人脸中识别出相关的模式。尽管可以从技术角度讨论模型的可靠性,但关键的批评还是从伦理角度出发:我们应该清楚地识别深度学习算法所使用的输入数据中的固有偏见,并考虑其应用将如何影响人们的生活。

Timothy Revell。使用面部识别技术“识别”犯罪分子的担忧。《新科学家》2016 年 12 月 1 日。可通过以下链接访问:www.newscientist.com/article/2114900-concernsas-face-recognition-tech-used-to-identify-criminals/。有关学习算法(包括深度学习)伦理问题的更多了解,请参考 AI Now 研究所的工作(ainowinstitute.org/),该机构旨在理解智能系统的社会影响。

神经网络的常见组成部分和操作

神经网络有两个关键组件:层和节点。节点负责特定的操作,而层是由多个节点组成的,用于区分系统的不同阶段。

通常,神经网络有以下三类层:

  • 输入:接收输入数据并进行初步解释的地方

  • 隐藏:计算发生的地方,数据在这里被修改并传递

  • 输出:输出被组装和评估的地方

图 2:神经网络中最常见层的示意图。由 Glosser.ca - 自主创作,衍生自文件:人工神经网络.svg,CC BY-SA 3.0,commons.wikimedia.org/w/index.php…

隐藏层是神经网络中最重要的层。它们被称为隐藏层,因为在这些层中生成的表示在数据中不可用,而是从数据中学习到的。正是在这些层中,神经网络的主要计算过程发生。

节点是数据在网络中表示的地方。与节点相关联的有两个值:偏差和权重。这两个值会影响数据如何被节点表示并传递给其他节点。当网络学习时,它会有效地调整这些值以满足优化函数。

神经网络的大部分工作发生在隐藏层中。不幸的是,目前并没有明确的规则来确定一个网络应该有多少层或节点。在实现神经网络时,人们通常会花时间尝试不同层和节点的组合。建议总是从一个单层开始,并且节点的数量应该反映输入数据的特征数(即数据集中有多少)。然后,继续添加层和节点,直到达到满意的性能——或者当网络开始过拟合训练数据时。

当代神经网络实践通常仅限于实验节点和层的数量(例如,网络的深度)以及每层执行的操作类型。许多成功的案例表明,神经网络仅通过调整这些参数就能超越其他算法。

作为直观的理解,想象数据通过输入层进入神经网络系统,然后在网络中通过节点逐一传递。数据所走的路径将取决于节点的互联程度、每个节点的权重和偏差、每层执行的操作类型以及数据在这些操作后的状态。神经网络通常需要多次“运行”(或训练周期),以不断调整节点的权重和偏差,这意味着数据在图的不同层之间会多次传递。

本节为您提供了神经网络和深度学习的概述。此外,我们讨论了初学者理解以下关键概念的直觉:

  • 在原则上,神经网络可以近似大多数函数,只要有足够的资源和数据。

  • 层和节点是神经网络的最重要结构组件。通常,人们花费大量时间来修改这些组件,以找到适用的架构。

  • 权重和偏差是网络在训练过程中“学习”的关键属性。

这些概念将在我们下一节中证明其有用性,因为我们将探索一个实际训练过的神经网络,并对其进行修改以训练我们自己的网络。

配置深度学习环境

在完成本章之前,我们希望您与一个真实的神经网络进行交互。我们将首先介绍本书中使用的主要软件组件,并确保它们已正确安装。然后,我们将探索一个预训练的神经网络,并探讨之前讨论的一些组件和操作,这些操作位于“什么是神经网络?”部分。

用于深度学习的软件组件

我们将在深度学习中使用以下软件组件:

Python 3

我们将使用 Python 3。Python 是一种通用编程语言,在科学界非常流行,因此在深度学习中被广泛采用。本书不支持 Python 2,但可以用它来训练神经网络,而不是 Python 3。即使选择在 Python 2 中实现解决方案,考虑迁移到 Python 3,因为其现代功能集比前者更为强大。

TensorFlow

TensorFlow 是一个用于执行图形形式的数学操作的库。TensorFlow 最初由 Google 开发,今天是一个拥有许多贡献者的开源项目。它专为神经网络而设计,是创建深度学习算法时最受欢迎的选择之一。

TensorFlow 也以其生产组件而闻名。它附带 TensorFlow Serving (https://github.com/tensorflow/serving), 这是一个用于提供深度学习模型的高性能系统。此外,经过训练的 TensorFlow 模型可以在其他高性能编程语言(如 Java、Go 和 C)中使用。这意味着可以在从微型计算机(即 RaspberryPi)到 Android 设备的任何设备上部署这些模型。

Keras

为了与 TensorFlow 高效交互,我们将使用 Keras (keras.io/),一个提供高级 API 以开发神经网络的 Python 包。虽然 TensorFlow 专注于组件之间的计算图交互,但 Keras 则专注于神经网络。Keras 使用 TensorFlow 作为其后端引擎,使得开发这类应用程序更加容易。

截至 2017 年 11 月(TensorFlow 版本 1.4),Keras 作为 TensorFlow 的一部分进行分发。它在 tf.keras 命名空间下可用。如果你已经安装了 TensorFlow 1.4 或更高版本,你的系统中已经包含了 Keras。

TensorBoard

TensorBoard 是一个数据可视化工具套件,用于探索 TensorFlow 模型,并与 TensorFlow 原生集成。TensorBoard 通过消耗 TensorFlow 在训练神经网络时创建的检查点和摘要文件来工作。这些文件可以在接近实时(延迟 30 秒)或者在网络训练完成后进行探索。

TensorBoard 使得实验和探索神经网络的过程变得更加容易——而且追踪你的网络训练过程非常令人兴奋!

Jupyter Notebooks,Pandas 和 NumPy

在使用 Python 创建深度学习模型时,通常会从交互式工作开始,慢慢开发出一个最终变成更结构化软件的模型。在这个过程中,经常使用三个 Python 包:Jupyter Notebooks,Pandas 和 NumPy

  • Jupyter Notebooks 创建使用 web 浏览器作为界面的交互式 Python 会话

  • Pandas 是一个用于数据处理和分析的包

  • NumPy 经常用于数据的形状变换和执行数值计算

这些包偶尔使用。它们通常不构成生产系统的一部分,但在探索数据和开始构建模型时常被使用。我们更详细地关注其他工具。

Michael Heydt(2017 年 6 月,Packt 出版)的《学习 Pandas》和 Dan Toomey(2016 年 11 月,Packt 出版)的《学习 Jupyter》提供了如何使用这些技术的全面指南。这些书籍是继续深入学习的好参考。

组件描述最低版本
Python通用编程语言。流行的用于开发深度学习应用程序的语言。 3.6
TensorFlow开源图计算 Python 包,通常用于开发深度学习系统。 1.4

| Keras | 提供高层次接口到 TensorFlow 的 Python 包。 |  2.0.8-tf(与 TensorFlow 一起分发)

|

TensorBoard基于浏览器的软件,用于可视化神经网络统计数据。 0.4.0
Jupyter Notebook基于浏览器的软件,用于交互式工作与 Python 会话。 5.2.1
Pandas用于分析和处理数据的 Python 包。 0.21.0
NumPy用于高性能数值计算的 Python 包。 1.13.3

表 1:创建深度学习环境所需的软件组件

活动:验证软件组件

在我们探索一个训练好的神经网络之前,让我们验证所有必需的软件组件是否已准备好。我们提供了一个脚本来验证这些组件的可用性。让我们花点时间运行脚本,并处理可能遇到的任何问题。

现在我们将测试是否所有本书所需的软件组件都在您的工作环境中可用。首先,我们建议使用 Python 的原生模块 venv 创建一个 Python 虚拟环境。虚拟环境用于管理项目依赖项。我们建议您为每个创建的项目配置独立的虚拟环境。现在让我们创建一个。

如果您更喜欢使用 conda 环境,可以随意使用它们。

  1. 可以使用以下命令创建一个 Python 虚拟环境:
      $ python3 -m venv venv
      $ source venv/bin/activate
  1. 后者命令会将字符串(venv)添加到命令行的开头。使用以下命令来停用您的虚拟环境:
      $ deactivate 

确保在处理项目时始终激活您的 Python 虚拟环境。

  1. 激活虚拟环境后,通过执行 pip 对 requirements.txt 文件进行操作,确保正确的组件已安装。这将尝试在该虚拟环境中安装本书使用的模型。如果它们已存在,则不会执行任何操作:

图 3:终端运行 pip 安装 requirements.txt 中的依赖项的图片

运行以下命令来安装依赖项:

      $ pip install –r requirements.txt 

这将为您的系统安装所有必需的依赖项。如果它们已经安装,该命令将简单地通知您。

这些依赖项对于所有代码活动的正常运行至关重要。

作为此活动的最后一步,让我们执行脚本 test_stack.py。该脚本正式验证此书所需的所有包是否已安装并在您的系统中可用。

  1. 学生们,运行脚本 Chapter_4/activity_1/test_stack.py 检查 Python 3、TensorFlow 和 Keras 是否可用。使用以下命令:
      $ python3 chapter_4/activity_1/test_stack.py 

脚本返回有用的消息,说明已安装什么以及需要安装什么。

  1. 在您的终端中运行以下脚本命令:
      $ tensorboard --help 

您应该看到一条帮助信息,解释每个命令的作用。如果没有看到该消息,或者看到错误消息,请向您的讲师寻求帮助:

图 4:终端运行 python3 test_stack.py 的图片。脚本返回消息,告知所有依赖项已正确安装。

如果出现类似以下消息,无需担心:

运行时警告:模块'tensorflow.python.framework.fast_tensor_util'的编译时版本 3.5 与运行时版本 3.6 不匹配,返回 f(*args, **kwds)

如果您运行的是 Python 3.6 并且分发

TensorFlow wheel 是在不同版本(在此情况下为 3.5)下编译的。您可以安全地忽略该消息。

一旦我们确认安装了 Python 3、TensorFlow、Keras、TensorBoard 以及requirements.txt中列出的软件包,我们就可以继续进行演示,了解如何训练神经网络,然后使用这些工具来探索已经训练好的网络。

探索训练好的神经网络

在本节中,我们将探索一个已经训练好的神经网络。我们这么做是为了理解神经网络如何解决一个实际问题(预测手写数字),同时熟悉 TensorFlow 的 API。在探索这个神经网络时,我们会看到许多在前面章节中介绍过的组件,如节点和层,但我们也会看到许多不太熟悉的组件(例如激活函数)——我们将在后续章节中进一步探讨这些内容。然后,我们将通过一个练习,讲解该神经网络是如何训练的,并且尝试自己训练这个网络。

我们将要探索的网络已经经过训练,可以识别手写数字(整数)。它使用了 MNIST 数据集( yann.lecun.com/exdb/mnist/),这是一个经典的数据集,常用于探索模式识别任务。

MNIST 数据集

修改版国家标准与技术研究院MNIST)数据集包含一个包含 60,000 张图像的训练集和一个包含 10,000 张图像的测试集。每张图像包含一个手写数字。这个数据集最初是由美国政府创建的,用来测试不同的计算机系统识别手写文字的方法。能够做到这一点对于提高邮政服务、税收系统和政府服务的效率具有重要意义。由于 MNIST 数据集对现代方法来说过于简单,因此现在的研究通常使用不同的、更新的数据集(例如 CIFAR)。然而,MNIST 数据集仍然非常有助于理解神经网络的工作原理,因为已知的模型可以高效地实现较高的准确率。

CIFAR 数据集是一个机器学习数据集,包含按照不同类别组织的图像。与 MNIST 数据集不同,CIFAR 数据集包含多个领域的类别,如动物、活动和物体。CIFAR 数据集可以在以下链接找到:www.cs.toronto.edu/~kriz/cifar.html

图 5:MNIST 数据集训练集的摘录。每张图像是一个单独的 20x20 像素图像,包含一个手写数字。原始数据集可以在以下链接找到:yann.lecun.com/exdb/mnist/…

使用 TensorFlow 训练神经网络

现在,让我们训练一个神经网络,使用 MNIST 数据集来识别新的数字。

我们将实现一种特殊用途的神经网络,称为“卷积神经网络”,来解决这个问题(我们将在后续章节中更详细地讨论这些内容)。我们的网络包含三层隐藏层:两层全连接层和一层卷积层。卷积层由以下 TensorFlow 的 Python 代码片段定义:

    W = tf.Variable(
        tf.truncated_normal([5, 5, size_in, size_out],
        stddev=0.1),
        name="Weights")    
    B = tf.Variable(tf.constant(0.1, shape=[size_out]), 
        name="Biases")

    convolution = tf.nn.conv2d(input, W, strides=[1, 1, 1, 1],
    padding="SAME")
    activation = tf.nn.relu(convolution + B)

    tf.nn.max_pool(
    activation,
    ksize=[1, 2, 2, 1],
    strides=[1, 2, 2, 1],
    padding="SAME") 

我们在训练网络时只执行这段代码一次。

变量 W 和 B 代表权重和偏置。这些值是隐藏层中的节点用来改变网络对数据的解释的,数据在网络中传递时会被这些值所修改。暂时不要担心其他变量。

全连接层由以下 Python 代码片段定义:

    W = tf.Variable(
        tf.truncated_normal([size_in, size_out], stddev=0.1),
        name="Weights")
    B = tf.Variable(tf.constant(0.1, shape=[size_out]),
        name="Biases")
        activation = tf.matmul(input, W) + B

这里,我们还有两个 TensorFlow 变量 W 和 B。请注意这些变量的初始化有多简单:W 被初始化为一个来自剪枝高斯分布的随机值(剪枝范围为size_in 和 size_out),标准差为 0.1,B(偏置项)被初始化为0.1,这是一个常数。这两个值会在每次运行时不断变化。这段代码执行两次,产生两个全连接网络——一个将数据传递给另一个。

这 11 行 Python 代码代表了我们的完整神经网络。在第五章模型架构中,我们将详细讲解每个组件如何使用 Keras 实现。目前,请重点理解网络如何在每次运行时改变每一层中 W 和 B 的值,以及这些代码片段如何构成不同的层。这 11 行 Python 代码是数十年神经网络研究的结晶。

现在让我们训练这个网络,评估它在 MNIST 数据集上的表现。

训练神经网络

按照以下步骤设置本次练习:

  1. 打开两个终端实例。

  2. 在两个终端中,导航到chapter_4/exercise_a目录。

  3. 在两个终端中,确保你的 Python 3 虚拟环境是激活状态,并且requirements.txt中列出的依赖已安装。

  4. 其中之一是通过以下命令启动 TensorBoard 服务器:

    $ tensorboard --logdir=mnist_example/

  5. 在另一个终端中,从该目录中运行train_mnist.py脚本。

  6. 在浏览器中打开当你启动服务器时提供的 TensorBoard URL。

在你运行train_mnist.py脚本的终端中,你将看到一个包含模型训练进度的进度条。当你打开浏览器页面时,你会看到几个图表。点击显示Accuracy的图表,放大它并让页面刷新(或者点击refresh按钮)。你将看到随着训练轮次的增加,模型的准确度逐渐提高。

利用这个时刻,解释神经网络在训练过程早期迅速达到高精度的强大能力。

我们可以看到,在大约 200 个周期(或步骤)后,网络超过了 90% 的准确率。也就是说,网络在测试集上正确预测了 90% 的数字。随着训练的进行,网络继续提高准确率,直到第 2000 步,最终达到 97% 的准确率。

现在,我们还将测试这些网络在未见数据上的表现。我们将使用由 Shafeen Tejani 创建的一个开源 Web 应用,探索训练好的网络是否能正确预测我们创建的手写数字。

使用未见数据测试网络性能

在浏览器中访问 mnist-demo.herokuapp.com/ 并在指定的白色框中绘制一个 0 到 9 之间的数字:

图 6:我们可以手动绘制数字并测试两个训练网络准确性的 Web 应用

来源:github.com/ShafeenTejani/mnist-demo

在应用程序中,你可以看到两个神经网络的结果。我们训练的那个在左边(叫做 CNN)。它能正确分类你所有的手写数字吗?试着在指定区域的边缘绘制数字。例如,试着在该区域的右边缘附近绘制数字1

图 7:两个网络都难以估算绘制在区域边缘的值

在这个例子中,我们看到数字1被绘制在绘图区域的右侧。在两个网络中,这个数字是1的概率都是0

MNIST 数据集不包含图像边缘的数字。因此,两个网络都没有为该区域内的像素分配相关的值。如果我们将数字绘制得离指定区域的中心更近,这两个网络在分类数字时会表现得更好。这表明神经网络的强大程度仅取决于用来训练它们的数据。如果训练数据与我们试图预测的数据相差甚远,网络很可能会产生令人失望的结果。

活动:探索训练过的神经网络

在本节中,我们将探索我们在练习中训练的神经网络。我们还将通过调整超参数来训练一些其他的网络。让我们先来探索在练习中训练的网络。

我们已经将训练好的网络作为二进制文件提供在该目录下。让我们使用 TensorBoard 打开这个训练好的网络,并探索其组成部分。

使用你的终端,导航到目录 chapter_4/activity_2 并执行以下命令启动 TensorBoard:

  $ tensorboard --logdir=mnist_example/ 

现在,在浏览器中打开 TensorBoard 提供的 URL。你应该能够看到 TensorBoard 的标量页面:

图 8:启动 TensorBoard 实例后的终端图像

在你打开tensorboard命令提供的网址后,你应该能够看到以下 TensorBoard 页面:

图 9:TensorBoard 登录页面的图像

现在,让我们来探索我们训练好的神经网络,看看它的表现如何。

在 TensorBoard 页面上,点击Scalars页面,并放大准确率图表。现在,将平滑滑块移到0.9

准确率图表衡量了网络在测试集上猜测标签的准确性。刚开始时,网络的标签猜测完全错误。这是因为我们将网络的权重和偏置初始化为随机值,所以它的第一次尝试只是一个猜测。然后,网络将在第二次运行时调整其层的权重和偏置;网络将通过改变权重和偏置来投资于那些给出正面结果的节点,而通过逐渐减少它们对网络的影响(最终达到 0)来惩罚那些表现不佳的节点。正如你所看到的,这是一种非常高效的技巧,可以快速得到良好的结果。

让我们将注意力集中在准确率图表上。看看算法是如何在大约 1,000 个 epoch 后达到很高的准确率(> 95%)的?在 1,000 到 2,000 个 epoch 之间发生了什么?

如果我们继续训练更多的 epoch,网络会变得更准确吗?在 1,000 到 2,000 个 epoch 之间,网络的准确率持续提高,但提升的速度在减慢。如果继续训练,网络可能会有轻微的改进,但在当前架构下,它无法达到 100%的准确率。

这个脚本是一个修改版的官方 Google 脚本,旨在展示 TensorFlow 是如何工作的。我们将脚本分成了更易理解的函数,并添加了许多注释来引导你的学习。尝试通过修改脚本顶部的变量来运行这个脚本:

     LEARNING_RATE = 0.0001
     EPOCHS = 2000

现在,尝试通过修改这些变量的值来运行那个脚本。例如,试着将学习率修改为0.1,将 epoch 设置为100。你认为网络能够获得相似的结果吗?

你的神经网络中有许多其他参数可以修改。现在,尝试调整网络的 epoch 和学习率。你会发现,这两个参数本身就能极大地改变网络的输出——但也有其限制。尝试看看通过仅仅改变这两个参数,是否能够使当前架构下的网络训练更快。

使用 TensorBoard 验证你的网络训练情况。通过将起始值乘以 10,多次调整这些参数,直到你注意到网络有所改善。这种调整网络并找到更好准确率的过程类似于今天在工业应用中用来改进现有神经网络模型的方法。

总结

在本章中,我们使用 TensorBoard 探索了一个基于 TensorFlow 训练的神经网络,并用不同的训练轮次和学习率训练了我们自己的修改版网络。这为你提供了如何训练一个高效神经网络的实际操作经验,并且让你有机会探索其一些局限性。

你认为我们能否在真实的比特币数据上实现类似的准确度?我们将在第五章模型架构中尝试使用一种常见的神经网络算法来预测未来的比特币价格。在第六章模型评估与优化中,我们将评估并改进该模型,最后在第七章产品化中,我们将创建一个通过 HTTP API 提供该系统预测的程序。

第五章:模型架构

基于第四章《神经网络与深度学习介绍》的基本概念,我们现在进入一个实际问题:我们能否使用深度学习模型预测比特币价格?在本章中,我们将学习如何构建一个尝试进行此预测的深度学习模型。我们将通过将所有这些组件结合起来,构建一个简单但完整的深度学习应用程序的初步版本来结束本章。

在本章结束时,您将能够:

  • 为深度学习模型准备数据

  • 选择正确的模型架构

  • 使用 Keras,这是一个 TensorFlow 抽象库

  • 使用训练好的模型进行预测

选择合适的模型架构

深度学习是一个正在进行激烈研究活动的领域。研究人员致力于发明新的神经网络架构,这些架构可以解决新问题或提高之前实现的架构的性能。在本节中,我们将研究旧的和新的架构。

旧的架构已经被广泛应用于解决各种问题,并且通常被认为是在开始新项目时的正确选择。较新的架构在特定问题上取得了巨大成功,但它们更难以推广。后者作为下一步探索的参考非常有趣,但在启动项目时并不是一个好的选择。

常见架构

考虑到众多架构的可能性,有两种流行的架构经常作为许多应用的起点:卷积神经网络CNNs)和递归神经网络RNNs)。这些是基础性网络,应该作为大多数项目的起点。我们还介绍了另外三种网络,因其在该领域的重要性:长短期记忆LSTM)网络,RNN 的变种;生成对抗网络GANs);以及深度强化学习。这些后者架构在解决当代问题时取得了巨大成功,但使用起来相对更为复杂。

卷积神经网络

卷积神经网络因其在处理具有网格状结构的问题中表现出色而声名显赫。它们最初是为了分类图像而创建的,但已经在许多其他领域得到了应用,从语音识别到自动驾驶汽车。

CNN 的核心思想是将紧密相关的数据作为训练过程的一个要素,而不仅仅是单独的数据输入。这个理念在图像处理中尤其有效,因为图像中位于另一个像素右侧的像素与该像素相关,因为它们是更大构图的一部分。在这种情况下,网络训练的目标就是预测该构图。因此,将几个像素组合在一起比仅使用单独的像素要好。

卷积这个名称是用来表示这个过程的数学表达式:

图 1:卷积过程的插图 图像来源:Volodymyr Mnih 等人。

欲了解更多信息,请参考《通过深度强化学习实现人类级别的控制》,2015 年 2 月,《自然》杂志。可通过以下链接访问:storage.googleapis.com/deepmind-media/dqn/DQNNaturePaper.pdf

循环神经网络

卷积神经网络通过一组输入来工作,这些输入会不断改变网络各层和节点的权重和偏置。这种方法的一个已知限制是,它的架构在决定如何改变网络的权重和偏置时忽略了这些输入的顺序。

循环神经网络正是为了应对这个问题而创建的。RNNs 旨在处理序列数据。这意味着在每个迭代中,层级可以受到前一层输出的影响。在给定序列中的先前观察的记忆在评估后续观察时起着重要作用。

由于语音识别问题具有序列性质,RNNs(循环神经网络)在该领域得到了成功的应用。此外,它们还用于翻译问题。谷歌翻译当前的算法——称为Transformer——使用 RNN 将文本从一种语言翻译成另一种语言。

欲了解更多信息,请参考《Transformer:一种用于语言理解的新型神经网络架构》,作者:Jakob Uszkoreit,谷歌研究博客,2017 年 8 月。可通过以下链接访问:ai.googleblog.com/2017/08/transformer-novel-neural-network.html

图 2:来自 distill.pub 的插图(distill.pub/2016/augmen…

图 2 显示了英语中的单词与法语中的单词之间的关系,这种关系取决于它们在句子中出现的位置。RNNs 在语言翻译问题中非常流行。

长短期记忆网络(LSTM)是为了解决梯度消失问题而创建的 RNN 变种。梯度消失问题是由于记忆组件距离当前步骤太远,导致它们因距离较远而获得较低的权重。LSTM 是 RNN 的一种变体,包含一个叫做忘记门的记忆组件。该组件可用于评估近期和旧的元素如何影响权重和偏置,具体取决于观察在序列中的位置。

欲了解更多细节,请参见 Sepp Hochreiter 和 Jürgen Schmidhuber 于 1997 年首次提出的 LSTM 架构。当前的实现版本已有多次修改。关于 LSTM 每个组件如何工作的详细数学解释,建议参考 Christopher Olah 于 2015 年 8 月发布的文章《理解 LSTM 网络》,可访问:colah.github.io/posts/2015-08-Understanding-LSTMs/

生成对抗网络

生成对抗网络 (GANs) 是由 Ian Goodfellow 及其在蒙特利尔大学的同事们于 2014 年发明的。GANs 提出,应该有两个神经网络相互竞争,以此来优化权重和偏置,而不是仅有一个神经网络去最小化其错误。

欲了解更多细节,请参见 Ian Goodfellow 等人所著的《生成对抗网络》,发表于 arXiv. 2014 年 6 月 10 日。可访问:arxiv.org/abs/1406.2661

GANs 拥有一个生成新数据(即“假”数据)的网络和一个评估由第一个网络生成的数据是否真实的网络。它们相互竞争,因为它们都在学习:一个学习如何更好地生成“假”数据,另一个则学习如何区分数据是否为真实。它们在每一个迭代周期中不断优化,直到两者都收敛。此时,评估生成数据的网络无法再区分“假”数据和真实数据。

GANs 已成功应用于数据具有明确拓扑结构的领域。其最初的实现是使用 GAN 生成与真实图像相似的物体、人物面孔和动物的合成图像。图像生成是 GAN 应用最为广泛的领域,但在其他领域的应用也偶尔出现在研究论文中。

图 3:展示不同 GAN 算法在根据给定情绪变化人物面孔的结果。来源:StarGAN 项目。可访问:github.com/yunjey/StarGAN

深度强化学习

原始的深度强化学习(DRL)架构由 Google 旗下的人工智能研究机构 DeepMind 提出,该机构位于英国。

DRL 网络的关键思想是它们本质上是无监督的,并且通过试错学习,只优化奖励函数。也就是说,与其他使用监督学习方法来优化预测错误(与已知正确答案相比)的网络不同,DRL 网络并不知道处理问题的正确方式。它们只是被给定系统规则,并且每当它们正确执行一个任务时,就会获得奖励。这个过程需要大量的迭代,最终训练网络在多个任务中表现出色。

如需更多信息,请参见,Volodymyr Mnih 等人的《通过深度强化学习实现人类级别控制》,2015 年 2 月,发表于《自然》杂志。可在以下地址获取:storage.googleapis.com/deepmind-media/dqn/DQNNaturePaper.pdf

深度强化学习(DRL)模型在 DeepMind 创建 AlphaGo 后获得了广泛关注,AlphaGo 是一个在围棋游戏中超越职业选手的系统。DeepMind 还创建了能够自学并以超人类水平玩视频游戏的 DRL 网络:

图 4:表示 DQN 算法如何工作的图像

如需更多信息,请参见,DQN 是 DeepMind 为了击败 Atari 游戏而创建的。该算法使用深度强化学习解决方案,不断提高其奖励。图片来源:keon.io/deep-q-learning/

架构数据结构成功应用

| 卷积神经网络(CNNs)

| 网格状拓扑结构(即,图像)

图像识别和分类
循环神经网络(RNN)和长短期记忆(LSTM)网络序列数据(即,时间序列数据)语音识别、文本生成与翻译
生成对抗网络(GANs)网格状拓扑结构(即,图像)图像生成
深度强化学习(DRL)具有明确规则和清晰定义奖励函数的系统玩视频游戏和自动驾驶车辆

表 1:不同的神经网络架构在不同领域取得了成功。网络的架构通常与当前问题的结构相关。

数据归一化

在构建深度学习模型之前,还有一步是必须的:数据归一化。

数据归一化是机器学习系统中的常见做法。尤其在神经网络中,研究人员提出归一化是训练 RNN(和 LSTM)的一个关键技术,主要是因为它能减少网络的训练时间,并提高网络的整体性能。

欲了解更多信息,请参考 Sergey Ioffe 等人在 arXiv 上发布的《批量归一化:通过减少内部协变量偏移加速深度网络训练》,2015 年 3 月,网址:arxiv.org/abs/1502.03167

决定采用哪种归一化技术取决于数据和具体问题。以下是常用的几种技术。

Z-分数

当数据呈正态分布(即高斯分布)时,可以计算每个观测值与其均值之间的标准差距离。

当识别数据点与分布中更可能发生的事件之间的距离时,这种归一化非常有用。Z-分数的定义为:

这里, 的观测值, 是均值, 是该序列的标准差。

欲了解更多信息,请参考标准分数文章(Z-分数)。维基百科,网址:en.wikipedia.org/wiki/Standard_score

点相对归一化

这种归一化计算给定观测值与序列第一个观测值之间的差异。这种归一化有助于识别相对于起始点的趋势。点相对归一化的定义为:

这里, 的观测值, 是该序列的第一个观测值。

正如 Siraj Raval 在他的视频《如何轻松预测股票价格 深度学习入门》第 7 集中所建议的,视频可在 YouTube 上观看:www.youtube.com/watch?v=ftMq5ps503w

最大值和最小值归一化

这种归一化计算给定观测值与序列的最大值和最小值之间的距离。当处理的序列中的最大值和最小值不是异常值并且对未来预测有重要作用时,这种归一化非常有用。

这种归一化技术可以应用于:

这里, 的观测值,O 代表包含所有 O 值的向量,min (O) 和 max (O) 函数分别表示该序列的最小值和最大值。

在下一个活动中,我们将探索比特币数据集并为模型准备数据。我们将准备可用的比特币数据以供 LSTM 模型使用。这包括选择感兴趣的变量,选择相关的时间段,并应用前述的点相对归一化技术。

结构化你的问题

与研究人员相比,实践者在开始一个新的深度学习项目时,花在确定选择哪种架构上的时间要少得多。获取能够正确代表给定问题的数据是开发这些系统时需要考虑的最重要因素,其次是理解数据集固有的偏差和局限性。

在开始开发深度学习系统时,考虑以下反思问题:

  • 我有正确的数据吗? 这是训练深度学习模型时最难的挑战。首先,用数学规则定义你的问题。使用精确的定义,并将问题组织为类别(分类问题)或连续尺度(回归问题)。现在,如何收集关于这些度量的数据呢?

  • 我有足够的数据吗? 通常,深度学习算法在大数据集上的表现明显优于在小数据集上的表现。知道训练一个高性能算法需要多少数据,取决于你试图解决的问题类型,但尽量收集尽可能多的数据。

  • 我可以使用预训练模型吗? 如果你正在处理的问题是更一般应用的一个子集——但在同一领域内——考虑使用预训练模型。预训练模型可以帮助你在解决问题时,专注于问题的具体模式,而不是领域的更一般特征。一个好的起点是官方的 TensorFlow 仓库(github.com/tensorflow/models)。

图 5:深度学习项目开始时需要考虑的关键反思问题的决策树

在某些情况下,数据可能根本无法获得。根据具体情况,可以使用一系列技术有效地从输入数据中生成更多数据。这个过程称为数据增强,在处理图像识别问题时具有成功的应用。

一个很好的参考是文章《使用深度神经网络分类浮游生物》,可以在benanne.github.io/2015/03/17/plankton.html找到。作者展示了一系列技术,用于增强一小组图像数据,以增加模型的训练样本数量。

活动:探索比特币数据集并为模型准备数据

我们将使用一个公开的数据集,该数据集最初来自于 CoinMarketCap,一个追踪不同加密货币统计信息的流行网站。数据集已与本章一起提供,将会被使用。

我们将使用 Jupyter Notebooks 探索数据集。Jupyter Notebooks 提供通过网页浏览器访问的 Python 会话,使你可以互动地处理数据。它们是探索数据集的流行工具,在本书的活动中会被使用。

使用终端,导航到目录 Chapter_5/activity_3 并执行以下命令启动 Jupyter Notebook 实例:

     $ jupyter notebook 

现在,在浏览器中打开应用程序提供的网址。你应该能够看到一个 Jupyter Notebook 页面,页面上显示了文件系统中的多个目录。你应该能看到以下输出:

图 6:启动 Jupyter Notebook 实例后的终端图像。导航到浏览器中显示的网址,你应该能够看到 Jupyter Notebook 的登录页面。

现在,导航到目录并点击文件 Activity Exploring_Bitcoin_ Dataset.ipynb。这是一个 Jupyter Notebook 文件,它将会在新的浏览器标签中打开。应用程序将自动为你启动一个新的 Python 交互式会话。

图 7:你的 Jupyter Notebook 实例的登录页面

图 8:Notebook Activity_Exploring_Bitcoin_Dataset.ipynb 的图片。你现在可以与该 Notebook 进行交互并进行修改。

打开我们的 Jupyter Notebook 后,让我们现在探索本章提供的比特币数据。

数据集 data/bitcoin_historical_prices.csv 包含自 2013 年初以来的比特币价格数据。最新的观测数据为 2017 年 11 月——该数据集来自 CoinMarketCap,这是一个每天更新的在线服务。数据集包含八个变量,其中两个(日期和周数)描述了数据的时间周期——这些可以用作索引——另外六个(openhighlowclosevolumemarket_capitalization)则可以用来理解比特币价格和价值是如何随时间变化的:

变量描述
date观察的日期。
iso_week给定年份的周数。
open单个比特币的开盘值。
high给定日期期间内的最高值。
low给定日期期间内的最低值。
close交易日结束时的值。
volume当天交易的比特币总量。
market_capitalization市值,计算公式为 市值 = 价格 * 流通供应量。

表 2:比特币历史价格数据集中可用的变量(即列)

使用打开的 Jupyter Notebook 实例,现在让我们探索这两个变量的时间序列:closevolume。我们将从这些时间序列开始,探索价格波动模式。

导航到已打开的 Jupyter Notebook 实例 Activity Exploring_Bitcoin_ Dataset.ipynb。现在,执行标题为 Introduction 下的所有单元格。这将导入所需的库并将数据集导入内存。

在数据集导入内存后,转到探索部分。你会看到一段生成 close 变量时间序列图的代码片段。你能为 volume 变量生成相同的图吗?

图 9:比特币收盘价的时间序列图,数据来自 close 变量。请在下方的新单元格中重现该图,但使用 volume 变量。

你一定已经注意到,2017 年这两个变量都有大幅上升。这反映了一个当前现象,即比特币的价格和价值自 2017 年初以来持续增长。

图 10:比特币收盘价(以美元计)。注意到 2013 年底和 2014 年初的早期价格飙升。同时,也可以注意到自 2017 年初以来,最近的价格已经飙升。

图 11:比特币交易量(以美元计)显示,从 2017 年开始,市场上的比特币交易量显著增加。与每日收盘价相比,总体交易量的波动性要大得多。

此外,我们还注意到,很多年前,比特币的价格波动不如近几年那样剧烈。虽然这些早期的周期可以被神经网络用来理解某些模式,但考虑到我们关注的是预测未来不远的价格,我们将排除较旧的观察数据。让我们只筛选 2016 年和 2017 年的数据。

导航到“准备数据集以供模型使用”部分。我们将使用 pandas API 筛选 2016 年和 2017 年的数据。Pandas 提供了一个直观的 API 来执行此操作:

     bitcoin_recent = bitcoin[bitcoin['date'] >= '2016-01-01']

变量 bitcoin_recent 现在包含了我们原始比特币数据集的一个副本,但仅包含 2016 年 1 月 1 日或之后的观测数据。

作为最后一步,我们现在使用 数据归一化 部分描述的点相对归一化技术来归一化我们的数据。我们只归一化两个变量(close 和 volume),因为这两个是我们要预测的变量。

在包含本章节的同一目录下,我们放置了一个名为 normalizations.py 的脚本。该脚本包含了本章节中描述的三种归一化技术。我们将该脚本导入到 Jupyter Notebook 中,并将函数应用于我们的系列。

导航到“准备数据集以供模型使用”部分。现在,使用 iso_week 变量按周对所有日期观察进行分组,使用 pandas 方法 groupby()。然后,我们可以直接对该周内的系列应用归一化函数 normalizations.point_relative_normalization()。我们将该归一化的输出存储为同一个 pandas 数据框中的新变量,方法如下:

     bitcoin_recent['close_point_relative_normalization'] =
     bitcoin_recent.groupby('iso_week')['close'].apply(
     lambda x: normalizations.point_relative_normalization(x))

现在,变量 close_point_relative_normalization 包含了 close 变量的归一化数据。请对 volume 变量执行相同的操作:

图 12:Jupyter Notebook 的图像,聚焦于应用标准化函数的部分。

标准化的收盘变量包含每周有趣的方差模式。我们将使用该变量来训练我们的 LSTM 模型。

图 13:显示来自标准化变量 close_point_relative_normalization 的系列图。

为了评估我们的模型表现如何,我们需要将其准确性与其他数据进行对比。我们通过创建两个数据集来实现这一点:一个训练集和一个测试集。在本次活动中,我们将使用 80%的数据集来训练我们的 LSTM 模型,剩下的 20%用于评估其表现。

鉴于数据是连续的,并且以时间序列的形式存在,我们使用最后 20%的可用周作为测试集,前 80%作为训练集:

图 14:使用周数创建训练集和测试集

最后,导航到“存储输出”部分,并将过滤后的变量保存到磁盘,如下所示:

     test_dataset.to_csv('data/test_dataset.csv', index=False)
     train_dataset.to_csv('data/train_dataset.csv', index=False)
     bitcoin_recent.to_csv('data/bitcoin_recent.csv', index=False)

在本节中,我们探索了比特币数据集,并为深度学习模型做好了准备。

我们了解到,在 2017 年,比特币的价格暴涨。这一现象需要较长时间才能发生——并且可能受到许多外部因素的影响,这些因素是仅凭这些数据无法解释的(例如,其他加密货币的出现)。我们还使用了点相对标准化技术来处理比特币数据集,并按周进行划分。我们这样做是为了训练 LSTM 网络学习比特币价格变化的每周模式,以便它可以预测未来一整周的价格。然而,比特币的统计数据显示其每周波动很大。我们能预测比特币未来的价格吗?

那么,七天后的价格将是多少?我们将在下一节中使用 Keras 构建一个深度学习模型来探索这个问题。

使用 Keras 作为 TensorFlow 接口

本节重点介绍 Keras。我们使用 Keras 是因为它将 TensorFlow 的接口简化为通用抽象。在后台,计算仍然是在 TensorFlow 中进行的——图形仍然是使用 TensorFlow 组件构建的——但接口要简单得多。我们减少了对单独组件(如变量和操作)的关注,更多地关注构建网络作为一个计算单元。Keras 使得实验不同的架构和超参数变得更加容易,从而更快地朝着高效的解决方案迈进。

从 TensorFlow 1.4.0(2017 年 11 月)开始,Keras 现在正式与 TensorFlow 一起分发为tf.keras。这表明 Keras 现在与 TensorFlow 紧密集成,且它很可能会继续作为开源工具开发很长一段时间。

模型组件

正如我们在《神经网络与深度学习简介》第四章中所看到的,LSTM 网络也有输入、隐藏和输出层。每个隐藏层都有一个激活函数,用于评估该层的相关权重和偏差。如预期的那样,网络按顺序从一层传递数据到另一层,并通过每次迭代的输出来评估结果(即一个 epoch)。

Keras 提供直观的类来表示每一个这些组件:

组件Keras 类
完整顺序神经网络的高级抽象。keras.models.Sequential()
密集连接层。keras.layers.core.Dense()
激活函数。keras.layers.core.Activation()
LSTM 循环神经网络。这个类包含了专属于这个架构的组件,其中大部分被 Keras 抽象化。keras.layers.recurrent.LSTM()

表 3:Keras API 中关键组件的描述。我们将使用这些组件来构建深度学习模型。

Keras 的keras.models.Sequential()组件代表了一个完整的顺序神经网络。该 Python 类可以独立实例化,然后随后添加其他组件。

我们对构建 LSTM 网络感兴趣,因为这些网络在处理顺序数据时表现良好——而时间序列是顺序数据的一种。使用 Keras,完整的 LSTM 网络实现如下所示:

     from keras.models import Sequential
     from keras.layers.recurrent import LSTM
     from keras.layers.core import Dense, Activation

     model = Sequential()

     model.add(LSTM(
     units=number_of_periods,
     input_shape=(period_length, number_of_periods)
     return_sequences=False), stateful=True)
     model.add(Dense(units=period_length))
     model.add(Activation("linear"))
     model.compile(loss="mse", optimizer="rmsprop")

Snippet 1:使用 Keras 的 LSTM 实现

这个实现将在《模型评估与优化》第六章中进一步优化。

Keras 的抽象化允许专注于使深度学习系统更高性能的关键元素:正确的组件序列是什么,包括多少层和节点,以及使用哪种激活函数。所有这些选择都由将组件添加到实例化的keras.models.Sequential()类的顺序或通过传递给每个组件实例化的参数(即Activation("linear"))确定。最终的.compile()步骤使用 TensorFlow 组件构建神经网络。

构建网络后,我们使用model.fit()方法来训练我们的网络。这将产生一个经过训练的模型,可以用来进行预测:

     model.fit(
     X_train, Y_train,
     batch_size=32, epochs=epochs)

Snippet 2.1:使用model.fit()的示例

变量X_trainY_train分别用于训练的一组数据和用于评估损失函数的较小数据集(即测试网络预测数据的效果)。

最后,我们可以使用model.predict()方法进行预测:

      model.predict(x=X_train)

Snippet 2.2:使用model.predict()的示例

前面的步骤涵盖了 Keras 在处理神经网络时的范式。尽管不同的架构可以以非常不同的方式处理,但 Keras 通过使用三个组件——网络架构、拟合和预测,简化了处理不同架构的接口:

图 15:Keras 神经网络范式:A. 设计神经网络架构,B. 训练神经网络(或拟合),C. 做出预测

Keras 在每个步骤中都允许更大的控制。然而,它的重点是尽可能简单地帮助用户在最短的时间内创建神经网络。这意味着我们可以从一个简单的模型开始,然后在上述每个步骤中添加复杂性,使初始模型的性能更好。

我们将在接下来的活动和章节中利用这个范式。在下一个活动中,我们将创建最简单的 LSTM 网络。然后,在 第六章模型评估与优化,我们将不断评估并修改该网络,使其更加强大和高效。

活动:使用 Keras 创建 TensorFlow 模型

在本活动中,我们将使用 Keras 创建一个 LSTM 模型。

Keras 作为一个接口连接低层次的程序;在这个例子中是 TensorFlow。当我们使用 Keras 设计神经网络时,该神经网络会被 编译 为一个 TensorFlow 计算图。

导航到打开的 Jupyter Notebook 实例 Activity_4_Creating_a_ TensorFlow_Model_Using_Keras.ipynb。现在,执行 构建模型 下的所有单元格。在该部分,我们构建了第一个 LSTM 模型,设置了两个参数:训练观察值的输入大小(对于单个日期等同为 1)和预测期的输出大小——在我们的案例中是七天:

使用 Jupyter Notebook Activity_4_Creating_a_TensorFlow_Model_Using_Keras.ipynb 来构建与 模型组件 部分相同的模型,设置输入和输出的周期长度,以便进行实验。

在模型编译完成后,我们继续将其存储为磁盘上的 h5 文件。定期将模型版本存储到磁盘上是一个好习惯,这样你就可以将模型架构与其预测能力一起保存在硬盘上。

仍然在同一个 Jupyter Notebook 中,导航到 保存模型 头部。在该部分,我们将使用以下命令将模型存储为磁盘上的文件:

     model.save('bitcoin_lstm_v0.h5')

模型 'bitcoin_lstm_v0.h5' 还没有经过训练。当在没有先前训练的情况下保存模型时,实际上只保存了模型的架构。该模型稍后可以通过 Keras 的 load_model() 函数加载,如下所示:

     1 model = keras.models.load_model('bitcoin_lstm_v0.h5')

当加载 Keras 库时,您可能会遇到以下警告:使用 TensorFlow 后端。Keras 可以配置为使用其他后端而不是 TensorFlow(即 Theano)。为了避免此消息,您可以创建一个名为keras.json的文件,并在其中配置其后端。该文件的正确配置取决于您的系统。因此,建议您访问 Keras 官方文档,了解相关主题:keras.io/backend/.

在本节中,我们学习了如何使用 Keras(TensorFlow 的接口)构建深度学习模型。我们研究了 Keras 的核心组件,并使用这些组件基于 LSTM 模型构建了第一个比特币价格预测系统的版本。

在我们接下来的章节中,我们将讨论如何将本章中的所有组件整合到一个(几乎完整的)深度学习系统中。该系统将产生我们第一次的预测,作为未来改进的起点。

从数据准备到建模

本节专注于深度学习系统的实现方面。我们将使用在选择正确的模型架构一节中的比特币数据,以及在使用 Keras 作为 TensorFlow 接口一节中的 Keras 知识,将这两个组件结合起来。本节通过构建一个系统来结束本章,该系统从磁盘读取数据,并将其作为一个整体输入模型。

训练神经网络

神经网络的训练可能需要较长时间。许多因素会影响该过程需要的时间。其中,有三个因素通常被认为是最重要的:

  • 网络架构

  • 网络有多少层和神经元

  • 用于训练过程的数据量

其他因素也可能大大影响网络的训练时间,但神经网络在解决业务问题时的优化大多数来源于探索这三点。

我们将使用上一节中的归一化数据。回想一下,我们已经将训练数据存储在一个名为train_dataset.csv的文件中。我们将使用 pandas 将该数据集加载到内存中,以便进行简便的探索:

    import pandas as pd
    train = pd.read_csv('data/train_dataset.csv')

图 17:显示从train_dataset.csv文件加载的训练数据集的前五行的表格

我们将使用来自变量close_point_relative_normalization的系列数据,这是一组归一化的比特币收盘价序列(来自变量 close),自 2016 年初以来的数据。

变量close_point_relative_normalization是基于每周归一化的。每个观测值都相对于该周期间第一天的收盘价差异进行归一化。这个归一化步骤很重要,它将帮助我们的网络更快地训练。

图 18:展示从归一化变量 close_point_relative_normalization 中绘制的时间序列图。这个变量将用于训练我们的 LSTM 模型。

调整时间序列数据

神经网络通常处理向量和张量,这些都是组织数据的数学对象,它们在多个维度上组织数据。在 Keras 中实现的每个神经网络都会根据规格组织一个向量或张量作为输入。一开始,理解如何将数据调整为给定层所期望的格式可能会令人困惑。为了避免混淆,建议从一个尽可能简单的网络开始,然后逐步添加组件。Keras 的官方文档(在部分)对学习每种层的要求非常重要。

Keras 官方文档可以在 keras.io/layers/core/ 获取。该链接直接将您带到 Layers 部分。

NumPy 是一个流行的 Python 库,用于执行数值计算。深度学习社区使用它来操作向量和张量,并将它们为深度学习系统做准备。特别是,numpy.reshape() 方法在调整数据以适应深度学习模型时非常重要。该方法允许操作 NumPy 数组,这些数组是 Python 对象,类似于向量和张量。

我们现在使用 2016 和 2017 年的周数据来组织来自 close_point_relative_normalization 变量的价格。我们将数据分成不同的组,每组包含七个观察值(每个周的一天),总共有 77 个完整的周。

我们这样做是因为我们对预测一周的交易价格感兴趣。

我们使用 ISO 标准来确定一周的开始和结束时间。其他类型的组织方式也是完全可能的。这种方式简单且直观,但仍有改进的空间。

LSTM 网络使用三维张量。每个维度都代表网络的一个重要属性。这些维度是:

  • 周期长度:周期长度,即每个周期中有多少观察值

  • 周期数量:数据集中可用的周期数量

  • 特征数量:数据集中可用的特征数量

我们从变量 close_point_relative_normalization 获取的数据目前是一个一维向量——我们需要将其调整为符合这三个维度的格式。

我们将使用一周的时间周期。因此,我们的周期长度是七天(周期长度 = 7)。我们的数据中有 77 个完整的周。我们将在训练期间使用最后一个周进行模型测试。这使得我们剩下 76 个不同的周(周期数量 = 76)。最后,我们将在这个网络中使用单一的特征(特征数量 = 1)——我们将在未来的版本中加入更多的特征。

我们如何重塑数据以匹配这些维度?我们将使用基础 Python 属性和来自 numpy 库的 reshape() 函数的组合。首先,我们用纯 Python 创建 76 个不同的周组,每个周组有七天:

     group_size = 7
     samples = list()
     for i in range(0, len(data), group_size):
     sample = list(data[i:i + group_size])
     if len(sample) == group_size:
     samples.append(np.array(sample).reshape(group_size, 1).tolist())

     data = np.array(samples) 

代码片段 3:创建不同周组的 Python 代码片段

结果变量数据是一个包含所有正确维度的变量。Keras 的 LSTM 层期望这些维度按特定顺序组织:特征数量、观测数量和周期长度。

让我们重塑数据集以匹配该格式:

     X_train = data[:-1,:].reshape(1, 76, 7)
     Y_validation = data[-1].reshape(1, 7)

代码片段 4:创建不同周组的 Python 代码片段

每个 Keras 层都会期望其输入以特定的方式组织。然而,Keras 通常会根据需要重新塑形数据。每次添加新层或遇到维度问题时,都应参考 Keras 层的文档 (keras.io/layers/core/)。

代码片段 4 还将我们数据集中的最后一周选择为验证集(via data[-1])。我们将尝试使用前 76 周的数据预测数据集中的最后一周。接下来的步骤是使用这些变量来拟合我们的模型:

      model.fit(x=X_train, y=Y_validation, epochs=100) 

代码片段 5:展示如何训练我们的模型

LSTM 是计算开销较大的模型。在现代计算机上,训练我们的数据集可能需要几分钟时间。大部分时间都花费在计算的开始阶段,当时算法创建完整的计算图。训练开始后,速度会逐渐提升:

图 19:显示在每个 epoch 评估的损失函数结果的图形

这比较了模型在每个 epoch 中的预测结果,然后使用均方误差技术与实际数据进行比较。此图显示了这些结果。

一目了然,我们的网络表现得非常好:它从一个非常小的误差率开始,并持续减少。那么,我们的预测结果告诉我们什么呢?

进行预测

在我们的网络训练完成后,我们现在可以进行预测。我们将对超出时间范围的未来一周进行预测。

一旦我们通过 model.fit() 训练了我们的模型,做出预测就变得非常简单:

     model.predict(x=X_train)

代码片段 6:使用之前用于训练的相同数据进行预测

我们使用与训练数据相同的数据进行预测(即 X_train 变量)。如果我们有更多数据可用,我们可以使用这些数据,只要我们将其重新塑形为 LSTM 所要求的格式。

过拟合

当神经网络在验证集上发生过拟合时,意味着它学会了训练集中存在的模式,但无法将其推广到未见过的数据(例如,测试集)。在下一章中,我们将学习如何避免过拟合,并创建一个系统来评估我们的网络并提升其性能:

图 20:去归一化后,我们的 LSTM 模型预测 2017 年 7 月底,比特币的价格将从 2200 美元上涨到约 2800 美元,单周上涨 30%

活动:组装深度学习系统

在本活动中,我们将所有构建基础深度学习系统的关键元素汇集在一起:数据、模型和预测。

我们将继续使用 Jupyter Notebooks,并将使用之前练习中准备的数据(data/train_dataset.csv)以及我们本地存储的模型(bitcoin_lstm_v0.h5)。

  1. 启动 Jupyter Notebook 实例后,导航到名为Activity_5_Assembling_a_Deep_Learning_System.ipynb的 Notebook 并打开它。从标题开始执行单元格以加载所需的组件,然后导航到标题数据整形

图 21:展示归一化变量close_point_relative_normalization的时间序列图

close_point_relative_normalization变量将用于训练我们的 LSTM 模型。

我们将从加载我们在之前活动中准备的数据集开始。我们使用 pandas 将该数据集加载到内存中。

  1. 使用 pandas 将训练数据集加载到内存中,如下所示:
      train = pd.read_csv('data/train_dataset.csv')
  1. 现在,通过执行以下命令,快速检查数据集:
      train.head()

正如本章所解释的,LSTM 网络需要三维张量。这些维度是:周期长度、周期数和特征数。

现在,继续创建每周的分组,然后重新排列生成的数组以匹配这些维度。

  1. 随时使用提供的create_groups()函数来执行此操作:
       create_groups(data=train, group_size=7)

该函数的默认值为 7 天。如果你将该数字更改为其他值,比如 10,会发生什么呢?

现在,确保将数据分成两个集合:训练集和验证集。我们通过将比特币价格数据集的最后一周分配到评估集来实现这一点。然后,我们训练网络来评估这一最后一周的数据。

将训练数据的最后一周分离出来,并使用numpy.reshape()进行重塑。重塑非常重要,因为 LSTM 模型只接受这种格式的数据:

       X_train = data[:-1,:].reshape(1, 76, 7)
       Y_validation = data[-1].reshape(1, 7)

我们的数据现在已经准备好用于训练。现在我们加载之前保存的模型,并用给定的 epochs 数量训练它。

  1. 导航到标题加载我们的模型,并加载我们之前训练的模型:
      model = load_model('bitcoin_lstm_v0.h5')
  1. 现在,用我们的训练数据X_trainY_validation来训练该模型:
      history = model.fit(
      x=X_train, y=Y_validation,
      batch_size=32, epochs=100)

请注意,我们将模型的日志存储在名为 history 的变量中。这些日志对于探索模型训练准确率的具体变化,以及理解损失函数的表现非常有用:

图 22:Jupyter Notebook 部分,我们加载了之前的模型,并用新数据训练它

最后,让我们用训练好的模型进行预测。

  1. 使用相同的data X_train,调用以下方法:
      model.predict(x=X_train)
  1. 模型立即返回一个标准化值的列表,包含未来七天的预测数据。使用 denormalize() 函数将数据转化为美元值。请使用最新的可用值作为参考来调整预测结果:
       denormalized_prediction = denormalize(predictions, last_weeks_value)

图 23:Jupyter Notebook 部分,展示了我们预测未来七天比特币价格的过程。

我们的预测表明,比特币价格可能会大约上涨 30%。

图 24:使用我们刚刚建立的 LSTM 模型预测未来七天的比特币价格走势。

我们在这张图中结合了两个时间序列:真实数据(线之前)和预测数据(线之后)。该模型展示的方差与之前看到的模式类似,并且它暗示未来七天内可能会有价格上涨。

  1. 完成实验后,使用以下命令保存你的模型:
      model.save('bitcoin_lstm_v0_trained.h5')

我们将保存这个训练好的网络以供将来参考,并与其他模型的表现进行比较。

网络可能已经从我们的数据中学习到了一些模式,但它是如何在如此简单的架构和如此少的数据下做到这一点的呢?LSTM 是一种从数据中学习模式的强大工具。然而,我们将在接下来的课程中学习到,它们也可能会遭遇 过拟合 问题,这是神经网络中常见的现象,其中模型学习到了训练数据中的一些模式,但这些模式在预测现实世界的数据时并没有什么用处。我们将学习如何处理这一问题,并改进我们的网络以进行有用的预测。

总结

在本章中,我们已经组装了一个完整的深度学习系统:从数据到预测。此活动中创建的模型需要进行多次改进才能算得上有用。然而,它为我们提供了一个很好的起点,之后我们将不断改进。

我们的下一章将探索评估模型性能的技术,并继续进行修改,直到我们得到一个既有用又健壮的模型。

第六章:模型评估与优化

本章重点讨论如何评估神经网络模型。与其他类型的模型不同,使用神经网络时,我们会调整网络的超参数以提高其性能。然而,在修改任何参数之前,我们需要先衡量模型的表现。

到本章结束时,你将能够:

  • 评估模型

    • 探索神经网络处理的不同类型问题

    • 探索损失函数、准确率和错误率

    • 使用 TensorBoard

    • 评估指标和技术

  • 超参数优化

    • 添加层和节点

    • 探索并添加训练轮次

    • 实现激活函数

    • 使用正则化策略

模型评估

在机器学习中,常常定义两个不同的术语:参数和超参数。参数是影响模型如何从数据中做出预测的属性。超参数则是指模型如何从数据中学习。参数可以从数据中学习并动态修改,而超参数则是更高级的属性,通常不会从数据中学习。如需更详细的概述,请参考 Sebastian Raschka 和 Vahid Mirjalili 所著的《Python 机器学习》(Packt,2017 年)。

问题分类

通常,神经网络解决的问题分为两类:分类和回归。分类问题是关于从数据中预测正确的类别;例如,温度是热还是冷。回归问题则是关于预测连续标量中的值;例如,实际的温度值是多少?

这两类问题的特点如下:

  • 分类:以类别为特征的问题。类别可以不同,也可以相同;它们还可以是二分类问题。然而,每个数据元素必须清晰地被分配到某个类别。一个分类问题的例子是,使用卷积神经网络为图像分配标签 非车。在第四章《神经网络与深度学习简介》中探索的 MNIST 示例是另一个分类问题的例子。

  • 回归:以连续变量(即标量)为特征的问题。这些问题通过范围来衡量,并评估网络与真实值的接近程度。例如,一个时间序列分类问题,其中使用循环神经网络预测未来的温度值。比特币价格预测问题是另一个回归问题的例子。

尽管评估这两类问题模型的整体结构相同,但我们会采用不同的技术来评估模型的表现。在接下来的部分中,我们将探讨分类或回归问题的评估技术。

本章中的所有代码片段都实现于活动 6 和 7。欢迎跟着一起做,但不必感到强制性,因为它们将在活动中更详细地重复。

损失函数、准确度和误差率

神经网络利用衡量网络与验证集(即从数据中分离出来作为训练过程一部分的部分数据)比较时的表现的函数。这些函数称为损失函数

损失函数评估神经网络预测的错误程度;然后它们会将这些错误反向传播并调整网络,改变单个神经元的激活方式。损失函数是神经网络的关键组件,选择合适的损失函数对网络性能有着重要影响。

错误是如何传播到网络中的每个神经元的?

错误通过一个称为反向传播的过程进行传播。反向传播是一种将损失函数返回的错误传播到神经网络中每个神经元的技术。传播的错误会影响神经元的激活方式,最终影响该网络的输出。

包括 Keras 在内的许多神经网络包默认使用此技术。

如需了解更多关于反向传播的数学内容,请参考深度学习(作者:Ian Goodfellow 等,麻省理工学院出版社,2016 年)。

我们对回归和分类问题使用不同的损失函数。对于分类问题,我们使用准确率函数(即预测正确的比例)。而对于回归问题,我们使用误差率(即预测值与观测值的接近程度)。

下表提供了常见损失函数的总结,并列出了它们的常见应用:

问题类型损失函数问题示例

| 回归 | 均方误差(MSE)

| 预测一个连续函数。即在一系列值的范围内预测值。

| 使用过去的温度测量预测未来的温度。

|

回归均方根误差(RMSE)与前面相同,但处理负值。RMSE 通常提供更具可解释性的结果。与前面相同。

| 回归 | 平均绝对百分比误差

(MAPE)

| 预测连续函数。与去归一化范围工作时,表现更好。 | 使用产品属性(例如,价格、类型、目标受众、市场条件)预测产品的销售。

|

| 分类 | 二元交叉熵 | 对两个类别或两者之间的分类

值(即,truefalse)。

| 根据网站访问者的浏览器活动预测其性别。

|

| 分类 | 分类交叉熵

| 从已知类别集中分类多类问题

分类的类别数。

根据说话者的口音预测其国籍,条件是说出一段英语句子。

对于回归问题,MSE 函数是最常用的选择。而对于分类问题,二元交叉熵(用于二分类问题)和多类别交叉熵(用于多分类问题)是常见的选择。建议从这些损失函数开始,然后在神经网络的发展过程中,尝试其他函数,以提升性能。

对于回归问题,MSE 函数是最常用的选择。而对于分类问题,二元交叉熵(用于二分类问题)和多类别交叉熵(用于多分类问题)是常见的选择。建议从这些损失函数开始,然后在神经网络的发展过程中,尝试其他函数,以提升性能。

我们在第五章中开发的网络使用 MSE 作为其损失函数。在接下来的章节中,我们将探讨该函数在网络训练过程中的表现。

不同的损失函数,相同的架构

在继续进入下一节之前,让我们从实践角度探讨一下,这些问题在神经网络中的差异。

TensorFlow Playground 应用程序由 TensorFlow 团队提供,帮助我们理解神经网络是如何工作的。在这里,我们看到一个神经网络,其中的层包括:输入层(左侧)、隐藏层(中间)和输出层(右侧)。

我们还可以选择不同的样本数据集进行实验,位于最左侧。最后,在最右侧,我们看到网络的输出。

图 1:TensorFlow Playground 网页应用程序。在这个可视化中,使用神经网络的参数来获取一些

直观地了解每个参数如何影响模型结果。

该应用程序帮助我们探索在前一节中讨论的不同问题类别。当我们选择分类作为问题类型(右上角)时,数据集中的点只有两种颜色值:蓝色或橙色。

当我们选择回归时,点的颜色会在橙色和蓝色之间的色值范围内变化。在处理分类问题时,网络根据错误的蓝色和橙色数量来评估其损失函数;在处理分类问题时,它会检查每个点距离正确色值的远近,如下图所示:

图 2:TensorFlow Playground 应用程序的细节。不同的色值分配给点,

这取决于问题类型。

点击播放按钮后,我们会注意到训练损失区域的数字随着网络不断训练而持续下降。每个问题类别中的数字非常相似,因为损失函数在两个神经网络中扮演着相同的角色。然而,每个类别所使用的实际损失函数是不同的,且根据问题类型选择。

使用 TensorBoard

评估神经网络是 TensorBoard 的强项。如同在Chapter 4*《神经网络与深度学习简介》中所解释的,TensorBoard 是随 TensorFlow 一起提供的一套可视化工具。其中之一的功能是,在每个 epoch 后,可以探索损失函数评估结果。TensorBoard 的一个伟大特点是,用户可以单独组织每次运行的结果,并比较每次运行的损失函数指标。之后,用户可以决定需要调整哪些超参数,并对网络的表现有一个大致的了解。最棒的是,这一切都可以实时完成。

为了在我们的模型中使用 TensorBoard,我们将使用 Keras 回调函数。我们通过导入 TensorBoard 回调函数,并在调用 fit() 函数时将其传递给模型。以下代码展示了如何在我们之前章节中创建的比特币模型中实现:

    from keras.callbacks import TensorBoard
    model_name = 'bitcoin_lstm_v0_run_0'
    tensorboard = TensorBoard(log_dir='./logs/{}'.format(model_name))
    model.fit(x=X_train, y=Y_validate,
    batch_size=1, epochs=100,
    verbose=0, callbacks=[tensorboard])

代码片段 1:在我们的 LSTM 模型中实现 TensorBoard 回调函数的代码片段

Keras 回调函数在每个 epoch 运行结束时被调用。在这种情况下,Keras 调用 TensorBoard 回调函数,将每次运行的结果存储到磁盘中。还有许多其他有用的回调函数可以使用,用户也可以使用 Keras API 创建自定义回调函数。

更多信息请参考 Keras 回调文档(keras.io/callbacks/)。

在实现 TensorBoard 回调函数后,loss 函数的指标现在可以在 TensorBoard 界面中查看。你可以运行 TensorBoard 进程(with tensorboard --logdir=./logs)并在训练网络时保持它运行(使用 fit())。评估的主要图形通常称为损失。用户可以通过将已知指标传递给 fit() 函数中的 metrics 参数来添加更多指标,这些指标将在 TensorBoard 中进行可视化,但不会用于调整网络权重。

交互式图形将继续实时更新,帮助你理解每个 epoch 中发生的情况。

图 3:TensorBoard 实例的截图,显示了损失函数结果以及添加到指标参数中的其他指标

实现模型评估指标

在回归和分类问题中,我们将输入数据集拆分为三个数据集:训练集、验证集和测试集。训练集和验证集用于训练网络。训练集作为输入提供给网络,验证集由损失函数用来将神经网络的输出与真实数据进行比较,并计算预测的误差。最后,测试集在网络训练完毕后用于评估网络在未见过的数据上的表现。

并没有明确的规则来确定训练集、验证集和测试集应如何划分。常见的方法是将原始数据集划分为 80% 的训练集和 20% 的测试集,然后将训练集进一步划分为 80% 的训练集和 20% 的验证集。有关此问题的更多信息,请参考 Sebastian Raschka 和 Vahid Mirjalili 合著的《Python 机器学习》(Packt,2017)。

在分类问题中,您将数据和标签作为相关但不同的数据传递给神经网络。网络随后学习数据如何与每个标签相关。在回归问题中,您不传递数据和标签,而是将感兴趣的变量作为一个参数传递,将用于学习模式的变量作为另一个参数。Keras 为这两种用例提供了接口,即 fit() 方法。请参见 代码片段 2 了解示例:

    model.fit(x=X_train, y=Y_train,
    batch_size=1, epochs=100,
    verbose=0, callbacks=[tensorboard],
    validation_split=0.1,
    validation_data=(X_validation, Y_validation))

代码片段 2:演示如何使用 validation_splitvalidation_data 参数的代码片段

fit() 方法可以使用 validation_splitvalidation_data 参数,但不能同时使用这两个参数。

损失函数评估模型的进展并在每次运行时调整其权重。然而,损失函数仅描述训练数据和验证数据之间的关系。为了评估模型是否正确执行,我们通常使用第三组数据——即未用于训练网络的数据——并将我们模型的预测结果与该数据集中的实际值进行比较。

这就是测试集的作用。Keras 提供了 model.evaluate() 方法,使得将训练好的神经网络与测试集进行评估的过程变得简单。请参见以下代码了解示例:

     model.evaluate(x=X_test, y=Y_test)

代码片段 3:演示如何使用 evaluate() 方法的代码片段

evaluate() 方法返回损失函数的结果以及传递给 metrics 参数的函数结果。在比特币问题中,我们将频繁使用该方法来测试模型在测试集上的表现。

你会注意到,比特币模型看起来与上述示例有些不同。这是因为我们使用了 LSTM 架构。LSTM 被设计用来预测序列。因此,我们不使用一组变量来预测另一个单一变量——即使它是回归问题。相反,我们使用单一变量(或一组变量)的先前观察值来预测该变量(或变量组)未来的观察值。在Keras.fit()y参数包含了与x参数相同的变量,只不过是预测的序列。

评估比特币模型

我们在Chapter 4*中创建了一个测试集,神经网络与深度学习简介。该测试集包含了 19 周的比特币每日价格观察数据,相当于原始数据集的约 20%。

我们还使用数据集的其他 80%(即包含 56 周数据的训练集,减去一个用于验证集的数据)在Chapter 5*中训练了神经网络,模型架构,并将训练好的网络存储在磁盘上(bitcoin_lstm_v0)。现在,我们可以在测试集的每一周(共 19 周)使用evaluate()方法,查看该神经网络的表现。

然而,要做到这一点,我们必须提供前 76 周的数据。这是因为我们的网络被训练为通过连续的 76 周数据预测一个周的数据(我们将在Chapter 7中重新训练我们的网络,以使用更长时间的数据周期,并在产品化*章节中讨论将神经网络部署为 Web 应用程序时处理此行为):

    combined_set = np.concatenate((train_data, test_data), axis=1)
        evaluated_weeks = []
        for i in range(0, validation_data.shape[1]):
        input_series = combined_set[0:,i:i+77]

       X_test = input_series[0:,:-1].reshape(1, input_series.shape[1] - 1,)
       Y_test = input_series[0:,-1:][0]

       result = B.model.evaluate(x=X_test, y=Y_test, verbose=0)
       evaluated_weeks.append(result)

Snippet 4:实现evaluate()方法以评估我们模型在测试数据集上表现的代码片段

在前面的代码中,我们使用 Keras 的model.evaluate()评估每一周的数据,然后将输出存储在变量evaluated_weeks中。接着,我们将每周的 MSE 结果绘制在下图中:

图 4:测试集每周的 MSE;注意,在第 5 周,模型预测的结果比其他任何一周都要差。

我们模型的 MSE 结果表明,大多数周我们的模型表现良好,除了第 5 周,其 MSE 值上升至约0.08。除了第 5 周,我们的模型似乎在几乎所有其他测试周的表现都很好。

过拟合

我们的第一个训练过的网络(bitcoin_lstm_v0)可能正遭受一种叫做过拟合的现象。过拟合是指模型在训练时优化验证集,但这样做牺牲了从我们感兴趣的现象中提取更具普适性的模式。过拟合的主要问题是,模型学会了如何预测验证集数据,但无法预测新的数据。

我们模型中使用的损失函数在训练结束时达到了非常低的水平(约为 2.9 * 10^-6)。不仅如此,这个过程发生得很早:用于预测数据中最后一周的 MSE 损失函数在大约第 30 个训练周期时降低到了一个稳定的水平。这意味着我们的模型几乎完美地预测了第 77 周的数据,使用了前 76 周的数据。难道这可能是过拟合的结果吗?

我们再看一下图 4。我们知道我们的 LSTM 模型在验证集上达到了极低的值(约为 2.9 * 10^-6),但它在测试集上也达到了较低的值。然而,关键的区别在于尺度。我们测试集中每一周的 MSE 大约比验证集高出 4,000 倍(平均而言)。这意味着模型在我们的测试数据上的表现比在验证集上要差得多。这一点值得关注。

然而,尺度掩盖了我们 LSTM 模型的能力:即使在测试集上的表现要差得多,预测的 MSE 误差仍然非常非常低。这表明我们的模型可能正在从数据中学习到模式。

模型预测

一方面是通过比较 MSE 误差来衡量我们的模型,另一方面是能够直观地解释其结果。

使用相同的模型,接下来我们将使用 76 周的数据作为输入,为接下来的几周生成一系列预测。我们通过将 76 周的滑动窗口应用到完整的数据序列上(即训练集加测试集),并为每个窗口做出预测来实现。预测是通过 Keras 的model.predict()方法完成的:

    combined_set = np.concatenate((train_data, test_data), axis=1)

        predicted_weeks = []
        for i in range(0, validation_data.shape[1] + 1):
        input_series = combined_set[0:,i:i+76]
        predicted_weeks.append(B.predict(input_series))

代码片段 5:使用model.predict()方法为测试数据集中的所有周做出预测的代码片段

在前面的代码中,我们使用model.predict()进行预测,然后将这些预测存储在predicted_weeks变量中。接着我们绘制了结果预测图,得到了以下图形:

图 5:测试集中每一周的 MSE。请注意,在第 5 周,模型的预测比其他任何一周都要差。

我们模型的结果(如图 5所示)表明,它的表现并没有那么糟糕。通过观察预测线的模式,可以发现网络已经识别出一个每周波动的模式,其中标准化的价格在周中会上升,然后在周末下降。除了几个星期——最显著的是第 5 周,与我们之前的 MSE 分析相同——大多数周的数据都接近正确值。

现在让我们去归一化预测结果,以便使用与原始数据相同的尺度(即美元)来调查预测值。我们可以通过实现一个去归一化函数来做到这一点,该函数利用预测数据中的日期索引来识别测试数据中相应的一周。确定该周后,函数会取该周的第一个值,并使用该值通过倒置的点相对归一化技术去归一化预测值:

    def denormalize(reference, series,

    normalized_variable='close_point_relative_normalization',
    denormalized_variable='close'):
    week_values = observed[reference['iso_week']==series['iso_week'].
    values[0]]
    last_value = week_values[denormalized_variable].values[0]
    series[denormalized_variable] = 
    last_value*(series[normalized_variable]+1)

    return series

    predicted_close = predicted.groupby('iso_week').apply
    (lambda x: denormalize(observed, x))

代码片段 6:使用倒置的点相对归一化技术对数据进行去归一化处理。denormalize()函数取自测试集第一天的第一个收盘价,作为与之对应的那一周的数据。

我们的结果现在通过美元与测试集进行对比。如图 5 所示,bitcoin_lstm_v0 模型在预测未来七天比特币价格方面表现得相当不错。但是,我们如何用易于理解的方式来衡量这个表现呢?

图 6:测试集中每周的均方误差(MSE);注意到在第 5 周,模型预测的结果比其他任何一周都要差。

解释预测

我们的最后一步是为我们的预测增加可解释性。图 6 显示我们的模型预测与测试数据相对接近,但到底有多接近呢?

Keras 的 model.evaluate() 函数对于理解模型在每次评估步骤中的表现非常有用。然而,鉴于我们通常使用归一化数据集来训练神经网络,model.evaluate() 方法生成的指标也很难解释。

为了解决这个问题,我们可以收集模型的完整预测集,并使用 表 1 中的另外两个更容易解释的函数将其与测试集进行比较:分别是 mape()rmse(),它们分别表示 MAPE 和 RMSE:

    def mape(A, B):
    return np.mean(np.abs((A - B) / A)) * 100

    def rmse(A, B):
    return np.sqrt(np.square(np.subtract(A, B)).mean())

代码片段 7mape()rmse() 函数的实现

这些函数是使用 NumPy 实现的。原始实现来自 stats.stackexchange.com/questions/58391/mean-absolute-percentage-error-mape-in-scikit-learn(MAPE)和 stackoverflow.com/questions/16774849/mean-squared-error-in-numpy(RMSE)。

在使用这两个函数将我们的测试集与预测结果进行比较后,我们得到了以下结果:

  • 去归一化后的 RMSE:$399.6

  • 去归一化后的 MAPE:8.4%

这意味着我们的预测与真实数据的差异平均约为 $399。这相当于与实际比特币价格的差异大约为 8.4%。

这些结果有助于理解我们的预测。我们将继续使用model.evaluate()方法来跟踪我们的 LSTM 模型如何改进,同时也会计算每个版本模型在完整系列上的rmse()mape(),以解释我们在预测比特币价格时的准确度。

活动:创建一个主动的训练环境

在此活动中,我们为神经网络创建了一个训练环境,促进其训练和评估。这个环境对于下一章尤为重要,在那一章中,我们将寻找最佳的超参数组合。

首先,我们将启动一个 Jupyter Notebook 实例和一个 TensorBoard 实例。接下来的活动中,这两个实例可以保持打开状态。

  1. 使用终端,导航到目录chapter_6/activity_6,并执行以下代码以启动 Jupyter Notebook 实例:
      $ jupyter notebook
  1. 在浏览器中打开应用程序提供的 URL,并打开名为Activity_6_Creating_an_active_training_environment.ipynb的 Jupyter Notebook:

图 7:Jupyter Notebook 中高亮显示的“评估 LSTM 模型”部分

  1. 同时,使用终端启动一个 TensorBoard 实例,执行以下命令:
      $ cd ./chapter_3/activity_6/
      $ tensorboard --logdir=logs/
  1. 打开屏幕上出现的 URL,并保持该浏览器标签页打开。

  2. 现在,将训练集(train_dataset.csv)和测试集(test_dataset.csv)以及我们之前编译的模型(bitcoin_lstm_v0.h5)加载到 Notebook 中。

  3. 使用以下命令将训练集和测试集加载到 Jupyter Notebook 实例中:

      $ train = pd.read_csv('data/train_dataset.csv')
      $ test = pd.read_csv('data/test_dataset.csv') 
  1. 此外,使用以下命令加载我们之前编译的模型:
      $ model = load_model('bitcoin_lstm_v0.h5')

现在,让我们评估模型在测试数据上的表现。我们的模型使用 76 周的数据来预测未来一周的情况,即接下来的七天。当我们构建第一个模型时,我们将原始数据集分为训练集和测试集。现在,我们将合并这两个数据集(我们称之为合并集),并滑动一个 76 周的窗口。在每个窗口中,我们执行 Keras 的model.evaluate()方法,评估网络在该特定周的表现。

  1. 执行“评估 LSTM 模型”标题下的单元格。这些单元格的关键概念是对测试集中的每一周调用model.evaluate()方法。以下这一行是最重要的:
       $ result = model.evaluate(x=X_test, y=Y_test, verbose=0) 
  1. 每个评估结果现在存储在变量evaluated_weeks中。这个变量是一个简单的数组,包含测试集中每一周的 MSE 预测结果。现在可以继续绘制这些结果:

如我们在章节中讨论的那样,MSE 损失函数很难解释。为了便于理解模型的表现,我们还会对测试集中的每一周调用model.predict()方法,并将其预测结果与实际值进行比较。

  1. 导航到解释模型结果部分并执行做出预测子标题下的代码单元格。请注意,我们正在调用model.predict()方法,但使用的是稍有不同的参数组合。我们只使用X,而不是同时使用XY值:
      predicted_weeks = []
      for i in range(0, test_data.shape[1]):
      input_series = combined_set[0:,i:i+76]
      predicted_weeks.append(model.predict(input_series)) 

在每个窗口,我们将对下一周进行预测并存储结果。我们现在可以将归一化结果与测试集中的归一化值进行比较,如下图所示:

图 9:绘制从*model.predict()*返回的每周归一化值

我们也将进行相同的比较,但使用去归一化后的值。为了去归一化我们的数据,我们首先需要识别测试集和预测结果之间的等效周。然后,我们取该周的第一个价格值,并用它来反转第5 章中的基于点的归一化方程,模型架构

  1. 导航到标题“去归一化预测”并执行该标题下的所有单元格。

  2. 在这一部分,我们定义了denormalize()函数,它执行完整的去归一化过程。与其他函数不同,这个函数接受的是一个 Pandas DataFrame,而不是 NumPy 数组。我们这样做是为了使用日期作为索引。这是该部分标题下最相关的代码块:

      predicted_close = predicted.groupby('iso_week').apply(
         lambda x: denormalize(observed, x))

我们的去归一化结果(如以下图所示)显示,我们的模型做出的预测与实际比特币价格非常接近。但到底有多接近呢?

图 10:绘制从model.predict()返回的每周去归一化值

LSTM 网络使用均方误差(MSE)值作为其损失函数。然而,正如在章节中讨论的,MSE 值难以解释。为了解决这个问题,我们实现了两个函数(从script utilities.py中加载),它们分别实现了 RMSE 和 MAPE 函数。这些函数通过返回与我们原始数据使用相同量纲的度量,并通过将量纲差异作为百分比进行比较,从而为我们的模型增加了解释性。

  1. 导航到“去归一化预测”标题并从utilities.py脚本中加载两个函数:
      from scripts.utilities import rmse, mape 

脚本中的函数实际上非常简单:

      def mape(A, B):
      return np.mean(np.abs((A - B) / A)) * 100

      def rmse(A, B):
      return np.sqrt(np.square(np.subtract(A, B)).mean())

每个函数都是通过 NumPy 的向量化操作来实现的。它们在相同长度的向量上运行良好。它们被设计用于应用于完整的结果集。

使用mape()函数,我们现在可以理解,我们的模型预测结果与测试集的价格相差大约 8.4%。这相当于根均方误差(使用rmse()函数计算)大约为 399.6 美元。

在进入下一节之前,回到 Notebook 中找到标题为使用 TensorBoard 重新训练模型的部分。你可能已经注意到我们创建了一个名为train_model()的辅助函数。这个函数是我们模型的封装器,它训练(using model.fit())我们的模型,并将其结果存储在一个新的目录下。TensorBoard 随后将这些结果作为判别器,显示不同模型的统计数据。

  1. 请修改传递给model.fit()函数的一些参数值(例如试试 epochs)。现在,运行从磁盘加载模型到内存的单元(这将替换你训练过的模型):
      model = load_model('bitcoin_lstm_v0.h5') 
  1. 现在,再次运行train_model()函数,但使用不同的参数,表示一个新的运行版本:
      train_model(X=X_train, Y=Y_validate, version=0, run_number=0)

在本节中,我们学习了如何使用损失函数评估网络。我们了解到,损失函数是神经网络的关键元素,它们评估网络在每个 epoch 的表现,并且是将调整回传到层和节点的起点。我们还探讨了为什么一些损失函数可能难以解释(例如 MSE),并通过使用另外两个函数——RMSE 和 MAPE——来解释我们 LSTM 模型的预测结果。

最重要的是,本章以一个主动训练环境作为结尾。我们现在拥有一个能够持续训练深度学习模型并评估其结果的系统。这将在我们下一节优化网络时发挥关键作用。

超参数优化

我们已经训练了一个神经网络,利用前 76 周的比特币价格预测接下来七天的比特币价格。平均来说,这个模型给出的预测值与实际比特币价格之间的误差约为 8.4%。

本节描述了提高神经网络模型性能的常见策略:

  • 添加或移除层并更改节点数量

  • 增加或减少训练的 epoch 次数

  • 尝试不同的激活函数

  • 使用不同的正则化策略

我们将使用到目前为止在模型评估部分开发的相同主动学习环境来评估每个修改,衡量这些策略如何帮助我们开发出更精确的模型。

层和节点 - 添加更多层

单隐层神经网络在许多问题上可以表现得相当好。我们的第一个比特币模型(bitcoin_lstm_v0)就是一个很好的例子:它使用单个 LSTM 层,能够预测接下来七天的比特币价格(来自测试集),误差率约为 8.4%。然而,并不是所有问题都能用单层模型建模。

你要预测的函数越复杂,你需要添加更多层的可能性就越高。判断是否应该添加新层的一个好直觉是了解它们在神经网络中的作用。

每一层都会创建输入数据的模型表示。链条中的早期层创建较低级别的表示,而后期层则创建更高级别的表示。

虽然这个描述可能难以转化为现实世界的问题,但它的实际直觉很简单:在处理具有不同表示级别的复杂函数时,你可能想要尝试添加层。

添加更多节点

层所需的神经元数量与输入和输出数据的结构有关。

例如,如果你正在将一张 4 x 4 像素的图像分类到两个类别中的一个,你可以从一个具有 12 个神经元的隐藏层开始(每个神经元对应一个像素),然后再加一个只有两个神经元的输出层(每个神经元对应一个预测类别)。

在添加新层时,通常会添加新的神经元。然后,可以添加一个层,该层的神经元数量与前一层相同,或者是前一层神经元数量的倍数。例如,如果你的第一个隐藏层有 12 个神经元,你可以尝试添加一个第二层,它的神经元数量可以是 12、6 或 24。

添加层和神经元可能会导致性能的显著限制。可以随意尝试添加层和节点。通常的做法是从较小的网络开始(即网络中有少量的层和神经元),然后根据其性能的提升逐渐增长。

如果上面的内容听起来不够精确,你的直觉是对的。引用 YouTube 前视频分类负责人 Aurélien Géron 的话,找到合适数量的神经元仍然有些像黑魔法

《动手学机器学习》 by Aurelién Géron,O'Reilly 出版,2017 年 3 月。

最后,提醒一句:你添加的层越多,你需要调整的超参数也就越多——并且训练网络所需的时间也会更长。如果你的模型表现不错,并且没有对数据过拟合,可以在添加新层之前,先尝试本章中提到的其他策略。

层和节点 - 实现

我们将通过添加更多层来修改我们原来的 LSTM 模型。在 LSTM 模型中,通常会按顺序添加 LSTM 层,在 LSTM 层之间建立链条。在我们的案例中,新的 LSTM 层具有与原始层相同的神经元数量,因此我们不需要配置该参数。

我们将修改后的模型命名为bitcoin_lstm_v1。将每个尝试不同超参数配置的模型命名为不同的名称是一个好习惯。这有助于你跟踪每种不同架构的表现,并在 TensorBoard 中轻松比较模型之间的差异。我们将在本章末尾比较所有不同的修改过的架构。

在添加新的 LSTM 层之前,我们需要将第一个 LSTM 层的 return_sequences 参数修改为 True。这样做是因为第一个层期望的数据输入是一个序列,这与第一个层的数据输入格式相同。当这个参数设置为 False 时,LSTM 层会输出不兼容的预测参数。

考虑以下代码示例:

    period_length = 7
    number_of_periods = 76
    batch_size = 1

    model = Sequential()
    model.add(LSTM(
        units=period_length,
        batch_input_shape=(batch_size, number_of_periods, period_length),
        input_shape=(number_of_periods, period_length),
        return_sequences=True, stateful=False))

    model.add(LSTM(
        units=period_length,
        batch_input_shape=(batch_size, number_of_periods, period_length),
        input_shape=(number_of_periods, period_length),
        return_sequences=False, stateful=False))

    model.add(Dense(units=period_length))
    model.add(Activation("linear"))

    model.compile(loss="mse", optimizer="rmsprop") 

代码片段 8:向原始 bitcoin_lstm_v0 模型添加第二个 LSTM 层,使其变为 bitcoin_lstm_v1

轮次

轮次是网络在响应数据传递和损失函数时调整权重的次数。运行更多轮次的模型可以让它从数据中学习更多,但也会面临过拟合的风险。

在训练模型时,建议以指数方式增加轮次,直到损失函数开始趋于平稳。对于 bitcoin_lstm_v0 模型,其损失函数大约在 100 个轮次时趋于平稳。

我们的 LSTM 模型使用的数据量较小,因此增加训练轮次对性能几乎没有显著影响。例如,如果尝试在 103 个轮次下训练该模型,模型几乎没有任何改进。但如果训练的模型使用的是大量数据,情况则会有所不同。在这种情况下,大量的轮次对模型的良好性能至关重要。

我建议你使用以下关联:用于训练模型的数据量越大,所需的轮次就越多,以实现良好的性能。

轮次 - 实现

我们的比特币数据集相对较小,因此增加模型训练的轮次可能对性能的提升影响不大。为了让模型训练更多轮次,只需在 model.fit() 中更改轮次参数:

    number_of_epochs = 10**3
    model.fit(x=X, y=Y, batch_size=1,
        epochs=number_of_epochs,
        verbose=0,
    callbacks=[tensorboard]) 

代码片段 9:改变我们模型训练的轮次,使其变为bitcoin_lstm_v2

这个更改将我们的模型升级到了 v2,实际上使其变为 bitcoin_lstm_v2

激活函数

激活函数评估了需要激活每个神经元的程度。它们决定了每个神经元将传递给网络下一个元素的值,使用来自前一层的输入和损失函数的结果——或者决定神经元是否应当传递任何值。

激活函数是神经网络研究领域的一个重要话题。如果你想了解当前关于此主题的研究概况,以及对激活函数工作原理的更详细回顾,请参考 Ian Goodfellow 等人撰写的《Deep Learning》,MIT 出版社,2017 年。

TensorFlow 和 Keras 提供了许多激活函数——并且偶尔会新增一些。作为介绍,有三个函数非常重要,值得我们关注;我们将逐一探索它们。

本节内容深受 Avinash Sharma V 的文章 理解神经网络中的激活函数 启发,文章链接为:medium.com/the-theory-of-everything/understanding-activation-functions-in-neural-networks-9491262884e0

线性(恒等函数)

线性函数仅根据常数值激活神经元,其定义如下:

当 c = 1 时,神经元将按原样传递值,而不受激活函数的修改。使用线性函数的问题在于,由于神经元是线性激活的,链式层次现在作为一个大的单一层来工作。换句话说,失去了构建具有多层的网络的能力,其中一层的输出影响另一层:

图 11:线性函数示意图

线性函数的使用通常被认为在大多数网络中已经过时。

双曲正切(Tanh)

Tanh 是一个非线性函数,其公式如下:

这意味着它们对节点的影响是持续评估的。而且,由于其非线性特性,可以使用此函数改变一层如何影响链中下一层。当使用非线性函数时,各层以不同方式激活神经元,从数据中学习不同的表示变得更容易。然而,它们具有类似 Sigmoid 的模式,会反复惩罚极端的节点值,造成一种称为“梯度消失”的问题。梯度消失对网络学习能力产生负面影响:

图 12:tanh 函数示意图

Tanhs 是常用的选择,但由于其计算开销较大,通常会使用 ReLU 作为替代。

修正线性单元

ReLU 具有非线性特性,其定义如下:

图 13:ReLU 函数示意图

ReLU 函数通常被推荐作为在尝试其他函数之前的一个很好的起点。ReLU 倾向于惩罚负值。因此,如果输入数据(例如,归一化至 -1 到 1 之间)包含负值,这些值将会受到 ReLU 的惩罚。这可能不是预期的行为。

我们在网络中将不会使用 ReLU 函数,因为我们的归一化过程会生成许多负值,从而导致学习模型的速度大大减慢。

激活函数 - 实现

在 Keras 中实现激活函数的最简单方法是实例化Activation()类,并将其添加到Sequential()模型中。Activation()可以使用 Keras 中提供的任何激活函数进行实例化(完整列表请见keras.io/activations/)。在我们的例子中,我们将使用tanh函数。

在实现激活函数后,我们将模型的版本提升为v2,使其成为bitcoin_lstm_v3

    model = Sequential()

    model.add(LSTM(
        units=period_length,
        batch_input_shape=(batch_size, number_of_periods, period_length),
        input_shape=(number_of_periods, period_length),
        return_sequences=True, stateful=False))

    model.add(LSTM(
        units=period_length,
        batch_input_shape=(batch_size, number_of_periods, period_length),
        input_shape=(number_of_periods, period_length),
        return_sequences=False, stateful=False))

    model.add(Dense(units=period_length))
    model.add(Activation("tanh"))

    model.compile(loss="mse", optimizer="rmsprop") 

代码片段 10:将激活函数tanh添加到bitcoin_lstm_v2模型中,更新为bitcoin_lstm_v3

还有许多其他值得尝试的激活函数。TensorFlow 和 Keras 都在各自的官方文档中提供了已实现的函数列表。在实现自己的激活函数之前,最好先从 TensorFlow 和 Keras 中已经实现的函数开始。

正则化策略

神经网络尤其容易出现过拟合。过拟合发生在网络学习了训练数据的模式,但无法找到可以应用于测试数据的可泛化模式。

正则化策略是指通过调整网络学习的方式来处理过拟合问题的技术。在本书中,我们讨论了两种常见的策略:L2 正则化和 Dropout。

L2 正则化

L2 正则化(或称权重衰减)是一种常见的解决过拟合模型的技术。在一些模型中,某些参数的变化幅度较大。L2 正则化会对这些参数进行惩罚,从而减少这些参数对网络的影响。

L2 正则化使用参数来决定惩罚模型神经元的程度。通常将该值设置为非常小的数值(即0.0001);否则,可能会完全消除某个神经元的输入。

Dropout(丢弃法)

Dropout 是一种基于简单问题的正则化技术:如果从层中随机移除一部分节点,剩下的节点会如何适应?事实证明,剩余的神经元会适应,学习表示那些之前由缺失神经元处理的模式。

Dropout 策略实现起来简单,通常在避免过拟合方面非常有效。这将是我们首选的正则化策略。

正则化策略 – 实现

为了使用 Keras 实现 Dropout 策略,我们导入Dropout()类,并将其添加到每个 LSTM 层之后的网络中。

这一添加有效地将我们的网络变为bitcoin_lstm_v4

    model = Sequential()
    model.add(LSTM(
        units=period_length,
        batch_input_shape=(batch_size, number_of_periods, period_length),
        input_shape=(number_of_periods, period_length),
        return_sequences=True, stateful=False))

    model.add(Dropout(0.2))
    model.add(LSTM(
        units=period_length,
        batch_input_shape=(batch_size, number_of_periods, period_length),
        input_shape=(number_of_periods, period_length),
        return_sequences=False, stateful=False))

    model.add(Dropout(0.2))

    model.add(Dense(units=period_length))
    model.add(Activation("tanh"))

    model.compile(loss="mse", optimizer="rmsprop") 

代码片段 11:在此代码片段中,我们将Dropout()步骤添加到我们的模型(bitcoin_lstm_v3)中,更新为bitcoin_lstm_v4

也可以使用 L2 正则化代替 Dropout。为此,只需实例化ActivityRegularization()类,并将 L2 参数设置为较小的值(例如0.0001)。然后,将其放置在网络中添加 Dropout()类的位置。可以通过将其添加到网络中,同时保留两个Dropout()步骤,或简单地将所有Dropout()实例替换为ActivityRegularization()来进行实验。

优化结果

总的来说,我们已经创建了四个版本的模型。这些版本中的三个是通过应用本章所述的不同优化技术创建的。

在创建了所有这些版本后,我们现在需要评估哪个模型表现最好。为此,我们使用与第一个模型相同的指标:MSE、RMSE 和 MAPE。MSE 用于比较模型在每一周预测中的误差率,RMSE 和 MAPE 用于使模型结果更易于解释。

模型MSE(最后一轮)RMSE(整个序列)MAPE(整个序列)训练时间
bitcoin_lstm_v0- 399.6 8.4%** -**
bitcoin_lstm_v1 7.15*10^(-6) 419.3 8.8%49.3 秒
bitcoin_lstm_v2 3.55*10^(-6) 425.4 9.0%1 分 13 秒
bitcoin_lstm_v3 2.8*10^(-4) 423.9 8.8%1 分 19 秒
bitcoin_lstm_v4 4.8*10^(-7) 442.4 8.8%1 分 20 秒

表 2:所有模型的结果

有趣的是,我们的第一个模型(bitcoin_lstm_v0)在几乎所有的指标中表现最好。我们将使用该模型来构建我们的 Web 应用程序,并持续预测比特币价格。

活动:优化深度学习模型

在这个活动中,我们对第五章模型架构bitcoin_lstm_v0)中创建的模型应用了不同的优化策略。该模型在完整的去归一化测试集上的 MAPE 性能约为 8.4%。我们将尝试减少这个差距。

  1. 使用终端,通过执行以下命令启动 TensorBoard 实例:
      $ cd ./chapter_3/activity_7/
      $ tensorboard --logdir=logs/ 
  1. 打开屏幕上出现的 URL,并保持该浏览器标签页打开。同时,启动一个 Jupyter Notebook 实例:
       $ jupyter notebook

打开出现在另一个浏览器窗口中的 URL。

  1. 现在,打开名为Activity_7_Optimizing_a_deep_learning_model.ipynb的 Jupyter Notebook,导航到 Notebook 的标题并导入所有所需的库。我们将像之前的活动一样加载训练和测试数据。我们还将使用实用函数split_lstm_input()将其分割为训练组和测试组。

在 Notebook 的每个部分,我们都会在模型中实现新的优化技术。每次我们这样做时,都会训练一个全新的模型,并将其训练后的实例存储在一个描述模型版本的变量中。例如,我们的第一个模型bitcoin_lstm_v0在 Notebook 中被称为model_v0。在 Notebook 的最后,我们使用 MSE、RMSE 和 MAPE 评估所有模型。

  1. 现在,在打开的 Jupyter Notebook 中,导航到Adding Layers(添加层)和Nodes(节点)部分。你将在下一个单元格中看到我们第一个模型。这是我们在第五章 模型架构 中构建的基础 LSTM 网络。现在,我们需要向这个网络添加一个新的 LSTM 层。

利用本章的知识,继续添加一个新的 LSTM 层,编译并训练模型。在训练模型时,请记得经常访问正在运行的 TensorBoard 实例。

你将能够看到每个模型的运行并比较它们的损失函数结果:

图 14:运行 TensorBoard 实例,显示多个不同的模型运行。TensorBoard 实际上是一个非常

有助于实时跟踪模型训练进展。

  1. 现在,导航到Epochs(训练轮次)部分。在这一部分,我们将探索不同规模的epochs。使用工具函数train_model()来命名不同的模型版本和运行:
      train_model(model=model_v0, X=X_train, Y=Y_validate, epochs=100,
      version=0, run_number=0) 

使用不同的 epoch 参数训练模型。

目前,你需要确保模型不会过拟合训练数据。你希望避免这种情况,因为如果模型过拟合,它将无法预测训练数据中表现出的模式,而这些模式在测试数据中可能会有不同的表现形式。

在你完成对 epoch 的实验后,继续进行下一个优化技术:激活函数。

  1. 现在,导航到 Notebook 中的Activation Functions(激活函数)部分。在这一部分,你只需要更改以下变量:
      activation_function = "tanh" 

本节中我们使用了tanh函数,但你可以尝试其他激活函数。查看keras.io/activations/中列出的激活函数,并尝试其他可能的选项。

我们的最终选择是尝试不同的正则化策略。这通常更为复杂,可能需要多次迭代才能看到任何改进——特别是在数据量如此之少的情况下。此外,添加正则化策略通常会增加网络的训练时间。

  1. 现在,导航到 Notebook 中的Regularization Strategies(正则化策略)部分。在这一部分,你需要实现Dropout()正则化策略。找到合适的位置将此步骤加入,并在我们的模型中实现。

  2. 你还可以尝试 L2 正则化(或者两者结合使用)。和Dropout()一样,使用ActivityRegularizationl2=0.0001)进行操作。

  3. 现在,导航到笔记本中的评估模型部分。在这一部分,我们将评估模型对测试集未来 19 周数据的预测。然后,我们将计算预测系列与测试系列之间的 RMSE 和 MAPE。

我们已经实现了与第 6 个活动相同的评估技术,所有功能都封装在实用函数中。只需运行本节的所有单元格直到笔记本结束,即可查看结果。

抓住这个机会调整前面提到的优化技术的值,尝试超越该模型的性能。

总结

在这一章节中,我们学习了如何使用均方误差(MSE)、均方根误差(RMSE)和平均绝对百分比误差(MAPE)来评估我们的模型。我们在由我们的第一个神经网络模型进行的 19 周预测系列中计算了后两个指标。然后,我们了解到该模型表现良好。

我们还学习了如何优化模型。我们查看了通常用于提高神经网络性能的优化技术。此外,我们实现了其中的一些技术,并创建了几个不同的模型来预测比特币价格,具有不同的误差率。

在下一章节中,我们将把我们的模型转化为一个 Web 应用,完成两件事:定期使用新数据重新训练我们的模型,并能够通过 HTTP API 接口进行预测。

第七章:产品化

本章重点讨论如何将深度学习模型进行产品化。我们使用“产品化”一词来定义将深度学习模型转化为软件产品的过程,使其能够被其他人和应用程序使用。

我们关注的是能够在新数据可用时使用它的模型,持续从新数据中学习模式,并因此做出更好的预测。我们研究了处理新数据的两种策略:一种是重新训练现有模型,另一种是创建一个完全新的模型。然后,我们将在比特币价格预测模型中实现后一种策略,以使其能够持续预测新的比特币价格。

本章还提供了如何将模型部署为 Web 应用程序的练习。到本章结束时,我们将能够部署一个有效的 Web 应用程序(具有功能齐全的 HTTP API)并根据需要进行修改。

我们以 Web 应用程序为例,来展示如何部署深度学习模型,因为它简单且普遍(毕竟,Web 应用程序非常常见),但也有许多其他的可能性。

到本章结束时,你将能够:

  • 处理新数据

  • 将模型部署为 Web 应用程序

处理新数据

模型可以在一组数据上进行训练,然后用来做预测。这些静态模型非常有用,但通常我们希望模型能够从新数据中持续学习——并随着学习的进行不断改进。

在本节中,我们将讨论如何重新训练深度学习模型的两种策略,并在 Python 中实现它们。

数据与模型的分离

在构建深度学习应用时,最重要的两个领域是数据和模型。从架构角度来看,我们建议将这两个领域分开。我们认为这是一个好的建议,因为这两个领域各自包含的功能本质上是相互独立的。数据通常需要收集、清洗、组织和规范化;而模型则需要训练、评估,并能够进行预测。这两个领域是相互依赖的,但分别处理会更好。

根据这一建议,我们将使用两个类来帮助我们构建 Web 应用程序:CoinMarketCap()Model()

  • CoinMarketCap() :这是一个用于从以下网站获取比特币价格的类:www.coinmarketcap.com。这也是我们最初比特币数据的来源。这个类使我们能够定期提取该数据,返回一个包含解析记录和所有可用历史数据的 Pandas DataFrame。CoinMarketCap() 是我们的数据组件。

  • Model() : 该类将我们迄今为止编写的所有代码整合成一个类。该类提供了与我们之前训练的模型进行交互的功能,并允许使用去标准化的数据进行预测——这使得理解更加容易。Model() 类是我们的模型组件。

这两个类在我们的示例应用中被广泛使用,定义了数据和模型组件。

数据组件

CoinMarketCap() 类创建了用于检索和解析数据的方法。它包含一个相关方法 historic(),其详细代码如下:

    @classmethod
 def historic(cls, start='2013-04-28', stop=None,
 ticker='bitcoin', return_json=False):
    start = start.replace('-', '')
    if not stop:
        stop = datetime.now().strftime('%Y%m%d')
    base_url = 'https://coinmarketcap.com/currencies'

    url = '/{}/historical-10\. data/?start={}&end={}'.format(ticker, start,
    stop)
    r = requests.get(url)

代码片段 1CoinMarketCap() 类中的 historic() 方法。

该方法从 CoinMarketCap 网站收集数据,解析数据,并返回一个 Pandas DataFrame。

historic() 类返回一个 Pandas DataFrame,准备由 Model() 类使用。

在使用其他模型时,考虑创建一个程序组件(例如,Python 类),其功能与 CoinMarketCap() 类相同。也就是说,创建一个组件,能够从数据源获取数据,解析数据,并将其以可用格式提供给模型组件。

CoinMarketCap() 类使用参数 ticker 来确定收集哪种加密货币。CoinMarketCap 提供了许多其他加密货币,包括非常流行的以太坊(ethereum)和比特币现金(bitcoin-cash)。使用 ticker 参数可以更改加密货币,训练不同于本书中使用的比特币模型的模型。

模型组件

Model() 类是我们实现应用程序模型组件的地方。该类包含实现本书中所有不同建模主题的文件方法。这些方法包括:

  • build() : 使用 Keras 构建一个 LSTM 模型。该函数作为一个简单的包装器,封装了手动创建的模型。

  • train() : 使用类实例化时提供的数据训练模型。

  • evaluate() : 使用一组损失函数对模型进行评估。

  • save() : 将模型作为文件保存在本地。

  • predict() : 基于按周排序的观察输入序列进行预测并返回结果。

我们在本章中使用这些方法来处理、训练、评估并发出模型预测。Model() 类是如何将 Keras 的核心功能封装到 Web 应用中的示例。这些方法的实现几乎与前面章节中的实现完全相同,但增加了语法糖以增强它们的接口。

例如,train() 方法在以下代码中实现:

 def train(self, data=None, epochs=300, verbose=0, batch_size=1):
        self.train_history = self.model.fit(
            x=self.X, y=self.Y,
            batch_size=batch_size, epochs=epochs,
            verbose=verbose, shuffle=False)
    self.last_trained = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    return self.train_history

代码片段 2Model() 类中的 train() 方法。该方法使用来自 self.X 和 self.Y 的数据训练自 self.model 的模型。

在前面的代码片段中,您会注意到train()方法类似于来自Chapter 6活动 6*和 7 的解决方案,模型评估与优化。其核心思想是,Keras 工作流中的每个过程(构建或设计、训练、评估和预测)都可以轻松地转化为程序的不同部分。在我们的案例中,我们将它们变成了可以从Model()类调用的方法。这有助于组织我们的程序,并提供一系列约束(例如模型架构或某些 API 参数),帮助我们在稳定的环境中部署模型。

在接下来的章节中,我们将探讨处理新数据的常见策略。

处理新数据

机器学习模型的核心思想——包括神经网络——是它们能够从数据中学习模式。假设一个模型已使用某个数据集进行训练,并且现在开始进行预测。现在,假设有新的数据可用。我们可以采用什么策略,使模型能够利用新数据来学习新的模式并改进其预测?

本节我们讨论两种策略:重新训练旧模型和训练新模型。

重新训练旧模型

使用这种策略,我们使用新数据重新训练现有模型。通过这种策略,可以不断调整模型参数,以适应新现象。然而,后期训练使用的数据可能与早期数据有显著不同。这些差异可能会导致模型参数发生重大变化,使其学习到新模式并遗忘旧模式。这一现象通常被称为灾难性遗忘

灾难性遗忘是影响神经网络的常见现象。深度学习研究人员多年来一直在努力解决这个问题。DeepMind,谷歌旗下的英国深度学习研究小组,在寻找解决方案方面取得了显著进展。文章《克服神经网络中的灾难性遗忘》(Overcoming Catastrophic Forgetting in Neural Networks)是这类工作的一个良好参考。该论文可在以下链接获得:arxiv. org/pdf/1612.00796.pdf

用于首次训练的相同接口(model.fit())可以用于用新数据进行训练:

    X_train_new, Y_train_new = load_new_data()

    model.fit(x=X_train_new, y=Y_train_new,
    batch_size=1, epochs=100,
    verbose=0)

代码片段 3:在我们的 LSTM 模型中实现 TensorBoard 回调的代码片段

在 Keras 中,当模型进行训练时,其权重信息会被保留——这是模型的状态。当使用model.save()方法时,该状态也会被保存。当调用model.fit()方法时,模型会使用之前的状态作为起点,用新数据集重新训练。

在典型的 Keras 模型中,这种技术可以顺利使用。然而,在处理 LSTM 模型时,这种技术有一个关键的限制:训练数据和验证数据的形状必须相同。例如,我们的 LSTM 模型(bitcoin_lstm_v0)使用 76 周的数据来预测未来一周。如果我们在下一周尝试使用 77 周的数据重新训练网络,模型将抛出异常,提示数据形状不正确。

处理这个问题的一种方法是将数据安排为模型所期望的格式。在我们的例子中,我们需要配置模型,使用 40 周的数据来预测未来的一周。采用这个解决方案时,我们首先用 2017 年最初的 40 周数据训练模型,然后继续在接下来的几周中重新训练,直到达到第 50 周。

我们使用Model()类在以下代码中执行此操作:

    M = Model(data=model_data[0*7:7*40 + 7],
        variable='close',
        predicted_period_size=7)
    M.build()
    6 M.train()
    for i in range(1, 10 + 1):
 M.train(model_data[i*7:7*(40 + i) + 7])

Snippet 4:实现重新训练技术的代码片段

这种技术训练速度较快,且通常适用于较大的数据序列。接下来的技术更易于实现,且在较小的数据序列中表现良好。

训练新模型

另一种策略是每次有新数据时创建并训练一个新模型。这种方法有助于减少灾难性遗忘,但随着数据量的增加,训练时间也会增加。其实现非常简单。

以比特币模型为例,假设我们拥有 2017 年 49 周的旧数据,并且在一周后,新的数据可用。我们用以下的old_datanew_data变量来表示这一点:

    old_data = model_data[0*7:7*48 + 7]
    new_data = model_data[0*7:7*49 + 7]

    M = Model(data=old_data,
        variable='close',
        predicted_period_size=7)
    M.build()
    M.train()

 M = Model(data=new_data,
 variable='close',
 predicted_period_size=7)
 M.build()
 M.train()

Snippet 5:实现当新数据可用时训练新模型的策略的代码片段

这种方法非常简单实现,并且对于小数据集来说效果良好。这将是我们比特币价格预测应用程序的首选解决方案。

活动:处理新数据

在这个活动中,我们每次获得新数据时都会重新训练我们的模型。

首先,我们通过导入cryptonic开始。Cryptonic 是为本书开发的一个简单软件应用,它使用 Python 类和模块实现了直到这一部分的所有步骤。可以将 Cryptonic 看作是一个模板,展示了你如何开发类似的应用程序。

cryptonic作为一个 Python 模块随本活动一起提供。首先,我们将启动一个 Jupyter Notebook 实例,然后加载cryptonic包。

  1. 使用你的终端,导航到目录Chapter_7/activity_8,然后执行以下代码以启动一个 Jupyter Notebook 实例:
      $ jupyter notebook 
  1. 在浏览器中打开应用程序提供的 URL,并打开名为Activity_8_Re_training_a_model_dynamically.ipynb的 Jupyter Notebook。

现在,我们将从cryptonic: Model()CoinMarketCap()加载两个类。这些类有助于操作我们的模型,并且能够从网站 CoinMarketCap (coinmarketcap.com/) 获取数据。

  1. 在 Jupyter Notebook 实例中,导航到标题 获取实时数据。我们现在将从 CoinMarketCap 获取更新的历史数据。只需调用该方法:
      $ historic_data = CoinMarketCap.historic() 

变量 historic_data 现在被填充为一个 Pandas DataFrame,包含直到今天或昨天的数据。这很棒,并且当更多数据可用时,这使得我们可以更容易地重新训练模型。

数据基本包含了我们早期数据集中的相同变量。然而,大部分数据来自较早的时期。近期的比特币价格相比几年前已经出现了较大的波动。在将这些数据用于模型之前,让我们确保只使用 2017 年 1 月 1 日之后的日期数据。

  1. 使用 Pandas API,过滤仅包含 2017 年可用日期的数据:
      $ model_data = # filter the dataset using pandas here 

你应该可以使用日期变量作为过滤索引来实现这一点。确保在继续之前过滤数据。

Model() 类将我们迄今为止在所有活动中编写的代码汇总起来。我们将使用这个类来构建、训练和评估我们的模型。

  1. 使用 Model() 类,我们现在使用之前过滤过的数据来训练模型:
      M = Model(data=model_data,
         variable='close',
         predicted_period_size=7)
      M.build()
      M.train()
      M.predict(denormalized=True) 

上述步骤展示了使用 Model() 类训练模型时的完整工作流程。

接下来,我们将专注于每次新数据可用时重新训练模型。这会根据新数据调整网络的权重。

为了做到这一点,我们已经配置了模型,通过使用 40 周数据来预测一周。现在我们希望使用剩余的 10 周完整数据,创建包含其中一个完整周的 40 周重叠周期,并为每个周期重新训练模型。

  1. 在 Jupyter Notebook 中导航到标题 重新训练旧模型。现在,完成范围函数和 model_data 过滤参数,使用索引将数据拆分为重叠的七天组。然后,重新训练模型并收集结果:
      results = []
      for i in range(A, B):
         M.train(model_data[C:D])
         results.append(M.evaluate()) 

变量 ABCD 是占位符。使用整数创建重叠的七天组,其中重叠部分为一天。

重新训练模型后,请调用 M.predict(denormalized=True) 函数并欣赏结果。

接下来,我们将专注于每次新数据可用时创建并训练一个新模型。为此,我们假设我们已经拥有 2017 年 49 周的旧数据,并且一周后,我们将拥有新数据。我们用变量 old_datanew_data 来表示这一点。

  1. 导航到标题 训练新模型,并将数据分割到变量 old_datanew_data 中:
      old_data = model_data[0*7:7*48 + 7]
      new_data = model_data[0*7:7*49 + 7] 
  1. 然后,首先使用 old_data 训练模型:
      M = Model(data=old_data,
        variable='close',
        predicted_period_size=7)
      M.build()
      M.train()

这个策略是从零开始构建模型,并在有新数据时进行训练。请继续在接下来的单元格中实现这一点。

现在我们已经拥有训练模型所需的所有组件。在下一节中,我们将部署我们的模型为 Web 应用程序,通过 HTTP API 在浏览器中提供预测。

在本节中,我们学习了在新数据可用时训练模型的两种策略:

  • 重新训练旧模型

  • 训练新模型

后者创建一个新模型,该模型使用除测试集中的观察数据外的完整数据集进行训练。前者则在可用数据上训练一次模型,然后继续创建重叠的批次,每当有新数据可用时重新训练同一模型。

部署模型为 Web 应用程序

在本节中,我们将把我们的模型部署为 Web 应用程序。我们将使用一个名为 "cryptonic" 的示例 Web 应用程序来部署我们的模型,探索其架构,以便未来可以进行修改。目的是让您将这个应用程序作为更复杂应用程序的起点;它是一个完全可运行的起点,您可以根据需要进行扩展。

除了熟悉 Python,本主题假设您已了解创建 Web 应用程序。具体来说,我们假设您对 Web 服务器、路由、HTTP 协议和缓存有所了解。您将能够在没有深入了解这些主题的情况下,本地部署示范的 cryptonic 应用程序,但学习这些内容将使未来的开发变得更加容易。

最后,Docker 被用于部署我们的 Web 应用程序,因此掌握该技术的基础知识也很有用。

应用程序架构和技术

为了部署我们的 Web 应用程序,我们将使用表 1 中描述的工具和技术。Flask 是关键,因为它帮助我们为模型创建 HTTP 接口,使我们能够访问 HTTP 端点(例如 /predict)并以通用格式接收数据。其他组件则是开发 Web 应用程序时的流行选择:

工具或技术描述角色

| Docker | Docker 是一种用于处理以容器形式打包的应用程序的技术

容器。Docker 是一种日益流行的

技术,用于构建 Web 应用程序。

| 打包 Python 应用程序和 UI。

|

| Flask | Flask 是一个用于构建 Python Web 应用程序的微框架。

| 创建应用程序路由

|

| Vue.js | 通过动态更改模板来工作的 JavaScript 框架

基于来自

后端。

渲染用户界面。

| Nginx | Web 服务器,易于配置,用于将流量路由到 Docker 化的应用程序并处理 SSL

用于 HTTPS 连接的证书。

| 路由流量在用户和 Flask 应用程序之间。

|

| Redis | 键值数据库。由于其

简洁性和速度。

缓存 API 请求。

表 1:用于部署深度学习 Web 应用程序的工具和技术

这些组件组合在一起,如下图所示:

图 1:本项目中构建的 Web 应用程序的系统架构

用户通过浏览器访问 Web 应用程序。然后,Nginx 会将流量路由到包含 Flask 应用程序的 Docker 容器(默认情况下,运行在 5000 端口)。Flask 应用程序在启动时已经实例化了我们的比特币模型。如果给定了模型,它将使用该模型而不进行训练;如果没有,它将创建一个新模型并使用来自 CoinMarketCap 的数据从头开始训练。

在准备好模型后,应用程序会检查请求是否已缓存于 Redis 中——如果有,则返回缓存的数据。如果没有缓存,它将继续发出预测并在 UI 中渲染结果。

部署和使用 Cryptonic

cryptonic 被开发为一个 Docker 化应用程序。从 Docker 的角度来看,这意味着应用程序可以作为 Docker 镜像构建,然后在开发或生产环境中作为 Docker 容器部署。

Docker 使用名为 Dockerfile 的文件来描述如何构建镜像以及当镜像作为容器部署时会发生什么。Cryptonic 的 Dockerfile 可以在以下代码中找到:

    FROM python:3.6
    COPY . /cryptonic
    WORKDIR "/cryptonic"
    RUN pip install -r requirements.txt
    EXPOSE 5000
    CMD ["python", "run.py"]

片段 6:cryptonic 镜像的 Docker 文件

可以使用以下命令通过 Docker 文件构建 Docker 镜像:

     $ docker build --tag cryptonic:latest

片段 7:用于在本地构建 Docker 镜像的 Docker 命令

此命令将使镜像 cryptonic:latest 可用,并可以作为容器进行部署。构建过程可以在生产服务器上重复,或者直接部署镜像并作为容器运行。

在镜像构建并可用后,可以使用 docker run 命令来运行 cryptonic 应用程序,如以下代码所示:

     $ docker run --publish 5000:5000 \ 
             --detach cryptonic:latest

片段 8:在终端中执行 docker run 命令的示例

--publish 标志将本地主机的 5000 端口绑定到 Docker 容器的 5000 端口,而 --detach 会将容器作为守护进程在后台运行。

如果您已经训练了一个不同的模型,并希望使用该模型而不是训练一个新模型,您可以在 docker-compose.yml 文件中修改 MODEL_NAME 环境变量,如 片段 9 所示。该变量应包含您训练并希望提供的模型的文件名(例如,bitcoin_lstm_v1_trained.h5)——它应该也是一个 Keras 模型。如果您这么做,确保将本地目录挂载到 /models 文件夹中。您决定挂载的目录必须包含您的模型文件。

cryptonic 应用程序还包括一些环境变量,您在部署自己的模型时可能会觉得很有用:

  • MODEL_NAME:允许提供一个训练好的模型,以供应用程序使用。

  • BITCOIN_START_DATE:确定用作比特币系列起始日的日期。近年来,比特币价格的波动性远大于早期的波动性。此参数将过滤数据,仅使用感兴趣年份的数据。默认值是 2017 年 1 月 1 日。

  • PERIOD_SIZE:设置周期大小(以天为单位)。默认值为 7。

  • EPOCHS:配置模型每次运行时训练的轮次。默认值为 300。

这些变量可以在docker-compose.yml文件中进行配置,如下所示:

    version: "3"
    services:
    cache:
    image: cryptonic-cache:latest
    volumes: - $PWD/cache_data:/data
    networks:- cryptonic
    ports: - "6379:6379"
        environment:
            - MODEL_NAME=bitcoin_lstm_v0_trained.h5
            - BITCOIN_START_DATE=2017-01-01
            - EPOCH=300
            - PERIOD_SIZE=7

代码片段 9:包含环境变量的docker-compose.yml文件

部署 cryptonic 最简单的方法是使用代码片段 9 中的docker-compose.yml文件。该文件包含应用程序运行所需的所有规格,包括如何连接 Redis 缓存以及使用哪些环境变量的说明。导航到docker-compose.yml文件所在的位置后,可以使用命令docker-compose up启动 cryptonic,如下所示:

     $ docker-compose up -d 

代码片段 10:使用 docker-compose 启动 Docker 应用程序。-d标志将在后台执行应用程序。

部署后,可以通过 Web 浏览器在5000端口访问 cryptonic。该应用程序具有一个简单的用户界面,显示一个时间序列图,其中展示了真实的历史价格(即观察到的)和深度学习模型预测的未来价格(即预测的)。还可以在文本中看到使用Model().evaluate()方法计算的 RMSE 和 MAPE:

图 2:部署后的 cryptonic 应用程序截图

除了其用户界面(使用Vue.js开发),该应用程序还具有一个 HTTP API,在调用时进行预测。

API 有/predict端点,该端点返回一个 JSON 对象,包含未来一周内的去归一化比特币价格预测:

    {
    message: "API for making predictions.",
    period_length: 7,
    result: [
        15847.7,
        15289.36,
        17879.07,
    …
        17877.23,
        17773.08
    ],
        success: true,
        version: 1
    } 

代码片段 11:/predict 端点的示例 JSON 输出

该应用程序现在可以部署在远程服务器上,并用于持续预测比特币价格。

活动:部署深度学习应用程序

在本次活动中,我们将模型作为本地 Web 应用程序部署。这使得我们可以通过浏览器连接到该 Web 应用程序,或通过应用程序的 HTTP API 使用其他应用程序。在继续之前,请确保您的计算机中已安装并可用以下应用程序:

  • Docker(社区版)17.12.0-ce 或更高版本

  • Docker Compose (docker-compose) 1.18.0 或更高版本

上述两个组件可以从网站docker.com/.下载并安装到所有主要系统中。这些是完成此活动的必要组件。在继续之前,请确保这些组件在您的系统中可用。

  1. 使用终端导航到 cryptonic 目录并构建所有必需组件的 Docker 镜像:
      $ docker build --tag cryptonic:latest .    
      $ docker build --tag cryptonic-cache:latest ./ cryptonic-cache/ 
  1. 这两条命令构建了我们将在此应用中使用的两个镜像:cryptonic(包含 Flask 应用)和 cryptonic-cache(包含 Redis 缓存)。

  2. 构建完镜像后,找到 docker-compose.yml 文件并用文本编辑器打开。将参数 BITCOIN_START_DATE 改为除 2017-01-01 以外的其他日期:

      BITCOIN_START_DATE = # Use other date here 
  1. 最后的步骤是使用 docker-compose 在本地部署你的网页应用,步骤如下:
      docker-compose up 

你应该能在终端中看到一份活动日志,其中包括模型的训练周期。

  1. 模型训练完成后,你可以访问 http://localhost:5000 你的应用,并在 http://localhost:5000/predict 进行预测:

图 3:在本地部署的 Cryptonic 应用程序的截图

总结

本章总结了我们创建深度学习模型并将其作为网页应用部署的过程。我们的最后一步是部署一个使用 Keras 和 TensorFlow 引擎构建的比特币价格预测模型。我们通过将应用打包成 Docker 容器并进行部署,使得其他人可以使用我们的模型进行预测——以及通过其 API 使用其他应用。

除了这些工作,你还会发现有许多地方可以改进。我们的比特币模型仅仅是模型可以做的一个示例(特别是 LSTM 模型)。现在的挑战有两个方面:如何随着时间的推移让这个模型表现得更好?以及,如何在你的网页应用中添加一些功能,使得你的模型更易于访问?祝你好运,继续学习!