R-神经网络-全-

R 神经网络(全)

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

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

神经网络是解决复杂计算问题的最具吸引力的机器学习模型之一,具有高效性。神经网络被广泛应用于人工智能和机器学习的不同领域,以解决各种问题。

本书解释了神经网络的细分领域,并为你提供了开始深入学习高级主题的基础。书中首先介绍了使用neuralnet包进行神经网络设计;然后,你将构建扎实的知识,了解神经网络如何从数据中学习及其背后的原理。本书涵盖了多种类型的神经网络,包括递归神经网络和卷积神经网络。你不仅将学习如何训练神经网络,还将探索这些网络的泛化。随后,我们将深入探讨如何结合不同的神经网络模型,并结合实际案例进行工作。

到本书结束时,你将学习如何在应用中实现神经网络模型,并通过书中的实践示例来帮助你。

本书涵盖的内容

第一章,神经网络与人工智能概念,介绍了人工神经网络ANN)和人工智能AI)的基本理论概念。它展示了 ANN 和 AI 的简单应用,并利用数学概念进行阐释。同时也涉及了一些 R 中 ANN 函数的介绍。

第二章,神经网络中的学习过程,展示了如何在图形模型中进行精确推理,并将应用展示为专家系统。推理算法是学习和使用这些类型模型的基础组件。读者必须至少理解它们的使用方式,并了解它们的工作原理。

第三章,使用多层神经网络进行深度学习,是关于理解深度学习和神经网络在深度学习中的应用。本章详细介绍了使用 R 包的实现过程,涵盖了深度学习中设置的多个隐藏层,并使用实际数据集帮助理解该实现。

第四章,感知器神经网络——基本模型,帮助理解什么是感知器以及可以用它构建的应用程序。本章介绍了使用 R 实现感知器的过程。

第五章,在 R 中训练和可视化神经网络,展示了使用数据集训练神经网络的另一个示例。它还通过使用 R 中的plot()函数,提供了输入层、隐藏层和输出层的图形表示,帮助更好地理解神经网络。

第六章,递归神经网络和卷积神经网络,介绍了递归神经网络RNN)和卷积神经网络CNN)以及它们在 R 中的实现。提出了多个示例以帮助理解基本概念。

第七章,神经网络的应用案例—高级话题,展示了来自不同领域的神经网络应用,以及神经网络如何在人工智能领域中使用。这将帮助读者理解神经网络算法的实际应用。读者还可以通过使用不同的数据集并运行 R 代码进一步提升自己的技能。

本书所需内容

本书专注于 R 环境中的神经网络。我们使用了 R 版本 3.4.1 来构建各种应用,并使用了 R 的开源及企业级专业软件 RStudio 版本 1.0.153。我们重点介绍如何以最佳方式利用各种 R 库来构建实际应用。为此,我们尽力保持所有代码简洁易读。我们认为,这将使读者能够轻松理解代码,并在不同场景中方便地使用它。

本书的目标读者

本书适用于具有统计学背景、熟悉 R 语言并希望通过神经网络从复杂数据中获得更好结果的读者。如果您对人工智能和深度学习感兴趣并希望提升技能,那么这本书正是您需要的!

约定

本书中,您将看到多种文本样式,用于区分不同类型的信息。以下是这些样式的一些示例及其含义的解释。

文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 账户名均如下所示:“R 中的这一行包含了neuralnet()库。”

任何命令行输入或输出均按如下方式编写:

mydata=read.csv('Squares.csv',sep=",",header=TRUE)
mydata
attach(mydata)
names(mydata)

新术语重要单词以粗体显示。您在屏幕上看到的词语,例如在菜单或对话框中出现的词语,会以以下方式出现在文本中:“帮助浏览器中的参考页面”。

警告或重要提示会显示在类似这样的框中。

提示和技巧会以这种方式出现。

读者反馈

我们始终欢迎读者反馈。请告诉我们您对本书的看法——您喜欢或不喜欢的部分。读者的反馈对我们非常重要,因为它帮助我们开发出您能真正从中受益的书籍。

要向我们提供一般反馈,只需发送电子邮件至feedback@packtpub.com,并在邮件主题中提及书名。

如果您在某个主题上有专长,并且有兴趣编写或参与编写书籍,请查看我们的作者指南,网址为www.packtpub.com/authors

客户支持

现在,您是一本 Packt 书籍的骄傲拥有者,我们为您准备了一些资源,帮助您最大化地利用您的购买。

下载示例代码

您可以从您的账户中下载此书的示例代码文件,网址是www.packtpub.com。如果您从其他地方购买了此书,可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。您可以按照以下步骤下载代码文件:

  1. 使用您的电子邮件地址和密码登录或注册我们的网站。

  2. 将鼠标指针悬停在顶部的“SUPPORT”标签上。

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

  4. 在搜索框中输入书名。

  5. 选择您要下载代码文件的书籍。

  6. 从下拉菜单中选择您购买此书的来源。

  7. 点击“代码下载”。

文件下载完成后,请确保使用以下最新版本的工具解压文件夹:

  • WinRAR / 7-Zip for Windows

  • Zipeg / iZip / UnRarX for Mac

  • 7-Zip / PeaZip for Linux

本书的代码包也托管在 GitHub 上,网址是github.com/PacktPublishing/Neural-Networks-with-R。我们还提供其他书籍和视频的代码包,您可以在github.com/PacktPublishing/查看。快去看看吧!

勘误

尽管我们已尽力确保内容的准确性,但难免会出现错误。如果您在我们的书籍中发现错误——可能是文本或代码中的错误——我们将不胜感激,如果您能将其报告给我们。这样,您可以帮助其他读者避免困扰,并帮助我们改进此书的后续版本。如果您发现任何勘误,请访问www.packtpub.com/submit-errata报告,选择您的书籍,点击“勘误提交表单”链接,并输入勘误的详细信息。一旦您的勘误得到验证,您的提交将被接受,勘误将被上传到我们的网站,或添加到该书籍勘误列表的勘误部分。要查看已提交的勘误,请访问www.packtpub.com/books/content/support,并在搜索框中输入书名。所需信息将显示在勘误部分。

盗版

网络上的版权材料盗版问题在所有媒体中都是一个持续存在的问题。在 Packt,我们非常重视版权和许可的保护。如果你在互联网上遇到任何形式的我们作品的非法复制,请立即提供其网址或网站名称,以便我们采取措施。请通过copyright@packtpub.com与我们联系,并附上涉嫌盗版材料的链接。感谢你帮助我们保护作者的权益,以及我们为你带来有价值内容的能力。

问题

如果你在本书的任何方面遇到问题,可以通过questions@packtpub.com联系我们,我们将尽力解决问题。

第一章:神经网络与人工智能概念

通过几百年来的科学与哲学研究,已经识别出一些特殊的机制,它们构成人类智能的基础。受其运作的启发,人类成功创造出了部分模仿这些机制的机器。问题在于它们尚未能成功地模仿并整合所有这些机制,因此我们现有的人工智能AI)系统在很大程度上是不完整的。

这种机器改进的决定性一步来自所谓的人工神经网络ANNs)的使用,这些网络从调节自然神经网络的机制出发,计划模拟人类思维。现在,软件可以模仿赢得国际象棋比赛或将文本翻译成另一种语言的机制,并遵循其语法规则。

本章介绍了 ANN 和 AI 的基本理论概念。预期您对以下内容有基础理解:

  • 基础的高中数学;微分学和如 sigmoid 等函数

  • R 编程及 R 库的使用

我们将介绍神经网络的基础,并使用 R 尝试一个模型。本章是神经网络的基础,所有后续章节都将基于此。

本章将涵盖以下主题:

  • ANN 概念

  • 神经元、感知器和多层神经网络

  • 偏差、权重、激活函数和隐藏层

  • 前向传播和反向传播方法

  • 图形处理单元GPU)的简要概述

在本章结束时,您将能够识别 R 提供的不同神经网络算法和工具,以便处理它们。

介绍

大脑是人体最重要的器官,是我们进行所有功能处理的中央处理单元。它仅重 1.5 公斤,却有约 860 亿个神经元。神经元被定义为传递神经冲动或电化学信号的细胞。大脑是一个复杂的神经网络,通过一系列相互连接的神经元处理信息。理解大脑功能一直是一个挑战;然而,随着计算技术的进步,我们现在可以人工编程神经网络。

人工神经网络(ANN)的学科源自模仿人类大脑功能的思想,这正是试图解决问题的地方。传统方法及其随后的应用在明确的技术环境中得到了克服。

人工智能或机器智能是一个研究领域,旨在赋予计算机认知能力,使其能够进行学习和解决问题的编程。其目标是模拟具有类似人类智能的计算机。人工智能无法完全模仿人类智能;计算机只能被编程去执行人类大脑的某些方面。

机器学习是人工智能(AI)的一个分支,帮助计算机根据输入数据自我编程。机器学习赋予人工智能基于数据进行问题解决的能力。人工神经网络(ANNs)是机器学习算法的一个例子。

深度学习DL)是一种复杂的神经网络集合,具有更多的处理层,能够发展出更高水平的抽象。它们通常用于复杂的任务,如图像识别、图像分类和手写识别。

大多数观众认为神经网络难以学习,并把它当作一个“黑箱”来使用。本书的目的是打开这个“黑箱”,帮助读者通过 R 语言的实现来学习神经网络的内部工作原理。通过工作知识,我们可以看到神经网络在以下图示中展示的许多应用场景,能够大大提升其价值:

神经网络的灵感来源

神经网络的灵感来自于人类大脑的工作方式。人类大脑通过人类感官(特别是视觉)传递的数据来处理大量信息。神经元在处理过程中,通过电信号在它们之间传递,并应用翻转逻辑,就像信号传输时门的开闭。下图展示了神经元的结构:

每个神经元的主要组成部分是:

  • 树突:每个神经元的输入点,接收来自网络中其他神经元的电信号输入。

  • 细胞体:它根据树突输入生成推论,并决定采取什么行动。

  • 轴突末端:它们以电信号的形式将输出传输到下一个神经元。

每个神经元只有在信号超过某一特定阈值时才会处理信号。神经元要么触发,要么不触发;要么是0,要么是1

人工智能曾是科幻电影和小说书籍中的一个领域。人工神经网络(ANNs)自 20 世纪 50 年代以来就已经存在,但由于计算架构和性能的进步,过去 10 年它们变得更加主流。计算机处理能力的重大进展导致了:

  • 大规模并行处理

  • 分布式表示与计算

  • 学习和泛化能力

  • 容错性

  • 低能耗

在数值计算和符号操作领域,在集中的架构上解决问题时,现代计算机已经在很大程度上超越了人类。它们在模式识别、噪声降低和优化领域仍有所滞后。例如,一个幼儿能够在人群中认出自己的妈妈,但使用集中式架构的计算机却做不到这一点。

这就是大脑的生物神经网络超越机器的地方,因此激发了开发一种松散的、去中心化的结构,模拟大脑的灵感。

人工神经网络(ANN)是一个大规模并行计算系统,由大量简单的处理器和许多互连组成。

全球领先的新闻机构之一《卫报》利用大数据对档案进行数字化,通过上传他们所有档案的快照。然而,用户复制内容并在其他地方使用是这里的限制。为了解决这个问题,可以使用人工神经网络(ANN)进行文本模式识别,将图像转换为文本文件,再根据最终用户的需求转换成任何格式。

神经网络是如何工作的?

类似于生物神经元结构,人工神经网络将神经元定义为一个中央处理单元,它执行数学运算,将一组输入生成一个输出。神经元的输出是输入的加权和加上偏置的函数。每个神经元执行一个非常简单的操作,当接收到的总信号超过激活阈值时就会被激活,如下图所示:

整个神经网络的功能就是计算所有神经元的输出,这完全是一个确定性的计算过程。从本质上讲,ANN 是一组数学函数的逼近。我们现在将介绍与 ANN 相关的新术语:

  • 输入层

  • 隐藏层

  • 输出层

  • 权重

  • 偏置

  • 激活函数

分层方法

任何神经网络处理框架都有以下结构:

这里有一组输入,一个处理器和一组输出。这种分层方法也被神经网络所采用。输入层由输入组成,执行处理的 中间层 被称为 隐藏层,而 输出层 形成输出层。

我们的神经网络架构也基于相同的原理。隐藏层有魔力,它将输入转换为所需的输出。理解隐藏层需要了解权重、偏置和激活函数,这是我们接下来讨论的话题。

权重和偏置

在人工神经网络(ANN)中,权重是将输入转化为影响输出的最重要因素。这类似于线性回归中的斜率,其中权重被乘以输入,以加和形成输出。权重是数值参数,决定了每个神经元对其他神经元的影响强度。

对于一个典型的神经元,如果输入为 x[1]x[2]x[3],那么应用于它们的突触权重分别表示为 w[1]w[2]w[3]

输出为

其中 i 是从 1 到输入数量的编号。

简单来说,这是一个矩阵乘法过程,用来得到加权和。

偏置就像线性方程中加上的截距。它是一个额外的参数,用于调整输出,同时与神经元输入的加权和一起作用。

神经元所执行的处理可以表示为:

对此输出应用一个函数,称为激活函数。下一层的输入是前一层神经元的输出,如下图所示:

神经网络训练

训练是通过向网络提供一些样本数据,并修改权重以更好地逼近期望的功能。

训练主要有两种类型:监督学习和无监督学习。

监督学习

我们向神经网络提供输入和期望的输出。网络对输入的响应被测量,权重被修改,以减少实际输出和期望输出之间的差异。

无监督学习

我们只提供输入。神经网络调整自身的权重,使得相似的输入产生相似的输出。网络识别输入中的模式和差异,而不依赖外部帮助。

训练轮次(Epoch)

一次迭代或遍历网络过程,即给网络提供输入并更新网络权重的过程,称为训练轮次(epoch)。它是前馈和反向传播更新权重的完整过程,也是对整个数据集的完整读取。

通常,需要进行多次训练轮次,有时达到数万次,才能高效地训练神经网络。我们将在后续章节中了解更多关于训练轮次的内容。

激活函数

神经网络处理的抽象主要通过激活函数来实现。激活函数是一个数学函数,它将输入转化为输出,并增加神经网络处理的魔力。没有激活函数,神经网络的工作就像线性函数。线性函数的输出与输入成正比,例如:

线性函数是一次多项式。简单来说,它是一条没有任何曲线的直线。

然而,大多数神经网络试图解决的问题本质上是非线性和复杂的。为了实现非线性,使用了激活函数。非线性函数是高次多项式函数,例如:

非线性函数的图形是曲线的,增加了复杂性因素。

激活函数赋予神经网络非线性特性,使其成为真正的通用函数逼近器。

不同的激活函数

神经网络有许多可用的激活函数。我们将在这里介绍其中一些。

线性函数

最简单的激活函数,是在神经网络问题中常用于输出层的激活函数,它由以下公式表示:

输出与输入相同,函数定义在范围(-infinity, +infinity)内。下图展示了一个线性激活函数:

单位阶跃激活函数

单位阶跃激活函数是神经网络中使用频繁的特性。对于负数参数,输出值为0,对于正数参数,输出值为1。该函数如下所示:

范围在(0,1)之间,输出是二元的。这些类型的激活函数在二进制方案中非常有用。当我们想要将输入模型分类到两个组中的一个时,可以使用带有单位阶跃激活函数的二进制编译器。单位阶跃激活函数如下图所示:

Sigmoid

sigmoid函数是一个数学函数,产生一个 sigmoidal 曲线;其特征曲线呈S形状。这是最早且常用的激活函数。它将输入压缩到01之间的任何值,使模型在本质上具有逻辑性。该函数是由以下公式定义的逻辑函数的特例:

下图显示了一个具有S形状的 sigmoid 曲线:

双曲正切函数

另一个非常流行并广泛使用的激活特性是tanh函数。如果你看接下来的图形,你会发现它看起来与sigmoid非常相似;事实上,它是一个缩放过的sigmoid函数。这是一个非线性函数,定义在(-1, 1)的范围内,因此你不必担心激活值爆炸的问题。需要澄清的一点是,tanh的梯度比sigmoid更强(其导数更陡峭)。在sigmoidtanh之间做决定将取决于你的梯度强度需求。像sigmoid一样,tanh也有缺失斜率的问题。该函数由以下公式定义:

下图显示了一个双曲正切激活函数:

这看起来与sigmoid非常相似;事实上,它是一个缩放过的sigmoid函数。

修正线性单元(ReLU)

修正线性单元ReLU)是自 2015 年以来使用最广泛的激活函数。它是一个简单的条件,并且相较于其他函数有优势。该函数由以下公式定义:

下图显示了一个 ReLU 激活函数:

输出的范围在0到无穷大之间。ReLU 在计算机视觉和语音识别中使用深度神经网络时找到了应用。还有许多其他的激活函数,但我们在这里涵盖了最重要的几个。

应该使用哪些激活函数?

由于神经网络需要支持非线性和更复杂的特性,所使用的激活函数必须足够强大,满足以下要求:

  • 它应该是可微分的;我们将在反向传播中看到为什么需要微分。它不应导致梯度消失。

  • 它应该是简单且快速的处理。

  • 它不应是零中心的。

sigmoid是最常用的激活函数,但它有以下几个问题:

  • 由于使用的是逻辑模型,计算过程非常耗时且复杂。

  • 它会导致梯度消失,某个时刻神经元之间没有信号传递。

  • 它收敛较慢。

  • 它不是零中心的。

这些缺点通过 ReLU 得到了改善。ReLU 简单且处理速度更快。它没有消失梯度问题,并且相比于sigmoidtanh函数,取得了显著的改进。ReLU 是神经网络和深度学习问题中最常用的激活函数。

ReLU 用于隐藏层,而输出层可以使用softmax函数来解决逻辑回归问题,线性函数用于回归问题。

感知器和多层架构

感知器是一个单一神经元,用于将一组输入分类为两类之一(通常是1-1)。如果输入是以网格形式出现,感知器可以用来识别形状的视觉图像。感知器通常使用阶跃函数,如果输入的加权和超过某个阈值,返回1,否则返回0

当多个感知器层组合在一起时,它们形成一个多层架构,这提供了神经网络处理所需的复杂性。多层感知器MLPs)是最广泛使用的神经网络架构。

前向传播和反向传播

从输入层到隐藏层(或多个隐藏层)再到输出层的处理过程称为前向传播。每一层都应用sum(inputweights)+bias*,然后激活函数的值被传递到下一层。下一层可以是另一个隐藏层,也可以是输出层。神经网络的构建使用大量的隐藏层,从而形成深度神经网络DNN)。

一旦在最后一层(输出层)得到了输出,我们计算误差(预测输出减去原始输出)。这个误差用于修正前向传播中使用的权重和偏差。这里需要使用导数函数。需要改变的权重量由梯度下降决定。

反向传播过程使用每个神经元激活函数的偏导数来识别每个输入权重的梯度(或坡度)。梯度指示权重变化时,误差将如何剧烈地增加或减少。反向传播会不断地调整权重,直到误差减少到最大程度,减小量由学习率决定。

学习率是一个标量参数,类似于数值积分中的步长,用于设置调整速率以加快误差的减少。学习率在反向传播过程中用于调整权重和偏差。

学习率越高,算法减少误差的速度越快,训练过程也越快:

神经网络和激活函数的逐步说明

我们将通过逐步的方式,理解带有单隐藏层的前向和反向传播。输入层有一个神经元,输出将解决一个二元分类问题(预测 0 或 1)。下图展示了带有单隐藏层的前向和反向传播:

接下来,让我们详细逐步分析网络训练过程中需要执行的所有操作:

  1. 将输入作为矩阵处理。

  2. 初始化权重和偏差为随机值。这是一次性操作,我们将通过误差传播过程不断更新这些值。

  3. 对每个训练模式重复步骤 4 到 9(以随机顺序呈现),直到误差最小化。

  4. 将输入应用到网络中。

  5. 计算从输入层到输出层,每个神经元的输出,包括隐藏层(如果有)。

  6. 计算输出的误差:实际值减去预测值。

  7. 使用输出误差计算前面层的误差信号。激活函数的偏导数用于计算误差信号。

  8. 使用误差信号计算权重调整。

  9. 应用权重调整。

步骤 4 和 5 是前向传播,步骤 6 到 9 是反向传播。

学习率是控制权重更新量的配置参数。

完整的来回传递过程称为训练周期时代。更新后的权重和偏差将在下一个周期中使用。我们会不断进行递归训练,直到误差极小。

本书将进一步详细讲解前向传播和反向传播。

前馈网络与反馈网络

神经网络中信号的流动可以是单向的,也可以是循环的。在第一种情况下,我们称神经网络架构为前馈网络,因为输入信号被输入到输入层后,经过处理后会传递到下一层,就像下图所示。MLP(多层感知机)和径向基函数也是前馈网络的典型例子。下图展示了一个 MLP 架构:

当神经网络存在某种内部反馈时,意味着信号被反馈到已经接收和处理过该信号的神经元或层中,那么网络就是反馈型网络,如下图所示:

在网络中添加循环的特别原因是产生动态行为,尤其是在网络处理涉及时间序列或模式识别的任务时,这些任务需要一个内部记忆来加强学习过程。然而,这类网络尤其难以训练,最终可能会无法学习。大多数反馈型网络是单层的,如Elman网络和Hopfield网络,但也可以构建递归的多层网络,如回声网络和递归 MLP 网络。

梯度下降

梯度下降是任何学习模型中用于错误校正的迭代方法。对于神经网络在反向传播期间,通过误差乘以激活函数的导数来迭代更新权重和偏置的过程是梯度下降的方法。最陡下降步长被上一步的相似大小替换。梯度基本上定义为曲线的斜率,是激活函数的导数:

在每一步推导梯度下降的目标是找到全局成本最低点,其中误差最小。这是模型对数据拟合良好且预测更准确的地方。

梯度下降可以在完整批次或随机中执行。在完整批次梯度下降中,为整个训练数据集计算梯度,而随机梯度下降 (SGD) 则使用单个样本进行梯度计算。它还可以采用小批量并执行计算。SGD 的一个优点是更快地计算梯度。

神经网络的分类

人工神经网络的基本基础是相同的,但在其演变过程中设计了各种神经网络模型。以下是一些人工神经网络模型:

  • 自适应线性元素 (ADALINE),是一个简单的感知器,只能解决线性问题。每个神经元都接收输入的加权线性和,并将其传递给双极函数,该函数根据总和产生+1-1。该函数检查传递的输入总和,如果净值>= 0,则为+1,否则为-1

  • 多个 ADALINE (MADALINE),是 ADALINE 单元的多层网络。

  • 感知器是单层神经网络(单个神经元或单元),其中输入是多维的(向量),输出是输入权重和的函数。

  • 径向基函数网络是一种人工神经网络,其中径向基函数用作激活函数。网络输出是输入和一些神经元参数的径向基函数的线性组合。

  • 前馈是神经网络中最简单的形式。数据在各层之间处理,没有任何循环或循环。我们将在本书中研究以下前馈网络:

    • 自编码器

    • 概率的

    • 时间延迟

    • 卷积的

  • 递归神经网络 (RNNs),与前馈网络不同,将数据前向和后向传播,从后续处理阶段向早期阶段传播。以下是 RNN 的类型;我们将在后续章节中学习它们:

    • 霍普菲尔德网络

    • 波尔兹曼机器

    • 自组织映射 (SOMs)

    • 双向联想记忆 (BAM)

    • 长短期记忆 (LSTM)

以下图片描述了(a) 循环神经网络(b) 前向神经网络

使用 R 神经网络库进行简单示例 - neuralnet()

考虑一个简单的数据集,包含数字的平方,我们将使用它来训练 R 中的 neuralnet 函数,然后测试构建的神经网络的准确性:

输入 输出
0 0
1 1
2 4
3 9
4 16
5 25
6 36
7 49
8 64
9 81
10 100

我们的目标是设置权重和偏置,以便模型能够完成这里所做的工作。输出需要基于输入的函数进行建模,这个函数未来可以用于根据输入确定输出:

######################################################################### 
###Chapter 1 - Introduction to Neural Networks - using R ################ 
###Simple R program to build, train and test neural Networks############# 
######################################################################### 
 #Choose the libraries to use
library("neuralnet")

#Set working directory for the training data
setwd("C:/R")
getwd()

#Read the input file
mydata=read.csv('Squares.csv',sep=",",header=TRUE)
mydata
attach(mydata)
names(mydata)

#Train the model based on output from input
model=neuralnet(formula = Output~Input, 
 data = mydata, 
 hidden=10, 
 threshold=0.01 )
print(model)

#Lets plot and see the layers
plot(model)

#Check the data - actual and predicted
final_output=cbind (Input, Output, 
 as.data.frame(model$net.result) )
colnames(final_output) = c("Input", "Expected Output", 
 "Neural Net Output" )
print(final_output)
#########################################################################

让我们逐行查看代码

为了理解上面所提到的所有代码步骤,我们将逐一详细讲解。如果某些步骤目前看起来不清楚,不用担心,您可以在接下来的示例中深入了解。首先将显示代码片段,然后给出相应的解释:

library("neuralnet")

该行代码在我们的程序中包含了 neuralnet() 库。neuralnet()综合 R 档案网络CRAN)的一部分,CRAN 包含了许多用于各种应用的 R 库。

mydata=read.csv('Squares.csv',sep=",",header=TRUE)
mydata
attach(mydata)
names(mydata)

| 该代码读取以 ,(逗号)为分隔符的 CSV 文件,文件的第一行为表头。使用 names() 可以显示文件的表头。|

model=neuralnet(formula = Output~Input, 
 data = mydata, 
 hidden=10, 
 threshold=0.01 )

| 输出与输入的训练过程发生在此。neuralnet() 函数接收输出和输入的列名(output~input),数据集,隐藏层中的神经元数目,以及停止标准(threshold)。 |

neuralnet 包的简要描述,摘自官方文档,见下表:

neuralnet 包
描述
使用反向传播训练神经网络,带有(Riedmiller,1994)或不带权重回溯的弹性反向传播(Riedmiller,1993),或由 Anastasiadis 等人(2005)修改的全局收敛版本。该包允许通过自定义选择误差和激活函数来进行灵活设置。此外,还实现了广义权重的计算(Intrator O 和 Intrator N,1993)。
详情
包:neuralnet 类型:包 版本:1.33 日期:2016-08-05 许可:GPL (>=2)
作者
Stefan Fritsch, Frauke Guenther (电子邮件:guenther@leibniz-bips.de) 维护者:Frauke Guenther (电子邮件:guenther@leibniz-bips.de)
用法
neuralnet(formula, data, hidden = 1, threshold = 0.01, stepmax = 1e+05, rep = 1, startweights = NULL, learningrate.limit = NULL, learningrate.factor = list(minus = 0.5, plus = 1.2), learningrate=NULL, lifesign = "none", lifesign.step = 1000, algorithm = "rprop+", err.fct = "sse", act.fct = "logistic", linear.output = TRUE, exclude = NULL, constant.weights = NULL, likelihood = FALSE)
参数的含义
formula: 模型的符号描述。data: 一个包含公式中指定变量的数据框。hidden: 一个整数向量,指定每一层中的隐藏神经元(节点)数量。threshold: 一个数值,指定误差函数的偏导数的阈值作为停止标准。stepmax: 神经网络训练的最大步数。达到此最大值会导致神经网络训练过程停止。rep: 神经网络训练的重复次数。startweights: 一个包含初始权重值的向量。权重将不会随机初始化。learningrate.limit: 一个向量或列表,包含学习率的最低和最高限制。仅用于RPROPGRPROPlearningrate.factor: 一个向量或列表,包含学习率上限和下限的乘法因子,仅用于RPROPGRPROPlearningrate: 一个数值,指定传统反向传播算法使用的学习率。仅用于传统反向传播。lifesign: 一个字符串,指定神经网络计算过程中输出的详细程度- 'none''minimal''full'lifesign.step: 一个整数,指定在完整的生命周期模式下,打印最小阈值的步长。algorithm: 一个字符串,包含用于计算神经网络的算法类型。err.fct: 一个可微函数,用于计算误差。act.fct: 一个可微函数,用于平滑协变量或神经元与权重的叉积结果。linear.output: 逻辑值。如果输出神经元不应用 act.fct,则将线性输出设置为 TRUE,否则设置为 FALSEexclude: 一个向量或矩阵,指定从计算中排除的权重。constant.weights: 一个向量,指定在训练过程中被排除并视为固定的权重值。likelihood: 逻辑值。如果误差函数等于负对数似然函数,则会计算信息标准 AIC 和 BIC。此外,置信区间的使用也是有意义的。

在简要浏览了包的文档后,我们来看一下剩余的代码示例:

 print(model)

该命令输出刚生成的模型,如下所示:

$result.matrix
 1
error                          0.001094100442
reached.threshold              0.009942937680
steps                      34563.000000000000
Intercept.to.1layhid1         12.859227998180
Input.to.1layhid1             -1.267870997079
Intercept.to.1layhid2         11.352189417430
Input.to.1layhid2             -2.185293148851
Intercept.to.1layhid3          9.108325110066
Input.to.1layhid3             -2.242001064132
Intercept.to.1layhid4        -12.895335140784
Input.to.1layhid4              1.334791491801
Intercept.to.1layhid5         -2.764125889399
Input.to.1layhid5              1.037696638808
Intercept.to.1layhid6         -7.891447011323
Input.to.1layhid6              1.168603081208
Intercept.to.1layhid7         -9.305272978434
Input.to.1layhid7              1.183154841948
Intercept.to.1layhid8         -5.056059256828
Input.to.1layhid8              0.939818815422
Intercept.to.1layhid9         -0.716095585596
Input.to.1layhid9             -0.199246231047
Intercept.to.1layhid10        10.041789457410
Input.to.1layhid10            -0.971900813630
Intercept.to.Output           15.279512257145
1layhid.1.to.Output          -10.701406269616
1layhid.2.to.Output           -3.225793088326
1layhid.3.to.Output           -2.935972228783
1layhid.4.to.Output           35.957437333162
1layhid.5.to.Output           16.897986621510
1layhid.6.to.Output           19.159646982676
1layhid.7.to.Output           20.437748965610
1layhid.8.to.Output           16.049490298968
1layhid.9.to.Output           16.328504039013
1layhid.10.to.Output          -4.900353775268

让我们回到代码分析:

plot(model)

之前的命令为我们绘制了神经网络,如下所示:

final_output=cbind (Input, Output, 
 as.data.frame(model$net.result) )
colnames(final_output) = c("Input", "Expected Output", 
 "Neural Net Output" )
print(final_output)

之前的代码输出最终结果,将预测输出与实际输出进行比较,如下所示:

> print(final_output)
 Input Expected Output Neural Net Output
1    0               0     -0.0108685813
2    1               1      1.0277796553
3    2               4      3.9699671691
4    3               9      9.0173879001
5    4              16     15.9950295615
6    5              25     25.0033272826
7    6              36     35.9947137155
8    7              49     49.0046689369
9    8              64     63.9972090104
10   9              81     81.0008391011
11  10             100     99.9997950184    

使用 nnet() 库的实现

为了提高我们对nnet库的使用,我们来看另一个例子。这一次,我们将使用通过顾客访谈收集的数据。顾客被要求对以下方面进行评分:服务、环境和食物。顾客还被问到是否会根据这些评分给小费。在这个案例中,输入的数量是2,输出是一个分类值(Tip=1No-tip=0)。

将使用的输入文件如下面的表格所示:

编号 顾客是否给小费 服务 环境 食物 是否给小费
1 1 4 4 5 Tip
2 1 6 4 4 Tip
3 1 5 2 4 Tip
4 1 6 5 5 Tip
5 1 6 3 4 Tip
6 1 3 4 5 Tip
7 1 5 5 5 Tip
8 1 5 4 4 Tip
9 1 7 6 4 Tip
10 1 7 6 4 Tip
11 1 6 7 2 Tip
12 1 5 6 4 Tip
13 1 7 3 3 Tip
14 1 5 1 4 Tip
15 1 7 5 5 Tip
16 0 3 1 3 No-tip
17 0 4 6 2 No-tip
18 0 2 5 2 No-tip
19 0 5 2 4 No-tip
20 0 4 1 3 No-tip
21 0 3 3 4 No-tip
22 0 3 4 5 No-tip
23 0 3 6 3 No-tip
24 0 4 4 2 No-tip
25 0 6 3 6 No-tip
26 0 3 6 3 No-tip
27 0 4 3 2 No-tip
28 0 3 5 2 No-tip
29 0 5 5 3 No-tip
30 0 1 3 2 No-tip

这是一个分类问题,包含三个输入和一个分类输出。我们将通过以下代码来解决这个问题:

######################################################################## 
##Chapter 1 - Introduction to Neural Networks - using R ################ 
###Simple R program to build, train and test neural networks ########### 
### Classification based on 3 inputs and 1 categorical output ########## 
######################################################################## 

###Choose the libraries to use
library(NeuralNetTools)
library(nnet)

###Set working directory for the training data
setwd("C:/R")
getwd()

###Read the input file
mydata=read.csv('RestaurantTips.csv',sep=",",header=TRUE)
mydata
attach(mydata)
names(mydata)

##Train the model based on output from input
model=nnet(CustomerWillTip~Service+Ambience+Food, 
 data=mydata, 
 size =5, 
 rang=0.1, 
 decay=5e-2, 
 maxit=5000)
print(model)
plotnet(model)
garson(model)

########################################################################

让我们逐行分析代码。

为了理解刚才提出的代码中的所有步骤,我们将详细分析它们。首先会展示代码片段,然后进行解释。

library(NeuralNetTools)
library(nnet)

这包括我们的程序所需的库NeuralNetToolsnnet()

###Set working directory for the training data
setwd("C:/R")
getwd()
###Read the input file
mydata=read.csv('RestaurantTips.csv',sep=",",header=TRUE)
mydata
attach(mydata)
names(mydata)

这将设置工作目录并读取输入的 CSV 文件。

##Train the model based on output from input
model=nnet(CustomerWillTip~Service+Ambience+Food, 
 data=mydata, 
 size =5, 
 rang=0.1, 
 decay=5e-2, 
 maxit=5000)
print(model)

这调用了nnet()函数,并传入了参数。输出如下。nnet()会处理前向传播和反向传播直到收敛:

> model=nnet(CustomerWillTip~Service+Ambience+Food,data=mydata, size =5, rang=0.1, decay=5e-2, maxit=5000)
# weights:  26
initial  value 7.571002 
iter  10 value 5.927044
iter  20 value 5.267425
iter  30 value 5.238099
iter  40 value 5.217199
iter  50 value 5.216688
final  value 5.216665 
converged

nnet包的简要描述,摘自官方文档,如下表所示:

nnet-package:前馈神经网络和多项式对数线性模型
描述
用于前馈神经网络(单隐层)和多项式对数线性模型的软件。
详情

| 包名:nnet 类型:包

版本:7.3-12

日期:2016-02-02

许可证:GPL-2 | GPL-3

作者
Brian Ripley William Venables
使用方法:
nnet(formula, data, weights, subset, na.action, contrasts = NULL)
参数含义:

| Formula: 形如 class ~ x1 + x2 + ... 的公式 data: 从数据框中提取优先使用公式中指定的变量

weights: (案例)每个示例的权重;如果缺失,则默认为 1

subset: 一个索引向量,指定训练样本中要使用的案例

na.action: 一个函数,用于指定在发现缺失值(NA)时要采取的措施

contrasts: 用于某些或所有出现在模型公式中的变量的对比列表 |

在简要介绍包文档后,接下来让我们查看以下代码示例中剩余的行:

print(model) 

此命令打印出net()的详细信息,如下所示:

> print(model)
a 3-5-1 network with 26 weights
inputs: Service Ambience Food 
output(s): CustomerWillTip 
options were - decay=0.05

要绘制model,使用以下命令:

plotnet(model)

model的绘图如下;单隐藏层中有五个节点:

使用NeuralNetTools,可以通过garson算法获取神经网络中输入变量的相对重要性:

garson(model)

此命令打印出各种输入参数及其对输出预测的重要性,如下图所示:

从应用 Garson 算法获得的图表中,可以注意到,在决定给小费时,顾客所获得的服务对这一决定的影响最大。

我们已经看到过 R 中的两个神经网络库,并在简单示例中使用了它们。接下来,我们将深入探讨本书中的几个实际用例。

深度学习

深度学习(DL)形成了一个具有众多隐藏层的高级神经网络。深度学习是一个庞大的学科,是构建人工智能的重要概念。它被广泛应用于各种领域,例如:

  • 图像识别

  • 计算机视觉

  • 手写识别

  • 文本分类

  • 多类分类

  • 回归问题等

我们将在未来的章节中详细了解使用 R 进行深度学习。

神经网络的优缺点

神经网络是深度学习的基础,深度学习的应用非常广泛,从语音识别到癌症检测。神经网络的优缺点在本节中进行了描述。优点超过缺点,因此神经网络被视为数据科学、机器学习和预测建模的首选技术。

优势

以下是神经网络的一些优势:

  • 神经网络具有灵活性,可以用于回归和分类问题。任何可以转化为数值的数据都可以用于模型,因为神经网络是一个具有近似函数的数学模型。

  • 神经网络适合建模具有大量输入的非线性数据,例如图像。它在处理涉及大量特征的任务时非常可靠。它通过将分类问题拆分成简单元素的层次网络来工作。

  • 一旦训练完成,预测速度相当快。

  • 神经网络可以用任意数量的输入和层进行训练。

  • 神经网络在有更多数据点时效果最佳。

缺点

让我们来看看神经网络的一些缺点:

  • 神经网络是黑箱模型,意味着我们无法知道每个独立变量对因变量的影响程度。

  • 使用传统的 CPU 进行训练在计算上非常昂贵且耗时。

  • 神经网络在很大程度上依赖于训练数据。这会导致过拟合和泛化问题。模型过于依赖训练数据,可能会被调整得过于符合数据。

神经网络实现中的最佳实践

以下是一些最佳实践,有助于神经网络的实现:

  • 神经网络在有良好训练数据的情况下实现最佳。

  • 在多层感知机(MLP)中,隐藏层越多,模型的预测准确度越高。

  • 隐藏层最好有五个节点。

  • ReLU 和 误差平方和 (SSE) 分别是最佳的激活函数和误差调整技术。

快速说明 GPU 处理

处理能力的提升为神经网络在日常问题中的应用提供了强大支持。GPU 是专门设计来执行图形操作的处理器(例如,游戏、3D 动画等)。它们执行数学密集型任务,且与 CPU 配合使用。CPU 负责计算机的操作任务,而 GPU 则用于处理繁重的工作负载。

神经网络架构需要强大的数学计算能力,GPU 是首选的硬件。在每个神经元的权重和输入之间的向量化点积可以通过 GPU 并行计算。GPU 的进步正在推动神经网络的普及。深度学习在图像处理、计算机视觉、生物信息学和天气建模等领域的应用都受益于 GPU。

总结

在本章中,我们回顾了人工神经网络(ANN)的概述。神经网络的实现相对简单,但其内部机制非常复杂。我们可以将神经网络总结为一种通用的数学函数逼近方法。任何一组输入输出的映射都可以通过神经网络转化为一个黑箱数学函数,近年来,神经网络的应用也变得非常广泛。

本章我们讨论了以下内容:

  • 神经网络是一种机器学习技术,并且是数据驱动的。

  • 人工智能、机器学习和神经网络是让机器像人类一样工作的不同范式。

  • 神经网络可以用于监督学习和无监督学习。

  • 权重、偏置和激活函数是神经网络中的重要概念。

  • 神经网络是非线性和非参数化的。

  • 神经网络在预测时非常快速,并且相较于其他机器学习模型最为准确。

  • 任何神经网络架构中都包含输入层、隐藏层和输出层。

  • 神经网络基于构建多层感知机(MLP),我们理解了神经网络的基础:权重、偏差、激活函数、前向传播和反向传播处理

  • 前向传播和反向传播是推导神经网络模型的技术

神经网络可以通过多种编程语言实现,例如 Python、R、MATLAB、C 和 Java 等。本书的重点将是使用 R 构建应用程序。深度神经网络(DNN)和人工智能(AI)系统正基于神经网络不断发展。在接下来的章节中,我们将深入探讨不同类型的神经网络及其各种应用。

第二章:神经网络中的学习过程

正如人类学习有许多不同的类型和方法一样,机器学习也可以有不同的方式。为了确保机器能够从经验中学习,定义最适合的学习方法非常重要,这取决于具体的工作要求。这通常意味着选择当前情况下有效的技术,并不时进行评估,以决定是否需要尝试新的方法。

我们已经在第一章中学习了神经网络的基础知识,神经网络与人工智能概念,并通过 R 语言进行了两个简单的实现。在本章中,我们将探讨学习过程,也就是如何训练、测试和部署神经网络机器学习模型。训练阶段用于学习,以调整神经网络的参数。测试阶段用于评估完全训练好的神经网络的性能。最后,在部署阶段,实际数据会通过模型进行预测。

以下是本章涵盖的概念列表:

  • 学习过程

  • 有监督学习

  • 无监督学习

  • 训练、测试和部署模型

  • 评估指标——误差测量与微调;衡量模型的准确性

  • 使用神经网络的有监督学习模型

  • 使用神经网络的无监督学习模型

  • 反向传播

到本章结束时,我们将理解学习过程的基本概念,并学习如何在 R 环境中实现它。我们将发现实现神经网络的不同类型的算法。我们将学习如何训练、测试和部署模型。我们将知道如何执行正确的评估程序。

机器学习是什么?

机器学习是什么意思?这个定义相当复杂,因此我们需要请广大科学家们帮助解答。我们可以提到一位人工智能先驱的名言:

“一个研究领域,赋予计算机无需明确编程即可学习的能力。”

– 阿瑟·塞缪尔

机器学习是指通过数据训练模型或算法,然后使用该模型预测新的数据。例如,一个幼儿从爬行阶段被教会如何走路。最初,幼儿的父母扶着孩子的手帮助他站起来,并通过给定的数据进行教学。基于这些过程,如果在幼儿的路上出现障碍,或者某个地方需要转弯,在训练之后,幼儿能够自己导航。用于训练的数据称为训练数据,接受者在正式训练后仍然继续学习。

机器也可以像教小孩子一样,基于训练来完成任务。首先,我们输入足够的数据来告诉机器在什么情况下需要做什么。经过训练后,机器可以自动执行任务,还能学会自我调整。这种训练机器的方式叫做机器学习

机器学习与编程的主要区别在于,机器学习不涉及编码/编程,而编程是给机器一组执行指令的过程。在机器学习中,数据是唯一的输入,而模型是基于我们决定使用的算法。

要使用的算法是基于数据的各种因素:特征(或自变量)、因变量的类型、模型的准确性以及模型训练和预测的速度。

基于机器学习数据的自变量,有三种不同的方式来训练模型:

  • 监督学习

  • 无监督学习

  • 强化学习

下图展示了用于训练机器学习模型的不同算法:

在接下来的章节中,我们将逐一讲解这些内容。

监督学习

监督学习是一种学习方法,其中一部分训练数据作为算法的“老师”,帮助确定模型。机器被教会如何从目标数据中学习。目标数据或因变量是自变量的集体作用结果。网络训练是通过目标数据及其与输入数据模式的关系进行的。目标标签是事先已知的,数据被输入算法以推导模型。

大多数神经网络的使用是基于监督学习的。权重和偏差根据输出值进行调整。输出可以是分类的(如真/假或 0/1/2)或连续的(如 1、2、3,依此类推)。模型依赖于输出变量的类型,而在神经网络中,输出层是根据目标变量的类型来构建的。

对于神经网络,所有的自变量和因变量需要是数值型的,因为神经网络是基于数学模型的。数据科学家需要将数据转换为数值形式,以便输入模型。

监督学习通过下图来表示:

无监督学习

在无监督学习(或自组织)中,输出层被训练以将输入数据组织成另一组数据,而无需目标变量。输入数据会被分析,并从中找到模式以推导输出,如下图所示。由于没有教师(或目标变量),这种学习方式称为无监督学习

无监督学习中可用的不同技术如下:

  • 聚类(K 均值,层次聚类)

  • 关联技术

  • 降维

  • 自组织映射SOM)/ Kohonen 网络

总结一下,以下图示了机器学习的两种主要类型:

对于神经网络,我们有两种类型可用,使用 R 中不同的方式。

强化学习

强化学习是一种机器学习类型,在这种类型中,模型会不断收到反馈,以适应环境。在每个步骤中都会进行性能评估,以改进模型。对于神经网络,有一种特殊类型叫做Q-learning,它结合神经元来实现反向传播反馈机制中的强化学习。本书不涉及详细内容。

以下是我们目前已经涵盖的三种学习类型:

训练和测试模型

训练和测试模型是将该模型用于预测分析的基础。给定一个包含100行数据的数据集,其中包括预测变量和响应变量,我们将数据集分成一个合适的比例(例如70:30),并分配70行用于训练,30行用于测试。数据行是随机选择的,以减少偏差。

一旦训练数据可用,数据将被传入神经网络,以建立巨大的通用函数。训练数据决定了权重、偏置和激活函数的使用,以便从输入得到输出。直到最近,我们才不能说一个权重对目标变量有正向或负向影响。但现在我们已经能够揭示这个黑盒中的一些信息。例如,通过绘制一个训练过的神经网络,我们可以发现训练过的突触权重和有关训练过程的基本信息。

一旦足够的收敛性达到,模型将存储在内存中,下一步是测试模型。我们将30行数据传入模型,检查实际输出是否与模型的预测输出相符。评估用于获取各种指标,以验证模型。如果准确度差异过大,则需要通过更改训练数据和传递给神经网络函数的其他参数重新构建模型。我们将在本章稍后讨论更多关于评估指标的内容。

在训练和测试之后,模型被认为已部署,其中实际数据通过模型传递以获得预测。例如,使用案例可能是基于各种输入参数确定欺诈交易或房贷资格检查。

训练、测试和部署在下图中表示:

到目前为止,我们关注的是各种可用的算法;现在是时候将注意力集中在代表每个分析基本元素的数据上。

数据周期

数据是模型构建和学习过程中的关键组成部分。数据需要被收集、清洗、转换,然后输入模型进行学习。整体数据生命周期如下所示:

建模的一个关键要求是拥有良好且平衡的数据。这有助于提高模型的准确度,并更好地利用现有的算法。数据科学家的大部分时间都花费在数据清洗上,然后才能开始建模。

我们已经看到模型部署前的训练和测试过程。在测试时,结果作为评估指标进行记录,帮助我们决定是否使用某个模型,或是否需要改变模型。

接下来我们将看到评估指标。

评估指标

评估一个模型涉及在测试阶段检查预测值是否等于实际值。有多种评估指标可用,它们取决于目标变量的状态。

对于二分类问题,预测目标变量和实际目标变量可以处于以下四种状态之一:

预测 实际
预测 = 真 实际 = 真
预测 = 真 实际 = 假
预测 = 假 实际 = 真
预测 = 假 实际 = 假

当预测值与实际值相同,我们认为模型是准确的。如果所有预测值和实际值都相同(无论是全为还是全为),则模型准确度为100%。但实际情况中永远不会如此。

由于神经网络是近似模型,总会有一些误差的可能性。前述表格中的四种状态都是可能的。

我们为模型定义以下术语和指标:

  • 真阳性 (TP):所有预测值与实际值均为的情况(准确度良好)。

  • 真负例 (TN):所有预测为且实际也是的情况(准确度良好)。

  • 假阳性 (FP):这是当我们预测某个值为阳性(),但实际为阴性时的情况。就像是误报或 FP 错误。例如,当预测男性为孕妇时。所有预测为,而实际为的情况。这也叫做第一类错误

  • 假负例 (FN):当我们预测某个值为,但实际却为时,该情况称为 FN。例如,当孕妇被孕检试剂预测为未怀孕时,就是一个 FN 案例。所有预测为且实际为的情况。这也叫做第二类错误

混淆矩阵

当分类值被绘制成一个nxn矩阵(在二分类情况下为2x2矩阵)时,该矩阵称为混淆矩阵。所有评估指标都可以从混淆矩阵本身得出:

预测值 预测值
实际值
TP FN
FP TN

现在,让我们详细看看一些评估指标。

真阳性率

真阳性率 (TPR) 或敏感性或召回率或命中率,是指从所有识别出的正样本中,正确识别的真阳性比例:

理想情况下,如果模型的值接近 1,那么模型的表现会更好。

真负率

真负率 (TNR) 或特异度是正确预测的负样本与我们预测的所有负样本总数之比:

如果这个比率接近零,模型就更准确。

准确率

准确率是衡量我们模型好坏的标准。如果模型表现良好,它应该接近 1。

准确率是正确预测与所有总预测的比率:

精确度和召回率

精确度和召回率分别是 TP 与 (TP+FP) 以及 TP 与 (TP+FN) 的比率。这些比率决定了我们的预测与实际的相关程度。

精确度定义为选定项目中相关项目的比例。也就是说,预测的结果中有多少是实际正确的。

方程式是:

如果精确度接近 1,我们的预测就会更准确。

召回率则告诉我们选择了多少相关项目。从数学上讲,它是:

以下图表清晰地展示了我们迄今为止的讨论:

F 分数

F 分数,或 F1 分数,是衡量准确度的另一种方式。从技术上讲,它是精确度和召回率的调和均值:

接收者操作特征曲线

接收者操作特征 (ROC) 曲线是一种图形展示,说明二分类系统的预测能力。ROC 曲线通过在各种阈值设置下绘制真阳性率(TPR)与假阳性率FPR)的图表来创建。这给我们提供了敏感性与(1 - 特异度)的关系。ROC 曲线通常看起来是这样的:

获得必要技能后,我们已经准备好详细分析用于构建神经网络的算法。

神经网络中的学习

正如我们在第一章《神经网络与人工智能概念》中看到的,神经网络是一种机器学习算法,具有从数据中学习并使用构建的模型进行预测的能力。它是一种通用的函数近似方法,也就是说,任何输入输出数据都可以逼近为数学函数。

正向传播通过选择随机权重为我们提供了一个初始的数学函数,用于根据输入得到输出。实际输出与预测输出之间的差异称为误差项。神经网络的学习过程实际上发生在反向传播阶段。通过减少每次迭代中的误差项,模型会对权重进行微调。在反向传播过程中使用梯度下降法。

让我们在本章详细讲解反向传播,因为它是神经网络中重要的机器学习方面。

返回到反向传播

我们已经在第一章中详细讨论了正向传播,神经网络与人工智能概念,以及使用梯度下降法的反向传播的一些内容。反向传播是理解神经网络的重要概念之一,它依赖于微积分来更新每一层的权重和偏置。误差的反向传播类似于从错误中学习。我们在每次迭代中纠正我们的错误(误差),直到达到一个称为收敛的点。*反向传播的目标是纠正每一层的权重,并最小化输出层的整体误差。

神经网络的学习在前馈网络中极度依赖反向传播。正向传播和误差修正的常见步骤如下所示:

  1. 通过为隐藏层中每个神经元分配随机的权重和偏置,开始神经网络的正向传播。

  2. 在每个神经元上获取 sum(weightinput) + bias* 的总和。

  3. 在每个神经元上应用激活函数(sigmoid)。

  4. 将此输出传递给下一个层的神经元。

  5. 如果该层是输出层,应用权重并获取每个输出层神经元的 sum(weightinput) + bias* 的总和。

  6. 再次,在输出层神经元应用激活函数。

  7. 这就形成了神经网络在一次正向传播中的输出层输出。

  8. 现在,使用训练数据,我们可以通过减去实际输出和激活函数输出值来识别每个输出神经元的误差项。

  9. 误差的总和通过以下公式计算得出:

当误差函数 E 随后进行微分时,使用一个 1/2 的因子来消除指数。

  1. 梯度下降技术要求计算误差项(E)相对于网络权重的偏导数。使用链式法则来计算误差项相对于权重 w[ij] 的全误差的偏导数:

导数定义为值变化的速率,梯度下降使用导数(或斜率)来最小化误差项并得到一组正确的权重。

  1. 第一个因子是该特定神经元 j 对输出的误差项的偏导数,并且 o[j] 等于 y

  1. 链式法则中的第二个因子是神经元 o[j] 对其输入的输出的偏导数,并且是激活函数(sigmoid 函数)的偏导数:

这里 net[j] 是输入到神经元的值。

  1. 链式法则中的第三项只是 o[i]

  1. 将第 11 步、第 12 步和第 13 步结合,我们得到:

  1. 每个神经元(任何层级)上的权重 w[ij] 会通过此偏导数与学习率结合进行更新。

这些步骤会重复进行,直到我们获得非常低的误差项的收敛或达到指定的次数。

所有步骤都在可用的 R 包中内部处理。我们可以提供学习率以及其他各种参数。

反向传播过程如下所示:

就像生活中的一切事物一样,即便是算法也有进一步改进的空间。在接下来的章节中,我们将看到如何实现这一点。

神经网络学习算法优化

在神经网络中执行学习过程的程序称为训练算法。学习算法是机器学习算法选择的最佳优化模型。其目标是最小化损失函数并提高准确性。这里我们展示一些优化技术,除了梯度下降法。

粒子群优化PSO)方法受到鸟群在寻找食物或生存过程中的集体行为观察的启发。它类似于鱼群试图一起移动。我们知道粒子的位置和速度,PSO 的目标是在由位置和速度控制的数学方程的指导下,在大空间中搜索解决方案集。它从生物体行为中汲取灵感,用于集体智能。

模拟退火是一种基于概率方法的技术,用来逼近代价函数的全局最优解。该方法在大空间中通过模拟搜索解决方案。

进化方法源自生物学中的进化过程,且

进化可以通过繁殖、变异、选择和重组来进行。

适应度函数用于确定模型的性能,并基于此进行调整。

函数,我们选择最终模型。

期望最大化EM)方法是一种统计学习方法,采用迭代方法找到最大似然或最大后验估计,从而最小化误差。

神经网络中的监督学习

如前所述,监督学习是一种学习方法,其中一部分训练数据作为算法的“老师”,帮助确定模型。在接下来的部分中,将提出一个回归预测建模问题示例,以便了解如何使用神经网络解决该问题。

波士顿数据集

该数据集描述了波士顿郊区住宅的 13 个数值属性,并涉及在这些郊区以千美元为单位建模住宅价格。因此,这是一个回归预测建模问题。输入属性包括犯罪率、非零售商业用地比例、化学浓度等。以下列表显示了所有变量及其简要描述:

  • 实例数量:506

  • 属性数量:13 个连续属性(包括 class

    属性MEDV,以及一个二进制值属性

每个属性的详细描述如下:

  1. crim 按城镇划分的每人犯罪率。

  2. zn 划为住宅用地的、超过25,000平方英尺的地块比例。

  3. indus 每个城镇的非零售商业用地比例。

  4. chas 查尔斯河虚拟变量(如果区域边界是河流,= 1;否则为0)。

  5. nox 氮氧化物浓度(单位:每10百万)。

  6. rm 每个住宅的平均房间数。

  7. age 1940 年前建成的自有住房比例。

  8. dis 到波士顿五个就业中心的加权距离

  9. rad 通往放射性高速公路的可达性指数。

  10. tax10,000美元的全额财产税率。

  11. ptratio 按城镇划分的师生比。

  12. black 1000(Bk - 0.63)² 其中 Bk 是按城镇划分的黑人比例。

  13. lstat 低收入人群的百分比。

  14. medv 自有住房的中位数价值(单位:1000 美元)。

在这些变量中,medv 是响应变量,而其他十三个变量是可能的预测变量。本分析的目标是拟合一个回归模型,最有效地解释 medv 的变化。

前十三列与 medv 响应变量之间存在关系。我们可以根据输入的十三列预测 medv 的值。

该数据集已通过 R 库(MASS)提供,正如我们稍后将看到的那样,因此我们不必担心如何获取数据。

使用波士顿数据集进行神经网络回归

在本节中,我们将为 Boston 数据集运行回归神经网络。预测测试数据的 medv 值。训练和测试的划分比例为70:30neuralnet函数用于通过神经网络建模数据:

#####################################################################
###Chapter 2 - Introduction to Neural Networks - using R ############
###Simple R program to build, train, test regression neural networks#
#########################flename: Boston.r###########################
#####################################################################

library("neuralnet")
library(MASS)

set.seed(1)

data = Boston

max_data <- apply(data, 2, max) 
min_data <- apply(data, 2, min)
data_scaled <- scale(data,center = min_data, scale = max_data - min_data) 

index = sample(1:nrow(data),round(0.70*nrow(data)))
train_data <- as.data.frame(data_scaled[index,])
test_data <- as.data.frame(data_scaled[-index,])

n = names(data)
f = as.formula(paste("medv ~", paste(n[!n %in% "medv"], collapse = " + ")))
net_data = neuralnet(f,data=train_data,hidden=10,linear.output=T)
plot(net_data)

predict_net_test <- compute(net_data,test_data[,1:13])

predict_net_test_start <- predict_net_test$net.result*(max(data$medv)-min(data$medv))+min(data$medv)
test_start <- as.data.frame((test_data$medv)*(max(data$medv)-min(data$medv))+min(data$medv))
MSE.net_data <- sum((test_start - predict_net_test_start)²)/nrow(test_start)

Regression_Model <- lm(medv~., data=data)
summary(Regression_Model)
test <- data[-index,]
predict_lm <- predict(Regression_Model,test)
MSE.lm <- sum((predict_lm - test$medv)²)/nrow(test)

MSE.net_data
MSE.lm
###########################################################################

别担心,我们将逐行详细解释整个代码。

library("neuralnet")
library(MASS)

代码的前两行很简单,因为它们加载了我们将用于后续计算的库。具体来说,neuralnet 库将帮助我们构建和训练神经网络,而 MASS 库将帮助我们加载之前已详细介绍过的 Boston 数据集。

请记住,要安装 R 初始发行版中没有的库,必须使用 install.package 函数。这是安装软件包的主要函数。它接受一个包含名称的向量和一个目标库,从仓库下载软件包并进行安装。

例如,在我们的例子中,要安装 neuralnet 包,我们应该写:

install.neuralnet

最后需要强调的是,这个函数只应该使用一次,而不是每次运行代码时都使用。相反,通过以下命令加载库,并且每次运行代码时必须重复这一过程:

library (neuralnet)

set.seed 函数设置 R 随机数生成器的种子,这对创建可以复现的模拟或随机对象很有用:

set.seed(1)

每次你想获得可复现的随机结果时,都必须使用这个函数。在这种情况下,随机数是相同的,无论我们在序列中走多远,它们都会保持不变。

以下命令加载 Boston 数据集,该数据集如我们预期,包含在 MASS 库中,并将其保存到给定的框架中:

data = Boston

使用 str 函数查看任意 R 对象的紧凑结构。在我们的例子中,使用 str(data),我们将获得如下结果:

> str(data)
'data.frame': 506 obs. of 14 variables:
 $ crim : num 0.00632 0.02731 0.02729 0.03237 0.06905 ...
 $ zn : num 18 0 0 0 0 0 12.5 12.5 12.5 12.5 ...
 $ indus : num 2.31 7.07 7.07 2.18 2.18 2.18 7.87 7.87 7.87 7.87 ...
 $ chas : int 0 0 0 0 0 0 0 0 0 0 ...
 $ nox : num 0.538 0.469 0.469 0.458 0.458 0.458 0.524 0.524 0.524 0.524 ...
 $ rm : num 6.58 6.42 7.18 7 7.15 ...
 $ age : num 65.2 78.9 61.1 45.8 54.2 58.7 66.6 96.1 100 85.9 ...
 $ dis : num 4.09 4.97 4.97 6.06 6.06 ...
 $ rad : int 1 2 2 3 3 3 5 5 5 5 ...
 $ tax : num 296 242 242 222 222 222 311 311 311 311 ...
 $ ptratio: num 15.3 17.8 17.8 18.7 18.7 18.7 15.2 15.2 15.2 15.2 ...
 $ black : num 397 397 393 395 397 ...
 $ lstat : num 4.98 9.14 4.03 2.94 5.33 ...
 $ medv : num 24 21.6 34.7 33.4 36.2 28.7 22.9 27.1 16.5 18.9 ...

对给定对象获得的结果如下面的图所示:

让我们回过头来解析代码:

max_data <- apply(data, 2, max) 
min_data <- apply(data, 2, min)
data_scaled <- scale(data,center = min_data, scale = max_data - min_data) 

我们需要这段代码来规范化数据。

请记住,在训练神经网络之前,规范化数据是一个好习惯。通过规范化,数据单位被消除,从而可以轻松地比较来自不同地点的数据。

这是构建神经网络中一个极其重要的步骤,因为它可以避免不必要的结果或非常复杂的训练过程,从而导致算法收敛问题。你可以选择不同的数据缩放方法(z-标准化最小-最大缩放等)。在这个示例中,我们将使用最小-最大方法(通常称为特征缩放)将所有数据缩放到 [0,1] 范围内。实现这一点的公式如下:

在应用所选的规范化方法之前,必须先计算每个数据库列的最小值和最大值。为此,我们使用 apply 函数。该函数返回一个通过将函数应用于数组或矩阵的边缘而获得的向量、数组或列表。让我们理解所使用参数的含义。

max_data <- apply(data, 2, max) 

apply 函数的第一个参数指定应用该函数的数据集,在我们的例子中是名为 data 的数据集。第二个参数必须包含一个向量,给出函数将应用于的下标。在我们的例子中,1 表示行,2 表示列。第三个参数必须包含要应用的函数;在我们的例子中是 max 函数。

为了对数据进行标准化,我们使用了scale函数,这是一个通用函数,其默认方法是对数值矩阵的列进行居中和/或缩放。

index = sample(1:nrow(data),round(0.70*nrow(data)))
train_data <- as.data.frame(data_scaled[index,])
test_data <- as.data.frame(data_scaled[-index,])

在刚才建议的代码的第一行中,数据集被分割为70:30,目的是使用70百分比的数据来训练网络,剩余的30百分比用于测试网络。在第二行和第三行中,名为data的数据框的内容被划分为两个新的数据框,分别称为train_datatest_data

n = names(data)
f = as.formula(paste("medv ~", paste(n[!n %in% "medv"], collapse = " + ")))
net_data = neuralnet(f,data=train_data,hidden=10,linear.output=T)
plot(net_data)

到目前为止,所有工作仅用于准备数据。现在是时候构建网络了。为此,我们首先通过names函数恢复所有变量名称。该函数可以获取或设置对象的名称。

接下来,我们构建了用于构建网络的formula,因此我们使用neuralnet函数来构建和训练网络。在这种情况下,我们将创建一个只有一个隐藏层,且有10个节点的网络。最后,我们绘制了神经网络,如下图所示:

既然我们有了网络,接下来该做什么呢?当然,我们用它来进行预测。我们已经预留了30百分比的可用数据来进行预测:

predict_net_test <- compute(net_data,test_data[,1:13])

在我们的案例中,我们将该函数应用于test_data数据集,仅使用前13列,代表网络的输入变量:

predict_net_test_start <- predict_net_test$net.result*(max(data$medv)-       min(data$medv))+min(data$medv)
test_start <- as.data.frame((test_data$medv)*(max(data$medv)-min(data$medv))+min(data$medv))
MSE.net_data <- sum((predict_net_test_start - test_start)²)/nrow(test_start)

那么,如何判断网络能够进行的预测是否准确呢?我们可以使用均方误差MSE)作为衡量我们预测值与真实数据之间差距的标准。

在这方面,值得记住的是,在构建网络之前,我们已经对数据进行了标准化。现在,为了能够进行比较,我们需要退回一步,回到起始位置。一旦数据集的值恢复后,我们就可以通过以下公式计算MSE

好的,我们现在已经计算了MSE,那么该用什么来进行比较呢?为了了解网络预测的准确性,我们可以构建一个线性回归模型:

Regression_Model <- lm(medv~., data=data)
summary(Regression_Model)
test <- data[-index,]
predict_lm <- predict(Regression_Model,test)
MSE.lm <- sum((predict_lm - test$medv)²)/nrow(test)

我们使用lm函数构建了一个线性回归模型。该函数用于拟合线性模型,可以用于回归分析、单层方差分析和协方差分析。为了生成模型拟合结果的摘要,我们使用了summary函数,返回了以下结果:

> summary(Regression_Model)

Call:
lm(formula = medv ~ ., data = data)

Residuals:
 Min 1Q Median 3Q Max 
-15.5944739 -2.7297159 -0.5180489 1.7770506 26.1992710

Coefficients:
 Estimate Std. Error t value Pr(>|t|) 
(Intercept) 36.4594883851 5.1034588106 7.14407 0.00000000000328344 ***
crim -0.1080113578 0.0328649942 -3.28652 0.00108681 ** 
zn 0.0464204584 0.0137274615 3.38158 0.00077811 ***
indus 0.0205586264 0.0614956890 0.33431 0.73828807 
chas 2.6867338193 0.8615797562 3.11838 0.00192503 ** 
nox -17.7666112283 3.8197437074 -4.65126 0.00000424564380765 ***
rm 3.8098652068 0.4179252538 9.11614 < 0.000000000000000222 ***
age 0.0006922246 0.0132097820 0.05240 0.95822931 
dis -1.4755668456 0.1994547347 -7.39800 0.00000000000060135 ***
rad 0.3060494790 0.0663464403 4.61290 0.00000507052902269 ***
tax -0.0123345939 0.0037605364 -3.28001 0.00111164 ** 
ptratio -0.9527472317 0.1308267559 -7.28251 0.00000000000130884 ***
black 0.0093116833 0.0026859649 3.46679 0.00057286 ***
lstat -0.5247583779 0.0507152782 -10.34715 < 0.000000000000000222 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 4.745298 on 492 degrees of freedom
Multiple R-squared: 0.7406427, Adjusted R-squared: 0.7337897 
F-statistic: 108.0767 on 13 and 492 DF, p-value: < 0.00000000000000022204

此外,对于回归模型,我们计算了平均均方误差(MSE)。最后,为了评估网络的性能,我们将其与使用相同数据库计算的多元线性回归模型进行比较,具体如下:

MSE.net_data
MSE.lm

结果如下:

> MSE.net_data
[1] 12.0692812
> MSE.lm
[1] 26.99265692

从结果分析中可以看出,神经网络的MSE低于线性回归模型。

神经网络中的无监督学习

本节中,我们介绍了神经网络中的无监督学习模型,分别是竞争学习和 Kohonen 自组织映射(SOM)。Kohonen SOM 是由名为 Teuvo Kohonen 的教授发明的,它是一种将多维数据表示为更低维度的方法:1D2D。它可以在没有监督的情况下对数据进行分类。无监督学习旨在发掘数据集中的隐藏模式,并将它们聚类成不同的数据类别。

有许多无监督学习技术,如 K 均值聚类、降维、EM 等。它们的共同特征是没有输入输出映射,我们仅根据输入值来创建一个输出的组或集合。

对于神经网络,它们可以用于无监督学习。它们可以将数据分组到不同的桶中(聚类),或者将原始数据抽象成不同的一组输出数据点(特征抽象或降维)。无监督技术比有监督技术需要更少的处理能力和内存。

在无监督神经网络中,没有目标变量,因此我们无法进行反向传播。相反,我们在没有误差度量的情况下持续调整权重,并尝试将相似的数据分组。我们将看到两种无监督神经网络方法:

  • 竞争学习

  • Kohonen 自组织映射(SOM)

竞争学习

在这里,神经网络节点之间相互竞争,以争夺对输入数据子集的响应权。隐藏层被称为竞争层。每个竞争神经元都有自己的权重,我们计算每个输入向量与神经元权重之间的相似度。对于每个输入向量,隐藏神经元之间相互竞争,看哪个最类似于该输入向量:

输出神经元被认为是在为输入模式进行竞争。

  • 在训练过程中,提供给定输入模式最高激活的输出神经元被声明为胜者,并且其权重被移向该输入模式,而其余神经元保持不变。

  • 这种策略也叫做胜者全得,因为只有获胜的神经元会被更新:

让我们看一个简单的竞争学习算法示例,以在给定的输入数据中找到三个神经元:

  1. 我们将在输入层中设置三个输入神经元。每个神经元的输入是一个连续变量,假设每个输入神经元的权重是一个介于0.01.0之间的随机数。每个节点的输出是三个权重与其输入的乘积。

  2. 每个竞争层神经元接收权重和输入的乘积之和。

  3. 输出最大值的竞争层节点被视为胜者。然后,输入被分类为属于该节点对应的聚类。

  4. 胜者更新其每个权重,将权重从那些提供较弱信号的连接转移到那些提供较强信号的连接上。

因此,随着我们接收更多数据,每个节点都会收敛到它所代表的聚类中心。它对属于该聚类的输入激活得更强烈,而对属于其他聚类的输入激活得较弱。

竞争学习基本上有两个停止条件:

  • 预定义的周期数:仅运行N个周期,这防止了算法在没有收敛的情况下运行过长时间。

  • 最小权重更新值:算法会一直运行,直到我们得到最小的权重更新值。

Kohonen SOM

竞争学习的概念与邻域神经元相结合,得到了 Kohonen SOM。输出层中的每个神经元都有两个邻居。在竞争学习中,发出最大值的神经元会更新其权重,但在 SOM 中,邻域神经元也会以相对较慢的速度更新其权重。网络更新权重的邻域神经元数量取决于问题的维度。

对于一个2D问题,SOM 的表示方式如下:

从图示来看,SOM 是如何将不同的颜色映射到不同的聚类中的:

让我们一步一步地理解 Kohonen SOM 的工作原理:

  1. 初始化定义 SOM 结构的输入数量、聚类数量以及每个节点的权重。

  2. 从训练数据集中随机选择一个向量并将其呈现给网络。

  3. 检查网络中的每个节点,计算哪个节点的权重与输入向量最相似。获胜节点通常被称为最佳匹配单元BMU)。

  4. 计算 BMU 的邻域半径。这个值开始时较大,通常设置为网络的半径,并随着每个时间步逐渐减小。

  5. 任何位于步骤 4 中计算的 BMU 半径范围内的神经元,都将进行调整,使其更加接近输入向量。神经元离 BMU 越近,其权重的改变越大。

  6. N次迭代重复执行步骤 2。

步骤会重复执行N次周期,或者直到获得最小的权重更新。

SOM 被应用于聚类(将数据分组到不同的桶中)、数据抽象(从输入中派生输出数据)和降维(减少输入特征的数量)等领域。SOM 以类似于多维尺度法MDS)的方式处理问题,但不是最小化距离,而是尝试重新分组拓扑,换句话说,它们尽量保持相同的邻居。

让我们看看 R 中 SOM 实现的一个示例。kohonen包是一个需要安装的包,用于在 R 中使用 SOM 相关的函数。

以下 R 程序说明了kohonen包中的一些函数:

######################################################################
###Chapter 2 - Introduction to Neural Networks - using R    ##########
###Usuervised ML technique using Kohonen package  ####################
######################filename: kohonen.r#############################
######################################################################
library("kohonen")

data("wines")
str(wines)
head(wines)
View (wines)

set.seed(1)
som.wines = som(scale(wines), grid = somgrid(5, 5, "hexagonal"))
som.wines
dim(getCodes(som.wines))

plot(som.wines, main = "Wine data Kohonen SOM")
par(mfrow = c(1, 1))
plot(som.wines, type = "changes", main = "Wine data: SOM")

training = sample(nrow(wines), 150)
Xtraining = scale(wines[training, ])
Xtest = scale(wines[-training, ],
 center = attr(Xtraining, "scaled:center"),
 scale = attr(Xtraining, "scaled:scale"))
trainingdata = list(measurements = Xtraining,
 vintages = vintages[training])
testdata = list(measurements = Xtest, vintages = vintages[-training])
mygrid = somgrid(5, 5, "hexagonal")
som.wines = supersom(trainingdata, grid = mygrid)

som.prediction = predict(som.wines, newdata = testdata)
table(vintages[-training], som.prediction$predictions[["vintages"]])
######################################################################

代码使用了一个葡萄酒数据集,该数据集包含一个有177行和13列的数据框;vintages对象包含类标签。这些数据来自于意大利(皮埃蒙特)同一地区葡萄酒的化学分析,这些葡萄酒源自三种不同的葡萄品种,即NebbioloBarberasGrignolino葡萄。Nebbiolo葡萄酿成的葡萄酒叫做巴罗洛。数据包括每种葡萄酒中多种成分的含量,以及一些光谱变量。

现在,让我们查看每个代码部分的输出。

library("kohonen")

代码的第一行很简单,它加载了我们将用于后续计算的库。具体来说,kohonen库将帮助我们训练 SOM。此外,支持对映射的查询和使用训练好的映射进行预测。

请记住,要安装 R 中初始分发版中没有的库,您必须使用install.package函数。这是安装包的主要函数。它接受一个名称向量和目标库,从仓库中下载包并安装它们。

data("wines")
str(wines)
head(wines)
view (wines)

这些代码行加载了wines数据集,正如我们所预期的,它包含在 R 的分发版中,并将其保存到一个名为data的数据框中。然后,我们使用str函数来紧凑显示数据集的结构。head函数用于返回数据框的前部分或后部分。最后,view函数用于调用数据框对象的电子表格样式数据查看器,如下图所示:

接下来,我们将继续分析代码:

set.seed(1)
som.wines = som(scale(wines), grid = somgrid(5, 5, "hexagonal"))
dim(getCodes(som.wines))
plot(som.wines, main = "Wine data Kohonen SOM")

加载葡萄酒数据并设置seed以确保结果可重复后,我们调用som函数来创建一个5x5的矩阵,其中需要对特征进行聚类。该函数内部执行kohonen处理,结果可以通过特征聚类来看。共有25个聚类,每个聚类都有一组具有相似模式的特征,如下图所示:

代码的下一部分绘制了与som迭代次数相关的最近单元的平均距离:

graphics.off()
par(mfrow = c(1, 1))
plot(som.wines, type = "changes", main = "Wine data: SOM")

下图展示了与迭代次数相关的最近单元的平均距离:

接下来,我们创建了一个training数据集,包含150行,和一个test数据集,包含27行。我们运行 SOM 并使用测试数据进行预测。这里使用了supersom函数。此处的模型是监督式 SOM:

training = sample(nrow(wines), 150)
Xtraining = scale(wines[training, ])
Xtest = scale(wines[-training, ],
 center = attr(Xtraining, "scaled:center"),
 scale = attr(Xtraining, "scaled:scale"))
trainingdata = list(measurements = Xtraining,
 vintages = vintages[training])
testdata = list(measurements = Xtest, vintages = vintages[-training])
mygrid = somgrid(5, 5, "hexagonal")
som.wines = supersom(trainingdata, grid = mygrid)

som.prediction = predict(som.wines, newdata = testdata)
table(vintages[-training], som.prediction$predictions[["vintages"]])

最后,我们调用table函数,它使用交叉分类因素构建一个列出每个因素水平组合计数的列联表,如下所示:

> table(vintages[-training], som.prediction$predictions[["vintages"]])

 Barbera Barolo Grignolino
 Barbera            5      0          0
 Barolo             0     11          0
 Grignolino         0      0         11

kohonen包具有标准的 SOM(自组织映射)功能,并提供了两个扩展功能:用于分类和回归的扩展,以及用于数据挖掘的扩展。同时,它还具有广泛的图形功能用于可视化。

下表列出了kohonen包中可用的函数:

函数名称 描述
som 标准 SOM
xyf, bdk 监督式 SOM;两个并行映射
supersom 带有多个并行映射的 SOM
plot.kohonen 通用绘图函数
summary.kohonen 通用总结函数
map.kohonen 将数据映射到最相似的神经元
predict.kohonen 通用预测属性函数

总结

在本章中,我们探讨了机器学习领域,并观察了神经网络中的学习过程。我们学会了区分监督学习、无监督学习和强化学习。为了详细了解必要的流程,我们还学习了如何训练和测试模型。

随后,我们发现了数据周期的意义,以及数据必须如何收集、清理、转换,再输入模型进行学习。因此,我们深入研究了评估模型,查看测试阶段的预期值是否等于实际值。我们分析了可用于控制模型的不同度量标准,这些度量标准依赖于目标变量的状态。

然后我们发现了理解神经网络时一个重要的概念——反向传播算法,它基于计算来更新每个层级的权重和偏置。

最后,我们介绍了两个 R 中的实际程序,应用了neuralnetkohonen库来实现学习过程。我们可以系统地使用这些基础知识进一步构建复杂的网络。

在下一章中,我们将探索深度神经网络DNN)。我们将了解H2O包的一些基础知识。总体而言,H2O是一个高度用户友好的包,可以用于训练前馈网络或深度自编码器。它支持分布式计算,并提供 Web 界面。通过引入H2O包,像使用任何其他 R 包一样,我们可以进行各种 DNN 建模和处理。

第三章:使用多层神经网络进行深度学习

深度学习是机器学习/人工智能领域的最新热门趋势。它的核心在于构建先进的神经网络。通过使多个隐藏层在神经网络模型中工作,我们可以处理复杂的非线性数据表示。我们通过基础神经网络创建深度学习。深度学习在现实生活中有许多应用案例,例如无人驾驶汽车、医学诊断、计算机视觉、语音识别、自然语言处理NLP)、手写识别、语言翻译等多个领域。

在本章中,我们将讨论深度学习过程:如何训练、测试和部署深度神经网络DNN)。我们将探讨 R 中可用于处理 DNN 的不同包。我们将了解如何使用neuralnet包构建和训练 DNN。最后,我们将分析一个使用 h2o 平台训练和建模 DNN 的例子,h2o 是一个可扩展的开源内存学习平台,用于在大数据集上创建模型并实施高精度的预测方法。

本章涵盖的主题如下:

  • DNN 的类型

  • 深度学习的 R 包

  • 使用neuralnet训练和建模 DNN

  • h2o

在本章结束时,我们将理解深度学习的基本概念,以及如何在 R 环境中实现它。我们将了解不同类型的 DNN。我们将学习如何训练、测试和部署模型。我们将知道如何使用h2o训练和建模 DNN。

深度神经网络(DNN)介绍

随着大数据处理基础设施、GPU 和 GP-GPU 的出现,我们现在能够克服浅层神经网络的挑战,即过拟合和梯度消失问题,使用各种激活函数和 L1/L2 正则化技术。深度学习可以轻松高效地处理大量标注和未标注的数据。

如前所述,深度学习是机器学习中的一类,其中学习发生在多个神经网络层次上。标准的 DNN 图示如下所示:

从前面的图分析中,我们可以注意到与我们迄今为止研究的神经网络之间的显著相似性。然后,我们可以放心,尽管它看起来可能不一样,深度学习实际上只是神经网络的扩展。在这方面,我们在前几章中看到的大部分内容仍然适用。简而言之,DNN 是一个包含两个或更多隐藏层的多层神经网络。这里没有什么特别复杂的内容。通过添加更多层和每层更多神经元,我们增加了模型对训练数据的专门化,但却降低了其在测试数据上的性能。

正如我们所预期的,DNN 是 ANN 的衍生物。通过增加隐藏层的数量,我们构建了 DNN。DNN 有很多变种,以下列举了几个不同的术语:

  • 深度信念网络DBN):它通常是一个前馈网络,数据从一层流向另一层,而没有回流。它至少有一层隐藏层,并且可以有多个隐藏层,从而增加复杂性。

  • 限制玻尔兹曼机RBM):它只有一个隐藏层,且同一组内的节点之间没有连接。它是一个简单的多层感知器(MLP)神经网络模型。

  • 循环神经网络RNN)和长短期记忆网络LSTM):这些网络在组内和组间的数据流动是双向的。

与任何机器学习算法一样,DNN(深度神经网络)也需要构建、训练和评估的过程。以下图展示了深度学习的基本工作流程:

我们在前一图中看到的工作流程与典型的监督学习算法非常相似。但它与其他机器学习算法有何不同呢?

几乎所有的机器学习算法在识别原始输入数据的特征时都会显示出它们的局限性,尤其是在数据复杂且缺乏明显顺序的情况下,比如图像。通常,这种局限性是通过人类的帮助来突破的,人类负责识别机器无法做到的部分。深度学习则去除了这一环节,依赖于训练过程通过输入示例找到最有用的模型。在这种情况下,人类干预仍然是必要的,以便在开始训练之前做出选择,但自动发现特征大大简化了过程。与机器学习提供的其他解决方案相比,神经网络特别具有优势的地方在于其模型的强大泛化能力。

这些特性使得深度学习在几乎所有需要自动学习的任务中都非常有效,尤其是在复杂的层级数据情境下表现尤为突出。它的基础人工神经网络(ANN)形式表现为高度非线性的表示;这些表示通常由多个层以及非线性变换和定制的架构组成。

从本质上讲,深度学习在处理来自现实世界的杂乱数据时表现得非常出色,使其成为未来几年多个技术领域的关键工具。直到最近,它仍然是一个让人感到陌生且令人生畏的领域,但它的成功带来了许多极好的资源和项目,使得开始学习它比以往任何时候都更加容易。

现在我们了解了什么是 DNN,让我们看看 R 开发环境为我们处理这一特定主题提供了哪些工具。

R for DNNs

在前一部分中,我们澄清了一些深度学习基础的关键概念。我们还了解了深度学习在应用时特别方便的特点。此外,它的快速普及也得益于各种编程语言中框架和库的广泛可用性。

R 编程语言因其极易使用而被科学家和程序员广泛采用。此外,R 还有丰富的库集合,能够进行专业的数据可视化和分析,使用最流行的算法。深度学习算法的快速扩散促使了越来越多的深度学习包的创建,甚至在 R 中也不例外。

以下表格展示了使用 R 进行深度学习的各种包/接口:

CRAN 包 支持的神经网络分类 基础语言/供应商
MXNet 前馈神经网络,卷积神经网络(CNN) C/C++/CUDA
darch RBM,DBN C/C++
deepnet 前馈神经网络,RBM,DBN,自编码器 R
h2o 前馈神经网络,自编码器 Java
nnetneuralnet 前馈神经网络 R
Keras 多种深度神经网络(DNN) Python/keras.io
TensorFlow 多种深度神经网络(DNN) C++、Python/Google

MXNet 是一个现代的、可移植的深度学习库,支持多台机器。世界上最大的公司和大学都已将 MXNet 作为机器学习框架。这些公司包括亚马逊、英特尔、Data、百度、微软、Wolfram Research、卡内基梅隆大学、麻省理工学院、华盛顿大学和香港科技大学。

MXNet 是一个开源框架,支持快速建模,并在多种编程语言中支持灵活的编程模型(C++、Python、Julia、MATLAB、JavaScript、Go、R、Scala、Perl 和 Wolfram Language)。

MXNet 框架支持 R 编程语言。MXNet R 包提供灵活高效的 GPU 计算和最先进的 R 深度学习。它允许我们在 R 中使用多个 GPU 进行无缝的张量/矩阵计算。它还允许我们在 R 中构建和定制最先进的深度学习模型,并将其应用于图像分类、数据科学挑战等活动。

darch 框架基于 G. E. Hinton 和 R. R. Salakhutdinov 编写的代码,并在 MATLAB 环境中实现 DBN。该包能够生成多层神经网络(深度架构),并通过作者开发的创新方法对其进行构建。该方法提供了一种与 G. Hinton(2002)发布的对比发散法相结合的预形成方法,并通过常见的训练算法如反向传播或共轭梯度法进行微调。此外,微调监督可以通过 maxout 和 dropout 进行改善,这两种是近年来为提高深度学习微调效果而开发的技术。

deepnet 库是一个相对较小但功能强大的包,提供多种架构供选择。该库实现了一些深度学习架构和神经网络算法,包括反向传播、RBM、DBN、深度自编码器等。与我们分析的其他库不同,它是专为 R 编写的。它有几个功能,包括:

  • nn.train:用于通过 BP 训练单个或多个隐藏层的神经网络

  • nn.predict:用于通过训练好的神经网络预测新的样本

  • dbn.dnn.train:用于训练一个由 DBN 初始化权重的 DNN

  • rbm.train:用于训练一个 RBM

h2o R 包具有构建普通线性回归、K-means、朴素贝叶斯、主成分分析 (PCA)、森林和深度学习(多层 neuralnet 模型)等功能。h2o 是一个外部包,不属于 CRAN,使用 Java 构建,支持多种平台。它是一个开源的大数据数学引擎,能够并行分布式计算机器学习算法。

在前几章中我们广泛讨论了 nnetneuralnet 包。这是两个用于管理 R 中神经网络的包。它们还能够构建和训练多核神经网络,因此依赖于深度学习。

Keras 是一个用 Python 编写的开源神经网络库。它旨在支持快速实验,专注于最小化、模块化和可扩展性。该库包含许多常用神经网络构建块的实现,如层、目标、激活函数、优化器,以及一系列使得处理图像和文本数据更简单的工具。代码托管在 GitHub 上,社区支持论坛包括 GitHub 问题页面、Gitter 频道和 Slack 频道。

TensorFlow 是一个开源的机器学习软件库。它包含一个用于构建和训练神经网络的系统,用来检测和解码模式与关联,方法类似于人类学习采用的方式。它被用于搜索以及 Google 的生产环境中。

使用 neuralnet 训练多层神经网络

在理解了深度学习的基本原理后,是时候将所学技能应用到实际案例中了。我们在上一节中看到,两个我们熟悉的库已经列出在 R for DNNs 部分的可用包中。我指的是我们在前几章中通过实际示例学习使用的 nnetneuralnet 包。由于我们已经对 neuralnet 库有了一些实践经验,我认为我们应该从这里开始,实际探索深度学习的精彩世界。

首先,我们介绍将用于构建和训练网络的数据集。它名为 College 数据集,包含大量美国大学的统计数据,数据来自 1995 年 US News and World Report 的刊物。该数据集来自由卡内基梅隆大学维护的 StatLib 库,并曾在 ASA Section on Statistical Graphics 中使用。

对我们来说,情况更加简化,因为我们不需要获取数据然后导入到 R 中,因为这些数据已包含在 R 包中。我指的是 ISLR 包。我们只需要安装包并加载相应的库。但我们稍后会看到,在详细解释代码时会提到这一点。现在,让我们仅仅查看数据集 College 的内容。它是一个包含 777 个观测值的 dataframe,包含以下 18 个变量:

  • Private:一个包含 NoYes 两个级别的因子,表示私立或公立大学

  • Apps:收到的申请数量

  • Accept:被录取的申请数量

  • Enroll:新入学学生人数

  • Top10perc:来自高年级前 10%学生的新生比例

  • Top25perc:来自高年级前 25%学生的新生比例

  • F.Undergrad:全日制本科生人数

  • P.Undergrad:兼职本科生人数

  • Outstate:州外学费

  • Room.Board:住宿和餐饮费用

  • Books:预计书籍费用

  • Personal:预计个人开销

  • PhD:拥有博士学位的教师比例

  • Terminal:拥有终极学位的教师比例

  • S.F.Ratio:师生比

  • perc.alumni:捐赠校友的比例

  • Expend:每个学生的教学支出

  • Grad.Rate:毕业率

我们的目标是建立一个多层神经网络,能够根据其他 17 个变量的值预测学校是公立还是私立:

###########################################################################
#############Chapter 3 - Deep Learning with neuralnet###################### ###########################################################################
library("neuralnet")
library(ISLR)

data = College
View(data)

max_data <- apply(data[,2:18], 2, max) 
min_data <- apply(data[,2:18], 2, min)
data_scaled <- scale(data[,2:18],center = min_data, scale = max_data - min_data) 

Private = as.numeric(College$Private)-1
data_scaled = cbind(Private,data_scaled)

index = sample(1:nrow(data),round(0.70*nrow(data)))
train_data <- as.data.frame(data_scaled[index,])
test_data <- as.data.frame(data_scaled[-index,])

n = names(train_data)
f <- as.formula(paste("Private ~", paste(n[!n %in% "Private"], collapse = " + ")))
deep_net = neuralnet(f,data=train_data,hidden=c(5,3),linear.output=F)
plot(deep_net)

predicted_data <- compute(deep_net,test_data[,2:18])
print(head(predicted_data$net.result))
predicted_data$net.result <- sapply(predicted_data$net.result,round,digits=0)

table(test_data$Private,predicted_data$net.result)

和往常一样,我们将逐行分析代码,详细解释所有用于捕获结果的功能。

library("neuralnet")
library(ISLR)

和往常一样,初始代码的前两行用于加载运行分析所需的库。

请记住,要安装 R 初始分发版中没有的库,必须使用install.package函数。这是安装包的主要函数。它接收一个名称向量和一个目标库,从仓库下载包并进行安装。此函数应该仅使用一次,而不是每次运行代码时都使用。

data = College
View(data)

该命令加载了 College 数据集,正如我们所预期,它包含在 ISLR 库中,并将其保存到一个给定的数据框中。使用 View 函数查看任意 R 对象的结构的简洁显示。以下图显示了 College 数据集中的部分数据:

每个大学都有一系列统计数据,如何表示;行表示观察,列则呈现所检测的特征:

max_data <- apply(data[,2:18], 2, max) 
min_data <- apply(data[,2:18], 2, min)
data_scaled <- scale(data[,2:18],center = min_data, scale = max_data - min_data) 

在这段代码中,我们需要对数据进行归一化。

请记住,在训练神经网络之前进行数据归一化是良好的实践。通过归一化,数据单位被消除,允许你轻松比较来自不同地点的数据。

对于这个例子,我们将使用最小-最大方法(通常称为特征缩放)来将所有数据缩放到【0,1】的范围内。在应用所选择的归一化方法之前,必须计算每个数据库列的最小值和最大值。这个过程在我们分析的第二章《神经网络中的学习过程》示例中已经采用。

最后一行通过采用预期的归一化规则来缩放数据。请注意,我们仅对最后17行(从218)进行了归一化,排除了第一列Private,该列包含有NoYes两个层次的因子,表示私立或公立大学。这个变量将是我们即将构建的网络中的目标。为了验证我们所说的内容,可以检查数据集中变量的类型。为此,我们将使用str函数,以紧凑的方式显示一个任意 R 对象的结构:

> str(data)
'data.frame': 777 obs. of 18 variables:
 $ Private : Factor w/ 2 levels "No","Yes": 2 2 2 2 2 2 2 2 2 2 ...
 $ Apps : num 1660 2186 1428 417 193 ...
 $ Accept : num 1232 1924 1097 349 146 ...
 $ Enroll : num 721 512 336 137 55 158 103 489 227 172 ...
 $ Top10perc : num 23 16 22 60 16 38 17 37 30 21 ...
 $ Top25perc : num 52 29 50 89 44 62 45 68 63 44 ...
 $ F.Undergrad: num 2885 2683 1036 510 249 ...
 $ P.Undergrad: num 537 1227 99 63 869 ...
 $ Outstate : num 7440 12280 11250 12960 7560 ...
 $ Room.Board : num 3300 6450 3750 5450 4120 ...
 $ Books : num 450 750 400 450 800 500 500 450 300 660 ...
 $ Personal : num 2200 1500 1165 875 1500 ...
 $ PhD : num 70 29 53 92 76 67 90 89 79 40 ...
 $ Terminal : num 78 30 66 97 72 73 93 100 84 41 ...
 $ S.F.Ratio : num 18.1 12.2 12.9 7.7 11.9 9.4 11.5 13.7 11.3 11.5 ...
 $ perc.alumni: num 12 16 30 37 2 11 26 37 23 15 ...
 $ Expend : num 7041 10527 8735 19016 10922 ...
 $ Grad.Rate : num 60 56 54 59 15 55 63 73 80 52 ...

如预期的那样,第一个变量是Factor类型,具有两个levelsNoYes。对于剩余的17个变量,它们是数值型的。正如在第一章《神经网络与人工智能概念》中预期的那样,只有数值数据可以用于模型,因为神经网络是一个具有逼近函数的数学模型。所以我们遇到了第一个变量Private的问题。别担心,这个问题可以很容易地解决;只需将其转换为数值变量即可:

Private = as.numeric(College$Private)-1
data_scaled = cbind(Private,data_scaled)

在这方面,第一行将Private变量转换为数值型,而第二行代码则用于用该变量和剩余的17个适当归一化的变量重新构建数据集。为此,我们使用cbind函数,它接受一系列向量、矩阵或数据框作为参数,并按列或行分别组合:

index = sample(1:nrow(data),round(0.70*nrow(data)))
train_data <- as.data.frame(data_scaled[index,])
test_data <- as.data.frame(data_scaled[-index,])

现在是时候将数据拆分用于网络的训练和测试了。在刚才建议的代码的第一行中,数据集被拆分为70:30,目的是将70%的数据用于训练网络,剩余的30%用于测试网络。在第三行中,名为 data 的数据框被细分为两个新的数据框,分别称为train_datatest_data

n = names(train_data)
f <- as.formula(paste("Private ~", paste(n[!n %in% "Private"], collapse = " + ")))

在这段代码中,我们首先使用names函数恢复所有变量名。该函数用于获取或设置对象的名称。接下来,我们构建将用于构建网络的公式,因此我们使用neuralnet函数来构建和训练网络。到目前为止,一切只是用于准备数据。现在是时候构建网络了:

deep_net = neuralnet(f,data=train_data,hidden=c(5,3),linear.output=F)

这是代码中的关键行。这里,网络被构建并训练完成;让我们详细分析一下。我们曾预期使用neuralnet库来构建我们的 DNN。但与我们之前构建单隐层网络的情况相比,发生了什么变化呢?一切都发生在hidden参数的设置上。

记住,hidden参数必须包含一个整数向量,指定每一层中隐藏神经元(顶点)的数量。

在我们的案例中,我们将隐藏层设置为包含向量(5,3),这对应于两个隐藏层,第一个隐藏层有五个神经元,第二个隐藏层有三个神经元。

plot(deep_net)

前一行简单地绘制了网络图,如下图所示:

如我们所见,网络已经构建并训练完成,我们只需要验证其预测能力:

predicted_data <- compute(deep_net,test_data[,2:18])
print(head(predicted_data$net.result))

为了预测保留用于测试的数据,我们可以使用compute方法。这是nn类对象的方法,通常由neuralnet函数生成。给定一个经过训练的神经网络,它计算所有神经元在特定自变量向量上的输出。确保新矩阵或数据框中的自变量顺序与原始神经网络中的一致是至关重要的。然后,为了可视化,使用print函数展示预测结果的前几行,如下所示:

> print(head(predicted_data$net.result))
 [,1]
Abilene Christian University 0.1917109322
Adelphi University           1.0000000000
Adrian College               1.0000000000
Agnes Scott College          1.0000000000
Albertus Magnus College      1.0000000000
Albion College               1.0000000000

如所见,预测结果以小数形式提供,接近预期的两个类别的值(一和零),但并不完全等于这些值。我们需要精确假设这些值,以便与当前值进行比较。为此,我们将使用sapply()函数将这些结果四舍五入到零或一类别,从而与测试标签进行评估:

predicted_data$net.result <- sapply(predicted_data$net.result,round,digits=0)

正如预期的那样,sapply()函数将两个可用类别的预测结果进行了四舍五入。现在我们拥有了进行比较所需的一切,以评估 DNN 作为预测工具的效果:

table(test_data$Private,predicted_data$net.result)

为了进行比较,我们依赖于混淆矩阵。构建它时,只需使用table函数。实际上,table函数利用交叉分类因子来构建每个因子水平组合的计数列联表。

混淆矩阵是一种特定的表格布局,用于可视化算法的表现。每一行代表实际类别中的实例,而每一列代表预测类别中的实例。术语“混淆矩阵”源于它能清楚地显示系统是否将两个类别混淆。

那么,让我们看看得到的结果:

> table(test_data$Private,predicted_data$net.result)

 0   1
 0 49   8
 1  9 167

让我们理解一下这些结果。首先,让我们记住,在混淆矩阵中,主对角线上的术语表示正确预测的数量,即预测类别与实际类别的实例相符的数量。看来在我们的模拟中,一切顺利。事实上,我们得到了49个类别0No)的结果,和167个类别1Yes)的结果。但现在让我们分析另外两个术语,它们表示模型犯的错误。

如第二章中定义的,神经网络中的学习过程8是 FN,9是 FP。在这方面,我们回顾一下,FN 表示实际数据中为负的预测为正,而 FP 表示实际数据中为正的预测为负。我们可以通过再次使用table函数来检查这一点:

> table(test_data$Private)
 0   1 
 57 176

这些表示实际结果,特别是,57个结果属于类别0176个结果属于类别1。通过求和混淆矩阵中行的数据,我们实际上得到了这些值的实际结果:

> 49 + 8
[1] 57
> 9 + 167
[1] 176

现在我们再次使用table函数来获取预测数据中的出现次数:

> table(predicted_data$net.result)
 0   1 
 58 175

这些表示预测结果,特别是,58个结果属于类别0175个结果属于类别1。通过求和混淆矩阵中列的数据,我们实际上得到了这些值的结果:

> 49 + 9
[1] 58
> 8 + 167
[1] 175

在这一点上,我们通过使用混淆矩阵中的数据来计算模拟的准确率。让我们记住,准确率由以下公式定义:

这里:

TP = 真阳性

TN = 真负性

FP = 假阳性

FN = 真负性

让我们通过以下代码示例来看一下:

> Acc = (49 + 167) / (49 + 167 + 9 + 8) 
> Acc
[1] 0.9270386266

我们得到了大约 93%的准确率,确认我们的模型能够很好地预测数据并取得良好结果。

使用 H2O 训练和建模 DNN

在本节中,我们将介绍使用h2o训练和建模 DNN 的示例。h2o是一个开源的内存中可扩展的机器学习和 AI 平台,用于构建大数据集的模型并实现高准确率的预测方法。h2o库在许多组织中被大规模应用,用于实现数据科学并提供构建数据产品的平台。h2o可以运行在单个笔记本电脑上,也可以在高性能可扩展服务器的集群上运行。它运行非常快,充分利用了机器架构的进展和 GPU 处理。它具有高准确度的深度学习、神经网络和其他机器学习算法的实现。

如前所述,h2o R 包包含用于构建一般线性回归、K 均值、朴素贝叶斯、PCA、森林和深度学习(多层neuralnet模型)等功能。h2o包是一个外部包,不属于 CRAN,使用 Java 构建。它适用于多种平台。

我们将通过以下代码在 R 中安装h2o

install.packages("h2o")

我们得到了以下结果:

> install.packages("h2o")
Installing package into ‘C:/Users/Giuseppe/Documents/R/win-library/3.4’
(as ‘lib’ is unspecified)
trying URL 'https://cran.rstudio.com/bin/windows/contrib/3.4/h2o_3.10.5.3.zip'
Content type 'application/zip' length 73400625 bytes (70.0 MB)
downloaded 70.0 MB
package ‘h2o’ successfully unpacked and MD5 sums checked
The downloaded binary packages are in
 C:\Users\Giuseppe\AppData\Local\Temp\RtmpGEc5iI\downloaded_packages

为了测试这个包,让我们通过以下示例来使用流行的数据集 Irisdataset。我所指的是鸢尾花数据集,这是英国统计学家和生物学家 Ronald Fisher 在其 1936 年的论文《多重测量在分类问题中的应用》中引入的一个多变量数据集,用作线性判别分析的例子。

数据集包含来自三种鸢尾花(鸢尾花 setosa,鸢尾花 virginica 和鸢尾花 versicolor)的 50 个样本。每个样本测量了四个特征:花萼和花瓣的长度和宽度,单位是厘米。

包含以下变量:

  • Sepal.Length 单位:厘米

  • Sepal.Width 单位:厘米

  • Petal.Length 单位:厘米

  • Petal.Width 单位:厘米

  • 类别:setosaversicolourvirginica

下图紧凑地显示了 iris 数据集的结构:

我们希望构建一个分类器,能够根据花萼和花瓣的大小来对花的种类进行分类:

##########################################################################
#################Chapter 3 - Deep Learning with H2O and R################# ##########################################################################

library(h2o)

c1=h2o.init(max_mem_size = "2G", 
 nthreads = 2, 
 ip = "localhost", 
 port = 54321)

data(iris)
summary(iris)

iris_d1 <- h2o.deeplearning(1:4,5,
 as.h2o(iris),hidden=c(5,5),
 export_weights_and_biases=T)
iris_d1
plot(iris_d1)

h2o.weights(iris_d1, matrix_id=1)
h2o.weights(iris_d1, matrix_id=2)
h2o.weights(iris_d1, matrix_id=3)
h2o.biases(iris_d1, vector_id=1)
h2o.biases(iris_d1, vector_id=2)
h2o.biases(iris_d1, vector_id=3)

#plot weights connecting `Sepal.Length` to first hidden neurons
plot(as.data.frame(h2o.weights(iris_d1, matrix_id=1))[,1])

##########################################################################

现在,让我们通过代码来了解如何应用 h2o 包解决模式识别问题。

在继续之前,必须说明的是,在 R 中运行 h2o 需要 Java 8 运行时。请提前验证您计算机上安装的 Java 版本,并最终从 www.java.com/en/download/win10.jsp 下载 Java 8 版本。

下图展示了来自 Oracle 网站的 Java 下载页面:

此外,h2o 包是由一些必需的包构建的;因此,为了正确安装 h2o 包,请记得安装以下依赖项,所有这些都可以在 CRAN 中找到:

  • RCurl

  • bitops

  • rjson

  • jsonlite

  • statmod

  • tools

在成功安装 h2o 包后,我们可以继续加载库:

library(h2o)

此命令将在 R 环境中加载该库。返回以下信息:

Your next step is to start H2O:
 > h2o.init()
For H2O package documentation, ask for help:
 > ??h2o
After starting H2O, you can use the Web UI at http://localhost:54321
For more information visit http://docs.h2o.ai
c1=h2o.init(max_mem_size = "2G", 
 nthreads = 2, 
 ip = "localhost", 
 port = 54321)

我们按照 R 提示中的说明进行操作:

c1=h2o.init(max_mem_size = "2G", 
 nthreads = 2, 
 ip = "localhost", 
 port = 54321)

h20.init 函数初始化 h2o 引擎,最大内存大小为 2 GB,并使用两个并行核心。初始化 h2o 控制台后,我们运行此脚本时会收到以下信息:

> c1=h2o.init(max_mem_size = "2G", nthreads = 2)
H2O is not running yet, starting it now...

Note: In case of errors look at the following log files:
 C:\Users\Giuseppe\AppData\Local\Temp\RtmpU3xPvT/h2o_Giuseppe_started_from_r.out
 C:\Users\Giuseppe\AppData\Local\Temp\RtmpU3xPvT/h2o_Giuseppe_started_from_r.err

java version "1.8.0_144"
Java(TM) SE Runtime Environment (build 1.8.0_144-b01)
Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode)

Starting H2O JVM and connecting: . Connection successful!

R is connected to the H2O cluster: 
 H2O cluster uptime: 6 seconds 912 milliseconds 
 H2O cluster version: 3.10.5.3 
 H2O cluster version age: 2 months and 9 days 
 H2O cluster name: H2O_started_from_R_Giuseppe_woc815 
 H2O cluster total nodes: 1 
 H2O cluster total memory: 1.78 GB 
 H2O cluster total cores: 4 
 H2O cluster allowed cores: 2 
 H2O cluster healthy: TRUE 
 H2O Connection ip: localhost 
 H2O Connection port: 54321 
 H2O Connection proxy: NA 
 H2O Internal Security: FALSE 
 R Version: R version 3.4.1 (2017-06-30)

一旦 h2o 被初始化,控制台可以通过任何浏览器访问,只需指向 localhost:54321h2o 库运行在 JVM 上,控制台允许:

控制台直观易用,并提供与 h[2]o 引擎交互的界面。我们可以训练和测试模型,并基于这些模型进行预测。第一个文本框标记为 CS,允许我们输入要执行的例程。assist 命令会列出可用的例程。让我们继续分析以下示例代码。

data(iris)
summary(iris)

第一个命令加载了 iris 数据集,该数据集包含在数据集库中,并将其保存在指定的数据框中。然后,我们使用 summary 函数来生成数据集结果的摘要。该函数调用依赖于第一个参数的类的特定方法。结果如下所示:

> summary(iris)
 Sepal.Length    Sepal.Width     Petal.Length    Petal.Width 
 Min.   :4.300   Min.   :2.000   Min.   :1.000   Min.   :0.100 
 1st Qu.:5.100   1st Qu.:2.800   1st Qu.:1.600   1st Qu.:0.300
 Median :5.800   Median :3.000   Median :4.350   Median :1.300 
 Mean   :5.843   Mean   :3.057   Mean   :3.758   Mean   :1.199 
 3rd Qu.:6.400   3rd Qu.:3.300   3rd Qu.:5.100   3rd Qu.:1.800 
 Max.   :7.900   Max.   :4.400   Max.   :6.900   Max.   :2.500      Species
setosa    :50 
versicolor:50 
virginica :50 

让我们分析接下来的代码:

iris_d1 <- h2o.deeplearning(1:4,5,
 as.h2o(iris),hidden=c(5,5),
 export_weights_and_biases=T)

h2o.deeplearning 函数是 h2o 中的一个重要函数,可用于多种操作。此函数使用 CPU 构建 DNN 模型,并在 H2OFrame 上构建一个前馈多层人工神经网络(ANN)。hidden 参数用于设置隐藏层的数量和每个隐藏层的神经元数量。在我们的例子中,我们设置了一个具有两个隐藏层的 DNN,每个隐藏层有 5 个神经元。最后,参数 export_weights_and_biases 告诉我们,权重和偏置可以存储在 H2OFrame 中,并可以像其他数据框一样访问以进行进一步处理。

在继续代码分析之前,需要做一个澄清。细心的读者可能会问,基于什么评估我们选择了每个隐藏层的隐藏层数量和神经元数量。不幸的是,没有一个精确的规则,甚至没有一个数学公式可以帮助我们确定哪些数字适合特定问题。这是因为每个问题都不同,每个网络对系统的近似方式也不同。那么,是什么使得一个模型和另一个模型之间有所不同呢?答案显而易见,而且非常清楚:那就是研究者的经验。

我能给出的建议,来源于我在数据分析方面的丰富经验,就是尝试,尝试,再尝试。实验活动的秘密就在于此。在神经网络的情况下,这意味着要尝试搭建不同的网络,然后验证它们的性能。例如,在我们的例子中,我们可以从一个包含两个隐藏层且每个隐藏层有 100 个神经元的网络开始,然后逐渐减少这些值,最终达到我在示例中提出的值。这个过程可以通过 R 所拥有的迭代结构来自动化。

然而,有些事情可以说,例如,对于神经元数量的最佳选择,我们需要知道:

  • 神经元数量过少会导致系统误差较大,因为预测因子可能过于复杂,难以为少量神经元所捕捉。

  • 神经元数量过多会导致训练数据过拟合,且泛化效果差。

  • 每个隐藏层的神经元数量应介于输入层和输出层的大小之间,可能是其均值。

  • 每个隐藏层的神经元数量不应超过输入神经元数量的两倍,否则你可能会严重过拟合。

话虽如此,我们回到代码:

iris_d1

在 R 提示符下,此命令打印出我们刚创建的模型的简要描述,如下图所示:

通过仔细分析前面的图,我们可以清晰地区分模型的细节以及混淆矩阵。现在让我们看看训练过程是如何进行的:

plot(iris_d1)

plot方法会根据 h2o 模型的类型来选择正确的评分历史记录。参数仅限于特定模型类型的评分历史记录中可用的内容,如下图所示:

在此图中,展示了训练分类误差与迭代次数(epochs)的关系,我们可以看到梯度下降过程以及误差随着迭代次数的增加而减少。数据集的迭代次数(流式处理)可以是小数,默认值为10

h2o.weights(iris_d1, matrix_id=1)
h2o.weights(iris_d1, matrix_id=2)
h2o.weights(iris_d1, matrix_id=3)
h2o.biases(iris_d1, vector_id=1)
h2o.biases(iris_d1, vector_id=2)
h2o.biases(iris_d1, vector_id=3)

代码的最后六行简单地打印出三种鸢尾花物种的权重和偏差的简要总结,如下所示:

> h2o.weights(iris_d1, matrix_id=1) 
 Sepal.Length Sepal.Width Petal.Length  Petal.Width
1 -0.013207575 -0.06818321  -0.02756812  0.092810206
2  0.036195096  0.02568028   0.05634377  0.035429616
3 -0.002411760 -0.11541270   0.08219513  0.001957144
4  0.091338813 -0.03271343  -0.25603485 -0.205898494
6 -0.151234403  0.01785624  -0.11815275 -0.110585481 
[200 rows x 4 columns] 

> h2o.biases(iris_d1, vector_id=1) 
C11 0.48224932 0.47699773 0.48994124 0.49552965 0.48991496 0.4739439 
[200 rows x 1 column] 

出于空间原因,我们将自己限制只查看setosa物种的权重和偏差。在以下代码中,我们再次使用了绘图函数:

plot(as.data.frame(h2o.weights(iris_d1, matrix_id=1))[,1])

此命令绘制了第一隐藏层神经元的权重与花萼长度的关系,如下图所示:

现在,让我们花点时间分析结果;特别是,我们恢复了刚才在模型摘要屏幕上看到的混淆矩阵。要调用混淆矩阵,我们可以使用h2o.confusionMatrix函数,如下代码示例所示,它从h2o对象中获取单个或多个混淆矩阵。

> h2o.confusionMatrix(iris_d1)
Confusion Matrix: Row labels: Actual class; Column labels: Predicted class 
 setosa versicolor virginica  Error      Rate
setosa         50          0         0 0.0000 =  0 / 50
versicolor      0         48         2 0.0400 =  2 / 50
virginica       0          2        48 0.0400 =  2 / 50
Totals         50         50        50 0.0267 = 4 / 150

从混淆矩阵的分析中可以看出,模型能够正确地分类三种花卉物种,只犯了四个错误。这些错误相当分布在两个物种中:versicolorvirginica。然而,setosa物种在所有50次出现中都被正确分类。那么,为什么会这样呢?为了理解这一点,我们来看一下原始数据。在多维数据的情况下,最好的方法是绘制数据集中选定变量的散点图矩阵:

> pairs(iris[1:4], main = "Scatterplot matrices of Iris Data", pch = 21, bg = c("red", "green3", "blue")[unclass(iris$Species)])

结果如下图所示:

让我们详细分析一下刚才提出的图表。变量沿着从左上到右下的对角线排列。然后,每个变量都会与其他变量进行绘制。例如,第一列第二个框是Sepal.LengthSepal.Width的单独散点图,Sepal.Length作为X轴,Sepal.Width作为Y轴。这个图在第二列的第一个图中得到了复制。从本质上讲,整个散点图右上角的框是左下角图的镜像。

从刚刚看到的图表分析中,可以看出versicolorvirginica物种的边界重叠。这使我们明白,当模型在这些区域进行分类时,可能会出现错误。我们可以看到setosa物种的情况,它的边界与其他花卉物种相距甚远,且没有任何分类错误。

也就是说,我们根据花瓣和萼片的大小来评估模型在分类花卉物种方面的准确性:

> h2o.hit_ratio_table(iris_d1)
Top-3 Hit Ratios: 
 k hit_ratio
1 1 0.973333
2 2 1.000000
3 3 1.000000

结果表明,基于第一个假设的模拟将物种的分类准确率排到了97百分比。我认为这是一个不错的结果;模型拟合数据非常好。但我们如何衡量这个特性呢?找到更好拟合的方法之一是计算决定系数(R-squared)。在h2o中计算 R-squared 的方法是使用h2o.r2方法:

> h2o.r2(iris_d1)
[1] 0.9596034

现在让我们理解一下我们所计算的内容以及如何解读结果。决定系数(R-squared)衡量模型预测数据的能力,其值介于零和一之间。决定系数的值越高,模型预测数据的能力越强。

我们得到了0.96的值,所以根据我们之前所说的,这是一个很棒的结果。为了确认这一点,我们需要将其与另一个模拟模型的结果进行比较。因此,我们基于相同的数据构建了一个线性回归模型,即iris数据集。

要构建线性回归模型,我们可以使用glm函数。该函数用于拟合广义线性模型,通过给出线性预测符号描述和误差分布描述来指定:

m=iris.lm <- h2o.glm(x=2:5,y=1,training_frame=as.h2o(iris))

现在我们计算模型的决定系数:

> h2o.r2(m)
[1] 0.8667852

现在我们可以比较基于 DNN 的模型和线性回归模型。DNN 提供的 R-squared 值为0.96,而回归模型的 R-squared 值为0.87。显然,DNN 提供了更好的性能。

最后,分析神经网络专家认为重要的参数可能会很有用,如下表所示:

Argument Description
x 包含用于构建模型的预测变量名称或索引的向量。如果x缺失,则使用除y外的所有列。
y 模型中响应变量的名称。如果数据没有包含表头,则这是第一列的索引,从左到右递增(响应变量必须是整数或类别变量)。
model_id 这是模型的目标id;如果没有指定,则会自动生成。
standardize 这是一个逻辑函数。如果启用,它会自动标准化数据。如果禁用,用户必须提供适当缩放的输入数据。默认值为TRUE
activation 这是一个激活函数。它必须是TanhTanhWithDropoutRectifierRectifierWithDropoutMaxoutMaxoutWithDropout之一。默认值为Rectifier
hidden 该参数指定隐藏层的大小(例如,[100, 100])。默认值为[200, 200]
epochs 数据集应被迭代(流式处理)的次数,可以是小数。默认值为10
adaptive_rate 这是一个逻辑参数,用于指定自适应学习率。默认值为TRUE
rho 这是描述自适应学习率时间衰减因子的参数(类似于先前的更新)。默认值为0.99
rate_annealing 学习率退火由rate/(1 + rate_annealing * samples)给出。默认值为1e-06
rate_decay 这是层之间的学习率衰减因子(N^(th)层: rate * rate_decay ^ (n - 1))。默认值为1
input_dropout_ratio 输入层的 dropout 比率(可以提高泛化能力,尝试0.10.2)。默认值为0
hidden_dropout_ratios 隐藏层的 dropout 比率可以提高泛化能力。每个隐藏层指定一个值。默认值为0.5
l1 L1 正则化可以增加稳定性并提高泛化能力,它使得许多权重变为0。默认值为0
l2 L2 正则化可以增加稳定性并提高泛化能力,它使得许多权重变得很小。默认值为0
initial_weights 这是一个H2OFrame ID 的列表,用于初始化模型的权重矩阵。
initial_biases 这是一个H2OFrame ID 的列表,用于初始化模型的偏置向量。
loss 损失函数必须是AutomaticCrossEntropyQuadraticHuberAbsoluteQuantile之一。默认值为Automatic
distribution 分布函数必须是AUTObernoullimultinomialgaussianpoissongammatweedielaplacequantilehuber之一。默认值为AUTO
score_training_samples 它是用于评分的训练集样本数量(0 表示所有样本)。默认值为10000
score_validation_samples 它是用于评分的验证集样本数量(0 表示所有样本)。默认值为0
classification_stop 分类误差比例的停止准则,针对训练数据(-1表示禁用)。默认值为0
regression_stop 这是回归误差(均方误差)在训练数据上的停止准则(-1表示禁用)。默认值为1e-06
stopping_rounds 基于stopping_metric收敛情况的早期停止。如果stopping_metric的长度为k的简单移动平均值在k:=stopping_rounds次评分事件中没有改善,则停止训练(0表示禁用)。默认值为5
max_runtime_secs 这是模型训练的最大允许运行时间,单位为秒。使用0可以禁用该设置。默认值为0
diagnostics 启用隐藏层的诊断。默认值为TRUE
fast_mode 启用快速模式(反向传播时的微小近似)。默认值为TRUE
replicate_training_data 在每个节点上复制整个训练数据集,以加快小数据集上的训练。默认值为TRUE
single_node_mode 在单个节点上运行,用于微调模型参数。默认值为FALSE
shuffle_training_data 启用训练数据的随机打乱(如果训练数据被复制且train_samples_per_iteration接近#nodes x #rows,或者如果使用balance_classes,建议启用)。默认值为FALSE
missing_values_handling 处理缺失值时,必须选择MeanImputationSkip。默认值为MeanImputation
quiet_mode 启用安静模式,减少标准输出的内容。默认值为FALSE
verbose 将评分历史打印到控制台(对于 GBM、DRF 和 XGBoost,每棵树的指标;对于深度学习,每个 epoch 的指标)。默认值为False
autoencoder 逻辑自编码器,默认值为FALSE
export_weights_and_biases 是否将神经网络的权重和偏差导出到H2OFrame。默认值为FALSE
mini_batch_size 小批量大小(较小的值有助于更好地拟合,而较大的值可以加速并更好地泛化)。默认值为1

有一些与 R 和h2o相关的深度学习函数。以下列出了一些有用的函数:

函数 描述
predict.H2Omodel 返回一个包含概率和默认预测的H2OFrame对象。
h2o.deepwater 使用多个本地 GPU 后端构建深度学习模型。在包含各种数据源的H2OFrame上构建 DNN。
as.data.frame.H2OFrame H2OFrame转换为数据框。
h2o.confusionMatrix 显示分类模型的混淆矩阵。
print.H2OFrame 打印H2OFrame
h2o.saveModel 将一个h2o模型对象保存到磁盘。
h2o.importFile 将文件导入到 h2o 中。

使用 H2O 的深度自编码器

自编码器是神经网络中的无监督学习方法。在第七章,神经网络的应用案例 – 高级主题中,我们将进一步了解这些内容。h2o可以通过使用深度自编码器来检测异常。要训练这样的模型,使用相同的函数h2o.deeplearning(),只需要对参数进行一些更改:

anomaly_model <- h2o.deeplearning(1:4,
 training_frame = as.h2o(iris),
 activation = "Tanh",
 autoencoder = TRUE,
 hidden = c(50,20,50),
 sparse = TRUE,
 l1 = 1e-4,
 epochs = 100)

autoencoder=TRUEdeeplearning方法设置为使用自编码器技术的无监督学习方法。我们仅使用训练数据,而没有测试集和标签。需要使用深度autoencoder而不是前馈网络的事实是由autoencoder参数指定的。

我们可以选择不同层中要包含的隐藏单元的数量。如果选择一个整数值,得到的模型被称为朴素自编码器

总结

深度学习是一个非常重要的学科,涉及从图像识别到语音识别及 AI 相关活动。市场上有许多深度学习的产品和包,其中包括KerasTensorFlowh2o以及其他许多。

在这一章,我们学习了深度学习的基础知识、DNN 的许多变种、最重要的深度学习算法以及深度学习的基本工作流程。我们探索了 R 中处理 DNN 的不同包。

为了了解如何构建和训练 DNN,我们分析了一个使用neuralnet包实现的 DNN 实践示例。我们学习了如何通过各种现有的技术对数据进行归一化,以去除数据单位,从而便于比较来自不同位置的数据。我们还了解了如何将数据拆分用于网络的训练和测试。我们学会了使用neuralnet函数来构建和训练多层神经网络。因此,我们理解了如何使用训练好的网络进行预测,并学会了使用混淆矩阵来评估模型性能。

我们了解了h2o包的一些基础知识。总体而言,h2o包是一个非常用户友好的包,可以用来训练前馈网络或深度自编码器。它支持分布式计算,并提供了一个 web 界面。通过像使用其他包一样引入h2o包,我们可以进行各种类型的 DNN 模型构建和处理。h2o的强大功能可以通过包中提供的多种特性来充分利用。

在下一章,我们将了解什么是感知机,以及可以使用基本感知机构建的应用程序。我们将学习在 R 环境中实现简单感知机的函数。我们还将学习如何训练和建模一个 MLP。我们将探索线性可分的分类器。

第四章:感知机神经网络建模 – 基本模型

到目前为止,我们已经了解了神经网络的基础知识以及学习部分的工作原理。在本章中,我们将研究神经网络架构的基本且简单的形式——感知机。

感知机被定义为神经网络的基本构建模块。在机器学习中,感知机是一种用于二分类器的监督学习算法。它们将输出分类为二元:TRUE/FALSE1/0

本章帮助理解以下主题:

  • 感知机的解释

  • 线性可分分类器

  • 简单的感知机实现函数

  • 多层感知机MLPs

到本章结束时,我们将理解感知机的基本概念及其在神经网络算法中的应用。我们将发现线性可分分类器。我们将学习在 R 环境中的简单感知机实现函数。我们将了解如何训练和建模一个多层感知机(MLP)。

感知机及其应用

感知机可以理解为任何接收多个输入并产生一个输出的实体。它是最简单的神经网络形式。感知机由 Frank Rosenblatt 于 1958 年提出,作为具有输入层和输出层的实体,并基于最小化误差的学习规则。这个学习函数叫做误差反向传播,它根据网络的实际输出与给定输入之间的差异,改变连接权重(突触)。

当时的热情极高,控制论行业也随之诞生。但后来,科学家 Marvin Minsky 和 Seymour Papert(1969 年)展示了感知机的局限性。事实上,感知机经过适当的训练后只能识别线性可分函数。例如,XOR 逻辑函数无法通过感知机实现。

以下图像展示了 Frank Rosenblatt 在康奈尔航空实验室(1957-1959)时,研究 Mark I 感知机分类器的情景:

潜在地,一个多层感知机网络可以解决更复杂的问题,但训练的计算复杂性增加使得这条路径变得不可行。直到最近,我们才开始重新考虑这种操作实体的实用性。

在单一形式中,感知机有一个神经元单元,接受输入并生成一组输出。

例如,让我们看看以下图像:

这里x[1], x[2],.., x[n]是输入集合,x[0]是偏置。x[0]被设置为1。输出yw[i]x[i]的加权和。符号函数在加权和计算后应用。

它将输出分隔为:

  • 如果y>0,输出为1

  • 如果y<=0,输出为-1

偏置是常数,与权重w[0]相关。这个感知机充当线性分隔器,将输出分为-1+1两类。

请注意,这里没有反向传播,权重更新是通过我们很快会看到的步骤进行的。这里有一个阈值设置,它决定了输出的值。输出是二值的(-1+1),可以设置为零或一。

因此,感知机是一个简单的分类函数,直接做出预测。其功能的核心在于权重,以及我们如何更新权重,以便对 y 做出最佳预测。

这个例子是 简单感知机 或基础感知机,输出是二进制的:0/1 真/假 +1/-1

另一种类型的感知机叫做 多类感知机,它可以对动物进行多种可能标签的分类,比如狗、猫或鸟。

在下图中展示了一个简单感知机架构与多类感知机架构的对比:

通过修改权重向量,我们可以修改感知机的输出,以提高学习或存储特性。例如,我们可以尝试指导感知机,使得给定输入 x 时,输出 y 尽可能接近预先选定的 y 实际值。然而,单个感知机的计算能力是有限的,其性能很大程度上依赖于输入选择和你想要实现的函数选择。

实际上,输入可以限制为所有可能输入的一个子集,或者根据某种预定的概率分布随机提取。较小程度上,系统的性能也取决于实际输出与预期输出之间的距离如何量化。

一旦你识别出了学习问题,你就可以尝试为给定的问题找到最佳的权重分配。

简单感知机 – 线性可分分类器

如我们所见,简单感知机是一个单层神经单元,它是一个线性分类器。它是一个只能生成两种输出模式的神经元,这些模式可以合成为 激活非激活。其决策规则通过 阈值 行为实现:如果组成输入层的各个神经元的激活模式的总和,按它们的权重加权后,超过某个阈值,那么输出神经元将采用输出模式 激活。反之,输出神经元将保持在 非激活 状态。

如前所述,输出是 权重输入*的总和,并在其上应用一个函数;输出是 +1 (y>0)-1(y<=0),如下面的图所示:

我们可以看到这里的线性交互;输出 y 与输入是线性相关的。

与大多数神经网络模型一样,即使在感知器中,也可以通过修改突触连接权重来实现学习功能。在训练阶段开始时,感知器突触连接的权重w是完全随机的。对于训练,我们有若干个示例及其相关的正确分类。网络依次呈现待分类的不同案例,每次网络处理其响应(大于阈值或小于阈值)。如果分类正确(网络输出与预期一致),则训练算法不会做出任何更改。相反,如果分类不正确,算法会改变突触权重,以期提高网络的分类性能。

单一感知机是一个在线学习者。权重更新通过以下步骤发生:

  1. 获取x并输出标签y

  2. 更新f(x)w

  3. 如果f(x)=y,则标记为完成;否则,修正它。

  4. 现在根据错误调整评分:

f(x) = sign(权重输入之和)*,此时可能出现错误。

如果y=+1f(x)=-1,则wx*太小,增大它。

如果y=-1f(x)=+1,则wx*太大,减小它。

  1. 应用以下规则:

如果f(f)=+1y=-1,则w=w-x

如果f(f)=-1y=+1,则w=w+x

如果f(x)=y,则w=w

或简单地说,w=w+yx,如果f(x)!=y

  1. 重复步骤 3 到 5,直到f(x) = y

感知机保证能够满足我们所有的数据,但仅适用于具有单一神经元的二分类器。在步骤 5 中,我们引入了一个叫做学习率的术语。这有助于我们的模型收敛。在步骤 5 中,w被写为:w=w+αyx,如果f(x) != y,其中α是选定的学习率。

如果f(x) != y,则偏置也会更新为b=b+ αyb 实际上就是我们的 w[0]

如果布尔函数是一个线性阈值函数(即它是线性可分的),那么局部感知机规则可以在有限步骤内找到一组能够实现它的权重。

这个定理,称为感知机定理,在全局规则的情况下也适用,该规则修改突触权重的向量w,而不是在单个输入向量上,而是根据感知机在整个输入向量集上的行为进行修改。

我们刚刚提到了线性可分函数,那么这个术语是什么意思呢?我们将在接下来的章节中理解它。

线性分离

当一组输出值可以通过一条直线分开时,称这些输出值是线性可分的。从几何学上看,这个条件描述了在输入的向量空间中存在一个超平面,它将需要正输出的与需要负输出的分开,如下图所示:

在这里,分隔符的一侧是预测属于一个类别的点,而另一侧是预测属于不同类别的点。布尔神经元的决策规则对应于由超平面操作的输入特征空间的划分。

如果除了输出神经元外,神经网络的输入也是布尔值,那么使用神经网络进行分类相当于确定输入向量的布尔函数。该函数在超过阈值时取值为 1,否则取值为 0。例如,对于两个输入和输出布尔神经元,可以以一种非常直观的方式表示ANDOR函数。

事实上,AND门和OR门是线性可分的。让我们通过实际测试来验证,首先列出可能的情况并将其表示在二维平面上。

我们首先来做AND函数的例子。下表列出了所有可能的情况及其逻辑结果:

x1 x2 y (AND 门)
1 1 1
1 0 0
0 1 0
0 0 0

下图显示了在二维平面中的所有四种情况:

所有超平面上的点假设为1/TRUE,而其下方的点假设为0/FALSE

现在我们来做OR函数的例子。下表列出了所有可能的情况及其逻辑结果:

x1 x2 y (或门)
1 1 1
1 0 1
0 1 1
0 0 0

下图显示了在二维平面中的所有四种情况:

在这种情况下,所有超平面上的点假设为1/TRUE,而其下方的点假设为0/FALSE

然而,一些布尔函数无法通过网络结构来复制,比如这里看到的函数。XOR和恒等函数就是不可分的:要将它们隔离,需要两条线,而这只能通过更复杂的网络结构来实现。

下表列出了XOR函数的所有可能情况及其逻辑结果:

x1 x2 y (XOR 门)
1 1 0
1 0 1
0 1 1
0 0 0

下图显示了在二维平面中的所有四种情况:

如预期的那样,这样的函数需要两条线来划分所有可能的情况。

在理解了感知机理论的基础之后,我们可以研究一个实际的案例。

R 中的感知机函数

在之前的章节中,我们理解了感知器作为分类器的基本概念。现在,实践的时刻到了,我们将通过分析一个示例来应用迄今为止所学的内容,在这个示例中,我们将尝试根据鸢尾花的花瓣和萼片的大小来对花卉物种进行分类。如你所记得,iris数据集已经在第三章中使用过,使用多层神经网络的深度学习。重新使用这个数据集的原因不仅是因为其中的数据质量使读者能够轻松理解所阐述的概念,而且更重要的是,能够比较不同的算法。

如你所记得,数据集包含了来自三种鸢尾花物种(Iris setosa、Iris virginica 和 Iris versicolor)的 50 个样本。从每个样本中测量了四个特征:萼片和花瓣的长度和宽度,单位为厘米。

包含以下变量:

  • 萼片长度(单位:厘米)

  • 萼片宽度(单位:厘米)

  • 花瓣长度(单位:厘米)

  • 花瓣宽度(单位:厘米)

  • 类别:setosaversicolourvirginica

在所示的示例中,我们将尝试通过线性分隔来分类setosaversicolor物种。

让我们在 R 语言中为iris数据集实现一个感知器函数。代码如下:

######################################################################
###Chapter 4 - Introduction to Neural Networks - using R    ##########
###Simple Perceptron implementation function in R - iris dataset  ####
######################################################################

data(iris)
head(iris, n=20)

iris_sub=iris[1:100, c(1, 3, 5)] 
names(iris_sub)=c("sepal", "petal", "species") 
head(iris_sub) 

library(ggplot2) 

ggplot(iris_sub, aes(x = sepal, y = petal)) + 
 geom_point(aes(colour=species, shape=species), size = 3) +
 xlab("Sepal length") +
 ylab("Petal length") +
 ggtitle("Species vs Sepal and Petal lengths")

euclidean.norm = function(x) {sqrt(sum(x * x))}

distance.from.plane = function(z,w,b) {
 sum(z*w) + b
}

classify.linear = function(x,w,b) {
 distances = apply(x, 1, distance.from.plane, w, b)
 return(ifelse(distances < 0, -1, +1))
}

perceptron = function(x, y, learning.rate=1) {
 w = vector(length = ncol(x)) # initialize weights
 b = 0 # Initialize bias
 k = 0 # count updates
 R = max(apply(x, 1, euclidean.norm))
 mark.complete = TRUE 

 while (mark.complete) {
 mark.complete=FALSE 
 yc = classify.linear(x,w,b)
 for (i in 1:nrow(x)) {
 if (y[i] != yc[i]) {
 w = w + learning.rate * y[i]*x[i,]
 b = b + learning.rate * y[i]*R²
 k = k+1
 mark.complete=TRUE
 }
 }
 }
 s = euclidean.norm(w)
 return(list(w=w/s,b=b/s,updates=k))
}

x = cbind(iris_sub$sepal, iris_sub$petal)

y = ifelse(iris_sub$species == "setosa", +1, -1)

p = perceptron(x,y)

plot(x,cex=0.2)

points(subset(x,Y==1),col="black",pch="+",cex=2)
points(subset(x,Y==-1),col="red",pch="-",cex=2)

intercept = - p$b / p$w[[2]]
slope = - p$w[[1]] /p$ w[[2]]

abline(intercept,slope,col="green")

现在,让我们逐行分析代码。按照本书其余部分的风格,我们首先呈现一部分代码如下,然后详细解释:

data(iris)
head(iris, n=20)

第一条命令加载了iris数据集,该数据集包含在 datasets 库中,并将其保存在给定的数据框中。然后,我们使用head函数显示数据集的前20行。请记住,head函数返回向量、矩阵、表格、数据框或函数的前部分或后部分。在这里,我们指定了要显示的行数(n=20)。以下是结果:

> head(iris, n=20)
 Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1           5.1         3.5          1.4         0.2  setosa
2           4.9         3.0          1.4         0.2  setosa
3           4.7         3.2          1.3         0.2  setosa
4           4.6         3.1          1.5         0.2  setosa
5           5.0         3.6          1.4         0.2  setosa
6           5.4         3.9          1.7         0.4  setosa
7           4.6         3.4          1.4         0.3  setosa
8           5.0         3.4          1.5         0.2  setosa
9           4.4         2.9          1.4         0.2  setosa
10          4.9         3.1          1.5         0.1  setosa
11          5.4         3.7          1.5         0.2  setosa
12          4.8         3.4          1.6         0.2  setosa
13          4.8         3.0          1.4         0.1  setosa
14          4.3         3.0          1.1         0.1  setosa
15          5.8         4.0          1.2         0.2  setosa
16          5.7         4.4          1.5         0.4  setosa
17          5.4         3.9          1.3         0.4  setosa
18          5.1         3.5          1.4         0.3  setosa
19          5.7         3.8          1.7         0.3  setosa
20          5.1         3.8          1.5         0.3  setosa

让我们回到代码。我们将通过提取iris数据集中的100行,并仅提取sepal长度和petal长度以及species来获取二元输出:

iris_sub=iris[1:100, c(1, 3, 5)] 
names(iris_sub)=c("sepal", "petal", "species") 
head(iris_sub) 

在这里,我们仅取iris数据集中的前100行,并选择第135列。这是因为前100行包含了我们感兴趣的两个物种(setosaversicolor)的数据。三列分别是sepal.length(x1)petal.length(x2)species(y - output)

library(ggplot2) 

ggplot(iris_sub, aes(x = sepal, y = petal)) + 
 geom_point(aes(colour=species, shape=species), size = 3) +
 xlab("Sepal length") +
 ylab("Petal length") +
 ggtitle("Species vs Sepal and Petal lengths")

首先,我们加载ggplot2库,然后使用ggplot()绘制出物种分布与sepal.lengthpetal.length的散点图。当然,库应该事先安装。

请记住,要安装一个不在 R 初始分发版中的库,你必须使用install.package函数。这是安装包的主要函数。它接受一个名称向量和一个目标库,从存储库中下载并安装这些包。

perceptron函数的目标是找到setosaversicolor物种的线性分离。下图展示了萼片长度花瓣长度之间的关系,分别对应两种鸢尾花:

如图所示,两种物种位于平面的不同区域,因此可以进行线性分离。此时,我们需要定义函数来进行感知器处理:

euclidean.norm = function(x) {sqrt(sum(x * x))}

distance.from.plane = function(z,w,b) {
 sum(z*w) + b
}

classify.linear = function(x,w,b) {
 distances = apply(x, 1, distance.from.plane, w, b)
 return(ifelse(distances < 0, -1, +1))
}

perceptron = function(x, y, learning.rate=1) {
 w = vector(length = ncol(x)) # initialize weights
 b = 0 # Initialize bias
 k = 0 # count updates
 R = max(apply(x, 1, euclidean.norm))
 mark.complete = TRUE 

 while (mark.complete) {
 mark.complete=FALSE 
 yc = classify.linear(x,w,b)
 for (i in 1:nrow(x)) {
 if (y[i] != yc[i]) {
 w = w + learning.rate * y[i]*x[i,]
 b = b + learning.rate * y[i]*R²
 k = k+1
 mark.complete=TRUE
 }
 }
 }
 s = euclidean.norm(w)
 return(list(w=w/s,b=b/s,updates=k))
}

我们定义了perceptron函数,如感知器训练算法中所讨论的那样。我们将learning.rate设置为1,并在每个循环中尝试更新权重。一旦输出和函数(weightsinputs)*相等,我们就停止训练并退出。更新后的权重将由函数返回。该函数的目标是获得模型所需的最优权重集,如下所示:

x = cbind(iris_sub$sepal, iris_sub$petal)

y = ifelse(iris_sub$species == "setosa", +1, -1)

p = perceptron(x,y)

在第一行中,我们将x输入设置为萼片花瓣长度。sepal.lengthpetal.length构成输入矩阵。在第二行中,我们将标签输出设置为setosa为正,其余为负。输出是setosa或非setosa+1-1)。在第三行中,我们运行perceptron函数。

我们调用perceptron函数,传入xy,它返回感知器的最优权重,如下所示的代码示例:

plot(x,cex=0.2)

points(subset(x,Y==1),col="black",pch="+",cex=2)
points(subset(x,Y==-1),col="red",pch="*",cex=2)

intercept = - p$b / p$w[[2]]
slope = - p$w[[1]] /p$ w[[2]]

abline(intercept,slope,col="green")

之前的代码行绘制了xy,并在图中将setosaversicolor分别标记为+*点。然后我们找到了p变量(感知器)的截距和斜率,并绘制了线性分离线,得到如下图表:

总结来说,我们使用 R 代码实现了感知器并找到了最优权重。通过感知器实现了线性分离。

多层感知器

我们看到,ANDOR门的输出是线性可分的,感知器可以用来建模这些数据。然而,并不是所有的函数都是可分的。实际上,能够分开的函数非常少,而且随着比特数的增加,它们在所有可实现函数中的比例趋近于零。正如我们预期的那样,如果我们考虑XOR门,线性分离是不可能的。交叉点和零点的位置不同,我们无法画一条线将它们分开,如下图所示:

我们可以考虑解析更多的感知器。这样,得到的结构可以学习更多的函数,这些函数都属于线性可分函数的子集。

为了实现更广泛的功能,必须在输入层和输出层之间引入中间传输,允许对输入进行某种形式的内部表示。由此产生的感知器称为 MLP。

我们已经在第一章,神经网络与人工智能概念中看到过它作为前馈网络的应用。MLP 至少由三层节点组成:输入层、隐藏层和输出层。除输入节点外,每个节点都是使用非线性激活函数的神经元。MLP 使用监督学习技术和反向传播进行训练。多层结构和非线性特性将 MLP 与简单的感知器区分开来。MLP 特别用于数据不能线性分割的情况。

例如,下面图示中的 MLP 能够实现 XOR 函数,这一点我们之前已经看到,单一的感知器无法实现:

XOR 使用三层网络实现,它是 ORAND 感知器的组合。输出层包含一个神经元,给出 XOR 输出。这种配置允许两个神经元分别专注于特定的逻辑功能。例如,在 XOR 的情况下,两个神经元可以分别执行 ANDOR 逻辑功能。

MLP 这个术语并不是指具有多层的单一感知器。相反,它包含多个感知器,这些感知器被组织成不同的层。另一种形式是 MLP 网络。

MLP 的应用包括:

  • MLP 对于研究中的复杂问题极为有用。

  • MLP 是通用的函数逼近器,可用于通过回归分析创建数学模型。MLP 也是很好的分类算法。

  • MLP 被广泛应用于语音识别、图像识别和语言翻译等领域,它们是深度学习的基础。

我们现在将使用 R 包 SNNS 来实现 MLP。

使用 RSNNS 实现的 MLP R 实现

该示例中的 RSNNS 包来自 CRAN,用于 mlp() 模型构建。SNNS 是一个用 C++ 编写的库,包含了许多标准的神经网络实现。这个 RSNNS 包封装了 SNNS 的功能,使其可以在 R 内部使用。通过 RSNNS 的低级接口,可以访问 SNNS 的所有算法功能和灵活性。该包还包含一个高级接口,用于最常用的神经网络拓扑和学习算法,能够与 R 无缝集成。以下表格展示了从官方文档中提取的 RSNNS 包的简要描述:

RSNNS 包
描述
SNNS 是一个包含许多神经网络标准实现的库。此包封装了 SNNS 的功能,使其能够在 R 中使用。通过 RSNNS 低级接口,可以访问 SNNS 的所有算法功能和灵活性。此外,包还包含一个方便的高级接口,最常见的神经网络拓扑结构和学习算法能够无缝集成到 R 中。
详细信息:

| 包: RSNNS 类型: 包

版本: 0.4-9

日期: 2016-12-16

许可证: LGPL (>=2) |

作者:
Christoph Bergmeir José M. Benítez
用法:

| mlp(x, y, size = c(5),

maxit = 100,

initFunc = "随机初始化权重",

initFuncParams = c(-0.3, 0.3),

learnFunc = "标准反向传播",

learnFuncParams = c(0.2, 0),

updateFunc = "拓扑顺序",

updateFuncParams = c(0),

hiddenActFunc = "激活 _ 逻辑函数",

shufflePatterns = TRUE,

linOut = FALSE,

outputActFunc = if (linOut) "激活 _ 恒等函数" else "激活 _ 逻辑函数",

inputsTest = NULL,

targetsTest = NULL,

pruneFunc = NULL,

pruneFuncParams = NULL, ...) |

我们使用 mlp() 函数来创建和训练 MLP。训练通常通过反向传播完成。

最常用的参数列在下表中:

x 用于网络的训练输入矩阵
y 对应的目标值
size 隐藏层中单元的数量
maxit 学习的最大迭代次数
hiddenActFunc 所有隐藏单元的激活函数
outputActFunc 所有输出单元的激活函数
inputsTest 用于测试网络的输入矩阵
targetsTest 测试输入对应的目标值

让我们来看一下使用完整的 Iris 数据集构建 SNNS MLP 的代码:

###################################################################
###Chapter 4 - Introduction to Neural Networks - using R ##########
###Simple RSNNS implementation function in R - iris dataset #######
###################################################################

data(iris)

library("RSNNS")

iris = iris[sample(1:nrow(iris),length(1:nrow(iris))),1:ncol(iris)]

irisValues = iris[,1:4]
irisTargets = decodeClassLabels(iris[,5])

iris = splitForTrainingAndTest(irisValues, irisTargets, ratio=0.15)
iris = normTrainingAndTestSet(iris)

model = mlp(iris$inputsTrain, 
 iris$targetsTrain, 
 size=5, 
 learnFuncParams=c(0.1),
 maxit=50, 
 inputsTest=iris$inputsTest, 
 targetsTest=iris$targetsTest)

summary(model)
weightMatrix(model)

par(mfrow=c(2,2))
plotIterativeError(model)

predictions = predict(model,iris$inputsTest)

plotRegressionError(predictions[,2], iris$targetsTest[,2])

confusionMatrix(iris$targetsTrain,fitted.values(model))
confusionMatrix(iris$targetsTest,predictions)

par(mfrow=c(1,2))
plotROC(fitted.values(model)[,2], iris$targetsTrain[,2])
plotROC(predictions[,2], iris$targetsTest[,2])

confusionMatrix(iris$targetsTrain, 
 encodeClassLabels(fitted.values(model),
 method="402040", 
 l=0.4, 
 h=0.6)) ###################################################################

让我们一步步分析代码。

此命令加载包含在 datasets 库中的 iris 数据集,并将其保存在给定的数据框中。考虑到我们已经多次使用它,我认为不需要再做什么。此命令加载 RSNNS 库以供程序使用:


 install.packages("RSNNS") 
 library("RSNNS")

记住,要安装 R 初始分发中没有的库,你必须使用 install.package 函数。这是安装包的主要函数。它接收一个名称向量和目标库,从仓库中下载包并安装它们。

在我们的例子中,我们必须使用命令 install.packages("RSNNS")。只需第一次使用安装包,将 RSNNS 包从 CRAN 安装。

iris = iris[sample(1:nrow(iris),length(1:nrow(iris))),1:ncol(iris)]

在前面的这一行中,iris 数据集在行内进行了洗牌。此操作使得数据集中的行顺序变得随机。事实上,在原始数据集中,观察值是按照花卉种类排序的:首先是50setosa 物种的出现次数,其次是50versicolor 物种的出现次数,最后是50virginica 物种的出现次数。经过这一操作后,行的位置变得随机。为了验证这一点,我们打印修改后的数据集的前20行:

> head(iris, n=20)
 Sepal.Length Sepal.Width Petal.Length Petal.Width    Species
75           6.4         2.9          4.3         1.3 versicolor
112          6.4         2.7          5.3         1.9  virginica
54           5.5         2.3          4.0         1.3 versicolor
36           5.0         3.2          1.2         0.2     setosa
14           4.3         3.0          1.1         0.1     setosa
115          5.8         2.8          5.1         2.4  virginica
125          6.7         3.3          5.7         2.1  virginica
27           5.0         3.4          1.6         0.4     setosa
8            5.0         3.4          1.5         0.2     setosa
41           5.0         3.5          1.3         0.3     setosa
85           5.4         3.0          4.5         1.5 versicolor
64           6.1         2.9          4.7         1.4 versicolor
108          7.3         2.9          6.3         1.8  virginica
65           5.6         2.9          3.6         1.3 versicolor
66           6.7         3.1          4.4         1.4 versicolor
98           6.2         2.9          4.3         1.3 versicolor
39           4.4         3.0          1.3         0.2     setosa
84           6.0         2.7          5.1         1.6 versicolor
2            4.9         3.0          1.4         0.2     setosa
142          6.9         3.1          5.1         2.3  virginica

第一列中的数字是原始数据集的行号。我们可以注意到洗牌操作是完美地完成的。为了与原始顺序进行比较,请参见前面的示例:

irisValues = iris[,1:4]
irisTargets = decodeClassLabels(iris[,5])

自变量和目标变量被设置并分别分配给 irisValuesirisTargets

iris = splitForTrainingAndTest(irisValues, irisTargets, ratio=0.15)
iris = normTrainingAndTestSet(iris)

在第一行中,训练数据和测试数据通过 splitForTrainingAndTest() 函数进行拆分。此函数将输入值和目标值分配到训练集和测试集。测试集是从数据的末尾提取的。如果数据需要洗牌,应该在调用该函数之前完成此操作。具体来说,数据的拆分如下:85 百分比用于训练,15 百分比用于测试。在第二行中,数据被标准化。为此,使用了 normTrainingAndTestSet() 函数。该函数以如下方式对训练集和测试集进行标准化:使用 normalizeData 函数对 inputsTrain 成员进行标准化,并使用类型中给定的参数。标准化过程中获得的标准化参数随后用于标准化 inputsTest 成员。如果未设置 dontNormTargets 参数,则目标值也将以相同的方式进行标准化:

model = mlp(iris$inputsTrain, 
 iris$targetsTrain, 
 size=5, 
 learnFuncParams=c(0.1),
 maxit=50, 
 inputsTest=iris$inputsTest, 
 targetsTest=iris$targetsTest)

mlp() 函数与训练数据集一起调用,以构建模型。此函数创建一个 MLP 并对其进行训练。MLP 是完全连接的前馈神经网络,可能是当前使用最广泛的网络架构。训练通常通过误差反向传播或相关程序进行。测试数据集也被传递以提供测试结果:

summary(model)
weightMatrix(model)

这些代码行使我们能够从新创建的模型中提取有用信息。summary() 函数打印出网络的摘要信息。打印的信息可以是网络的所有信息,采用原始 SNNS 文件格式,也可以是由 extractNetInfo 提供的信息。这一行为由参数 origSnnsFormat 控制,而 weightMatrix() 函数则提取 rsnns 对象的权重矩阵。下图显示了摘要结果的截图:

现在我们来衡量算法在模型训练中的表现:

plotIterativeError(model)

plotIterativeError() 函数绘制了模型网络的迭代训练和测试误差。结果显示在下图中:

上图显示了迭代拟合误差作为黑线,迭代测试误差作为红线。从图中可以看出,两条线都有明显的下降趋势,证明算法迅速收敛。

在正确训练好模型之后,是时候用它来进行预测了:

predictions = predict(model,iris$inputsTest)

在这个案例中,我们使用了predict()函数。这是一个通用的预测函数,适用于从各种模型拟合函数的结果中进行预测。该函数调用特定的方法,这些方法取决于第一个参数的类。我们既有预测值也有实际数据,我们只需要通过回归误差计算将它们进行比较:

plotRegressionError(predictions[,2], iris$targetsTest[,2])

为了绘制回归误差,我们使用了plotRegressionError()函数。该函数在X轴上显示目标值,在Y轴上显示拟合/预测值。最佳拟合应该是通过零点的直线,且斜率为一。这条最佳线在下图中以黑色表示。对实际数据的线性拟合则用红色显示。下图展示了我们之前训练的模型的回归误差:

现在,让我们通过计算混淆矩阵来评估模型在预测数据方面的表现:

confusionMatrix(iris$targetsTrain,fitted.values(model))
confusionMatrix(iris$targetsTest,predictions)

为了计算混淆矩阵,我们使用了confusionMatrix()函数。

记住,混淆矩阵显示了一个真实类别x的模式被分类为类别y的次数。完美的方法应该得到一个对角线矩阵。所有不在对角线上的值都是该方法的错误。

在代码的第一行,我们计算了用于训练的数据的混淆矩阵(这些数据占85百分比),而在第二行,我们计算了用于测试的数据的混淆矩阵(这些数据占剩余的15百分比)。结果如下:

> confusionMatrix(iris$targetsTrain,fitted.values(model))
 predictions
targets  1  2  3
 1 45  0  0
 2  0 34  3
 3  0  1 44
> confusionMatrix(iris$targetsTest,predictions)
 predictions
targets  1  2  3
 1  5  0  0
 2  0 13  0
 3  0  0  5

如图所示,在训练阶段有四个错误,只涉及versicolorvirginica两个物种。记住,我们在第三章中展示的示例中得到了相同的结果,使用多层神经网络的深度学习。然而,在测试中,我们没有犯任何错误。我会说这是非常好的结果,尽管处理的数据实际上较少。我们以图形方式评估这些结果:

par(mfrow=c(1,2))
plotROC(fitted.values(model)[,2], iris$targetsTrain[,2])
plotROC(predictions[,2], iris$targetsTest[,2])

为了评估网络性能,我们绘制了接收者操作特征曲线。前面的命令绘制了两个阶段(训练和测试)的 ROC 曲线。

ROC 是一种用于检查分类器质量的度量标准。对于每个分类器的类别,ROC 会对输出应用 [0,1] 区间内的阈值。ROC 曲线是 TPR 与 FPR 之间的关系图,随着阈值的变化。完美的测试将在左上角显示点,具有 100 百分比的灵敏度和 100 百分比的特异性。线条越接近左上角,网络性能就越好。下图显示了两个阶段的 ROC 曲线(训练阶段在左,测试阶段在右):

如前所述,在训练阶段出现了错误,但在测试阶段则没有。

请注意,我们使用了 par() 函数在一个窗口中显示这两个图表。在其中,我们设置了将图形以矩阵形式显示,行数为 1,列数为 2。

RSNNS 中没有 plot 函数,因此我们使用来自 GitHub 的 plot 函数来绘制我们刚刚构建的神经网络的 MLP。这里有三个输出类别和四个输入节点:

我们已经看到使用 RSNNSiris 数据集神经网络的简单实现。同样的 mlp() 函数可以用于任何 MLP 神经网络架构。

总结

在这一章中,我们介绍了感知机的概念,它是神经网络的基本构建块。我们还看到了多层感知机以及使用 RSNNS 的实现。简单的感知机仅对线性分离问题有效,对于输出数据不线性可分的情况则无法使用。这些局限性通过使用 MLP 算法得到了克服。

我们理解了感知机的基本概念及其在神经网络算法中的应用。我们发现了线性可分的分类器及其适用的函数。我们学习了在 R 环境中实现简单感知机的函数,并且接着学习了如何训练和建模 MLP。

在下一章中,我们将理解如何使用神经网络模型来训练、测试和评估数据集。我们将学习如何在 R 环境中可视化神经网络模型。我们将涵盖诸如早停法、避免过拟合、神经网络的泛化和神经网络参数的缩放等概念。

第五章:在 R 中训练和可视化神经网络

如在第一章《神经网络与人工智能概念》、第二章《神经网络中的学习过程》中所见,训练神经网络模型为构建神经网络奠定了基础。

前馈和反向传播是用于确定模型权重和偏置的技术。权重不能为零,但偏置可以为零。首先,权重会被初始化为一个随机数,通过梯度下降法,误差最小化;我们获得一组最佳的权重和偏置值。

一旦模型使用任何 R 函数训练完成,我们可以将独立变量传递给模型,以预测目标或未知变量。在本章中,我们将使用一个公开可用的数据集来训练、测试并可视化一个神经网络模型。以下内容将被涵盖:

  • 使用神经网络模型训练、测试和评估数据集

  • 可视化神经网络模型

  • 提前停止

  • 避免过拟合

  • 神经网络的泛化

  • 神经网络参数的缩放

  • 集成模型

本章结束时,我们将理解如何使用神经网络模型训练、测试和评估数据集。我们将学习如何在 R 环境中可视化神经网络模型。我们将讨论如提前停止、避免过拟合、神经网络泛化和神经网络参数缩放等概念。

使用神经网络进行数据拟合

数据拟合是构建一条与一组先前收集的数据点最匹配的曲线或数学函数的过程。曲线拟合可以涉及插值,即要求精确的数据点,也可以是平滑拟合,即构建一个平坦的函数来近似数据。通过数据拟合获得的近似曲线可以帮助展示数据、预测没有数据的函数值,并总结两个或多个变量之间的关系。下图展示了收集数据的线性插值:

数据拟合是将一组输入数据训练神经网络,以产生相关的目标输出数据的过程。一旦神经网络完成数据拟合,它就形成了输入输出关系的泛化,并可以用于生成未曾训练过的输入数据的输出。

车辆的燃油消耗一直是全球主要制造商研究的课题。在一个以石油补给问题和更严重的空气污染问题为特征的时代,车辆的燃油消耗已成为一个关键因素。在本例中,我们将构建一个神经网络,目的是根据某些特征预测车辆的燃油消耗。

为此,使用我们在第三章的示例中已经使用过的 ISLR 包中的 Auto 数据集,使用多层神经网络进行深度学习Auto 数据集包含 392 辆车的油耗、马力和其他信息。它是一个数据框,包含 392 个观测值,涉及以下九个变量:

  • mpg:每加仑英里数

  • cylinders:气缸数量(4 到 8 个之间)

  • displacement:发动机排量(立方英寸)

  • horsepower:发动机马力

  • weight:车辆重量(磅)

  • acceleration:从 0 加速到 60 英里每小时的时间(秒)

  • year:型号年份(模 100)

  • origin:汽车的来源(美国、欧洲、日本)

  • name:车辆名称

以下是我们将在本示例中使用的代码:

###########################################################################
########Chapter 5 - Introduction to Neural Networks - using R############## 
##########R program to build, train and test neural networks############### 
###########################################################################
library("neuralnet")
library("ISLR")

data = Auto
View(data)

plot(data$weight, data$mpg, pch=data$origin,cex=2)
par(mfrow=c(2,2))
plot(data$cylinders, data$mpg, pch=data$origin,cex=1)
plot(data$displacement, data$mpg, pch=data$origin,cex=1)
plot(data$horsepower, data$mpg, pch=data$origin,cex=1)
plot(data$acceleration, data$mpg, pch=data$origin,cex=1)

mean_data <- apply(data[1:6], 2, mean)
sd_data <- apply(data[1:6], 2, sd)

data_scaled <- as.data.frame(scale(data[,1:6],center = mean_data, scale = sd_data))
head(data_scaled, n=20)

index = sample(1:nrow(data),round(0.70*nrow(data)))
train_data <- as.data.frame(data_scaled[index,])
test_data <- as.data.frame(data_scaled[-index,])

n = names(data_scaled)
f = as.formula(paste("mpg ~", paste(n[!n %in% "mpg"], collapse = " + ")))

net = neuralnet(f,data=train_data,hidden=3,linear.output=TRUE)
plot(net)

predict_net_test <- compute(net,test_data[,2:6])
MSE.net <- sum((test_data$mpg - predict_net_test$net.result)²)/nrow(test_data)

Lm_Mod <- lm(mpg~., data=train_data)
summary(Lm_Mod)
predict_lm <- predict(Lm_Mod,test_data)
MSE.lm <- sum((predict_lm - test_data$mpg)²)/nrow(test_data)

par(mfrow=c(1,2))
plot(test_data$mpg,predict_net_test$net.result,col='black',main='Real vs predicted for neural network',pch=18,cex=4)
abline(0,1,lwd=5)
plot(test_data$mpg,predict_lm,col='black',main='Real vs predicted for linear regression',pch=18,cex=4)
abline(0,1,lwd=5)
###########################################################################

正如以往一样,我们将逐行分析代码,详细解释应用于捕捉结果的所有特性。

library("neuralnet")
library("ISLR")

初始代码的前两行用于加载运行分析所需的库。

请记住,要安装在 R 的初始发行版中没有的库,必须使用install.package函数。这是安装包的主要函数。它接受一个名称向量和一个目标库,从仓库中下载包并进行安装。这个函数只需要使用一次,而不是每次运行代码时都调用。

neuralnet 库用于通过反向传播、弹性反向传播 (RPROP)(带或不带权重回溯)或修改后的全局收敛版本 (GRPROP)训练神经网络。该函数允许通过自定义选择误差和激活函数来灵活设置。此外,还实现了广义权重的计算。

ISLR 库包含了一组可以自由使用的数据集,供我们在示例中使用。这是一系列在研究中心进行的重要研究中收集的数据。

data = Auto
View(data)

此命令加载Auto数据集,正如我们预期的那样,它包含在ISLR库中,并将其保存到给定的数据框中。使用View函数查看任意 R 对象的结构的紧凑显示。以下截图显示了Auto数据集中的一些数据:

如你所见,该数据库由 392 行和 9 列组成。行表示从 1970 到 1982 年期间的 392 辆商用车。列则表示为每辆车收集的 9 个特征,依次为:mpgcylindersdisplacementhorsepowerweightaccelerationyearoriginname

探索性分析

在开始通过构建和训练神经网络进行数据分析之前,我们进行探索性分析,以了解数据如何分布,并提取初步知识。

我们可以通过绘制预测因子与目标变量之间的图表来开始我们的探索性分析。在这方面,我们回顾一下我们的分析中的预测因子变量包括:cylindersdisplacementhorsepowerweightaccelerationyearoriginname。目标变量是mpg,它包含 392 辆样本车的每加仑英里数数据。

假设我们想要检查来自三个不同地区的汽车的重量和油耗,如下图所示,并使用以下代码:

plot(data$weight, data$mpg, pch=data$origin,cex=2)

为了绘制图表,我们使用了plot()函数,指定* x 轴(weight)、 y *轴(mpg),并最终基于哪个变量对数据进行分组(origin),如下面的图表所示:

记住,origin列中的数字对应以下地区:1=美国,2=欧洲,3=日本)。通过对前述图表的分析,我们发现油耗随着重量的增加而增加。我们要记住,目标变量衡量的是每加仑油行驶的英里数,即多少英里可以行驶一加仑的油。因此,mpg 值(每加仑英里数)越大,油耗越低。

另一个来自图表分析的观察是,美国生产的汽车较重。事实上,在图表的右侧(对应较高的重量值)只出现了该地区生产的汽车。

最后,如果我们将分析集中在图表的左侧,即对应最低油耗的上部,我们会发现大多数情况下是日本和欧洲的汽车。总之,我们可以得出结论,油耗最低的汽车是日本制造的。

现在,让我们看看其他的图表,即如果我们将剩余的数值型预测因子(cylindersdisplacementhorsepoweracceleration)与目标(mpg)进行比较,我们得到什么结果。

par(mfrow=c(2,2))
plot(data$cylinders, data$mpg, pch=data$origin,cex=1)
plot(data$displacement, data$mpg, pch=data$origin,cex=1)
plot(data$horsepower, data$mpg, pch=data$origin,cex=1)
plot(data$acceleration, data$mpg, pch=data$origin,cex=1)

出于空间考虑,我们决定将四个图表合并为一个图表。R 语言使得将多个图表组合成一个总图变得简单,使用par()函数即可。通过使用par()函数,我们可以选择 mfrow=c(nrows, ncols)来创建一个 nrows x ncols 的图表矩阵,按行填充。例如,mfrow=c(3,2)选项创建一个 3 行 2 列的矩阵图表。此外,mfcol=c(nrows, ncols)选项则是按列填充矩阵。

在下图中显示了 4 个图表,排列成 2 行 2 列的矩阵:

从前图的分析中,我们确认了之前提到的内容。我们可以注意到,马力较大的汽车油耗更高。同样的结论也适用于发动机排量;在这种情况下,排量较大的汽车油耗更高。再次强调,马力和排量较大的汽车多为美国生产。

相反,加速值较高的汽车通常有更低的油耗。这是因为这类车的重量较轻。通常来说,重型汽车加速较慢。

神经网络模型

在第二章,神经网络中的学习过程中,我们在构建网络之前进行了数据缩放。当时我们提到,训练神经网络之前对数据进行标准化是一个良好的实践。通过标准化,可以消除数据的单位,使得来自不同位置的数据能够轻松进行比较。

并非所有情况下都需要对数值数据进行标准化。然而,研究表明,当数值数据经过标准化时,神经网络的构建通常更高效,并且能够获得更好的预测效果。事实上,如果数值数据没有标准化,而两个预测变量的规模差异非常大,那么神经网络权重的变化对较大值的影响将更为显著。

有几种标准化技术;在第二章,神经网络中的学习过程中,我们采用了最小-最大标准化。在本章中,我们将采用 Z 分数标准化。该技术包括从每一列的每个值中减去该列的均值,然后将结果除以该列的标准差。实现这一点的公式如下:

总结来说,Z 分数(也称为标准分数)表示观察值或数据相对于所观察或测量的平均值的标准差个数。大于均值的值有正的 Z 分数,而小于均值的值有负的 Z 分数。Z 分数是一个无单位的量,通过从单个粗略分数中减去总体均值,然后将差值除以总体的标准差得到。

在应用选择的标准化方法之前,你必须计算每个数据库列的均值和标准差。为此,我们使用 apply 函数。该函数返回一个向量、数组或列表,通过将函数应用于数组或矩阵的维度来获取值。让我们来理解所使用的参数的含义。

mean_data <- apply(data[1:6], 2, mean)
sd_data <- apply(data[1:6], 2, sd)

第一行代码可以让我们计算每个变量的均值,然后进入第二行,计算每个变量的标准差。让我们看看如何使用apply()函数。apply函数的第一个参数指定了数据集,表示应用该函数的目标数据集,在我们的案例中是名为 data 的数据集。具体来说,我们只考虑了前六个数值型变量,其他变量将用于其他目的。第二个参数必须包含一个向量,给出应用函数时的下标。在我们的案例中,1 表示行,2 表示列。第三个参数必须包含要应用的函数;在我们的案例中,第一行是mean()函数,第二行是sd()函数。结果如下所示:

> mean_data
 mpg    cylinders displacement   horsepower       weight 
 23.445918     5.471939   194.411990   104.469388  2977.584184 
acceleration
 15.541327
> sd_data
 mpg    cylinders displacement    horsepower       weight 
 7.805007     1.705783    04.644004     38.491160   849.402560 
acceleration
 2.758864

为了规范化数据,我们使用scale()函数,这是一种通用函数,其默认方法会对数字矩阵的列进行居中和/或缩放:

data_scaled <- as.data.frame(scale(data[,1:6],center = mean_data, scale = sd_data))

让我们来看一下经过规范化处理的数据:

head(data_scaled, n=20)

结果如下:

> head(data_scaled, n=20)
 mpg  cylinders displacement horsepower     weight acceleration
1  -0.69774672  1.4820530   1.07591459  0.6632851  0.6197483  -1.28361760
2  -1.08211534  1.4820530   1.48683159  1.5725848  0.8422577  -1.46485160
3  -0.69774672  1.4820530   1.18103289  1.1828849  0.5396921  -1.64608561
4  -0.95399247  1.4820530   1.04724596  1.1828849  0.5361602  -1.28361760
5  -0.82586959  1.4820530   1.02813354  0.9230850  0.5549969  -1.82731962
6  -1.08211534  1.4820530   2.24177212  2.4299245  1.6051468  -2.00855363
7  -1.21023822  1.4820530   2.48067735  3.0014843  1.6204517  -2.37102164
8  -1.21023822  1.4820530   2.34689042  2.8715843  1.5710052  -2.55225565
9  -1.21023822  1.4820530   2.49023356  3.1313843  1.7040399  -2.00855363
10 -1.08211534  1.4820530   1.86907996  2.2220846  1.0270935  -2.55225565
11 -1.08211534  1.4820530   1.80218649  1.7024847  0.6892089  -2.00855363
12 -1.21023822  1.4820530   1.39126949  1.4426848  0.7433646  -2.73348966
13 -1.08211534  1.4820530   1.96464205  1.1828849  0.9223139  -2.18978763
14 -1.21023822  1.4820530   2.49023356  3.1313843  0.1276377  -2.00855363
15  0.07099053 -0.8629108  -0.77799001 -0.2460146 -0.7129531  -0.19621355
16 -0.18525522  0.3095711   0.03428778 -0.2460146 -0.1702187  -0.01497955
17 -0.69774672  0.3095711   0.04384399 -0.1940546 -0.2396793  -0.01497955
18 -0.31337809  0.3095711   0.05340019 -0.5058145 -0.4598340   0.16625446
19  0.45535916 -0.8629108  -0.93088936 -0.4278746 -0.9978592  -0.37744756
20  0.32723628 -0.8629108  -0.93088936 -1.5190342 -1.3451622   1.79736053

现在我们来将数据分割为训练集和测试集:

index = sample(1:nrow(data),round(0.70*nrow(data)))
train_data <- as.data.frame(data_scaled[index,])
test_data <- as.data.frame(data_scaled[-index,])

在刚才建议的第一行代码中,数据集被按 70:30 的比例拆分,目的是使用 70%的数据训练网络,剩下的 30%用于测试网络。在第二行和第三行中,名为 data 的数据框被细分为两个新的数据框,分别叫做train_datatest_data。现在,我们需要构建提交给网络的函数:

n = names(data_scaled)
f = as.formula(paste("mpg ~", paste(n[!n %in% "mpg"], collapse = " + ")))

在第一行,我们通过使用names()函数来恢复data_scaled数据框中的所有变量名。在第二行,我们构建了一个公式,用于训练神经网络。这个公式代表了什么?

使用neuralnet()函数拟合的模型以紧凑的符号形式表示。~运算符是构建这类模型的基础。像y ~ model 这样的表达式表示响应y是通过符号表示的预测变量 model 进行建模的。这样的模型由一系列通过+运算符分隔的项组成。每一项由变量和因子名通过:运算符分隔。这样的项表示出现在该项中的所有变量和因子的交互作用。让我们来看一下我们设置的公式:

> f
mpg ~ cylinders + displacement + horsepower + weight + acceleration

现在我们可以构建并训练神经网络了。

在第三章,使用多层神经网络进行深度学习中,我们提到,为了选择最佳的神经元数量,我们需要知道:

  • 神经元数量过少会导致系统误差较大,因为预测因子可能对较少的神经元来说过于复杂,无法捕捉。

  • 神经元数量过多会导致过拟合训练数据,且无法很好地泛化。

  • 每个隐藏层中的神经元数量应该介于输入层和输出层的大小之间,可能是它们的均值。

  • 每个隐藏层中的神经元数量不应超过输入神经元数量的两倍,因为此时你可能已经严重过拟合。

在此情况下,我们有五个输入变量(cylindersdisplacementhorsepowerweightacceleration),以及一个输出变量(mpg)。我们选择在隐藏层设置三个神经元。

net = neuralnet(f,data=train_data,hidden=3,linear.output=TRUE)

hidden 参数接受一个包含每个隐藏层神经元数量的向量,而 linear.output 参数用于指定我们是进行回归(linear.output=TRUE)还是分类(linear.output=FALSE)。

neuralnet() 中使用的算法默认基于弹性反向传播算法,不包括权重回溯,并且额外修改了一个学习率,即与最小绝对梯度(sag)相关的学习率或最小学习率(slr)本身。neuralnet() 函数返回一个 nn 类的对象。nn 类的对象是一个列表,最多包含以下表格中显示的组件:

组件 描述
call 匹配的调用。
response data 参数中提取。
covariate 从数据参数中提取的变量。
model.list 一个列表,包含从 formula 参数中提取的协变量和 response 变量。
err.fct 错误函数。
act.fct 激活函数。
data 数据参数。
net.result 一个列表,包含每次重复的神经网络整体结果。
weights 一个列表,包含每次重复的神经网络拟合权重。
generalized.weights 一个列表,包含每次重复神经网络的广义权重。
result.matrix 一个矩阵,包含每次重复所达到的阈值、所需步骤、误差、AIC 和 BIC(如果已计算),以及权重。每一列表示一次重复。
startweights 一个列表,包含每次重复神经网络的起始权重。

要生成模型结果的摘要,我们使用 summary() 函数:

> summary(net)
 Length Class      Mode 
call                   5   -none-     call 
response             274   -none-     numeric
covariate           1370   -none-     numeric
model.list             2   -none-     list 
err.fct                1   -none-     function
act.fct                1   -none-     function
linear.output          1   -none-     logical
data                   6   data.frame list 
net.result             1   -none-     list 
weights                1   -none-     list 
startweights           1   -none-     list 
generalized.weights    1   -none-     list 
result.matrix         25   -none-     numeric 

对于神经网络模型的每个组件,显示了三个特征:

  • 长度:这是组件的长度,即其中包含多少个此类型的元素。

  • 类别:此项包含组件类别的具体说明。

  • 模式:这是组件的类型(数值、列表、函数、逻辑等)。

要绘制带有每个连接权重的模型的图形表示,我们可以使用plot()函数。plot()函数是 R 中用于表示对象的通用函数。通用函数意味着它适用于不同类型的对象,从变量到表格,再到复杂的函数输出,产生不同的结果。应用于名义变量时,它将生成条形图;应用于序数变量时,它将生成散点图;如果是相同的变量,但经过制表,即频率分布,它将生成直方图。最后,应用于两个变量,一个名义变量和一个序数变量,它将生成箱线图。

plot(net)

神经网络图显示在下图中:

在前面的图中,黑色线条(这些线条从输入节点开始)显示了每一层之间的连接以及每个连接上的权重,而蓝色线条(这些线条从偏置节点开始,偏置节点通过数字 1 来区分)则显示了在每一步中添加的偏置项。可以把偏置看作是线性模型的截距。

尽管随着时间的推移我们已经了解了许多神经网络基础的机制,但在许多方面,我们构建和训练的模型仍然是一个“黑箱”。拟合、权重和模型本身并不够清晰。我们可以满意地认为训练算法是收敛的,然后模型就可以开始使用了。

我们可以在视频中打印权重和偏置:

> net$result.matrix
 1
error                      21.800203210980
reached.threshold           0.009985137179
steps                    9378.000000000000
Intercept.to.1layhid1      -1.324633695625
cylinders.to.1layhid1       0.291091600669
displacement.to.1layhid1   -2.243406161080
horsepower.to.1layhid1      0.616083122568
weight.to.1layhid1          1.292334492287
acceleration.to.1layhid1   -0.286145921068
Intercept.to.1layhid2     -41.734205163355
cylinders.to.1layhid2      -5.574494023650
displacement.to.1layhid2   33.629686446649
horsepower.to.1layhid2    -28.185856598271
weight.to.1layhid2        -50.822997942647
acceleration.to.1layhid2   -5.865256284330
Intercept.to.1layhid3       0.297173606203
cylinders.to.1layhid3       0.306910802417
displacement.to.1layhid3   -5.897977831914
horsepower.to.1layhid3      0.379215333054
weight.to.1layhid3          2.651777936654
acceleration.to.1layhid3   -1.035618563747
Intercept.to.mpg           -0.578197055155
1layhid.1.to.mpg           -3.190914666614
1layhid.2.to.mpg            0.714673177354
1layhid.3.to.mpg            1.958297807266

如图所示,这些值与我们在网络图中看到的值相同。例如,cylinders.to.1layhid1 = 0.291091600669是输入“气缸数”与隐层第一个节点之间连接的权重。

现在我们可以使用网络进行预测。为此,我们已将test_data数据框中的 30%的数据预留出来。现在是使用它的时候了。

predict_net_test <- compute(net,test_data[,2:6])

在我们的例子中,我们将这个函数应用于test_data数据集,仅使用26列,代表网络的输入变量。为了评估网络的性能,我们可以使用均方误差MSE)作为衡量我们的预测与实际数据之间差距的标准。

MSE.net <- sum((test_data$mpg - predict_net_test$net.result)²)/nrow(test_data)

这里test_data$mpg是实际数据,predict_net_test$net.result是分析目标的预测数据。以下是结果:

> MSE.net
[1] 0.2591064572

看起来结果不错,但我们应该与什么进行比较呢?为了了解网络预测的准确性,我们可以构建一个线性回归模型:

Lm_Mod <- lm(mpg~., data=train_data)
summary(Lm_Mod)

我们使用lm函数构建了一个线性回归模型。这个函数用于拟合线性模型。它可以用于执行回归、单层方差分析和协方差分析。为了生成模型拟合结果的摘要,我们使用了summary()函数,返回以下结果:

> summary(Lm_Mod)
Call:
lm(formula = mpg ~ ., data = train_data)
Residuals:
 Min          1Q      Median          3Q         Max
-1.48013031 -0.34128989 -0.04310873  0.27697893  1.77674878
Coefficients:
 Estimate  Std. Error  t value        Pr(>|t|) 
(Intercept)   0.01457260  0.03268643  0.44583        0.656080 
cylinders    -0.14056198  0.10067461 -1.39620        0.163809 
displacement  0.06316568  0.13405986  0.47118        0.637899 
horsepower   -0.16993594  0.09180870 -1.85098        0.065273 . 
weight       -0.59531412  0.09982123 -5.96380 0.0000000077563 ***
acceleration  0.03096675  0.05166132  0.59942        0.549400 
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 0.5392526 on 268 degrees of freedom
Multiple R-squared:  0.7183376, Adjusted R-squared:  0.7130827
F-statistic: 136.6987 on 5 and 268 DF,  p-value: < 0.00000000000000022204

现在我们使用test_data数据框中的数据来进行线性回归模型的预测:

predict_lm <- predict(Lm_Mod,test_data)

最后,我们计算回归模型的均方误差(MSE):

MSE.lm <- sum((predict_lm - test_data$mpg)²)/nrow(test_data)

以下是结果:

> MSE.lm
[1] 0.3124200509

从两种模型(神经网络模型与线性回归模型)之间的比较中可以看出,神经网络再次获胜(0.26 与 0.31)。

现在,我们通过绘制图形进行视觉对比,先是神经网络的实际值与预测值,然后是线性回归模型的:

par(mfrow=c(1,2))

plot(test_data$mpg,predict_net_test$net.result,col='black',main='Real vs predicted for neural network',pch=18,cex=4)
abline(0,1,lwd=5)

plot(test_data$mpg,predict_lm,col='black',main='Real vs predicted for linear regression',pch=18,cex=4)
abline(0,1,lwd=5)

神经网络模型(左侧)与线性回归模型(右侧)在测试集上的表现对比如下图所示:

如我们所见,神经网络的预测值比线性回归模型的更集中在直线周围,尽管你可能没有注意到很大差异。

使用神经网络分类乳腺癌

乳房由一组腺体和脂肪组织组成,位于皮肤和胸壁之间。实际上,它不是一个单一的腺体,而是一组叫做小叶的腺体结构组合而成的一个叶。在每个乳房中,有 15 到 20 个小叶。乳汁通过小管道(称为乳管)从小叶流向乳头。

乳腺癌是一个潜在的严重疾病,如果长时间没有被发现并治疗。它是由乳腺中一些细胞的失控增殖所引起,这些细胞转化为恶性细胞。这意味着它们有能力从生成它们的组织中脱离,侵入周围组织,最终扩散到身体的其他器官。理论上,癌症可以从所有类型的乳腺组织中形成,但最常见的类型是来自腺体细胞或形成导管壁的细胞。

本例的目标是识别一系列良性或恶性类别中的每一个。为此,我们将使用名为BreastCancer的数据集(威斯康星乳腺癌数据库),该数据集包含在mlbench包中。这些数据来源于 UCI 机器学习数据库,在 DNA 样本定期到达时,沃尔伯格博士会报告他的临床案例。因此,数据库反映了这些数据的时间顺序分组。这个分组信息已经被从数据本身中移除。每个变量(除了第一个)被转换成了 11 个基本的数值属性,取值范围从 0 到 10。有 16 个缺失值。

数据框包含 699 个观察值,涉及 11 个变量——其中 1 个是字符变量,9 个是有序或名义变量,1 个是目标类别:

  • Id: 样本编码号

  • Cl.thickness: 聚集厚度

  • Cell.size: 细胞大小均匀性

  • Cell.shape: 细胞形态均匀性

  • Marg.adhesion: 边缘粘附

  • Epith.c.size: 单一上皮细胞大小

  • Bare.nuclei: 光核

  • Bl.cromatin: 平淡染色质

  • Normal.nucleoli: 正常核仁

  • Mitoses: 有丝分裂

  • Class: 类别

如前所述,此示例的目标是识别一系列良性或恶性类别中的每一个。以下是我们将在此示例中使用的代码:

###########################################################################
########Chapter 5 - Introduction to Neural Networks - using R############## 
####################Classifing breast cancer with R######################## 
########################################################################### 
library("mlbench")
library(neuralnet)

data(BreastCancer)
summary(BreastCancer)

mvindex = unique (unlist (lapply (BreastCancer, function (x) which (is.na (x)))))
data_cleaned <- na.omit(BreastCancer) 
summary(data_cleaned)

boxplot(data_cleaned[,2:10])
hist(as.numeric(data_cleaned$Mitoses))

par(mfrow=c(3, 3))
hist(as.numeric(data_cleaned$Cl.thickness))
hist(as.numeric(data_cleaned$Cell.size))
hist(as.numeric(data_cleaned$Cell.shape))
hist(as.numeric(data_cleaned$Marg.adhesion))
hist(as.numeric(data_cleaned$Epith.c.size))
hist(as.numeric(data_cleaned$Bare.nuclei))
hist(as.numeric(data_cleaned$Bl.cromatin))
hist(as.numeric(data_cleaned$Normal.nucleoli))
hist(as.numeric(data_cleaned$Mitoses))

str(data_cleaned)
input<-data_cleaned[,2:10]
indx <- sapply(input, is.factor)
input <- as.data.frame(lapply(input, function(x) as.numeric(as.character(x))))

max_data <- apply(input, 2, max)
min_data <- apply(input, 2, min)
input_scaled <- as.data.frame(scale(input,center = min_data, scale = max_data - min_data))
View(input_scaled)

Cancer<-data_cleaned$Class
Cancer<-as.data.frame(Cancer)
Cancer<-with(Cancer, data.frame(model.matrix(~Cancer+0)))

final_data<-as.data.frame(cbind(input_scaled,Cancer))

index = sample(1:nrow(final_data),round(0.70*nrow(final_data)))
train_data <- as.data.frame(final_data[index,])
test_data <- as.data.frame(final_data[-index,])

n = names(final_data[1:9])
f = as.formula(paste("Cancerbenign + Cancermalignant ~", paste(n, collapse = " + ")))

net = neuralnet(f,data=train_data,hidden=5,linear.output=FALSE)
plot(net)

predict_net_test <- compute(net,test_data[,1:9])
predict_result<-round(predict_net_test$net.result, digits = 0)
net.prediction = c("benign", "malignant")[apply(predict_result, 1, which.max)]
predict.table = table(data_cleaned$Class[-index], net.prediction)
predict.table

library(gmodels)
CrossTable(x = data_cleaned$Class[-index], y = net.prediction,
 prop.chisq=FALSE)
###########################################################################

我们开始逐行分析代码,详细解释捕获结果所应用的所有特性。

library("mlbench")
library("neuralnet")

初始代码的前两行用于加载运行分析所需的库。

请记住,要安装 R 的初始分布中没有的库,您必须使用 install.package 函数。这是安装包的主要函数。它接受名称向量和目标库,从存储库下载包并安装它们。此函数应仅使用一次,而不是每次运行代码时都使用。

mlbench 库包含一系列人工和真实世界的机器学习基准问题,包括来自 UCI 仓库的几个数据集。

neuralnet 库用于使用反向传播、带或不带权重回溯的 RPROP,或修改后的 GRPROP 训练神经网络。该函数通过自定义选择误差和激活函数实现灵活设置。此外,实现了广义权重的计算。从官方文档中提取的 nnet 包简要描述显示在以下表格中:

neuralnet:神经网络的训练
描述
使用反向传播、弹性反向传播(Riedmiller, 1994),或不带权重回溯(Riedmiller and Braun, 1993),或由 Anastasiadis 等人修改的全局收敛版本(2005)训练神经网络。该包允许通过自定义选择误差和激活函数进行灵活设置。
细节

| 包:neuralnet 类型:包

版本:1.33

日期:2016-08-05

许可证:GPL-2 |

作者

| Stefan Fritsch Frauke Guenther

Marc Suling

Sebastian M. Mueller |

回到代码,此时我们需要加载要分析的数据:

data(BreastCancer)

使用此命令,我们上传名为 BreastCancer 的数据集,如前所述,在 mlbench 库中。

探索性分析

在通过构建和训练神经网络进行数据分析之前,我们进行探索性分析,以了解数据如何分布并提取初步知识。

summary(BreastCancer)

使用 summary() 函数,我们将看到一个简要的摘要。

请记住,summary() 函数是用于生成各种模型拟合函数结果摘要的通用函数。该函数调用依赖于第一个参数的类别的特定方法。

在这种情况下,该函数被应用于数据框架,并且结果显示在以下截图中:

summary() 函数返回每个变量的统计信息。特别地,它对于突出显示 class 变量的结果非常有用,该变量包含肿瘤的诊断信息。在此案例中,检测到 458 个良性 class 和 241 个恶性 class。另一个值得注意的特征是 Bare.nuclei 变量。对于该变量,检测到 16 个缺失值。

缺失值是指其值未知的值。缺失值在 R 中由 NA 符号表示。NA 是一个特殊的值,其属性不同于其他值。NA 是 R 中少数几个保留字之一;你不能将其他任何事物命名为 NA。例如,当你读取包含空单元格的 Excel 表格时,NA 可能会出现。当你尝试进行某些非法或无意义的操作时,也会看到 NA。缺失值不一定是错误的结果;在现实生活中,缺失值通常是由于未检测到某些数据。

一个问题自然而然地出现了:我们需要担心缺失值的存在吗?不幸的是,答案是肯定的,这主要是因为几乎在对 NA 进行的每个操作中,都会产生一个 NA。因此,数据集中缺失值的存在可能会导致我们后续计算中出现错误。这就是为什么我们必须删除缺失值的原因。

要删除缺失值,我们必须先识别它们。is.na() 函数可以帮助我们找到缺失值;该函数返回一个与其参数长度相同的逻辑向量,对于缺失值为T,对于非缺失值为F。通常我们希望知道缺失值的索引,which() 函数可以帮助我们实现这一点。要找到数据框中所有包含至少一个 NA 的行,可以尝试以下方法:

mvindex = unique (unlist (lapply (BreastCancer, function (x) which (is.na (x)))))

lapply() 函数对每一列应用指定的函数,并返回一个列表,其中的第 i 个元素是包含第 i 列缺失值元素索引的向量。unlist() 函数将该列表转换为向量,而 unique() 函数则去除重复项。

现在我们知道了缺失值(NA)出现的行数,正如我们接下来看到的:

> mvindex
 [1] 24 41 140 146 159 165 236 250 276 293 295 298 316 322 412 618

现在我们知道数据库中有缺失值,并且知道它们的位置。接下来,我们只需将这些行从原始数据集中删除。为此,我们可以使用以下函数:

  • na.omit:删除所有包含缺失值的行,并将其永久遗忘。

  • na.exclude:删除包含缺失值的行,但会记录这些行的位置,以便在进行预测时,得到一个与原始响应长度相同的向量。

我们将使用第一种方法,以便将缺失值永久删除:

data_cleaned <- na.omit(BreastCancer) 

为了确认已经删除了缺失值所在的行,再次应用 summary() 函数:

summary(data_cleaned)

结果显示在以下截图中:

如你所见,现在已经没有缺失值了。

现在,让我们进入探索性分析。我们可以做的第一件事是绘制变量的箱线图。通过查看summary()函数的结果,已经初步了解了一些情况。自然地,我们将只关注数值型变量。

boxplot(data_cleaned[,2:10])

在下图中,展示了清洗过的数据集(data_cleaned)中数值变量(从第 2 个到第 10 个)的箱线图:

从前一个图形的分析中,我们可以注意到几个变量有异常值,其中Mitoses变量的异常值数量最多。

异常值在数值上与其余数据显著不同。包含异常值的样本数据所得出的统计结果可能会产生误导。

为了更好地识别异常值,我们可以绘制数据库中各变量的直方图。直方图是数值数据分布的准确图形表示。它是连续变量概率分布的估计。构建直方图的第一步是指定值的范围(即将整个值域划分为一系列区间),然后统计每个区间内有多少值。直方图的区间通常是连续的、互不重叠的变量区间。区间必须是相邻的,且通常大小相等。通过直方图,我们可以看到数据分布的中心在哪里,数据如何围绕这个中心分布,以及可能存在的异常值位置。

在 R 环境中,我们可以简单地使用hist()函数绘制直方图,该函数计算给定数据值的直方图。我们必须将数据集的名称放在该函数的括号内。为了在同一窗口绘制多个图形,我们将使用在前面的示例中已经使用过的par()函数:

par(mfrow=c(3, 3))
hist(as.numeric(data_cleaned$Cl.thickness))
hist(as.numeric(data_cleaned$Cell.size))
hist(as.numeric(data_cleaned$Cell.shape))
hist(as.numeric(data_cleaned$Marg.adhesion))
hist(as.numeric(data_cleaned$Epith.c.size))
hist(as.numeric(data_cleaned$Bare.nuclei))
hist(as.numeric(data_cleaned$Bl.cromatin))
hist(as.numeric(data_cleaned$Normal.nucleoli))
hist(as.numeric(data_cleaned$Mitoses))

由于hist()函数需要一个向量作为参数,因此我们使用as.numeric()函数将数据集列中的值转换为数值型向量。该函数创建或强制转换为numeric类型的对象。在接下来的图形中,展示了清洗过的数据集(data_cleaned)中数值变量(从第 2 个到第 10 个)的直方图:

从直方图的分析中,可以注意到一些变量存在异常值。

神经网络模型

和之前的示例一样,在构建和训练网络之前,我们必须先进行数据标准化。在本例中,我们将采用最小-最大标准化方法。

请记住,在训练神经网络之前,进行数据标准化是良好的实践。通过标准化,可以消除数据的单位,使来自不同位置的数据可以轻松进行比较。

在我们开始之前,使用str()函数做一个进一步的检查。这个函数提供了对象内部结构的紧凑显示,是一种诊断功能,并且是summary()函数的替代方法。理想情况下,每个基本结构仅显示一行。它特别适合紧凑地显示(可能嵌套的)列表的(简化)内容。其目的是为任何 R 对象提供合理的输出。

str(data_cleaned)

结果显示在以下截图中:

正如可以注意到的,变量作为因子存在。我们需要对其进行转换以便于计算。

input<-data_cleaned[,2:10]
indx <- sapply(input, is.factor)
input <- as.data.frame(lapply(input, function(x) as.numeric(as.character(x))))

我们首先识别了因子类型的变量,然后将它们转换为数值类型。现在我们可以进行标准化。

在本示例中,我们将使用最小-最大方法(通常称为特征缩放)将所有数据缩放到* [0, 1] * 范围内。实现此目的的公式如下:

在应用选择的标准化方法之前,您必须计算每个数据库列的最小值和最大值。为此,我们使用apply()函数。此函数返回一个向量、数组或值的列表,通过将函数应用于数组或矩阵的边界来获得这些值。让我们来理解所使用参数的含义。

max_data <- apply(data_cleaned[,2:10], 2, max)

apply函数的第一个参数指定了要应用函数的数据集,在我们的案例中是名为data的数据集。第二个参数必须包含一个向量,指定函数将应用于的子脚标。在我们的案例中,1 表示行,2 表示列。第三个参数必须包含要应用的函数;在我们的案例中是max函数。接下来,我们将计算每一列的最小值:

min_data <- apply(data_cleaned[,2:10], 2, min)

最后,为了对数据进行标准化,我们使用scale()函数,这是一个通用函数,其默认方法会对数值矩阵的列进行居中和/或缩放,代码如下所示:

data_scaled <- scale(data_cleaned[,2:10],center = min_data, scale = max_data - min_data)

为了确认数据的标准化,我们来看一下我们创建的新矩阵的前 20 行。为此,我们将使用View()函数:

如您所见,现在数据在零和一之间。此时,我们重新构建数据集,加入我们的目标变量(即class变量),它表示癌症的诊断结果(良性恶性)。这个话题需要我们的关注:正如我们之前所看到的,这个变量(class)是类别型的,特别是在数据框中它作为因子存在,因此为了正确地在网络中使用它,我们必须对其进行转换。我们的目标是一个二分类变量(只有两个值:良性恶性),因此它可以轻松地转换为两个哑变量。

哑变量是取值为01的变量,用来指示某些类别效应的缺失或存在,这些效应可能会影响结果。

我们将做的是创建两个新的变量(CancerbenignCancermalignant),从表示目标的Class变量开始。Cancerbenign变量将在Class变量中每次出现benign值时赋值为 1,而在其他情况下赋值为 0。相反,Cancermalignant变量将在Class变量中每次出现malignant值时赋值为 1,而在其他情况下赋值为 0。

Cancer<-data_cleaned$Class
Cancer<-as.data.frame(Cancer)
Cancer<-with(Cancer, data.frame(model.matrix(~Cancer+0)))

为了获得两个新的虚拟变量,我们使用了model.matrix()函数。该函数通过扩展因子为一组虚拟变量(根据对比方式),并类似地扩展交互作用,来创建一个模型矩阵。最后,我们将新变量添加到数据集中:

final_data<-as.data.frame(cbind(input_scaled,Cancer))

训练网络的时刻已经到来。

网络训练阶段

人工神经网络由并行运行的简单元素组成。网络元素之间的连接至关重要,因为它们决定了网络的功能。这些连接通过其权重影响结果,而权重是在神经网络训练阶段调节的。下图展示了串行和并行处理的比较:

然后,在训练阶段,通过更改连接权重来调节网络,使得特定的输入能引导到特定的输出。例如,通过比较输出(我们实际计算的结果)和目标(我们希望得到的结果),直到网络输出与目标匹配,从而调整网络。为了获得足够可靠的结果,需要许多输入/目标对来形成网络。下图展示了训练阶段的简单流程图:

这些权重调整的方式由我们采用的具体算法决定。在强调算法在网络训练中的重要性后,必须特别关注提供给网络的数据准备。

在网络训练过程中,必须调整权重和偏置,以优化网络的性能。这是整个过程中的最重要阶段,因为网络越好,通用化能力在处理新的、未知的数据时表现越好。在这个阶段,随机抽取一部分收集到的数据(通常是 70%的可用数据)。

在神经网络训练后,我们可以使用该网络,在这一阶段,随机抽取一部分收集到的数据(通常是 30%的可用数据)传递给网络进行测试。然后,神经网络对象可以保存,并且在任何新数据上多次使用。下图展示了原始数据集是如何被划分的:

这段代码中的数据划分如下:

index = sample(1:nrow(final_data),round(0.70*nrow(final_data)))
train_data <- as.data.frame(final_data[index,])
test_data <- as.data.frame(final_data[-index,])

在刚刚提到的代码的第一行中,数据集被分割为 70:30,目的是使用 70%的数据来训练网络,剩余的 30%用于测试网络。在第二行和第三行中,名为data的数据框被细分为两个新的数据框,分别称为train_datatest_data。现在我们需要构建要提交给网络的函数:

n = names(final_data[1:9])
f = as.formula(paste("Cancerbenign + Cancermalignant ~", paste(n, collapse = " + ")))

在第一行中,我们使用names()函数恢复data_scaled数据框中前九个变量的名称。在第二行中,我们构建了将用于训练网络的公式。这个公式代表了什么?

neuralnet()函数拟合的模型以紧凑的符号形式表示。~ 运算符在这种模型的构建中是基础。形式为 y ~ model 的表达式被解释为响应 y 是通过符号上指定的预测因子 model 建模的。这样的模型由一系列通过 + 运算符分隔的项组成。各项本身由变量和因子名称组成,这些名称通过 : 运算符分隔。这样的项被解释为项中所有变量和因子的交互。让我们来看一下我们设置的公式:

> f
Cancerbenign + Cancermalignant ~ Cl.thickness + Cell.size + Cell.shape + 
 Marg.adhesion + Epith.c.size + Bare.nuclei + Bl.cromatin + 
 Normal.nucleoli + Mitoses

现在我们已经具备了所需的所有内容,可以创建并训练网络。我们回顾一下在前一个示例中关于隐藏层神经元数目选择的建议。我们有八个输入变量(Cl.thicknessCell.sizeCell.shapeMarg.adhesionEpith.c.sizeBare.nucleiBl.cromatinNormal.nucleoliMitoses)和一个输出变量(Cancer)。然后我们选择在隐藏层设置五个神经元:

net = neuralnet(f,data=train_data,hidden=5,linear.output=FALSE)

hidden参数接受一个向量,指定每个隐藏层的神经元数,而linear.output参数用于指定我们是做回归(linear.output=TRUE)还是分类(linear.output=FALSE,在我们的例子中是分类)。

neuralnet()中使用的算法默认基于弹性反向传播,且不进行权重回溯,另外还会修改一个学习率,学习率可能是与最小绝对梯度相关的学习率(sag),或者是最小学习率(slr)本身。

为了绘制带有每个连接权重的模型图形表示,我们可以使用plot()函数,前一部分已经对此进行了广泛的解释:

plot(net)

神经网络的图示如下所示:

在之前的图中,黑色的线(这些线从输入节点开始)显示了各层之间的连接以及每个连接上的权重,而蓝色的线(这些线从偏置节点开始,偏置节点由数字一区分)显示了每一步中添加的偏置项。可以把偏置看作线性模型的截距。

测试网络

我们终于训练好了网络并准备好使用它了。现在,我们可以使用它来进行预测。记住,我们已经将 30%的可用数据保留出来,用它来测试网络。是时候使用它了。

predict_net_test <- compute(net,test_data[,1:9])

为了预测数据,我们使用了计算函数,它在给定训练好的神经网络的情况下,计算所有神经元对于特定任意协变量向量的输出。让我们通过打印前十行来查看结果:

> head(predict_net_test$net.result,n=10)
 [,1]                       [,2]
1  0.9999999935589190 0.000000003587253510720848
2  0.0000011083596034 0.999999376764558189911725
4  0.9792070465712006 0.017164709664531079685856
5  0.9999999746453074 0.000000021909385204003642
9  0.9999993390597798 0.000000327298596658228207
14 0.9999999999953126 0.000000000000889095157872
17 0.9999999999989946 0.000000000000442776879837
19 0.0000001409393993 0.999999920006766185309743
21 0.0000024771345578 0.999998553964539960148272
23 0.9999999999999967 0.000000000000001305142352

如我们所见,这些是带有若干小数的实数。为了与数据集中包含的数据进行比较,我们必须将它们四舍五入到最接近的整数。为此,我们将使用round()函数,它将第一个参数中的值四舍五入到指定的小数位数(默认为零)。

predict_result<-round(predict_net_test$net.result, digits = 0)

现在,我们重新构建初始变量。我们不再需要那两个虚拟变量;它们已经完成了它们的任务,但现在我们不再需要它们了。

net.prediction = c("benign", "malignant")[apply(predict_result, 1, which.max)]

现在,我们可以构建混淆矩阵来检查分类器的性能。

predict.table = table(data_cleaned$Class[-index], net.prediction)

混淆矩阵如下所示:

> predict.table
 net.prediction
 benign malignant
 benign       132         5
 malignant      3        65

尽管简单地讲,矩阵告诉我们我们只犯了 8 个错误。有关混淆矩阵的更多信息,我们可以使用gmodels包中的CrossTable()函数。和往常一样,在加载该包之前,你需要先安装它。

library(gmodels)
CrossTable(x = data_cleaned$Class[-index], y = net.prediction,
 prop.chisq=FALSE)

使用CrossTable()函数得到的混淆矩阵如下所示:

主对角线上的单元格包含分类器正确分类的样本计数。在左上角的单元格中,标记为TN,表示真正负例。205 个值中的 132 个表示癌症为良性,且算法正确识别为良性。右下角的单元格,标记为TP,表示真正正例,分类器与临床确定的标签一致,认为肿块是恶性的。在 205 个预测中,共有 65 个是真正正例。

另一对角线上的单元格包含分类器错误分类的样本计数。左下角的FN单元格中的三个例子是伪阴性结果;在这种情况下,预测值为良性,但实际上癌症是恶性的。这类错误可能非常昂贵,因为它可能会导致患者误以为自己没有癌症,而实际上疾病可能继续扩散。标记为FP的单元格将包含伪阳性结果(如果有的话)。这些值出现于模型将癌症错误分类为恶性,而实际上它是良性的时候。虽然这种错误比伪阴性错误危险性较小,但也应避免,因为它可能导致医疗系统的额外财务负担,或者给患者带来额外的压力,因为可能需要额外的检测或治疗。

神经网络训练中的早停

训练周期(epoch)是每次从正向传播训练到反向传播更新权重和偏置的过程的度量。训练的往返过程必须在收敛(最小误差项)或预设的迭代次数后停止。

提前停止是一种用于处理模型过拟合的技术(在接下来的几页中会详细讨论过拟合)。训练集被分为两部分:一部分用于训练,另一部分用于验证。我们将IRIS数据集分成了两部分:一部分占 75%,另一部分占 25%。

使用训练数据,我们计算梯度并更新网络权重和偏置。第二组数据,即测试或验证数据,用于验证模型的过拟合。如果验证过程中的误差在指定次数的迭代后增加(nnet.abstol/reltol),则停止训练,并且此时的权重和偏置将被模型使用。这种方法称为提前停止

使用提前停止的神经网络集成泛化误差与通过传统算法训练的最优架构的单一神经网络相当。单个神经网络需要复杂且完美的调优才能在没有提前停止的情况下实现这种泛化。

避免模型过拟合

训练数据的拟合使得模型确定权重、偏置以及激活函数值。当算法在某些训练数据集上表现过好时,就说它与该特定数据集过于契合。这会导致当测试数据与训练数据差异较大时,输出值的方差很高。这个高估计方差被称为过拟合。预测会受到训练数据影响。

处理神经网络中过拟合的方式有很多种。第一种是正则化,类似于回归。正则化有两种类型:

  • L1 或 Lasso 正则化

  • L2 或岭正则化

  • 最大范数约束

  • 神经网络中的丢弃法

正则化引入了一个成本项来影响激活函数。它通过引入更多特征到目标函数中来尝试改变大部分系数。因此,它试图将许多变量的系数推向零,并减少成本项。

  • Lasso 或 L1 正则化或 L1 惩罚:这有一个惩罚项,使用权重的绝对值之和,使得权重得到优化以减少过拟合。最小绝对收缩与选择算子LASSO)引入了惩罚权重,将网络权重压缩到接近零。

  • L2 惩罚或岭回归:这与 L1 类似,但惩罚是基于权重的平方,而不是绝对权重之和。较大的权重会受到更多的惩罚。

对于这两种情况,只考虑权重进行优化,偏置(或偏移量或截距)被排除在外。

  • 最大范数约束:这是一种正则化技术,通过对每个神经元的输入权重向量施加绝对上限,使得投影梯度下降无法修改权重。这意味着参数向量不能失控(即使学习率过高),因为权重的更新始终受到限制。

  • Dropout:这是一种防止过拟合的技术。在训练过程中,dropout 通过以某个概率 p(一个超参数)保持神经元激活,否则将其设置为零。这意味着在训练过程中某些神经元可能不被激活,因此被丢弃。即便某些信息缺失,网络仍不受影响,并且变得更加准确。这防止了网络过度依赖任何单一神经元或任何小组合的神经元。以下图示说明了 dropout 过程。红色(或深色)神经元为被丢弃的神经元,而神经网络模型在没有这些神经元的情况下依然能够生存,表现出较少的过拟合和更高的准确性:

神经网络的泛化

泛化的目标是使模型能够拟合训练数据。这是我们在神经网络模型上进行训练的扩展。其目标是最小化模型在训练数据上的平方误差和(例如使用普通最小二乘法),并减少模型的复杂度。

以下是列出的泛化方法:

  • 提前停止训练

  • 使用不同训练数据重新训练神经网络

    • 使用随机抽样、分层抽样或任何有效的目标数据组合
  • 训练多个神经网络并对它们的输出进行平均

神经网络模型中的数据缩放

数据缩放或归一化是将模型数据转换为标准格式的过程,从而提高训练的效果、准确性和速度。神经网络中数据缩放的方法类似于任何机器学习问题中的数据归一化方法。

一些简单的数据归一化方法列举如下:

  • Z-score 归一化:如前所述,首先计算给定数据的算术平均值和标准差。然后按以下方式计算标准化得分或Z得分:

这里,X 是数据元素的值,μ 是均值,σ 是标准差。Z 得分或标准得分表示数据元素距离均值的标准差数目。由于均值和标准差对异常值敏感,因此此标准化方法对异常值也很敏感。

  • 最小-最大归一化:此方法为每个数据元素计算以下内容:

这里,x[i] 是数据元素,min(x) 是所有数据值中的最小值,max(x) 是所有数据值中的最大值。此方法将所有分数转换到一个共同的范围 [0, 1]。然而,它对异常值比较敏感。

  • 中位数和 MAD:中位数和中位数绝对偏差(MAD)归一化通过以下公式计算归一化数据值:

在这里,x[i]表示每个数据值。这种方法对离群值和分布极端尾部的点不敏感,因此它是健壮的。然而,这种技术不会保留输入分布,也不会将分数转换为通用的数值范围。

使用神经网络的集成预测

另一种正则化方法是结合神经网络模型并对结果进行平均。最终的模型是最准确的。

神经网络集成是通过平均各个模型的结果来做决策的一组神经网络模型。集成技术是一种简单的提高泛化能力的方法,特别是在噪声数据或小数据集导致的情况下。我们训练多个神经网络,并平均它们的输出。

作为示例,我们采用 20 个神经网络解决相同的学习问题,我们调整训练过程中的各种参数,然后将均方误差与它们平均的均方误差进行比较。

以下是遵循的步骤:

  1. 数据集已加载并分为训练集和测试集。不同神经网络模型可以使用不同的百分比划分。

  2. 通过调整nnet()函数中的参数,使用不同的训练集创建多个模型。

  3. 所有模型都已训练完毕,并将每个模型中的错误列出。

  4. 对测试数据中的每一行计算平均误差,并为每个模型计算均方误差。

  5. 将均方误差与均方误差的平均值进行比较。

  6. 最佳模型是通过比较选择的,并进一步用于预测。

这种方法允许我们通过调整数据和函数参数来得到模型的最佳设置。我们可以选择集成中的任意数量的模型,并使用 R 进行模型的并行处理。

高度减少过拟合,并在此处得出模型的最佳参数。

总结

在本章中,我们介绍了使用 R 训练和可视化一个简单的神经网络。在这里,我们可以改变神经元的数量、隐藏层的数量、激活函数等,以确定模型的训练方式。

在处理回归问题时,最后一层是一个单元,它将给出连续值;对于分类问题,有 n 个终端单元,每个单元表示输出类别及其概率。乳腺癌示例有两个输出神经元,表示神经网络输出的两类值。

我们已经学会了如何使用神经网络模型训练、测试和评估数据集。我们还学会了如何在 R 环境中可视化神经网络模型。我们涵盖了早停、避免过拟合、神经网络的泛化以及神经网络参数的缩放等概念。

第六章:循环神经网络与卷积神经网络

到目前为止,我们一直在研究前馈网络,其中数据单向流动,每一层中的节点没有相互连接。面对与某些问题交互的基本假设,前馈网络固有的单向结构显得尤为局限。然而,我们可以从前馈网络出发,构建结果计算会影响到其他计算过程的网络。显然,管理这些网络动态的算法必须满足新的收敛标准。

在本章中,我们将介绍循环神经网络RNN),它是具有循环数据流的网络。我们还将介绍卷积神经网络CNN),这是一种主要用于图像识别的标准化神经网络。对于这两种类型的网络,我们将在 R 中做一些示例实现。以下是本章涵盖的主题:

  • RNN

  • rnn

  • 长短期记忆LSTM)模型

  • CNN

  • 常见的 CNN 架构——LeNet

本章结束时,我们将理解如何训练、测试和评估一个 RNN。我们将学习如何在 R 环境中可视化 RNN 模型。我们还将能够训练一个 LSTM 模型。我们将涵盖 CNN 的概念和常见的 CNN 架构——LeNet。

循环神经网络

人工神经网络ANN)的范畴内,基于隐藏层数量和数据流的不同,有几种变体。其中一种变体是 RNN,其中神经元之间的连接可以形成一个循环。与前馈网络不同,RNN 可以利用内部记忆进行处理。RNN 是一类具有隐藏层连接并且这些连接通过时间传播以学习序列的 ANN。RNN 的应用场景包括以下领域:

  • 股票市场预测

  • 图像标注

  • 天气预报

  • 基于时间序列的预测

  • 语言翻译

  • 语音识别

  • 手写识别

  • 音频或视频处理

  • 机器人动作序列

到目前为止,我们研究的网络(前馈网络)基于输入数据,这些数据被输入到网络中并转换为输出。如果是监督学习算法,输出是一个标签,用来识别输入数据。基本上,这些算法通过识别模式将原始数据与特定类别连接起来。

循环网络则不同,它们不仅接受当前输入数据,还会结合它们随着时间积累的经验进行处理。

循环网络在特定时刻做出的决策会影响它随后的决策。因此,循环网络有两个输入源——现在和最近的过去——它们结合起来决定如何响应新数据,就像人们每天生活中一样。

循环网络与前馈网络的区别在于,它们通过反馈环路与过去的决策相联系,因此会暂时将其输出作为输入。这个特性可以通过说循环网络具有记忆来加以强调。向神经网络添加记忆是有目的的:序列本身包含信息,而循环网络利用这些信息完成前馈网络无法完成的任务。

RNN 是一类神经网络,其中神经元之间的连接形成一个有向循环。一个典型的 RNN 如下图所示:

在这里,一个实例的输出作为下一个实例的输入,应用于同一个神经元。数据如何在不同时间点保持在记忆中并流动,使得 RNN 强大而成功。

在 RNN 中,数据向后流动的方式有更多变种:

  • 完全循环

  • 递归

  • 霍普菲尔德(Hopfield)

  • 埃尔曼(Elman)和乔丹(Jordan)网络

  • 神经历史压缩器

  • 长短期记忆(LSTM)

  • 门控循环单元(GRU)

  • 双向

  • 循环多层感知机(Recurrent MLP)

循环网络旨在识别作为数据序列的模式,对预测和预测任务非常有帮助。它们可以应用于文本、图像、语音和时间序列数据。RNN 是强大的人工神经网络(ANN)之一,代表了生物大脑,包括具有处理能力的记忆。循环网络从当前输入(像前馈网络一样)和先前计算的输出中获取输入:

为了更好地理解这一点,我们将 RNN 看作是一个神经网络的网络,其循环特性以以下方式展开。神经元h的状态会在不同的时间段(t-1tt+1等)被考虑,直到收敛或达到总的训练轮数。

Vanilla 是最早提出的循环 ANN 模型。一个 vanilla RNN 如下图所示:

由于实现的简便性,其他变种如 GRU 或 LSTM 网络更为广泛,并且它们在语言建模、语音识别、图像字幕生成和自动翻译等涉及序列的广泛应用中表现出色。

RNN 可以通过以下包在 R 中实现:

  • rnn

  • MxNetR

  • TensorFlow for R

循环神经网络(RNN)主要用于序列建模。输入和输出被视为向量(数字矩阵)。为了更深入理解 RNN,我建议你学习 Andrej Karpathy 的字符序列示例。

RNN 的特点使其像一个带记忆的 ANN。ANN 的记忆更像是人类的大脑。有了记忆,我们可以让机器从零开始思考并从它们的“记忆”中学习。RNN 基本上是带有循环的 ANN,这些循环允许信息在网络中保持。循环允许信息从状态 t 传递到状态t+1

如前图所示,RNN 可以被看作是相同 ANN 的多个副本,其中一个的输出作为输入传递给下一个。当我们保存信息时,随着模式的变化,RNN 能够预测t+1的值。这对于分析基于时间序列的问题尤其有用。

无需特别标注;输入的一部分值形成时间序列变量,RNN 能够学习模式并进行预测。

RNN 的内部状态在每个学习过程的时间步长中更新。RNN 中的前馈机制类似于 ANN;然而,反向传播则是通过所谓的时间反向传播BPTT)进行误差项修正。

时间反向传播遵循以下伪代码:

  1. 展开 RNN 以包含n个前馈网络。

  2. 将权重w初始化为随机值。

  3. 执行以下操作,直到满足停止条件或完成所需的训练周期数。

  4. 将输入设置为每个网络的值,作为x[i.]

  5. 将输入在整个展开的网络上进行前向传播。

  6. 将误差反向传播到展开的网络中。

  7. 更新网络中的所有权重。

  8. 将权重平均化,以找到折叠网络中的最终权重。

R 中的 rnn 包

在 R 环境中实现 RNN,我们可以使用通过 CRAN 提供的rnn包。该包广泛用于实现 RNN。以下表格展示了从官方文档中提取的rnn包的简要描述:

rnn:递归神经网络
描述
R 中实现 RNN
详细信息

| 包:rnn 类型:包

版本:0.8.0

日期:2016-09-11

许可证:GPL-3 |

作者
Bastiaan Quast Dimitri Fichou

rnn包中使用的主要函数显示在下表中:

predict_rnn 预测 RNN 模型的输出:predict_rnn(model, X, hidden = FALSE, real_output = T, ...)
run.rnn_demo 启动rnn_demo应用程序的函数:run.rnn_demo(port = NULL)
trainr 训练 RNN 的函数。该模型由predictr函数使用。
predictr 预测 RNN 模型的输出:predictr(model, X, hidden = FALSE, real_output = T, ...)

和往常一样,要使用一个库,必须先安装并将其加载到我们的脚本中。

请记住,要安装在 R 初始分发中不存在的库,必须使用install.package函数。这是安装包的主要功能。它接受一个名称向量和一个目标库,从仓库下载包并进行安装。此函数应只使用一次,而不是每次运行代码时都使用。

所以我们来安装并加载库:

install.packages("rnn")
library("rnn")

当我们加载库(library("rnn"))时,可能会收到以下错误:

> library("rnn")
Error: package or namespace load failed for ‘rnn’ in get(Info[i, 1], envir = env):
 cannot open file 'C:/Users/Giuseppe/Documents/R/win-library/3.4/digest/R/digest.rdb': No such file or directory

别担心,这没什么大不了的!R 只是告诉你,为了运行 rnn 库,你还需要安装 digest 库。记住这一点;以后如果出现类似问题,你就知道怎么解决了。只需添加以下命令:

install.packages("digest")

现在我们可以启动演示:

run.rnn_demo()

当我们安装了 rnn 包并运行 run.rnn_demo() 后,可以通过 127.0.0.1:5876 访问一个网页,网页上可以运行一个具有预设值的 RNN 演示,同时可以直观地看到参数如何影响 RNN,如下图所示:

到此为止,我们可以设置网络的参数,并通过标签选择合适的值填入框中。以下参数必须正确设置:

  • time dimension

  • training sample dimension

  • testing sample dimension

  • number of hidden layers

  • 层数 1 中的单元数

  • 层数 2 中的单元数

  • learningrate

  • batchsize

  • numepochs

  • momentum

  • learningrate_decay

做完这些后,我们只需点击训练按钮,命令就会被构建并进行训练。

下图展示了模拟的结果:

trainrpredictr 函数是 rnn 包中最重要的函数。trainr() 函数使用 XY 参数集来训练模型,训练后的模型可以通过 predictr() 函数进行预测:

trainr(Y, X, 
 learningrate, 
 learningrate_decay = 1, 
 momentum = 0, 
 hidden_dim = c(10), 
 network_type = "rnn", 
 numepochs = 1, 
 sigmoid = c("logistic", "Gompertz", "tanh"), 
 use_bias = F, 
 batch_size = 1, 
 seq_to_seq_unsync = F, 
 update_rule = "sgd", 
 epoch_function = c(epoch_print, epoch_annealing), 
 loss_function = loss_L1, ...) 

predictr(model, 
 X, 
 hidden = FALSE, 
 real_output = T, 
 arguments to pass to sigmoid function)

trainr() 函数接受以下参数。输出为一个可以用于预测的模型:

| Y | 输出值数组:

  • dim 1:样本(必须等于 X 的 dim 1)

  • dim 2:时间(必须等于 X 的 dim 2)

  • dim 3:变量(可以是一个或多个,如果是矩阵,将被强制转换为数组)

|

| X | 输入值数组:

  • dim 1:样本

  • dim 2:时间

  • dim 3:变量(可以是一个或多个;如果是矩阵,将被强制转换为数组)

|

learningrate 应用于权重迭代的学习率。
learningrate_decay 通过 epoch_annealing 函数在每个 epoch 应用到学习率的系数。
momentum 用于加速学习的最后一次权重迭代的系数。
hidden_dim 隐藏层的维度。
network_type 网络类型,可以是 rnngrulstm
numepochs 迭代次数,即整个数据集被网络呈现的次数
sigmoid 传递给 sigmoid 函数的方法。
batch size 每次权重迭代使用的样本数量。当前仅支持一个。
epoch_function 在每个 epoch 循环中应用的函数向量。用它与模型中的对象进行交互,或者在每个 epoch 打印和绘图。它应该返回模型。
loss function 应用于每个样本循环,词汇表用于验证。
... 传递给方法的参数,用于用户定义的函数中。

现在让我们看一个简单的例子。这个例子包含在 CRAN rnn包的官方文档中,用于演示trainrpredictr函数,并查看预测的准确性。

我们有X1X,其中的随机数在0-127范围内。Y被初始化为X1+X2。在将X1X2Y转换为二进制值后,我们使用trainr根据X(由X1X2组成的数组)训练Y

使用模型,我们根据另一组A1+A2样本预测B。错误的差异绘制为直方图:

library("rnn")

#Create a set of random numbers in X1 and X2
X1=sample(0:127, 7000, replace=TRUE)
X2=sample(0:127, 7000, replace=TRUE)

#Create training response numbers
Y=X1 + X2

# Convert to binary
X1=int2bin(X1)
X2=int2bin(X2)
Y=int2bin(Y)

# Create 3d array: dim 1: samples; dim 2: time; dim 3: variables.
X=array( c(X1,X2), dim=c(dim(X1),2) )

# Train the model
model <- trainr(Y=Y[,dim(Y)[2]:1],
 X=X[,dim(X)[2]:1,],
 learningrate = 0.1,
 hidden_dim = 10,
 batch_size = 100,
 numepochs = 100)

plot(colMeans(model$error),type='l',xlab='epoch',ylab='errors')

# Create test inputs
A1=int2bin(sample(0:127, 7000, replace=TRUE))
A2=int2bin(sample(0:127, 7000, replace=TRUE))

# Create 3d array: dim 1: samples; dim 2: time; dim 3: variables
A=array( c(A1,A2), dim=c(dim(A1),2) )

# Now, let us run prediction for new A
B=predictr(model,
 A[,dim(A)[2]:1,] )
B=B[,dim(B)[2]:1]

# Convert back to integers
A1=bin2int(A1)
A2=bin2int(A2)
B=bin2int(B)

# Plot the differences as histogram
hist( B-(A1+A2) )

像往常一样,我们将逐行分析代码,详细解释应用于捕获结果的所有特性:

library("rnn")

初始代码的第一行用于加载运行分析所需的库。接下来我们来看以下命令:

X1=sample(0:127, 7000, replace=TRUE)
X2=sample(0:127, 7000, replace=TRUE)

这些行创建训练响应数字;这两个向量将成为我们即将构建的网络的输入。我们使用了sample()函数,从x的元素中按指定大小取样,既可以有放回也可以没有放回。两个向量包含 7,000 个在1127之间的随机整数值。

Y = X1 + X2

该命令创建训练响应数字;这是我们的目标,或者说是我们希望通过网络来预测的内容。

X1=int2bin(X1)
X2=int2bin(X2)
Y=int2bin(Y)

这三行代码将整数转换为二进制序列。我们需要在逐位相加之前将数字转换为二进制。最终,每个值会得到一个由八个值组成的序列,这些值为01。为了理解这个转换,我们分析这些变量之一的预览:

> head(X1,n=10)
 [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8]
 [1,]    1    1    1    0    0    1    0    0
 [2,]    0    0    0    1    0    0    0    0
 [3,]    1    0    0    0    1    0    1    0
 [4,]    0    0    0    0    0    0    1    0
 [5,]    0    1    0    0    0    0    0    0
 [6,]    0    0    0    1    1    1    0    0
 [7,]    1    0    1    1    0    1    1    0
 [8,]    1    1    0    0    0    1    0    0
 [9,]    1    0    1    0    0    0    0    0
[10,]    0    0    0    1    0    0    0    0

让我们回过头来分析代码:

X=array( c(X1,X2), dim=c(dim(X1),2) )

这段代码创建了一个 3D 数组,这是trainr()函数所要求的。在这个数组中,我们有以下内容:

  • dim 1:样本(必须等于输入的dim 1

  • dim 2:时间(必须等于输入的dim 2

  • dim 3:变量(可以是一个或多个;如果是矩阵,将被强制转换为数组)

model <- trainr(Y=Y[,dim(Y)[2]:1],
 X=X[,dim(X)[2]:1,],
 learningrate = 0.1,
 hidden_dim = 10,
 batch_size = 100,
 numepochs = 100)

trainr()函数在本地 R 中训练 RNN。由于训练是基于XY进行的,所以需要几分钟时间。以下代码展示了在 R 提示符上显示的最后 10 次训练周期结果:

Trained epoch: 90 - Learning rate: 0.1
Epoch error: 3.42915263914405
Trained epoch: 91 - Learning rate: 0.1
Epoch error: 3.44100549476955
Trained epoch: 92 - Learning rate: 0.1
Epoch error: 3.43627697030863
Trained epoch: 93 - Learning rate: 0.1
Epoch error: 3.43541472188254
Trained epoch: 94 - Learning rate: 0.1
Epoch error: 3.43753094787383
Trained epoch: 95 - Learning rate: 0.1
Epoch error: 3.43622412149714
Trained epoch: 96 - Learning rate: 0.1
Epoch error: 3.43604894997742
Trained epoch: 97 - Learning rate: 0.1
Epoch error: 3.4407798878595
Trained epoch: 98 - Learning rate: 0.1
Epoch error: 3.4472752590403
Trained epoch: 99 - Learning rate: 0.1
Epoch error: 3.43720125450988
Trained epoch: 100 - Learning rate: 0.1
Epoch error: 3.43542353819336

我们可以通过绘制算法在后续周期中的错误来查看算法的演变:

plot(colMeans(model$error),type='l',xlab='epoch',ylab='errors')

该图显示了周期与错误之间的关系:

现在模型已经准备好,我们可以使用它来测试网络。但首先,我们需要创建一些测试数据:

A1=int2bin(sample(0:127, 7000, replace=TRUE))
A2=int2bin(sample(0:127, 7000, replace=TRUE))
A=array( c(A1,A2), dim=c(dim(A1),2) )

现在,让我们运行对新数据的预测:

B=predictr(model, A[,dim(A)[2]:1,] ) 
B=B[,dim(B)[2]:1]

转换回整数:

A1=bin2int(A1)
A2=bin2int(A2)
B=bin2int(B)

最后,将差异绘制为直方图:

hist( B-(A1+A2) )

错误的直方图如下所示:

如图所示,频率较高的区间靠近零,表明在大多数情况下,预测值与当前值一致。所有其他区间与误差相关。因此,我们可以说网络以良好的性能模拟了系统。

LSTM 模型

我们已经看到,RNN 具有一种记忆,利用持续的先前信息来处理当前神经网络的计算任务。之前的信息会在当前任务中被使用。然而,这种记忆是短期的,神经节点无法访问所有的历史信息。

当我们在 RNN 中引入长期记忆时,就能记住大量的历史信息并在当前处理时使用。这一概念被称为 LSTM 模型,它在视频、音频、文本预测及其他各种应用中有着广泛的使用场景。

LSTM 由 Hochreiter 和 Schmidhuber 于 1997 年提出。

LSTM 网络使用BPTT进行训练,并减轻了消失梯度问题。LSTM 在时间序列预测中有强大的应用,并能够创建大型递归网络来解决机器学习中的复杂序列问题。

LSTM 具有,使得长短期记忆成为可能。这些门包含在通过层连接的记忆块中:

单元内有三种类型的门:

  • 输入门:将输入缩放到单元(写入)

  • 输出门:将输出缩放到单元(读取)

  • 遗忘门:将旧的单元值缩放(重置)

每个门就像一个开关,控制读/写,从而将长期记忆功能整合到 LSTM 模型中。

LSTM 可以用于解决以下序列预测问题:

  • 直接序列预测

  • 序列分类

  • 序列生成

  • 序列到序列的预测

GRU 和 LSTM 之间的关键区别是:

  • GRU 有两个门,而 LSTM 有三个门。

  • GRU 没有任何不同于暴露的隐藏状态的内部记忆。它们没有 LSTM 中存在的输出门。

  • 在计算 GRU 的输出时没有应用第二个非线性操作。

卷积神经网络

深度学习中另一个重要的神经网络是 CNN。它们专门用于图像识别和分类。CNN 有多个神经网络层,能够从图像中提取信息并判断其属于哪个类别。

例如,如果 CNN 经过一组猫的图像训练,它就能检测图像中是否为猫。在这一部分,我们将了解 CNN 的架构和工作原理。

对于一个程序,任何图像都只是以向量格式表示的一组 RGB 数字。如果我们能够让神经网络理解这个模式,它就能形成 CNN 并检测图像。

普通神经网络是通用的数学近似器,它接受输入,通过一系列函数进行转换,并得出输出。然而,这些普通神经网络对于图像分析的扩展性不好。对于一个 32 x 32 像素的 RGB 图像,隐藏层需要32323=3072个权重。对于这种情况,普通神经网络运行良好。然而,当 RGB 图像扩展到200 x 200像素时,隐藏层所需的权重数是2002003=120,000,此时网络表现不佳。

输入 CNN 以解决这个可扩展性问题。在 CNN 中,CNN 的各层神经元在三维中排列(高度宽度深度)。

下图显示了神经网络和卷积神经网络(CNN):

卷积神经网络(CNN)是由神经网络层组成的序列,其中每一层通过可微分函数将一个激活值的体积转换为另一个激活值。CNN 包含三种类型的层:

  • 卷积层

  • 池化层

  • 完全连接层

第#1 步 – 滤波

卷积层执行繁重的数学运算。在计算机视觉中,处理图像的典型方法是用滤波器进行卷积,只提取其中的显著特征。这是 CNN 中的第一步操作。输入图像应用滤波器逻辑,创建激活图特征图

卷积特征向量是通过将卷积核向量应用到图像的每个 3 x 3 向量上创建的。

滤波的数学步骤如下:

  1. 将特征与图像补丁对齐。

  2. 将每个图像像素与相应的特征像素相乘。

  3. 将它们加起来。

  4. 将每个求和结果除以特征中像素的总数。

滤波完成后,下一步是压缩已滤波的像素。

第#2 步 – 池化

在这一步,我们缩小图像堆栈。对于卷积步骤中获得的每个特征,我们建立一个矩阵,并在每个选择的矩阵中找到最大值,从而缩小整个输入。步骤如下:

  1. 选择一个窗口大小(通常为 2 或 3)。

  2. 选择一个步幅移动像素范围(通常为 2)。

  3. 在已滤波的图像上滑动窗口。

  4. 对于每个窗口,我们取最大值。

如果滑动窗口没有与之前的窗口相同数量的单元,我们取所有可用的值。

第#3 步 – ReLU 归一化

在这一步,我们采用池化输出,并对每个像素应用 ReLU 归一化以调整值。如果任何值为负数,我们将其设为零。

第#4 步 – 在完全连接层进行投票和分类

最后一层是完全连接层,通过一组值进行投票,确定输出的类别。完全连接层只是所有先前输出的合并矩阵。

这是最后一层,输出根据最高票选类别确定。

通过将第 1、2、3 步中的层叠加,我们形成了卷积网络,利用反向传播减少误差项,从而为我们提供最佳预测。

层次可以重复多次,每个层的输出会作为下一个层的输入。

经典的 CNN 架构将如下所示:

以下图例展示了使用 CNN 进行分类预测的示例:

我们将在第七章中看到使用 R 实现 CNN 的案例,神经网络的应用案例 - 高级话题

常见的 CNN 架构 - LeNet

LeNet-5 是 Le Cun 在 1990 年代为手写和机器印刷字符识别设计的卷积网络。

这是卷积网络的第一次成功应用。它具有以下架构:

使用 RNN 进行湿度预测

作为 RNN 的第一个应用案例,我们将看到如何使用trainr()函数训练并预测 RNN.我们的目标是预测某一地点的湿度与日期的关系。输入文件包含来自多个澳大利亚气象站的每日气象观测数据。这些观测数据来自澳大利亚联邦气象局,并经过处理后创建了一个相对较大的样本数据集,用于展示使用 R 和 rattle.data 包进行分析、数据挖掘和数据科学。weatherAUS数据集会定期更新,该数据包的更新通常对应于此数据集的更新。数据来自气象局官网。locationsAUS数据集记录了每个气象站的地点。源数据集由澳大利亚联邦气象局拥有版权,并经许可使用。

此数据集的 CSV 版本可通过以下链接获得:

rattle.togaware.com/weatherAUS.csv

weatherAUS数据集是一个数据框,包含来自 45 个以上澳大利亚气象站的超过 14 万个每日观测数据。该数据集包含以下变量:

  • Date:观测日期(Date对象)。

  • Location:气象站地点的常用名称。

  • MinTemp:摄氏度下的最低温度。

  • MaxTemp:摄氏度下的最高温度。

  • Rainfall:当天记录的降水量(mm)。

  • Evaporation:到上午 9 点的 24 小时内的蒸发量(mm)。

  • Sunshine:白天的明媚阳光时数。

  • WindGustDir:午夜前 24 小时内最强风速的方向。

  • WindGustSpeed:午夜前 24 小时内最强风速的速度(km/h)。

  • Temp9am:上午 9 点的温度(摄氏度)。

  • RelHumid9am:上午 9 点的相对湿度(百分比)。

  • Cloud9am:上午 9 点时,云层遮挡的天空比例。这个比例是以 oktas 为单位的,oktase 是以八分之一为单位的度量。它记录了有多少八分之一的天空被云层遮挡。0 表示完全晴朗的天空,而 8 则表示完全阴天。

  • WindSpeed9am:上午 9 点前 10 分钟的平均风速(公里/小时)。

  • Pressure9am:上午 9 点的气压(hpa),经过海平面标准化。

  • Temp3pm:下午 3 点的温度(摄氏度)。

  • RelHumid3pm:下午 3 点的相对湿度(百分比)。

  • Cloud3pm:下午 3 点时,云层遮挡的天空比例(以 oktas 为单位:八分之一)。

  • WindSpeed3pm:下午 3 点前 10 分钟的平均风速(公里/小时)。

  • Pressure3pm:下午 3 点的气压(hpa),经过海平面标准化。

  • ChangeTemp:温度变化。

  • ChangeTempDir:温度变化的方向。

  • ChangeTempMag:温度变化的幅度。

  • ChangeWindDirect:风向变化的方向。

  • MaxWindPeriod:最大风速的周期。

  • RainToday:如果在 9 点之前的 24 小时内降水量(mm)超过 1mm,则为整数 1,否则为 0。

  • TempRange:到上午 9 点的 24 小时内,最低温度与最高温度之间的差值(摄氏度)。

  • PressureChange:气压变化。

  • RISK_MM:降水量,某种风险的度量。

  • RainTomorrow:目标变量。明天会下雨吗?

在我们的案例中,我们将只使用其中的两个变量:

  • Date:观察日期(Date 对象)

  • RelHumid9am:上午 9 点的相对湿度(百分比)。

如前所述,本示例的目标是预测某个地点的湿度与日期的关系。这里是我们将在本示例中使用的代码:

##########################################################
### Chapter 6 - Introduction to RNNs - using R  ##########
########## Humidity forecasting with RNNs#################
##########################################################
 library("rattle.data")
library("rnn")

data(weatherAUS)
View(weatherAUS)

#extract only 1 and 14 clumn and first 3040 rows (Albury location)
data=weatherAUS[1:3040,c(1,14)]
summary(data)

data_cleaned <- na.omit(data) 
data_used=data_cleaned[1:3000]

x=data_cleaned[,1]
y=data_cleaned[,2]

head(x)
head(y)

X=matrix(x, nrow = 30)
Y=matrix(y, nrow = 30)

# Standardize in the interval 0 - 1
Yscaled = (Y - min(Y)) / (max(Y) - min(Y))
Y=t(Yscaled)

train=1:70
test=71:100

model <- trainr(Y = Y[train,],
 X = Y[train,],
 learningrate = 0.05,
 hidden_dim = 16,
 numepochs = 1000)

plot(colMeans(model$error),type='l',xlab='epoch',ylab='errors')

Yp <- predictr(model, Y[test,])

plot(as.vector(t(Y[test,])), col = 'red', type='l', 
 main = "Actual vs Predicted Humidity: testing set", 
 ylab = "Y,Yp")
lines(as.vector(t(Yp)), type = 'l', col = 'black')
legend("bottomright", c("Predicted", "Actual"), 
 col = c("red","black"), 
 lty = c(1,1), lwd = c(1,1))

############################################################

我们开始逐行分析代码,详细解释所有应用的特性,以捕获结果:

library("rattle.data")
library("rnn")

初始代码的前两行用于加载分析所需的库。

请记住,要安装一个在 R 的初始发行版中没有的库,必须使用 install.package 函数。这是安装包的主要函数。它接收一个包含名称的向量和一个目标库,从仓库中下载包并安装它们。这个函数应该仅使用一次,而不是每次运行代码时都使用。

rattle.data 库包含由 rattle 包默认使用的数据集。可以独立于 rattle 包使用这些数据集来展示分析、数据挖掘和数据科学任务。

rnn 库包含用于在 R 中实现 RNN 的多个函数。

data(weatherAUS)
View(weatherAUS)

使用此命令,我们上传名为 weatherAUS 的数据集,该数据集包含在 rattle.data 库中。如第二行所示,view 函数用于在数据框对象上调用类似电子表格的数据查看器,如下图所示:

回到代码,如前所述,我们只使用了两个变量。此外,数据集包含来自澳大利亚不同位置的数据。我们将把研究范围限制在第一个位置(Albury):

data=weatherAUS[1:3040,c(1,14)]

让我们使用summary()函数进行初步数据分析:

> summary(data)
 Date             Humidity9am 
 Min.   :2008-12-01   Min.   : 18.00 
 1st Qu.:2010-12-30   1st Qu.: 61.00 
 Median :2013-04-27   Median : 76.00 
 Mean   :2013-03-22   Mean   : 74.07 
 3rd Qu.:2015-05-27   3rd Qu.: 88.00 
 Max.   :2017-06-25   Max.   :100.00 
 NA's   :9 

summary()函数返回每个变量的一组统计信息。特别地,突出显示Humidity9am变量的结果很有用;它代表我们的目标。对于这个变量,检测到了九个缺失值。为了删除这些缺失值,我们将使用na.omit()函数;它会删除任何包含缺失值的行,并永久忘记它们:

data_cleaned <- na.omit(data) 
data_used=data_cleaned[1:3000]

使用第二行代码,我们将分析限制在前3000个观察值内。现在,我们必须将输入和输出数据设置为trainr()函数所需的格式:

x=data_cleaned[,1]
y=data_cleaned[,2]

通过这种方式,x将代表我们的输入,y将代表我们的目标:

X=matrix(x, nrow = 30)
Y=matrix(y, nrow = 30)

通过这段代码,我们构建了一个包含30行和100列的矩阵,使用现有数据。回忆一下,回忆(recall)是我们将在模型构建中使用的一个大小设置。现在我们可以对其进行标准化:

Yscaled = (Y - min(Y)) / (max(Y) - min(Y))
Y=t(Yscaled)

对于这个例子,我们使用了最小-最大方法(通常称为特征缩放)来将所有缩放数据映射到* [0,1] *范围内。其公式如下:

在归一化过程中,我们必须计算每一列数据库的最小值和最大值。然后,我们将转置得到的矩阵:

train=1:70
test=71:100

在这些代码行中,数据集被分割为70:30,目的是使用70百分比的数据来训练网络,剩下的30百分比用于测试网络。现在是时候构建并训练模型了:

model <- trainr(Y = Y[train,],
 X = Y[train,],
 learningrate = 0.05,
 hidden_dim = 16,
 numepochs = 1000)

trainr()函数在 R 环境中训练 RNN。我们在隐藏层中使用了16个神经元,训练周期为1,000次。trainr()函数需要几分钟时间,因为训练是基于XY进行的。以下是 R 提示符显示的最后 10 个训练周期结果:

Trained epoch: 990 - Learning rate: 0.05
Epoch error: 0.382192317958489
Trained epoch: 991 - Learning rate: 0.05
Epoch error: 0.376313106021699
Trained epoch: 992 - Learning rate: 0.05
Epoch error: 0.380178990096884
Trained epoch: 993 - Learning rate: 0.05
Epoch error: 0.379260612039631
Trained epoch: 994 - Learning rate: 0.05
Epoch error: 0.380475314573825
Trained epoch: 995 - Learning rate: 0.05
Epoch error: 0.38169633378182
Trained epoch: 996 - Learning rate: 0.05
Epoch error: 0.373951666567461
Trained epoch: 997 - Learning rate: 0.05
Epoch error: 0.374880624458934
Trained epoch: 998 - Learning rate: 0.05
Epoch error: 0.384185799764121
Trained epoch: 999 - Learning rate: 0.05
Epoch error: 0.381408598560978
Trained epoch: 1000 - Learning rate: 0.05
Epoch error: 0.375245688144538

我们可以通过绘制算法在随后的周期中所犯错误的图表,查看算法的演变:

plot(colMeans(model$error),type='l',xlab='epoch',ylab='errors')

这张图展示了周期错误之间的关系:

我们终于训练好了网络并准备好使用它;现在我们可以用它来进行预测。记住,我们已经将 30%的可用数据留出来用于测试网络。是时候使用它了:

Yp <- predictr(model, Y[test,])

最后,为了比较结果,我们绘制一个图表,按顺序显示测试集中的湿度含量和预测结果:

plot(as.vector(t(Y[test,])), col = 'red', type='l', 
 main = "Actual vs Predicted Humidity: testing set", 
 ylab = "Y,Yp")
lines(as.vector(t(Yp)), type = 'l', col = 'black')
legend("bottomright", c("Predicted", "Actual"), 
 col = c("red","black"), 
 lty = c(1,1), lwd = c(1,1))

以下图展示了实际值和预测值:

从图表分析中,我们可以注意到一件事:数据经过良好的调整,表明模型能够较好地预测湿度条件。

总结

在本章中,我们了解了 RNN 以及如何利用内部记忆进行处理。我们还介绍了 CNN,它是一种主要用于图像识别的标准化神经网络。对于 RNN,我们研究了一些 R 中的示例实现。

我们学习了如何训练、测试和评估 RNN。我们还学习了如何在 R 环境中可视化 RNN 模型。我们发现了 LSTM 模型,并介绍了 CNN 的概念以及一种常见的 CNN 架构:LeNet。

在下一章中,我们将看到更多涉及神经网络和深度学习的 R 实现的应用案例。

第七章:神经网络的应用案例——高级主题

使用人工神经网络ANN),让我们尝试模拟典型的大脑活动,如图像感知、模式识别、语言理解、感觉-运动协调等。ANN 模型由一个节点系统组成,等同于人脑的神经元,这些节点通过加权连接互相连接,类似于神经元之间的突触。网络的输出通过不断迭代的方式从连接权重调整至收敛状态。

本章最后,我们将展示 ANN 的不同应用案例,并探讨神经网络如何在人工智能领域中使用。我们将看到一些应用案例及其在 R 中的实现。你可以将相同的程序集应用到其他实际工作场景中。

本章将涵盖以下主题:

  • TensorFlow 与 R 的集成

  • Keras 与 R 的集成

  • 使用MNIST数据集和H2O进行手写数字识别

  • 使用 mxnet 构建 LSTM

  • 使用H2O的自编码器进行数据聚类

  • 使用H2O进行主成分分析PCA

  • 使用darch包进行乳腺癌检测

到本章结束时,你将理解学习过程的高级概念及其在 R 环境中的实现。我们将应用不同类型的算法来实现一个神经网络。我们将复习如何训练、测试和部署模型。我们还会再次讨论如何执行正确的评估程序。此外,随着深度学习的快速发展,我们将更多地涵盖其在应用中的使用,深度学习是基于先进神经网络的最新技术。

TensorFlow 与 R 的集成

TensorFlow 是 Google 提供的一个开源数值计算库,用于机器智能。它隐藏了构建深度学习模型所需的所有编程工作,提供了一个“黑盒”接口供开发者使用。TensorFlow 的 Keras API 为神经网络提供了一个高级接口。

Python 是深度学习的事实标准编程语言,但 R 也在赶超。现在,R 也有深度学习库,开发者可以像使用其他 R 库一样,轻松下载 TensorFlow 或 Keras 并加以使用。

在 TensorFlow 中,图中的节点代表数学运算,而图的边缘则代表在节点之间传递的多维数据数组(张量)。TensorFlow 最初由 Google Brain 团队在 Google 的机器智能研究中开发,用于机器学习和深度神经网络研究,但现在已经公开发布。TensorFlow 在配置得当时,可以利用 GPU 进行处理。

TensorFlow 的通用应用案例如下:

  • 图像识别

  • 计算机视觉

  • 声音/语音识别

  • 时间序列分析

  • 语言检测

  • 语言翻译

  • 基于文本的处理

  • 手写识别HWR

  • 其他许多应用

在本节中,我们将学习如何将 TensorFlow 库引入 R。这样就能在 R 中使用 TensorFlow 进行深度学习,开辟了大量的可能性。为了使用 TensorFlow,我们必须先安装 Python。如果你的机器上没有 Python 环境,现在是时候安装它了。

Python 是一种动态的 面向对象编程OOP)语言,可以用于许多类型的软件开发。它强有力地支持与其他语言和程序的集成,提供了丰富的标准库,并且可以在几天内学会。许多 Python 程序员确认,通过使用 Python,他们的生产力有了显著提升,并认为它有助于开发更高质量、易于维护的代码。Python 可以运行在 Windows、Linux/Unix、macOS X、OS/2、Amiga、Palm 手持设备和诺基亚手机上。它还可以在 Java 和 .NET 虚拟机上运行。Python 采用 OSI 批准的开源许可证,使用是免费的,包括用于商业产品。

Python 由 Guido van Rossum 于 1990 年代初在荷兰的 Stichting Mathematisch Centrum 创建,作为一种名为 ABC 的语言的继承者。尽管 Python 现在包含了许多其他人的贡献,但 Guido 仍然是 Python 的主要作者。

如果你不知道选择哪个版本,可以参考一个(英文)文档,它能帮助你做出选择。原则上,如果你必须从零开始,我们推荐选择 Python 3;如果你需要使用可能与 Python 3 不兼容的第三方软件包,我们推荐使用 Python 2.7。所有有关可用版本及如何安装 Python 的信息可以在www.python.org/找到。

在正确安装了机器上的 Python 版本后,我们需要关注如何安装 TensorFlow。我们可以通过以下链接获取操作系统的所有库信息及可用版本:www.tensorflow.org/

此外,在安装部分,我们可以找到一系列指南,解释如何安装允许我们用 Python 编写应用程序的 TensorFlow 版本。以下操作系统有相关的安装指南:

  • 在 Ubuntu 上安装 TensorFlow

  • 在 macOS X 上安装 TensorFlow

  • 在 Windows 上安装 TensorFlow

  • 从源码安装 TensorFlow

例如,要在 Windows 上安装 TensorFlow,我们必须选择以下类型之一:

  • 仅支持 CPU 的 TensorFlow

  • 支持 GPU 的 TensorFlow

要安装 TensorFlow,首先启动具有管理员权限的终端。然后在该终端中输入适当的 pip3 安装命令。若要安装仅支持 CPU 的版本,请输入以下命令:

C:\> pip3 install --upgrade tensorflow

安装过程的执行状态将通过一系列代码行在视频中显示,如下图所示:

此时,我们可以回到我们最喜欢的环境;我指的是 R 开发环境。我们需要安装 TensorFlow 的接口。R 接口让你能够高效地使用高级 Keras 和 Estimator API,且当需要更多控制时,它提供对核心 TensorFlow API 的完全访问权限。为了安装 R 接口到 TensorFlow,我们将按照以下步骤操作。

首先,从 CRAN 安装tensorflow R 包,如下所示:

install.packages("tensorflow")

然后,使用install_tensorflow()函数来安装 TensorFlow(为了正确安装,你需要管理员权限):

library(tensorflow)
install_tensorflow()

我们可以确认安装成功:

sess = tf$Session()
hello <- tf$constant('Hello, TensorFlow!')
sess$run(hello)

这将为你提供一个默认的 TensorFlow 安装,适用于tensorflow R 包。如果你想了解更多安装选项,包括安装支持 NVIDIA GPU 的 TensorFlow 版本(前提是你已安装相应的 CUDA 库),请继续阅读。在以下代码中,我们可以检查安装是否成功:

> library(tensorflow)
> sess = tf$Session()
> hello <- tf$constant('Hello, TensorFlow!')
> sess$run(hello)
b'Hello, TensorFlow!'

Keras 与 R 的集成

MNIST dataset.

默认情况下,RStudio 加载的是 TensorFlow 的 CPU 版本。一旦加载了 Keras,我们便有了一套强大的深度学习库,R 程序员可以利用这些库来执行神经网络和深度学习任务。要安装 Keras for R,使用以下代码:

install.packages("devtools")
devtools::install_github("rstudio/keras")

此时,我们加载keras库:

library(keras)

最后,我们通过加载MNIST数据集来检查 keras 是否正确安装:

> data=dataset_mnist()

使用 R 进行 MNIST HWR

手写识别(HWR)是现代技术中非常常见的过程。通过光学扫描(光学字符识别OCR))或智能词识别,可以离线从纸张上的文本图像中检测到书写内容。或者,可以通过在线检测笔尖的运动(例如,从笔-计算机表面进行,通常较容易,因为有更多的线索可用)。从技术上讲,手写识别是指计算机从纸质文档、照片、触摸屏及其他设备等来源接收并解释可理解的手写输入的能力。

HWR 通过各种技术进行处理,通常需要 OCR。然而,一个完整的脚本识别系统还管理格式,执行正确的字符分割,并找出最合理的单词。

修改版国家标准与技术研究院MNIST)是一个大型手写数字数据库。它包含 70,000 个数据样本。它是 NIST 更大数据集的一个子集。这些数字的分辨率为 28x28 像素,并以 70,000 行和 785 列的矩阵形式存储;784 列形成每个像素值,来自 28x28 矩阵,且一个值是实际的数字。数字已经被尺寸标准化并集中在一个固定大小的图像中。

MNIST数据集中的数字图像最初由 Chris Burges 和 Corinna Cortes 使用边界框归一化和居中进行选择和实验。Yann LeCun 的版本使用较大窗口中的质量中心进行居中。数据可以在 Yann LeCun 的官方网站上获取:yann.lecun.com/exdb/mnist/

每张图像的大小为 28x28。以下是来自MNIST数据集的0-8数字的图像样本:

MNIST数据集包含多个手写数字样本。我们可以将此数据集输入到 R 程序中进行训练,代码可以识别任何新的手写数字,并作为预测数据进行处理。这是神经网络架构作为计算机视觉系统应用于人工智能(AI)的一种情况。

下表展示了 LeCun 网站上可用的MNIST数据集的分布情况:

数字 计数
0 5923
1 6742
2 5958
3 6131
4 5842
5 5421
6 5918
7 6265
8 5851
9 5949

我们不会使用h2o包进行深度学习训练和测试MNIST数据集。我们将 70,000 行的数据集拆分为 60,000 行训练数据和 10,000 行测试数据。然后,我们将计算模型的准确率。该模型可以用于预测任何包含 0 到 9 之间手写数字的 28x28 像素的输入数据集。最后,我们将文件大小压缩到 100 行,用于在两个.csv格式的数据集上进行演示训练处理,文件名为mnist_train_100.csvmnist_test_10.csv

对于我们的示例 R 代码,我们使用了一个包含 100 行的训练数据集和一个包含 10 行的测试数据集。以下是 R 代码的展示:

#################################################################
### Chapter 7 - Neural Networks with R - Use cases      #########
### Handwritten digit recognition through MNIST dataset #########
#################################################################

library("h2o")

h2o.init(nthreads=-1,max_mem_size="3G")

setwd ("c://R")

train_mnist=read.csv("mnist_train_100.csv", header=FALSE)
attach(train_mnist)
names(train_mnist)

test_mnist=read.csv("mnist_test_10.csv", header=FALSE)
attach(test_mnist)
names(test_mnist)

m = matrix(unlist(train_mnist[10,-1]), 
 nrow = 28, 
 byrow = TRUE)

image(m,col=grey.colors(255))

rotate = function(x) t(apply(x, 2, rev)) 

image(rotate(m),col=grey.colors(255))

par(mfrow=c(2,3))
lapply(1:6, 
 function(x) image(
 rotate(matrix(unlist(train_mnist[x,-1]),
 nrow = 28, 
 byrow = TRUE)),
 col=grey.colors(255),
 xlab=train_mnist[x,1]
 )
)

par(mfrow=c(1,1))

str(train_mnist)

x=2:785
y=1

table(train_mnist[,y])

model=h2o.deeplearning(x,
 y,
 as.h2o(train_mnist),
 model_id="MNIST_deeplearning",
 seed=405,
 activation="RectifierWithDropout",
 l1=0.00001,
 input_dropout_ratio=0.2,
 classification_stop = -1,
 epochs=2000
 )

summary(model)

h2o.scoreHistory(model)

preds=h2o.performance(model, 
 as.h2o(test_mnist))

newdata = h2o.predict(model, 
 as.h2o(test_mnist))

predictions = cbind(as.data.frame(seq(1,10)),
 test_mnist[,1],
 as.data.frame(newdata[,1]))

names(predictions) = c("Number","Actual","Predicted")

as.matrix(predictions)
#################################################################

现在,让我们通过代码了解如何应用h2o包来解决手写数字识别问题。我们在第三章中已经详细介绍了h2o包,使用多层神经网络的深度学习h2o包通过以下代码引入并初始化:

library("h2o")
h2o.init(nthreads=-1,max_mem_size="3G")

以下结果显示在 R 提示符下:

> h2o.init(nthreads=-1,max_mem_size="3G")
H2O is not running yet, starting it now...
Note: In case of errors look at the following log files:
 C:\Users\lavoro\AppData\Local\Temp\Rtmpiit6zE/h2o_lavoro_started_from_r.out
 C:\Users\lavoro\AppData\Local\Temp\Rtmpiit6zE/h2o_lavoro_started_from_r.err
java version "1.7.0_40"
Java(TM) SE Runtime Environment (build 1.7.0_40-b43)
Java HotSpot(TM) 64-Bit Server VM (build 24.0-b56, mixed mode)
Starting H2O JVM and connecting: ..... Connection successful!
R is connected to the H2O cluster: 
 H2O cluster uptime: 15 seconds 229 milliseconds 
 H2O cluster version: 3.10.5.3 
 H2O cluster version age: 2 months and 18 days 
 H2O cluster name: H2O_started_from_R_lavoro_huu267 
 H2O cluster total nodes: 1 
 H2O cluster total memory: 2.67 GB 
 H2O cluster total cores: 4 
 H2O cluster allowed cores: 4 
 H2O cluster healthy: TRUE 
 H2O Connection ip: localhost 
 H2O Connection port: 54321 
 H2O Connection proxy: NA 
 H2O Internal Security: FALSE 
 R Version: R version 3.4.1 (2017-06-30)

训练文件通过句柄打开,设置为 100 行以简化演示工作。完整的数据集可以从之前建议的网址下载。

setwd("C://R")

该命令设置了工作目录,我们将在其中插入用于下一次读取的数据集。

train_mnist=read.csv("mnist_train_100.csv", header=FALSE)
attach(train_mnist)
names(train_mnist)

这段代码首先加载了MNIST的训练数据集,将文件大小减少到 100 行以进行演示训练处理。然后我们使用attach()函数将数据库附加到 R 的搜索路径中。这意味着 R 在评估变量时会搜索该数据库,因此可以通过简单地给出变量名来访问数据库中的对象。最后,我们使用names()函数来设置数据集的名称。我们将在测试阶段使用的同样方式处理数据集:

test_mnist=read.csv("mnist_test_10.csv", header=FALSE)
attach(test_mnist)
names(test_mnist)

此时,我们通过提取数据集中的第十行(包含数字零),创建一个 28x28 的矩阵,其中包含像素颜色值:

m = matrix(unlist(train_mnist[10,-1]),
 + nrow = 28,
 + byrow = TRUE)

让我们通过绘制对象image来看一下我们得到了什么:

image(m,col=grey.colors(255))

以下展示的是手写数字的图像:

现在,让我们创建手写数字的镜像:

> rotate = function(x) t(apply(x, 2, rev))

然后,查看图像以验证刚刚执行的操作:

> image(rotate(m),col=grey.colors(255))

以下展示的是镜像图像:

现在,我们对数据集中的前六行做同样的操作:

par(mfrow=c(2,3))
lapply(1:6,
 function(x) image(
 rotate(matrix(unlist(train_mnist[x,-1]),
 nrow = 28,
 byrow = TRUE)),
 col=grey.colors(255),
 xlab=train_mnist[x,1]
 )
)

这些是数据集中前六行手写数字的图像:

重置图表选项为默认设置:

 par(mfrow=c(1,1)) 

下一个命令让我们进行一些关于训练数据的解释性分析:

str(train_mnist)
x=2:785
y=1 

此命令用于查找训练矩阵中每个数字的数量:

table(train_mnist[,y]) 

结果显示如下:

> table(train_mnist[,y])
 0  1  2  3  4  5  6  7  8  9
13 14  6 11 11  5 11 10  8 11

上面显示的是数据集中每个数字出现的次数。现在是构建并训练模型的时候了:

model=h2o.deeplearning(x,
 y,
 as.h2o(train_mnist),
 model_id="MNIST_deeplearning",
 seed=405,
 activation="RectifierWithDropout",
 l1=0.00001,
 input_dropout_ratio=0.2,
 classification_stop = -1,
 epochs=2000
)

现在,为了生成model拟合函数的结果摘要,我们将使用summary()函数:

summary(model)

以下图示显示了部分得到的结果:

我们可以通过检查训练模型的性能来理解算法的演变:

preds=h2o.performance(model,
 as.h2o(test_mnist))

此时,我们已经有了一个训练良好的model,因此可以用它进行预测。在我们的案例中,我们将用它来识别手写数字:

newdata = h2o.predict(model,
 as.h2o(test_mnist))

既然我们已经使用了model,我们需要格式化实际和预期的矩阵来验证准确性:

predictions = cbind(as.data.frame(seq(1,10)),
 test_mnist[,1],
 as.data.frame(newdata[,1]))

输入插入矩阵的变量名称:

names(predictions) = c("Number","Actual","Predicted")

最后,检查输出:

as.matrix(predictions)

结果显示如下:

> as.matrix(predictions)
 Number Actual   Predicted
 [1,]      1      7  6.90180840
 [2,]      2      3  3.62368445
 [3,]      3      1  0.53782891
 [4,]      4      0 -0.03092147
 [5,]      5      6  5.21024129
 [6,]      6      1  0.30850593
 [7,]      7      6  6.44916207
 [8,]      8      9  3.59962551
 [9,]      9      5  3.17590073
[10,]     10      9  7.35213625

从刚才提出的表格分析可以看出,对于测试数据,模型正确预测了 60%(10 个中有 6 个)。这个准确度仅适用于小型训练数据集。模型可以通过以下方式进一步改进:

  • 增加训练数据集的数量

  • 调整h20.deeplearning函数的参数

  • h2o JVM 分配更多内存

  • 扩展测试数据集

使用鸢尾花数据集的 LSTM

继续使用第六章中介绍的 LSTM 架构用于 RNN,递归神经网络与卷积神经网络,我们展示了使用mxnet LSTM 函数处理iris数据集。该函数期望所有输入和输出为数值型。它特别适用于处理文本序列,但在这里我们将训练一个 LSTM 模型,基于iris数据集。输入值为petal.lengthpetal.widthsepal.lengthsepal.width,输出变量是Species,它被转换为介于 1 和 3 之间的数值。iris数据集已在第四章中详细介绍,感知器神经网络建模 - 基本模型

#################################################################
### Chapter 7 - Neural Networks with R - Use cases      #########
### Prediction using LSTM on IRIS dataset               #########
#################################################################

##Required one time
library("mxnet")

data(iris)

x = iris[1:5!=5,-5]
y = as.integer(iris$Species)[1:5!=5]

train.x = data.matrix(x)
train.y = y

test.x = data.matrix(iris[1:5==5,-5])
test.y = as.integer(iris$Species)[1:5==5]

model <- mx.mlp(train.x, train.y, hidden_node=10, out_node=3, out_activation="softmax",
 num.round=20, array.batch.size=15, learning.rate=0.07, momentum=0.9,
 eval.metric=mx.metric.accuracy)

preds = predict(model, test.x)
pred.label = max.col(t(preds))

test.y
pred.label
#################################################################

程序需要安装mxnetmxnet支持 R 语言,并可用于 CPU 和 GPU,支持以下操作系统:Linux、macOS 和 Windows。

我们仅说明 Windows 机器和 CPU 版本的安装程序。有关其他架构的安装程序信息,请参考以下网址:mxnet.incubator.apache.org/get_started/install.html

要在 CPU 处理器的计算机上安装mxnet,我们使用预构建的二进制包。我们可以通过以下代码直接在 R 控制台中安装该包:

cran <- getOption("repos")
cran["dmlc"] <- "https://s3-us-west-2.amazonaws.com/apache-mxnet/R/CRAN/"
options(repos = cran)
install.packages("mxnet")

安装了以下包:

package ‘bindr’ successfully unpacked and MD5 sums checked
package ‘brew’ successfully unpacked and MD5 sums checked
package ‘assertthat’ successfully unpacked and MD5 sums checked
package ‘bindrcpp’ successfully unpacked and MD5 sums checked
package ‘glue’ successfully unpacked and MD5 sums checked
package ‘pkgconfig’ successfully unpacked and MD5 sums checked
package ‘BH’ successfully unpacked and MD5 sums checked
package ‘plogr’ successfully unpacked and MD5 sums checked
package ‘yaml’ successfully unpacked and MD5 sums checked
package ‘irlba’ successfully unpacked and MD5 sums checked
package ‘hms’ successfully unpacked and MD5 sums checked
package ‘XML’ successfully unpacked and MD5 sums checked
package ‘Rook’ successfully unpacked and MD5 sums checked
package ‘tidyselect’ successfully unpacked and MD5 sums checked
package ‘gridExtra’ successfully unpacked and MD5 sums checked
package ‘dplyr’ successfully unpacked and MD5 sums checked
package ‘downloader’ successfully unpacked and MD5 sums checked
package ‘htmltools’ successfully unpacked and MD5 sums checked
package ‘htmlwidgets’ successfully unpacked and MD5 sums checked
package ‘igraph’ successfully unpacked and MD5 sums checked
package ‘influenceR’ successfully unpacked and MD5 sums checked
package ‘purrr’ successfully unpacked and MD5 sums checked
package ‘readr’ successfully unpacked and MD5 sums checked
package ‘rstudioapi’ successfully unpacked and MD5 sums checked
package ‘rgexf’ successfully unpacked and MD5 sums checked
package ‘tidyr’ successfully unpacked and MD5 sums checked
package ‘viridis’ successfully unpacked and MD5 sums checked
package ‘DiagrammeR’ successfully unpacked and MD5 sums checked
package ‘visNetwork’ successfully unpacked and MD5 sums checked
package ‘data.table’ successfully unpacked and MD5 sums checked
package ‘mxnet’ successfully unpacked and MD5 sums checked

正如你所看到的,安装mxnet包时,除了安装mxnet外,还安装了其他几个包。所以,我们已经准备好继续了。这个mxnet库包含了我们将要使用的mx.lstm函数:

library("mxnet")

在以下代码中,加载了内部数据集iris,并将xy变量分别设置为自变量和目标变量。Species变量被转换为介于 1 到 3 之间的数字:

data(iris)
x = iris[1:5!=5,-5]
y = as.integer(iris$Species)[1:5!=5]

只是一个解释,使用以下代码:

x = iris[1:5!=5,-5]

我们要求 R 从iris数据集中选择 150 行五列中的 1 到 4 行,省略掉第五行。这个过程还将对 5 的倍数进行,因此最后我们会从选择中省略每个倍数为 5 的行。同时,我们也会省略第五列。最终,我们将得到 120 行和四列。

现在我们设置输入和输出:

train.x = data.matrix(x)
train.y = y

然后,我们通过选择之前省略的行来设置用于测试的数据框:

test.x = data.matrix(iris[1:5==5,-5])
test.y = as.integer(iris$Species)[1:5==5]

调用mx.lstm函数,使用输入和输出值来训练 RNN 上的 LSTM 模型,数据集为:

model <- mx.mlp(train.x, train.y, hidden_node=10, out_node=3, out_activation="softmax",
 num.round=20, array.batch.size=15, learning.rate=0.07, momentum=0.9,
 eval.metric=mx.metric.accuracy)

现在我们可以进行预测:

preds = predict(model, test.x)
pred.label = max.col(t(preds))

最后,我们打印结果以比较模型性能:

test.y
pred.label

这里是结果:

> test.y
 [1] 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3
> pred.label
 [1] 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3

从测试数据和预测结果的比较中可以看出,最好的结果是针对versicolor物种获得的。从结果来看,模型需要改进,因为它能够进行的预测还不如我们在前面示例中获得的模型那样好。

使用自编码器

我们在深度学习章节中已经看到过自编码器,它们用于无监督学习。自编码器利用神经网络执行非线性降维。通过使用通用函数逼近器,它们能以更好的方式表示数据,找出数据中的潜在特征。自编码器试图以不同的方式结合或压缩输入数据。

这里展示了一个使用 MLP 的示例表示:

使用 H2O 的 PCA

在多元统计分析中,遇到的最大困难之一是如何显示具有多个变量的数据集。幸运的是,在具有多个变量的数据集中,一些数据往往彼此紧密相关。这是因为它们实际上包含相同的信息,因为它们测量的是支配系统行为的同一量。因此,这些是冗余变量,对我们想要构建的模型没有任何贡献。我们可以通过用一个包含信息内容的新变量替换一组变量来简化问题。

PCA 生成了一组新的变量,其中包含不相关的变量,称为主成分;每个主成分是原始变量的线性组合。所有主成分彼此正交,因此没有冗余信息。主成分整体构成了数据空间的正交基。PCA 的目标是用最少数量的主成分解释最多的方差。这是一种多维缩放方法。它是将变量线性变换到一个较低维度的空间,保留关于变量的最大信息。因此,主成分是原始变量在进行线性变换后的组合。

在以下示例中,我们使用h2o实现 PCA。prcomp()函数用于寻找一组输入特征的主成分。这是无监督学习:

library(h2o)
h2o.init()

ausPath = system.file("extdata", "australia.csv", package="h2o")
australia.hex = h2o.uploadFile(path = ausPath)
summary(australia.hex)

pca_model=h2o.prcomp(training_frame = australia.hex, 
 k = 8, 
 transform = "STANDARDIZE")

summary(pca_model)
barplot(as.numeric(pca_model@model$importance[2,]),
 main="Pca model", 
 xlab="Pca component",
 ylab="Proportion of Variance")

现在,让我们通过代码来了解如何应用 h2o 包进行 PCA。

我们可以继续加载库:

library(h2o)

此命令将库加载到 R 环境中。以下函数以最大内存大小为2 GB 和两个并行核心初始化h2o引擎:

h2o.init()

返回以下消息:

> h2o.init()
 Connection successful!

R is connected to the H2O cluster: 
 H2O cluster uptime: 5 hours 40 minutes 
 H2O cluster version: 3.10.5.3 
 H2O cluster version age: 2 months and 18 days 
 H2O cluster name: H2O_started_from_R_lavoro_huu267 
 H2O cluster total nodes: 1 
 H2O cluster total memory: 2.63 GB 
 H2O cluster total cores: 4 
 H2O cluster allowed cores: 4 
 H2O cluster healthy: TRUE 
 H2O Connection ip: localhost 
 H2O Connection port: 54321 
 H2O Connection proxy: NA 
 H2O Internal Security: FALSE 
 R Version: R version 3.4.1 (2017-06-30) 

我们按照 R 提示符上的指示操作:

c1=h2o.init(max_mem_size = "2G", 
 nthreads = 2, 
 ip = "localhost", 
 port = 54321)

h20.init 函数以最大内存大小为 2 GB 和两个并行核心初始化 h2o 引擎。以下命令将数据加载到 R 环境中:

ausPath = system.file("extdata", "australia.csv", package="h2o")
australia.hex = h2o.uploadFile(path = ausPath)

第一条指令生成包含要上传文件的路径。要上传位于 h2o 实例本地目录中的文件,使用 h2o.uploadFile(),它除了能够上传本地文件数据外,还能上传 R 会话中的数据。在括号内,指定 R 中的 h2o 引用对象以及文件的完整 URL 或规范化文件路径。现在让我们来看一下它在其中:

summary(australia.hex)

现在让我们打印数据集的简要总结:

要对给定的数据集执行 PCA,我们将使用prcomp()函数:

pca_model=h2o.prcomp(training_frame = australia.hex, 
 k = 8, 
 transform = "STANDARDIZE")

现在让我们打印模型的简要总结

summary(pca_model)

在下图中,我们看到 PCA 模型的总结:

为了更好地理解结果,我们可以绘制一个 scree 图,显示每个主成分解释的方差百分比。解释的方差百分比包含在 PCA 模型的模型重要性变量中。

以下图展示了每个主成分解释的百分比方差的碎石图:

条形图显示了每个主成分的方差比例;如你所见,前两个主成分约占方差的 70%。

使用 H2O 的自编码器

自编码器是一种用于无效编码控制的人工神经网络(ANN)。自编码器的目的是学习一组数据的编码,通常用于减少数据的维度。

从架构上看,自编码器的最简单形式是一个先进的、非递归的神经网络,类似于多层感知机(MLP),具有输入层、输出层和一个或多个连接它们的隐藏层,但层的输出与输入层的节点数相同,用于重建输入。

以下是使用h2omovie数据集上进行自编码器的示例:

本示例中使用的数据集是一组来自grouplens.org/datasets/movielens的电影和类型数据。

我们使用的movies.csv文件包含三列:

  • movieId

  • title

  • genres

聚类的数据有 164,979 行。我们将使用h2o.deeplearning来让autoencoder参数修正聚类。这个练习的目的是根据类型对电影进行聚类,然后可以用来向用户推荐相似的电影或同类型的电影。程序使用h20.deeplearning,并将autoencoder参数设置为T

#################################################################
### Chapter 7 - Neural Networks with R - Use cases      #########
### Autoencoder using H2O on a movie dataset            #########
#################################################################

library("h2o")

setwd ("c://R")
#Load the training dataset of movies
movies=read.csv ( "movies.csv", header=TRUE)
head(movies)

model=h2o.deeplearning(2:3, 
 training_frame=as.h2o(movies),
 hidden=c(2), 
 autoencoder = T, 
 activation="Tanh")

summary(model)

features=h2o.deepfeatures(model,
 as.h2o(movies),
 layer=1)

d=as.matrix(features[1:10,])
labels=as.vector(movies[1:10,2])
plot(d,pch=17)
text(d,labels,pos=3)

现在,让我们来浏览代码:

library("h2o")
setwd ("c://R")

这些命令会在 R 环境中加载库,并设置工作目录,之后我们会将数据集插入该目录进行下一步读取。然后,我们加载数据:

movies=read.csv( "movies.csv", header=TRUE)

为了可视化数据集中包含的数据类型,我们分析了其中一个变量的预览:

head(movies)

以下图显示了movie数据集的前20行:

现在,我们构建并训练model

model=h2o.deeplearning(2:3, 
 training_frame=as.h2o(movies),
 hidden=c(2), 
 autoencoder = T, 
 activation="Tanh")

让我们分析一下model中包含的一些信息:

summary(model)

这是summary()函数结果的一个摘录:

在接下来的命令中,我们使用h2o.deepfeatures()函数从h2o数据集提取非线性特征,使用的是 H2O 深度学习模型:

features=h2o.deepfeatures(model,
 as.h2o(movies),
 layer=1)

在以下代码中,显示了从模型中提取的前六行特征:

> features
 DF.L1.C1 DF.L1.C2
1 0.2569208 -0.2837829
2 0.3437048 -0.2670669
3 0.2969089 -0.4235294
4 0.3214868 -0.3093819
5 0.5586608 0.5829145
6 0.2479671 -0.2757966
[9125 rows x 2 columns]

最后,我们绘制一个图表,查看模型如何通过分析结果将电影分组:

d=as.matrix(features[1:10,])
labels=as.vector(movies[1:10,2])
plot(d,pch=17)
text(d,labels,pos=3)

完成聚类后,接下来显示的是电影的图形。由于空间限制,我们只绘制了 100 个电影标题。我们可以看到一些电影被紧密排列在一起,意味着它们属于相同类型。电影标题根据它们之间的距离进行了聚类,基于的是类型。

由于电影标题数量庞大,无法区分电影名称,但明显可以看出模型已经将电影分为三组。

使用 DARCH 进行乳腺癌检测

在这一部分,我们将使用 darch 包,该包用于深度架构和限制玻尔兹曼机RBM)。darch 包是基于 G. E. Hinton 和 R. R. Salakhutdinov 的代码(可以通过 MATLAB 代码获得,适用于深度信念网络DBN))构建的。此包用于生成具有多层的神经网络(深度架构)并通过作者提出的方法对其进行训练。

该方法包括使用对比散度方法进行预训练,并使用常见的训练算法(如反向传播或共轭梯度法)进行微调。此外,监督微调可以通过 maxout 和 dropout 两种新近发展的技术进行增强,这些技术用于改善深度学习的微调。

示例的基础是基于一组输入进行分类。为此,我们将使用数据集 BreastCancer.csv 中的数据,该数据集在 第五章中提到过,在 R 中训练和可视化神经网络。该数据取自 UCI 机器学习仓库,数据集会在 Wolberg 博士报告其临床病例后定期更新。数据包含乳腺癌患者的资料,并根据十个独立变量对良性或恶性肿瘤进行分类。

要获取数据,我们借用了 UCI 机器学习仓库中大量可用的数据,网址为 archive.ics.uci.edu/ml

数据的详细信息如下:

  • 实例数量:699(截至 1992 年 7 月 15 日)

  • 属性数量:10 个加上类属性

  • 属性信息:类属性已移至最后一列

属性的描述如下:

 #  Attribute                     Domain
   -- -----------------------------------------
   1\. Sample code number            id number
   2\. Clump Thickness               1 - 10
   3\. Uniformity of Cell Size       1 - 10
   4\. Uniformity of Cell Shape      1 - 10
   5\. Marginal Adhesion             1 - 10
   6\. Single Epithelial Cell Size   1 - 10
   7\. Bare Nuclei                   1 - 10
   8\. Bland Chromatin               1 - 10
   9\. Normal Nucleoli               1 - 10
  10\. Mitoses                       1 - 10
  11\. Class:                        (2 for benign, 4 for malignant)

为了理解 darch 函数,我们首先设置一个 XOR 门,然后将其用于训练和验证。darch 函数使用输出数据和输入属性来构建模型,该模型可以由 darch 本身进行内部测试。在这种情况下,我们达到了 0% 的错误率和 100% 的准确率。

接下来,我们使用乳腺癌数据构建 darch 模型,并检查其准确性:

#####################################################################
####Chapter 7 - Neural Networks with R #########
####Breast Cancer Detection using darch package #########
#####################################################################
library("mlbench")
library("darch")

data(BreastCancer)
summary(BreastCancer)

data_cleaned <- na.omit(BreastCancer) 
summary(data_cleaned)

model <- darch(Class ~ ., data_cleaned,layers = c(10, 10, 1),
 darch.numEpochs = 50, darch.stopClassErr = 0, retainData = T)

plot(model)

predictions <- predict(model, newdata = data_cleaned, type = "class")
cat(paste("Incorrect classifications:", sum(predictions != data_cleaned[,11])))
table(predictions,data_cleaned[,11])

library(gmodels)
CrossTable(x = data_cleaned$Class, y = predictions,
 prop.chisq=FALSE)

我们开始逐行分析代码,详细解释所有用于捕捉结果的特性:

library("mlbench")
library("darch")

初始代码的前两行用于加载运行分析所需的库。

请记住,要安装初始 R 发行版中未包含的库,必须使用 install.package 函数。这是安装软件包的主要函数。它接受一个名称向量和目标库,从仓库下载并安装软件包。此函数应仅使用一次,而不是每次运行代码时都调用。

mlbench 库包含了一系列人工和真实世界的机器学习基准问题,例如包括来自 UCI 仓库的几个数据集。

darch 库是一个用于深度架构和 RBM 的包:

data(BreastCancer)

使用这个命令,我们加载了名为 BreastCancer 的数据集,正如在 mlbench 库中提到的。现在让我们看看它是否已经加载:

summary(BreastCancer)

使用这个命令,我们可以通过 summary() 函数查看简要的总结。

请记住,summary() 函数是一个通用函数,用于生成各种模型拟合函数的结果摘要。该函数会调用特定的方法,这些方法取决于第一个参数的类。

在这种情况下,函数已应用于数据框,结果列在下图中:

summary() 函数返回每个变量的一组统计信息。特别有用的是突出显示 Class 变量的结果,该变量包含癌症肿块的诊断信息。在这种情况下,检测到 458良性 类别和 241恶性 类别的病例。另一个需要强调的特征是 Bare.nuclei 变量。对于该变量,检测到 16 个缺失值的案例。

为了去除缺失值,我们可以使用 na.omit() 函数:

data_cleaned <- na.omit(BreastCancer) 

现在我们构建并训练模型:

model <- darch(Class ~ ., data_cleaned,layers = c(10, 10, 1),
 darch.numEpochs = 50, darch.stopClassErr = 0, retainData = T)

为了评估 model 的性能,我们可以绘制原始网络误差图:

plot(model)

错误与周期(epoch)的图形如下所示:

我们在 34 个周期时得到了最小的错误。

我们最终训练好了网络并准备好使用;现在我们可以用它来进行预测:

predictions <- predict(model, newdata = data_cleaned, type = "class")

我们使用了我们所有可用的数据集来进行预测。我们只需要将模型预测结果与数据集中可用的实际结果进行比较:

cat(paste("Incorrect classifications:", sum(predictions != data_cleaned[,11])))

结果如下所示:

> cat(paste("Incorrect classifications:", sum(predictions != data_cleaned[,11])))
Incorrect classifications: 2

结果真的很好!只有两个错误的分类!我认为我们可以对从 683 个观测值开始的结果感到满意。为了更好地理解错误是什么,我们构建了一个混淆矩阵:

table(predictions,data_cleaned[,11])

结果如下所示:

> table(predictions,data_cleaned[,11])

predictions benign malignant
 benign       443         1
 malignant      1       238

尽管这是一种简单的方式,矩阵告诉我们我们仅犯了两个错误,并且错误在类别的两个值之间均匀分布。有关混淆矩阵的更多信息,我们可以使用 gmodels 包中的 CrossTable() 函数。和往常一样,在加载这个包之前,您需要先安装它:

library(gmodels)
CrossTable(x = data_cleaned$Class, y = predictions,
 prop.chisq=FALSE)

使用 CrossTable() 函数获得的混淆矩阵如下图所示:

正如我们在分类中预期的那样,我们的模型只有两个错误:FPFN。然后计算准确率;如第二章《神经网络中的学习过程》所示,准确率由以下公式给出:

让我们在 R 环境中计算准确率:

> Accuracy = (443+238)/683
> Accuracy
[1] 0.9970717

如前所述,分类器取得了非常好的成绩。

总结

在本章的最后,我们看到了神经网络和深度学习的一些应用案例。这些内容应该构成你未来从事神经网络工作的基础。在大多数情况下,使用方法是常见的,训练和测试过程中模型所涉及的数据集会有所变化。

我们在本章中看到以下示例:

  • 将 TensorFlow 和 Keras 与 R 集成,这为使用 R 构建广泛的应用案例开辟了巨大空间

  • 使用 H2O 构建数字识别器,通过分类实现

  • 使用 MxNet 理解 LSTM 函数

  • 使用 H2O 进行 PCA

  • 使用 H2O 构建自编码器

  • 使用darch解决分类问题

R 是一种非常灵活的统计编程语言,是全球数据科学家的主要工具。掌握 R 中的神经网络将有助于社区的进一步发展,并增加 R 在深度学习和新兴应用中的使用。

posted @ 2025-07-13 15:43  绝不原创的飞龙  阅读(49)  评论(0)    收藏  举报