Keras-深度神经网络学习手册-全-
Keras 深度神经网络学习手册(全)
一、深度学习和 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 之间的争论也是如此。下图展示了上述三个字段的数据大小对模型性能的总体影响。

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

综合起来,我们可以说,AI 是人工地将智能诱导到机器或系统中的领域,有或没有显式编程。人工智能是人工智能中的一个子领域,其中智能是在没有显式编程的情况下诱导的。最后,DL 是 ML 中的一个领域,在这里,智能被引入到系统中,而无需使用算法的显式编程,这些算法是由人脑的生物功能所启发的。
DL 在当今市场上解决了哪些经典问题?
今天,我们可以在数字世界的日常生活中看到 DL 的应用。如果你在社交媒体上很活跃,你可能已经注意到脸书建议你在上传照片时给你的朋友加标签。还要注意特斯拉汽车中的自动驾驶模式,你的 iOS 或 Android 手机上的消息系统中的下一个单词的预测,Alexa,Siri 和谷歌助手对你作为人类的回应,等等。如果我们试图分析我们可以使用 DL 解决的用例类型,我们已经可以在当今世界中使用的几乎任何系统中见证 DL 的力量。
分解 DL 模型
在其最基本的形式中,使用神经网络架构来设计 DL 模型。神经网络是神经元(类似于大脑中的神经元)与其他神经元连接的分层组织。这些神经元根据收到的输入向其他神经元传递消息或信号,并形成一个复杂的网络,通过某种反馈机制进行学习。
下面是一个基本神经网络的简单表示。

正如您在前面的图中所看到的,输入数据被第一个隐藏层中的神经元所消耗,然后向下一层提供输出,依此类推,最终得到最终的输出。每层可以有一个或多个神经元,每个神经元将计算一个小函数(例如,激活函数)。连续层的两个神经元之间的连接将具有相关联的权重。权重定义了输入对下一个神经元的输出的影响,以及最终对整个最终输出的影响。在神经网络中,初始权重在模型训练期间都是随机的,但是这些权重被迭代更新以学习预测正确的输出。分解网络,我们可以定义几个逻辑构建块,如神经元、层、权重、输入、输出、神经元内部的激活函数来计算学习过程,等等。
为了直观的理解,让我们举一个人类大脑如何学习识别不同人的例子。当你第二次遇见一个人时,你就能认出他。这是怎么发生的?人在整体结构上有相似之处;两只眼睛,两只耳朵,一个鼻子,嘴唇,等等。每个人都有相同的结构,但我们能够很容易地区分人,对不对?
大脑中学习过程的本质是相当直观的。大脑不是学习面部的结构来识别人,而是学习与普通面部的偏差(例如,个人的眼睛与参考眼睛有多不同),然后可以量化为具有定义强度的电信号。同样,它从一个参考基准中学习人脸所有部分的偏差,并将这些偏差组合成新的维度,最后给出一个输出。所有这一切发生得如此之快,以至于我们没有人意识到我们的潜意识实际上做了什么。
同样,上图中展示的神经网络试图使用数学方法来模拟相同的过程。输入由第一层中的神经元消耗,并且在每个神经元内计算激活函数。基于一个简单的规则,它将输出转发给下一个神经元,类似于人脑学习的偏差。神经元的输出越大,输入维度的重要性就越大。然后,这些维度在下一层中组合起来,形成额外的新维度,我们可能无法理解这些维度。但是系统凭直觉学习。这一过程,当乘以几倍,发展成一个复杂的网络与几个连接。
现在已经了解了神经网络的结构,让我们来了解学习是如何发生的。当我们向已定义的结构提供输入数据时,最终输出将是一个预测,它可能是正确的,也可能是不正确的。基于输出,如果我们向网络提供反馈,以通过使用一些手段来进行更好的预测而更好地适应,则系统通过更新连接的权重来学习。为了实现提供反馈和定义下一步以正确的方式做出改变的过程,我们使用了一种称为“反向传播”的漂亮的数学算法随着越来越多的数据,逐步迭代该过程几次,有助于网络适当地更新权重,以创建一个系统,在该系统中,它可以根据它通过权重和连接为自己创建的规则来做出预测输出的决策。
“深度神经网络”这个名称是从使用更多隐藏层演变而来的,使其成为学习更复杂模式的“深度”网络。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。

是的,这就是我们之前在探索主题“分解 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 在浏览器中运行的情况。

单击最右侧的“新建”按钮,并从下拉菜单中选择 Python。如果你已经安装了一个或多个虚拟环境,所有的虚拟环境都会显示在下拉列表中;请选择您所选择的 Python 环境。
选择后,您的 Jupyter 笔记本应该会打开,并准备好开始使用。下面的截图展示了一个 Jupyter 笔记本在浏览器中运行。

绿色突出显示的单元格是您编写代码的地方,Ctrl + Enter 将执行选定的单元格。您可以使用控制栏中的“+”图标添加更多单元格,或者从菜单栏中浏览其他选项。如果这是你第一次使用 Jupyter,我推荐导航菜单中的可用选项。
现在我们已经设置并运行了所有需要的工具,让我们从简单的带有 Keras 的 DL 构建块开始。
Keras 中的 DL 入门
让我们从研究 DNN 及其逻辑组件开始,理解每个组件的用途以及这些构建块如何在 Keras 框架中映射。
如果您还记得第一章中的主题“分解 DL 模型”,我们已经将 DNN 中的逻辑组件定义为输入数据、神经元、激活函数、层(即神经元组)、神经元或边之间的连接、学习过程(即反向传播算法)和输出层。
让我们一个一个地看看这些逻辑组件。
输入数据
DL 算法的输入数据可以有多种类型。本质上,该模型将数据理解为“张量”。张量只不过是向量的一般形式,或者用计算机工程术语来说,是一个简单的 n 维矩阵。任何形式的数据最终都表示为一个齐次的数字矩阵。因此,如果数据是表格形式的,它将是一个二维张量,其中每一列代表一个训练样本,整个表/矩阵将是 m 个样本。为了更好地理解这一点,请看下图。

您还可以颠倒训练样本的表示(即,每一行可以是一个训练样本),因此在测试示例中的学生通过/失败的上下文中,一行将指示一个学生的所有属性(他的分数、年龄等)。).对于 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 ))进行操作,并根据输入以适当的值进行响应。如果接收到更高影响力的输入,则输出应该更高,反之亦然。它在某种程度上类似于激活信号(即,更高的影响->然后激活,否则去激活)。对计算出的输入数据起作用的函数称为激活函数。

激活功能
激活函数是这样一种函数,它采用上图所示的组合输入 z,对其应用函数,并传递输出值,从而试图模仿激活/停用函数。因此,激活函数通过计算组合输入的激活函数来确定神经元的状态。
一个快速的想法可能会出现在你的脑海中:当我们可以传递 z 的值作为最终输出时,为什么我们真的需要一个激活函数来计算组合输出 z?这里有几个问题。首先,输出值的范围将是-无穷大到+无穷大,在这种情况下,我们没有明确的方法来定义应该发生激活的阈值。其次,网络将在某种程度上变得无用,因为它不会真正学习。这就是微积分和导数的作用。为了简化故事,我们可以说,如果你的激活函数是线性函数(基本没有激活),那么那个函数的导数就变成了 0;这成为一个大问题,因为用反向传播算法进行训练有助于向网络提供关于错误分类的反馈,从而有助于神经元通过使用函数的导数来调整其权重。如果这个值变成 0,网络就失去了这种学习能力。换句话说,我们可以说拥有 DNN 毫无意义,因为只有一层的输出与拥有 n 层的输出相似。为了简单起见,我们总是需要一个非线性激活函数(至少在所有隐藏层中)来让网络正确学习。
有多种选择可用作激活功能。最常见的是 sigmoid 函数和 ReLU(整流线性单元)。
Sigmoid 激活函数
一个 sigmoid 函数被定义为
,它呈现 0 和 1 之间的输出,如下图所示。非线性输出(如图所示的 s 形)很好地改善了学习过程,因为它非常类似于以下原则——较低影响:低输出和较高影响:较高输出——并且还将输出限制在 0 到 1 的范围内。
在 Keras 中,sigmoid 激活函数可作为 keras.activations.sigmoid(x)使用。
我们可以简单地用import命令将其导入 Python:
import keras.activations.sigmoid

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

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).
我们可以通过设置一个小常数α的值来直接使用激活函数。

在 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))
训练模型时的输出显示如下:

我们可以看到,在每个时期之后,模型打印平均训练损失和准确度以及验证损失和准确度。我们可以使用这些中间结果来判断模型的性能。在大多数大型 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]
输出

该输出可用于做出更精确的最终预测。一个简单的例子是,模型将使用 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。

有了 SCQ,我们现在对问题陈述有了更全面的理解。我们知道,有一个营销团队正在设计针对特定商店的促销活动,以锁定目标客户并增加整体收入,同时更明智地使用资源。因此,他们不希望向无论如何都表现出色的商店提供促销活动。如果他们能够看到预计的未来销售额,他们可以根据为实现预期目标所需的折扣和促销的定义阈值,将一些商店分为“低”、“中”和“高”。
团队遇到了障碍,因为他们没有办法估计给定商店的未来销售额。因此,为了解决这个问题,我们提出以下问题:“我们如何估计一个商店的未来销售额?”鉴于路障已经被克服,营销团队现在有办法研究和估计未来的商店销售,从而设计更有效的促销活动。
设计解决方案
商业问题的关键问题的答案现在可能很容易猜到了。我们将开发一个 ML 模型,它可以将商店的销售额作为内部、外部和时间(基于时间)属性的函数来学习,然后根据可用的属性来预测未来的销售额。
这可能看起来像一个时间序列预测问题,在这种情况下,我们纯粹将销售额或类似目标定义为时间的函数(即,考虑历史每周或每天的销售额,并通过模拟趋势和模式来预测未来的数据点)。但这仅适用于我们只需要对一家商店进行估算的情况。对于 1000 家商店,手动研究每周销售额并开发模型来估计未来销售额是一个费力且不可行的解决方案。或者,我们可以使用全局时间序列模型来解决这个问题(即,只开发一个用于所有商店的模型)。虽然这肯定是可能的,但是预测的结果不会给利益相关者增加任何价值,因为它很可能会偏离目标。
相反,我们可以通过将这个问题从时间序列预测问题转化为回归问题来开发一个更有效的模型。万一这很难理解,让我们用一个例子来简化它。任何用例可用的数据都可以分为时间序列或横截面。在时间序列数据中,每个训练样本(即一行数据)都与和时间序列相关联的另一个样本有关系。日销售额或周销售额是时间序列数据的适当示例,因为一周的销售额与前几周的销售额有关系。在横截面数据中,每个训练样本都是独立的,与其他样本没有基于时间的关系。客户的广告点击或信用客户通过信用卡提供商进行的交易都是横截面数据的例子。这两个样本之间没有基于时间的关系。
在我们的用例中,我们可以将数据表示为
作为商店功能的销售额+其他属性
而不是基于时间序列的模型定义为
作为时间函数的销售额
通过这种方式,我们可以定义一个模型,该模型可以从各种商店和其他外部属性(我们将使用数据探索这些属性)中学习模式,以预测预期的销售额。随着我们探索数据和更接近模型开发,这个过程将变得清晰。现在让我们从探索数据开始。
探索数据
我希望你已经在注册账户并接受比赛规则后从 Kaggle 链接下载了数据集。如果您还没有,以下是详细的步骤:
-
进入 Kaggle 首页:
www.kaggle.com -
使用“注册”创建新帐户或使用现有帐户登录。
前往罗斯曼专卖店销售竞赛:
www.kaggle.com/c/rossmann-store-sales/data -
导航到页面中间,找到“全部下载”选项。你会得到一个竞赛规则页面,你必须阅读,然后接受这些条件。一旦它们被接受,就可以下载了。
-
将下载的数据集解压缩并移动到一个新文件夹中,以供练习使用。
您需要两个重要的文件:
-
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)

同样,让我们导入第二个数据集 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)

如您所见,训练数据集有 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()
输出

直方图有助于我们在高层次上理解数据的分布。从前面的图中,我们可以看到数据范围是从 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))
输出

让我们分析上图中展示的直方图的结果。我们可以看到,特征 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
突出显示的行显示了相应列中大量缺失的数据点。我们可以看到Promo2SinceWeek、Promo2SinceYear、PromoInterval、CompetitionOpenSinceMonth、CompetitionOpenSinceYear有超过 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)

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

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

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

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

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

我们可以注意到,一个类别中不同类别之间的数据点分布是偏斜的。对StoreType和分类的简单检查显示b在数据集中的商店或数据点数量明显较少。因此,我们最初对观察到的关系的理解是不正确的。
因此,假设我们已经研究了数据集的长度、宽度、内容、性质和摘要,并进一步分别研究了连续(数值)和分类特征以获得对数据的良好理解,我们现在可以继续为开发 DL 模型准备数据。
数据工程
如前所述,DL 模型只理解数字数据。因此,对于模型训练数据,所有存储为文本列的分类特征都需要转换为一次性编码形式。
一键编码是将分类列表示为扩展的二进制标记矩阵的简单过程。因此,具有三个不同值的分类特征,比如“A 类”、“B 类”和“C 类”,可以用三列而不是一列来表示,其中每一列都代表一个单独类别值的二进制标志。这将在下面的例子中进一步总结。

在我们的数据集中,我们有三个分类变量需要转换;它们是季节、商店类型和分类。然而,在分类变量的上下文中,星期几、月、日、季度以及实际上商店 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()

我们可以看到,在一个点之后,损失的净减少相当低,但仍然相对较好。我们可能会增加历元的数量,以测试模型性能是否会进一步提高。当然,这伴随着大量的训练计算时间,但是一旦您最终确定了模型的架构,您就可以增加训练的时期数,并检查是否有任何进一步的改进。
手动测试模型
我们还可以在测试数据集上手动测试模型的性能,而不是使用模型的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 来解决这个问题。****
四、用于监督学习的深度神经网络:分类
在第三章中,我们探索了一个用于回归的 DL 用例。我们用业务推进战略探索了整个问题解决方法。我们利用我们从基础 DL 和 Keras 框架的第 1 和 2 章节中学到的知识来开发回归用例的 DNNs。在这一章中,我们将进一步学习并设计一个分类用例的网络。总体方法保持不变,但是在解决分类用例时,我们需要记住一些细微差别。此外,我们将把本章的学习向前推进一步,扩展 DNN 体系结构。让我们开始吧。
入门指南
类似于第三章中的,我们将考虑 Kaggle 作为我们用例的数据源。从可用选项中,我们将使用为“Red Hat Business Value”竞赛提供的数据集。这个比赛是几年前在 Kaggle 上举办的,数据集对于我们的研究来说是一个非常好的商业用例。存档比赛可在 www.kaggle.com/c/predicting-red-hat-business-value 获取。就像在前面的用例中一样,我们需要在为我们的实验下载数据集之前阅读并接受竞争规则。一旦你接受了比赛规则,你就可以从“数据”标签或 www.kaggle.com/c/predicting-red-hat-business-value/data 下载数据集。数据将以 zip 文件的形式下载。解压缩后,您将有四个不同的数据集。我们只需要其中的两个:act_train.csv 和 people.csv。
您可以复制这两个数据集,并将它们保存在一个新的文件夹中,用于当前章节的实验。我们将为用例使用相同的环境,但是在我们开始之前,让我们看一下问题陈述并定义 SCQ 和解决方案方法,就像我们在第三章中所做的一样。
问题陈述
高层次的问题陈述在比赛的描述页中被提及。它强调了基于运营交互数据来预测高价值客户的问题,从而帮助公司有效地优化资源以产生更多业务并更好地服务于客户。
让我们从一个更加以业务为中心的角度来看问题陈述。我们将从更好地了解客户开始。该组织是一家美国跨国软件公司,向企业社区提供开源软件产品。他们的主要产品是 Red Hat Enterprise Linux,这是 Linux OS 最流行的发行版,被各种大型企业所使用。在其服务中,它通过开放的业务模式和经济实惠、可预测的订阅模式提供企业级解决方案,从而帮助组织调整其 it 战略。这些来自大型企业客户的订阅为他们创造了很大一部分收入,因此对他们来说,了解他们有价值的客户并通过优化资源和策略来更好地服务他们以提高业务价值是至关重要的。
设计 SCQ
这个问题的最终涉众可能是销售团队或业务开发团队;这两个团队都处于公司运营的最前沿,为他们最有价值的客户提供最好的服务。为了更有效地实现这一目标,业务开发团队现在已经探索了一种数据驱动的解决方案。鉴于大量的运营交互数据和一些客户属性,他们希望开发数据驱动的技术来预测业务的潜在高价值客户。在这种背景下,现在让我们为业务问题起草 SCQ,就像我们在第三章中为回归用例所做的那样。

设计解决方案
上图展示的 SCQ 清楚地界定了当前局势和期望的未来局势,同时阐明了障碍和需要回答的问题,以便克服实现更大目标的障碍。为了设计解决方案,我们需要从关键问题开始,然后逆向工作。
我们如何识别潜在客户?
红帽已经存在超过 25 年了。在长期的业务中,他们从客户互动及其描述性属性中积累并获取了大量数据。这种丰富的数据源可能是模式的金矿,可以通过研究交互数据中大量复杂的历史模式来帮助识别潜在客户。
随着 DL 的日益普及和强大,我们可以开发一种 DNN,它可以从历史客户属性和运营交互数据中学习,以了解深层模式并预测新客户是否有可能成为各种业务服务的高价值客户。
因此,我们将开发和训练一个 DNN,使用各种客户属性和运营互动属性来学习一个客户成为潜在高价值客户的机会。
探索数据
现在,我们已经清楚地草拟了业务问题,并准备好了高级解决方案,让我们开始研究数据。从 Kaggle 下载红帽商业价值竞赛数据的流程与之前在第三章中展示的流程相同。所需数据集可在此下载: www.kaggle.com/c/predicting-red-hat-business-value/data 。请按照上一章演示的五个步骤下载数据。
现在,让我们打开 Jupyter 笔记本,为当前的实验创建一个新的笔记本。
#Import the necessary packages
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
#Import the 2 datasets provided in the Zip Folder
df = pd.read_csv("/Users/jojomoolayil/Book/Chapter4/Data/act_train.csv")
people = pd.read_csv("/Users/jojomoolayil/Book/Chapter4/Data/people.csv")
#Explore the shape of the datasets
print("Shape of DF:",df.shape)
print("Shape of People DF:",people.shape)
输出
Shape of DF: (2197291, 15)
Shape of People DF: (189118, 41)
最后一行代码:
#Explore the contents of the first dataset
df.head()
输出

我们可以看到,为比赛提供的训练数据超过 200 万行 15 列,而 people 数据集大约有 19 万行 41 列。探索训练数据集的内容,我们可以看到它大部分有客户交互数据,但完全匿名。考虑到客户及其属性的保密性,整个数据都是匿名的,这使得我们对其真实性质知之甚少。这是数据科学中常见的问题。开发 DL 模型的团队经常面临终端客户数据保密性的挑战,因此只能得到匿名的、有时是加密的数据。这仍然不应该是一个路障。拥有一个数据字典和对数据集的完全理解肯定是最好的,但是尽管如此,我们仍然可以用提供的信息开发模型。
act_train.csv(以下称为活动数据)有许多空数据点。在高层次上,数据集捕获客户活动并提供一些活动属性、一些客户属性(在前面的输出中显示为空)、另一个我们不太了解的名为“char_10”的分类特征,以及最后的结果变量。
让我们看看活动数据有多少个空值。
#Calculating the % of Null values in each column for activity data
df.isnull().sum()/df.shape[0]
输出
people_id 0.000000
activity_id 0.000000
date 0.000000
activity_category 0.000000
char_1 0.928268
char_2 0.928268
char_3 0.928268
char_4 0.928268
char_5 0.928268
char_6 0.928268
char_7 0.928268
char_8 0.928268
char_9 0.928268
char_10 0.071732
outcome 0.000000
dtype: float64
大约九个特征的空值超过 90%。我们无法修复这些功能。让我们继续,看看人员数据集。
#Explore the contents of People dataset
people.head()
输出

我们已经知道,人员数据集(此后称为客户数据)大约有 41 列;当我们进入内容时(由于大量的列,在前面的图中只显示了一部分),我们看到提供给我们许多客户属性,尽管我们无法理解它们。此外,列名与活动数据中的列名相同。我们需要在加入之前更改它们,以避免名称冲突。
让我们检查一下客户数据集有多少缺失的数据点。由于客户数据集有大约 40 多个特性,我们可以将所有列的缺失值百分比与前面的代码结合起来,而不是单独查看每一列。
#Calculate the % of null values in for the entire dataset
people.isnull().sum().sum()
输出
0
我们看到客户数据集中没有任何列缺少值。
为了创建一个合并的数据集,我们需要在 people_id 键上连接活动和客户数据。但在此之前,我们需要注意一些事情。我们需要删除活动数据中有 90%缺失值的列,因为它们无法修复。其次,“date”和“char_10”列出现在两个数据集中。为了避免名称冲突,让我们将活动数据集中的“date”列重命名为“activity_date”,将活动数据中的“char_10”重命名为“activity_type”接下来,我们还需要修复“activity_type”列中缺失的值。一旦这两项任务完成,我们将连接这两个数据集并研究整合后的数据。
#Create the list of columns to drop from activity data
columns_to_remove = ["char_"+str(x) for x in np.arange(1,10)]
print("Columns to remove:",columns_to_remove)
#Remove the columns from the activity data
df = df[list(set(df.columns) - set(columns_to_remove))]
#Rename the 2 columns to avoid name clashes in merged data
df = df.rename(columns={"date":"activity_date","char_10":"activity_type"})
#Replace nulls in the activity_type column with the mode
df["activity_type"] = df["activity_type"].fillna(df["activity_type"].mode()[0])
#Print the shape of the final activity dataset
print("Shape of DF:",df.shape)
输出
Columns to remove: ['char_1', 'char_2', 'char_3', 'char_4', 'char_5', 'char_6', 'char_7', 'char_8', 'char_9']
Shape of DF: (2197291, 6)
我们现在可以连接这两个数据集,以创建一个整合的活动和客户属性数据集。
#Merge the 2 datasets on 'people_id' key
df_new = df.merge(people,on=["people_id"],how="inner")
print("Shape before merging:",df.shape)
print("Shape after merging :",df_new.shape)
输出
Shape before merging: (2197291, 6)
Shape after merging : (2197291, 46)
一致的行数和增加的列数有助于我们验证连接操作是否按预期工作。现在让我们研究数据集中名为“结果”的目标(即我们想要预测的变量)。我们可以检查潜在客户和非潜在客户之间的分布。
print("Unique values for outcome:",df_new["outcome"].unique())
print("\nPercentage of distribution for outcome-")
print(df_new["outcome"].value_counts()/df_new.shape[0])3
Outcome
Unique values for outcome: [0 1]
Percentage of distribution for outcome-
0 0.556046
1 0.443954
Name: outcome, dtype: float64
我们可以看到,潜在客户的分布情况很好,大约 45%是潜在客户。
数据工程
接下来,假设我们总共有 45 个专栏要探索和转换,让我们通过自动化一些事情来加速这个过程。让我们看看整合数据框架中的不同数据类型。
#Checking the distinct datatypes in the dataset
print("Distinct DataTypes:",list(df_new.dtypes.unique()))
输出
Distinct DataTypes: [dtype('int64'), dtype('O'), dtype('bool')]
数据集中有数字、分类(对象)和布尔特征。Python 中的 Boolean 表示真或假值;我们需要将其转换成数字(1 和 0 ),以便模型处理数据。以下代码片段将 dataframe 中的布尔列转换为基于数字(1 和 0)的值。
#Create a temp dataset with the datatype of columns
temp = pd.DataFrame(df_new.dtypes)
temp.columns = ["DataType"]
#Create a list with names of all Boolean columns
boolean_columns = temp.index[temp["DataType"] == 'bool'].values
print("Boolean columns - \n",boolean_columns)
#Convert all boolean columns to Binary numeric values
for column in boolean_columns:
df_new[column] = np.where(df_new[column] == True,1,0)
print("\nDistinct DataTypes after processing:",df.dtypes.unique())
输出
Boolean columns -
['char_10"char_11"char_12"char_13"char_14"char_15"char_16'
'char_17"char_18"char_19"char_20"char_21"char_22"char_23'
'char_24"char_25"char_26"char_27"char_28"char_29"char_30'
'char_31"char_32"char_33"char_34"char_35"char_36"char_37']
Distinct DataTypes after processing: [dtype('int64') dtype('O')]
现在让我们来看看分类特征。我们将首先进行健全性检查,以了解每个分类特性中不同值的数量。如果分类特征中有非常多的不同值,我们必须决定是否真的可以将它们转换成一个独一无二的编码结构,以便进一步处理。
#Extracting the object columns from the above dataframe
categorical_columns = temp.index[temp["DataType"] == 'O'].values
#Check the number of distinct values in each categorical column
for column in categorical_columns:
print(column+" column has :",str(len(df_new[column].unique()))+" distinct values")
输出
activity_category column has : 7 distinct values
activity_id column has : 2197291 distinct values
people_id column has : 151295 distinct values
activity_type column has : 6516 distinct values
activity_date column has : 411 distinct values
char_1 column has : 2 distinct values
group_1 column has : 29899 distinct values
char_2 column has : 3 distinct values
date column has : 1196 distinct values
char_3 column has : 43 distinct values
char_4 column has : 25 distinct values
char_5 column has : 9 distinct values
char_6 column has : 7 distinct values
char_7 column has : 25 distinct values
char_8 column has : 8 distinct values
char_9 column has : 9 distinct values
输出中显示的五个突出显示的列具有大量不同的值。很难将它们转换成一键编码的形式,因为它们在处理过程中会消耗太多的内存。如果您有多余的 RAM,请随意将它们转换为一键编码的数据形式。
现在,我们可以看看这些分类列中的内容,以了解将它们转换成数字的方法。另外,date和activity_date列是日期值;因此,我们可以像上一章那样将它们转换成与数据相关的特征。让我们首先修复与日期相关的列,然后再处理剩余的分类列。以下代码片段将日期值转换为新功能,然后删除实际的列。
#Create date related features for 'date' in customer data
df_new["date"] = pd.to_datetime(df_new["date"])
df_new["Year"] = df_new["date"].dt.year
df_new["Month"] = df_new["date"].dt.month
df_new["Quarter"] = df_new["date"].dt.quarter
df_new["Week"] = df_new["date"].dt.week
df_new["WeekDay"] = df_new["date"].dt.weekday
df_new["Day"] = df_new["date"].dt.day
#Create date related features for 'date' in activity data
df_new["activity_date"] = pd.to_datetime(df_new["activity_date"])
df_new["Activity_Year"] = df_new["activity_date"].dt.year
df_new["Activity_Month"] = df_new["activity_date"].dt.month
df_new["Activity_Quarter"] = df_new["activity_date"].dt.quarter
df_new["Activity_Week"] = df_new["activity_date"].dt.week
df_new["Activity_WeekDay"] = df_new["activity_date"].dt.weekday
df_new["Activity_Day"] = df_new["activity_date"].dt.day
#Delete the original date columns
del(df_new["date"])
del(df_new["activity_date"])
print("Shape of data after create Date Features:",df_new.shape)
输出
Shape of data after create Date Features: (2197291, 56)
现在让我们看看剩下的分类列,它们有非常多的不同值。
print(df_new[["people_id","activity_type","activity_id","group_1"]].head())
输出
people_id activity_type activity_id group_1
0 ppl_100 type 76 act2_1734928 group 17304
1 ppl_100 type 1 act2_2434093 group 17304
2 ppl_100 type 1 act2_3404049 group 17304
3 ppl_100 type 1 act2_3651215 group 17304
4 ppl_100 type 1 act2_4109017 group 17304
似乎我们可以通过从每一列中提取相关的数字 ID,将前面所有的分类列转换成数字,因为每一列都有形式为someText_someNumber的值。我们可以暂时将这些分类列用作数字特征,而不是将它们转换成臃肿的一次性编码数据集。然而,如果在几次实验后,模型的性能没有达到我们的期望,我们可能不得不重新审视这些特性,并尽最大努力以不同的方式融入它们。但是现在,我们可以把它们看作数字特征。
下面的代码片段提取列的数字部分,并将列从字符串转换为数字特征。
#For people ID, we would need to extract values after '_'
df_new.people_id = df_new.people_id.apply(lambda x:x.split("_")[1])
df_new.people_id = pd.to_numeric(df_new.people_id)
#For activity ID also, we would need to extract values after '_'
df_new.activity_id = df_new.activity_id.apply(lambda x:x.split("_")[1])
df_new.activity_id = pd.to_numeric(df_new.activity_id)
#For group_1 , we would need to extract values after "
df_new.group_1 = df_new.group_1.apply(lambda x:x.split("")[1])
df_new.group_1 = pd.to_numeric(df_new.group_1)
#For activity_type , we would need to extract values after "
df_new.activity_type = df_new.activity_type.apply(lambda x:x.split("")[1])
df_new.activity_type = pd.to_numeric(df_new.activity_type)
#Double check the new values in the dataframe
print(df_new[["people_id","activity_type","activity_id","group_1"]].head())
输出
people_id activity_type activity_id group_1
0 100.0 76 1734928.0 17304
1 100.0 1 2434093.0 17304
2 100.0 1 3404049.0 17304
3 100.0 1 3651215.0 17304
4 100.0 1 4109017.0 17304
现在,我们将布尔列转换为数字列,并且将包含大量不同值的分类列也转换为数字列。(注意:这种分类到数字的转换并不总是可行的。)接下来,让我们将剩余的非重复值数量相对较少的分类列转换为一键编码形式,并呈现最终的合并数据集。
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
#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
target = ["outcome"]
numeric_columns = list(set(temp.index[(temp.DataType =="float64") |
(temp.DataType =="int64")].values) - set(target))
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)
print("\nShape of final df after onehot encoding:",temp.shape)
输出
Shape of final df after onehot encoding: (2197291, 183)
我们现在已经为模型开发准备好了数据集的最终形式。在本练习中,我们转换并保留了与日期相关的功能,因为它们是数字形式,而不是转换为一键编码形式。这个选项是可选的。我考虑了数据集的大小,大约有 180 列,开始时足够大了。我们将进行一些基本实验,如果我们没有看到良好的性能,我们将需要重新访问数据。在这种情况下,我们需要寻找改进的策略,以便以最节省内存和计算的方式从大量选择的特征中提取最佳信息。
最后,在我们开始模型开发之前,我们需要将我们的数据集分成训练、验证和测试,就像我们在第三章中对回归用例所做的那样。以下代码片段利用 Python 中 sklearn 包的“train_test_split”将前面创建的最终数据集拆分为 train 和 test,然后进一步将 train 划分为 train 和 validation。
from sklearn.model_selection import train_test_split
#split the final dataset into train and test with 80:20
x_train, x_test, y_train, y_test = train_test_split(temp,df_new[target], test_size=0.2,random_state=2018)
#split the train dataset further into train and validation with 90:10
x_train, x_val, y_train, y_val = train_test_split(x_train, y_train, test_size=0.1, random_state=2018)
#Check the shape of each new dataset created
print("Shape of x_train:",x_train.shape)
print("Shape of x_test:",x_test.shape)
print("Shape of x_val:",x_val.shape)
print("Shape of y_train:",y_train.shape)
print("Shape of y_test:",y_test.shape)
print("Shape of y_val:",y_val.shape)
输出
Shape of x_train: (1582048, 183)
Shape of x_test: (439459, 183)
Shape of x_val: (175784, 183)
Shape of y_train: (1582048, 1)
Shape of y_test: (439459, 1)
Shape of y_val: (175784, 1)
现在,我们有了构建分类的 DL 模型所需形式的训练数据。我们需要定义一个基线基准,它将帮助我们设置我们应该从模型中期望的阈值性能,以使它们被认为是有用的和可接受的。
定义模型基线准确性
在第三章中,当我们使用回归用例时,我们通过使用训练数据集的平均值作为测试数据集中所有值的最终预测来定义基线精度。然而,在分类用例中,我们需要一个稍微不同的方法。
对于所有监督分类用例,我们的目标变量将是二元或多类(两个以上的类)结果。在我们的用例中,我们的结果是 0 或 1。为了验证模型的有效性,我们应该将结果与如果我们没有模型会发生的情况进行比较。在这种情况下,我们将最大的类作为所有客户的预测,并检查其准确性。
如果您还记得,我们用例中的目标(即结果变量)具有 1 和 0 的良好分布。这里是结果变量在 1 和 0 之间的分布。
#Checking the distribution of values in the target
df_new["outcome"].value_counts()/df_new.shape[0]
Output
0 0.556046
1 0.443954
Name: outcome, dtype: float64
因此,对于前面的分布,我们可以说,如果我们没有任何模型,所有预测都为 0(最大类),也就是说,预测没有一个客户是潜在的高价值客户,那么无论如何,我们都至少有 55.6%的准确性。这是我们的基线精度。如果我们建立一个模型,提供给我们的整体精度低于我们的基准,那么它实际上是没有用的。
设计用于分类的 DNN
对于这个用例,我们有更大的数据集。训练过程可能比回归用例更耗时。为了节省我们的时间,并且能够快速得到一个功能良好的架构,我们将使用一个简单的策略。对于我们将要试验的每种网络,我们将从三个时期开始,一旦我们发现有希望的结果,我们将用期望数量的时期来重新训练最佳架构以获得改进的结果。
首先,让我们遵循我们在第三章中学到的相同的架构开发指南。也就是说,让我们遵循规则 1:从小处着手。
下面的代码片段构建了一个只有一层和 256 个神经元的 DNN。我们使用binary_crossentropy(因为这是一个二进制分类问题)作为损失函数,并使用精确度作为监控的度量。对于分类问题,我们可以使用 Keras 中可用的其他几个指标,但是准确性很简单,也很容易理解。我们将只训练三个时期的网络,并持续监控训练和验证数据集的损失和准确性。如果我们看不到有希望的结果,我们可能不得不尝试新的架构。
from keras.models import Sequential
from keras.layers import Dense
#Design the deep neural network [Small + 1 layer]
model = Sequential()
model.add(Dense(256,input_dim = x_train.shape[1],activation="relu"))
model.add(Dense(256,activation="relu"))
model.add(Dense(1,activation = "sigmoid")) #activation = sigmoid for binary classification
model.compile(optimizer = "Adam",loss="binary_crossentropy",metrics=["accuracy"])
model.fit(x_train,y_train, validation_data = (x_val,y_val),epochs=3, batch_size=64)
输出
Using TensorFlow backend.
Train on 1582048 samples, validate on 175784 samples
Epoch 1/3
1582048/1582048 [==============================] - 112s 71us/step - loss: 8.8505 - acc: 0.4449 - val_loss: 8.8394 - val_acc: 0.4455
Epoch 2/3
1582048/1582048 [==============================] - 111s 70us/step - loss: 8.8669 - acc: 0.4438 - val_loss: 8.8394 - val_acc: 0.4455
Epoch 3/3
1582048/1582048 [==============================] - 110s 69us/step - loss: 8.8669 - acc: 0.4438 - val_loss: 8.8394 - val_acc: 0.4455
如果您仔细观察来自训练输出的结果,您将会看到训练和验证数据集的总体准确度约为 0.44 (44%),这远远低于我们的基线准确度。因此,我们可以得出结论,进一步训练这个模型可能不是一个富有成效的想法。
让我们为相同数量的神经元尝试一个更深的网络。所以,我们保持一切不变,但增加了一层相同数量的神经元。
#Design the deep neural network [Small + 2 layers]
model = Sequential()
model.add(Dense(256,input_dim = x_train.shape[1],activation="relu"))
model.add(Dense(256,activation="relu"))
model.add(Dense(1,activation = "sigmoid"))
model.compile(optimizer = "Adam",loss="binary_crossentropy",metrics=["accuracy"])
model.fit(x_train,y_train, validation_data = (x_val,y_val),epochs=3, batch_size=64)
输出
Train on 1582048 samples, validate on 175784 samples
Epoch 1/3
1582048/1582048 [==============================] - 124s 79us/step - loss: 8.8669 - acc: 0.4438 - val_loss: 8.8394 - val_acc: 0.4455
Epoch 2/3
1582048/1582048 [==============================] - 125s 79us/step - loss: 8.8669 - acc: 0.4438 - val_loss: 8.8394 - val_acc: 0.4455
Epoch 3/3
1582048/1582048 [==============================] - 124s 78us/step - loss: 8.8669 - acc: 0.4438 - val_loss: 8.8394 - val_acc: 0.4455
同样,正如我们所看到的,最初的结果一点也不乐观。来自更深层次网络的训练和验证准确性与我们预期的相差甚远。让我们试着用一个更大(中等规模)的网络来训练,而不是尝试另一个更深层次的网络,比如三到五层。这次我们将使用一个只有一层但有 512 个神经元的新架构。让我们再次训练三个纪元,并查看度量标准,以检查它是否符合我们的预期。
#Design the deep neural network [Medium + 1 layers]
model = Sequential()
model.add(Dense(512,input_dim = x_train.shape[1],activation="relu"))
model.add(Dense(1,activation = "sigmoid"))
model.compile(optimizer = "Adam",loss="binary_crossentropy",metrics=["accuracy"])
model.fit(x_train,y_train, validation_data = (x_val,y_val),epochs=3, batch_size=64)
输出
Train on 1582048 samples, validate on 175784 samples
Epoch 1/3
1582048/1582048 [==============================] - 113s 71us/step - loss: 8.8669 - acc: 0.4438 - val_loss: 8.8394 - val_acc: 0.4455
Epoch 2/3
1582048/1582048 [==============================] - 112s 71us/step - loss: 8.8669 - acc: 0.4438 - val_loss: 8.8394 - val_acc: 0.4455
Epoch 3/3
1582048/1582048 [==============================] - 112s 71us/step - loss: 8.8669 - acc: 0.4438 - val_loss: 8.8394 - val_acc: 0.4455
中型网络也返回了令人失望的结果。中型网络的训练和验证准确性与我们的预期相差甚远。现在,让我们尝试增加中型网络的深度,看看结果是否有所改善。
#Design the deep neural network [Medium + 2 layers]
model = Sequential()
model.add(Dense(512,input_dim = x_train.shape[1],activation="relu"))
model.add(Dense(512,activation="relu"))
model.add(Dense(1,activation = "sigmoid"))
model.compile(optimizer = "Adam",loss="binary_crossentropy",metrics=["accuracy"])
model.fit(x_train,y_train, validation_data = (x_val,y_val),epochs=3, batch_size=64)
输出
Train on 1582048 samples, validate on 175784 samples
Epoch 1/3
1582048/1582048 [==============================] - 135s 86us/step - loss: 7.1542 - acc: 0.5561 - val_loss: 7.1813 - val_acc: 0.5545
Epoch 2/3
1582048/1582048 [==============================] - 134s 85us/step - loss: 7.1534 - acc: 0.5562 - val_loss: 7.1813 - val_acc: 0.5545
Epoch 3/3
1582048/1582048 [==============================] - 135s 85us/step - loss: 7.1534 - acc: 0.5562 - val_loss: 7.1813 - val_acc: 0.5545
我们可以看到结果有所改善,但只是一点点。我们看到训练和验证数据集的准确率约为 55%,但这些结果也不是很好,尽管比我们之前的结果要好。
重新审视数据
最初试图建立一个具有良好结果的模型的努力已经失败。我们可以进一步增加网络的规模和深度,但这只会略微提高网络性能。如前所述,我们可能不得不考虑改进用于训练的数据。我们有两个主要选择。我们已经在第二章的“输入数据”部分和第三章的“探索数据”部分讨论了这两点。我们可以使用 Python 的 sklearn 包中的工具,使用“Standardscaler”或“Minmaxscaler”对输入数据进行标准化,或者我们可以探索各种选项,重新对我们编码为数值的分类特征进行一次性编码。从这两个选项中,最简单和最省时的是标准化或规范化数据。
标准化、规范化或缩放数据
如果您还记得,在第二章“Keras 中的 DL 入门”下的“输入数据”一节中,我们讨论过在提供数据作为 DL 模型的训练数据之前,将数据标准化或规范化是一个好的做法。我们没有在第三章的回归用例中使用这一选项,因为该模型在常规数据上表现良好。然而,在我们的分类用例中,我们可以看到在原始数据上的性能非常差。为了提高我们的模型性能,让我们尝试标准化我们的数据。(或者,您也可以标准化数据。)
在标准化中,我们将数据转换为均值为 0、标准差为 1 的形式。这种形式的数据分布是我们神经元激活功能的一个很好的输入候选,因此提高了更恰当地学习的能力。
最简单的形式是,标准化可以通过以下使用虚拟输入数据集的示例来解释。我们执行标准缩放;查看转换后的值、平均值及其标准差;最后将输出逆变换为其原始形式。
#Create a dummy input
dummy_input = np.arange(1,10)
print("Dummy Input = ",dummy_input)
from sklearn.preprocessing import StandardScaler
#Create a standardscaler instance and fit the data
scaler = StandardScaler()
output = scaler.fit_transform(dummy_input.reshape(-1,1))
print("Output =\n ",list(output))
print("Output's Mean = ",output.mean())
print("Output's Std Dev = ",output.std())
print("\nAfter Inverse Transforming = \n",list(scaler.inverse_transform(output)))
输出
Dummy Input = [1 2 3 4 5 6 7 8 9]
Output =
[array([-1.54919334]), array([-1.161895]), array([-0.77459667]),
array([-0.38729833]), array([0.]), array([0.38729833]),
array([0.77459667]), array([1.161895]), array([1.54919334])]
Output's Mean = 0.0
Output's Std Dev = 1.0
After Inverse Transforming =
[array([1.]), array([2.]), array([3.]), array([4.]), array([5.]),
array([6.]), array([7.]), array([8.]), array([9.])]
转换输入数据
要转换用于模型开发的输入数据,请注意,我们应该仅使用训练数据来拟合缩放器转换,并使用相同的拟合对象来转换验证和测试输入数据。以下代码片段使用x_train数据集来拟合和转换所有三个数据集的缩放值(即x_train和x_val以及x_test)。
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaler.fit(x_train)
x_train_scaled = scaler.transform(x_train)
x_val_scaled = scaler.transform(x_val)
x_test_scaled = scaler.transform(x_test)
既然我们已经有了标准的缩放数据集,我们就可以为训练提供这些新增加的数据。请注意,我们没有对标签或目标进行任何转换。
用于改进数据分类的 DNNs
现在让我们从一个中等规模的网络开始,看看我们是否能得到改进的结果。我们将从三个时代开始。
from keras import Sequential
from keras.layers import Dense
model = Sequential()
model.add(Dense(512,input_dim = x_train_scaled.shape[1],activation="relu"))
model.add(Dense(1,activation = "sigmoid"))
model.compile(optimizer = "Adam",loss="binary_crossentropy",metrics=["accuracy"])
model.fit(x_train_scaled,y_train, validation_data = (x_val_scaled,y_val), epochs=3, batch_size=64)
输出
Train on 1582048 samples, validate on 175784 samples
Epoch 1/3
1582048/1582048 [==============================] - 109s 69us/step - loss: 0.2312 - acc: 0.8994 - val_loss: 0.1894 - val_acc: 0.9225
Epoch 2/3
1582048/1582048 [==============================] - 108s 68us/step - loss: 0.1710 - acc: 0.9320 - val_loss: 0.1558 - val_acc: 0.9387
Epoch 3/3
1582048/1582048 [==============================] - 108s 68us/step - loss: 0.1480 - acc: 0.9444 - val_loss: 0.1401 - val_acc: 0.9482
现在,我们走吧!
我们可以看到网络在提供标准化数据集方面的性能有了显著提高。我们在训练和验证数据集上有几乎 95%的准确率。让我们使用这个模型来评估我们之前创建的测试数据集上的模型性能。
result = model.evaluate(x_test_scaled,y_test)
for i in range(len(model.metrics_names)):
print("Metric ",model.metrics_names[i],":",str(round(result[i],2)))
输出
439459/439459 [==============================] - 34s 76us/step
Metric loss : 0.1
Metric acc : 0.96
我们在测试数据集上看到了很好的结果。让我们试着改进一下架构,然后看看。我们可以建立一个中等规模的深度网络,看看结果是否比中等规模的网络更好。
#Designing the Deep Neural Network [Medium – 2 Layers]
model = Sequential()
model.add(Dense(512,input_dim = x_train_scaled.shape[1],activation="relu"))
model.add(Dense(512,activation="relu"))
model.add(Dense(1,activation = "sigmoid"))
model.compile(optimizer = "Adam",loss="binary_crossentropy",metrics=["accuracy"])
model.fit(x_train_scaled,y_train, validation_data = (x_val_scaled,y_val),epochs=3, batch_size=64)
输出
Train on 1582048 samples, validate on 175784 samples
Epoch 1/3
1582048/1582048 [==============================] - 131s 83us/step - loss: 0.1953 - acc: 0.9141 - val_loss: 0.1381 - val_acc: 0.9421
Epoch 2/3
1582048/1582048 [==============================] - 130s 82us/step - loss: 0.1168 - acc: 0.9529 - val_loss: 0.1051 - val_acc: 0.9578
Epoch 3/3
1582048/1582048 [==============================] - 131s 83us/step - loss: 0.0911 - acc: 0.9646 - val_loss: 0.0869 - val_acc: 0.9667
训练和验证准确率进一步提高到 96%。这个只有 3 个纪元的小增长是令人敬畏的。我们现在可以对该架构模型的性能充满信心。我们肯定可以尝试更多的架构并检查结果,但让我们用一个更大更深的网络做最后一次尝试,看看 3 个时期的结果。如果我们只看到很小的改进,我们将在 15 个时期使用相同的架构,并使用该模型进行最终预测。
#Designing the network Deep Neural Network – [Large + 2 Layers]
model = Sequential()
model.add(Dense(1024,input_dim = x_train_scaled.shape[1],activation="relu"))
model.add(Dense(1024,activation = "relu"))
model.add(Dense(1,activation = "sigmoid"))
model.compile(optimizer = "Adam",loss="binary_crossentropy",metrics=["accuracy"])
model.fit(x_train_scaled,y_train, validation_data = (x_val_scaled,y_val),epochs=3, batch_size=64)
输出
Train on 1582048 samples, validate on 175784 samples
Epoch 1/3
1582048/1582048 [==============================] - 465s 294us/step - loss: 0.2014 - acc: 0.9099 - val_loss: 0.1438 - val_acc: 0.9390
Epoch 2/3
1582048/1582048 [==============================] - 483s 305us/step - loss: 0.1272 - acc: 0.9469 - val_loss: 0.1184 - val_acc: 0.9524
Epoch 3/3
1582048/1582048 [==============================] - 487s 308us/step - loss: 0.1015 - acc: 0.9593 - val_loss: 0.1011 - val_acc: 0.9605
我们看到验证数据集的总体准确率为 96%,训练数据集也有类似的分数。因此,由于将规模从中等(512 个神经元)增加到更大(1024 个神经元)的架构,模型的性能实际上没有太大的改善。利用这些结果来验证我们的实验,让我们训练一个中等规模(512 个神经元)的两层深度网络 15 个时期,查看最终的训练和验证准确性,然后使用训练好的模型来评估测试数据集。
#Designing the network Deep Neural Network – [Medium + 2 Layers]
model = Sequential()
model.add(Dense(512,input_dim = x_train_scaled.shape[1],activation="relu"))
model.add(Dense(512,activation = "relu"))
model.add(Dense(1,activation = "sigmoid"))
model.compile(optimizer = "Adam",loss="binary_crossentropy",metrics=["accuracy"])
model.fit(x_train_scaled,y_train, validation_data = (x_val_scaled,y_val),epochs=15, batch_size=64)
输出
Train on 1582048 samples, validate on 175784 samples
Epoch 1/15
1582048/1582048 [==============================] - 133s 84us/step - loss: 0.1949 - acc: 0.9142 - val_loss: 0.1375 - val_acc: 0.9426
Epoch 2/15
1582048/1582048 [==============================] - 133s 84us/step - loss: 0.1173 - acc: 0.9527 - val_loss: 0.1010 - val_acc: 0.9599
Epoch 3/15
1582048/1582048 [==============================] - 133s 84us/step - loss: 0.0911 - acc: 0.9643 - val_loss: 0.0887 - val_acc: 0.9660
----Skipping output from intermediate epochs -----
Epoch 14/15
1582048/1582048 [==============================] - 133s 84us/step - loss: 0.0402 - acc: 0.9863 - val_loss: 0.0614 - val_acc: 0.9821
Epoch 15/15
1582048/1582048 [==============================] - 133s 84us/step - loss: 0.0394 - acc: 0.9869 - val_loss: 0.0629 - val_acc: 0.9818
具有 512 个神经元和两层的中等规模架构的最终模型在训练和验证数据集上给出了很好的性能结果。我们对两个数据集的准确率都在 98%左右。现在让我们在测试数据集上验证模型性能。
result = model.evaluate(x_test_scaled,y_test)
for i in range(len(model.metrics_names)):
print("Metric ",model.metrics_names[i],":",str(round(result[i],2)))
输出
439459/439459 [==============================] - 20s 45us/step
Metric loss : 0.06
Metric acc : 0.98
在看不见的测试数据集上的性能也很好,并且一致。我们的模型在测试数据集上表现得非常好。让我们看看模型的损失曲线,就像我们对回归用例所做的那样。我们将为训练和验证数据集绘制每个历元中的损失(对于该模式总共 15 个)。下面的代码片段利用了模型历史并绘制了这些指标。
import matplotlib.pyplot as plt
%matplotlib inline
plt.plot(model.history.history['loss'])
plt.plot(model.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()

我们可以看到两个数据集的损失都在减少。类似地,让我们看看模型训练期间的准确性度量。训练和验证数据集的准确性度量也存储在模型历史中。
plt.plot(model.history.history['acc'])
plt.plot(model.history.history['val_acc'])
plt.title("Model's Training & Validation Accuracy across epochs")
plt.ylabel('Accuracy')
plt.xlabel('Epochs')
plt.legend(['Train', 'Validation'], loc='upper right')
plt.show()

正如我们所看到的,精确度随着每个时期不断提高。在我们观察到任何指标的训练和验证数据之间存在巨大差距的情况下,这将是模型过度拟合的迹象。在这种情况下,模型在训练数据集上表现很好,但在看不见的数据(即验证和测试数据集)上表现很差。
摘要
在本章中,我们探索了一个业务用例,并通过利用 DNN 进行分类来解决它。我们从理解问题陈述的业务本质开始,探索所提供的数据,并扩充成适合 DNNs 的形式。我们试验了一些架构,牢记我们在第三章中学到的经验法则,我们看到了模型性能的一个主要缺点。然后,我们重新审视了这些数据,并使用标准化技术以一种更加 DL 友好的形式和架构为一些 dnn 表示这些数据,我们看到了惊人的结果。总的来说,我们加强了在数据工程、数据探索、DL 以及 Keras 和 Python 方面的学习。在下一章中,我们将探索通过超参数调整提高模型性能的其他策略,了解迁移学习,并探索大型软件的模型部署的高级流程。
五、调整和部署深度神经网络
到目前为止,在本书的旅程中,我们主要讨论了如何为一个给定的用例开发一个 DNN,并且查看了一些策略和经验法则来绕过我们在这个过程中可能面临的障碍。在这一章中,我们将讨论开发初始模型之后的旅程,探索当开发的模型没有达到您的期望时,您需要实现的方法和途径。我们将讨论正则化和超参数调整,在本章的最后,我们还将对调整后部署模型的过程有一个高层次的了解。然而,我们实际上不会讨论部署的实现细节;这只是一个概述,提供了在这一过程中取得成功的指南。让我们开始吧。
过度拟合的问题
在开发和训练 ML 和 DL 模型的过程中,您经常会遇到这样的情况:经过训练的模型在训练数据集上表现良好,但在测试数据集上表现不佳。在数据科学中,这种现象被称为“过度拟合”。从字面上看,你的模型过度拟合了数据。虽然你在本书的前面已经遇到过这个术语,但是到目前为止我们还没有详细讨论过这个话题。让我们试着用一种更简化的方式来理解这种现象。
训练一个模型的过程叫做“拟合数据”;神经网络学习数据中的潜在模式,并在数学上改进模型权重/结构,以适应它在学习过程中发现的模式。简而言之,训练模型会调整其结构(权重)以适应数据(模式),从而提高其性能。当它发现的模式在现实中仅仅是噪音时,这个美丽的过程就变得复杂了。不幸的是,数学方程并不具有总是区分信号和噪声的能力(对于噪声,我们指的是不代表训练样本但由于随机机会而出现的数据点)。当它失败时,它也学习噪声并调整它的权重以适应新的信号,这实际上是噪声。
为了理解这个过程,我们举一个简单的例子。比如说一个五岁的孩子爱吃妈妈烤的蛋糕。他要求每天在家烤蛋糕。他的母亲礼貌地拒绝了这些要求,但向他保证,她会在某些场合烤蛋糕。小男孩现在期待着每一个新的一天,希望这将是他妈妈烤蛋糕的一个机会。另一方面,他的母亲并不想找机会烤蛋糕。她每周日下班后都会烤一个蛋糕。这个五岁的孩子继续每天观看,慢慢地知道他的妈妈会在每个星期天烤一个蛋糕。所以,他学会了下面的模式:“如果 day == Sunday,那么妈妈会烤蛋糕。”一个晴朗的星期天,他的母亲不得不出差,没有时间烤蛋糕。这个五岁的孩子无法理解他的模式被打破了。因此,为了适应新的事件,他修改了他的规则,制定了新的模式如下:“如果 day == Sunday,那么妈妈将烤一个蛋糕,但如果这一天是在一个月的最后一周,那么没有蛋糕。”事实上,他妈妈错过烤蛋糕的那个星期天是一个噪音。理想情况下,他应该忽略这一点,并保持他之前学到的模式不变。但不幸的是,他未能区分信号和噪音,从而使他的规则过于复杂,数据过于拟合。
类似地,当 DL 模型从噪声中学习并通过调整权重来适应噪声时,它会过度拟合数据。这个问题变得很严重,因为学习噪声会导致模型性能的显著下降。这就是为什么您会观察到模型在训练数据上的性能和在看不见的数据上的性能之间有很大的差距。通过正则化,可以在很大程度上(尽管不是完全)避免这个问题,并定制模型的学习过程,以仅适应信号(或真实模式)而不是噪声。
那么,什么是正规化呢?
简而言之,正则化是一个减少过度拟合的过程。这是一种数学方法,当模型适应噪声时,将警告引入模型的学习过程。为了给出更现实的定义,这是一种在过度拟合的情况下惩罚模型权重的方法。
让我们用一种非常简单的方式来理解这个过程。在 DL 中,神经元连接的权重在每次迭代后更新。当模型遇到有噪声的样本并假设该样本是有效样本时,它会尝试通过更新与噪声一致的权重来适应该模式。在真实的数据样本中,噪声数据点不像任何接近常规数据点的东西;他们离他们很远。因此,权重更新也将与噪声同步(即,权重的变化将是巨大的)。正则化过程将边缘的权重添加到定义的损失函数中,并且整体上表示更高的损失。然后,网络调整自身以减少损失,从而使权重在正确的方向上更新;这是通过忽略噪音而不是在学习过程中适应噪音来实现的。
正则化的过程可以表示为
成本函数= 损失 (如模型定义)+ 超参数×权重】**
*超参数表示为
,λ的值由用户定义。
基于如何将权重添加到损失函数,我们有两种不同类型的正则化技术:L1 和 L2。
L1 正则化
在 L1 正则化中,绝对权重被添加到损失函数中。为了使模型更一般化,权重的值被减少到 0,因此当我们试图压缩模型以进行更快的计算时,这种方法是非常优选的。
该等式可以表示为
成本函数=损失(定义)+ 
在 Keras 中,通过向“内核正则化”参数提供“正则化”对象,可以将 L1 损失添加到图层中。以下代码片段演示了如何将 L1 正则化器添加到 Keras 中的密集图层。
from keras import regularizers
from keras import Sequential
model = Sequential()
model.add(Dense(256, input_dim=128, kernel_regularizer=regularizers.l1(0.01))
值 0.01 是我们为λ设置的超参数值。
L2 正则化
在 L2 正则化中,平方权重被添加到损失函数中。为了使模型更一般化,权重值被减少到接近 0(但实际上不是 0),因此这也被称为“权重衰减”方法。在大多数情况下,L2 比 L1 更能减少过度拟合。
该等式可以表示为
成本函数=损失(定义)+ 
我们可以像 L1 一样给 DL 模型添加一个 L2 正则化子。以下代码片段演示了如何向密集图层添加 L2 正则化因子。
model = Sequential()
model.add(Dense(256, input_dim=128, kernel_regularizer=regularizers.l2(0.01))
值 0.01 是我们为λ设置的超参数值。
辍学正规化
除了 L1 和 L2 正则化,在 d L 中还有另一种流行的技术来减少过拟合。这种技术使用了一种退出机制。在这种方法中,模型在每次迭代过程中任意删除或停用一层的几个神经元。因此,在每一次迭代中,该模型会查看自身稍有不同的结构进行优化(因为一些神经元和连接会被停用)。假设我们有两个连续的层,H1 和 H2,分别有 15 和 20 个神经元。在这两层之间应用丢弃技术将导致 H1 随机丢弃一些神经元(基于定义的百分比),从而减少 H1 和 H2 之间的连接。这个过程在每次迭代中随机重复,因此如果模型已经学习了一批并更新了权重,下一批可能会有一组完全不同的权重和连接来训练。该过程不仅由于减少的计算而有效,而且在减少过拟合方面直观地起作用,因此提高了整体性能。
使用下图可以直观地理解辍学的概念。我们可以看到,规则网络的所有神经元和两个连续层之间的连接都完好无损。使用 dropout,每次迭代都会通过任意停用或丢弃一些神经元及其关联的权重连接来引入一定程度的随机性。

在 Keras 中,我们可以按照以下约定对层使用 dropout:
keras.layers.Dropout(rate, noise_shape=None, seed=None)
以下代码片段展示了添加到密集隐藏层的 dropout。参数值 0.25 表示退出率(即要退出的神经元的百分比)。
from keras import Sequential
from keras.layers.core import Dropout, Dense
model = Sequential()
model.add(Dense(100, input_dim= 50, activation="relu"))
model.add(Dropout(0.25))
model.add(Dense(1,activation="linear"))
超参数调谐
超参数是定义模型整体结构和学习过程的参数。我们也可以将超参数作为模型的元参数。它不同于模型的实际参数,模型的实际参数是在训练过程中学习的(例如,模型权重)。与模型参数不同,超参数无法学习;我们需要用不同的方法来调优它们,以获得更好的性能。
为了更好地理解这个话题,让我们以一种更简化的方式来看这个定义。当我们设计一个 DNN 时,模型的架构是由一些高级工件定义的。这些工件可以是层中神经元的数量、隐藏层的数量、激活函数、优化器、架构的学习速率、时期的数量、批量大小等等。所有这些参数共同用于设计网络,它们对模型的学习过程及其最终性能有着巨大的影响。这些参数不能被训练;事实上,它们需要用经验和判断来选择,就像我们在第三章学到的规则一样,来决定架构的大小。定义模型整体架构的参数统称为超参数。选择正确的超参数是一个密集和迭代的过程,但随着经验的积累,它会变得更容易。用超参数的不同值进行试验以改进整个模型过程的过程称为模型调整或超参数调整。
DL 中的超参数
让我们看看 DL 模型可用的不同超参数,并研究可供选择的选项。然后,我们将研究为模型选择正确的超参数集的各种方法。
一层中的神经元数量
对于大多数使用表格横截面数据的分类和回归用例,可以通过调整网络的宽度(即一层中神经元的数量)来增强 DNNs 的鲁棒性。通常,选择第一层神经元数量的简单经验法则是参考输入维度的数量。如果给定训练数据集中输入维度的最终数量(这也包括一次性编码的特征)是 x,我们应该至少使用最接近 2x 的 2 次方的数字。假设您的训练数据集中有 100 个输入维度:最好从 2 × 100 = 200 开始,取最接近的 2 的幂,即 256。神经元的数量最好是 2 的幂,因为这有助于网络的计算更快。此外,神经元数量的最佳选择是 8、16、32、64、128、256、512、1024 等等。根据输入尺寸的数量,取最接近尺寸两倍的数字。所以,当你有 300 个输入维度时,试着用 512 个神经元。
层数
的确,仅仅增加几层通常会提高性能,至少是略微提高。但问题是,随着层数的增加,训练时间和计算量显著增加。此外,要看到有希望的结果,您需要更多的历元数。不使用更深的网络并不总是一个选项;在必要的情况下,尝试使用一些最佳实践。
如果您正在使用一个非常大的网络,比方说超过 20 层,请尝试使用一个逐渐变小的架构(即,随着深度的增加,逐渐减少每层中的神经元数量)。因此,如果您使用 30 层架构,每层有 512 个神经元,请尝试慢慢减少层中神经元的数量。一个改进的架构是前 8 层有 512 个神经元,接下来的 8 层有 256 个,接下来的 8 层有 128 个,依此类推。对于最后一个隐藏层(不是输出层),尝试将神经元的数量至少保持在输入大小的 30–40%左右。
或者,如果您使用更宽的网络(即,不减少较低层的神经元数量),请始终使用 L2 正则化或丢弃率约为 30%的丢弃层。过度拟合的机会大大减少。

时代数
有时,仅仅增加模型训练的历元数就能获得更好的结果,尽管这是以增加计算和训练时间为代价的。
重量初始化
初始化网络的权重也会对整体性能产生巨大影响。好的权重初始化技术不仅加速了训练过程,而且避免了模型训练过程中的死锁。默认情况下,Keras 框架使用 glorot 统一初始化,也称为 Xavier 统一初始化,但这可以根据您的需要进行更改。我们可以使用内核初始化参数初始化层的权重,也可以使用偏差初始化参数初始化偏差。
可供选择的其他常用选项有“He Normal”和“He Uniform”初始化以及“lecun normal”和“lecun uniform”初始化。在 Keras 中也有相当多的其他选择,但上述选择是最受欢迎的。
以下代码片段展示了一个使用random_uniform在 DNN 的图层中初始化权重的示例。
from keras import Sequential
from keras.layers import Dense
model = Sequential()
model.add(Dense(64,activation="relu", input_dim = 32, kernel_initializer = "random_uniform",bias_initializer = "zeros"))
model.add(Dense(1,activation="sigmoid"))
批量
使用适度的批量总是有助于实现模型的更平滑的学习过程。在大多数情况下,不管数据集大小和样本数量如何,32 或 64 的批量大小将提供平滑的学习曲线。即使在您的硬件环境具有大 RAM 内存来容纳更大的批处理大小的情况下,我仍然建议保持 32 或 64 的批处理大小。
学习率
学习率是在优化算法的上下文中定义的。它定义了每一步的长度,或者简单地说,在每次迭代中可以对权重进行多大的更新。在本书中,我们忽略了设置或改变学习率,因为我们使用了各自优化算法的默认值,在我们的例子中是 Adam。默认值为 0.001,这是大多数情况下的最佳选择。然而,在一些特殊的情况下,您可能会遇到一个用例,在这个用例中,学习率较低或者稍高可能会更好。
激活功能
我们对神经元的激活功能有很多选择。在大多数情况下,ReLU 可以完美地工作。您几乎总是可以将 ReLU 作为任何用例的激活来进行,并获得良好的结果。在 ReLU 可能不会提供很好的结果的情况下,尝试 PReLU 是一个很好的选择。
最佳化
类似于激活函数,对于网络的优化算法,我们也有相当多的选择。虽然最推荐的是 Adam,但在 Adam 可能无法为您的架构提供最佳结果的情况下,您可以探索 Adamax 以及 Nadam 优化器。对于像单词嵌入这样的参数更新很少的架构来说,Adamax 通常是更好的选择,单词嵌入通常用于自然语言处理技术。我们在书中没有涉及这些高级主题,但是在探索各种体系结构时记住这些要点是有好处的。
超参数调谐方法
到目前为止,我们已经讨论了可用于 DL 模型的各种超参数,并且还研究了针对一般情况的最推荐选项。然而,根据数据和问题类型为超参数选择最合适的值更像是一门艺术。这门艺术也是艰巨而缓慢的。DL 中超参数调整的过程几乎总是很慢并且资源密集。然而,基于为超参数选择值和进一步调整模型性能的方式,我们可以将不同类型的方法大致分为四大类:
-
手动搜索
-
网格搜索
-
随机搜索
-
贝叶斯优化
在上述四种方法中,我们将简要介绍前三种。贝叶斯优化是一个漫长而困难的话题,超出了本书的范围。让我们简单看一下前三种方法。
手动搜索
顾名思义,手动搜索是为 DL 模型中所需的超参数选择最佳候选值的一种完全手动的方式。这种方法需要在训练网络方面有丰富的经验,以便使用最少的实验次数为所有期望的超参数获得正确的候选值集。通常这种方法效率很高,前提是你有使用它们的丰富经验。开始手动搜索的最佳方法是简单地利用给定超参数的所有推荐值,然后开始训练网络。结果可能不是最好的,但绝对不会是最差的。对于该领域的任何新手来说,尝试几个风险最低的超参数候选者都是一个很好的起点。
网格搜索
在网格搜索方法中,您实际上是在为一组定义的超参数值试验所有可能的组合。“网格”这个名称实际上来源于每个超参数所提供值的网格状组合。下面是一个示例视图,展示了逻辑网格如何寻找三个超参数,每个超参数中有三个不同的值。

方法是尝试为每个组合开发一个模型,如前面所示。“x”表示将使用该特定超参数值开发的模型。例如,对于学习率(0.1),垂直列显示将使用不同的优化器和批处理大小值开发的不同模型。类似地,如果您查看超参数“batch-size”= 32 的水平行,该行所有单元格中的“x”表示将使用不同的学习率和优化器值开发的不同模型。因此,在一个只有三个超参数和三个值的网格中,我们正在开发太多的模型。如果我们正在开发相当大的网络并使用更大的训练数据样本,这个过程将会非常漫长。
这种方法的优点是,它为超参数的定义网格提供了最佳模型。然而,缺点是如果你的网格没有很好的选择,你的模型也不会是最好的。简单地假设,研究模型的科学家对哪些模型可能是给定超参数的最佳候选模型有一个合理的想法。
Keras 没有直接提供在模型上执行网格搜索调优的方法。但是,我们可以使用自定义 for 循环和已定义的训练值,或者使用 Keras 提供的 sklearn 包装器将模型打包到 sklearn 类型对象中,然后利用 sklearn 中的网格搜索方法来完成结果。下面的代码片段展示了通过使用虚拟模型的 Keras 包装器从 sklearn 包中使用网格搜索的方法。
from keras import Sequential
from sklearn.model_selection import GridSearchCV
from keras.wrappers.scikit_learn import KerasClassifier
from keras.layers import Dense
import numpy as np
#Generate dummy data for 3 features and 1000 samples
x_train = np.random.random((1000, 3))
#Generate dummy results for 1000 samples: 1 or 0
y_train = np.random.randint(2, size=(1000, 1))
#Create a python function that returns a compiled DNN model
def create_dnn_model():
model = Sequential()
model.add(Dense(12, input_dim=3, activation="relu"))
model.add(Dense(1, activation="sigmoid"))
model.compile(loss='binary_crossentropy', optimizer="adam", metrics=['accuracy'])
return model
#Use Keras wrapper to package the model as an sklearn object
model = KerasClassifier(build_fn=create_dnn_model)
# define the grid search parameters
batch_size = [32,64,128]
epochs = [15, 30, 60]
#Create a list with the parameters
param_grid = {"batch_size":batch_size, "epochs":epochs}
#Invoke the grid search method with the list of hyperparameters
grid_model = GridSearchCV(estimator=model, param_grid=param_grid, n_jobs=-1)
#Train the model
grid_model.fit(x_train, y_train)
#Extract the best model grid search
best_model = grid_model.best_estimator_
随机搜索
网格搜索的一个改进的替代方法是随机搜索。在随机搜索中,我们可以从一个分布中随机选择一个值,而不是从一个定义的数字列表中为超参数选择一个值,如学习率。然而,这仅适用于数值超参数。因此,代替尝试 0.1、0.01 或 0.001 的学习率,它可以从我们用一些属性定义的分布中选择一个随机值作为学习率。该参数现在有更大范围的值可以试验,并且获得更好性能的机会也更大。它通过引入随机性为更好的超参数选择带来机会,克服了人为猜测限定在限定范围内的超参数的最佳值的缺点。在现实中,对于大多数实际情况,随机搜索大多优于网格搜索。
进一步阅读
要探索一些更具体的例子和贝叶斯优化的简要指南,请参考以下内容:
模型部署
现在,我们终于可以讨论一些关于模型部署的重要问题了。我们从学习 Keras 和 DL 开始,用实际的 dnn 进行回归和分类实验,然后讨论调整超参数以提高模型性能。我们现在可以讨论一些在生产环境中部署 DL 模型的指导方针。我想澄清的是,我们实际上不会作为软件工程师学习在生产中部署模型的过程,或者讨论大型企业项目的 DL 软件管道和架构。相反,我们将重点关注在部署实际模型时需要记住的几个重要方面。
裁剪测试数据
在本书的整个过程中,我们已经看到测试数据与训练数据完全同步。在本书和任何 ML/DL 学习指南中,实验总是在模型训练开始前准备好测试数据。我们通常将现有数据分为训练样本和测试样本,然后使用我们这边的测试数据来验证模型的真实性能。这是一个公平的过程,只要你的目标是培训和开发一个模型。一旦你训练好的模型在软件中投入使用,你实际上并不能访问测试数据。为了实际使用该模型,需要以预期的格式定制数据,以便该模型可以预测并返回预测结果。这个过程实际上是艰巨的,需要精心设计生产软件的数据角力和转换管道。
我们用一个例子来理解这个过程。假设您已经设计并开发了一个 DNN,使用监督分类模型来预测信用卡交易是“真实的”还是“欺诈的”。开发模型时,您可以访问客户数据、交易、销售点属性、时间相关属性、地理属性等。所有这些数据点都存在于不同的来源中。对于模型的开发,您将努力从这些不同的来源获取数据,并以统一的形式呈现出来。对于您的实验,这实际上是一次性的工作。实际上,一旦模型投入使用,整个流程的设计方式就需要能够复制给定客户的数据摄取以及来自不同来源的所有其他必要属性,将其统一并转换为模型预测所需的形式,然后进行大规模的推理。想象一下一家大型银行,其中的实时应用程序同时处理全球数千笔交易。从模型中获取用于推理的定制数据需要真正合理的工程原则,以使模型能够无故障地工作。
设置将实时计算查询请求的数据库或群集/节点的设计原则需要考虑您在训练数据集上完成的数据工程和转换,因为每次使用模型进行预测时都需要执行完全相同的过程。这种动态定制数据以做出推论的过程本身是一种完全不同的艺术,需要精心的工程来构建。通常,数据科学家最不担心这部分问题。我们认为这是软件和数据工程师的工作,我们可以不去管它。这个神话最终会被打破,因为需要建立一个严肃的和谐来解决这个难题。这两个团队,即数据科学家和软件工程师,需要携手合作来完成这项任务。一个数据科学家在理解软件工程师的需求时所面临的困难,反之亦然,这导致了一个新的行业角色 ML engineer 的出现。一个 ML 工程师是一个对这两个领域的交叉有很好理解的候选人。
将模型保存到内存中
在本章的过程中,我们没有讨论的另一个有用的点是将模型作为文件保存到内存中,并在其他时间点重用它。这在 DL 中变得极其重要的原因是训练大型模型所消耗的时间。当你遇到连续几周在超级计算机上训练模型的 DL 工程师时,你不应该感到惊讶。包含图像、音频和非结构化文本数据的现代 DL 模型耗费了大量的训练时间。在这种情况下,一个方便的做法是能够暂停和恢复 DL 模型的训练,并保存中间结果,以便在某个时间点之前执行的训练不会浪费。这可以通过一个简单的回调(Keras 中的一个过程,可以在训练的不同阶段应用于模型)来实现,回调会在定义的里程碑之后将模型的权重与模型结构一起保存到一个文件中。这个保存的模型可以在以后您想要恢复训练时再次导入。这个过程就像你希望的那样继续。我们需要做的就是在一个时期之后或者当我们有了最佳模型时,保存模型结构以及权重。Keras 提供了在每个时期后保存模型或在多个时期的训练期间保存最佳模型的能力。
下面的代码片段显示了在大量时期的训练期间保存模型的最佳权重的示例。
from keras.callbacks import ModelCheckpoint
filepath = "ModelWeights-{epoch:.2f}-{val_acc:.2f}.hdf5"
checkpoint = ModelCheckpoint(filepath, save_best_only=True, monitor="val_acc")
model.fit(x_train, y_train, callbacks=[checkpoint],epochs=100, batch_size=64)
正如您在这个代码片段中看到的,我们用期望的参数定义了一个callbacks对象。我们定义何时保存模型,度量什么指标,以及在哪里保存模型。文件路径使用命名约定,将模型权重存储到文件中,文件名描述纪元编号和相应的精度度量。最后,callbacks对象作为列表传递给模型拟合方法。
或者,您也可以在完成训练后使用save_model方法保存整个模型,然后使用load_model方法将其加载到内存中(可能是第二天)。下面的代码片段显示了一个示例。
from keras.models import load_model
#Train a model for defined number of epochs
model.fit(x_train, y_train, epochs=100, batch_size=64)
# Saves the entire model into a file named as 'dnn_model.h5'
model.save('dnn_model.h5')
# Later, (maybe another day), you can load the trained model for prediction.
model = load_model('dnn_model.h5')
用新数据重新训练模型
当您将模型部署到生产中时,生态系统将继续生成更多数据,这些数据可用于再次训练您的模型。比方说,对于信用卡欺诈用例,您使用 10 万个样本训练您的模型,并获得了 93%的准确率。您觉得性能足够好,可以开始使用,所以您将模型部署到生产中。在一个月的时间内,可以从客户的新交易中获得额外的 10K 样本。现在,您会希望您的模型利用这些新的可用数据,并进一步提高其性能。要实现这一点,你不需要重新训练整个模型;你可以使用暂停再继续的方法。您所需要做的就是使用已经训练好的模型的权重,并提供几个时期的附加数据来传递和迭代新样本。它已经学会的权重不需要被处理掉;您可以简单地使用暂停-恢复公式,继续处理增量数据。
在线模型
在理解了重新训练模型的过程之后,您可能会思考的一个直接问题是,您应该多长时间进行一次这样的训练:每天、每周还是每月进行一次重新训练是一个好的方法吗?正确的答案是你想多频繁就多频繁。只要所需的计算不是瓶颈,每次有新的数据点可用时,递增地训练您的模型是无害的。一个好的做法是,一旦有新的一批样本可用,就迭代一个训练实例。因此,如果您将batch_size设置为 64,您可以自动进行模型训练,以获取最新可用的一批数据,并通过自动执行软件基础架构来为每一批新的数据样本训练模型,从而进一步提高未来预测的性能。将模型性能保持在最佳状态的一个非常积极的方法是,用每个新的数据点进行增量训练,并添加以前的样本作为该批次的剩余样本。这种方法计算量极大,而且回报也较少。这种对每个新样本而不是一批样本进行超实时和增量训练的方法通常不被推荐。
这种模型被称为在线模型,当新的一批数据可用时,它总是在学习。最受欢迎的在线模型的例子可以在你的手机上看到。像预测文本和自动更正这样的功能会随着时间的推移而显著改进。如果你通常以一种特定的风格打字,比如结合两种语言或者缩短几个单词或者使用俚语等等,你会注意到手机非常积极地倾向于适应你的风格。这纯粹是因为手机的操作系统在后台启动了在线模型不断学习和改进的机制。
将您的模型作为 API 交付
如今,将模型作为服务交付给更大的软件堆栈的最佳实践是将其作为 API 交付。这是非常有用和有效的,因为它完全摆脱了技术栈的需求。您的模型可以轻松地在软件生态系统中各种复杂的组件之间进行协作,您可以更少地担心用于开发模型的语言或框架。通常,当您开发 ML 或 DL 模型时,交付模型的选择仅仅由两个简单的点驱动:
- 用软件工程师理解的语言构建模型
或者
- 使用 API
虽然 Python 和 Keras 在今天的现代技术堆栈中几乎是通用的,但我们仍然可以期待一些例外情况,在这些情况下,这种选择可能不是集成的简单选择。因此,我们总是可以选择 API 作为 DL 模型的首选部署模式,并适当地定义数据需求和 API 的调用方式。
有两个非常有用且易于操作的选项可以将您的服务部署为 API。您可以使用 Flask(一种轻量级 Python web 框架)或 Amazon Sagemaker(在 AWS 上可用)。还有其他的选择,我鼓励你去探索它们。Keras 博客上有一篇关于使用 Flask 部署 DL 模型的文章写得非常好。
你可以在这里了解更多: https://blog.keras.io/building-a-simple-keras-deep-learning-rest-api.html 。
此外,您可以在这里通过几个步骤探索如何使用 AWS Sagemaker 将您的模型部署为 API:https://docs.aws.amazon.com/sagemaker/latest/dg/how-it-works-hosting.html。
把所有的拼图拼在一起
最后,我们可以把上一节学到的所有这些小组件收集起来,放入一个简单的(小)架构中,如下图所示。

对于生产你的模型来说,这绝对是一个过于简单的解释;我建议您以一种更合适的方式探索您的用例的改进架构。当您的规模、数据量、安全性和可用性达到更高水平时,很多事情都会发生变化。前面的视觉展示了一个适用于小型软件的架构。一旦您完成了模型构建过程,您就可以设置您的大部分逻辑来预测、定制数据、定期测量性能、自动化在线学习、日志记录等,并将其放入一个小 Flask 应用程序中,在服务器上运行,并将其作为 API 进行部署。软件客户端可以是 web 客户端或运行在同一服务器上的另一个服务,它可以通过调用定义格式的 API 来利用该模型。这种架构适用于小型概念验证(POC ),不建议用于生产企业应用程序。讨论 DL 模型的大规模部署、动态定制数据的艺术、支持在线学习以及扩展整个服务基本上还需要几本书。
摘要
在这一章中,我们讨论了当模型性能与您的期望不一致时可以期待的方法和策略。简而言之,我们研究了当您的 DL 模型运行不佳时的整合方法。我们讨论了正则化和超参数调整,还探索了可以用来调整超参数并获得改进模型的不同策略。最后,我们讨论了在部署模型时需要解决的几个原则。我们对模型预测的数据裁剪过程进行了概述,了解了如何使用暂停-恢复方法训练模型,并研究了在线模型和重新训练它们的方法。最后,我们还查看了可用于部署模型的选项,并研究了使用 Flask 部署模型的小型架构。*
六、前方的路
本快速入门指南旨在让您以最快且最有效的方式熟悉使用 Keras 的 DL 领域。我希望你在这次旅行中过得愉快。在这最后一章中,我们将简要地看一下前方的道路。我们将尝试回答以下问题:对于一名数据科学家来说,在 DL 旅程中取得成功还有哪些重要的额外主题?
让我们开始吧。
DL 专家的下一步是什么?
我们已经用分类和回归的 DNNs 介绍了 DL 中的基础知识。最有趣的部分,事实上也是 DL 在 2012 年获得人气和发展势头的主要原因,是计算机视觉领域的 DL。几年前,设计一种算法来帮助计算机理解图像几乎是不可能的。使用算法从图像中提取含义或将图像归类到特定类别的想法是不可想象的。随着时间的推移,ML 变得流行起来,在图像中使用手工制作的特征,然后使用分类器来训练算法的方法展示了改进的结果,但这不是我们想要的结果。2012 年,Alexnet(由 Alex Krizhevsky、Ilya Sutskever 和 Geoffrey Hinton 开发的架构)被用于参加“ImageNet 大规模视觉识别挑战赛”。这是一场开发算法的比赛,该算法可以学习和预测如何将图像分类到一组定义的类别中。Alexnet 取得了 15.3%的前五名误差;这比之前的最好成绩低了近 11%,创下了挑战赛的历史纪录。该建筑是一种 DNN 建筑,专门用于图像分类。就在那时,DL 受到了关注,并立即成为研究的热门话题。DL 的旅程从此一飞冲天。随着对 DL 的更多研究和实验,这个领域扩展到了视频、音频、文本和几乎任何形式的数据。如今,DL 无处不在。几乎每一个主要的技术公司都在它的整个产品堆栈中采用了 DL。
作为一个 DL 爱好者,探索高级 DL 主题的一小步是首先从计算机视觉的 DL 开始。这是您将探索卷积神经网络(CNN)的地方。
美国有线新闻网;卷积神经网络
CNN 是一类用于计算机视觉用例的 DL 算法,例如对图像或视频进行分类,并检测图像中的对象,甚至图像中的区域。CNN 算法是计算机视觉领域的一个巨大突破,因为与当时其他流行的技术相比,它只需要最少的图像处理,并且表现得非常好。CNN 对图像分类的性能改进是惊人的。构建 CNN 的过程在 Keras 中也得到了简化,所有的逻辑组件都被巧妙地抽象出来。Keras 提供了 CNN 层,开发 CNN 模型的整体过程与我们在开发回归和分类模型时所学的非常相似。
为了简单地理解这个过程,我们将使用一个小例子来说明它的实现。以下代码片段展示了 CNN 的“hello world”等效实现。我们将使用 MNIST 数据(即带有手写数字的图像集合)。目标是将图像分类为[0,1,2,3,4,5,6,7,8,9]中的一个数字。Keras 数据集模块中已经提供了这些数据。尽管这个主题是全新的,但是代码片段中的注释将为您提供模型设计的基本概念。
#Importing the necessary packages
import numpy as np
import matplotlib.pyplot as plt
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
#Importing the CNN related layers as described in Chapter 2
from keras.layers.convolutional import Conv2D, MaxPooling2D
from keras.utils import np_utils
#Loading data from Keras datasets
(x_train, y_train), (x_test, y_test) = mnist.load_data()
#Defining the height and weight and number of samples
#Each Image is a 28 x 28 with 1 channel matrix
training_samples, height, width = x_train.shape
testing_samples,_,_ = x_test.shape
print("Training Samples:",training_samples)
print("Testing Samples:",testing_samples)
print("Height: "+str(height)+" x Width:"+ str(width))
输出
Training Samples: 60000
Testing Samples: 10000
Height: 28 x Width:28
该法典继续规定:
#Lets have a look at a sample image in the training data
plt.imshow(x_train[0],cmap='gray', interpolation="none")
#We now have to engineer the image data into the right form
#For CNN, we would need the data in Height x Width X Channels form Since the image is in grayscale, we will use channel = 1
channel =1
x_train = x_train.reshape(training_samples, height, width,channel).astype('float32')
x_test = x_test.reshape(testing_samples, height, width, channel).astype('float32')
#To improve the training process, we would need to standardize or normalize the values We can achieve this using a simple
divide by 256 for all values
x_train = x_train/255
x_test =x_test/255
#Total number of digits =10
target_classes = 10
# numbers 0-9, so ten classes
n_classes = 10
# convert integer labels into one-hot vectors
y_train = np_utils.to_categorical(y_train, n_classes)
y_test = np_utils.to_categorical(y_test, n_classes)
#Designing the CNN Model
model = Sequential()
model.add(Conv2D(64, (5, 5), input_shape=(height,width ,1), activation="relu"))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Conv2D(64, (3, 3), activation="relu"))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(128, activation="relu"))
model.add(Dense(n_classes, activation="softmax"))
# Compile model
model.compile(loss='categorical_crossentropy', optimizer="adam", metrics=['accuracy'])
# Fit the model
model.fit(x_train, y_train, validation_data=(x_test, y_test), epochs=10, batch_size=200)
输出
Train on 60000 samples, validate on 10000 samples
Epoch 1/10
60000/60000 [==============================] - 61s 1ms/step - loss: 0.2452 - acc: 0.9266 - val_loss: 0.0627 - val_acc: 0.9806
Epoch 2/10
60000/60000 [==============================] - 64s 1ms/step - loss: 0.0651 - acc: 0.9804 - val_loss: 0.0414 - val_acc: 0.9860
Epoch 3/10
60000/60000 [==============================] - 62s 1ms/step - loss: 0.0457 - acc: 0.9858 - val_loss: 0.0274 - val_acc: 0.9912
--- Skipping intermediate output----
Epoch 9/10
60000/60000 [==============================] - 58s 963us/step - loss: 0.0172 - acc: 0.9943 - val_loss: 0.0284 - val_acc: 0.9904
Epoch 10/10
60000/60000 [==============================] - 56s 930us/step - loss: 0.0149 - acc: 0.9949 - val_loss: 0.0204 - val_acc: 0.9936
最后,让我们评估模型性能:
metrics = model.evaluate(x_test, y_test, verbose=0)
for i in range(0,len(model.metrics_names)):
print(str(model.metrics_names[i])+" = "+str(metrics[i]))
输出
loss = 0.02039033946258933
acc = 0.9936
我们可以看到,我们在测试数据集上的总体准确率约为 99%。这是一个相当简单的例子。当图像的大小和要预测的类的数量增加时,复杂性就出现了。
为了对 CNN 的工作有一个高层次的理解,你可以参考一些有趣的博客:
为了进行更多的实验并研究一些非常酷且简单易懂的例子,您可以查看一些流行的 git 库,以了解与 CNN 相关的用例。
以下是一些例子:
RNN
在探索了 CNN 之后,DL 的下一步是开始探索 RNN,俗称“序列模型”这个名字变得流行是因为 RNN 利用了顺序信息。到目前为止,我们研究的所有 dnn 都在假设任何两个训练样本之间没有关系的情况下处理训练数据。然而,这是我们可以使用数据解决的许多问题中的一个问题。考虑你的 iOS 或 Android 手机中的预测文本功能;下一个单词的预测高度依赖于您已经键入的最后几个单词。这就是顺序模型发挥作用的地方。RNNs 也可以理解为有记忆的神经网络。它将一个层连接到自身,从而可以同时访问两个或多个连续的输入样本,以处理最终输出。这一特性是 RNN 独有的,随着其研究的兴起,它在自然语言理解领域取得了惊人的成功。所有遗留的自然语言处理技术现在都可以用 RNNs 得到显著的改进。聊天机器人的兴起,短信中改进的自动更正,电子邮件客户端和其他应用程序中的建议回复,以及机器翻译(即,将文本从源语言翻译成目标语言,谷歌翻译是典型的例子)都是随着 RNN 的采用而推动的。还有不同类型的 LSTM(长短期记忆)网络,它们克服了现有 RNN 架构中的限制,并将自然语言处理相关任务的性能提升了一个档次。RNN 最流行的版本是 LSTM 和 GRU(门控循环单元)网络。
与我们为 CNN 所做的类似,我们将看一下 RNN/LSTM 网络的一个简单(hello world 等效)示例实现。以下代码片段对 Keras 中的 IMDB reviews 数据集执行二进制分类。这是一个用例,其中我们提供了用户评论(文本日期)和相关的积极或消极的结果。
#Import the necessary packages
from keras.datasets import imdb
from keras.models import Sequential
from keras.layers import Dense, LSTM
from keras.layers.embeddings import Embedding
from keras.preprocessing import sequence
#Setting a max cap for the number of distinct words
top_words = 5000
#Loading the training and test data from keras datasets
(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=top_words)
#Since the length of each text will be varying
#We will pad the sequences (i.e. text) to get a uniform length throughout
max_text_length = 500
x_train = sequence.pad_sequences(x_train, maxlen=max_text_length)
x_test = sequence.pad_sequences(x_test, maxlen=max_text_length)
#Design the network
embedding_length = 32
model = Sequential()
model.add(Embedding(top_words, embedding_length, input_length=max_text_length))
model.add(LSTM(100))
model.add(Dense(1, activation="sigmoid"))
#Compile the model
model.compile(loss='binary_crossentropy', optimizer="adam", metrics=['accuracy'])
#Fit the model
model.fit(x_train, y_train, validation_data=(x_test, y_test), epochs=3, batch_size=64)
输出
Train on 25000 samples, validate on 25000 samples
Epoch 1/3
25000/25000 [==============================] - 222s 9ms/step - loss: 0.5108 - acc: 0.7601 - val_loss: 0.3946 - val_acc: 0.8272
Epoch 2/3
25000/25000 [==============================] - 217s 9ms/step - loss: 0.3241 - acc: 0.8707 - val_loss: 0.3489 - val_acc: 0.8517
Epoch 3/3
25000/25000 [==============================] - 214s 9ms/step - loss: 0.3044 - acc: 0.8730 - val_loss: 0.5213 - val_acc: 0.7358
评估测试数据集的准确性:
scores = model.evaluate(x_test, y_test, verbose=0)
print("Accuracy:",scores[1])
输出
Accuracy: 0.73584
准确度随着用于训练和改进的架构的时期数量的增加而提高。要全面了解 RNN 是如何运作的,你可以浏览几个博客:
为了进行更多的实验和研究一些非常酷的例子,您可以查看一些流行的 git 库,以了解与 LSTM 相关的用例。以下是一些例子:
-
https://github.com/danielefranceschi/lstm-climatological-time-series -
https://github.com/shashankbhatt/Keras-LSTM-Sentiment-Classification
CNN + RNN
另一个有趣的探索领域是有线电视新闻网和 RNN 的交集。听起来很困惑?想象一下,你可以结合 CNN(即理解图像)和 RNN(即理解自然文本)的力量;交集或组合会是什么样子?你可以用文字描述一幅画。没错,通过将 RNN 和 CNN 结合在一起,我们可以帮助计算机用自然风格的文本描述图像。这个过程被称为图像字幕。今天,如果你在 google.com 搜索类似“黄色汽车”的查询,你的结果实际上会返回一吨黄色汽车。如果你认为这些图片的说明是由人类完成的,然后可以被搜索引擎索引,那你就大错特错了。对于人类来说,我们无法将为图像添加字幕的过程扩展到每天数十亿张图像。这个过程是不可行的。你需要一个更聪明的方法来做到这一点。CNN+RNN 的图像字幕不仅在搜索引擎的图像搜索方面带来了突破,而且在我们日常生活中使用的其他一些产品上也带来了突破。RNN 和 CNN 的交集带给人类的最重要和最具革命性的成果是智能眼镜(百度称之为 duLight):一种配备在老花镜上的摄像头,可以描述周围的环境。对于视力受损的人来说,这是一个很棒的产品。今天,我们有一个较小的版本,在几个应用程序中实现,可以安装在手机上,并与手机摄像头配合工作。如果你有兴趣阅读更多,你可以浏览以下博客:
-
https://towardsdatascience.com/image-captioning-in-deep-learning-9cd23fb4d8d2 -
https://machinelearningmastery.com/introduction-neural-machine-translation/ -
https://towardsdatascience.com/neural-machine-translation-with-python-c2f0a34f7dd
展示图像标题的例子超出了本书的范围。但是,这里有几个 github 存储库,您可以开始探索:
DL 为什么需要 GPU?
在探索第二章设置的环境时,我们偶然发现为 GPU 安装了 TensorFlow。我相信你已经听说了很多关于 GPU 用于 DL 和 NVIDIA 等公司推出专门为 DL 设计的 GPU。一般来说,任何人首先会问的问题是 GPU 与 DL 有什么关系。我们将尝试立即获得这个问题和其他问题的答案。
鉴于您在本指南中对 DL 的了解,我假设您已经意识到 DL 是计算密集型的。为特定任务训练模型确实需要大量的 CPU 能力和时间。如果你更深入一步,并试图理解在一个 DL 模型的训练过程中实际发生了什么,它将归结为一个简单的任务(即矩阵乘法)。你有张量形式的输入数据(比如三维矩阵),测试数据也有类似的形式,神经元连接的权重也以矩阵形式存储,事实上,任何形式的 DNN 的一切,比如 CNN、RNN、DNN 或所有这些的组合,在内部很大程度上都表示为不同维度的矩阵。具有反向传播的学习过程也通过矩阵乘法来执行。
有趣的是,矩阵乘法很大一部分可以并行处理。因此,为了加快训练过程,您的 CPU 中的核心数量可以进一步提高模型所需的训练时间。不幸的是,虽然 CPU 实现的并行处理水平很高,但还不是最好的。特别是对于大型矩阵乘法,这个过程并不像我们希望的那样有效。
但是,我们有市场上已经有的 GPU。使用 GPU 的主要目的是为了增强视频性能(即更高的屏幕刷新率)。一般来说,你的笔记本电脑或计算机的屏幕是一个确定大小的图像,比如 1920 × 1080 像素。这个图像也是一个大小为 1920 × 1080 × 3 的三维矩阵。第三维表示颜色通道“RGB”。因此,简而言之,你在屏幕上看到的任何时间点都是使用 1920 × 1080 × 3 矩阵显示的图像。当这个矩阵每秒刷新(计算)30 次时,它就变成了一个平滑的视频,你可以看到物体没有延迟地移动。因此,为了在屏幕上显示一秒钟,计算机内部计算 1920 × 1080 × 3 矩阵的值至少 30 次。那是相当多的计算。此外,当您玩游戏或执行任何需要高端图形的任务(如视频编辑或在 Photoshop 中设计图像等任务)时,刷新率需要大幅提高。一个好的估计是每秒 60 的屏幕刷新率,而不是每秒 30。现在,为了显示如此高的图形内容,CPU 上有不寻常的额外负载,它可能无法提供所需的性能。为了解决这个问题,我们有专门设计用于渲染高端图形的 GPU,帮助计算机处理刷新屏幕 60 次所需的计算。GPU 承担处理屏幕刷新计算的全部责任。这种处理是通过大规模并行处理完成的。我们在普通笔记本电脑中使用的现代 GPU 至少有 400 个内核,而台式机上的 GPU 要强大得多。这些内核有助于大规模并行处理,以高刷新率显示高清图形内容。
碰巧的是,同样的技术可以用来解决我们在数字图书馆面临的问题。对矩阵进行大规模并行处理以在屏幕上呈现平滑的图形内容可以替代地用于处理 DL 模型训练过程中的计算。在那一刻之后,NVIDIA 开发了 CUDA,这是一个为 GPU 创建的并行处理接口模型。它允许开发人员使用支持 CUDA 的图形处理单元进行通用处理。这项技术为训练 DL 模型带来了巨大的突破。用数字来描述,一个模型在我的笔记本电脑上用 CPU 训练了 40 分钟,用 GPU 训练了 2 分钟。几乎快了 20 倍。您可以想象使用更强大的 GPU 我们可以实现什么。今天,大多数 DL 库都支持 GPU。一旦您为您的 GPU 安装并设置了 CUDA 驱动程序,并安装了一个 GPU 兼容的 DL 库,您就一切就绪了。剩下的就完全抽象给你了。你所需要做的就是以通常的方式训练模型,框架会无缝地使用来自 GPU 和 CPU 的资源。
同样的过程也可以用其他厂商的 GPU 实现,比如 AMD 用 OpenGL。但 NVIDAs GPUs 要优越得多,至少领先其他任何竞争对手五年。如果你打算投资硬件来研究 DL,我强烈建议你购买一台配备兼容 NVIDIA CUDA 的 GPU 的笔记本电脑或台式机(首选)。你将在实验中节省大量的时间。
DL (GAN)中的其他热点区域
我们为您探索了掌握高级 DL 主题的前进道路。但是如果不讨论 DL 中最热门的研究领域,这个讨论将是不完整的。我们将简要讨论生成性对抗网络(GANs ),尽管还有更多。
gan 处于 DL 中断的最前沿,最近一直是一个活跃的研究课题。简而言之,GAN 允许网络从代表现实世界实体(比如,猫或狗;当我们简单地开发 DL 模型以在猫和狗之间进行分类,然后使用它在该过程中学习到的相同特征来生成新图像时;也就是说,它可以生成一个看起来(几乎)真实的猫的新图像,并且与您为训练提供的图像集完全不同。我们可以将 GAN 的整个解释简化为一个简单的任务(即图像生成)。如果训练时间和训练时提供的样本图像足够大,它可以学习一个网络,该网络可以生成与训练时提供的图像不相同的新图像;它会生成新的图像。
如果你想知道图像生成的应用,有一个直到最近才被想到的全新的可能性。之前,大多数 DL 模型只有推理(相对容易)和勉强生成(非常难)。如果你看一看蒙娜丽莎,很容易把它归类为一幅女人的画,但是要做出一幅真的很难。然而,如果有可能这样做,那么就可以开发出全新一代的应用程序。给你一个很好的例子,印度在线时尚零售商 Myntra 使用 GAN 来创建新的 t 恤设计。它用一系列 t 恤设计训练 GAN 网络,然后该模型生成新的设计。在系统生成的 100 个新设计中,即使有 50 个被认为是他们可以制造的好设计,这个领域的奇迹也将是无穷无尽的。同样的想法可以扩展到任何其他领域。在上一节中,我们讨论了图像字幕(即从图像中生成类似描述的自然文本)。这已经是一个很酷的应用程序了,现在想想相反的情况;想象一下,向一个系统提供一个自然的文本描述,然后它生成一张图片作为回报。这个想法听起来太超前了,但是我们已经非常接近这种可能性了。想象一下,你在路上看到一个罪犯,警察需要你帮忙画出他的脸,以便进一步调查;对于未来的 GAN 系统,我们可以想象一个系统,你描述罪犯面部的细节,系统为你绘制面部草图。GAN 的应用过于超前,但研究仍在进行中。到目前为止,研究人员设计的 GAN 网络能够以高清晰度呈现/生成图像,并且在该领域中有持续的实验和研究来开发能够生成高清晰度视频的 GAN 网络。
您可以在此阅读更多关于 GAN 及其应用的信息:
总结想法
这一章的目的是强调数字图书馆领域是多么有前途,现在是开始学习它的基础的好时机。我希望您现在对该领域的高级主题以及您可以立即采取的进一步探索 DL 前沿的下一步有一个公平的想法。这本书旨在以最快但最有效的方式帮助您入门,作为使用 DNNs 的现代 DL 的入门指南。
我们从 DL 主题的简单介绍开始了本指南,并理解了它的基本原理和与市场上流行词汇的区别。我们研究了使用框架开发 DL 模型的必要性,探索了当今市场上几种流行的选择,并理解了为什么 Keras 最有可能成为初学者的首选框架。在后面的章节中,我们通过研究 Keras 框架提供的逻辑抽象和在 DL 生态系统中以小而渐进的步骤映射它的对等物,探索了 Keras 框架,然后将所有的知识与分类和回归中的两个以业务为中心的基本用例结合在一起。然后,我们研究了设计网络的技巧和诀窍,在难以入门的情况下的一些变通方法,以及通过正则化和超参数优化进行模型调整的过程。我们还研究了在生产中部署 DL 模型时应该遵守的一些准则,并最终通过 CNN、、CNN+RNN 以及 DL 中最热门的研究领域(即 GAN)对 DL 中的高级产品进行了初步了解。
我非常享受以加速模式交付本指南内容的过程,我希望你也喜欢这个旅程。现在是结束的时候了,祝你们在 DL 的旅程中好运。我希望你们在发展 DL 技能的过程中有一个非常快乐和愉快的学习之路。





浙公网安备 33010602011771号