Keras 神经网络秘籍(一)

原文:annas-archive.org/md5/b038bef44808c012b36120474e9e0841

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

深度学习在快速进步,无论是神经网络架构本身,还是它们在现实世界应用中的实际应用。本书将引导你从构建神经网络的基础开始,到多个先进架构的开发,这些架构广泛应用于各种实际应用中。你会发现,本书分为五个部分。

在第一部分,你将通过从零开始在 Python 中构建神经网络的组件,来学习神经网络的运作方式,然后再在 Keras 中进行构建。此外,你将学习不同超参数对网络准确性的影响,并了解如何灵活地将神经网络应用于多个领域的不同应用。

在第二部分,你将学习如何从零开始在 Python 中构建卷积神经网络CNN),然后将其应用于图像分类,在此过程中你将学习如何构建一个模型来检测图像中人物的性别,并识别人脸图像中的关键点。此外,你还将学习迁移学习在物体检测和定位中的应用,通过这些技术来分类图像中的物体,并识别图像中人物的位置。此外,你还将学习图像分析在自动驾驶汽车中的各种应用,利用语义分割等技术。

在第三部分,我们将从图像分析转向文本分析,学习如何对图像和文本数据进行编码,以便使用自编码器和词向量分别将相似的图像和文本聚合在一起。此外,你将学习构建推荐系统的各种建模方法,以便为用户推荐相关的电影。你还将学习如何利用生成对抗网络GANs)生成新图像,以及生成艺术图像,同时学习如何通过对抗性攻击来欺骗网络。

在第四部分,你将深入探讨文本分析,在这里你将学习如何从零开始用 Python 构建递归神经网络RNNs)和长短期记忆LSTM)网络,并进一步学习如何构建多个利用文本分析的应用案例,如股票价格预测、情感分类、机器翻译以及构建聊天机器人,使用的高级神经网络架构包括双向 LSTM 和注意力机制。

在最后一部分,你将学习端到端学习,在这一部分你将进行图像和音频的转录以及生成字幕。此外,你还将学习深度 Q 学习,你将构建代理来玩各种 Atari 游戏。

到本书结束时,你将掌握能够将各种深度学习架构应用于你可能遇到的大部分深度学习问题的技能。

本书适合人群

本书适合初学者和中级机器学习从业者及数据科学家,特别是那些刚刚开始接触神经网络,并寻找一份能引导他们了解神经网络不同架构的资源的人。本书的案例研究按问题的复杂度排序,最简单的在最前面。只要具备基本的 Python 编程知识和对基础机器学习的了解,你就能顺利开始本书的学习。

为了最大限度地从本书中获得收益

  • 你应该具备 Keras 的基础知识,并且熟悉 Python,且对机器学习有基本了解。另外,在阅读书中的示例时,请参考 GitHub 上的代码文件。我们已确保书中的所有代码正确缩进,但强烈建议你在实现代码时,参考 GitHub 上的代码。

下载示例代码文件

你可以从 www.packt.com 的账户中下载本书的示例代码文件。如果你在其他地方购买了本书,可以访问 www.packt.com/support 并注册,以便直接通过电子邮件获取文件。

你可以通过以下步骤下载代码文件:

  1. 登录或注册 www.packt.com

  2. 选择 SUPPORT 标签。

  3. 点击“代码下载与勘误”。

  4. 在搜索框中输入书名,并按照屏幕上的指示操作。

文件下载后,请确保使用最新版本解压或提取文件夹:

  • Windows 的 WinRAR/7-Zip

  • Zipeg/iZip/UnRarX for Mac

  • 7-Zip/PeaZip for Linux

本书的代码包也托管在 GitHub 上,地址是github.com/PacktPublishing/Neural-Networks-with-Keras-Cookbook。如果代码有更新,它将会在现有的 GitHub 仓库中更新。

我们还提供了来自丰富书籍和视频目录中的其他代码包,地址是**github.com/PacktPublishing/**。快去看看吧!

下载彩色图片

我们还提供了一个 PDF 文件,包含本书中使用的截图/图表的彩色图像。你可以在这里下载:www.packtpub.com/sites/default/files/downloads/9781789346640_ColorImages.pdf.

使用的约定

本书中使用了若干文本约定。

CodeInText:表示文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 用户名。例如:“名为 Defaultin2yrs 的变量是我们需要预测的输出变量。”

代码块的格式如下:

data['DebtRatio_newoutlier']=np.where(data['DebtRatio']>1,1,0)
data['DebtRatio']=np.where(data['DebtRatio']>1,1,data['DebtRatio'])

粗体:表示新术语、重要单词或屏幕上显示的单词。

警告或重要说明将以这样的形式出现。

小贴士和技巧将以这样的形式出现。

各节

在本书中,您将会看到几个经常出现的标题(准备工作如何操作…它是如何工作的…还有更多…另见)。

为了提供清晰的操作步骤,请按照以下方式使用这些章节:

准备工作

本节告知您在食谱中可以期待什么,并描述如何设置任何软件或食谱所需的初步设置。

如何操作…

本节包含遵循此食谱所需的步骤。

它是如何工作的…

本节通常包括对前一节内容的详细解释。

还有更多…

本节包含关于食谱的额外信息,帮助您更深入了解食谱。

另见

本节提供了其他有用信息的链接,帮助您更好地理解食谱。

联系我们

我们始终欢迎读者的反馈。

一般反馈:如果您对本书的任何部分有疑问,请在邮件主题中提到书名,并通过电子邮件 customercare@packtpub.com 与我们联系。

勘误表:尽管我们已尽一切努力确保内容的准确性,但错误难免。如果您在本书中发现错误,烦请向我们报告。请访问 www.packt.com/submit-errata,选择您的书籍,点击勘误表提交表单链接并填写相关细节。

盗版:如果您在互联网上发现我们作品的任何非法复制版本,烦请提供该位置地址或网站名称。请通过电子邮件 copyright@packt.com 并附上材料链接与我们联系。

如果您有意成为作者:如果您在某个领域具有专业知识,并且有兴趣写书或为书籍做贡献,请访问 authors.packtpub.com

评论

请留下评论。读完并使用本书后,为什么不在您购买书籍的网站上留下评论呢?潜在读者可以查看并根据您的公正意见做出购买决策,我们 Packt 能了解您对我们产品的看法,作者也能看到您对他们书籍的反馈。谢谢!

欲了解更多关于 Packt 的信息,请访问 packt.com

第一章:构建前馈神经网络

在本章中,我们将覆盖以下内容:

  • 在 Python 中从零开始实现前馈传播

  • 在 Python 中从零开始构建反向传播

  • 在 Keras 中构建神经网络

介绍

神经网络是一种监督学习算法,灵感来源于大脑的功能方式。与大脑中神经元之间的连接方式相似,神经网络接收输入,通过一个函数进行处理,某些后续神经元被激活,最终产生输出。

在本章中,您将学习以下内容:

  • 神经网络的架构

  • 神经网络的应用

  • 设置前馈神经网络

  • 前向传播是如何工作的

  • 计算损失值

  • 梯度下降在反向传播中的工作原理

  • 训练轮次(epochs)和批量大小(batch size)的概念

  • 各种损失函数

  • 各种激活函数

  • 从零开始构建神经网络

  • 在 Keras 中构建神经网络

简单神经网络的架构

人工神经网络的灵感来源于人脑的工作方式。技术上,它是线性回归和逻辑回归的改进,因为神经网络引入了多个非线性度量来估计输出。此外,神经网络在修改网络架构方面提供了极大的灵活性,可以通过利用结构化和非结构化数据解决多个领域的问题。

函数越复杂,网络越有可能调整以适应输入数据,从而提高预测的准确性。

一个典型的前馈神经网络结构如下所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/838c8087-2c87-495b-9064-13c3d634e009.png

一层是一个或多个节点(计算单元)的集合,每个层中的节点都与下一层中的每个节点连接。输入层由预测输出值所需的输入变量组成。

输出层的节点数量取决于我们是尝试预测连续变量还是分类变量。如果输出是连续变量,则输出只有一个单元。

如果输出是分类的,并且有 n 个可能的类别,则输出层将有 n 个节点。隐藏层用于将输入层的值转换为高维空间中的值,以便我们可以从输入中学习更多的特征。隐藏层将输出转换如下:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/7ae13d98-43f6-4c6a-83e8-687365e00a27.png

在上面的图中,x[1],x[2],…,x[n] 是独立变量,而 x[0] 是偏置项(类似于线性/逻辑回归中的偏置)。

注意,w[1],w[2],…,w[n] 是分配给每个输入变量的权重。如果 a 是隐藏层中的一个单元,它将等于以下值:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/96089758-dfe2-470f-99c0-79f087b84c7b.png

f 函数是激活函数,用于在输入与其对应权重值的乘积和之上应用非线性。此外,通过增加多个隐藏层,可以实现更高的非线性。

总结来说,神经网络是一个由权重分配给节点并由层连接的集合。这个集合分为三大部分:输入层、隐藏层和输出层。请注意,你可以有 n 个隐藏层,深度学习的概念通常意味着有多个隐藏层。当神经网络需要理解一些非常复杂、具有上下文或不明显的内容时,如图像识别,隐藏层是必需的。中间层(不是输入层或输出层的层)被称为隐藏层,因为它们在实践中是不可见的(关于如何可视化中间层的内容可以参考第四章,构建深度卷积神经网络)。

训练神经网络

训练神经网络基本上意味着通过重复两个关键步骤:正向传播和反向传播,来校准神经网络中的所有权重。

在正向传播中,我们将一组权重应用到输入数据,经过隐藏层,进行非线性激活,最后将隐藏层连接到输出层,通过将隐藏层节点值与另一组权重相乘来完成。对于第一次正向传播,权重的值是随机初始化的。

在反向传播中,我们通过测量输出的误差范围来尝试减少误差,并相应地调整权重。神经网络重复进行正向传播和反向传播,直到权重得到校准,从而预测输出。

神经网络的应用

最近,我们已经看到神经网络在各种应用中的广泛采用。在这一部分中,我们将尝试理解为什么这种采用可能大幅增加。神经网络可以通过多种方式进行架构设计。以下是一些可能的方式:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/78c3e0dd-0c85-40a9-ada6-5193ad120167.png

底部的框是输入,接下来是隐藏层(中间的框),顶部的框是输出层。一对一架构是典型的神经网络,输入层与输出层之间有一个隐藏层。不同架构的示例如下:

架构示例
一对多输入是图像,输出是该图像的标题
多对一输入是电影评论(多个词),输出是与评论相关的情感
多对多将一个语言中的句子翻译成另一种语言中的句子

除了上述要点,神经网络还能够理解图像中的内容,并利用一种名为卷积神经网络CNN)的架构,检测内容所在的位置,其架构如下所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/2ad769ae-ff98-4ed7-bd58-c9284e1c25f6.png

在这里,我们看到了推荐系统、图像分析、文本分析和音频分析的示例,可以看到神经网络为我们提供了灵活性,使我们能够使用多种架构来解决问题,随着应用数量的增加,神经网络的采用率也在上升。

从头开始实现前向传播(Feed-forward propagation)——Python 实现

为了建立前向传播工作的坚实基础,我们将通过一个训练神经网络的玩具示例来进行讲解,其中神经网络的输入为(1, 1),对应的输出为 0。

准备工作

我们将采用的策略如下:我们的神经网络将有一个隐藏层(包含神经元),该隐藏层连接输入层与输出层。请注意,隐藏层中的神经元数量比输入层多,因为我们希望让输入层在更多维度上得到表示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/ede5ca3a-46a3-4e12-a031-089dd2e8c3da.png

计算隐藏层单元值

我们现在为所有连接分配权重。请注意,这些权重是随机选择的(基于高斯分布),因为这是我们第一次进行前向传播。在这个特定的例子中,我们从初始权重开始,权重范围在 0 和 1 之间,但请注意,神经网络训练过程中的最终权重不需要介于特定的数值范围之间:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/12b639fb-0830-4075-b072-57b0d0d97885.png

在下一步中,我们执行输入与权重的乘法运算,以计算隐藏层中隐藏单元的值。

隐藏层的单元值如下所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/c1c88e58-271f-4993-9e05-96a7f4bec990.png

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/624d72e6-fad9-46b7-a852-de01b0dedb17.png

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/3662b621-8504-4f08-ad28-46f825de99cc.png

隐藏层的单元值在下面的图示中也有显示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/3b453e27-b9e7-4527-94a3-c32a3022c51f.png

请注意,在前面的输出中,我们计算了隐藏层的值。为了简化起见,我们忽略了需要在每个隐藏层单元中添加的偏置项。

现在,我们将通过激活函数将隐藏层值传递,以便在输出中获得非线性。

如果我们不在隐藏层中应用激活函数,神经网络将变成一个从输入到输出的巨大线性连接。

应用激活函数

激活函数在网络的多个层中应用。它们的作用是使输入具有较高的非线性,这在建模输入和输出之间复杂关系时非常有用。

不同的激活函数如下所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/12df72e4-af1b-4ce8-bca4-b125b185f18a.png

对于我们的示例,假设使用 sigmoid 函数作为激活函数。Sigmoid 函数的图形如下所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/41178e09-6508-41af-bf4d-616d4dbf7674.png

通过对三个隐藏层的总和应用 sigmoid 激活函数 S(x),我们得到如下结果:

final_h[1] = S(1.0) = 0.73

final_h[2] = S(1.3) = 0.78

final_h[3] = S(0.8) = 0.69

计算输出层值

现在我们已经计算出隐藏层的值,接下来我们将计算输出层的值。在以下图示中,隐藏层的值通过随机初始化的权重值与输出层相连。通过使用隐藏层的值和权重值,我们将计算以下网络的输出值:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/bdd85755-2ae5-4148-b584-46b254589d77.png

我们通过将隐藏层值与权重值做点积来计算输出值。为了简化计算,我们省略了每个隐藏层单元中需要添加的偏置项:

0.73 * 0.3 + 0.79 * 0.5 + 0.69 * 0.9 = 1.235

这些值在以下图示中显示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/5689850e-73e4-4ce8-86b5-3081086f9410.png

因为我们一开始使用的是随机初始化的权重,所以输出神经元的值与目标值差异很大,在这个例子中相差+1.235(因为目标值为 0)。

计算损失值

损失值(也称为成本函数)是我们在神经网络中优化的值。为了理解损失值是如何计算的,让我们来看两个场景:

  • 连续变量预测

  • 类别变量预测

在连续变量预测中计算损失

通常,当变量是连续型时,损失值通过平方误差计算,即我们通过调整与神经网络相关的权重值来最小化均方误差:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/caeef8ef-a6a7-4b83-b3f8-c9961b637f7b.png

在前面的公式中,y(i) 是实际的输出值,h(x) 是我们对输入(x)进行变换后得到的预测值 y,而 m 是数据集中的行数。

在类别变量预测中计算损失

当需要预测的变量是离散型时(即变量只有少数几类),我们通常使用类别交叉熵损失函数。当需要预测的变量只有两个不同的值时,损失函数为二元交叉熵,而当需要预测的变量有多个不同的值时,损失函数为类别交叉熵。

这里是二元交叉熵:

(ylog§+(1−y)log(1−p))

这里是类别交叉熵:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/28b794dc-8596-48ba-8f95-23211716611e.png

y 是输出的实际值,p 是预测的输出值,n 是数据点的总数。现在,假设我们在玩具示例中预测的结果是连续的。在这种情况下,损失函数值是均方误差,计算公式如下:

error = 1.235² = 1.52

在下一步中,我们将尝试使用反向传播来最小化损失函数值(我们将在下一节学习),在反向传播中,我们更新权重值(之前随机初始化的)以最小化损失(误差)。

如何执行…

在前面的部分中,我们学习了如何对输入数据执行以下步骤,以便在前向传播过程中计算误差值(代码文件可在 GitHub 上的 Neural_network_working_details.ipynb 找到):

  1. 随机初始化权重

  2. 通过将输入值与权重相乘来计算隐藏层单元值

  3. 对隐藏层的值执行激活

  4. 将隐藏层的值连接到输出层

  5. 计算平方误差损失

用于计算所有数据点的平方误差损失值的函数如下:

import numpy as np
def feed_forward(inputs, outputs, weights):
     pre_hidden = np.dot(inputs,weights[0])+ weights[1]
     hidden = 1/(1+np.exp(-pre_hidden))
     out = np.dot(hidden, weights[2]) + weights[3]
     squared_error = (np.square(pred_out - outputs))
     return squared_error

在前面的函数中,我们将输入变量值、权重(如果这是第一次迭代则随机初始化)以及提供的数据集中的实际输出作为输入传递给前馈函数。

我们通过执行输入和权重的矩阵乘法(点积)来计算隐藏层的值。此外,我们还会在隐藏层中加上偏置值,如下所示:

pre_hidden = np.dot(inputs,weights[0])+ weights[1]

前述场景适用于weights[0]为权重值,weights[1]为偏置值,权重和偏置连接输入层与隐藏层。

一旦计算出隐藏层的值,我们会对隐藏层的值进行激活,计算方法如下:

hidden = 1/(1+np.exp(-pre_hidden))

现在,我们通过将隐藏层的输出与连接隐藏层和输出的权重相乘,然后在输出处添加偏差项,来计算隐藏层的输出,如下所示:

pred_out = np.dot(hidden, weights[2]) + weights[3]

一旦输出被计算出来,我们会在每一行中计算平方误差损失,计算公式如下:

squared_error = (np.square(pred_out - outputs))

在前面的代码中,pred_out 是预测的输出,outputs 是实际的输出。

然后,我们可以在通过网络进行前向传播时获得损失值。

虽然在前面的代码中,我们对隐藏层的值考虑了 Sigmoid 激活函数,但现在让我们看看其他常用的激活函数。

Tanh

tanh 激活值(隐藏层单元值)的计算如下:

def tanh(x):
    return (exp(x)-exp(-x))/(exp(x)+exp(-x))

ReLu

修正线性单元ReLU)的值(隐藏层单元值)计算如下:

def relu(x):
    return np.where(x>0,x,0)

Linear

线性激活值就是该值本身。

Softmax

通常,Softmax 会应用于一组值的向量。这通常是为了确定输入属于给定场景中 n 个可能输出类别之一的概率。假设我们正在尝试将数字图像分类为 10 个可能的类别(0 到 9 的数字)。在这种情况下,有 10 个输出值,每个输出值应代表输入图像属于其中一个类别的概率。

Softmax 激活函数用于为输出中的每个类别提供一个概率值,计算方法将在以下部分中解释:

def softmax(x):
    return np.exp(x)/np.sum(np.exp(x))

除了前面提到的激活函数,构建神经网络时通常使用的损失函数如下:

均方误差

错误是输出的实际值和预测值之间的差异。我们对误差取平方,因为误差可能是正值或负值(当预测值大于实际值时,反之亦然)。平方确保正负误差不会相互抵消。我们计算均方误差,以便在两个数据集大小不相同的情况下,可以比较这两个数据集上的误差。

预测值(p)和实际值(y)之间的均方误差计算公式如下:

def mse(p, y):
    return np.mean(np.square(p - y))

均方误差通常用于预测本质上是连续的值。

平均绝对误差

平均绝对误差的工作原理与均方误差非常相似。平均绝对误差通过对所有数据点的实际值和预测值之间的绝对差异取平均值,确保正负误差不会相互抵消。

预测值(p)和实际值(y)之间的平均绝对误差实现公式如下:

def mae(p, y):
    return np.mean(np.abs(p-y))

类似于均方误差,平均绝对误差通常应用于连续变量。

类别交叉熵

交叉熵是衡量两个不同分布之间差异的指标:实际分布和预测分布。与我们讨论的前两种损失函数不同,交叉熵应用于类别输出数据。

两个分布之间的交叉熵计算公式如下:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/37b30e1e-0e2d-47e7-b9d5-cdfab9cbc8a0.png

y 是事件的实际结果,p 是事件的预测结果。

预测值(p)和实际值(y)之间的类别交叉熵实现公式如下:

def cat_cross_entropy(p, y):
     return -np.sum((y*np.log2(p)+(1-y)*np.log2(1-p)))

请注意,当预测值远离实际值时,类别交叉熵损失值较高;而当值接近时,损失值较低。

从头开始用 Python 构建反向传播

在前向传播中,我们将输入层连接到隐藏层,再连接到输出层。在反向传播中,我们采取相反的方式。

准备工作

我们会逐个调整神经网络中的每个权重,幅度很小。权重值的变化会影响最终的损失值(可能是增加或减少损失)。我们会在减少损失的方向上更新权重。

此外,在某些情况下,权重的小幅变化会导致误差大幅增加/减少,而在某些情况下误差变化较小。

通过小幅度更新权重并衡量更新后误差的变化,我们可以做到以下几点:

  • 确定权重更新的方向

  • 确定权重更新的大小

在实现反向传播之前,让我们理解神经网络的一个额外细节:学习率。

直观地说,学习率帮助我们建立对算法的信任。例如,在决定权重更新的幅度时,我们可能不会一次性改变很大,而是采取更谨慎的方式,慢慢地更新权重。

这使我们的模型得到稳定;我们将在下一章中讨论学习率如何帮助稳定性。

我们更新权重以减少误差的整个过程叫做梯度下降技术。

随机梯度下降是前述场景中最小化误差的方法。更直观地说,梯度代表差异(即实际值与预测值之间的差异),而下降意味着减少。随机表示基于一定数量的随机样本选择来做出决策。

除了随机梯度下降,还有许多其他优化技术可以帮助优化损失值;不同的优化技术将在下一章中讨论。

反向传播的工作流程如下:

  • 计算正向传播过程中的整体成本函数。

  • 将所有权重(逐个)按小幅度变化。

  • 计算权重变化对成本函数的影响。

  • 根据变化是否增加或减少了成本(损失)值,它会更新权重值,朝着减少损失的方向进行调整。然后,这一步骤会在所有权重上重复进行。

如果前述步骤执行了n次,它实际上会产生n 迭代次数

为了进一步巩固我们对神经网络中反向传播的理解,让我们从已知函数开始,看看如何推导出权重:

目前,我们将使用已知函数y = 2x,我们尝试找出权重值和偏置值,在这个特定的情况下分别为 2 和 0:

xy
12
24
36
48

如果我们将之前的数据集公式化为线性回归,(y = ax+b),其中我们要计算ab的值(我们已经知道它们是 2 和 0,但正在检查如何通过梯度下降获得这些值),我们可以将ab*的参数随机初始化为 1.477 和 0(理想值是 2 和 0)。

如何实现…

在这一部分,我们将手动构建反向传播算法,以便我们清楚地理解在神经网络中如何计算权重。在这个特定的案例中,我们将构建一个没有隐藏层的简单神经网络(因此我们正在解决一个回归方程)。代码文件可以在 GitHub 上的Neural_network_working_details.ipynb中找到。

  1. 初始化数据集如下:
x = [[1],[2],[3],[4]]
y = [[2],[4],[6],[8]]
  1. 随机初始化权重和偏置值(因为我们只需要一个权重和一个偏置值,因为我们要找出方程y = ax + bab*的最优值):
w = [[[1.477867]], [0.]]
  1. 定义前馈网络并计算平方误差损失值:
import numpy as np
def feed_forward(inputs, outputs, weights):
     out = np.dot(inputs,weights[0]) + weights[1]
     squared_error = (np.square(out - outputs))
     return squared_error

在之前的代码中,我们进行了输入与随机初始化的权重值的矩阵乘法,并将其与随机初始化的偏置值相加。

一旦计算出值,我们将计算实际值与预测值之间的平方误差。

  1. 将每个权重和偏置值增加一个非常小的量(0.0001),并逐一计算每个权重和偏置更新的平方误差损失值。

如果平方误差损失值随着权重增加而减少,则应该增加权重值。权重值增加的幅度应与权重变化所减少的损失值的数量成正比。

此外,确保不会像权重变化所引起的损失减少那样增加权重值,而是通过一个叫做学习率的因子来调整它。这样可以确保损失值更平滑地减少(下一章会详细讲解学习率如何影响模型准确度)。

在以下代码中,我们创建了一个名为update_weights的函数,它执行反向传播过程,以更新在步骤 3中获得的权重。我们还提到该函数需要运行epochs次(其中epochs是我们传递给update_weights函数的参数):

def update_weights(inputs, outputs, weights, epochs): 
     for epoch in range(epochs):
  1. 将输入通过前馈网络传递,以计算使用初始权重集的损失值:
        org_loss = feed_forward(inputs, outputs, weights)
  1. 确保你对权重列表进行deepcopy,因为在后续步骤中权重会被操作,因此deepcopy可以避免由于子变量的变化影响到它指向的父变量的问题:
        wts_tmp = deepcopy(weights)
        wts_tmp2 = deepcopy(weights)
  1. 一次遍历所有权重值,并对其进行微小的变化(0.0001):
        for i in range(len(weights)):
             wts_tmp[-(i+1)] += 0.0001
  1. 当权重被小幅更新时,计算更新后的前馈损失。计算由于输入的小变化所导致的损失变化。将损失变化除以输入的数量,因为我们希望计算所有输入样本的均方误差:
            loss = feed_forward(inputs, outputs, wts_tmp)
            delta_loss = np.sum(org_loss - loss)/(0.0001*len(inputs))

通过小幅更新权重并计算其对损失值的影响,相当于对权重变化执行导数操作。

  1. 根据权重导致的损失变化来更新权重。通过将损失变化乘以一个非常小的数字(0.01),即学习率参数(关于学习率参数的更多内容请参见下一章),以缓慢更新权重:
            wts_tmp2[-(i+1)] += delta_loss*0.01 
            wts_tmp = deepcopy(weights)
  1. 返回更新后的权重和偏置值:
    weights = deepcopy(wts_tmp2)
 return wts_tmp2

神经网络中的另一个参数是计算损失值时考虑的批处理大小。

在前面的场景中,我们考虑了所有数据点来计算损失值。然而,在实际应用中,当我们有成千上万(或在某些情况下,数百万)个数据点时,在计算损失值时,更多数据点的增量贡献将遵循递减回报法则,因此我们使用的批处理大小会比总数据点数量小得多。

在构建模型时,通常考虑的批处理大小范围是 32 到 1,024 之间。

还有更多内容…

在前一节中,我们构建了一个回归公式*(Y = ax + b),并编写了一个函数来识别ab的最优值。在本节中,我们将在相同的玩具数据集上构建一个简单的神经网络,隐藏层将输入连接到输出层。

我们定义模型如下(代码文件可以在 GitHub 上找到,文件名为Neural_networks_multiple_layers.ipynb):

  • 输入层连接到隐藏层,隐藏层有三个单元。

  • 隐藏层连接到输出层,输出层有一个单元。

让我们继续编写上述讨论的策略,代码如下:

  1. 定义数据集并导入相关包:
from copy import deepcopy
import numpy as np

x = [[1],[2],[3],[4]]
y = [[2],[4],[6],[8]]

我们使用deepcopy,这样在复制原始变量值到目标变量后,即使目标变量的值发生变化,原始变量的值也不会改变。

  1. 随机初始化权重和偏置值。隐藏层中有三个单元,因此总共有三个权重值和三个偏置值——每个都对应一个隐藏单元。

此外,最终层有一个单元,与隐藏层的三个单元相连接。因此,共有三个权重和一个偏置值决定输出层的值。

随机初始化的权重如下:

w = [[[-0.82203424, -0.9185806 , 0.03494298]], [0., 0., 0.], [[ 1.0692896 ],[ 0.62761235],[-0.5426246 ]], [0]]
  1. 实现一个前馈网络,其中隐藏层使用 ReLU 激活函数:
def feed_forward(inputs, outputs, weights):
     pre_hidden = np.dot(inputs,weights[0])+ weights[1]
     hidden = np.where(pre_hidden<0, 0, pre_hidden) 
     out = np.dot(hidden, weights[2]) + weights[3]
     squared_error = (np.square(out - outputs))
     return squared_error
  1. 像前一节那样定义反向传播函数。唯一的区别是,我们现在需要更新更多层中的权重。

在以下代码中,我们正在计算一个时期开始时的原始损失:

def update_weights(inputs, outputs, weights, epochs): 
     for epoch in range(epochs):
         org_loss = feed_forward(inputs, outputs, weights)

在以下代码中,我们将权重复制到两个权重变量集,以便在后续代码中重用它们:

        wts_new = deepcopy(weights)
        wts_new2 = deepcopy(weights)

在以下代码中,我们通过少量更新每个权重值,并计算与更新后权重值对应的损失值(同时保持其他所有权重不变)。此外,我们还确保在所有权重和所有网络层中都发生权重更新。

平方损失(del_loss)的变化归因于权重值的变化。我们对网络中所有存在的权重重复前述步骤:

         for i, layer in enumerate(reversed(weights)):
            for index, weight in np.ndenumerate(layer):
                wts_tmp[-(i+1)][index] += 0.0001
                loss = feed_forward(inputs, outputs, wts_tmp)
                del_loss = np.sum(org_loss - loss)/(0.0001*len(inputs))

权重值通过学习率参数进行加权更新——损失减少较大时,权重会大幅更新,而损失减少较小时,权重会少量更新:

               wts_tmp2[-(i+1)][index] += del_loss*0.01
               wts_tmp = deepcopy(weights)

鉴于权重值是逐个更新的,以估计它们对损失值的影响,因此存在对权重更新过程进行并行化的潜力。因此,在这种情况下,GPU 非常有用,因为它们比 CPU 有更多的核心,能够在相同时间内更新更多的权重。

最后,我们返回更新后的权重:


          weights = deepcopy(wts_tmp2)
 return wts_tmp2
  1. 运行函数多次,每次更新一次权重:
update_weights(x,y,w,1)

前述代码的输出(更新后的权重)如下:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/64c4ca0f-bfe2-4130-91a2-777979b04a6c.jpg

在前述步骤中,我们学习了如何在 Python 中从零开始构建神经网络。在下一部分中,我们将学习如何在 Keras 中构建神经网络。

在 Keras 中构建神经网络

在上一部分中,我们从零开始构建了一个神经网络,也就是说,我们编写了执行前向传播和反向传播的函数。

如何实现…

我们将使用 Keras 库构建一个神经网络,该库提供了使构建复杂神经网络过程更容易的工具。

安装 Keras

Tensorflow 和 Keras 在 Ubuntu 中实现,使用以下命令:

$pip install --no-cache-dir tensorflow-gpu==1.7

请注意,最好安装一个兼容 GPU 的版本,因为神经网络在 GPU 上运行时速度要快得多。Keras 是一个高层神经网络 API,用 Python 编写,能够在 TensorFlow、CNTK 或 Theano 之上运行。

它的开发重点是支持快速实验,可以通过以下方式安装:

$pip install keras

在 Keras 中构建我们的第一个模型

在这一部分中,让我们通过使用在前面部分中使用的相同玩具数据集(代码文件在 GitHub 上以Neural_networks_multiple_layers.ipynb提供)来理解在 Keras 中构建模型的过程:

  1. 实例化一个模型,可以顺序调用它以便在其上添加更多的层。Sequential方法使我们能够执行模型初始化操作:
from keras.models import Sequential
model = Sequential()
  1. 向模型添加全连接层。全连接层确保模型中各层之间的连接。在以下代码中,我们将输入层与隐藏层连接:
model.add(Dense(3, activation='relu', input_shape=(1,)))

在前面的代码初始化的全连接层中,我们确保为模型提供了输入形状(我们需要指定模型预期的数据形状,因为这是第一个全连接层)。

此外,我们提到,每个输入将与三个单元(隐藏层中的三个单元)连接,并且在隐藏层中需要执行的激活函数是 ReLU 激活函数。

  1. 将隐藏层与输出层连接:
model.add(Dense(1, activation='linear'))

请注意,在这个全连接层中,我们不需要指定输入形状,因为模型会根据前一层自动推断输入形状。

同时,考虑到每个输出是一维的,我们的输出层只有一个单元,且我们执行的激活函数是线性激活函数。

现在,模型的摘要可以如下可视化:

model.summary()

模型摘要如下:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/dc9ae0ff-7a5e-4f4a-9234-fbd8c877225f.png

前面的输出确认了我们在上一节中的讨论:从输入层到隐藏层的连接将有六个参数——三个权重和三个偏置项——对应三个隐藏单元的总共六个参数。此外,三个权重和一个偏置项将输入层与输出层连接。

  1. 编译模型。这确保我们定义了损失函数和优化器,用来减少损失函数,以及与优化器对应的学习率(我们将在下一章讨论不同的优化器和损失函数):
from keras.optimizers import sgd
sgd = sgd(lr = 0.01)

在前面的步骤中,我们指定了优化器为我们在上一节学习过的随机梯度下降法,学习率为 0.01。将预定义的优化器及其对应的学习率作为参数传递,并减少均方误差值:

model.compile(optimizer=sgd,loss='mean_squared_error')
  1. 拟合模型。更新权重,以便模型更好地拟合:
model.fit(np.array(x), np.array(y), epochs=1, batch_size = 4, verbose=1)

fit方法期望接收两个 NumPy 数组:一个输入数组和一个对应的输出数组。请注意,epochs表示数据集遍历的次数,batch_size表示在更新权重的迭代中需要考虑的数据点数量。此外,verbose指定输出的详细程度,包括训练和测试数据集中的损失信息以及模型训练过程的进展。

  1. 提取权重值。权重值的顺序是通过调用模型上权重方法获得的,如下所示:
model.weights

获取权重的顺序如下:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/faaaee67-07e8-49fa-bd86-10ec8aafd9f8.png

从前面的输出中,我们看到权重的顺序是dense_1层的三个权重(kernel)和三个偏置项(这是输入层与隐藏层之间的连接),以及dense_2层(输出层)之间的三个权重(kernel)和一个偏置项。

现在我们理解了权重值呈现的顺序,让我们提取这些权重的值:

model.get_weights()

请注意,权重以数组列表的形式呈现,其中每个数组对应于model.weights输出中指定的值。

上述代码的输出如下:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/7a280532-11b9-456d-b37c-311c31731327.jpg

你应该注意到,我们在这里观察到的输出与我们在手动构建神经网络时获得的输出一致。

  1. 使用predict方法预测一组新输入的输出:
x1 = [[5],[6]]
model.predict(np.array(x1))

请注意,x1是一个变量,它保存新一组示例的值,针对这些示例,我们需要预测输出值。与fit方法类似,predict方法也期望接收一个数组作为输入。

上述代码的输出如下:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/3c287bfb-ee6c-4bf3-a301-f201cb71cbb2.jpg

请注意,尽管前面的输出不正确,但我们运行 100 个 epoch 后的输出如下:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/39ac7491-ac09-432f-af83-1e6a4d3dec01.jpg

由于我们运行了更多的 epoch,前面的输出将与预期输出(即 10,12)一致。

第二章:构建深度前馈神经网络

在本章中,我们将涵盖以下内容:

  • 训练一个基础神经网络

  • 对输入数据集进行缩放

  • 当大多数输入值大于零时的训练影响

  • 批量大小对模型准确性的影响

  • 构建深度神经网络以提高网络准确性

  • 改变学习率以提高网络准确性

  • 改变损失优化器以提高网络准确性

  • 理解过拟合的情境

  • 使用批量归一化加速训练过程

在上一章中,我们了解了神经网络的基本功能。我们还学习了有许多超参数会影响神经网络的准确性。在本章中,我们将详细探讨神经网络中各种超参数的功能。

本章的所有代码可以在 https://github.com/kishore-ayyadevara/Neural-Networks-with-Keras-Cookbook/blob/master/Neural_network_hyper_parameters.ipynb 找到

训练一个基础神经网络

为了理解如何训练一个基础神经网络,我们将通过预测 MNIST 数据集中数字标签的任务,来实现这一点。MNIST 是一个流行的数字图像数据集(每张图片包含一个数字),并且每个图像都有对应的标签。

做好准备

训练神经网络的步骤如下:

  1. 导入相关的包和数据集

  2. 对目标进行预处理(将它们转换为独热编码向量),以便我们能够在其上进行优化:

    • 我们将最小化类别交叉熵损失
  3. 创建训练集和测试集:

    • 我们有训练数据集,因此我们可以基于它创建一个模型

    • 测试数据集对模型是不可见的:

      • 因此,测试数据集上的准确性是衡量模型在实际应用中可能表现如何的一个指标,因为生产环境中的数据(可能是在构建模型几天/几周后出现的数据)是模型无法看到的
  4. 初始化一个模型

  5. 定义模型架构:

    • 指定隐藏层中的单元数

    • 指定在隐藏层中执行的激活函数

    • 指定隐藏层的数量

    • 指定我们想要最小化的损失函数

    • 提供优化器,以最小化损失函数

  6. 训练模型:

    • 提到批量大小以更新权重

    • 提到总的训练轮数

  7. 测试模型:

    • 提到验证数据,否则提到验证分割,这将把总数据的最后 x%作为测试数据

    • 计算测试数据集上的准确率和损失值

  8. 检查随着训练轮次增加,损失值和准确性值变化中是否有任何有趣的现象

使用这种策略,让我们在接下来的部分中构建一个 Keras 神经网络模型。

如何做到这一点…

  1. 导入相关的包和数据集,并可视化输入数据集:
from keras.datasets import mnist
import numpy
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Dropout
from keras.utils import np_utils
(X_train, y_train), (X_test, y_test) = mnist.load_data()

在前面的代码中,我们导入了相关的 Keras 文件,并且还导入了 MNIST 数据集(该数据集作为 Keras 中的内置数据集提供)。

  1. MNIST 数据集包含数字图像,每张图像的形状为 28 x 28。我们来绘制一些图像,看看它们在代码中的样子:
import matplotlib.pyplot as plt
%matplotlib inline
plt.subplot(221)
plt.imshow(X_train[0], cmap=plt.get_cmap('gray'))
plt.grid('off')
plt.subplot(222)
plt.imshow(X_train[1], cmap=plt.get_cmap('gray'))
plt.grid('off')
plt.subplot(223)
plt.imshow(X_train[2], cmap=plt.get_cmap('gray'))
plt.grid('off')
plt.subplot(224)
plt.imshow(X_train[3], cmap=plt.get_cmap('gray'))
plt.grid('off')
plt.show()

以下截图显示了前面代码块的输出:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/97a5b714-f5d0-493b-a367-5565bf21d112.png

  1. 将 28 x 28 的图像展平,使得输入为所有 784 个像素值。此外,对输出进行 one-hot 编码。这一步骤在数据集准备过程中非常关键:
# flatten 28*28 images to a 784 vector for each image
num_pixels = X_train.shape[1] * X_train.shape[2]
X_train = X_train.reshape(X_train.shape[0], num_pixels).astype('float32')
X_test = X_test.reshape(X_test.shape[0], num_pixels).astype('float32')

在前面的步骤中,我们使用 reshape 方法对输入数据集进行重新调整形状,该方法将给定形状的数组转换为不同的形状。在这个特定的例子中,我们将具有 X_train.shape[0] 个数据点(图像)的数组,其中每个图像有 X_train.shape[1] 行和 X_train.shape[2] 列,转换为一个包含 X_train.shape[0] 个数据点(图像)和每个图像 X_train.shape[1] * X_train.shape[2] 个值的数组。类似地,我们对测试数据集执行相同的操作:

# one hot encode outputs
y_train = np_utils.to_categorical(y_train)
y_test = np_utils.to_categorical(y_test)
num_classes = y_test.shape[1]

让我们尝试理解 one-hot 编码是如何工作的。如果唯一可能的标签是 {0, 1, 2, 3},它们将被 one-hot 编码,如下所示:

Label0123
01000
10100
20010
30001

本质上,每个标签将占据数据集中的一个唯一列,如果标签存在,该列的值将为 1,其他所有列的值将为 0。

在 Keras 中,基于标签的 one-hot 编码是通过 to_categorical 方法实现的,该方法会计算目标数据中的唯一标签数,并将其转换为 one-hot 编码向量。

  1. 构建一个具有 1,000 个单元的隐藏层神经网络:
model = Sequential()
model.add(Dense(1000, input_dim=784, activation='relu'))
model.add(Dense(10,  activation='softmax'))

在前面的步骤中,我们提到输入有 784 个值,这些值连接到隐藏层中的 1,000 个值。此外,我们还指定了在输入与连接输入和隐藏层的权重矩阵相乘后,要在隐藏层执行的激活函数是 ReLu 激活函数。

最后,隐藏层与一个具有 10 个值的输出连接(因为 to_categorical 方法创建的向量有 10 列),并且我们在输出上执行 softmax,以便获得图像属于某一类别的概率。

  1. 前述的模型架构可以如下可视化:
model.summary()

模型的总结如下:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/02ffb258-590f-499a-a9b7-6d7f2bd3b7ee.png

在前面的架构中,第一层的参数数量为 785,000,因为 784 个输入单元连接到 1,000 个隐藏单元,导致 784 * 1,000 个权重值,以及 1,000 个偏置值,最终得到 785,000 个参数。

类似地,输出层有 10 个输出,它们与每个 1,000 个隐藏层连接,从而产生 1,000 * 10 个参数和 10 个偏置——总共 10,010 个参数。

输出层有 10 个单元,因为输出中有 10 个可能的标签。输出层现在为每个类提供给定输入图像的概率值。

  1. 如下所示编译模型:
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

请注意,由于目标变量是一个包含多个类别的独热编码向量,因此损失函数将是分类交叉熵损失。

此外,我们使用 Adam 优化器来最小化代价函数(关于不同优化器的更多信息请参考通过改变损失优化器来提升网络准确性配方)。

我们还注意到,在模型训练过程中,我们需要查看准确率指标。

  1. 如下所示拟合模型:
history = model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=500, batch_size=32, verbose=1)

在前面的代码中,我们已指定模型将拟合的输入(X_train)和输出(y_train)。此外,我们还指定了测试数据集的输入和输出,模型不会使用这些数据来训练权重;然而,它将帮助我们了解训练数据集和测试数据集之间损失值和准确率值的差异。

  1. 提取不同训练轮次下的训练和测试损失以及准确率指标:
history_dict = history.history
loss_values = history_dict['loss']
val_loss_values = history_dict['val_loss']
acc_values = history_dict['acc']
val_acc_values = history_dict['val_acc']
epochs = range(1, len(val_loss_values) + 1)

在拟合模型时,history 变量会存储每个训练轮次中训练数据集和测试数据集的准确率和损失值。在前面的步骤中,我们将这些值存储在一个列表中,以便绘制随着轮次增加,训练和测试数据集的准确率和损失的变化。

  1. 可视化不同训练轮次下的训练和测试损失以及准确率:
import matplotlib.pyplot as plt
%matplotlib inline 

plt.subplot(211)
plt.plot(epochs, history.history['loss'], 'rx', label='Training loss')
plt.plot(epochs, val_loss_values, 'b', label='Test loss')
plt.title('Training and test loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()

plt.subplot(212)
plt.plot(epochs, history.history['acc'], 'rx', label='Training accuracy')
plt.plot(epochs, val_acc_values, 'b', label='Test accuracy')
plt.title('Training and test accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.gca().set_yticklabels(['{:.0f}%'.format(x*100) for x in plt.gca().get_yticks()]) 
plt.legend()
plt.show()

前面的代码生成了以下图表,其中第一个图显示了随着训练轮次增加的训练和测试损失值,第二个图显示了随着训练轮次增加的训练和测试准确率:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/3d56bf42-6d83-4844-a9b7-06ea6ad79d71.png

请注意,前面的网络达到了 97%的准确率。还要注意,损失值(从而准确率)在不同的训练轮次中发生了阶跃变化。我们将在下一部分对比输入数据集缩放前后的损失变化。

  1. 手动计算模型的准确性:
preds = model.predict(X_test)

在前面的步骤中,我们使用predict方法来计算给定输入(在此案例中为X_test)的预期输出值。请注意,我们将其指定为model.predict,因为我们已初始化了一个名为model的顺序模型:

import numpy as np
correct = 0
for i in range(len(X_test)):
    pred = np.argmax(preds[i],axis=0)
    act = np.argmax(y_test[i],axis=0)
    if(pred==act):
        correct+=1
    else:
        continue

correct/len(X_test)

在前面的代码中,我们正在逐一遍历所有测试预测。对于每个测试预测,我们执行argmax来获取具有最高概率值的索引。

同样,我们对测试数据集的实际值执行相同的操作。预测最高值的索引在预测值和测试数据集的实际值中是相同的。

最终,模型在测试数据集上的准确率是正确预测的数量除以测试数据集中的总数据点数。

工作原理…

我们在前面的代码中执行的关键步骤如下:

  • 我们使用reshape方法将输入数据集展平,使每个像素都被视为一个变量。

  • 我们对输出值进行了独热编码,以便使用np_utils包中的to_categorical方法区分不同的标签。

  • 我们通过逐层添加的方式构建了一个带有隐藏层的神经网络。

  • 我们使用model.compile方法编译了神经网络,以最小化分类交叉熵损失(因为输出有 10 个不同的类别)。

  • 我们使用model.fit方法通过训练数据拟合模型。

  • 我们提取了存储在历史中的所有纪元的训练和测试损失准确率。

  • 我们使用model.predict方法预测了测试数据集中每个类别的概率。

  • 我们遍历了测试数据集中的所有图像,并识别出具有最高概率的类别。

  • 最终,我们计算了准确率(预测类别与图像实际类别匹配的实例数量占总实例数的比例)。

在下一节中,我们将探讨损失和准确率值发生阶跃变化的原因,并努力使变化更加平滑。

缩放输入数据集

缩放数据集是一个过程,其中我们限制数据集中变量的范围,确保它们没有非常宽泛的不同值。一种实现方法是将数据集中的每个变量除以该变量的最大值。通常情况下,神经网络在我们缩放输入数据集时表现较好。

在本节中,让我们了解为什么当数据集被缩放时,神经网络表现得更好。

准备工作

为了理解输入缩放对输出的影响,我们将检查输入数据集未缩放时的输出与输入数据集缩放时的输出进行对比。

输入数据未缩放:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/4c549749-a8f8-45e8-8f9c-1ab0f2077506.jpg

在前面的表格中,注意到即使权重值从 0.01 变化到 0.9,输出(sigmoid)变化不大。sigmoid 函数是通过输入与权重的乘积计算 sigmoid 值,然后加上偏置:

output = 1/(1+np.exp(-(w*x + b))

其中w是权重,x是输入,b是偏置值。

Sigmoid 输出没有变化的原因是,因为 w*x 的乘积是一个大数(因为 x 是一个大数),导致 sigmoid 值总是落在 sigmoid 曲线的饱和部分(位于 sigmoid 曲线的右上角或左下角的饱和值)。

在这种情况下,让我们将不同的权重值与一个小的输入数值相乘,如下所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/7de39d87-5f55-4a9c-ad30-771958d1d0ce.png

在前面的表格中,sigmoid 输出有所不同,因为输入和权重值较小,导致输入与权重相乘时得到较小的结果,进而导致 sigmoid 输出出现变化。

通过这次练习,我们了解了缩放输入数据集的重要性,这样在权重(前提是权重范围不大)的值与输入值相乘时会得到一个较小的结果。这种现象导致权重值更新不够迅速。

因此,为了达到最佳的权重值,我们应该缩放输入数据集,同时初始化权重值时不应有过大的范围(通常,在初始化时,权重值是一个介于 -1 和 +1 之间的随机值)。

当权重值也是一个非常大的数字时,这些问题仍然存在。因此,我们最好将权重值初始化为一个接近零的小值。

如何操作…

让我们回顾一下上节中使用的数据集缩放设置,并比较有无缩放的结果:

  1. 导入相关的包和数据集:
from keras.datasets import mnist
import numpy as np
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Dropout
from keras.utils import np_utils

(X_train, y_train), (X_test, y_test) = mnist.load_data()
  1. 缩放数据集有多种方法。一种方法是将所有数据点转换为介于零和一之间的值(通过将每个数据点除以整个数据集的最大值,这就是我们在以下代码中做的)。另一种流行的方式(在多种方法中)是对数据集进行标准化,使得值介于 -1 和 +1 之间,方法是用每个数据点减去整个数据集的均值,然后将每个结果数据点除以原始数据集中的标准差。

现在,我们将对输入数据集进行平展并缩放,如下所示:

# flatten 28*28 images to a 784 vector for each image
num_pixels = X_train.shape[1] * X_train.shape[2]
X_train = X_train.reshape(X_train.shape[0], num_pixels).astype('float32')
X_test = X_test.reshape(X_test.shape[0], num_pixels).astype('float32')

X_train = X_train/255
X_test = X_test/255

在前一步中,我们通过将每个值除以数据集中的最大值(255),将训练和测试输入缩放到一个介于零和一之间的值。此外,我们将输出数据集转换为 one-hot 编码格式:

# one hot encode outputs
y_train = np_utils.to_categorical(y_train)
y_test = np_utils.to_categorical(y_test)
num_classes = y_test.shape[1]
  1. 使用以下代码构建模型并进行编译:
model = Sequential()
model.add(Dense(1000, input_dim=784, activation='relu'))
model.add(Dense(10,  activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

请注意,前述模型与我们在上一节中构建的模型完全相同。唯一的区别是,它将在经过缩放的训练数据集上执行,而之前的模型没有经过缩放。

  1. 按如下方式拟合模型:
history = model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=500, batch_size=32, verbose=1)

你会注意到,前述模型的准确率大约是 98.25%。

  1. 绘制训练和测试准确度以及不同时期的损失值(生成以下图表的代码与我们在训练普通神经网络食谱的第 8 步中使用的代码相同):

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/4affc74a-14b2-4085-9407-5da31fb07f4b.png

从前面的图表中,你应该注意到,与上一部分我们看到的未缩放数据集相比,训练和测试损失在增加的时期中平滑下降。

尽管前述网络在平滑下降的损失值方面给出了良好的结果,但我们注意到训练准确度和测试准确度/损失值之间存在差距,表明在训练数据集上可能存在过拟合现象。过拟合是指模型过度专注于训练数据,导致在测试数据集上表现不佳的现象。

它是如何工作的…

我们在前面的代码中执行的关键步骤如下:

  • 我们使用 reshape 方法将输入数据集展平,以便每个像素被视为一个变量

  • 此外,我们对数据集进行了缩放,使得每个变量的值现在都在零和一之间

    • 我们通过将变量的值除以该变量的最大值来实现前述操作
  • 我们对输出值进行了独热编码,以便使用 np_utils 包中的 to_categorical 方法区分不同的标签

  • 我们通过顺序添加层的方式构建了一个具有隐藏层的神经网络

  • 我们使用 model.compile 方法将神经网络编译为最小化类别交叉熵损失(因为输出有 10 个不同的类别)

  • 我们使用 model.fit 方法通过训练数据来拟合模型

  • 我们提取了在所有时期中存储在历史记录中的训练和测试损失准确度

  • 我们还识别了一个我们认为是过拟合的场景

还有更多…

除了通过将变量的值除以该变量的最大值来缩放变量值外,其他常用的缩放方法如下:

  • 最小-最大归一化

  • 均值归一化

  • 标准化

有关这些缩放方法的更多信息,请访问维基百科:en.wikipedia.org/wiki/Feature_scaling

当大多数输入值大于零时对训练的影响

到目前为止,在我们考虑的数据集中,我们并未查看输入数据集中的值的分布。输入的某些值导致训练速度更快。在本节中,我们将了解在训练时间依赖于输入值时,权重训练速度更快的情况。

准备就绪

在本节中,我们将完全按照与上一节相同的方式进行模型构建。

然而,我们将对我们的策略做出一些小的调整:

  • 我们将反转背景颜色,以及前景颜色。本质上,在这种情况下,背景将是白色的,标签将用黑色书写。

这种变化影响模型准确性的直觉如下。

图像角落的像素对预测图像标签没有贡献。考虑到黑色像素(原始场景)的像素值为零,它会自动被处理,因为当这个输入与任何权重值相乘时,输出就是零。这将导致网络学习到,连接此角落像素与隐藏层的权重值的任何变化都不会对损失值的变化产生影响。

然而,如果角落的像素是白色的(我们已经知道角落的像素对预测图像标签没有贡献),它将对某些隐藏单元的值产生影响,因此权重需要微调,直到角落像素对预测标签的影响最小。

如何做到这一点…

  1. 加载并缩放输入数据集:
(X_train, y_train), (X_test, y_test) = mnist.load_data()
num_pixels = X_train.shape[1] * X_train.shape[2]
X_train = X_train.reshape(X_train.shape[0], num_pixels).astype('float32')
X_test = X_test.reshape(X_test.shape[0], num_pixels).astype('float32')
X_train = X_train/255
X_test = X_test/255
y_train = np_utils.to_categorical(y_train)
y_test = np_utils.to_categorical(y_test)
num_classes = y_test.shape[1]
  1. 让我们看看输入值的分布:
X_train.flatten()

上面的代码将所有输入值展平为一个单一的列表,因此其形状为 (47,040,000),与 28 x 28 x X_train.shape[0] 相同。接下来,让我们绘制所有输入值的分布:

plt.hist(X_train.flatten())
plt.grid('off')
plt.title('Histogram of input values')
plt.xlabel('Input values')
plt.ylabel('Frequency of input values')

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/aeb37d82-2cee-490f-b933-4cfe855c3975.png

我们注意到大多数输入值为零(你应该注意到所有输入图像的背景都是黑色的,因此,大多数值为零,这是黑色的像素值)。

  1. 在本节中,让我们探索一个场景,其中我们反转颜色,背景为白色,字母为黑色,使用以下代码:
X_train = 1-X_train
X_test = 1-X_test

让我们绘制这些图像:

import matplotlib.pyplot as plt
%matplotlib inline
plt.subplot(221)
plt.imshow(X_train[0].reshape(28,28), cmap=plt.get_cmap('gray'))
plt.grid('off')
plt.subplot(222)
plt.imshow(X_train[1].reshape(28,28), cmap=plt.get_cmap('gray'))
plt.grid('off')
plt.subplot(223)
plt.imshow(X_train[2].reshape(28,28), cmap=plt.get_cmap('gray'))
plt.grid('off')
plt.subplot(224)
plt.imshow(X_train[3].reshape(28,28), cmap=plt.get_cmap('gray'))
plt.grid('off')
plt.show()

它们将如下所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/41d3b675-5acf-4a2a-830c-d911857cb189.png

结果图像的直方图现在如下所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/c8f2c774-2d0e-47dd-9324-7e199534c934.png

你应该注意到,大多数输入值现在的值为一。

  1. 接下来,我们将使用在Scaling 输入数据集*部分中构建的相同模型架构来构建我们的模型:
model = Sequential()
model.add(Dense(1000,input_dim=784,activation='relu'))
model.add(Dense(10, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
history = model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=10, batch_size=32, verbose=1)
  1. 绘制不同训练周期(epochs)下的训练和测试准确率及损失值(生成以下图表的代码与我们在训练普通神经网络步骤 8 中使用的代码相同):

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/bf81c069-33e3-44f5-905f-76c8bcb34578.png

我们应该注意到,模型准确率现在已下降到约 97%,而使用相同的模型进行相同数量的周期和批量大小时,在一个大多数值为零(而不是大多数为一)的数据集上,准确率为约 98%。此外,模型的准确率为 97%,比输入像素大多数为零的场景要慢得多。

当大多数数据点为非零时,准确度下降的直觉是:当大多数像素为零时,模型的任务较简单(需要调整的权重较少),因为它只需根据少数像素值(那些像素值大于零的)进行预测。然而,当大多数数据点为非零时,模型需要调整更多的权重以进行预测。

批量大小对模型准确度的影响

在前面的各个部分中,我们为所有构建的模型都考虑了批量大小为 32。在这一部分中,我们将尝试理解批量大小对准确度的影响。

准备开始

为了理解批量大小对模型准确度的影响,让我们对比两个情境,其中数据集总大小为 60,000:

  • 批量大小为 30,000

  • 批量大小为 32

当批量大小较大时,每个训练轮次中的权重更新次数较少,相较于批量大小较小的情境。

批量大小较小时,每个训练轮次中权重更新次数较多的原因是,计算损失值时考虑的数据点较少。这导致每个训练轮次中批量的数量增多,因为大致而言,在一个训练轮次中,您需要遍历数据集中的所有训练数据点。

因此,批量大小越小,在相同的训练轮次下准确度越高。然而,在决定用于批量大小的数据点数量时,您还应确保批量大小不宜过小,以免在小批量数据上发生过拟合。

如何实现…

在前面的步骤中,我们建立了一个批量大小为 32 的模型。在这个步骤中,我们将继续实现模型,并对比低批量大小和高批量大小在相同训练轮次下的情境:

  1. 按照以下步骤预处理数据集并拟合模型:
(X_train, y_train), (X_test, y_test) = mnist.load_data()
num_pixels = X_train.shape[1] * X_train.shape[2]
X_train = X_train.reshape(X_train.shape[0], num_pixels).astype('float32')
X_test = X_test.reshape(X_test.shape[0], num_pixels).astype('float32')
X_train = X_train/255
X_test = X_test/255
y_train = np_utils.to_categorical(y_train)
y_test = np_utils.to_categorical(y_test)
num_classes = y_test.shape[1]
model = Sequential()
model.add(Dense(1000,input_dim=784,activation='relu'))
model.add(Dense(10, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
history = model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=10, batch_size=30000, verbose=1)

请注意,代码中的唯一变化是在模型拟合过程中使用的batch_size参数。

  1. 绘制不同训练轮次下的训练准确度、测试准确度和损失值(生成以下图表的代码与我们在训练基本神经网络步骤 8 中使用的代码相同):

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/8045caed-04a4-41b0-a58d-5aaafba99a70.png

在之前的情境中,您应该注意到,与批量大小较小的模型相比,模型准确度在较晚的训练轮次才达到约 98%。

它是如何工作的…

您应该注意到,最初准确度较低,只有在经过相当数量的训练轮次后才会赶上。初期准确度较低的原因是,在这种情况下,权重更新的次数远低于之前的情境(批量大小较小的情境)。

在这种情况下,当批次大小为 30,000,总数据集大小为 60,000 时,当我们运行 500 个周期时,权重更新会发生在周期 * (数据集大小 / 批次大小) = 500 * (60,000 / 30,000) = 1,000 次。

在之前的场景中,权重更新发生在 500 * (60,000 / 32) = 937,500 次。

因此,批次大小越小,权重更新的次数就越多,一般来说,对于相同数量的周期,准确率会更好。

同时,你应该小心不要让批次大小过小,这可能导致不仅训练时间过长,还有可能出现过拟合的情况。

构建深度神经网络以提高网络准确率

到目前为止,我们已经看过了神经网络模型,其中神经网络只有一个隐藏层,位于输入层和输出层之间。在这一节中,我们将讨论具有多个隐藏层(因此是深度神经网络)的神经网络,同时重用已缩放的相同 MNIST 训练和测试数据集。

准备工作

深度神经网络意味着有多个隐藏层将输入层和输出层连接起来。多个隐藏层确保神经网络学习输入和输出之间复杂的非线性关系,这是简单神经网络无法学习的(因为隐藏层数量有限)。

一个典型的深度前馈神经网络如下所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/5b116810-f567-4e06-9f94-efdbe6f013e0.png

如何做…

深度神经网络架构是通过在输入层和输出层之间添加多个隐藏层来构建的,如下所示:

  1. 加载数据集并进行缩放:
(X_train, y_train), (X_test, y_test) = mnist.load_data()
num_pixels = X_train.shape[1] * X_train.shape[2]
X_train = X_train.reshape(X_train.shape[0], num_pixels).astype('float32')
X_test = X_test.reshape(X_test.shape[0], num_pixels).astype('float32')
X_train = X_train/255
X_test = X_test/255
y_train = np_utils.to_categorical(y_train)
y_test = np_utils.to_categorical(y_test)
num_classes = y_test.shape[1]
  1. 构建一个具有多个隐藏层将输入层和输出层连接起来的模型:
model = Sequential()
model.add(Dense(1000, input_dim=784, activation='relu'))
model.add(Dense(1000,activation='relu'))
model.add(Dense(1000,activation='relu'))
model.add(Dense(10,  activation='softmax'))

前述模型架构的结果如下所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/f36020ac-67c3-494d-a646-f863b71527dc.png

请注意,前述模型会导致更多的参数数量,这是由于深度架构的结果(因为模型中有多个隐藏层)。

  1. 现在模型已经设置好,我们来编译并拟合模型:
model.compile(loss='categorical_crossentropy', optimizer=adam, metrics=['accuracy'])
history = model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=250, batch_size=1024, verbose=1)

前述结果得到一个 98.6%的准确率,这略高于我们之前看到的模型架构的准确率。训练和测试的损失及准确率如下所示(生成以下图表的代码与我们在训练基础神经网络食谱第 8 步中使用的代码相同):

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/f11ab003-caee-4343-8b8b-eadce1391e26.png

请注意,在这种情况下,训练损失和测试损失之间存在相当大的差距,表明深度前馈神经网络在训练数据上过拟合了。我们将在过拟合的章节中学习如何避免对训练数据的过拟合。

调整学习率以提高网络准确率

到目前为止,在之前的食谱中,我们使用的是 Adam 优化器的默认学习率 0.0001。

在本节中,我们将手动将学习率设置为更大的数值,并观察改变学习率对模型准确性的影响,同时复用之前食谱中已缩放的相同 MNIST 训练和测试数据集。

准备工作

在前一章节关于构建前馈神经网络的内容中,我们学习了学习率用于更新权重,且权重的变化与损失减少的量成正比。

此外,权重值的变化等于损失减少量与学习率的乘积。因此,学习率越低,权重值的变化越小,反之亦然。

你可以基本上将权重值视为一个连续的光谱,其中权重是随机初始化的。当权重值变化较大时,有很大可能在该光谱中并未考虑到所有可能的权重值。然而,当权重值变化较小,可能会实现全局最小值,因为可能会考虑更多的权重值。

为了更进一步理解,我们来考虑拟合y = 2x直线的玩具示例,其中初始权重值为 1.477,初始偏置值为零。前馈和反向传播函数将保持与前一章相同:

def feed_forward(inputs, outputs, weights):
     hidden = np.dot(inputs,weights[0])
     out = hidden+weights[1]
     squared_error = (np.square(out - outputs))
     return squared_error

def update_weights(inputs, outputs, weights, epochs, lr): 
    for epoch in range(epochs):
        org_loss = feed_forward(inputs, outputs, weights)
        wts_tmp = deepcopy(weights)
        wts_tmp2 = deepcopy(weights)
        for ix, wt in enumerate(weights):
            print(ix, wt)
            wts_tmp[-(ix+1)] += 0.0001
            loss = feed_forward(inputs, outputs, wts_tmp)
            del_loss = np.sum(org_loss - loss)/(0.0001*len(inputs))
            wts_tmp2[-(ix+1)] += del_loss*lr
            wts_tmp = deepcopy(weights)
        weights = deepcopy(wts_tmp2)
    return wts_tmp2

请注意,从前一章看到的反向传播函数中唯一的变化是我们将学习率作为参数传递给了前述函数。当学习率为 0.01 时,在不同训练轮次下的权重值如下:

w_val = []
b_val = []
for k in range(1000):
     w_new, b_new = update_weights(x,y,w,(k+1),0.01)
     w_val.append(w_new)
     b_val.append(b_new)

不同训练轮次下权重变化的图形可以通过以下代码获得:

import matplotlib.pyplot as plt
%matplotlib inline
plt.plot(w_val)
plt.title('Weight value over different epochs when learning rate is 0.01')
plt.xlabel('epochs')
plt.ylabel('weight value')
plt.grid('off')

前述代码的输出结果如下:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/ff23c179-fb57-4c71-905c-512353516f30.png

以类似的方式,当学习率为 0.1 时,在不同训练轮次下的权重值如下:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/226c0088-34ef-46bc-a399-46ce411fd70e.png

该截图显示了当学习率为 0.5 时,在不同训练轮次下权重的变化值:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/c6883c3e-8044-4ba1-97cc-34943ece045f.png

请注意,在前述场景中,最初权重值发生了剧烈变化,而学习率为 0.1 时收敛了,而学习率为 0.5 时未能收敛到最优解,因此陷入了局部最小值。

在学习率为 0.5 的情况下,由于权重值停滞在局部最小值,它无法达到最优值 2。

如何进行…

现在我们已经理解了学习率如何影响输出值,让我们看看学习率对之前看到的 MNIST 数据集的实际影响,我们将保持相同的模型架构,但只改变学习率参数。

请注意,我们将使用与缩放输入数据集食谱中第 1 步和第 2 步相同的数据预处理步骤。

一旦数据集预处理完成,我们通过在下一步中指定优化器来调整模型的学习率:

  1. 我们按如下方式调整学习率:
from keras import optimizers
adam=optimizers.Adam(lr=0.01)

通过前述代码,我们已使用指定的学习率 0.01 初始化了 Adam 优化器。

  1. 我们按照如下方式构建、编译并训练模型:
model = Sequential()
model.add(Dense(1000, input_dim=784, activation='relu'))
model.add(Dense(10,  activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer=adam, metrics=['accuracy']) 

history = model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=500, batch_size=1024, verbose=1)

前述网络的准确率在 500 个训练轮次结束时约为 90%。让我们看看损失函数和准确率在不同训练轮次下的变化(生成下图的代码与我们在训练基础神经网络方法第 8 步中使用的代码相同):

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/1b584542-f5c3-48b0-9af0-1855dea2f4c7.png

请注意,当学习率较高(在当前情况下为 0.01)时,与 0.0001(在缩放输入数据集方法中考虑的情况)相比,损失函数下降得不如低学习率模型平稳。

低学习率的模型更新权重较慢,因此导致损失函数平稳下降,并且准确率较高,这种准确率是在更多的训练轮次中逐渐获得的。

另外,当学习率较高时,损失值的步进变化是由于损失值陷入局部最小值,直到权重值改变为最优值。较低的学习率可以更快地到达最优权重值,因为权重变化较慢,但在正确的方向上稳步前进。

类似地,让我们探讨当学习率为 0.1 时网络的准确性:

from keras import optimizers
adam=optimizers.Adam(lr=0.1)

model = Sequential()
model.add(Dense(1000, input_dim=784, activation='relu'))
model.add(Dense(10,  activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer=adam, metrics=['accuracy'])
history = model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=500, batch_size=1024, verbose=1)

需要注意的是,由于学习率较高,损失值无法进一步显著下降;也就是说,可能权重已经陷入局部最小值:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/2c8659c9-e4e3-4524-9dfd-5f6661fd3b96.png

因此,一般来说,设置较低的学习率并让网络在较多的训练轮次中进行学习是一个好主意。

改变损失优化器以提高网络准确性

到目前为止,在前面的几种方法中,我们认为损失优化器是 Adam 优化器。然而,优化器有多种变体,优化器的更改可能会影响模型学习拟合输入与输出的速度。

在本方法中,我们将理解改变优化器对模型准确性的影响。

准备就绪

为了了解优化器变化对网络准确性的影响,下面我们将对比前面章节中的情况(使用的是 Adam 优化器)和这一节使用随机梯度下降优化器的情况,同时重新使用已缩放的相同 MNIST 训练和测试数据集(数据预处理步骤与缩放数据集方法中的步骤 1 和步骤 2 相同):

model = Sequential()
model.add(Dense(1000, input_dim=784, activation='relu'))
model.add(Dense(10,  activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy'])
history = model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=100, batch_size=32, verbose=1)

请注意,当我们在前面的代码中使用随机梯度下降优化器时,经过 100 轮训练后的最终准确率大约为 98%(以下图表的生成代码与我们在训练一个基础神经网络食谱的第 8 步中使用的代码相同):

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/38cdae47-275b-46d5-b662-fc29d5f85df3.png

然而,我们也应该注意,与使用 Adam 优化器的模型相比,该模型在达到高准确率的过程中变得更加缓慢。

还有更多…

其他一些可用的损失优化器如下:

  • RMSprop

  • Adagrad

  • Adadelta

  • Adamax

  • Nadam

你可以在这里了解更多关于不同优化器的信息:keras.io/optimizers/

此外,你还可以在这里找到每个优化器的源代码:github.com/keras-team/keras/blob/master/keras/optimizers.py

理解过拟合的情形

在之前的一些食谱中,我们注意到训练准确率接近 100%,而测试准确率约为 98%,这就是在训练数据集上发生过拟合的情况。让我们对训练和测试准确率之间的差异有一个直观的理解。

为了理解导致过拟合的现象,让我们对比两个情形,分别比较训练和测试的准确率,并且展示权重的直方图:

  • 模型运行五轮训练

  • 模型运行 100 轮训练

两个情形之间的训练集和测试集的准确率对比如下:

情形训练数据集测试数据集
5 轮训练97.59%97.1%
100 轮训练100%98.28%

一旦我们绘制出连接隐藏层和输出层的权重的直方图,我们会注意到,与五轮训练的情况相比,100 轮训练的权重分布范围更大:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/a6f6b04b-293f-4fb2-a76f-211d40b81f0c.png

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/7c5f76b7-f483-4666-9fc5-ea98573fc588.png

从前面的图片中你应该注意到,100 轮训练的情况相比于五轮训练的情况,权重值的分布更加广泛。这是因为在 100 轮训练中,模型有更多的机会在训练集上过拟合,而相比之下,五轮训练的更新次数较少,因此过拟合的机会也较少。

权重值过高(以及训练集和测试集之间的差异)是模型可能发生过拟合和/或可能有机会缩放输入/权重以提高模型准确率的良好指示。

此外,神经网络中可能包含数十万甚至百万的权重(某些架构中为数百万),这些权重都需要进行调整,因此,某些权重有可能会被更新为非常大的值,以便针对数据集中某一特异值行进行微调。

使用正则化克服过拟合

在前一部分中,我们已确定高权重幅度是过拟合的原因之一。在本节中,我们将探讨如何避免过拟合问题,例如对高权重幅度值进行惩罚。

正则化通过对模型中的高权重幅度进行惩罚来进行调节。L1 和 L2 正则化是最常用的正则化技术,其工作原理如下:

L2 正则化在最小化损失函数的同时(即以下公式中的平方损失和),还会最小化神经网络指定层的权重平方和:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/64a98b14-388a-4cbb-b604-30c56e739681.png

其中 https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/f9fef716-fe17-45b4-8abf-6a669c5d3cc6.png 是与正则化项相关的权重值,这是一个需要调整的超参数,y 是预测值,https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/28abf306-b171-4bd7-b8de-1bdffe82639c.png 是目标值, https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/f552cdd0-2311-4b72-8ec6-29e8c99fcf69.png 是模型所有层的权重值。

L1 正则化在最小化损失函数的同时(即以下公式中的平方损失和),还会最小化神经网络指定层的权重绝对值之和:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/9e52d2e2-aca1-4e06-a3af-007deacb2468.png

通过这种方式,我们可以确保权重不会仅针对训练集中的极端案例进行调整(从而避免在测试数据上无法泛化)。

如何做到这一点

L1/L2 正则化在 Keras 中的实现方式如下:

model = Sequential()
model.add(Dense(1000,input_dim=784,activation='relu',kernel_regularizer=l2(0.1)))model.add(Dense(10,  activation='softmax',kernel_regularizer=l2(0.1)))
model.compile(loss='categorical_crossentropy', optimizer=adam, metrics=['accuracy'])
history = model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=500, batch_size=1024, verbose=1)

请注意,前面的操作涉及调用一个额外的超参数——kernel_regularizer,然后指定它是 L1 还是 L2 正则化。此外,我们还会指定 lambda 值,这个值决定了正则化的权重。

我们注意到,在正则化之后,训练集的准确率并未达到 ~100%,而测试集的准确率为 98%。L2 正则化后权重的直方图将在下一个图中显示。

连接隐藏层和输出层的权重提取方式如下:

model.get_weights()[0].flatten()

一旦提取了权重,它们会按以下方式绘制:

plt.hist(model.get_weights()[0].flatten())

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/e672e37d-2059-4520-9564-12ee59698144.png

我们注意到,与之前的情况相比,绝大多数权重现在都更接近于零,这就避免了过拟合问题。在 L1 正则化的情况下,我们也能看到类似的趋势。

请注意,在存在正则化的情况下,权重值比没有进行正则化时的权重值要低得多。

因此,L1 和 L2 正则化帮助我们避免了训练数据集上的过拟合问题。

使用丢弃法克服过拟合

在上一节中,我们使用 L1/L2 正则化来克服过拟合问题。在这一节中,我们将使用另一种有助于实现相同目标的工具——丢弃法

丢弃法可以看作是一种方法,其中只有某个百分比的权重会被更新,而其他的权重则不会在某次权重更新迭代中被更新。这样,我们就处于一个位置,即并非所有权重都在权重更新过程中被更新,从而避免了某些权重相较于其他权重达到非常高的幅度:

model = Sequential()
model.add(Dense(1000, input_dim=784, activation='relu'))
model.add(Dropout(0.75))
model.add(Dense(10,  activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer=adam, metrics=['accuracy'])
history = model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=100, batch_size=1024, verbose=1)

在前面的代码中,我们设置了 0.75 的丢弃率;也就是说,在某次权重更新迭代中,75%的权重不会被更新。

上述情况会导致训练准确度和测试准确度之间的差距没有像在没有丢弃法的情况下构建模型时那么大,因为在没有丢弃法的情况下,权重的分布较广。

请注意,现在第一层权重的直方图:

plt.hist(model.get_weights()[-2].flatten())

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/64d666de-bbb5-4c96-94d6-79946b4e26a6.png

请注意,在前述场景中,超出 0.2 或-0.2 的权重的频次要比 100 个 epoch 的场景少。

使用批量归一化加速训练过程

在前一节关于数据集缩放的内容中,我们学习到,当输入数据没有被缩放(即它没有在零到一之间)时,优化过程会很慢。

在以下情境中,隐藏层的值可能会很高:

  • 输入数据值很高

  • 权重值很高

  • 权重和输入的乘积很高

这些情境中的任何一种都可能导致隐藏层的输出值很大。

请注意,隐藏层是输入层到输出层的过渡。因此,当隐藏层值很大时,高输入值导致优化缓慢的现象同样适用。

批量归一化在这种情况下发挥了重要作用。我们已经学到,当输入值很高时,我们会进行缩放以减少输入值。此外,我们还学到,可以使用另一种方法进行缩放,即减去输入的均值,并除以输入的标准差。批量归一化正是采用这种方法进行缩放。

通常,所有值都使用以下公式进行缩放:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/bc09c4fd-85ae-46d7-97ac-2dc608a9c27f.png

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/e4844bdd-acf2-4e1d-b5f7-f8288413842a.png

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/110c9b11-033b-4bfc-9b4f-445cc803a93e.png

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/aa3c6535-7868-42c7-bd2f-4056453df216.png

请注意,γβ 在训练过程中会学习到,连同网络的原始参数一起。

如何做…

在代码中,批量归一化是这样应用的:

请注意,我们将使用与步骤 1 和步骤 2 中在缩放输入数据集步骤中相同的数据预处理方法。

  1. 按如下方式导入BatchNormalization方法:
from keras.layers.normalization import BatchNormalization
  1. 实例化一个模型,并构建与我们使用正则化技术时相同的架构。唯一的不同之处是我们在一个隐藏层中执行批量归一化:
model = Sequential()
model.add(Dense(1000, input_dim=784,activation='relu', kernel_regularizer = l2(0.01)))
model.add(BatchNormalization())
model.add(Dense(10, activation='softmax', kernel_regularizer = l2(0.01)))
  1. 按如下方式构建、编译和拟合模型:
from keras.optimizers import Adam
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
history = model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=100, batch_size=1024, verbose=1)

前述结果显示,当没有批量归一化时,训练速度明显较慢,如下所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/2f5f1e2c-8a0d-45df-8c1a-d1a7918628b9.png

前面的图表显示了没有批量归一化,仅使用正则化时的训练和测试损失与准确度。接下来的图表显示了同时使用正则化和批量归一化时的训练和测试损失与准确度:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/32d51718-2639-441a-8155-bd78778bed39.png

请注意,在前述的两种场景中,当我们执行批量归一化时,训练速度明显更快(测试数据集准确率约为 97%),相比之下,当我们不执行时(测试数据集准确率约为 91%):

因此,批量归一化使得训练速度大大加快。

第三章:深度前馈神经网络的应用

本章中,我们将覆盖以下方案:

  • 预测信用违约

  • 预测房价

  • 将新闻文章分类到不同主题

  • 分类常见音频

  • 预测股票价格

介绍

在前几章中,我们学习了如何构建神经网络及需要调整的各种参数,以确保模型能够很好地泛化。此外,我们还学习了如何利用神经网络在 MNIST 数据集上进行图像分析。

在本章中,我们将学习如何利用神经网络进行预测,基于以下内容:

  • 结构化数据集

    • 分类输出预测

    • 连续输出预测

  • 文本分析

  • 音频分析

此外,我们还将学习以下内容:

  • 实现自定义损失函数

  • 对某些类别的输出赋予比其他类别更高的权重

  • 对数据集中某些行赋予比其他行更高的权重

  • 利用功能性 API 整合多个数据源

我们将通过以下方案来学习上述内容:

  • 预测信用违约

  • 预测房价

  • 分类新闻文章

  • 预测股票价格

  • 分类常见音频

然而,你应该注意,这些应用仅用于帮助你理解如何利用神经网络分析各种输入数据。关于卷积神经网络和循环神经网络的高级文本、音频和时间序列数据分析方法将会在后续章节中提供。

预测信用违约

在金融服务行业,客户违约是造成收入损失的主要原因之一。然而,实际发生违约的客户占总客户的比例非常小。因此,这成为一个分类问题,更重要的是,识别出稀有事件。

在这个案例研究中,我们将分析一个跟踪客户在某一时间点的关键属性的数据库,并尝试预测客户是否可能发生违约。

让我们考虑一下如何将我们构建的模型的预测结果投入到实际应用中。企业可能会特别关注那些更可能违约的客户——可能会为他们提供替代的付款选项或减少信用额度等方式。

准备工作

我们将采用以下策略来预测客户违约:

  • 目标:为更可能违约的客户分配高概率。

  • 测量 标准:通过降低违约概率,最大化仅考虑前 10%成员时实际违约的客户数量。

我们将采用以下策略为每个成员分配违约概率:

  • 考虑所有成员的历史数据。

  • 理解哪些变量可以帮助我们识别可能违约的客户:

    • 收入与债务比率是一个非常好的指示器,能够判断成员是否可能违约。

    • 我们将提取一些与此类似的其他变量。

  • 在前一步中,我们创建了输入变量;现在,让我们继续创建因变量:

    • 我们将通过回顾历史数据并查看成员是否在接下来的两年内发生违约,来提取那些实际违约的成员。

    • 设置时间滞后非常重要,因为如果我们在预测日期与成员可能违约之间没有时间间隔,可能无法为我们提供改变结果的杠杆。

  • 由于结果是二元的,我们将最小化二元交叉熵损失。

  • 模型将有一个隐藏层,连接输入层和输出层。

  • 我们将计算在测试数据集中,实际违约的前 10%概率成员的数量。

请注意,我们假设测试数据具有代表性,因为在没有将模型投入生产的情况下,我们无法评估模型在未见数据上的表现。我们假设模型在未见数据上的表现是预测模型在未来数据上表现的良好指标。

如何实现…

我们将按照以下策略编写代码(在实现代码时,请参考 GitHub 中的Credit default prediction.ipynb文件):

  1. 导入相关包和数据集:
import pandas as pd
data = pd.read_csv('...') # Please add path to the file you downloaded

我们下载的数据集的前三行如下:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/f2c32cdc-9ecb-4a70-89f9-1460501a5bc3.png

上面的截图展示的是原始数据集中一部分变量。名为Defaultin2yrs的变量是我们需要预测的输出变量,基于数据集中其他存在的变量。

  1. 总结数据集以更好地理解变量:
data.describe()

一旦查看输出,您将会注意到以下几点:

  • 某些变量的范围较小(age),而其他变量的范围则大得多(Income)。

  • 某些变量存在缺失值(Income)。

  • 某些变量存在异常值(Debt_income_ratio)。在接下来的步骤中,我们将纠正所有之前标记的问题。

  • 用变量的中位数值填充缺失值:

vars = data.columns[1:]
import numpy as np
for var in vars:
     data[var]= np.where(data[var].isnull(),data[var].median(),data[var])

在上面的代码中,我们排除了第一个变量,因为它是我们要预测的变量,然后对其余变量中的缺失值进行填充(前提是该变量确实存在缺失值)。

  1. 将每个变量限制在其对应的 95^(th)百分位值,以避免输入变量中出现异常值:
for var in vars:
     x=data[var].quantile(0.95)
     data[var+"outlier_flag"]=np.where(data[var]>x,1,0)
     data[var]=np.where(data[var]>x,x,data[var])

在上面的代码中,我们已识别出每个变量的 95^(th)百分位值,创建了一个新变量,如果行中存在给定变量的异常值,则该新变量的值为 1,否则为 0。此外,我们还将变量值限制为原始值的 95^(th)百分位值。

  1. 一旦我们总结了修改后的数据,我们注意到,除了Debt_income_ratio变量外,其他所有变量似乎都没有异常值了。因此,我们进一步限制Debt_income_ratio,将其输出范围限制在 80^(th)百分位值:
data['Debt_income_ratio_outlier']=np.where(data['Debt_incomeratio']>1,1,0)
data['Debt_income_ratio']=np.where(data['Debt_income_ratio']>1,1,data['Debt_income_ratio'])
  1. 将所有变量标准化到相同的尺度,使其值介于零和一之间:
for var in vars:
     data[var]= data[var]/data[var].max()

在前面的代码中,我们通过将每个输入变量的值除以该输入变量列的最大值,将所有变量限制在一个相似的输出范围内,该范围介于零和一之间。

  1. 创建输入和输出数据集:
X = data.iloc[:,1:]
Y = data['Defaultin2yrs']
  1. 将数据集划分为训练集和测试集:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size = 0.3, random_state= 42)

在前一步中,我们使用train_test_split方法将输入和输出数组拆分为训练集和测试集,其中测试集占输入和对应输出数组总数据点的 30%。

  1. 现在数据集已经创建,让我们定义神经网络模型,如下所示:
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Dropout
from keras.utils import np_utils
model = Sequential()
model.add(Dense(1000, input_dim=X_train.shape[1], activation='relu'))
model.add(Dense(1, activation='sigmoid'))
model.summary()

模型的总结如下:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/faf052a6-457c-4e04-9b97-131f47334ab3.png

在先前的架构中,我们将输入变量连接到一个包含 1,000 个隐藏单元的隐藏层。

  1. 编译模型。我们将使用二元交叉熵,因为输出变量只有两个类别。此外,我们将指定optimizeradam优化器:
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
  1. 拟合模型:
history = model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=20, batch_size=1024, verbose=1)

训练和测试损失、准确率随着训练轮次增加的变化如下所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/e6314265-8680-40d4-a66a-75d2da8f9876.png

  1. 对测试数据集进行预测:
pred = model.predict(X_test)
  1. 检查在按概率递减排序时,测试数据集前 10%中实际违约者的数量:
test_data = pd.DataFrame([y_test]).T
test_data['pred']=pred
test_data = test_data.reset_index(drop='index')
test_data = test_data.sort_values(by='pred',ascending=False)
print(test_data[:4500]['Defaultin2yrs'].sum())

在前面的代码中,我们将预测值与实际值连接起来,然后按概率对数据集进行排序。我们检查了在按概率递减排序时,测试数据集前 10%(即前 4,500 行)中捕获的实际违约者的数量。

我们应注意,我们通过筛选 4,500 个高概率客户,捕获了 1,580 个实际违约者。这是一个不错的预测,因为平均而言,只有 6%的客户会违约。因此,在这种情况下,约 35%的高违约概率客户实际上发生了违约。

它是如何工作的…

在本教程中,我们学习了以下概念:

  • 填补 缺失 :我们了解到填补变量缺失值的一种方法是用该变量的中位数替换缺失值。处理缺失值的其他方法包括用均值替代缺失值,或者通过将缺失值替换为与包含缺失值的行最相似的行中该变量的均值(这一技术称为识别最近邻)。

  • 限制异常值:我们还学到,一种限制异常值的方法是将大于 95^(th)百分位数的值替换为 95^(th)百分位数的值。我们执行此操作的原因是为了确保输入变量不会出现所有值都集中在一个小值附近的情况(当变量按最大值缩放时,该最大值为异常值)。

  • 缩放数据集:最后,我们对数据集进行了缩放,以便它可以传递给神经网络。

为类别分配权重

当我们为属于违约者的行和属于非违约者的行分配相同的权重时,模型可能会对非违约者进行微调。在本节中,我们将探讨如何分配更高的权重,以便我们的模型能更好地分类违约者。

准备工作

在上一节中,我们为每个类别分配了相同的权重;也就是说,当实际值和预测值之间的差异大小相同时,分类交叉熵损失是相同的,无论它是用于预测违约还是非违约。

为了进一步理解这个情境,让我们考虑以下示例:

情境违约概率实际违约值交叉熵损失
10.211log(0.2)*
20.80*(1-0)log(1-0.8)

在之前的情境中,交叉熵损失值是相同的,无论实际违约值如何。

然而,我们知道我们的目标是尽可能多地捕获实际违约者,在通过概率排序后的前 10%预测中。

因此,让我们继续分配更高的损失权重(权重为100),当实际违约值为1时,而当实际违约值为0时,分配较低的权重(权重为1)。

之前的情境现在发生了如下变化:

情境违约概率实际违约值交叉熵损失
10.211001log(0.2)
20.801(1-0)log(1-0.8)

现在,如果我们注意到交叉熵损失,当预测错误时,实际违约值为1的情况相比实际违约值为0时的预测,交叉熵损失要高得多。

现在我们已经理解了为类别分配权重的直觉,让我们继续在信用违约数据集中为输出类别分配权重。

为了构建数据集和模型执行的所有步骤与上一节相同,除了模型拟合过程。

如何操作…

模型拟合过程通过以下步骤完成(在实现代码时,请参考 GitHub 中的Credit default prediction.ipynb文件):

history = model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=10, batch_size=1024, verbose=1,class_weight = {0:1,1:100})

请注意,在前面的代码片段中,我们创建了一个字典,其中包含与输出中的不同类别对应的权重,然后将其作为输入传递给class_weight参数。

前一步骤确保我们在计算实际结果为1时,给损失值赋予权重100,而在计算实际结果为0时,给损失值赋予权重1

随着训练轮次的增加,准确率和损失值的变化如下:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/319b3dc9-49bd-46b1-b089-16157ec61b44.png

请注意,在此迭代中,准确度值显著降低,因为我们预测的数据点值为 1 的数量比在两个类别具有相等权重的场景中更多。

一旦模型训练完成,我们就可以检查在前 10%的预测中捕获到的实际违约者数量,如下所示:

pred = model.predict(X_test)
test_data = pd.DataFrame([y_test[:,1]]).T
test_data['pred']=pred[:,1]
test_data = test_data.reset_index(drop='index')
test_data = test_data.sort_values(by='pred',ascending=False)
test_data.columns = ['Defaultin2yrs','pred']
print(test_data[:4500]['Defaultin2yrs'].sum())

你会注意到,与之前捕获了 1,580 名客户的前 10%的情况相比,在这个场景下,我们捕获了 1,640 名客户进入前 10%,因此在我们设定的目标上取得了更好的结果,在这个场景中,我们捕获了 36%的所有违约者进入前 10%的高概率客户,而在之前的场景中是 35%。

并不是所有情况下随着类权重的增加,准确率都会提高。分配类权重是一种机制,用于给我们关注的预测赋予更高的权重。

预测房价

在前一个案例研究中,我们的输出是分类的。在本案例研究中,我们将探讨一个连续的输出,通过尝试预测房价,其中提供了 13 个可能影响房价的变量作为输入。

目标是最小化预测房价的误差。

准备开始

鉴于目标是最小化误差,让我们定义我们将要最小化的误差——我们应该确保正误差和负误差不会相互抵消。因此,我们将最小化绝对误差。另一种选择是最小化平方误差。

现在我们已经微调了目标,让我们定义解决这个问题的策略:

  • 对输入数据集进行归一化处理,使所有变量的范围都在 0 到 1 之间。

  • 将给定数据拆分为训练集和测试集。

  • 初始化隐藏层,将 13 个输入变量连接到一个输出变量。

  • 使用 Adam 优化器编译模型,并定义损失函数为最小化平均绝对误差值。

  • 训练模型。

  • 对测试数据集进行预测。

  • 计算在测试数据集上预测的误差。

现在我们已经定义了方法,让我们在下一节中使用代码来执行它。

如何进行…

  1. 导入相关数据集(在实施代码时,请参考 GitHub 中的Predicting house price.ipynb文件以及推荐的数据集):
from keras.datasets import boston_housing
(train_data, train_targets), (test_data, test_targets) = boston_housing.load_data()
  1. 对输入和输出数据集进行归一化处理,使所有变量的范围都在 0 到 1 之间:
import numpy as np
train_data2 = train_data/np.max(train_data,axis=0)
test_data2 = test_data/np.max(train_data,axis=0)
train_targets = train_targets/np.max(train_targets)
test_targets = test_targets/np.max(train_targets)

请注意,我们已经使用训练数据集中的最大值对测试数据集进行了归一化,因为我们不应在模型构建过程中使用测试数据集中的任何值。此外,请注意,我们已对输入和输出值都进行了归一化处理。

  1. 现在输入和输出数据集已经准备好,让我们继续并定义模型:
from keras.models import Sequential
from keras.layers import Dense, Dropout
from keras.utils import np_utils
from keras.regularizers import l1
model = Sequential()
model.add(Dense(64, input_dim=13, activation='relu', kernel_regularizer = l1(0.1)))
model.add(Dense(1, activation='relu', kernel_regularizer = l1(0.1)))
model.summary()

模型的总结如下:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/096001a6-4571-48cb-9e30-93b7c2c7ef5f.png

请注意,在模型构建过程中,我们执行了L1正则化,以防止模型在训练数据上过拟合(因为训练数据点数量较少)。

  1. 编译模型以最小化平均绝对误差值:
model.compile(loss='mean_absolute_error', optimizer='adam')
  1. 拟合模型:
history = model.fit(train_data2, train_targets, validation_data=(test_data2, test_targets), epochs=100, batch_size=32, verbose=1)
  1. 计算测试数据集上的平均绝对误差:
np.mean(np.abs(model.predict(test_data2) - test_targets))*50

我们应该注意到,平均绝对误差为*~6.7*单位。

在下一节中,我们将改变损失函数并添加自定义权重,看看是否能够改进平均绝对误差值。

定义自定义损失函数

在前一节中,我们使用了预定义的平均绝对误差loss函数进行优化。在本节中,我们将学习如何定义自定义损失函数来进行优化。

我们将构建的自定义损失函数是一个修改过的均方误差值,其中误差是实际值的平方根与预测值的平方根之间的差异。

自定义损失函数定义如下:

import keras.backend as K
def loss_function(y_true, y_pred):
    return K.square(K.sqrt(y_pred)-K.sqrt(y_true))

现在我们已经定义了loss函数,我们将重新使用前面准备的输入和输出数据集,并且我们将使用我们之前定义的相同模型。

现在,让我们编译模型:

model.compile(loss=loss_function, optimizer='adam')

在前面的代码中,请注意我们将loss值定义为我们之前定义的自定义损失函数—loss_function

history = model.fit(train_data2, train_targets, validation_data=(test_data2, test_targets), epochs=100, batch_size=32, verbose=1)

一旦我们拟合了模型,我们会注意到平均绝对误差约为*~6.5*单位,略低于我们在前一次迭代中使用mean_absolute_error损失函数时的误差。

将新闻文章分类为主题

在之前的案例研究中,我们分析了结构化的数据集,也就是包含变量及其对应值的数据集。在这个案例研究中,我们将处理一个以文本作为输入的数据集,预期的输出是与该文本相关的 46 个可能主题之一。

准备就绪

为了理解执行文本分析的直觉,我们可以考虑 Reuters 数据集,其中每篇新闻文章被分类为 46 个可能主题之一。

我们将采用以下策略来执行我们的分析:

  • 由于数据集可能包含数千个独特的单词,我们将筛选出我们要考虑的单词。

  • 对于这个特定的练习,我们将考虑最常见的前 10,000 个单词。

  • 另一种方法是考虑那些累积起来占数据集所有单词 80%的单词。这确保了所有稀有单词被排除在外。

  • 一旦选定词汇,我们将根据组成的常见词汇对文章进行独热编码。

  • 类似地,我们将对输出标签进行独热编码。

  • 每个输入现在是一个 10,000 维的向量,输出是一个 46 维的向量:

  • 我们将数据集分为训练集和测试集。然而,在代码中,你会注意到我们将使用 Keras 中内置的reuters数据集,该数据集具有内置函数,可以识别最常见的n个词汇,并将数据集拆分为训练集和测试集。

  • 在中间插入一个隐藏层,将输入和输出进行映射。

  • 我们将在输出层执行 softmax 操作,以获得输入属于 46 个类别之一的概率。

  • 由于我们有多个可能的输出,因此我们将使用分类交叉熵损失函数。

  • 我们将编译并训练模型,以衡量其在测试数据集上的准确性。

如何实现…

我们将按如下方式实现之前定义的策略(实现代码时请参考 GitHub 上的Categorizing news articles into topics.ipynb文件):

  1. 导入数据集:
from keras.datasets import reuters
(train_data, train_labels), (test_data, test_labels) = reuters.load_data(num_words=10000)

在上述代码片段中,我们加载了 Keras 中可用的reuters数据集的数据。此外,我们只考虑数据集中最常见的10000个词汇。

  1. 检查数据集:
train_data[0]

加载的训练数据集示例如下:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/40f57912-d7fe-41a3-8840-03871083ff15.png

请注意,前述输出中的数字表示输出中出现的单词的索引。

  1. 我们可以按如下方式提取值的索引:
word_index = reuters.get_word_index()
  1. 向量化输入。我们将以以下方式将文本转换为向量:

    • 对输入词汇进行独热编码——最终生成输入数据集中总共10000列。

    • 如果文本中存在某个词汇,则对应词汇索引的列将显示为 1,其他列将显示为 0。

    • 对文本中的所有唯一词汇执行上述步骤。如果一篇文本有两个唯一词汇,那么将有两列值为 1,其他所有列的值为 0:

import numpy as np
def vectorize_sequences(sequences, dimension=10000):
     results = np.zeros((len(sequences), dimension))
     for i, sequence in enumerate(sequences):
         results[i, sequence] = 1.
     return results

在上述函数中,我们初始化了一个零矩阵,并根据输入序列中的索引值将其填充为 1。

在以下代码中,我们将单词转换为 ID。

x_train = vectorize_sequences(train_data)
x_test = vectorize_sequences(test_data)
  1. 对输出进行独热编码:
from keras.utils.np_utils import to_categorical
one_hot_train_labels = to_categorical(train_labels)
one_hot_test_labels = to_categorical(test_labels)

上述代码将每个输出标签转换为一个长度为46的向量,其中46个值中的一个为 1,其他值为 0,具体取决于标签的索引值。

  1. 定义模型并编译:
from keras.models import Sequential
from keras.layers import Dense
model = Sequential()
model.add(Dense(64, activation='relu', input_shape=(10000,)))
model.add(Dense(64, activation='relu'))
model.add(Dense(46, activation='softmax'))
model.summary()
model.compile(optimizer='adam',loss='categorical_crossentropy',metrics=['accuracy'])

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/849f6764-c6d9-4993-8ffd-7319ebba6bf7.png

请注意,在编译时,我们将loss定义为categorical_crossentropy,因为此处的输出是分类的(输出有多个类别)。

  1. 训练模型:
history = model.fit(X_train, y_train,epochs=20,batch_size=512,validation_data=(X_test, y_test))

上述代码生成了一个模型,该模型在将输入文本分类到正确的主题时准确率为 80%,如下所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/2ef23cd1-8063-4ac8-8614-e647abd39c7f.png

分类常见音频

在前面的章节中,我们已经了解了如何在结构化数据集和非结构化文本数据上执行建模策略。

在本节中,我们将学习如何进行一个分类任务,输入是原始音频。

我们将采用的策略是从输入音频中提取特征,每个音频信号都表示为一个固定数量特征的向量。

提取音频特征的方式有多种,然而在这个练习中,我们将提取与音频文件对应的梅尔频率倒谱系数MFCC)。

一旦我们提取了特征,我们将执行分类任务,这与我们为 MNIST 数据集分类构建模型时非常相似——在那时我们有隐藏层连接输入层和输出层。

在接下来的章节中,我们将在音频数据集上执行分类任务,输出有十个可能的类别。

如何实现…

我们之前定义的策略的代码如下(在实现代码时,请参考 GitHub 上的Audio classification.ipynb文件):

  1. 导入数据集:
import pandas as pd
data = pd.read_csv('/content/train.csv')
  1. 提取每个音频输入的特征:
ids = data['ID'].values
def extract_feature(file_name):
    X, sample_rate = librosa.load(file_name)
    stft = np.abs(librosa.stft(X))
    mfccs = np.mean(librosa.feature.mfcc(y=X,sr=sample_rate, n_mfcc=40).T,axis=0)
    return mfccs

在前面的代码中,我们定义了一个函数,该函数以file_name作为输入,提取与音频文件对应的40个 MFCC,并返回这些特征。

  1. 创建输入和输出数据集:
x = []
y = []
for i in range(len(ids)):     
     try:
         filename = '/content/Train/'+str(ids[i])+'.wav'
         y.append(data[data['ID']==ids[i]]['Class'].values)
         x.append(extract_feature(filename))
     except:
         continue
x = np.array(x)

在前面的代码中,我们逐个处理音频文件,提取其特征并将其存储在输入列表中。类似地,我们将输出类别存储在输出列表中。此外,我们还将把输出列表转换为一个独热编码的类别值:

y2 = []
for i in range(len(y)):
     y2.append(y[i][0])
y3 = np.array(pd.get_dummies(y2))

pd.get_dummies方法的作用与我们之前使用的to_categorical方法非常相似;然而,to_categorical不适用于文本类别(它只适用于数值类型,将其转换为独热编码值)。

  1. 构建并编译模型:
model = Sequential()
model.add(Dense(1000, input_shape = (40,), activation = 'relu'))
model.add(Dense(10,activation='sigmoid'))
from keras.optimizers import Adam
adam = Adam(lr=0.0001)
model.compile(optimizer=adam, loss='categorical_crossentropy', metrics=['acc'])

前述模型的总结如下:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/23de187e-b380-4973-9db6-ef8af7130d91.png

  1. 创建训练和测试数据集,然后拟合模型:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(x, y3, test_size=0.30,random_state=10)
model.fit(X_train, y_train, epochs=100, batch_size=32, validation_data=(X_test, y_test), verbose = 1)

一旦模型拟合完成,您会注意到模型在将音频正确分类到相应类别时的准确率为 91%。

股票价格预测

在前面的章节中,我们学习了如何使用神经网络进行音频、文本和结构化数据分析。在这一节中,我们将学习如何通过预测股票价格的案例研究来进行时间序列分析。

准备中

为了预测股票价格,我们将执行以下步骤:

  1. 按照从最旧到最新的顺序排列数据集。

  2. 将前五个股票价格作为输入,第六个股票价格作为输出。

  3. 将窗口向前滑动,这样在下一个数据点中,第二个到第六个数据点作为输入,第七个数据点作为输出,依此类推,直到达到最后一个数据点。

  4. 由于我们预测的是一个连续数值,本次的loss函数将是均方误差值。

此外,我们还将尝试将文本数据与历史数值数据整合,以预测第二天的股票价格。

如何做…

上述策略的代码如下(在实现代码并推荐数据集时,请参阅 GitHub 中的Chapter 3 - stock price prediction.ipynb文件):

  1. 导入相关的包和数据集:
import pandas as pd
data2 = pd.read_csv('/content/stock_data.csv')
  1. 准备数据集,其中输入是过去五天的股票价格,输出是第六天的股票价格:
x= []
y = []
for i in range(data2.shape[0]-5):
 x.append(data2.loc[i:(i+4)]['Close'].values)
 y.append(data2.loc[i+5]['Close'])
import numpy as np
x = np.array(x)
y = np.array(y)
  1. 准备训练和测试数据集,构建模型,编译并拟合它:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(x, y, test_size=0.30,random_state=10)

构建模型并编译它:


from keras.layers import Dense
from keras.models import Sequential, Model
model = Sequential()
model.add(Dense(100, input_dim = 5, activation = 'relu'))
model.add(Dense(1,activation='linear'))
model.compile(optimizer='adam', loss='mean_squared_error')

上述代码结果总结如下:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/19ad486e-b866-4549-8fc7-a842143634a8.png

model.fit(X_train, y_train, epochs=100, batch_size=64, validation_data=(X_test, y_test), verbose = 1)

一旦我们拟合了模型,我们应该注意到,预测股票价格的均方误差值为*~$360*,或者预测股票价格时的误差为 ~$18。

请注意,以这种方式预测股票价格存在一个陷阱。然而,这将在 RNN 应用章节中处理。

目前,我们将专注于学习神经网络在不同场景中的多种用途。

在下一部分,我们将了解如何将数值数据与新闻标题的文本数据整合到一个模型中。

利用功能性 API

在这一部分,我们将通过将历史价格数据与我们预测的公司最新头条数据结合,继续提高股票价格预测的准确性。

我们将采用以下策略来整合来自多个来源的数据——结构化(历史价格)数据和非结构化(头条)数据:

  • 我们将以类似于将新闻文章分类为主题的方式,将非结构化文本转换为结构化格式。

  • 我们将通过神经网络传递结构化文本格式,并提取隐藏层输出。

  • 最后,我们将隐藏层的输出传递到输出层,输出层有一个节点。

  • 以类似的方式,我们将输入的历史价格数据传递给神经网络,提取隐藏层值,然后将其传递到输出层,输出层有一个输出单元。

  • 我们将每个单独神经网络操作的输出相乘,以提取最终输出。

  • 最终输出的平方误差值现在将被最小化。

如何做…

前述策略的代码如下:

  1. 让我们从《卫报》提供的 API 获取头条数据,如下所示:
from bs4 import BeautifulSoup
import urllib, json

dates = []
titles = []
for i in range(100):
     try:
         url = 'https://content.guardianapis.com/search?from-date=2010-01-01&section=business&page-size=200&order-by=newest&page='+str(i+1)+'&q=amazon&api-key=207b6047-a2a6-4dd2-813b-5cd006b780d7'
         response = urllib.request.urlopen(url)
         encoding = response.info().get_content_charset('utf8')
         data = json.loads(response.read().decode(encoding))
     for j in range(len(data['response']['results'])):
         dates.append(data['response']['results'][j]['webPublicationDate'])
         titles.append(data['response']['results'][j]['webTitle']) 
     except:
         break
  1. 一旦titlesdates被提取出来,我们将预处理数据,将date值转换为date格式,如下所示:
import pandas as pd
data = pd.DataFrame(dates, titles)
data['date']=data['date'].str[:10]
data['date']=pd.to_datetime(data['date'], format = '%Y-%m-%d')
data = data.sort_values(by='date')
data_final = data.groupby('date').first().reset_index()
  1. 既然我们已经得到了每个日期的最新头条数据,我们将整合这两个数据源,如下所示:
data2['Date'] = pd.to_datetime(data2['Date'],format='%Y-%m-%d')
data3 = pd.merge(data2,data_final, left_on = 'Date', right_on = 'date', how='left')
  1. 一旦数据集合并,我们将继续对文本数据进行归一化处理,以去除以下内容:

    • 将文本中的所有单词转换为小写,以便像Texttext这样的单词被视为相同。

    • 去除标点符号,以便像text.text这样的单词被视为相同。

    • 去除如aandthe等停用词,因为这些词对文本的上下文贡献不大:

import nltk
import re
nltk.download('stopwords')
stop = nltk.corpus.stopwords.words('english')
def preprocess(text):
     text = str(text)
     text=text.lower()
     text=re.sub('[⁰-9a-zA-Z]+',' ',text)
     words = text.split()
     words2=[w for w in words if (w not in stop)]
     words4=' '.join(words2)
     return(words4)
data3['title'] = data3['title'].apply(preprocess)
  1. 用连字符-替换title列中的所有空值:
data3['title']=np.where(data3['title'].isnull(),'-','-'+data3['title'])

现在我们已经预处理了文本数据,接下来为每个单词分配一个 ID。一旦我们完成了这个分配,就可以进行类似于在新闻文章分类部分中所做的文本分析,具体如下:

docs = data3['title'].values

from collections import Counter
counts = Counter()
for i,review in enumerate(docs):
     counts.update(review.split())
words = sorted(counts, key=counts.get, reverse=True)
vocab_size=len(words)
word_to_int = {word: i for i, word in enumerate(words, 1)}
  1. 鉴于我们已经对所有单词进行了编码,接下来让我们用它们在原文中的对应文本替换:
encoded_docs = []
for doc in docs:
     encoded_docs.append([word_to_int[word] for word in doc.split()])

def vectorize_sequences(sequences, dimension=vocab_size):
     results = np.zeros((len(sequences), dimension+1))
     for i, sequence in enumerate(sequences):
         results[i, sequence] = 1.
     return results
vectorized_docs = vectorize_sequences(encoded_docs)

现在我们已经对文本进行了编码,理解了将如何整合这两个数据源。

  1. 首先,我们将准备训练集和测试集,如下所示:
x1 = np.array(x)
x2 = np.array(vectorized_docs[5:])
y = np.array(y)

X1_train = x1[:2100,:]
X2_train = x2[:2100, :]
y_train = y[:2100]
X1_test = x1[2100:,:]
X2_test = x2[2100:,:]
y_test = y[2100:]

通常,当期望有多个输入或多个输出时,我们会使用功能性 API。在这种情况下,由于有多个输入,我们将利用功能性 API。

  1. 本质上,功能性 API 去除了构建模型的顺序过程,具体操作如下。以向量化的文档作为输入,并从中提取输出:
input1 = Input(shape=(2406,))
input1_hidden = (Dense(100, activation='relu'))(input1)
input1_output = (Dense(1, activation='tanh'))(input1_hidden)

在前面的代码中,请注意我们没有使用顺序建模过程,而是通过Dense层定义了各种连接。

请注意,输入的形状是2406,因为过滤过程后剩下了2406个唯一单词。

  1. 以之前的5个股票价格作为输入,构建模型:
input2 = Input(shape=(5,))
input2_hidden = (Dense(100, activation='relu'))(input2)
input2_output = (Dense(1, activation='linear'))(input2_hidden)
  1. 我们将对两个输入的输出进行相乘:
from keras.layers import multiply
out = multiply([model, model2])
  1. 现在我们已经定义了输出,接下来将按如下方式构建模型:
model = Model([input1, input2], out)
model.summary()

请注意,在前面的步骤中,我们使用了Model层来定义输入(作为列表传递)和输出:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/67dff3a9-1e83-4591-9096-e379635afd33.png

上述输出的可视化结果如下:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-keras-cb/img/e66fdee9-4b95-4101-b450-29712e649413.png

  1. 编译并拟合模型:
model.compile(optimizer='adam', loss='mean_squared_error')
model.fit(x=[X2_train, X1_train], y=y_train, epochs=100,batch_size = 32, validation_data = ([X2_test, X1_test], y_test))

上述代码的结果是平均平方误差为*~5000*,并清楚地表明模型存在过拟合现象,因为训练集的损失远低于测试集的损失。

可能,过拟合是由于向量化文本数据中维度过高所导致的。我们将在第十一章中探讨如何改进这一点,构建递归神经网络

为行定义权重

预测房价食谱中,我们了解了如何定义自定义损失函数。然而,我们目前还无法为某些行分配更高的权重。 (我们曾在一个信用违约预测案例研究中做过类似的练习,当时我们为一个类别分配了较高的权重;然而那是一个分类问题,而我们当前解决的问题是一个连续变量预测问题。)

在本节中,我们将为每一行定义权重,并将其传递给我们将要定义的custom_loss函数。

我们将继续使用在股票价格预测食谱中分析过的相同数据集。

如何做到……

  1. 为了在行级别上指定权重,我们将修改训练和测试数据集,使得按顺序排列后的前2100个数据点属于训练数据集,其余的数据点属于测试数据集:
X_train = x[:2100,:,:]
y_train = y[:2100]
X_test = x[2100:,:,:]
y_test = y[2100:]
  1. 输入中的一行如果较为近期发生,则会有较高的权重,反之则权重较低:
weights = np.arange(X_train.shape[0]).reshape((X_train.shape[0]),1)/2100

前面的代码块为初始数据点分配了较低的权重,为最近发生的数据点分配了较高的权重。

现在我们已经为每一行定义了权重,我们将在自定义损失函数中包含它们。请注意,在这种情况下,我们的自定义损失函数将包括输出的预测值和实际值,以及需要为每一行分配的权重。

  1. 部分方法使我们能够传递比实际值和预测值更多的变量给自定义损失函数:
import keras.backend as K
from functools import partial
  1. 为了将weights传递给custom_loss函数,我们将使用部分函数,将custom_lossweights作为参数传递给第 7 步。在接下来的代码中,我们定义了custom_loss函数:
def custom_loss_4(y_true, y_pred, weights):
     return K.square(K.abs(y_true - y_pred) * weights)
  1. 考虑到我们构建的模型有两个输入——输入变量和每一行对应的权重,我们将首先定义这两个输入的shape如下:
input_layer = Input(shape=(5,1))
weights_tensor = Input(shape=(1,))
  1. 现在我们已经定义了输入,让我们按以下方式初始化接受两个输入的model
inp1 = Dense(1000, activation='relu')(input_layer)
out = Dense(1, activation='linear')(i3)
model = Model([input_layer, weights_tensor], out)
  1. 现在我们已经初始化了model,我们将按以下方式定义优化函数:
cl4 = partial(custom_loss_4, weights=weights_tensor)

在前述场景中,我们指定需要最小化custom_loss_4函数,并且向自定义损失函数提供了一个额外的变量(weights_tensor)。

  1. 最后,在拟合模型之前,我们还将为每一行提供对应测试数据集的weights。考虑到我们正在预测这些值,给某些行提供较低的权重是没有意义的,因为测试数据集并没有提供给模型。然而,我们只会在使用我们定义的模型进行预测时指定这一点(该模型接受两个输入):
test_weights = np.ones((156,1))
  1. 一旦我们指定了测试数据的weights,我们将继续按照以下方式拟合模型:
model = Model([input_layer, weights_tensor], out)
model.compile(adam, cl4)
model.fit(x=[X_train, weights], y=y_train, epochs=300,batch_size = 32, validation_data = ([X_test, test_weights], y_test))

上述结果导致测试数据集的损失与我们在前一节中看到的结果大不相同。我们将在第十一章,《构建循环神经网络》一章中更详细地探讨这一原因。

在实现上述模型时,必须格外小心,因为它存在一些陷阱。然而,通常建议在进行充分的尽职调查后再实现预测股价波动的模型。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值