R 神经网络(二)

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

译者:飞龙

协议:CC BY-NC-SA 4.0

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

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

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

本章帮助理解以下主题:

  • 感知机的解释

  • 线性可分分类器

  • 简单的感知机实现函数

  • 多层感知机MLPs

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

感知机及其应用

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

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

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

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00080.jpeg

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

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

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

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00081.jpeg

这里**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

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

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

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00082.jpeg

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

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

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

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

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

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

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00083.jpeg

我们可以看到这里的线性交互;输出 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,而不是在单个输入向量上,而是根据感知机在整个输入向量集上的行为进行修改。

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

线性分离

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

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00084.jpeg

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

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

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

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

x1x2y (AND 门)
111
100
010
000

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

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00085.jpeg

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

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

x1x2y (或门)
111
101
011
000

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

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00086.jpeg

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

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

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

x1x2y (XOR 门)
110
101
011
000

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

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00087.jpeg

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

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

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物种的线性分离。下图展示了萼片长度花瓣长度之间的关系,分别对应两种鸢尾花:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00088.jpeg

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

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,并在每个循环中尝试更新权重。一旦输出和函数*(weights*inputs)*相等,我们就停止训练并退出。更新后的权重将由函数返回。该函数的目标是获得模型所需的最优权重集,如下所示:

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变量(感知器)的截距和斜率,并绘制了线性分离线,得到如下图表:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00089.gif

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

多层感知器

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

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00090.jpeg

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

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

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

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

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00091.jpeg

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 对象的权重矩阵。下图显示了摘要结果的截图:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00092.jpeg

现在我们来衡量算法在模型训练中的表现:

plotIterativeError(model)

plotIterativeError() 函数绘制了模型网络的迭代训练和测试误差。结果显示在下图中:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00093.jpeg

上图显示了迭代拟合误差作为黑线,迭代测试误差作为红线。从图中可以看出,两条线都有明显的下降趋势,证明算法迅速收敛。

在正确训练好模型之后,是时候用它来进行预测了:

predictions = predict(model,iris$inputsTest)

在这个案例中,我们使用了predict()函数。这是一个通用的预测函数,适用于从各种模型拟合函数的结果中进行预测。该函数调用特定的方法,这些方法取决于第一个参数的类。我们既有预测值也有实际数据,我们只需要通过回归误差计算将它们进行比较:

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

为了绘制回归误差,我们使用了plotRegressionError()函数。该函数在X轴上显示目标值,在Y轴上显示拟合/预测值。最佳拟合应该是通过零点的直线,且斜率为一。这条最佳线在下图中以黑色表示。对实际数据的线性拟合则用红色显示。下图展示了我们之前训练的模型的回归误差:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00094.gif

现在,让我们通过计算混淆矩阵来评估模型在预测数据方面的表现:

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 曲线(训练阶段在左,测试阶段在右):

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00095.gif

如前所述,在训练阶段出现了错误,但在测试阶段则没有。

请注意,我们使用了 par() 函数在一个窗口中显示这两个图表。在其中,我们设置了将图形以矩阵形式显示,行数为 1,列数为 2。

RSNNS 中没有 plot 函数,因此我们使用来自 GitHub 的 plot 函数来绘制我们刚刚构建的神经网络的 MLP。这里有三个输出类别和四个输入节点:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00096.jpeg

我们已经看到使用 RSNNSiris 数据集神经网络的简单实现。同样的 mlp() 函数可以用于任何 MLP 神经网络架构。

总结

在这一章中,我们介绍了感知机的概念,它是神经网络的基本构建块。我们还看到了多层感知机以及使用 RSNNS 的实现。简单的感知机仅对线性分离问题有效,对于输出数据不线性可分的情况则无法使用。这些局限性通过使用 MLP 算法得到了克服。

我们理解了感知机的基本概念及其在神经网络算法中的应用。我们发现了线性可分的分类器及其适用的函数。我们学习了在 R 环境中实现简单感知机的函数,并且接着学习了如何训练和建模 MLP。

在下一章中,我们将理解如何使用神经网络模型来训练、测试和评估数据集。我们将学习如何在 R 环境中可视化神经网络模型。我们将涵盖诸如早停法、避免过拟合、神经网络的泛化和神经网络参数的缩放等概念。

第五章:在 R 中训练和可视化神经网络

如在第一章《神经网络与人工智能概念》、第二章《神经网络中的学习过程》中所见,训练神经网络模型为构建神经网络奠定了基础。

前馈和反向传播是用于确定模型权重和偏置的技术。权重不能为零,但偏置可以为零。首先,权重会被初始化为一个随机数,通过梯度下降法,误差最小化;我们获得一组最佳的权重和偏置值。

一旦模型使用任何 R 函数训练完成,我们可以将独立变量传递给模型,以预测目标或未知变量。在本章中,我们将使用一个公开可用的数据集来训练、测试并可视化一个神经网络模型。以下内容将被涵盖:

  • 使用神经网络模型训练、测试和评估数据集

  • 可视化神经网络模型

  • 提前停止

  • 避免过拟合

  • 神经网络的泛化

  • 神经网络参数的缩放

  • 集成模型

本章结束时,我们将理解如何使用神经网络模型训练、测试和评估数据集。我们将学习如何在 R 环境中可视化神经网络模型。我们将讨论如提前停止、避免过拟合、神经网络泛化和神经网络参数缩放等概念。

使用神经网络进行数据拟合

数据拟合是构建一条与一组先前收集的数据点最匹配的曲线或数学函数的过程。曲线拟合可以涉及插值,即要求精确的数据点,也可以是平滑拟合,即构建一个平坦的函数来近似数据。通过数据拟合获得的近似曲线可以帮助展示数据、预测没有数据的函数值,并总结两个或多个变量之间的关系。下图展示了收集数据的线性插值:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00097.jpeg

数据拟合是将一组输入数据训练神经网络,以产生相关的目标输出数据的过程。一旦神经网络完成数据拟合,它就形成了输入输出关系的泛化,并可以用于生成未曾训练过的输入数据的输出。

车辆的燃油消耗一直是全球主要制造商研究的课题。在一个以石油补给问题和更严重的空气污染问题为特征的时代,车辆的燃油消耗已成为一个关键因素。在本例中,我们将构建一个神经网络,目的是根据某些特征预测车辆的燃油消耗。

为此,使用我们在第三章的示例中已经使用过的 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数据集中的一些数据:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00098.jpeg

如你所见,该数据库由 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),如下面的图表所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00099.jpeg

记住,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 列的矩阵:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00100.jpeg

从前图的分析中,我们确认了之前提到的内容。我们可以注意到,马力较大的汽车油耗更高。同样的结论也适用于发动机排量;在这种情况下,排量较大的汽车油耗更高。再次强调,马力和排量较大的汽车多为美国生产。

相反,加速值较高的汽车通常有更低的油耗。这是因为这类车的重量较轻。通常来说,重型汽车加速较慢。

神经网络模型

在第二章,神经网络中的学习过程中,我们在构建网络之前进行了数据缩放。当时我们提到,训练神经网络之前对数据进行标准化是一个良好的实践。通过标准化,可以消除数据的单位,使得来自不同位置的数据能够轻松进行比较。

并非所有情况下都需要对数值数据进行标准化。然而,研究表明,当数值数据经过标准化时,神经网络的构建通常更高效,并且能够获得更好的预测效果。事实上,如果数值数据没有标准化,而两个预测变量的规模差异非常大,那么神经网络权重的变化对较大值的影响将更为显著。

有几种标准化技术;在第二章,神经网络中的学习过程中,我们采用了最小-最大标准化。在本章中,我们将采用 Z 分数标准化。该技术包括从每一列的每个值中减去该列的均值,然后将结果除以该列的标准差。实现这一点的公式如下:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00101.jpeg

总结来说,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匹配的调用。
responsedata 参数中提取。
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)

神经网络图显示在下图中:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00102.gif

在前面的图中,黑色线条(这些线条从输入节点开始)显示了每一层之间的连接以及每个连接上的权重,而蓝色线条(这些线条从偏置节点开始,偏置节点通过数字 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)

神经网络模型(左侧)与线性回归模型(右侧)在测试集上的表现对比如下图所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00103.gif

如我们所见,神经网络的预测值比线性回归模型的更集中在直线周围,尽管你可能没有注意到很大差异。

使用神经网络分类乳腺癌

乳房由一组腺体和脂肪组织组成,位于皮肤和胸壁之间。实际上,它不是一个单一的腺体,而是一组叫做小叶的腺体结构组合而成的一个叶。在每个乳房中,有 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() 函数是用于生成各种模型拟合函数结果摘要的通用函数。该函数调用依赖于第一个参数的类别的特定方法。

在这种情况下,该函数被应用于数据框架,并且结果显示在以下截图中:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00104.jpeg

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)

结果显示在以下截图中:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00105.jpeg

如你所见,现在已经没有缺失值了。

现在,让我们进入探索性分析。我们可以做的第一件事是绘制变量的箱线图。通过查看summary()函数的结果,已经初步了解了一些情况。自然地,我们将只关注数值型变量。

boxplot(data_cleaned[,2:10])

在下图中,展示了清洗过的数据集(data_cleaned)中数值变量(从第 2 个到第 10 个)的箱线图:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00106.jpeg

从前一个图形的分析中,我们可以注意到几个变量有异常值,其中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 个)的直方图:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00107.gif

从直方图的分析中,可以注意到一些变量存在异常值。

神经网络模型

和之前的示例一样,在构建和训练网络之前,我们必须先进行数据标准化。在本例中,我们将采用最小-最大标准化方法。

请记住,在训练神经网络之前,进行数据标准化是良好的实践。通过标准化,可以消除数据的单位,使来自不同位置的数据可以轻松进行比较。

在我们开始之前,使用str()函数做一个进一步的检查。这个函数提供了对象内部结构的紧凑显示,是一种诊断功能,并且是summary()函数的替代方法。理想情况下,每个基本结构仅显示一行。它特别适合紧凑地显示(可能嵌套的)列表的(简化)内容。其目的是为任何 R 对象提供合理的输出。

str(data_cleaned)

结果显示在以下截图中:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00108.jpeg

正如可以注意到的,变量作为因子存在。我们需要对其进行转换以便于计算。

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] * 范围内。实现此目的的公式如下:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00109.jpeg

在应用选择的标准化方法之前,您必须计算每个数据库列的最小值和最大值。为此,我们使用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()函数:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00110.jpeg

如您所见,现在数据在零和一之间。此时,我们重新构建数据集,加入我们的目标变量(即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))

训练网络的时刻已经到来。

网络训练阶段

人工神经网络由并行运行的简单元素组成。网络元素之间的连接至关重要,因为它们决定了网络的功能。这些连接通过其权重影响结果,而权重是在神经网络训练阶段调节的。下图展示了串行和并行处理的比较:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00111.jpeg

然后,在训练阶段,通过更改连接权重来调节网络,使得特定的输入能引导到特定的输出。例如,通过比较输出(我们实际计算的结果)和目标(我们希望得到的结果),直到网络输出与目标匹配,从而调整网络。为了获得足够可靠的结果,需要许多输入/目标对来形成网络。下图展示了训练阶段的简单流程图:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00112.jpeg

这些权重调整的方式由我们采用的具体算法决定。在强调算法在网络训练中的重要性后,必须特别关注提供给网络的数据准备。

在网络训练过程中,必须调整权重和偏置,以优化网络的性能。这是整个过程中的最重要阶段,因为网络越好,通用化能力在处理新的、未知的数据时表现越好。在这个阶段,随机抽取一部分收集到的数据(通常是 70%的可用数据)。

在神经网络训练后,我们可以使用该网络,在这一阶段,随机抽取一部分收集到的数据(通常是 30%的可用数据)传递给网络进行测试。然后,神经网络对象可以保存,并且在任何新数据上多次使用。下图展示了原始数据集是如何被划分的:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00113.jpeg

这段代码中的数据划分如下:

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)

神经网络的图示如下所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00114.jpeg

在之前的图中,黑色的线(这些线从输入节点开始)显示了各层之间的连接以及每个连接上的权重,而蓝色的线(这些线从偏置节点开始,偏置节点由数字一区分)显示了每一步中添加的偏置项。可以把偏置看作线性模型的截距。

测试网络

我们终于训练好了网络并准备好使用它了。现在,我们可以使用它来进行预测。记住,我们已经将 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()函数得到的混淆矩阵如下所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00115.jpeg

主对角线上的单元格包含分类器正确分类的样本计数。在左上角的单元格中,标记为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 过程。红色(或深色)神经元为被丢弃的神经元,而神经网络模型在没有这些神经元的情况下依然能够生存,表现出较少的过拟合和更高的准确性:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00116.gif

神经网络的泛化

泛化的目标是使模型能够拟合训练数据。这是我们在神经网络模型上进行训练的扩展。其目标是最小化模型在训练数据上的平方误差和(例如使用普通最小二乘法),并减少模型的复杂度。

以下是列出的泛化方法:

  • 提前停止训练

  • 使用不同训练数据重新训练神经网络

    • 使用随机抽样、分层抽样或任何有效的目标数据组合
  • 训练多个神经网络并对它们的输出进行平均

神经网络模型中的数据缩放

数据缩放或归一化是将模型数据转换为标准格式的过程,从而提高训练的效果、准确性和速度。神经网络中数据缩放的方法类似于任何机器学习问题中的数据归一化方法。

一些简单的数据归一化方法列举如下:

  • Z-score 归一化:如前所述,首先计算给定数据的算术平均值和标准差。然后按以下方式计算标准化得分或Z得分:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00117.jpeg

这里,X 是数据元素的值,μ 是均值,σ 是标准差。Z 得分或标准得分表示数据元素距离均值的标准差数目。由于均值和标准差对异常值敏感,因此此标准化方法对异常值也很敏感。

  • 最小-最大归一化:此方法为每个数据元素计算以下内容:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00118.jpeg

这里,x[i] 是数据元素,min(x) 是所有数据值中的最小值,max(x) 是所有数据值中的最大值。此方法将所有分数转换到一个共同的范围 [0, 1]。然而,它对异常值比较敏感。

  • 中位数和 MAD:中位数和中位数绝对偏差(MAD)归一化通过以下公式计算归一化数据值:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00119.jpeg

在这里,*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 如下图所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00120.gif

在这里,一个实例的输出作为下一个实例的输入,应用于同一个神经元。数据如何在不同时间点保持在记忆中并流动,使得 RNN 强大而成功。

在 RNN 中,数据向后流动的方式有更多变种:

  • 完全循环

  • 递归

  • 霍普菲尔德(Hopfield)

  • 埃尔曼(Elman)和乔丹(Jordan)网络

  • 神经历史压缩器

  • 长短期记忆(LSTM)

  • 门控循环单元(GRU)

  • 双向

  • 循环多层感知机(Recurrent MLP)

循环网络旨在识别作为数据序列的模式,对预测和预测任务非常有帮助。它们可以应用于文本、图像、语音和时间序列数据。RNN 是强大的人工神经网络(ANN)之一,代表了生物大脑,包括具有处理能力的记忆。循环网络从当前输入(像前馈网络一样)和先前计算的输出中获取输入:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00121.gif

为了更好地理解这一点,我们将 RNN 看作是一个神经网络的网络,其循环特性以以下方式展开。神经元h的状态会在不同的时间段(t-1tt+1等)被考虑,直到收敛或达到总的训练轮数。

Vanilla 是最早提出的循环 ANN 模型。一个 vanilla RNN 如下图所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00122.gif

由于实现的简便性,其他变种如 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,如下图所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00123.jpeg

到此为止,我们可以设置网络的参数,并通过标签选择合适的值填入框中。以下参数必须正确设置:

  • time dimension

  • training sample dimension

  • testing sample dimension

  • number of hidden layers

  • 层数 1 中的单元数

  • 层数 2 中的单元数

  • learningrate

  • batchsize

  • numepochs

  • momentum

  • learningrate_decay

做完这些后,我们只需点击训练按钮,命令就会被构建并进行训练。

下图展示了模拟的结果:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00124.jpeg

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')

该图显示了周期与错误之间的关系:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00125.gif

现在模型已经准备好,我们可以使用它来测试网络。但首先,我们需要创建一些测试数据:

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) )

错误的直方图如下所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00126.gif

如图所示,频率较高的区间靠近零,表明在大多数情况下,预测值与当前值一致。所有其他区间与误差相关。因此,我们可以说网络以良好的性能模拟了系统。

LSTM 模型

我们已经看到,RNN 具有一种记忆,利用持续的先前信息来处理当前神经网络的计算任务。之前的信息会在当前任务中被使用。然而,这种记忆是短期的,神经节点无法访问所有的历史信息。

当我们在 RNN 中引入长期记忆时,就能记住大量的历史信息并在当前处理时使用。这一概念被称为 LSTM 模型,它在视频、音频、文本预测及其他各种应用中有着广泛的使用场景。

LSTM 由 Hochreiter 和 Schmidhuber 于 1997 年提出。

LSTM 网络使用BPTT进行训练,并减轻了消失梯度问题。LSTM 在时间序列预测中有强大的应用,并能够创建大型递归网络来解决机器学习中的复杂序列问题。

LSTM 具有,使得长短期记忆成为可能。这些门包含在通过层连接的记忆块中:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00127.gif

单元内有三种类型的门:

  • 输入门:将输入缩放到单元(写入)

  • 输出门:将输出缩放到单元(读取)

  • 遗忘门:将旧的单元值缩放(重置)

每个门就像一个开关,控制读/写,从而将长期记忆功能整合到 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):

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00128.gif

卷积神经网络(CNN)是由神经网络层组成的序列,其中每一层通过可微分函数将一个激活值的体积转换为另一个激活值。CNN 包含三种类型的层:

  • 卷积层

  • 池化层

  • 完全连接层

第#1 步 – 滤波

卷积层执行繁重的数学运算。在计算机视觉中,处理图像的典型方法是用滤波器进行卷积,只提取其中的显著特征。这是 CNN 中的第一步操作。输入图像应用滤波器逻辑,创建激活图特征图

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00129.gif

卷积特征向量是通过将卷积核向量应用到图像的每个 3 x 3 向量上创建的。

滤波的数学步骤如下:

  1. 将特征与图像补丁对齐。

  2. 将每个图像像素与相应的特征像素相乘。

  3. 将它们加起来。

  4. 将每个求和结果除以特征中像素的总数。

滤波完成后,下一步是压缩已滤波的像素。

第#2 步 – 池化

在这一步,我们缩小图像堆栈。对于卷积步骤中获得的每个特征,我们建立一个矩阵,并在每个选择的矩阵中找到最大值,从而缩小整个输入。步骤如下:

  1. 选择一个窗口大小(通常为 2 或 3)。

  2. 选择一个步幅移动像素范围(通常为 2)。

  3. 在已滤波的图像上滑动窗口。

  4. 对于每个窗口,我们取最大值。

如果滑动窗口没有与之前的窗口相同数量的单元,我们取所有可用的值。

第#3 步 – ReLU 归一化

在这一步,我们采用池化输出,并对每个像素应用 ReLU 归一化以调整值。如果任何值为负数,我们将其设为零。

第#4 步 – 在完全连接层进行投票和分类

最后一层是完全连接层,通过一组值进行投票,确定输出的类别。完全连接层只是所有先前输出的合并矩阵。

这是最后一层,输出根据最高票选类别确定。

通过将第 1、2、3 步中的层叠加,我们形成了卷积网络,利用反向传播减少误差项,从而为我们提供最佳预测。

层次可以重复多次,每个层的输出会作为下一个层的输入。

经典的 CNN 架构将如下所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00130.jpeg

以下图例展示了使用 CNN 进行分类预测的示例:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00131.jpeg

我们将在第七章中看到使用 R 实现 CNN 的案例,神经网络的应用案例 - 高级话题

常见的 CNN 架构 - LeNet

LeNet-5 是 Le Cun 在 1990 年代为手写和机器印刷字符识别设计的卷积网络。

这是卷积网络的第一次成功应用。它具有以下架构:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00132.jpeg

使用 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 函数用于在数据框对象上调用类似电子表格的数据查看器,如下图所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00133.jpeg

回到代码,如前所述,我们只使用了两个变量。此外,数据集包含来自澳大利亚不同位置的数据。我们将把研究范围限制在第一个位置(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] *范围内。其公式如下:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00134.gif

在归一化过程中,我们必须计算每一列数据库的最小值和最大值。然后,我们将转置得到的矩阵:

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')

这张图展示了周期错误之间的关系:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00135.gif

我们终于训练好了网络并准备好使用它;现在我们可以用它来进行预测。记住,我们已经将 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))

以下图展示了实际值和预测值:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/nn-r/img/00136.gif

从图表分析中,我们可以注意到一件事:数据经过良好的调整,表明模型能够较好地预测湿度条件。

总结

在本章中,我们了解了 RNN 以及如何利用内部记忆进行处理。我们还介绍了 CNN,它是一种主要用于图像识别的标准化神经网络。对于 RNN,我们研究了一些 R 中的示例实现。

我们学习了如何训练、测试和评估 RNN。我们还学习了如何在 R 环境中可视化 RNN 模型。我们发现了 LSTM 模型,并介绍了 CNN 的概念以及一种常见的 CNN 架构:LeNet。

在下一章中,我们将看到更多涉及神经网络和深度学习的 R 实现的应用案例。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值