Keras-深度神经网络学习手册-一-

107 阅读1小时+

Keras 深度神经网络学习手册(一)

原文:Learn Keras for Deep Neural Networks

协议:CC BY-NC-SA 4.0

一、深度学习和 Keras 简介

在这一章中,我们将探索深度学习(DL)领域,并对其进行简要介绍,然后再看一看 DL 开发可用框架的流行选择。我们还将进一步了解 Keras 生态系统,以了解它的特殊之处,并查看示例代码,了解该框架对于开发 DL 模型有多简单。

让我们开始吧。

数字图书馆简介

我们将首先从一个正式的定义开始,然后处理一个简单的方法来描述这个主题。

DL 是人工智能(AI)中机器学习(ML)的一个子领域,它处理从大脑的生物结构和功能中受到启发的算法,以帮助机器获得智能。

也许这是太高的水平,或者可能很难消费,所以让我们一步一步地分解它。我们在定义中看到三个重要的术语,按照特定的顺序:DL、ML 和 AI。让我们先从人工智能开始,逐个解决这些流行词。

揭开流行语的神秘面纱

人工智能最一般的形式可以被定义为引入机器的智能质量。机器通常是愚蠢的,所以为了让它们更聪明,我们在它们身上引入某种智能,让它们能够独立做出决定。一个例子是洗衣机,它可以决定正确的用水量以及浸泡、洗涤和脱水所需的时间;也就是说,当提供特定输入时,它会做出决定,因此以更智能的方式工作。同样,自动提款机可以根据机器中可用的正确纸币组合来支付你想要的金额。这种智能是以人工方式在机器中诱导出来的,因此得名 AI。

另一点需要注意的是,这里的智能是显式编程的,比如一个 if-else 规则的综合列表。设计系统的工程师仔细考虑了所有可能的组合,并设计了一个基于规则的系统,该系统可以通过遍历定义的规则路径来做出决策。如果我们需要在没有显式编程的情况下在机器中引入智能,可能是机器可以自己学习的东西,会怎么样?那就是我们和 ML 接触的时候。

机器学习可以定义为在没有显式编程的情况下,将智能引入系统或机器的过程。

—安德鲁·吴,斯坦福大学兼职教授

ML 的例子可以是通过从历史测试结果和学生属性中学习来预测学生是否会在测试中失败或通过的系统。在这里,系统并没有用一个所有可能的规则的综合列表来编码,这些规则可以决定一个学生是通过还是失败;相反,系统根据从历史数据中学习到的模式自行学习。

那么,DL 在这种情况下处于什么位置呢?虽然 ML 对于各种问题都非常有效,但它在一些对人类来说似乎非常简单的特定情况下却表现不佳:例如,将图像分类为猫或狗,区分音频剪辑是男性还是女性的声音,等等。ML 在处理图像和其他非结构化数据类型时表现不佳。在研究这种糟糕表现的原因时,一个灵感导致了模仿人类大脑生物过程的想法,人类大脑由数十亿个神经元连接和协调组成,以适应学习新事物。与此同时,神经网络已经成为一个研究课题好几年了,但是由于当时计算和数据的限制,只取得了有限的进展。当研究人员到达 ML 和神经网络的尖端时,出现了 DL 领域,它是通过开发深度神经网络(DNNs)来构建的,即具有许多更多层的临时神经网络。DL 擅长于 ML 落后的新领域。在适当的时候,额外的研究和实验导致了对我们可以在哪里利用 DL 来完成所有 ML 任务的理解,并且期望更好的性能,只要有剩余的数据可用性。因此,DL 成为解决预测问题的一个无处不在的领域,而不仅仅局限于计算机视觉、语音等领域。

今天,我们可以利用 DL 来处理几乎所有早期使用 ML 解决的用例,并期望超越我们以前的成就,只要有多余的数据。这种认识导致了基于数据区分字段的顺序。建立了一个新的经验法则:在某个阈值之后,ML 不能通过增加训练数据来提高性能,而 DL 能够更有效地利用剩余数据来提高性能。几年前统计模型和 ML 之间的争论也是如此。下图展示了上述三个字段的数据大小对模型性能的总体影响。

img/475458_1_En_1_Figa_HTML.jpg

现在,如果我们重新审视形式定义,你可能会更好地理解 ML 的 AI 子场是由人脑的生物学方面激发的这一说法。我们可以使用简单的文氏图简化这三个字段,如下所示。

img/475458_1_En_1_Figb_HTML.jpg

综合起来,我们可以说,AI 是人工地将智能诱导到机器或系统中的领域,有或没有显式编程。人工智能是人工智能中的一个子领域,其中智能是在没有显式编程的情况下诱导的。最后,DL 是 ML 中的一个领域,在这里,智能被引入到系统中,而无需使用算法的显式编程,这些算法是由人脑的生物功能所启发的。

DL 在当今市场上解决了哪些经典问题?

今天,我们可以在数字世界的日常生活中看到 DL 的应用。如果你在社交媒体上很活跃,你可能已经注意到脸书建议你在上传照片时给你的朋友加标签。还要注意特斯拉汽车中的自动驾驶模式,你的 iOS 或 Android 手机上的消息系统中的下一个单词的预测,Alexa,Siri 和谷歌助手对你作为人类的回应,等等。如果我们试图分析我们可以使用 DL 解决的用例类型,我们已经可以在当今世界中使用的几乎任何系统中见证 DL 的力量。

分解 DL 模型

在其最基本的形式中,使用神经网络架构来设计 DL 模型。神经网络是神经元(类似于大脑中的神经元)与其他神经元连接的分层组织。这些神经元根据收到的输入向其他神经元传递消息或信号,并形成一个复杂的网络,通过某种反馈机制进行学习。

下面是一个基本神经网络的简单表示。

img/475458_1_En_1_Figc_HTML.jpg

正如您在前面的图中所看到的,输入数据被第一个隐藏层中的神经元所消耗,然后向下一层提供输出,依此类推,最终得到最终的输出。每层可以有一个或多个神经元,每个神经元将计算一个小函数(例如,激活函数)。连续层的两个神经元之间的连接将具有相关联的权重。权重定义了输入对下一个神经元的输出的影响,以及最终对整个最终输出的影响。在神经网络中,初始权重在模型训练期间都是随机的,但是这些权重被迭代更新以学习预测正确的输出。分解网络,我们可以定义几个逻辑构建块,如神经元、层、权重、输入、输出、神经元内部的激活函数来计算学习过程,等等。

为了直观的理解,让我们举一个人类大脑如何学习识别不同人的例子。当你第二次遇见一个人时,你就能认出他。这是怎么发生的?人在整体结构上有相似之处;两只眼睛,两只耳朵,一个鼻子,嘴唇,等等。每个人都有相同的结构,但我们能够很容易地区分人,对不对?

大脑中学习过程的本质是相当直观的。大脑不是学习面部的结构来识别人,而是学习与普通面部的偏差(例如,个人的眼睛与参考眼睛有多不同),然后可以量化为具有定义强度的电信号。同样,它从一个参考基准中学习人脸所有部分的偏差,并将这些偏差组合成新的维度,最后给出一个输出。所有这一切发生得如此之快,以至于我们没有人意识到我们的潜意识实际上做了什么。

同样,上图中展示的神经网络试图使用数学方法来模拟相同的过程。输入由第一层中的神经元消耗,并且在每个神经元内计算激活函数。基于一个简单的规则,它将输出转发给下一个神经元,类似于人脑学习的偏差。神经元的输出越大,输入维度的重要性就越大。然后,这些维度在下一层中组合起来,形成额外的新维度,我们可能无法理解这些维度。但是系统凭直觉学习。这一过程,当乘以几倍,发展成一个复杂的网络与几个连接。

现在已经了解了神经网络的结构,让我们来了解学习是如何发生的。当我们向已定义的结构提供输入数据时,最终输出将是一个预测,它可能是正确的,也可能是不正确的。基于输出,如果我们向网络提供反馈,以通过使用一些手段来进行更好的预测而更好地适应,则系统通过更新连接的权重来学习。为了实现提供反馈和定义下一步以正确的方式做出改变的过程,我们使用了一种称为“反向传播”的漂亮的数学算法随着越来越多的数据,逐步迭代该过程几次,有助于网络适当地更新权重,以创建一个系统,在该系统中,它可以根据它通过权重和连接为自己创建的规则来做出预测输出的决策。

“深度神经网络”这个名称是从使用更多隐藏层演变而来的,使其成为学习更复杂模式的“深度”网络。DL 的成功故事在最近几年才浮出水面,因为训练网络的过程计算量很大,需要大量数据。只有当计算机和数据存储变得更容易获得和负担得起时,这些实验才最终得以实现。

探索流行的 DL 框架

鉴于 DL 的采用已经以惊人的速度发展,生态系统的成熟度也有了显著的提高。多亏了许多大型技术组织和开源项目,我们现在有了太多的选择。在我们深入研究各种框架的细节之前,让我们理解为什么我们本质上需要一个框架,以及什么可以作为替代。

让我们从理解软件行业如何在框架中发展开始。

如果你观察软件业的发展,你会明白今天开发高端软件比几年前容易得多。这归功于可用的工具,它们以简单易用的方式自动化或抽象了复杂的问题。技术兄弟会在贡献伟大的想法方面是仁慈的和创新的。我们在以前服务的基础上构建新的服务,最终将创建一个复杂的服务,它将能够编排服务集合,同时又是安全的和可伸缩的。鉴于目前可用的软件工具的成熟度,我们可以抽象出后台发生的一些复杂性。这些工具只不过是软件系统的构建模块。你在技术上不需要从头开始;相反,你可以依靠已经非常成熟的强大工具来处理一些软件构建服务。

类似地,在 DL 中,有一组代码块可以被不同类型的用例重用。具有不同参数值的相同算法可以用于不同的用例,那么为什么不将算法打包成一个简单的函数或类呢?DL 的几个方面已经被开发成可重用的代码,现在可以从框架中直接使用,这些框架在抽象概念方面做得很好。DL 模型中的构件包括神经元、激活函数、优化算法、数据扩充工具等等。你真的可以用大约 1000 行代码从零开始开发一个 DNN,比如用 C++、Java 或 Python,或者使用一个框架,用 10-15 行代码重用可用的工具。话虽如此,让我们来看看当今业界使用的 DL 框架的流行选择。

低级 DL 框架

给定框架提供的抽象级别,我们可以将其分类为低级或高级 DL 框架。虽然这绝不是业界公认的术语,但是我们可以使用这种分离来更直观地理解框架。下面是一些流行的 DL 底层框架。

提亚诺

Theano 是第一批广受欢迎的 DL 库之一。它是由蒙特利尔大学的蒙特利尔学习算法研究所(MILA)开发的。Theano 是一个开源 Python 库,于 2007 年推出;上一个主要版本由 MILA 于 2017 年底发布。

更多详细信息,请访问

火炬

Torch 是另一个基于 Lua 编程语言的流行 ML 和 DL 框架。它最初是由 Ronan Collobert,Koray Kavukcuoglu 和 Clement Farabet 开发的,但后来由脸书用一组扩展模块作为开源软件进行了改进。

更多详细信息,请访问

PyTorch

PyTorch 是 Python 的开源 ML 和 DL 库,由脸书人工智能研究团队开发。PyTorch 比 Torch 更受欢迎,因为任何对 Python 有基本了解的人都可以开始开发 DL 模型。此外,PyTorch 对于 DL 开发来说更加容易和透明。

更多详细信息,请访问

mxnet 系统

MxNet 发音为“mix-net”,代表“混合”和“最大化”,由来自 CMU、NYU、新加坡国立大学、麻省理工学院和其他机构的研究人员开发。这个想法被简化为将声明性和命令性编程结合在一起(混合)以最大化效率和生产力。它支持使用多个 GPU,并得到了 AWS 和 Azure 等主要云提供商的广泛支持。

更多详细信息,请访问

TensorFlow

TensorFlow 无疑是 DL 兄弟会中最流行、使用最广泛的 DL 框架之一。它由 Google 开发并开源,支持跨 CPU、GPU 以及移动和边缘设备的部署。它于 2015 年 11 月发布,随后在行业内的采用率大幅上升。

DL 框架的列表很长,讨论所有这些超出了本书的范围。您还可以研究其他一些流行的框架,如 Caffe、Microsoft CNTK、Chainer、PaddlePaddle 等等。讨论一个框架相对于另一个框架的利弊是另一个有趣且永无止境的争论。我强烈建议您探索并理解每个框架所能提供的改进。

这将是一个很好的起点:

高级 DL 框架

前面提到的框架可以被定义为 DL 模型的第一级抽象。您仍然需要编写相当长的代码和脚本来准备好您的 DL 模型,尽管这比只使用 Python 或 C++要少得多。使用第一级抽象的优点是它在设计模型时提供了灵活性。

然而,为了简化 DL 模型的过程,我们有工作在第二级抽象上的框架;也就是说,我们可以在现有框架的基础上使用新的框架,从而进一步简化 DL 模型开发,而不是直接使用前面提到的框架。

最流行的高级 DL 框架是 Keras,它为 DL 模型开发提供了二级抽象。也有其他框架,如 Gluon、Lasagne 等,但 Keras 是被最广泛采用的一个。

注意

虽然 Gluon 在 MxNet 上工作,Lasagne 在 Theano 上工作,但 Keras 可以在 TensorFlow、Theano、MxNet 和 Microsoft CNTK 上工作。这个列表一直在积极地扩展,很可能在你读这本书的时候,会有更多的列表被添加进来。

Keras 是一个用 Python 编写的高级神经网络 API,可以帮助您用不到 15 行代码开发一个全功能的 DL 模型。因为它是用 Python 编写的,所以它有更大的用户和支持者群体,并且非常容易上手。Keras 的简单之处在于,它帮助用户快速开发 DL 模型,并提供大量的灵活性,同时仍然是一个高级 API。这确实使 Keras 成为一个特殊的工作框架。此外,考虑到它支持其他几个框架作为后端,它增加了灵活性,可以根据需要为不同的用例利用不同的低级 API。到目前为止,Keras 最广泛采用的用法是将 TensorFlow 作为后端(即,Keras 作为高级 DL API,TensorFlow 作为其低级 API 后端)。简而言之,您在 Keras 中编写的代码被转换为 TensorFlow,然后在计算实例上运行。

你可以在这里阅读更多关于 Keras 及其最近的发展: https://keras.io/

先睹为快 Keras 框架

既然我们已经了解了可用于 DL 的不同框架以及使用其中一个的需要,在我们结束本章之前,我们可以先睹为快为什么 Keras 在 DL 开发中具有不公平的优势。我们肯定会在下一章更深入地了解 Keras 所提供的东西,但是在我们结束这一章之前看看 Keras 的美丽是很有趣的。

看看下面展示的 DNN。

img/475458_1_En_1_Figd_HTML.jpg

是的,这就是我们之前在探索主题“分解 DL 模型”时看到的同一个图如果我们试图定义这个网络,我们可以说它是一个 DNN,有两个隐藏层,分别有五个和四个神经元。第一隐藏层接受具有三维的输入数据,并在具有两个神经元的输出层中给出输出。

为了更直观地理解这一点,我们可以假设这是一个简单的 DNN,用于解决基于一些输入数据预测学生是否会通过或失败的问题。

假设我们有年龄、学习的小时数以及他作为输入数据点出现的所有先前测试的平均分(满分为 100)。

在 Keras 中构建神经网络就像下面的脚本一样简单。此刻不理解后面的全部代码是绝对没问题的;我们将在下一章一步一步更详细地探讨这一点。

#Import required packages
from keras.models import Sequential
from keras.layers import Dense
import numpy as np

# Getting the data ready
# Generate train dummy data for 1000 Students and dummy test for 500
#Columns :Age, Hours of Study &Avg Previous test scores
np.random.seed(2018). #Setting seed for reproducibility
train_data, test_data = np.random.random((1000, 3)), np.random.random((500, 3))
#Generate dummy results for 1000 students : Whether Passed (1) or Failed (0)
labels = np.random.randint(2, size=(1000, 1))

#Defining the model structure with the required layers, # of neurons, activation function and optimizers
model = Sequential()
model.add(Dense(5, input_dim=3, activation="relu"))
model.add(Dense(4, activation="relu"))
model.add(Dense(1, activation="sigmoid"))
model.compile(loss='binary_crossentropy', optimizer="adam", metrics=['accuracy'])

#Train the model and make predictions
model.fit(train_data, labels, epochs=10, batch_size=32)
#Make predictions from the trained model
predictions = model.predict(test_data)

前面的代码可以分为三个部分。

准备好数据

通常,我们会花一些时间来导入和研究数据内容,并对数据进行必要的扩充,作为模型的输入。在这里,由于这是一个虚拟用例,我们只是使用 Python 的 numpy 包中的随机数生成器来为 1000 名学生创建一个虚拟训练数据集,为 500 名学生创建另一个虚拟测试数据集,最后是学生的标签或实际输出(即,他们是通过还是失败)。

定义模型结构

一旦我们以必要的格式准备好数据,我们将需要首先设计 DNN 的结构。我们定义了层的数量和类型、每层中神经元的数量、所需的激活函数、要使用的优化器以及其他一些网络属性。

训练模型并进行预测

一旦定义了网络,我们就可以使用带有正确预测的训练数据,通过模型的“拟合”方法来训练网络。最后,一旦模型被训练,我们可以使用训练好的模型对新的测试数据集进行预测。

虽然这个例子过于简单,但我希望它能让您理解使用 Keras 框架开发 DL 模型是多么容易。如果在这一点上理解代码是压倒性的,那绝对没问题。我们将在下一章一步一步地详细讨论代码。

摘要

在本章中,我们通过简单的介绍学习了数字图书馆的基础知识,并探索了一些在日常数字生活中利用数字图书馆的常见使用案例。然后,我们研究了使用 DL 框架开发模型的必要性,并探索了行业中可用的一些低级和高级框架。然后我们看了 Keras,这是本书的首选框架,用一个简单的虚拟例子来说明创建 DL 模型的简单性。

在下一章中,我们将深入了解 Keras 及其提供的各种构建模块。我们将尝试使用 Keras 和 Python 开发一个简单的 DL 模型,并进行动手练习。

二、Keras 实战

在这一章中,我们将探索 Keras 框架,并从动手练习开始,学习 Keras 的基础知识以及一些 Python 和必要的 DL 主题。考虑到这是一个快速入门指南,需要注意的是:在 DL 中,我们没有足够的篇幅来详细讨论所有的主题。相反,我们将从一个简单的主题开始,探索其背后的基本思想,并添加参考资料,以便您可以更深入地了解该主题的更多基础知识。

设置环境

如前所述,我们将使用 TensorFlow 作为 Python 中的后端来开发带有 Keras 堆栈的 DL 模型。因此,为了开始,我们需要通过安装 Python、几个重要的 Python 包、TensorFlow 以及最后的 Keras 来设置我们的操场环境。

让我们开始吧。

选择 Python 版本

Python 目前有两个主要版本:2.7.x 和 3.x。尽管 Python 3.x 是最新版本,也是 Python 的未来,但由于开发人员社区在从 2.7 过渡到 3.x 方面的落后无能,已经出现了一系列冲突。不幸的是,许多开发人员仍然与 Python 2.7.x 版本联系在一起。然而,对于我们的用例,我强烈建议从 Python 3.x 开始,因为它是未来的趋势。有些人可能不愿意从 Python 3 开始,认为 3.x 版本中的许多包会有问题,但是对于几乎所有的实际用例,我们已经为 3.x 更新了所有主要的 DL、ML 和其他有用的包。

为 Windows、Linux 或 macOS 安装 Python

市场上有许多 Python 发行版。你可以从 python.org 官方网站下载并安装 Python,也可以选择任何流行的发行版。对于 ML 和 DL,最推荐的 Python 发行版是来自 Continuum Analytics 的 Anaconda 发行版。Anaconda 是 Python 的免费开源发行版,特别适合 ML 和 DL 大规模处理。它简化了整个包管理和部署过程,并附带一个非常易于使用的虚拟环境管理器和一些附加的编码工具,如 Jupyter 笔记本和 Spyder IDE。

要开始使用 Anaconda,您可以进入 www.anaconda.com/download/ ,根据您选择的 OS (Mac/Windows/Linux)和架构(32 位/64 位)选择合适的版本。在写这本书的时候,Python 3 的最新版本是 3.6。当你读到这本书的时候,可能会有更新的版本。您应该轻松下载并安装 Anaconda Python 的最新版本。

下载安装程序后,请安装应用程序。

对于 Windows 用户,这将是一个简单的可执行文件安装。双击。exe 文件,并按照屏幕上的指导完成安装过程。

Linux 用户可以在导航到下载的文件夹后使用以下命令:

bash Anaconda-latest-Linux-x86_64.sh

Mac 用户可以通过双击下载的来安装该软件。pkg 文件,然后按照屏幕上的说明进行操作。

Python 的 Anaconda 发行版通过安装 DL 所需的所有主要 Python 包,简化了 DL 和 ML 的过程。

安装 Keras 和 TensorFlow 后端

现在 Python 已经设置好了,我们需要安装 TensorFlow 和 Keras。使用 Python 的包管理器pip可以很容易地在 Python 中安装包。您可以在终端或命令提示符下使用命令pip install package-name安装任何 Python 包。

所以,让我们安装我们需要的包(即 TensorFlow 和 Keras)。

pip install keras

然后

pip install tensorflow

如果您在使用 TensorFlow 和 Keras 设置 Anaconda Python 时遇到任何问题,或者您希望仅在 Python 虚拟环境中进行实验,您可以在此浏览更详细的安装指南:

https://medium.com/@margaretmz/anaconda-jupyter-notebook-tensorflow-and-keras-b91f381405f8

此外,如果您的系统有任何兼容 NVIDIA CUDA 的 GPU,您可能需要安装支持 GPU 的 TensorFlow。以下是在 Windows、Mac 和 Linux 上安装带 GPU 支持的 TensorFlow 的分步指南链接: www.tensorflow.org/install/

要检查您的 GPU 是否与 CUDA 兼容,请浏览 NVIDIA 官方网站上的列表:

https://developer.nvidia.com/cuda-gpus

编写代码和开发模型,可以选择 Anaconda(即 Spyder)提供的 IDE,native terminal 或 command prompt,也可以选择基于 web 的笔记本 IDE,名为 Jupyter Notebooks。对于所有与数据科学相关的实验,我强烈推荐使用 Jupyter 笔记本电脑,因为它在探索性分析和再现性方面提供了便利。我们将在书中的所有实验中使用 Jupyter 笔记本。

Jupyter 笔记本预装了 Anaconda Python 如果您使用的是虚拟环境,您可能需要使用包管理器或命令来安装它

conda install jupyter

要启动 Jupyter 笔记本,您可以使用 Anaconda Navigator 或输入命令

jupyter notebook

在命令提示符或终端中;然后,Jupyter 应该在本地主机上的默认浏览器中启动。下面的截图显示了 Jupyter 在浏览器中运行的情况。

img/475458_1_En_2_Figa_HTML.jpg

单击最右侧的“新建”按钮,并从下拉菜单中选择 Python。如果你已经安装了一个或多个虚拟环境,所有的虚拟环境都会显示在下拉列表中;请选择您所选择的 Python 环境。

选择后,您的 Jupyter 笔记本应该会打开,并准备好开始使用。下面的截图展示了一个 Jupyter 笔记本在浏览器中运行。

img/475458_1_En_2_Figb_HTML.jpg

绿色突出显示的单元格是您编写代码的地方,Ctrl + Enter 将执行选定的单元格。您可以使用控制栏中的“+”图标添加更多单元格,或者从菜单栏中浏览其他选项。如果这是你第一次使用 Jupyter,我推荐导航菜单中的可用选项。

现在我们已经设置并运行了所有需要的工具,让我们从简单的带有 Keras 的 DL 构建块开始。

Keras 中的 DL 入门

让我们从研究 DNN 及其逻辑组件开始,理解每个组件的用途以及这些构建块如何在 Keras 框架中映射。

如果您还记得第一章中的主题“分解 DL 模型”,我们已经将 DNN 中的逻辑组件定义为输入数据、神经元、激活函数、层(即神经元组)、神经元或边之间的连接、学习过程(即反向传播算法)和输出层。

让我们一个一个地看看这些逻辑组件。

输入数据

DL 算法的输入数据可以有多种类型。本质上,该模型将数据理解为“张量”。张量只不过是向量的一般形式,或者用计算机工程术语来说,是一个简单的 n 维矩阵。任何形式的数据最终都表示为一个齐次的数字矩阵。因此,如果数据是表格形式的,它将是一个二维张量,其中每一列代表一个训练样本,整个表/矩阵将是 m 个样本。为了更好地理解这一点,请看下图。

img/475458_1_En_2_Figc_HTML.jpg

您还可以颠倒训练样本的表示(即,每一行可以是一个训练样本),因此在测试示例中的学生通过/失败的上下文中,一行将指示一个学生的所有属性(他的分数、年龄等)。).对于 n 行,我们将有一个包含 n 个训练样本的数据集。但是在 DL 实验中,通常在一列中使用一个训练样本。因此,m 列将表示 m 个样本。

此外,DL 模型只能解释数字数据。如果数据集有任何分类数据,如值为“男性”和“女性”的“性别”,我们将需要将它们转换为一次性编码变量(即,简单地用值 0 或 1 表示列,其中 0 表示“男性”,1 表示“女性”,反之亦然)。

图像数据也需要转换成 n 维张量。我们不会在本书中讨论图像数据的 DL 模型,但是我想让你知道它作为输入数据的表示。图像作为三维张量存储在数据中,其中二维定义 2D 平面上的像素值,第三维定义 RGB 颜色通道的值。所以本质上,一个图像变成三维张量,n 个图像变成四维张量,其中第四维将堆叠一个三维张量图像作为训练样本。因此,如果我们有 100 张分辨率为 512 × 512 像素的图像,它们将被表示为形状为 512 × 512 × 3 × 100 的 4D 张量。

最后,在训练之前对输入值进行规范化、标准化或定标是一个很好的做法。对值进行归一化会将输入张量中的所有值带入 0–1 范围内,而标准化会将值带入平均值为 0 且标准差为 1 的范围内。这有助于减少计算,因为学习提高了很大的幅度,性能也提高了,因为激活函数(在下面讨论)表现得更合适。

神经元

在 DNN 的核心,我们有执行输出计算的神经元。一个神经元接收来自前一层神经元的一个或多个输入。如果神经元位于第一个隐藏层,它们将接收来自输入数据流的数据。在生物神经元中,当接收到具有较高影响的输入时,电信号作为输出给出。为了在数学神经元中映射该功能,我们需要一个函数,该函数对输入的和乘以相应的权重(在下面的视图中表示为 f(z ))进行操作,并根据输入以适当的值进行响应。如果接收到更高影响力的输入,则输出应该更高,反之亦然。它在某种程度上类似于激活信号(即,更高的影响->然后激活,否则去激活)。对计算出的输入数据起作用的函数称为激活函数。

img/475458_1_En_2_Figd_HTML.jpg

激活功能

激活函数是这样一种函数,它采用上图所示的组合输入 z,对其应用函数,并传递输出值,从而试图模仿激活/停用函数。因此,激活函数通过计算组合输入的激活函数来确定神经元的状态。

一个快速的想法可能会出现在你的脑海中:当我们可以传递 z 的值作为最终输出时,为什么我们真的需要一个激活函数来计算组合输出 z?这里有几个问题。首先,输出值的范围将是-无穷大到+无穷大,在这种情况下,我们没有明确的方法来定义应该发生激活的阈值。其次,网络将在某种程度上变得无用,因为它不会真正学习。这就是微积分和导数的作用。为了简化故事,我们可以说,如果你的激活函数是线性函数(基本没有激活),那么那个函数的导数就变成了 0;这成为一个大问题,因为用反向传播算法进行训练有助于向网络提供关于错误分类的反馈,从而有助于神经元通过使用函数的导数来调整其权重。如果这个值变成 0,网络就失去了这种学习能力。换句话说,我们可以说拥有 DNN 毫无意义,因为只有一层的输出与拥有 n 层的输出相似。为了简单起见,我们总是需要一个非线性激活函数(至少在所有隐藏层中)来让网络正确学习。

有多种选择可用作激活功能。最常见的是 sigmoid 函数和 ReLU(整流线性单元)。

Sigmoid 激活函数

一个 sigmoid 函数被定义为\frac{1}{\left(1+{e}^{-z}\right)},它呈现 0 和 1 之间的输出,如下图所示。非线性输出(如图所示的 s 形)很好地改善了学习过程,因为它非常类似于以下原则——较低影响:低输出较高影响:较高输出——并且还将输出限制在 0 到 1 的范围内。

在 Keras 中,sigmoid 激活函数可作为 keras.activations.sigmoid(x)使用。

我们可以简单地用import命令将其导入 Python:

import keras.activations.sigmoid

img/475458_1_En_2_Fige_HTML.jpg

ReLU 激活功能

类似地,ReLU 使用函数 **f(z) = max(0,z),**这意味着如果输出为正,它将输出相同的值,否则它将输出 0。该函数的输出范围如下图所示。

img/475458_1_En_2_Figf_HTML.jpg

Keras 提供 ReLU as

keras.activations.relu(x, alpha=0.0, max_value=None)

这个函数看起来可能是线性的,但事实并非如此。ReLU 是一个有效的非线性函数,事实上作为一个激活函数工作得非常好。这不仅提高了性能,而且大大有助于减少训练阶段的计算量。当 z 为负时,这是输出中 0 值的直接结果,从而使神经元失活。

但是由于输出为 0 的水平线,我们有时会面临严重的问题。例如,在上一节中,我们讨论了一条水平线,它是一个导数为 0 的常数,因此可能成为训练过程中的瓶颈,因为权重不容易更新。为了解决这个问题,提出了一种新的激活函数:Leaky ReLU,其中负值输出一条稍微倾斜的线而不是水平线,这有助于通过反向传播有效地更新权重。

泄漏 ReLU 定义为

  • f(z)= z;当 z >0 时

  • f(z)=∝z;当 z<0 且其中∝是定义为小常数的参数,比如 0.005 时

Keras 提供如下泄漏 ReLU:

keras.layers.LeakyReLU(X, alpha=0.0, max_value=None).

我们可以通过设置一个小常数α的值来直接使用激活函数。

img/475458_1_En_2_Figg_HTML.jpg

在 DNN 中可以使用的激活功能还有很多,在 Keras 中也可以使用。其他一些流行的是 tanh(双曲线 tan 激活),swish 激活,elu(指数线性单位),卢瑟(缩放 elu),等等。

模型

DNN 的整体结构是使用 Keras 中的模型对象开发的。这提供了一种通过一个接一个地添加新层来创建层堆栈的简单方法。

定义模型最简单的方法是使用顺序模型,这样可以很容易地创建线性层堆栈。

下面的例子展示了一个简单的顺序模型的创建,该模型有一个层,后面有一个激活。该层将具有 10 个神经元,并且将接收具有 15 个神经元的输入,并且被 ReLU 激活功能激活。

from keras.models import Sequential
from keras.layers import Dense, Activation

model = Sequential()
model.add(Dense(10, input_dim=15))
model.add(Activation('relu'))

DNN 中的层被定义为一组神经元或分层网络结构中逻辑上分离的组。随着 DL 变得越来越流行,人们对网络架构进行了多次实验,以提高各种用例的性能。用例围绕着常规的监督算法,如分类和回归、计算机视觉实验、扩展 DL 用于自然语言处理和理解、语音识别以及不同领域的组合。为了简化模型开发过程,Keras 为我们提供了几种类型的层和各种连接它们的方法。讨论所有这些问题超出了本书的范围。但是,我们将仔细查看几个层,并浏览一些重要的层,以了解其他高级用例,您可以在以后探索这些用例。

coreplayer

我们将在大多数用例中使用几个重要的层。

致密层

密集层是一个常规的 DNN 层,它将定义层中的每个神经元与前一层中的每个神经元连接起来。例如,如果第 1 层有 5 个神经元,第 2 层(密集层)有 3 个神经元,则第 1 层和第 2 层之间的连接总数将是 15 (5 × 3)。因为它容纳了各层之间的所有可能的连接,所以它被称为“密集”层。

Keras 提供具有以下默认参数的密集层。

keras.layers.Dense(units, activation=None, use_bias=True,
                   kernel_initializer='glorot_uniform',
                   bias_initializer='zeros',
                   kernel_regularizer=None,
                   bias_regularizer=None,
                   activity_regularizer=None,
                   kernel_constraint=None,
                   bias_constraint=None)

它为任何给定的层提供了许多定制。我们可以指定单元的数量(即该层的神经元)、激活类型、内核和偏差的类型初始化以及其他约束。大多数情况下,我们只是使用像单位和激活这样的参数。为简单起见,其余部分可以保留默认值。当我们在专门的用例中工作时,这些额外的参数变得很重要,在这些用例中,为给定的层使用特定类型的约束和初始化器是非常重要的。

我们还需要为 Keras 层定义输入形状。只需为第一层定义输入形状。后续层只需要定义的神经元数量。我们可以使用input_dim属性来定义输入有多少个维度。例如,如果我们有一个包含 10 个特征和 1000 个样本的表,我们需要将input_dim设置为 10,以便图层了解输入数据的形状。

示例:具有一个隐藏层和用于简单二进制分类的输出层的网络。

第 1 层有 5 个神经元,预期输入有 10 个特征;因此, input_dim =10。最后一层是输出,有一个神经元。

model = Sequential()
model.add(Dense(5,input_dim=10,activation = "sigmoid"))
model.add(Dense(1,activation = "sigmoid"))

脱落层

DL 中的 dropout 层通过在模型中引入正则化和泛化功能来帮助减少过拟合。从字面意义上来说,辍学层放弃了一些神经元或将它们设置为 0,并减少了训练过程中的计算。任意丢弃神经元的过程在减少过度拟合方面非常有效。我们将在第五章更深入地探讨这个主题,并理解过度拟合、模型概括背后的基本原理。

Keras 提供了具有以下默认参数的辍学层:

keras.layers.Dropout(rate, noise_shape=None, seed=None)

我们在 DL 模型架构中的常规层之后添加了 dropout 层。以下代码显示了一个示例:

model = Sequential()
model.add(Dense(5,input_dim=10,activation = "sigmoid"))
model.add(Dropout(rate = 0.1,seed=100))
model.add(Dense(1,activation = "sigmoid"))

其他重要层

考虑到用例的多样性,Keras 内置了大多数已定义的层。在计算机视觉用例中,输入通常是图像。有专门的图层从图像中提取特征;它们被称为卷积层。同样,对于自然语言处理和类似的用例,有一个高级的 DNN,称为循环神经网络(RNN)。Keras 为其开发提供了几种不同类型的循环层。

这个列表相当长,我们现在不会涉及其他高级层。然而,为了让您及时了解最新情况,以下是 Keras 中的一些其他重要层,它们对您将来的高级用例很有用:

您还可以在 Keras 中为不同类型的用例编写自己的层。更多详情可以在这里探讨: https://keras.io/layers/writing-your-own-keras-layers/

损失函数

损失函数是帮助网络理解它是否在正确的方向上学习的度量。用简单的话来描述损失函数,把它看作是你在一次考试中取得的分数。假设你参加了同一个主题的几次测试:你会用什么标准来理解你在每次测试中的表现?很明显,考试成绩。假设你在连续五次的语言测试中得了 56、60、78、90 和 96 分(满分为 100 分)。你会清楚地看到,考试成绩的提高表明你的表现有多好。如果考试分数一直在下降,那么结论就是你的表现在下降,你需要改变你的学习方法或材料来提高。

同样,网络如何理解它是否在每次迭代中改进它的学习过程?它使用损失函数,类似于测试分数。损失函数本质上测量目标的损失。假设你正在开发一个模型来预测一个学生是否会通过或失败,通过或失败的机会是由概率定义的。因此,1 表示他有 100%的把握通过,0 表示他肯定会失败。

该模型从数据中学习,并预测该学生的分数为 0.87,可以通过。因此,这里的实际损失是 1.00–0.87 = 0.13。如果它用一些参数更新重复该练习以便改进,并且现在实现了 0.40 的损失,它将理解它所做的改变没有帮助网络适当地学习。或者,0.05 的新损失将指示来自学习的更新或改变在正确的方向上。

基于数据结果的类型,我们在 ML 和 DL 中定义了几个标准损失函数。对于回归用例(即,最终预测将是一个连续的数字,如学生的分数、商店售出的产品数量、联络中心收到的客户来电数量等)。),以下是一些常用的损失函数:

  • 均方差-实际值和预测值之间的平均平方差。差值的平方使得更容易对差值越高的模型进行更多的惩罚。因此,差 3 将导致损失 9,但差 9 将返回损失 81。

    • 数学上的等价形式是\sum \limits_{n=1}^k\frac{{\left( Actual- Predicted\right)}²}{k}

    • 喀拉斯当量

      keras.losses.mean_squared_error(y_actual, y_pred)

  • 平均绝对误差–实际和预测之间的平均绝对误差。

    • 数学上的等价形式是\sum \limits_{n=1}^k\mid Actual- Predicted\mid

    • 喀拉斯当量

      keras.losses.mean_absolute_error(y_actual, y_pred)

  • 类似地,很少有其他变体

    • MAPE-平均绝对百分比误差

      keras.losses.mean_absolute_percentage_error

    • 男性的

      keras.losses.mean_squared_logarithmic_error

对于分类结果,您的预测将是针对一个类,如学生是否会通过(1)或失败(0),客户是否会购买,客户是否会拖欠付款,等等。一些用例可能有多个类作为结果,比如对疾病类型进行分类(A 型、B 型或 C 型),将图像分类为猫、狗、汽车、马、风景等等。

在这种情况下,由于显而易见的原因,前面定义的损失不能使用。我们需要将课堂的结果量化为概率,并根据概率估计值定义损失为预测。

Keras 中分类结果损失的几种常见选择如下:

  • **二元交叉熵:**定义分类结果为二元变量时的损失,即有两种可能的结果:(通过/失败)或(是/否)

    • 数学形式应该是

      损耗=[y * log(p)+(1y)* log(1p)]

    • 喀拉斯当量

      keras.losses.binary_crossentropy(y_actual, y_predicted)

  • **分类交叉熵:**定义分类结果为非二元时的损失,即> 2 种可能的结果:(是/否/可能)或(1 型/2 型/…n 型)

    • 数学形式应该是

      \mathrm{Loss}=-\sum \limits_i^n\kern0.375em {y}_i^{`} lo{g}_2\ {y}_i

    • 喀拉斯当量

keras.losses.categorical_crossentropy(y_actual, y_predicted)

优化者

模型训练最重要的部分是优化器。到目前为止,我们已经通过一种叫做反向传播的算法解决了向模型提供反馈的过程;这其实是一种优化算法。

为了添加更多的上下文,想象一下您定义的用于分类学生是否会通过或失败的模型结构。通过定义具有神经元数量、激活函数和输入输出形状的层序列而创建的结构在开始时用随机权重初始化。确定一个神经元对下一个神经元或最终输出的影响的权重由网络在学习过程中更新。

简而言之,具有随机权重和确定结构的网络是模型的起点。该模型可以在这一点上做出预测,但几乎总是没有价值。该网络采用一个训练样本,并使用其值作为第一层神经元的输入,然后产生具有定义的激活函数的输出。输出现在成为下一层的输入,依此类推。最终层的输出将是对训练样本的预测。这就是损失函数发挥作用的地方。损失函数有助于网络了解当前的一组权重在训练样本上的表现是好是坏。模型的下一步是减少损失。它如何知道应该对权重执行什么步骤或更新来减少损失呢?优化器功能有助于理解这一步。优化函数是一种数学算法,它使用微积分中的导数、偏导数和链规则来了解通过对神经元的权重进行微小的改变,网络会在损失函数中看到多少变化。损失函数的变化将是增加或减少,这有助于确定连接权重所需的变化方向。从输入层到输出层的一个训练样本的计算被称为一遍。通常,由于系统内存的限制,训练会分批进行。批处理是来自整个输入的训练样本的集合。网络在处理一批中的所有样本后更新其权重。这被称为一次迭代(即一批中所有样品的成功通过,随后在网络中进行重量更新)。利用逐批权重更新对输入数据中提供的所有训练样本的计算被称为一个时期。在每次迭代中,网络利用优化器函数对其权重参数(在开始时随机初始化)进行小的改变,以通过减少损失函数来改善最终预测。一步一步地,通过几次迭代和几次历元,网络更新其权重,并学习对给定的训练样本做出正确的预测。

优化器功能运行的数学解释以一种简单的方式进行了抽象,便于您理解和体会在培训过程中 DNN 中发生的后台操作。深入的数学方程和优化过程的推理超出了本书的范围。如果你对学习数学和优化算法的实际过程非常好奇,我会推荐阅读 Santanu Pattanayak(2017 年出版)的《用 TensorFlow 进行 Pro 深度学习》一书中的一章。这本书用一种非常直观的方法解释了 DL 背后的数学原理,做了一件了不起的工作。我向所有探索 DL 的博士生强烈推荐这本书。

鉴于您对整个优化过程有相当的了解,我想花一点时间来讨论 Keras 中可用的各种优化算法。

随机梯度下降

SGD 对每个训练样本执行迭代(即,在每个训练样本通过后,它计算损失并更新权重)。由于权重更新过于频繁,总的损失曲线会非常嘈杂。但是,与其他优化相比,优化速度相对较快。

权重更新的公式可以简单地表示如下:

  • 权重=权重-学习率*损失

  • 其中学习速率是我们在网络架构中定义的参数。

  • 比方说,学习率=0.01

Keras 为 SGD 提供

keras.optimizers.SGD(lr=0.01, momentum=0.0, decay=0.0, nesterov=False)

对于每个训练样本的更新,我们需要在模型训练函数中使用 batch_size=1。

为了减少 SGD 优化中的高波动,更好的方法是通过提供一个微型批次来减少迭代次数,这样就可以对一个批次中的所有样本的损失求平均值,并在批次结束时更新权重。这种方法更加成功,培训过程更加顺畅。批量大小通常设置为 2 的幂(即 32、64、128 等)。).

圣经》和《古兰经》传统中)亚当(人类第一人的名字

Adam 是 Adaptive Moment Estimation 的缩写,是 DL 中最流行、最广泛使用的优化器。在大多数情况下,您可以盲目地选择 Adam 优化器,而忘记其他优化方案。这种优化技术计算每个参数的自适应学习率。它定义损失梯度的动量和方差,并利用组合效应来更新权重参数。动量和方差一起帮助平滑学习曲线并有效地改进学习过程。

数学表示可以通过以下方式简化:

  • 权重=权重-(动量和方差的组合)

Keras 提供了 Adam 优化器

keras.optimizers.Adam(lr=0.001, beta_1=0.9, beta_2=0.999,
epsilon=None, decay=0.0, amsgrad=False)

参数β_ 1 和β_ 2 分别用于计算动量和方差。默认值非常有效,在大多数用例中不需要改变。

其他重要的优化器

还有许多其他流行的优化器也可以用于不同的 DL 模型。讨论所有这些问题超出了本书的范围。为了让您更好地了解可用的选项,我想列出几个在 Keras 中使用和可用的其他流行的优化选项:

  • 阿达格拉德

  • 阿达德尔塔

  • RMSProp

  • 阿达玛斯

  • 那达慕

每种优化技术都有自己的优缺点。我们在 DL 中经常遇到的一个主要问题是消失梯度和鞍点问题。您可以更详细地研究这些问题,同时为您的问题选择最佳的优化器。但是对于大多数用例,Adam 总是工作得很好。

韵律学

类似于损失函数,我们也在 Keras 中为模型定义度量。简单地说,指标可以理解为用于判断模型在不同的未知数据集(也称为验证数据集)上的性能的函数。度量和损失函数之间的唯一区别在于,度量的结果不用于训练关于优化的模型。它们仅用于在报告时验证测试结果。

Keras 中的一些可用指标选项如下:

  • 二进制精度- keras.metrics.binary_accuracy

  • 分类准确性-keras . metrics . caetogracal _ Accuracy

  • 稀疏分类准确性-keras . metrics . sparse _ category _ Accuracy

您还可以为您的模型指标定义定制函数。Keras 为您提供了使用用户定义的指标轻松配置模型的能力。

模型配置

现在我们已经了解了 Keras 中 DNN 的最基本的构建模块,我们可以看看最终的模型配置步骤,它将前面的所有组件编排在一起。

一旦你设计了你的网络,Keras 为你提供了一个简单的“编译”命令一步到位的模型配置过程。为了编译一个模型,我们需要提供三个参数:一个优化函数、一个损失函数和一个度量模型在验证数据集上的性能的指标。

以下示例构建了一个具有两个隐藏层的 DNN,分别具有 32 个和 16 个神经元,并具有 ReLU 激活函数。最终输出是使用 sigmoid 激活的二进制分类数值输出。我们使用 Adam 优化器编译模型,并将二进制交叉熵定义为损失函数,将“准确性”定义为验证的度量。

from keras.models import Sequential
from keras.layers import Dense, Activation

model = Sequential()
model.add(Dense(32, input_dim=10,activation = "relu"))
model.add(Dense(16,activation = "relu"))
model.add(Dense(1,activation = "sigmoid"))

model.compile(optimizer='Adam',loss='binary_crossentropy',metrics=['accuracy'])

模特培训

一旦我们配置了一个模型,我们就已经准备好了模型所需的所有部分。我们现在可以继续用数据训练模型。在训练时,为我们提供一个验证数据集来评估模型在每个时期后的表现是否符合预期,这始终是一个好的做法。该模型利用训练数据来训练自己并学习模式,在每个时期结束时,它将使用看不见的验证数据来进行预测和计算指标。验证数据集上的性能是整体性能的良好提示。

对于验证数据,通常的做法是将可用数据分成三部分,比例为 60:20:20。我们将 60%用于训练,20%用于验证,最后 20%用于测试。这个比例不是强制性的。您可以根据自己的选择灵活地改变比例。一般来说,当你有非常大的训练数据集时,比如说 n>1MN 个样本,可以把 95%用于训练,2%用于验证,3%用于测试。同样,该比率是您根据自己的判断和可用数据做出的选择。

Keras 为模型对象提供了一个拟合函数,以便使用提供的训练数据进行训练。

下面是一个调用其 fit 方法的示例模型。在这一点上,假设您已经按照前面的讨论定义并配置(编译)了模型架构。

model.fit(x_train, y_train, batch_size=64, epochs=3, validation_data=(x_val, y_val))

我们在名为 x_train 的训练数据集上训练了一个模型,实际标签在 y_train 中。我们选择批量为 64。因此,如果有 500 个训练样本,模型将在更新模型权重之前一次成批接收和处理 64 个样本。如果不可用,最后一批可能具有少于 64 个训练样本。我们已经将纪元的数量设置为三个;因此,每批 64 个样本中训练 500 个样本的整个过程将重复三次。此外,我们还提供了 x_val 和 y_val 形式的验证数据集。在每个时期结束时,模型将使用验证数据进行预测,并计算模型配置的度量参数中定义的性能度量。

现在,我们已经拥有了设计、配置和训练模型所需的所有部分,让我们将所有部分放在一起,看看它是如何工作的。

import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Activation

# Generate dummy training dataset
np.random.seed(2018)
x_train = np.random.random((6000,10))
y_train = np.random.randint(2, size=(6000, 1))

# Generate dummy validation dataset
x_val = np.random.random((2000,10))
y_val = np.random.randint(2, size=(2000, 1))

# Generate dummy test dataset
x_test = np.random.random((2000,10))
y_test = np.random.randint(2, size=(2000, 1))

#Define the model architecture
model = Sequential()
model.add(Dense(64, input_dim=10,activation = "relu")) #Layer 1
model.add(Dense(32,activation = "relu"))               #Layer 2
model.add(Dense(16,activation = "relu"))               #Layer 3
model.add(Dense(8,activation = "relu"))                #Layer 4
model.add(Dense(4,activation = "relu"))                #Layer 5
model.add(Dense(1,activation = "sigmoid"))             #Output Layer

#Configure the model
model.compile(optimizer='Adam',loss='binary_crossentropy',metrics=['accuracy'])

#Train the model
model.fit(x_train, y_train, batch_size=64, epochs=3, validation_data=(x_val,y_val))

训练模型时的输出显示如下:

img/475458_1_En_2_Figh_HTML.jpg

我们可以看到,在每个时期之后,模型打印平均训练损失和准确度以及验证损失和准确度。我们可以使用这些中间结果来判断模型的性能。在大多数大型 DL 用例中,我们会有几个时期用于训练。一个很好的实践是使用我们配置的度量标准来跟踪模型性能,以便在几个时期后看到结果。如果结果似乎对您不利,停止培训并重新访问模型架构和配置可能是个好主意。

模型评估

在所有前面的例子中,我们已经研究了模型开发步骤的特定部分,或者我们已经以模型训练结束。到目前为止,我们还没有讨论模型性能。理解你的模型在一个未知的测试数据集上的表现是非常重要的。

Keras 提供了模型对象,配备了内置的模型评估和另一个函数来预测测试数据集的结果。让我们使用前面示例中生成的训练模型和虚拟测试数据来看看这两种情况。

Keras 为时序模型提供的方法如下所示:

evaluate(x=None, y=None, batch_size=None, verbose=1, sample_weight=None, steps=None)

我们在参数 x 和 y 中提供测试数据和测试标签。如果测试数据也很大,并且预计会消耗大量内存,您可以使用批处理大小来告诉 Keras 模型按批处理方式进行预测,然后合并所有结果。

print(model.evaluate(x_test,y_test))
[0.6925005965232849, 0.521]

在 evaluate 方法中,模型返回损失值和模型配置中定义的所有指标。这些度量标签在模型属性 metrics_names 中可用。

print(model.metrics_names)
['loss', 'acc']

因此,我们可以看到,该模型在测试数据集上的总体准确率为 52%。这肯定不是一个好的模型结果,但这是意料之中的,因为我们只使用了一个虚拟数据集。

或者,您可以使用模型的预测方法,并利用实际的概率预测(对于此用例,因为是二进制分类):

#Make predictions on the test dataset and print the first 10 predictions
pred = model.predict(x_test)
pred[:10]

输出

img/475458_1_En_2_Figi_HTML.jpg

该输出可用于做出更精确的最终预测。一个简单的例子是,模型将使用 0.5 作为预测的阈值。因此,任何高于 0.5 的预测值都被归类为 1(比如说,通过),其他的被归类为 0(失败)。

根据您的使用情况,您可能希望稍微调整您的预测,以便更积极地正确预测 1(通过),因此您可能选择阈值为 0.6 而不是 0.5,反之亦然。

把所有的积木放在一起

我希望你现在能理解我们在第一章最后一节看到的第一个 DNN 模型。在理解所有的基本构建模块之前,理解模型开发中使用的代码的推理是非常困难的。

既然我们已经准备好了所有基本的必要成分,在我们结束本章之前,让我们看看更具体的用例。要做到这一点,让我们拿一个更好的数据集,看看事情是什么样子的。Keras 还提供了一些数据集供您使用。这些都是真实的数据集,通常被大多数初学者在最初的 ML 和 DL 实验中使用。

在我们的实验中,让我们选择一个流行的 Keras 数据集来开发模型。我们可以从波士顿房价数据集开始。它取自卡内基梅隆大学的 StatLib 图书馆。数据存在于亚马逊 S2 桶中,我们可以通过使用专门为数据集提供的简单 Keras 命令来下载。

#Download the data using Keras; this will need an active internet connection
from keras.datasets import boston_housing
(x_train, y_train), (x_test, y_test) = boston_housing.load_data()

数据集被直接下载到 Python 环境中,并且随时可以使用。我们来看看数据是什么样子的。我们将使用基本的 Python 命令来查看数据的类型、长度和宽度,以及内容预览。

#Explore the data structure using basic python commands
print("Type of the Dataset:",type(y_train))
print("Shape of training data :",x_train.shape)
print("Shape of training labels :",y_train.shape)
print("Shape of testing data :",type(x_test))
print("Shape of testing labels :",y_test.shape)

输出

Type of the Dataset: <class 'numpy.ndarray'>
Shape of training data : (404, 13)
Shape of training labels : (404,)
Shape of testing data : <class 'numpy.ndarray'>
Shape of testing labels : (102,)

我们可以看到,训练和测试数据集是 Python numpy 数组。Numpy 是一个 Python 库,用于处理大型多维数组。我们有 404 行数据,在训练数据集中有 13 个特征,在测试数据集中有 102 行数据有相同数量的特征。总的来说,培训和测试的比例大约是 80:20。我们可以将 402 行训练数据进一步分为 300 行用于训练,102 行用于验证。

好的,数据结构和它的形状看起来很棒。让我们快速浏览一下数据集的内容。前面的代码展示了我们有 13 列数据。为了理解实际的列名,我们需要参考 CMU 提供的数据字典。你可以在这里找到更多关于数据集的细节: http://lib.stat.cmu.edu/datasets/boston

下表展示了数据中的特征描述。列表中的最后一行是我们用例中的标签或实际房价。

|

列名

|

描述

| | --- | --- | | 卷曲 | 按城镇分列的人均犯罪率 | | 锌 | 划作 25,000 平方英尺以上地段的住宅用地比例。制成 | | 印度西北部的河流 | 每个城镇的非零售商业用地比例 | | 临床科研信息整合平台 | 查尔斯河虚拟变量(= 1,如果区域边界为河流;否则为 0) | | 氮氧化合物 | 一氧化氮浓度(百万分之一) | | 空间 | 每所住宅的平均房间数 | | 年龄 | 1940 年以前建造的自有住房比例 | | 阴间 | 到五个波士顿就业中心的加权距离 | | 皇家舞蹈学院 | 放射状公路可达性指数 | | 税 | 每 10,000 美元的全额财产税税率 | | ptratio(ptratio) | 按城镇分列的师生比例 | | B | 1000(Bk–0.63)²,其中 bk 是按城镇划分的黑人比例 | | 上帝啊 | %较低的人口地位 | | 矢量 | 以千美元为单位的自有住房的中值 |

为了查看训练数据集的内容,我们可以使用 Python 的 numpy 库为 numpy n 维数组提供的索引切片选项。

x_train[:3,:]

输出

array([[1.23247e+00, 0.00000e+00, 8.14000e+00, 0.00000e+00, 5.38000e-01, 6.14200e+00, 9.17000e+01, 3.97690e+00,
        4.00000e+00, 3.07000e+02, 2.10000e+01, 3.96900e+02, 1.87200e+01],
       [2.17700e-02, 8.25000e+01, 2.03000e+00, 0.00000e+00, 4.15000e-01, 7.61000e+00, 1.57000e+01, 6.27000e+00, 2.00000e+00, 3.48000e+02, 1.47000e+01, 3.95380e+02,
        3.11000e+00],
       [4.89822e+00, 0.00000e+00, 1.81000e+01, 0.00000e+00, 6.31000e-01, 4.97000e+00, 1.00000e+02, 1.33250e+00,
        2.40000e+01, 6.66000e+02, 2.02000e+01, 3.75520e+02,
        3.26000e+00]])

所有列都有数值,所以不需要数据转换。通常,一旦我们导入了数据集,我们将需要广泛地研究数据,并且在开始开发模型之前,几乎总是要清理、处理和扩充数据。

但是现在,我们将直接使用一个简单的模型,看看结果是什么样的。

import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Activation

#Extract the last 100 rows from the training data to create the validation datasets.
x_val = x_train[300:,]
y_val = y_train[300:,]

#Define the model architecture
model = Sequential()
model.add(Dense(13, input_dim=13, kernel_initializer="normal", activation="relu"))
model.add(Dense(6, kernel_initializer="normal", activation="relu"))
model.add(Dense(1, kernel_initializer="normal"))

# Compile model
model.compile(loss='mean_squared_error', optimizer="adam", metrics=['mean_absolute_percentage_error'])

#Train the model
model.fit(x_train, y_train, batch_size=32, epochs=3, validation_data=(x_val,y_val))

输出

Train on 404 samples, validate on 104 samples
Epoch 1/3
404/404 [==============================] - 2s 4ms/step - loss: 598.8595 - mean_absolute_percentage_error: 101.7889 - val_loss: 681.4912 - val_mean_absolute_percentage_error: 100.0789
Epoch 2/3
404/404 [==============================] - 0s 81us/step - loss: 583.6991 - mean_absolute_percentage_error: 99.7594 - val_loss: 674.8345 - val_mean_absolute_percentage_error: 99.2616
Epoch 3/3
404/404 [==============================] - 0s 94us/step - loss: 573.6101 - mean_absolute_percentage_error: 98.3180 - val_loss: 654.3787 - val_mean_absolute_percentage_error: 96.9662

我们已经为回归用例创建了一个简单的双隐藏层模型。我们选择 MAPE 作为度量单位。一般来说,这不是研究模型性能的最佳选择,但是它的优点是理解结果简单。它给出了一个简单的误差百分比值,比如 10%的误差。所以,如果你知道你的预测的平均范围,你可以很容易地估计出预测的结果。

现在让我们训练模型,并使用评估函数来研究模型的结果。

results = model.evaluate(x_test, y_test)

for i in range(len(model.metrics_names)):
    print(model.metrics_names[i]," : ", results[i])

输出

102/102 [==============================] - 0s 87us/step
loss  :  589.7658882889093
mean_absolute_percentage_error  :  96.48218611174939

我们可以看到,MAPE 约为 96%,对于模型性能来说,这实际上不是一个很好的数字。这将转化为我们的模型预测约 96%的误差。所以,总的来说,如果一栋房子的价格是 10K,我们的模型会预测到 2 万英镑。

在 DL 中,模型在每次迭代后更新权重,并在每个时期后进行评估。由于更新非常小,一般模型通常需要相当多的历元才能正确学习。为了再次测试性能,让我们将时期的数量从 3 个增加到 30 个。这将显著增加计算量,并且可能需要一段时间才能执行。但由于这是一个相当小的数据集,用 30 个历元进行训练应该不成问题。它应该在您的系统上运行大约 1 分钟。

#Train the model
model.fit(x_train, y_train, batch_size=32, epochs=30, validation_data=(x_val,y_val))

输出

Train on 404 samples, validate on 104 samples
Epoch 1/1000
404/404 [==============================] - 0s 114us/step - loss: 536.6662 - mean_absolute_percentage_error: 93.4381 - val_loss: 580.3155 - val_mean_absolute_percentage_error: 88.6968
Epoch 2/1000
404/404 [==============================] - 0s 143us/step - loss: 431.7025 - mean_absolute_percentage_error: 79.0697 - val_loss: 413.4064 - val_mean_absolute_percentage_error: 67.0769

跳过中间时期的输出。

(仅添加最后三个时期的输出,即 28 至 30)

Epoch 28/30
404/404 [==============================] - 0s 111us/step - loss: 6.0758 - mean_absolute_percentage_error: 9.5185 - val_loss: 5.2524 - val_mean_absolute_percentage_error: 8.3853
Epoch 29/30
404/404 [==============================] - 0s 100us/step - loss: 6.2895 - mean_absolute_percentage_error: 10.1037 - val_loss: 6.0818 - val_mean_absolute_percentage_error: 8.9386
Epoch 30/30
404/404 [==============================] - 0s 111us/step - loss: 6.0761 - mean_absolute_percentage_error: 9.8201 - val_loss: 7.3844 - val_mean_absolute_percentage_error: 8.9812

如果我们仔细观察验证数据集的损失和 MAPE,我们可以看到显著的改进。从上例的 96%降到了现在的 8.9%。

让我们看看测试结果。

results = model.evaluate(x_test, y_test)

for i in range(len(model.metrics_names)):
    print(model.metrics_names[i]," : ", results[i])

输出

102/102 [==============================] - 0s 92us/step
loss  :  22.09559840782016
mean_absolute_percentage_error  :  16.22196163850672

我们可以看到,结果有了显著的改善,但验证数据集和测试数据集的 MAPE 之间似乎仍有很大的差距。如前所述,这种差距表明模型过度拟合,或者简单地说,学习过程过于复杂。我们将在下一章中详细讨论减少 DNNs 中过拟合的步骤,以获得更大更好的用例。到目前为止,我们已经成功地在真实数据集(虽然很小)上探索了 Keras,并在 Keras 中的 DL 构建块上使用了我们的知识。

摘要

在本章中,我们通过动手练习以及主题的上下文深度深入探讨了 Keras。我们研究了 DL 的基本构件及其在 Keras 中的实现。在使用 Keras 开发 DNN 模型时,我们研究了如何将不同的构件组合在一起。在下一章中,我们将开始一步一步地探索一个真实的用例,通过探索、清理、提取和应用必要的转换来为开发 DL 模型准备好数据。

三、用于监督学习的深度神经网络:回归

在第 1 和 2 章中,我们探讨了 DL 的主题,并研究了 DL 是如何从 ML 发展而来,以解决一个有趣的问题领域的。我们讨论了对 DL 框架的需求,并简要探讨了市场上一些流行的框架。然后,我们研究了 Keras 的特殊之处,并花了一些时间研究了为开发 DNNs 而提供的基本构建模块,同时也从整体上理解了 DL 模型背后的直觉。然后,我们将从实践练习中获得的所有知识整合在一起,为波士顿房价用例开发一个婴儿神经网络。

现在我们已经对不同的 DL 构建模块和相关的科学有了一个相当好的理解,让我们在本章中探索一个回归用例的实际 DNN。

入门指南

人工智能作为一个领域的演变以及该领域越来越多的研究人员和从业人员创造了一个成熟和仁慈的社区。今天,很容易获得工具、研究论文、数据集,事实上甚至是基础设施来将 DL 作为一个领域来实践。对于我们的第一个用例,我们需要一个数据集和一个业务问题来开始。这里有几个受欢迎的选择。

  • ****:www.kaggle.com/

    Kaggle 是世界上最大的数据科学家和机器学习者社区。它最初是一个在线 ML 竞赛论坛,后来发展成为一个成熟的平台,强烈推荐给数据科学领域的每个人。它仍然主办 ML 竞赛,还提供 ML 数据集、内核或社区开发的解决 ML 问题的脚本、ML 作业,以及为主办的竞赛和公共数据集开发和执行 ML 模型的平台。

*** 美国政府公开数据 : www.data.gov/

提供对数千个农业、气候、金融等数据集的访问。

*   **印度政府公开数据** **:** [`https://data.gov.in/`](https://data.gov.in/)

为印度的人口统计、教育、经济、工业等提供开放数据集。

*   **亚马逊网络服务数据集** **:** [`https://registry.opendata.aws/`](https://registry.opendata.aws/)

提供了一些来自 NASA NEX 和 Openstreetmap、德意志银行公共数据集等的大型数据集。

*   **谷歌数据集搜索** **:** [`https://toolbox.google.com/datasetsearch`](https://toolbox.google.com/datasetsearch)

这相对较新,仍处于测试阶段(在撰写本书时),但非常有前途。它提供了一个简单的搜索查询访问成千上万的公共数据集进行研究实验。它聚集了来自几个公共数据集存储库的数据集。

*   **UCI ML 资源库**??:[`https://archive.ics.uci.edu/ml/`](https://archive.ics.uci.edu/ml/)

探索 ML 和 DL 数据集的另一个流行的存储库。** 

**我们将使用 Kaggle 公共数据存储库来获取 DL 用例的数据集。我们将使用 Rossmann 商店销售数据集,该数据集可在 www.kaggle.com/c/rossmann-store-sales/data 获得。这是几年前举办的一个非常受欢迎的比赛,有相当大的数据集。你需要向 Kaggle 注册并接受竞赛规则,才能下载数据。如果你还没有注册 Kaggle,我强烈建议你注册。每个数据科学专业人士都应该密切关注 Kaggle,因为它为数据科学提供了很好的学习、实验和讨论平台。

从数据集来看,只需要 train.csv 和 store.csv,分别是 38MB 和 45KB 左右。请下载数据并将其保存在单独的文件夹中。

问题陈述

Rossmann 是德国最大的连锁药店之一,业务遍及欧洲。截至 2018 年,他们在欧洲拥有超过 3900 家店铺,年营业额为 90 亿欧元。我们的任务是预测某一天几个确定的商店的销售额。

现在,让我们从纯商业的角度来看问题。您需要问的第一个问题是:谁是业务问题的最终利益相关者,他将如何利用解决方案?好吧,鉴于这是一个在线数据科学竞赛,我们不会对这个问题有一个有效的答案,但我们或多或少可以弄清楚一个会是什么样子。

首先,我们需要用一种稍微有策略的方式重新构建问题陈述,以便能够将问题陈述表示为设计解决方案。有几个被市场认可的问题解决框架来帮助以标准方式定义和表示问题陈述,以便更有效地解决问题。麦肯锡的“情境-复杂-解决”(SCR)和穆适马公司的“情境复杂问题”(SCQ)是最受欢迎的框架。我们将利用上述框架之一,以更有效和简洁的方式来表示我们的问题陈述。但是让我们首先理解为什么这很重要。

为什么用设计原则表达问题陈述很重要?

大多数大型复杂的问题需要详细的设计、同行评审、方法和策略的验证、大量的头脑风暴,甚至可能在开始之前需要一个小的概念验证。企业软件开发就是一个经典的例子。您将有一个团队来定义业务需求并记录它们以供将来参考,设计一个高层次的图表,然后是低层次的设计,最后详细说明每个软件组件的细节以及最终解决方案的外观。在任何时候,如果一个新的工程师加入团队进行协作,设计文档、方法和业务需求将帮助他理解更大的图景,而不需要单独讨论。此外,在任何时候,设计和方法都有助于总体目标的顺利实现。

在开始任何工作之前,数据科学和 ML/DL 中的问题需要类似的方法。虽然不可能在一开始就起草完整的解决方案,但是考虑到整个过程是迭代的和探索性的,我们仍然可以更好地表现问题和解决方案的高层次方法。为了理解设计原则框架下的问题定义,让我们来看一个。

设计 SCQ

由 Mu 适马公司设计和发布的 SCQ 框架是一种流行的框架,用于表示咨询公司中的问题。它把问题分成三个简单的组,用正确的问题展开每个组,最后连接到期望的未来状态。

这四个组成部分可以定义如下:

  • 期望的未来状态

    问题解决后我们想要达到的最终状态。

  • 情况

    对整个问题陈述的简要叙述,详细描述了风险承担者所面临的问题。这个一般最多包两行。

  • 并发症

    确定阻碍利益相关者从当前情况向期望的未来情况过渡的主要障碍。

  • 问题

为了减少障碍,需要回答的关键问题。

对于我们的用例,我们可以定义如下图所示的 SCQ。

img/475458_1_En_3_Figa_HTML.jpg

有了 SCQ,我们现在对问题陈述有了更全面的理解。我们知道,有一个营销团队正在设计针对特定商店的促销活动,以锁定目标客户并增加整体收入,同时更明智地使用资源。因此,他们不希望向无论如何都表现出色的商店提供促销活动。如果他们能够看到预计的未来销售额,他们可以根据为实现预期目标所需的折扣和促销的定义阈值,将一些商店分为“低”、“中”和“高”。

团队遇到了障碍,因为他们没有办法估计给定商店的未来销售额。因此,为了解决这个问题,我们提出以下问题:“我们如何估计一个商店的未来销售额?”鉴于路障已经被克服,营销团队现在有办法研究和估计未来的商店销售,从而设计更有效的促销活动。

设计解决方案

商业问题的关键问题的答案现在可能很容易猜到了。我们将开发一个 ML 模型,它可以将商店的销售额作为内部、外部和时间(基于时间)属性的函数来学习,然后根据可用的属性来预测未来的销售额。

这可能看起来像一个时间序列预测问题,在这种情况下,我们纯粹将销售额或类似目标定义为时间的函数(即,考虑历史每周或每天的销售额,并通过模拟趋势和模式来预测未来的数据点)。但这仅适用于我们只需要对一家商店进行估算的情况。对于 1000 家商店,手动研究每周销售额并开发模型来估计未来销售额是一个费力且不可行的解决方案。或者,我们可以使用全局时间序列模型来解决这个问题(即,只开发一个用于所有商店的模型)。虽然这肯定是可能的,但是预测的结果不会给利益相关者增加任何价值,因为它很可能会偏离目标。

相反,我们可以通过将这个问题从时间序列预测问题转化为回归问题来开发一个更有效的模型。万一这很难理解,让我们用一个例子来简化它。任何用例可用的数据都可以分为时间序列或横截面。在时间序列数据中,每个训练样本(即一行数据)都与和时间序列相关联的另一个样本有关系。日销售额或周销售额是时间序列数据的适当示例,因为一周的销售额与前几周的销售额有关系。在横截面数据中,每个训练样本都是独立的,与其他样本没有基于时间的关系。客户的广告点击或信用客户通过信用卡提供商进行的交易都是横截面数据的例子。这两个样本之间没有基于时间的关系。

在我们的用例中,我们可以将数据表示为

作为商店功能的销售额+其他属性

而不是基于时间序列的模型定义为

作为时间函数的销售额

通过这种方式,我们可以定义一个模型,该模型可以从各种商店和其他外部属性(我们将使用数据探索这些属性)中学习模式,以预测预期的销售额。随着我们探索数据和更接近模型开发,这个过程将变得清晰。现在让我们从探索数据开始。

探索数据

我希望你已经在注册账户并接受比赛规则后从 Kaggle 链接下载了数据集。如果您还没有,以下是详细的步骤:

  1. 进入 Kaggle 首页: www.kaggle.com

  2. 使用“注册”创建新帐户或使用现有帐户登录。

    前往罗斯曼专卖店销售竞赛: www.kaggle.com/c/rossmann-store-sales/data

  3. 导航到页面中间,找到“全部下载”选项。你会得到一个竞赛规则页面,你必须阅读,然后接受这些条件。一旦它们被接受,就可以下载了。

  4. 将下载的数据集解压缩并移动到一个新文件夹中,以供练习使用。

您需要两个重要的文件:

  • train.csv

  • 大.csv

一旦数据准备就绪,我们就可以开始使用 Python 来探索和分析数据。你可以打开 Jupyter 笔记本,正如在第二章中所讨论的,它已经安装了 Anaconda。请在您的终端或命令提示符下使用命令'jupyter notebook'并按回车键;然后,Jupyter 应该会在您的默认浏览器中打开。您可以为我们的 DL 练习创建一个新笔记本。或者,您也可以使用来自 Anaconda 的任何 Python IDE 或 Spyder IDE 但是,强烈推荐 Jupyter。

为了探索数据,我们需要基本的 Python 命令。我们将使用懒惰编程方法来学习用 Python 进行数据探索;也就是说,当我们遇到一个代码块或者一个新的包时,我们会讨论它的细微差别。如果您是 Python 的新手,只需通读代码块和注释,然后通读代码块的解释就足够了。

让我们首先将数据导入我们的系统开始分析。下面的代码片段导入 Python 包“pandas ”,该包提供了可用的函数来导入、浏览、操作、转换、可视化以及以所需的形式导出数据。

import pandas as pd
df = pd.read_csv("/Users/jojomoolayil/Book/Ch3/Data/train.csv")

数据被导入到变量df *中。*因为 Python 是面向对象的,我们现在可以使用熊猫相关的函数作为对象的方法。

一旦导入了数据,我们需要研究的第一件事就是数据的长度、宽度和类型。下面的代码片段将数据的形状打印为长×宽,然后展示数据集的前五行。

print("Shape of the Dataset:",df.shape)
#the head method displays the first 5 rows of the data
df.head(5)

输出

Shape of the Dataset: (1017209, 9)

img/475458_1_En_3_Figb_HTML.jpg

同样,让我们导入第二个数据集 store.csv,并查看它的长度、宽度和前 5 行。

store = pd.read_csv("/Users/jojomoolayil/Book/Ch3/Data/store.csv")
print("Shape of the Dataset:",store.shape)

#Display the first 5 rows of data using the head method of pandas dataframe
store.head(5)

输出

Shape of the Dataset: (1115, 10)

img/475458_1_En_3_Figc_HTML.jpg

如您所见,训练数据集有 1,017,209 行和 9 列。head 方法展示了 dataframe 的前 5 行,我们可以通过浏览不言自明的列名来查看数据中的内容。在 train 数据集中,我们有不同日期商店的数据。我们有特定一天的总销售额和几个附加属性。

同样,商店数据有 1,115 行和 10 列数据。它为我们提供了额外的商店属性,这些属性描述了商店的特征,如产品组合类型、竞争情况以及促销相关属性。

查看数据字典

让我们看看 Kaggle 上比赛页面提供的数据字典。如果你错过了,你可以在这里通读一些定义。

  • 店铺 : 每个店铺的唯一 ID

  • 销售额 : 某一天的营业额(我们的目标 y 变量)

  • 客户 : 某一天的客户数量

  • 开门 : 店铺是否开门的指标:0 =关门,1 =开门

  • 法定假日 : 表示法定假日。通常,除了少数例外,所有的商店在法定假日都不营业。请注意,所有学校在公共假日和周末都关闭。a =公共假日,b =复活节,c =圣诞节,0 =无

  • 学校假期 : 表示(商店,日期)是否受到公立学校关闭的影响

  • 商店类型 : 区分四种不同的商店模式:a、b、c、d

  • 产品组合 : 描述产品组合级别:a =基本,b =附加,c =扩展

  • 竞争距离 : 距离最近的竞争对手商店的米数

  • 竞争优势[月/年] : 给出最近的竞争对手开业的大概年月

  • 促销 : 表示当天商店是否有促销活动

  • Promo2 : Promo2 是部分店铺的持续促销:0 =店铺不参与,1 =店铺参与

  • **Promo2 since[Year/Week]****:**描述商店开始参与 promo 2 的年份和日历周

  • 促销间隔 : 描述促销 2 开始的连续间隔,命名促销重新开始的月份(例如,“二月、五月、八月、十一月”表示该商店的每一轮促销在任何给定年份的二月、五月、八月和十一月开始)

为了将所有数据点放在一起,我们需要创建一个具有商店和促销功能的单一数据框架。我们可以通过连接“store”列上的两个数据帧来实现这一点,该列代表商店 ID。Pandas 提供了一个“合并”功能,类似于 SQL 中的 join 语句。我们可以使用一个或多个列作为连接键,在一个或多个数据帧上执行左、右、内和全外连接。

以下代码片段连接训练和存储数据帧以创建新的数据帧。

df_new = df.merge(store,on=["Store"], how="inner")
print(df_new.shape)

输出

(1017209, 18)

该形状向我们展示了两个数据帧中的所有列都在一个统一的数据帧中。对行数的简单检查(在我们的例子中是一致的)有助于我们理解连接以预期的方式工作。

现在我们有了统一形式的数据,让我们开始探索数据集以理解如下几个重要问题:我们有多少商店的数据?我们有多长时间的数据?一天的平均销售额是多少?商店在日常销售中彼此差别很大吗?让我们找出答案。

我们将首先找到数据中唯一商店的数量、我们拥有数据的唯一天数以及所有商店的平均销售额。

print("Distinct number of Stores :", len(df_new["Store"].unique()))
print("Distinct number of Days :", len(df_new["Date"].unique()))
print("Average daily sales of all stores : ",round(df_new["Sales"].mean(),2))

输出

Distinct number of Stores : 1115
Distinct number of Days : 942
Average daily sales of all stores :  5773.82

我们可以看到,总共有 1,115 家不同的商店有 942 天的数据,平均每天销售额为 5,773。

pandas dataframe 的unique方法返回所选列的唯一元素列表,而len函数返回列表中元素的总数。dataframe 的mean方法返回所选列的平均值,在我们的例子中是销售额。

您可能已经注意到,使用 Python 非常简单。对于几乎所有可以在数据上执行的主流任务,pandas 都提供了一个简单的方法,只需几个参数就可以使用。让我们继续研究数据集以理解其他列。

查找数据类型

我们需要知道数据帧中每个元素的数据类型。到目前为止,我们只看到了数据集中的实际内容;显示为数字的列可能在内部存储为字符。让我们看看最终合并的数据集中每一列的数据类型。

df_new.dtypes

输出

Store                          int64
DayOfWeek                      int64
Date                          object
Sales                          int64
Customers                      int64
Open                           int64
Promo                          int64
StateHoliday                  object
SchoolHoliday                  int64
StoreType                     object
Assortment                    object
CompetitionDistance          float64
CompetitionOpenSinceMonth    float64
CompetitionOpenSinceYear     float64
Promo2                         int64
Promo2SinceWeek              float64
Promo2SinceYear              float64
PromoInterval                 object
dtype: object

我们在这里看到混合的数据类型,大部分是int,其余的是 object 或 float。对象是字符数据类型的一种形式。从技术上讲,我们必须了解数据集中的每一列或每一个特征,才能开发出有效的模型。在模型开发中,大部分时间消耗在数据工程、清理和探索上。

与时间一起工作

我们现在对Store列有了一个大致的了解;我们来看看DayOfWeek的特点。

df_new["DayOfWeek"].value_counts()

输出

5    145845
4    145845
3    145665
2    145664
7    144730
6    144730
1    144730
Name: DayOfWeek, dtype: int64

正如我们所料,对于“星期几”特性,我们可以看到七个不同的值,每个值都有相似的记录数。假设我们已经将日期作为一个特性,我们可以直接使用日期列来创建星期几,还可以创建一些其他特性。让我们创建额外的特性来帮助我们的模型更好地学习模式。我们将从日期变量中创建周数、月、日、季度和年作为特性。同样,由于我们已经创建了与时间相关的特征,我们可以添加一个基于气候和季节的新特征。考虑到商店在欧洲,我们可以参考标准的季节周期,用春、夏、秋和冬的值创建一个新的季节特征。Pandas 提供了易于使用的函数来提取与日期相关的特征;与季节相关的特征可以用简单的“if else”等价约定来创建。

#We can extract all date properties from a datetime datatype
import numpy as np
df_new['Date'] = pd.to_datetime(df_new['Date'], infer_datetime_format=True)
df_new["Month"] = df_new["Date"].dt.month
df_new["Quarter"] = df_new["Date"].dt.quarter
df_new["Year"] = df_new["Date"].dt.year
df_new["Day"] = df_new["Date"].dt.day
df_new["Week"] = df_new["Date"].dt.week

df_new["Season"] = np.where(df_new["Month"].isin([3,4,5]),"Spring",
                      np.where(df_new["Month"].isin([6,7,8]),"Summer",
                           np.where(df_new["Month"].isin([9,10,11]),"Fall",
                                np.where(df_new["Month"].isin([12,1,2]),"Winter","None"))))

#Using the head command to view (only) the data and the newly engineered features
print(df_new[["Date","Year","Month","Day","Week","Quarter","Season"]].head())

输出

        Date  Year  Month  Day  Week  Quarter  Season
0 2015-07-31  2015      7   31    31        3  Summer
1 2015-07-30  2015      7   30    31        3  Summer
2 2015-07-29  2015      7   29    31        3  Summer
3 2015-07-28  2015      7   28    31        3  Summer
4 2015-07-27  2015      7   27    31        3  Summer

预测销售额

列表中的下一个特性是Sales列。这是我们的目标变量(即,我们开发模型来预测变量)。

#Import matplotlib, python most popular data visualizing library
import matplotlib.pyplot as plt
%matplotlib inline

#Create a histogram to study the Daily Sales for the stores
plt.figure(figsize=(15,8))
plt.hist(df_new["Sales"])
plt.title("Histogram for Store Sales")
plt.xlabel("bins")
plt.xlabel("Frequency")
plt.show()

输出

img/475458_1_En_3_Figd_HTML.jpg

直方图有助于我们在高层次上理解数据的分布。从前面的图中,我们可以看到数据范围是从 0 到 40,000,但是在 20,000 之后几乎没有任何数据。这表明大多数商店的销售额在 0-20,000 之间,只有少数商店的销售额超过 20,000。移除这些异常值可能是值得的,因为这有助于模型更好地学习。

浏览数字列

接下来,我们还有几个数字列要探索。为了节省时间,我们可以使用 pandas 内部提供的hist功能。Pandas 还通过内部包含 matplotlib 来提供绘图功能。以下命令帮助我们可视化数据集中所有数字列的直方图。

#Use the  histogram function provided by the Pandas object
#The function returns a cross-tab histogram plot for all numeric columns in the data
df_new.hist(figsize=(20,10))

输出

img/475458_1_En_3_Fige_HTML.jpg

让我们分析上图中展示的直方图的结果。我们可以看到,特征 Promo、Promo2、学校假期和 Open 实际上是二元分类特征:它们表示两个与性别相似的可能值:男性或女性。因此,这些实际上是分类特征,但已经编码为数字列。这太好了;我们不需要进一步处理它们,因为 DL 模型只理解数值。

Promo2 在两个不同的值之间分布良好,而 Promo 有更多的“1”记录,Open 有大部分商店记录为“1”。“Open”的值之间的分布是有意义的,因为除了法定假日,商店在大多数日子都是开放的。

大多数商店的顾客数量在 0 到 2,000 之间。一些商店每天有多达 7000 名顾客,但这些都是异常值,我们可能需要在建模前修复它们。

下一组数值变量是 Promo2SinceWeek 和 Promo2SinceYear 这些显示了相对均匀分布的特征。其余的直方图基本上是不言自明的。

我们忽略了一个重要的方面:数据集中是否有任何缺失的数据?前面的图通常不考虑缺失值;相反,它们排除了图中的空值。

让我们以相关的百分比形式来看看每一列中缺失数据点的数量(如果有)。

dataframe 的isnull()命令返回一个矩阵,其中包含所有数据点的真值,无论是否为空。将此输出传递给 sum 函数可以计算每个组中的空值数量。我们将这个数字除以总行数,再乘以 100,得到百分比形式的最终数字。

df_new.isnull().sum()/df_new.shape[0] * 100

输出

Store                         0.000000
DayOfWeek                     0.000000
Date                          0.000000
Sales                         0.000000
Customers                     0.000000
Open                          0.000000
Promo                         0.000000
StateHoliday                  0.000000
SchoolHoliday                 0.000000
StoreType                     0.000000
Assortment                    0.000000
CompetitionDistance           0.259730
CompetitionOpenSinceMonth    31.787764
CompetitionOpenSinceYear     31.787764
Promo2                        0.000000
Promo2SinceWeek              49.943620
Promo2SinceYear              49.943620
PromoInterval                49.943620
Month                         0.000000
Quarter                       0.000000
Year                          0.000000
Day                           0.000000
Week                          0.000000
dtype: float64

突出显示的行显示了相应列中大量缺失的数据点。我们可以看到Promo2SinceWeekPromo2SinceYearPromoIntervalCompetitionOpenSinceMonthCompetitionOpenSinceYear有超过 30%的空值。这是一个巨大的损失,我们对此无能为力。根据经验,如果有 0%到 10%之间的任何损失,我们可以尝试填充缺失的点并使用该特性。但是,30%在技术上超出了可用范围。另一方面,我们可以看到CompetitionDistance有大约 0.25%的缺失值。这将更容易处理和修复。

有几种方法可以处理丢失的数据点。最常见的方法,如“替换为平均值”和“替换为模式”,易于使用且效果相对较好。然而,这将完全取决于您的功能。如果在一个非常关键的特性中有 2%的损失,您可能想要利用一个更好的估计方法来填补缺口。在这种情况下,流行的技术是对缺失值进行聚类处理,开发更小的回归模型来估计缺失值,等等

现在,在这个用例中,我们将使用模式来填充缺失值的空白。这很简单,只需找到列的模式(列中最常见的值),忽略空值并用模式替换所有空值。下面的代码片段展示了 Python 中的方法。

#Replace nulls with the mode
df_new["CompetitionDistance"]=df_new["CompetitionDistance"].fillna(df_new["CompetitionDistance"].mode()[0])

#Double check if we still see nulls for the column
df_new["CompetitionDistance"].isnull().sum()/df_new.shape[0] * 100

输出

0.0

理解分类特征

既然我们已经对所有数字特征有了基本的了解,现在让我们来看看分类特征。总之,我们有StoreType、分类和新创建的季节特征作为分类特征。虽然“Open”、“Promo”、“Promo2”、“??”等是二元分类变量,但它们已被存储为数值,并已显示在我们研究的直方图中。现在让我们花点时间来看看剩下的三个特性。研究分类变量的最好方法是研究单个类别对目标变量的影响。我们可以通过绘制特征中不同类别值的平均销售额来实现这一点。为了实现这一点,我们可以利用“seaborn”,这是另一个强大且易于使用的 Python 可视化库,类似于 matplotlib,但提供了更漂亮的视觉效果。

import seaborn as sns  #Seaborn is another powerful visualization library for Python
sns.set(style="whitegrid")

#Create the bar plot for Average Sales across different Seasons
ax = sns.barplot(x="Season", y="Sales", data=df_new)

img/475458_1_En_3_Figf_HTML.jpg

#Create the bar plot for Average Sales

across different Assortments
ax = sns.barplot(x="Assortment", y="Sales", data=df_new)

img/475458_1_En_3_Figg_HTML.jpg

#Create the bar plot for Average Sales across different Store Types
ax = sns.barplot(x="StoreType", y="Sales", data=df_new)

img/475458_1_En_3_Figh_HTML.jpg

正如您所看到的,seaborn 包在内部计算了所提供的分类列的类的平均销售额,并显示了一个漂亮的条形图,展示了与我们的目标变量的关系。如果需要,我们可以将聚合函数更改为不同的函数;这可以通过使用barplot功能中的“估算器”参数来改变。不同季节的销售额似乎没有什么不同;然而,各种产品的销售似乎有增长的趋势。分类为“b”的商店通常销售额最高。商店类型还显示了与不同商店类型的销售额之间的独特关系。我们还可以看到“b”类商店的销售额也相当高。然而,在我们结束我们的观察之前,还需要进行一次检查来验证这些假设。如果前面提到的不同类型商店的数量不成比例或有偏差,该怎么办?在这种情况下,我们的观察可能是错误的。为了巩固我们对观察结果的理解,我们可以使用带有一个附加参数设置的相同的barplot函数简单地检查每个类别中的数据点数量。我们将使用一个新的聚合函数来显示计数,作为条形图的指标。下面的代码片段可视化了我们前面研究的同一组分类变量的条形图,尽管是计数。

ax = sns.barplot(x="Season", y="Sales", data=df_new,estimator=np.size)

img/475458_1_En_3_Figi_HTML.jpg

ax = sns.barplot(x="Assortment", y="Sales", data=df_new,estimator=np.size)

img/475458_1_En_3_Figj_HTML.jpg

ax = sns.barplot(x="StoreType", y="Sales", data=df_new,estimator=np.size)

img/475458_1_En_3_Figk_HTML.jpg

我们可以注意到,一个类别中不同类别之间的数据点分布是偏斜的。对StoreType和分类的简单检查显示b在数据集中的商店或数据点数量明显较少。因此,我们最初对观察到的关系的理解是不正确的。

因此,假设我们已经研究了数据集的长度、宽度、内容、性质和摘要,并进一步分别研究了连续(数值)和分类特征以获得对数据的良好理解,我们现在可以继续为开发 DL 模型准备数据。

数据工程

如前所述,DL 模型只理解数字数据。因此,对于模型训练数据,所有存储为文本列的分类特征都需要转换为一次性编码形式。

一键编码是将分类列表示为扩展的二进制标记矩阵的简单过程。因此,具有三个不同值的分类特征,比如“A 类”、“B 类”和“C 类”,可以用三列而不是一列来表示,其中每一列都代表一个单独类别值的二进制标志。这将在下面的例子中进一步总结。

img/475458_1_En_3_Figl_HTML.jpg

在我们的数据集中,我们有三个分类变量需要转换;它们是季节、商店类型和分类。然而,在分类变量的上下文中,星期几、月、日、季度以及实际上商店 ID 也可以被定义为分类变量。乍一看,这似乎违反直觉,但实际上,这些特性有一定数量的不同类别;例如,星期几只能是 1 到 7 之间的值。如果不同的类之间存在显著的差异,那么将它们表示为一个带有数字的列可能不是一个好主意。例如,周日和周一的销售额完全不同,但在内部,如果周日= 0,周一= 1,周二= 2 等等,则从周日到周一的阶梯增长与从周一到周二的阶梯增长是不同的。在这种情况下,用一个 hot 编码版本表示一个分类列是一个好的实践。但是我们在哪里停下来?在有些情况下,一个特性有有限但数量非常大的类,比如我们示例中的商店号有 1000 个。将商店编号表示为 1,000 个特征或仅表示为 1 个具有数值的特征有用吗?

这个问题的答案并不简单。最好的情况肯定是用它的一个热编码版本来表示商店号,但是这带来了数据大小的巨大问题。展开所有必要的列后,我们可能会得到一个大约有 1,200 个宽列和 100 万行的训练数据集。这将是一个 10GB 的数据集。在 RAM 有限的普通计算机上,开发具有这种规模的训练数据的模型可能是一个挑战。

为了克服这一困境,您可以回归到一个简单的经验法则:如果您有良好的硬件资源(GPU、RAM 和计算能力),就继续进行一键编码转换。但是如果您的资源有限,那么只转换那些看起来最重要的,并且有相当少的不同类的资源。然后,用模型性能结果反复验证实验是否有效。如果存在严重的折衷,您可能需要重新考虑训练数据扩充和要使用的硬件基础设施。

在这个用例中,我们首先将季节、分类、月份、年份、季度、星期和商店类型处理成一个热编码形式,暂时将星期、星期和商店保留为连续的。在我们建立了几个模型并研究了它们的性能之后,我们将再次讨论这个问题。

为了将分类列转换成一次性编码的版本,Python 在 sklearn 包中提供了预处理模块,该模块具有丰富且易于使用的函数。下面的代码片段将训练数据帧设计成模型开发所需的最终形式。

#Define a variable for each type of feature
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import OneHotEncoder
target = ["Sales"]
numeric_columns = ["Customers","Open","Promo","Promo2","StateHoliday","SchoolHoliday","CompetitionDistance"]
categorical_columns = ["DayOfWeek","Quarter","Month","Year", "StoreType","Assortment","Season"]

#Define a function that will intake the raw dataframe and the column name and return a one hot encoded DF
def create_ohe(df, col):
    le = LabelEncoder()
    a=le.fit_transform(df_new[col]).reshape(-1,1)
    ohe = OneHotEncoder(sparse=False)
    column_names = [col+ "_"+ str(i) for i in le.classes_]
return(pd.DataFrame(ohe.fit_transform(a),columns =column_names))

#Since the above function converts the column, one at a time
#We create a loop to create the final dataset with all features
temp = df_new[numeric_columns]
for column in categorical_columns:
    temp_df = create_ohe(df_new,column)
    temp = pd.concat([temp,temp_df],axis=1)

可以使用 shape 命令和数据集中的不同数据类型对前面的数据工程步骤的输出进行双重检查。如果有任何非整数列,在继续之前,我们将剩下最后一步将其转换为数字。

print("Shape of Data:",temp.shape)
print("Distinct Datatypes:",temp.dtypes.unique())

输出

Shape of Data: (1017209, 44)
Distinct Datatypes: [dtype('int64') dtype('O') dtype('float64')]

正如您所看到的,使用新的 one-hot 编码形式的数据,数据的形状看起来很好,但是在我们的 dataframe 中至少有一列的数据类型是 object。让我们检查哪一列仍在等待数据处理。

print(temp.columns[temp.dtypes=="object"])

输出

Index(['StateHoliday'], dtype="object")

我们可以看到,在数据处理中,我们只遗漏了一列。在将它转换成数字或一键编码形式之前,让我们先看看特性的内容。

temp["StateHoliday"].unique()

输出

array(['0', 'a', 'b', 'c', 0], dtype=object)

该特征的值似乎不正确。理想情况下,StateHoliday应该将 0 或 1 作为可能的值来指示它是否是假日。让我们通过将“a”、“b”和“c”的所有值替换为 1,将其余的值替换为 0 来修复这个特性,从而将变量转换为数字。

temp["StateHoliday"]= np.where(temp["StateHoliday"]== '0',0,1)
#One last check of the data type
temp.dtypes.unique()

输出

array([dtype('int64'), dtype('float64')], dtype=object)

现在我们已经有了整数形式的所有列,让我们继续构建我们的训练和测试数据集。如前所述,我们应该按照 60:20:20 的比例划分训练、验证和测试数据集。假设我们有一个相当大的训练数据集,如果需要保留大部分用于训练,我们会减少验证的规模。这一步不是必需的,但它是一个选项。

我们将首先以 80:20 的比例创建训练和测试数据集。然后,我们将使用训练数据集以 90:10 的比例进一步划分为训练数据集和验证数据集。这些比例可以根据你的判断进一步调整。我们可以使用 scikit-learn 包提供的train_test_split函数来划分数据集。

from sklearn.cross_validation import train_test_split
#Create train and test dataset with an 80:20 split
x_train, x_test, y_train, y_test = train_test_split(temp,df_new[target],test_size=0.2,random_state=2018)
#Further divide training dataset into train and validation dataset with an 90:10 split
x_train, x_val, y_train, y_val = train_test_split(x_train,y_train,test_size=0.1,random_state=2018)

#Check the sizes of all newly created datasets
print("Shape of x_train:",x_train.shape)
print("Shape of x_val:",x_val.shape)
print("Shape of x_test:",x_test.shape)
print("Shape of y_train:",y_train.shape)
print("Shape of y_val:",y_val.shape)
print("Shape of y_test:",y_test.shape)

输出

Shape of x_train: (732390, 44)
Shape of x_val: (81377, 44)
Shape of x_test: (203442, 44)
Shape of y_train: (732390, 1)
Shape of y_val: (81377, 1)
Shape of y_test: (203442, 1)

所有需要的数据集的形状看起来都很好。既然我们有了模型开发和训练所需形式的数据集,我们需要设计 DNN 架构。与以前的小型网络不同,我们现在需要改进模型的架构,以便进行适当的学习和预测。此外,我们稍后将需要测量模型性能,并验证它是否表现良好。

我们如何确定我们的模型是否表现良好?

即使在我们开始设计模型性能之前,这也是一个需要解决的重要问题。对于我们将要开发的每个模型,我们需要创建一个基线分数,作为考虑模型有用的最低分数。在大多数情况下,我们假设没有模型也能做出什么预测。对于回归模型,如果我们假设训练数据集中销售额的平均值是测试数据集中所有样本的预测值,我们将得到一个基本基准分数。DL 模型至少应该比这个分数更好才能被认为是有用的。

定义模型基线性能

要定义模型基线性能,我们应该将训练数据集中目标变量的平均值视为所有测试样本的预测值。我们将使用 MAE(平均绝对误差)进行测试。

#calculate the average score of the train dataset
mean_sales = y_train.mean()
print("Average Sales :",mean_sales)

输出

Average Sales : Sales    5773.099997
dtype: float64

现在,如果我们假设平均销售额是测试数据集中所有样本的预测值,那么 MAE 指标是什么样的呢?

#Calculate the Mean Absolute Error on the test dataset
print("MAE for Test Data:",abs(y_test - mean_sales).mean()[0])

输出

MAE for Test Data: 2883.587604303215

因此,我们的基准性能是 2,883.58。

如果我们的 DL 模型没有交付比基线分数更好(即更低)的结果,那么它几乎不会增加任何价值。

设计 DNN

设计 DNN 时,我们需要考虑几个重要方面。我们有有限的计算能力和时间,所以测试所有可能的架构组合的奢侈被简单地排除了。DL 模型消耗大量的数据和计算时间用于训练。我们需要明智地设计能够尽快学习的网络架构。

这里有一些指导原则。

  • 规则 1:从 小架构 开始。

    在 DNNs 的情况下,总是建议从具有大约 100-300 个神经元的单层网络开始。使用定义的指标训练网络并测量性能(同时定义基线分数)。如果结果不令人鼓舞,试着增加一层同样数量的神经元,重复这个过程。

  • 规则 2:当 小型架构 (有两层)失败时,增加规模。

    当来自小型网络的结果不是很大时,您需要将层中的神经元数量增加三到五倍(即,每层大约 1000 个神经元)。此外,将两层的正则化(将在第五章中深入讨论)增加到 0.3、0.4 或 0.5,并重复该过程进行训练和性能测量。

  • 法则三:当有两层的更大的网络失效时,深入下去。

    尝试使用越来越多的图层来增加网络的深度,同时在每个密集图层(或您选择的图层)之后使用丢失率介于 0.2 和 0.5 之间的丢失图层(如果需要)来保持正则化。

*** 法则四:当 更大更深的关系网 也失败时,去更大更深的地方。

如果拥有大约 1000 个神经元和五层或六层的大型网络也不能提供理想的性能,请尝试增加网络的宽度和深度。尝试添加每层有 8,000–10,000 个神经元的层,压差为 0.6 到 0.8。

*   **法则五:当一切都失败时,重温数据** **。**** 

**如果上述所有规则都失败了,重新访问数据以改进特征工程和标准化,然后您将需要尝试其他 ML 替代方案。

那么,我们开始吧。下面的代码片段创建了一个只有一层 150 个神经元的 DNN。

#Create Deep Neural Network Architecture
from keras.models import Sequential
from keras.layers import Dense, Dropout

model = Sequential()
model.add(Dense(150,input_dim = 44,activation="relu"))
#The input_dim =44, since the width of the training data=44 (refer data engg section)
model.add(Dense(1,activation = "linear"))

#Configure the model
model.compile(optimizer='adam',loss="mean_absolute_error",
metrics=["mean_absolute_error"])

#Train the model
model.fit(x_train.values,y_train.values, validation_data=(x_val,y_val),epochs=10,batch_size=64)

输出

Train on 732390 samples, validate on 81377 samples
Epoch 1/10
732390/732390 [==============================] - 14s 19us/step - loss: 2484443.9857 - mean_absolute_error: 982.3168 - val_loss: 1705817.0064 - val_mean_absolute_error: 866.8005
Epoch 2/10
732390/732390 [==============================] - 15s 20us/step - loss: 1556789.8048 - mean_absolute_error: 851.0444 - val_loss: 1513560.3941 - val_mean_absolute_error: 880.7449
Epoch 3/10
732390/732390 [==============================] - 14s 19us/step - loss: 1365229.7217 - mean_absolute_error: 823.4470 - val_loss: 1354828.9200 - val_mean_absolute_error: 843.5079
Epoch 4/10
732390/732390 [==============================] - 15s 20us/step - loss: 1264298.7544 - mean_absolute_error: 800.4497 - val_loss: 1176297.4208 - val_mean_absolute_error: 775.9128
Epoch 5/10
732390/732390 [==============================] - 14s 20us/step - loss: 1191949.2337 - mean_absolute_error: 776.4975 - val_loss: 1118038.9334 - val_mean_absolute_error: 754.8027
Epoch 6/10

732390/732390 [==============================] - 15s 21us/step - loss: 1145511.8379 - mean_absolute_error: 757.7596 - val_loss: 1077273.3024 - val_mean_absolute_error: 737.5510
Epoch 7/10
732390/732390 [==============================] - 15s 21us/step - loss: 1115707.3112 - mean_absolute_error: 744.6207 - val_loss: 1110957.5719 - val_mean_absolute_error: 747.7849
Epoch 8/10
732390/732390 [==============================] - 14s 19us/step - loss: 1096126.8665 - mean_absolute_error: 734.5611 - val_loss: 1056226.5925 - val_mean_absolute_error: 721.077873 - ETA: 0s - loss: 1096330.8107 - mean_absolute_error: 73
Epoch 9/10
732390/732390 [==============================] - 14s 20us/step - loss: 1077081.6034 - mean_absolute_error: 723.8428 - val_loss: 1043093.3088 - val_mean_absolute_error: 712.8212an_absolute_error: 7
Epoch 10/10

732390/732390 [==============================] - 14s 19us/step - loss: 1064185.7429 - mean_absolute_error: 715.7054 - val_loss: 1028792.2388 - val_mean_absolute_error: 697.6917

当模型训练 DNN 时,前面的输出会逐步显示。它在一次迭代中获取一批 64 个训练样本,通过网络传递每个样本,并测量我们定义的损失度量。它使用我们配置的优化技术来更新模型权重,并重复直到一个时期的最后一批。整个过程重复 10 次,因为我们将时期数设置为 10。在每个时期结束时,模型使用验证数据集来评估和报告我们配置的指标。

从初步结果来看,我们可以看到积极的表现。验证数据集上的模型性能是 697,这比我们的基线分数好得多。

测试模型性能

现在让我们在测试数据集上测试模型性能。

#Use the model's evaluate method to predict and evaluate the test datasets
result = model.evaluate(x_test.values,y_test.values)

#Print the results
for i in range(len(model.metrics_names)):
    print("Metric ",model.metrics_names[i],":",str(round(result[i],2)))

输出

203442/203442 [==============================] - 2s 10us/step
Metric  loss : 810.1835326664134
Metric  mean_absolute_error : 674.5

我们开始了:我们在测试数据集上也获得了相对一致的性能。

改进模型

现在,让我们通过试验几个更复杂的架构来进一步提高模型性能。在之前的网络中,我们使用mean_absolute_error作为损失函数。为了改进与用例同步的学习,我们可以使用mean_squared_error。误差的平方有助于更多地惩罚较高的误差率。

在下面的网络中,我们添加了两个神经元数量相似的层。我们将把损失函数更新为均方误差,而不是平均误差。让我们训练网络,看看在测试数据集上的性能。

model = Sequential()
model.add(Dense(150,input_dim = 44,activation="relu"))
model.add(Dense(150,activation="relu"))
model.add(Dense(150,activation="relu"))
model.add(Dense(1,activation = "linear"))

model.compile(optimizer='adam',loss="mean_squared_error",metrics=["mean_absolute_error"])

history = model.fit(x_train,y_train, validation_data=(x_val,y_val),epochs=10,batch_size=64)

#result = model.evaluate(x_test,y_test)
for i in range(len(model.metrics_names)):
    print("Metric ",model.metrics_names[i],":",str(round(result[i],2)))

输出

Train on 732390 samples, validate on 81377 samples
Epoch 1/10
732390/732390 [==============================] - 23s 32us/step - loss: 1708038.6039 - mean_absolute_error: 848.4737 - val_loss: 1138718.0817 - val_mean_absolute_error: 713.3368
Epoch 2/10
732390/732390 [==============================] - 23s 31us/step - loss: 1145557.5467 - mean_absolute_error: 718.0267 - val_loss: 1019385.8800 - val_mean_absolute_error: 679.1929
Epoch 3/10
732390/732390 [==============================] - 23s 31us/step - loss: 1075842.6427 - mean_absolute_error: 695.9032 - val_loss: 1066319.3633 - val_mean_absolute_error: 698.5687
Epoch 4/10
732390/732390 [==============================] - 23s 31us/step - loss: 1053733.9089 - mean_absolute_error: 688.2615 - val_loss: 996584.2376 - val_mean_absolute_error: 672.7340
Epoch 5/10
732390/732390 [==============================] - 23s 31us/step - loss: 1028932.4075 - mean_absolute_error: 681.4085 - val_loss: 963295.3702 - val_mean_absolute_error: 662.4607
Epoch 6/10

732390/732390 [==============================] - 23s 31us/step - loss: 1004636.7859 - mean_absolute_error: 673.8713 - val_loss: 985398.1829 - val_mean_absolute_error: 678.7933
Epoch 7/10
732390/732390 [==============================] - 24s 33us/step - loss: 980104.8595 - mean_absolute_error: 667.2302 - val_loss: 914751.1625 - val_mean_absolute_error: 651.7794
Epoch 8/10
732390/732390 [==============================] - 23s 32us/step - loss: 963304.7831 - mean_absolute_error: 662.4571 - val_loss: 955510.7847 - val_mean_absolute_error: 669.5784
Epoch 9/10

732390/732390 [==============================] - 23s 31us/step - loss: 944079.1561 - mean_absolute_error: 656.3804 - val_loss: 886288.1656 - val_mean_absolute_error: 639.5075
Epoch 10/10
732390/732390 [==============================] - 23s 31us/step - loss: 924452.3857 - mean_absolute_error: 650.0512 - val_loss: 911133.2878 - val_mean_absolute_error: 643.0542

203442/203442 [==============================] - 4s 19us/step
Metric  loss : 909847.03
Metric  mean_absolute_error : 638.72

我们可以看到,随着我们创建一个更深入的模型,它在测试数据集上的性能进一步提高。目前的结果比我们以前的模型好得多。

让我们再做几个实验,看看我们是否能期待性能的进一步提高。我们可以开发另一个更深层次的模型,它有五个隐藏层,每个隐藏层有 150 个神经元。在这种情况下,我们将纪元的数量从 10 个增加到 15 个。因此这将增加计算量。

model = Sequential()
model.add(Dense(150,input_dim = 44,activation="relu"))
model.add(Dense(150,activation="relu"))
model.add(Dense(150,activation="relu"))
model.add(Dense(150,activation="relu"))
model.add(Dense(150,activation="relu"))
model.add(Dense(1,activation = "linear"))

model.compile(optimizer='adam',loss="mean_squared_error",metrics=["mean_absolute_error"])

model.fit(x_train,y_train, validation_data=(x_val,y_val),epochs=15,batch_size=64)

result = model.evaluate(x_test,y_test)
for i in range(len(model.metrics_names)):
    print("Metric ",model.metrics_names[i],":",str(round(result[i],2)))

输出

732390/732390 [==============================] - 30s 41us/step - loss: 1101835.3958 - mean_absolute_error: 702.2829 - val_loss: 1010836.5122 - val_mean_absolute_error: 678.2764

    ----Skipping output for in between epochs----

Epoch 14/15
732390/732390 [==============================] - 30s 41us/step - loss: 891425.8829 - mean_absolute_error: 635.5511 - val_loss: 844685.8285 - val_mean_absolute_error: 620.1237
Epoch 15/15
732390/732390 [==============================] - 30s 41us/step - loss: 883631.1386 - mean_absolute_error: 632.5584 - val_loss: 871893.6526 - val_mean_absolute_error: 638.8337
203442/203442 [==============================] - 5s 23us/step
Metric  loss : 872514.05
Metric  mean_absolute_error : 635.84

我们现在可以看到一个饱和点。在测试数据集上的准确率为 635.8;虽然这是整体性能的一个小改进,但并没有我们预期的那么多。创建更深的网络对这种规模可能没什么用。让我们试着增加神经元的数量,从一层或两层开始。

增加神经元的数量

下面的代码片段设计了一个具有两个隐藏层的神经网络,每个隐藏层有 350 个神经元,并使用了一个与前面的体系结构类似的模型配置。

model = Sequential()
model.add(Dense(350,input_dim = 44,activation="relu"))
model.add(Dense(350,activation="relu"))
model.add(Dense(1,activation = "linear"))

model.compile(optimizer='adam',loss="mean_squared_error",metrics=["mean_absolute_error"])

model.fit(x_train,y_train, validation_data=(x_val,y_val),epochs=15,batch_size=64)

result = model.evaluate(x_test,y_test)
for i in range(len(model.metrics_names)):
    print("Metric ",model.metrics_names[i],":",str(round(result[i],2)))

输出

Train on 732390 samples, validate on 81377 samples
Epoch 1/15
732390/732390 [==============================] - 38s 52us/step - loss: 1697413.8672 - mean_absolute_error: 854.0277 - val_loss: 1467867.2202 - val_mean_absolute_error: 832.8275
Epoch 2/15
732390/732390 [==============================] - 39s 54us/step - loss: 1154938.1155 - mean_absolute_error: 725.5312 - val_loss: 1007847.0574 - val_mean_absolute_error: 685.1245
Epoch 3/15
732390/732390 [==============================] - 39s 53us/step - loss: 1085253.5922 - mean_absolute_error: 700.4208 - val_loss: 1050960.9477 - val_mean_absolute_error: 689.2257

    ----Skipping output for in between epochs----

Epoch 14/15

732390/732390 [==============================] - 44s 60us/step - loss: 889136.7336 - mean_absolute_error: 637.8075 - val_loss: 832445.6279 - val_mean_absolute_error: 621.5381
Epoch 15/15
732390/732390 [==============================] - 42s 57us/step - loss: 883337.1976 - mean_absolute_error: 635.5014 - val_loss: 844103.7393 - val_mean_absolute_error: 626.2723
203442/203442 [==============================] - 7s 33us/step
Metric  loss : 847824.59
Metric  mean_absolute_error : 623.83

当我们使用由更多神经元构建的架构时,我们可以看到相当多的改进。这是对模型的一个相当大的改进。现在让我们尝试相同架构的更深层次的模型。此外,我们向模型添加了一个新的[可选]配置,以记录培训过程中各种指标的历史。这可以通过添加callbacks参数来实现,如下例所示。我们可以使用历史、后期训练来可视化和理解模型的学习曲线。

from keras.callbacks import History
history = History()

model = Sequential()
model.add(Dense(350,input_dim = 44,activation="relu"))
model.add(Dense(350,activation="relu"))
model.add(Dense(350,activation="relu"))
model.add(Dense(350,activation="relu"))
model.add(Dense(350,activation="relu"))
model.add(Dense(1,activation = "linear"))

model.compile(optimizer='adam',loss="mean_squared_error",metrics=["mean_absolute_error"])

model.fit(x_train,y_train, validation_data=(x_val,y_val),
epochs=15,batch_size=64,callbacks=[history])

result = model.evaluate(x_test,y_test)

for i in range(len(model.metrics_names)):
    print("Metric ",model.metrics_names[i],":",str(round(result[i],2)))

输出

Train on 732390 samples, validate on 81377 samples
Epoch 1/15
732390/732390 [==============================] - 83s 113us/step - loss: 1652045.7426 - mean_absolute_error: 842.9293 - val_loss: 1176475.4327 - val_mean_absolute_error: 722.2341
Epoch 2/15
732390/732390 [==============================] - 78s 107us/step - loss: 1166282.9895 - mean_absolute_error: 723.2949 - val_loss: 1200598.2506 - val_mean_absolute_error: 741.1529
Epoch 3/15
732390/732390 [==============================] - 78s 107us/step - loss: 1107753.5017 - mean_absolute_error: 704.6886 - val_loss: 1014423.8244 - val_mean_absolute_error: 685.8464

    ----Skipping output for in between epochs----

Epoch 14/15

732390/732390 [==============================] - 72s 99us/step - loss: 867543.7561 - mean_absolute_error: 626.8261 - val_loss: 909483.9017 - val_mean_absolute_error: 639.9942
Epoch 15/15
732390/732390 [==============================] - 84s 115us/step - loss: 856165.2330 - mean_absolute_error: 622.1622 - val_loss: 823340.0147 - val_mean_absolute_error: 614.6816
203442/203442 [==============================] - 12s 59us/step
Metric  loss : 825525.53
Metric  mean_absolute_error : 612.01

正如您可能已经注意到的,我们已经看到了具有更深层次架构的模型的整体测试性能的进一步提高。我们可以继续探索各种架构,只要我们能为实验提供计算和训练时间。我强烈建议您探索更多的 DNN 架构,以了解性能是如何变化的。

绘制跨时段的损失度量

该模型还存储了我们为该模型配置的一些重要参数和指标的历史。要了解模型训练过程是什么样子,我们可以绘制跨时期的损失度量,并查看模型在每个时期实现的减少量。

下面的代码片段展示了模型跨时期的训练和验证损失。

plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title("Model's Training & Validation loss across epochs")
plt.ylabel('Loss')
plt.xlabel('Epochs')
plt.legend(['Train', 'Validation'], loc='upper right')
plt.show()

img/475458_1_En_3_Figm_HTML.jpg

我们可以看到,在一个点之后,损失的净减少相当低,但仍然相对较好。我们可能会增加历元的数量,以测试模型性能是否会进一步提高。当然,这伴随着大量的训练计算时间,但是一旦您最终确定了模型的架构,您就可以增加训练的时期数,并检查是否有任何进一步的改进。

手动测试模型

我们还可以在测试数据集上手动测试模型的性能,而不是使用模型的evaluate函数。以下代码片段通过对测试数据集使用手动预测来计算模型在测试数据集上的均方误差。

#Manually predicting from the model, instead of using model's evaluate function
y_test["Prediction"] = model.predict(x_test)
y_test.columns = ["Actual Sales","Predicted Sales"]
print(y_test.head(10))

#Manually predicting from the model, instead of using model's evaluate function
from sklearn.metrics import mean_squared_error, mean_absolute_error
print("MSE :",mean_squared_error(y_test["Actual Sales"].values,y_test["Predicted Sales"].values))
print("MAE :",mean_absolute_error(y_test["Actual Sales"].values,y_test["Predicted Sales"].values))

输出

        Actual Sales  Predicted Sales
115563             0         0.103189
832654             0         0.103189
769112          2933      3073.666748
350588          8602      7848.280762

84435           9239      8838.069336
53018              0         0.103189
262419             0         0.103189
702267          5885      5651.779297
981431             0         0.103189

MSE : 825525.5321821237
MAE : 612.0117530558458

摘要

在这一章中,我们从头到尾探讨了 DNN 的监督回归问题。我们从问题陈述开始,并使用行业标准框架来定义它,以便直观地理解我们为什么要解决这个问题。然后,我们研究了这些数据,以了解可用的特性和不同的数据类型。我们学习了基本的 Python 技能,以帮助我们接收、操作、可视化数据,并将数据转换成 DNNs 所需的形式。然后,我们利用在第二章中看到的 DNNs 和 Keras 的构建模块来设计、构建和迭代各种 DL 架构。我们看到了如何使用 DNNs 测量性能并进一步提高性能。

在下一章中,我们将看看另一个商业问题,我们可以使用监督学习中的分类 DNN 来解决这个问题。****