《Deep Learning for Computer Vision withPython》阅读笔记-StarterBundle(第10章)

1.神经网络基础

在本章中,我们将深入研究神经网络的基本原理。我们将首先讨论人工神经网络,以及它们是如何受到我们身体中现实生活中的生物神经网络的启发。从这里,我们将回顾经典的感知器算法,以及它在神经网络历史上所扮演的角色。

在感知器的基础上,我们还将研究反向传播算法,这是现代神经学习的基石——没有反向传播,我们将无法有效地训练我们的网络。我们还将从头开始用Python实现反向传播,以确保我们理解这个重要的算法。

当然,像Keras这样的现代神经网络库已经内置了(高度优化的)反向传播算法。手工实现反向传播每次我们想训练一个神经网络就像编码一个链表或哈希表数据结构从头每次我们通用编程问题,它不仅是不现实的,但它也浪费我们的时间和资源。为了简化这个过程,我将演示如何使用Keras库创建标准的前馈神经网络。

最后,我们将通过讨论构建任何神经网络时需要的四个成分来结束本章。

10.1神经网络基础知识

  1. 人工神经网络及其与生物学的关系。
  2. 最重要的感知器算法。
  3. 反向传播算法,以及如何使用它来有效地训练多层神经网络。
  4. 如何使用Keras库训练神经网络。

当你完成本章时,你将对神经网络有一个很好的理解,并能够继续学习更高级的卷积神经网络。

10.1.1神经网络概述

神经网络是深度学习系统的基石。为了在深度学习方面取得成功,我们需要首先回顾神经网络的基础知识,包括体系结构、节点类型和“教学”网络的算法。

什么是神经网络?

答案就在我们自己的身体里。我们每个人都有一个现实生活中的生物神经网络,它连接着我们的神经系统——这个网络由大量相互连接的神经元(神经细胞)组成。

“神经”一词是“神经元”的形容词形式,“网络”是一种图形状结构;因此,“人工神经网络”是一种计算系统,它试图模仿(或至少是受到启发)我们神经系统中的神经连接。人工神经网络也被称为“神经网络”或“人工神经系统”。通常将人工神经网络简称为“ANN”或简称为“NN”——在本书的其余部分中,我将使用这两个缩写。

对于一个被认为是神经网络的系统,它必须包含一个有标记的有向图结构,图中的每个节点执行一些简单的计算。从图论中,我们知道有向图由一组节点(即顶点)和一组连接(即边)组成,这些连接将成对的节点连接在一起。在图10.1中,我们可以看到这样一个NN图的例子。

每个节点执行一个简单的计算。然后,每个连接携带一个信号(即计算的输出)从一个节点到另一个节点,用一个权重标记,表示信号被放大或减弱的程度。有些连接具有较大的正权值来放大信号,这表明该信号在进行分类时非常重要。其他的则有负权,降低了信号的强度,从而说明节点的输出在最终的分类中不那么重要。如果这样的系统由一个图结构(如图10.1所示)组成,并且连接权值可以通过学习算法进行修改,我们就称其为人工神经网络。

与生物学的关系

然而,请记住,人工神经网络只是受到我们对大脑及其工作方式的了解的启发。深度学习的目标不是模仿我们大脑的功能,而是从我们理解的部分入手,让我们在自己的工作中找到相似之处。说到底,我们对神经科学和大脑的深层功能了解不够,无法正确地模拟大脑的工作方式——相反,我们会从那里获得灵感并继续前进。

人造模型

让我们先来看看一个基本的神经网络,它对图10.3中的输入执行一个简单的加权求和。值x1,x2和,x3是我们的NN的输入,通常对应于我们设计矩阵中的单行(即数据点)。常数值1是我们假设嵌入到设计矩阵中的偏差。我们可以把这些输入看作是NN的输入特征向量。

在实践中,这些输入可以是用于以系统的、预定义的方式量化图像内容的向量(例如,颜色直方图、定向梯度直方图[32]、局部二值模式[21]等)。在深度学习的背景下,这些输入是图像本身的原始像素强度。

每个x通过由w1,w2,…,wn组成的权值向量W连接到神经元,这意味着对于每个输入x,我们也有一个相关的权值W。

最后,图10.3右侧的输出节点取加权和,应用激活函数f(用于确定神经元是否“触发”),并输出一个值。用数学方式表示输出,你通常会遇到以下三种形式:

无论输出值是如何表示的,请理解我们只是取输入的加权和,然后应用激活函数f。

激活函数

最简单的激活函数是“阶跃函数”,由感知器算法使用(我们将在下一节介绍)。

从上面的方程可以看出,这是一个非常简单的阈值函数。如果加权∑ni=1 wixi > 0,则输出1,否则输出0。

沿着x轴绘制输入值,并沿着y轴绘制f (net)的输出,我们可以看到为什么这个激活函数接收到它的名字(图10.4,左上角)。当净值小于等于0时,f的输出总是0。如果net大于0,那么f将返回1。因此,这个功能看起来像一个楼梯台阶,与你每天上下行走的楼梯没有什么不同。

然而,阶跃函数虽然直观易用,但不可微,这在应用梯度下降和训练我们的网络时可能会产生问题。

 

对于学习来说,s型函数比简单的阶跃函数更好,因为它:1。是连续可微的。2. 围绕y轴对称。3.渐近地接近其饱和值。

这里的主要优点是,s形函数的平滑性使得设计学习算法更容易。然而,关于s形函数有两个大的问题:1。s形函数的输出不是以零为中心的。2. 饱和的神经元基本上会杀死梯度,因为梯度的delta会非常小。

直到20世纪90年代末,双曲正切(与s形相似)也被大量用作激活函数(图10.4,左中):

tanh函数是零中心的,但当神经元饱和时,梯度仍然被杀死。

我们现在知道有比s形函数和双曲函数更好的激活函数。具体来说,Hahnloser等人在2000年的论文《cortex-inspired silicon circuit》[101]中引入了整流线性单元(ReLU),定义为:

ReLU也被称为“斜坡函数”,这是由于它们在绘制时的样子(图10.4,中右)。注意函数在负输入时是零,而在正输入时是线性增长的。ReLU函数是不饱和的,而且计算效率极高。

根据经验,在几乎所有的应用中,ReLU激活函数往往都优于s形函数和双曲函数。结合的工作Hahnloser和Seung跟踪2003年允许和F orbidden集对称Threshold-Linear网络[102],发现ReLU激活函数具有较强的生物动机比前一个激活函数的家庭,包括更完整的数学的理由。

截至2015年,ReLU是深度学习[9]中最常用的激活函数。然而,当我们的值为0时,一个问题出现了——梯度不能取。

ReLUs的另一种变体Leaky ReLUs[103]允许在不活动的情况下有一个小的非零梯度:

参数ReLUs,简称prelu[96],建立在Leaky ReLUs的基础上,允许参数α在逐个激活的基础上被学习,这意味着网络中的每个节点可以学习不同于其他节点的“泄漏系数”。

最后,我们还有Clevert等人在2015年的论文《利用指数线性单元(ELUs)快速准确深度学习》中引入的指数线性单元(ELUs) [104]:

α的值是恒定的,并且在网络架构被实例化时被设置——这不同于在prelu中α是被学习到的。α的典型值为α = 1.0。图10.4(右下)展示了ELU激活功能。

通过Clevert等人的工作(以及我自己的轶事实验),ELUs通常比ReLUs获得更高的分类精度。elu很少会比你的标准ReLU功能差。

我应该使用哪个激活功能?

鉴于最近的深度学习的流行,激活函数也出现了相应的爆炸式增长。由于激活函数的选择很多,无论是现代激活函数(ReLU, Leaky ReLU, ELU等)还是“经典”激活函数(step, sigmoid, tanh等),选择一个合适的激活函数似乎是一项艰巨的任务,甚至可能是一项艰巨的任务。

然而,在几乎所有情况下,我都建议从ReLU开始,以获得基线精度(就像发表在深度学习文献中的大多数论文一样)。从那里,你可以试着把你的标准ReLU换成一个漏的ReLU变体。

我个人的偏好是从ReLU开始,调整我的网络和优化器参数(架构、学习速率、正则化强度等),并注意其准确性。一旦我对精度相当满意,我就换入ELU,通常会注意到分类精度有1 - 5%的提高,这取决于数据集。再说一遍,这只是我的轶事建议。您应该进行自己的实验,并记录您的发现,但作为一般的经验法则,从一个正常的ReLU开始,并调优网络中的其他参数——然后切换一些更“奇特”的ReLU变体。

前馈网络体系结构

在这种类型的体系结构中,节点之间的连接只允许从第i层的节点连接到第i+1层的节点(因此称为前馈)。不允许向后或层间连接。当前馈网络包含反馈连接(反馈到输入的输出连接)时,它们被称为循环神经网络。

在这本书中,我们关注前馈神经网络,因为它们是现代深度学习应用于计算机视觉的基石。正如我们将在第11章中发现的,卷积神经网络只是前馈神经网络的一个特殊情况。

为了描述一个前馈网络,我们通常使用一个整数序列来快速、简洁地存储每一层的节点数量。以上图10.5为例,为3-2-3-2前馈网络:

0层包含3个输入,我们的xi值。这些可以是图像的原始像素强度或从图像中提取的特征向量。

第1层和第2层为隐藏层,分别包含2个和3个节点。

第三层是输出层或可见层——我们从网络中获得总体输出分类。输出层通常有与类标签一样多的节点;每个潜在输出对应一个节点。例如,如果我们要构建一个NN来对手写数字进行分类,那么我们的输出层将由10个节点组成,每个节点代表0-9位数字。

神经学习

神经学习是指修改网络中节点之间的权值和连接的方法。生物学上,我们根据Hebb原理来定义学习:

“当A细胞的一个轴突足够接近B细胞时,并在激发B细胞的过程中反复或持续地发生,在一个或两个细胞中发生一些生长过程或代谢变化,使得A细胞的效率,如其中一个B细胞的发射,增加”- Donald Hebb [105]

就神经网络而言,这一原则意味着,当输入相同时,具有相似输出的节点之间的连接强度应该增加。我们称之为相关学习,因为神经元之间连接的强度最终代表了输出之间的相关性。

神经网络的用途是什么?

神经网络可以在有监督、无监督和半监督学习任务中使用,当然,只要使用了适当的体系结构。对神经网络的完整综述不在本书的范围之内(请参见Schmidhuber[40]对深度人工网络的广泛研究,以及Mehrota[106]对经典方法的综述);然而,神经网络的常见应用包括分类、回归、聚类、矢量量化、模式关联和函数逼近等。

事实上,对于机器学习的几乎每一个方面,神经网络都以某种形式得到了应用。在本书的上下文中,我们将使用神经网络进行计算机视觉和图像分类。

神经网络基础概述

在本节中,我们回顾了人工神经网络(ann,简称nn)的基础知识。我们从研究神经网络背后的生物动机开始,然后学习如何用数学方法定义一个功能来模拟神经元的激活(即激活功能)。

基于这个神经元模型,我们能够定义一个网络的结构,它至少由一个输入层和一个输出层组成。一些网络架构可能在输入和输出层之间包含多个隐藏层。最后,每一层可以有一个或多个节点。输入层中的节点不包含激活函数(它们是输入图像的单个像素强度的“地方”);然而,隐藏层和输出层中的节点都包含一个激活函数。

我们还回顾了三种常用的激活函数:sigmoid、tanh和ReLU(及其变体)。

传统上使用s形函数和双曲函数来训练网络;然而,自Hahnloser等人2000年的论文[101]以来,ReLU函数得到了更广泛的应用。

2015年,ReLU是目前为止深度学习架构[9]中最流行的激活函数。基于ReLU的成功,我们还推出了Leaky ReLUs,这是ReLUs的一个变种,旨在通过允许函数取负值来提高网络性能。Leaky ReLU系列功能包括您的标准漏式ReLU变体、prelu和elu。

最后,值得注意的是,尽管我们严格地在图像分类的背景下关注深度学习,但神经网络已经以某种方式在几乎所有的机器学习领域中使用。

既然我们已经理解了nn的基础知识,那么让我们通过分析实际的体系结构及其相关的实现来使这些知识更加具体。在下一节中,我们将讨论经典的感知器算法,这是有史以来第一个被创建的ann之一。

10.1.2感知器算法

Rosenblatt在1958年首先提出了感知器:大脑中信息存储和组织的概率模型[12]可以说是最古老和最简单的神经网络算法。在这篇文章发表之后,基于感知机的技术在神经网络社区风靡一时。这篇论文本身对神经网络今天的普及和应用负有很大的责任。

但在1969年,“人工智能冬天”降临在机器学习领域,几乎让神经网络永远停滞。明斯基,Papert发表感知器:介绍计算几何[14],一本书,有效地停滞在神经网络研究了近十年,有许多争论关于这本书[107],但是作者成功地证明了单层感知器无法分割非线性数据点。

考虑到大多数真实世界的数据集是自然非线性可分的,这似乎是感知机,以及其他神经网络研究,可能会到达一个不合时宜的终点。

在明斯基和帕佩特的发表和神经网络革新工业的承诺被打破之间,人们对神经网络的兴趣大幅下降。直到我们开始探索更深层次的网络(有时被称为多层感知器)以及反向传播算法(Werbos[15]和Rumelhart[16]),上世纪70年代的“人工智能冬天”才结束,神经网络研究又开始升温。

综上所述,感知机仍然是一个非常重要的算法,因为它为更高级的多层网络奠定了基础。我们将在这一节开始回顾感知机的架构,并解释用于训练感知机的训练产生的(称为增量规则)。我们还会看看网络的终止标准(即感知机应该在什么时候停止训练)。最后,我们将用纯Python实现感知器算法,并使用它来研究和检查网络是如何无法学习非线性可分数据集的。

AND, OR和XOR数据集

在我们学习感知机本身之前,让我们先讨论一下“位运算”,包括AND、OR和XOR(异或)。如果你之前学过计算机科学的入门课程,你可能已经熟悉了位函数。

按位操作符和相关的按位数据集接受两个输入位,并在应用操作后产生最终的输出位。给定两个输入位,每一个可能的值为0或1,这两个位有四种可能的组合-表10.1提供了and, or和XOR可能的输入输出值:

正如我们在左边看到的,当且仅当两个输入值都为1时,逻辑AND为真。如果其中一个输入值为0,AND返回0。因此,当and的输出为真时,只有一种组合,即x0 = 1和x1 = 1。

在中间,我们有OR操作,当只有一个输入值为1时,这个操作为真。因此,有三种可能的x0和x1的组合,产生y = 1的值。

最后,右边显示XOR操作,当且仅当输入为1时,该操作为真。当OR有三种y = 1的情况时,XOR只有两种。

AND和OR都是线性可分的——我们可以很清楚地画一条线把0类和1类分开——对于异或则不是这样。现在花点时间让自己相信,在XOR问题中,要清晰地将两个类分开是不可能的。因此,异或是非线性可分数据集的一个例子。

与我们将数据分割成训练和测试分割的标准程序不同,当使用按位数据集时,我们只是在同一组数据上训练和评估我们的网络。我们的目标是确定我们的学习算法是否有可能学习数据中的模式。我们会发现,感知器算法可以正确地对AND和OR函数进行分类,但不能对XOR数据进行分类。

感知器架构

Rosenblatt[12]将感知器定义为一个使用标记例子(即监督学习)学习特征向量(或原始像素强度)的系统,将这些输入映射到它们相应的输出类标签。

在最简单的形式中,一个感知器包含N个输入节点,每个节点对应输入行中的每个条目设计矩阵,在网络中只有一层,该层只有一个节点(图10.7)。

从输入xi 's到网络中单个输出节点存在连接及其对应的权值w1,w2, . .wi。该节点取输入的加权和,并应用一个步长函数来确定输出类标签。感知器输出的结果要么是0,要么是1 - 0;因此,在它的原始形式中,感知器只是一个二进制的、两个类的分类器。

感知器训练过程和Delta规则

训练一个感知机是一个相当简单的操作。我们的目标是获得一组权重w,精确地对训练集中的每个实例进行分类。为了训练我们的感知机,我们反复地向网络输入我们的训练数据。每当网络看到完整的训练数据集时,我们就说一个时代过去了。通常要花很多时间才能学会权重向量w来线性地分离这两类数据。

实际的“学习”发生在步骤2b和2c。首先,我们通过网络传递特征向量x j,取权值w,得到输出y j。这个值然后通过阶跃函数传递,如果x > 0,则返回1,否则返回0。

现在,我们需要更新权值向量w,使其朝着“更接近”正确分类的方向前进。权值向量的更新由步骤2c中的delta规则处理。

表达式(d j−y j)决定输出分类是否正确。如果分类是正确的,那么这个差异将为零。否则,差异将是正的或负的,为我们提供了更新权值的方向(最终使我们更接近正确的分类)。然后将(dj−yj)乘以x j,使我们更接近正确的分类。

  1. 用小的随机值初始化权值向量w;
  2. 直到感知器收敛:
  3. (a)遍历训练集D中的每个特征向量x j和真类标签di
  4. (b)取x通过网络,计算输出值:y j = f (w(t)·x j)
  5. (c)对于所有特征0 <= i <= n,更新权值w: wi(t + 1) = wi(t) +η(d j−y j) xj,i

α值是我们的学习速率,它控制着我们迈出的一步的大小。正确设置这个值非常重要。较大的α值将使我们向正确的方向迈出一步;然而,这个步骤可能太大了,我们可能很容易超越局部/全局最优。

相反,小的α值可以让我们朝着正确的方向迈出一小步,确保我们不会超越局部/全局最小值;然而,这些微小的步骤需要我们花费大量难以驾驭的时间来学习。

最后,我们加入之前t时刻的权值向量wj (t),完成向正确分类“步进”的过程。如果你觉得这个训练过程有点混乱,不要担心——我们将在后面的10.1.2节用Python代码详细介绍它。

感知器训练终止

感知机的训练过程被允许继续进行,直到所有的训练样本被正确分类或达到预设的时间点。当α足够小且训练数据线性可分时,保证终止。

那么,如果我们的数据不是线性可分的或者我们在α中做了一个糟糕的选择会发生什么?训练会无限地持续下去吗?在这种情况下,不——我们通常在命中了一组epoch后停止,或者如果错误分类的数量在大量epoch中没有改变(这表明数据不是线性可分的)。关于感知器算法的更多细节,请参考Andrew Ng的斯坦福讲座[76]或Mehrota等人的介绍性章节[106]。

用Python实现感知器

1 #  import  the  necessary  packages

2 import numpy as np

3

4 class Perceptron:

5 def __init__(self,  N,  alpha=0.1):

6 #  initialize  the  weight  matrix  and  store  the  learning  rate

7 self.W  =  np.random.randn(N  + 1)  /  np.sqrt(N)

8 self.alpha  =  alpha

  1. N:输入特征向量的列数。在我们的位数据集上下文中,我们将N设为2,因为有两个输入。2. alpha:我们感知器算法的学习速率。默认情况下,我们将该值设置为0.01。一般的学习率选择在α = 0.1,0.01,0.001的范围内。

第7行文件我们的权矩阵W的随机值采样自一个“正态”(高斯)分布,均值和单位方差为零。权重矩阵将有N + 1个条目,一个代表特征向量的N个输入,另一个代表偏差。我们用W除以输入数的平方根,这是一种常用的方法,用于缩放我们的权矩阵,从而加快收敛速度。我们将在本章后面介绍权重初始化技术。

为了训练感知机,我们定义一个函数fit。如果你以前有过机器学习、Python和scikit-learn库的经验,那么你就会知道将你的训练过程函数命名为fit是很常见的,就像“fit a model to the data”一样:

import numpy as np

class Perceptron:
    def __init__(self, N, alpha=0.1):
        # 初始化权值矩阵,存储学习速率
        sel.W = np.random.randn(N + 1) / np.sqrt(N)
        self.alpha = alpha


    # 定义阶跃函数
    def step(self, x):
        # 应用阶跃函数
        return 1 if x > 0 else 0

    def fit(self, X, y, epochs=10):
        # 训练感知机
        X = np.c_[X, np.ones((X.shape[0]))]
        # 循环每一个epoch
        for epoch in np.ranges(0, epochs):
            # 循环遍历每个单独的数据点
            for (x, target) in zip(X, y):
                # 得到每一个单独数据点对应的阶跃函数值
                p = self.step(np.dot(x, self.W))

                # 如果预测不正确的话,更新权重
                if p != target:
                    error = p - target
                    #更新权重
                    self.W += -self.alpha * error * x


    # 定义的预测函数
    def predict(self, X, addBias=True):
        # 确定输入是一个矩阵
        X = np.atleast_2d(X)
        # 检查是否应该增加bias列
        if addBias:
            X = np.c_[X, np.ones((X.shape[0]))]

        return self.step(np.dot(X, self.W))

评估感知器按位数据集

from pyimagesearch.nn import Perceptron
import numpy as np

# 构造OR数据集
X = np.array([[0, 0],  [0, 1],  [1, 0],  [1, 1]])
y = np.array([[0],  [1],  [1],  [1]])


# 定义感知机,并进行训练
print("[INFO] training perceptron...")
p = Perceptron.Perceptron(X.shape[1], alpha=0.1)
p.fit(X, y, epochs=20)


# 评价感知机
print("[INFO] testing perceptron...")

# 遍历数据点
for (x, target) in zip(X, y):
    # 做一个预测,并将结果显示在其中
    pred = p.predict(x)
    # ground-truth是真实值的意思
    print("[INFO] data={}, ground-truth={}, pred={}".format(x, target[0], pred))


//截止到2021.12.20日晚上19:38 第137页

2021.12.24日晚上20:33学习笔记

And xor or感知机中xor感知机并未实现

And感知机:

from pyimagesearch.nn import Perceptron
import numpy as np

# 构造and数据集
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y = np.array([[0], [0], [0], [1]])


# 定义感知机并进行训练
print("[INFO] training perception...")

p = Perceptron.Perceptron(X.shape[1], alpha=0.1)

p.fit(X, y, epochs=20)


# 测试感知机
print("[INFO] testing perception...")


for (x, target) in zip(X, y):
    pred = p.predict(x)
    print("[INFO] data={}, ground-truth={}, pred={}".format(x, target[0], pred))

//截止到2021.12.24日晚上20:50

2021.12.25日晚上19:44学习笔记

10.1.3反向传播与多层网络

反向传播可以说是神经网络历史上最重要的算法——没有(有效的)反向传播,就不可能将深度学习网络训练到我们今天看到的深度。反向传播可以被认为是现代神经网络和深度学习的基石。

反向传播最早出现在20世纪70年代,但直到1986年Rumelhart、Hinton和Williams发表了一篇开创性的论文《通过反向传播错误学习表示》(Learning表示by backpropagation errors by Rumelhart、Hinton和Williams[16]),我们才能够设计出一种更快的算法,更擅长于训练更深层次的网络。

反向传播

算法的两个阶段:

  1. 前向传递,输入通过网络传递并获得输出预测(也称为传播阶段)。

2.向后传递,我们在网络的最后一层(即预测层)计算损失函数的梯度,并使用这个梯度递归地应用链式法则来更新我们网络中的权值(也称为权值更新阶段)。

前向传播

为了将偏差项bias加入到神经网络的参数训练中,可以:

  1. 使用一个单独的变量;
  2. 在特征向量中输入一列1,将偏差项作为权值矩阵中的一个可训练参数来处理;

在我们的特征向量中插入一列1是通过编程完成的,但为了确保我们理解这一点,让我们更新XOR设计矩阵,明确地看到这种情况的发生(见表10.2)。如你所见,我们的特征向量中加入了一列1。在实践中,你可以在任何你喜欢的地方插入这一列,但是我们通常把它放在(1)特征向量的第一个条目或(2)特征向量的最后一个条目中。

在特征向量中使用偏差项的好处:

最后,我们的输入层和所有隐藏层都需要一个偏差项;然而,最后的输出层不需要偏压。使用偏差技巧的好处是,我们不再需要明确地跟踪偏差参数——它现在是权值矩阵中的一个可训练参数,从而使训练更有效,也更容易实施。请参阅第9章,了解关于为什么这种偏见会起作用的更深入的讨论。

在图10.10的最左边,我们给出了特征向量(0,1,1)(以及向网络输出的目标值1)。在这里,我们可以看到0、1和1被分配给了网络中的三个输入节点。为了通过网络传播这些值并获得最终的分类,我们需要取输入和权重值之间的点积,然后应用一个激活函数(在这种情况下,sigmoid函数,σ)。

前向传播的一个例子。将输入向量[0,1,1]表示给网络。取输入与权值之间的点积,然后应用sigmoid激活函数得到隐含层的值(分别为0.899、0.593、0.378)。最后,计算最后一层的点积和sigmoid激活函数,得到的输出为0.506。将step函数应用到0.506得到1,这确实是正确的目标类标签。

反向传播过程

为了应用反向传播算法,我们的激活函数必须是可微的,这样我们就可以计算误差对给定权值wi, j,损失(E),节点输出o j和网络输出net j的偏导数。

使用python实现反向传播算法

import numpy as np


# layers:  A list of integers which represents the actual architecture of the feedforward
# network. For example, a value of [2,2,1] would imply that our first input layer has two nodes,
# our hidden layer has two nodes, and our final output layer has one node.

# alpha:这里我们可以指定神经网络的学习率。此值在权重更新阶段应用。


class NeuralNetwork:
    def __init__(self, layers, alpha):
        self.W = []
        self.layers = layers
        self.alpha = alpha

        # 初始化权重矩阵
        for i in np.arange(0, len(layers) - 2):
            w = np.random.randn(layers[i] + 1, layers[i + 1] + 1)
            self.W.append(w / np.sqrt(layers[i]))

            # 上述for代码解释:例如,假设layers[i] = 2, layers[i + 1] = 2。因此,我们的权值矩阵将是2x2,以连接各层之间的所有节点集。然而,在这里我们需要小心,因为我们忘记了一个重要的组成部分——偏见项。为了解释这种偏差,我们在层数[i]和层数[i + 1]上加1——这样做会改变我们的权值矩阵w,使形状3x3给定当前层的2 + 1个节点和下一层的2 + 1个节点。我们通过除以当前层中节点数量的平方根来缩放w,从而标准化每个神经元输出[57]的方差(第19行)。

            # 构造函数中需要处理的特殊情况,其中输入连接需要一个偏置项,但输出不需要
            w = np.random.randn(layers[-2] + 1, layers[-1])
            self.W.append(w / np.sqrt(layers[-2]))


    # 定义的一个对函数调试有用处的函数,主要是得到当前网络的架构层数,以及每一层的节点数
    def __repr__(self):
        # construct  and  return  a  string  that  represents  the  network

        # architecture
        return "NeuralNetwork:  {}".format("-".join(str(l) for l in self.layers))

    # 定义sigmoid激活函数
    def sigmoid(self ,x):
        return 1.0 / (1 + np.exp(-x))


    # 同时我们将在反向传播过程中用到上述sigmoid函数的导数
    def sigmoid_deriv(self, x):
        return x * (1 - x)

    # 需要注意的是无论何时需要适用反向传播函数,再次注意,无论何时执行反向传播,您都希望选择一个可微的激活函数。

    # 定义一个fit函数来训练我们的神经网络
    def fit(self, X, y, epochs=1000, displayUpdate=100):
        X = np.c_[X, np.ones((X.shape[0]))]

        for epoch in np.arange(0, epochs):
            for (x, target) in zip(X, y):
                self.fit_partial(x, target)

            if epoch == 0 or (epoch + 1) % displayUpdate == 0:
                loss = self.calculate_loss(X, y)
                print("[INFO] epoch={}, loss={:.7f}".format(epoch + 1, loss))


    # 反向传播函数的核心在下面的函数中
    def fit_partial(self, x, y):
        # 初始化一个保存每层输出的网络
        A = [np.atleast_2d(x)]


        # 开始向前传播
        for layer in np.arange(0, len(self.W)):
            # 得到每层网络的权值和网络层的数据点之间的乘积
            net = A[layer].dot(self.W[layer])

            # 通过激活函数得到每层网络的输出
            out = self.sigmoid(net)

            # 将输出追加到A中
            A.append(out)

            # We start looping over every layer in the network on Line 71. The net input to the current layer
            # is computed by taking the dot product between the activation and the weight matrix (Line 76). The
            # net output of the current layer is then computed by passing the net input through the nonlinear
            # sigmoid activation function. Once we have the net output, we add it to our list of activations (Line
            # 84).

        # 进行反向传播
        error = A[-1] - y

        # 从这里开始,我们需要应用链式法则并构建我们的94 # delta ' D '列表;delta中的第一个条目是95 #,即输出层的误差乘以激活函数对输出值的导数96 #

        D = [error * self.sigmoid_deriv(A[-1])]

        # 向后传递的第一阶段是计算我们的错误,或者简单地计算我们的预测标签和地面真相标签之间的差值(第91行)。由于激活列表A中的最后一个条目包含了网络的输出,我们可以通过A[-1]访问输出预测。值y是输入数据点x的目标输出。

        #接下来,我们需要开始应用链式法则来构建delta的列表d。delta将被用来更新我们的权值矩阵,按学习速率缩放。delta列表中的第一个条目是输出层的误差乘以输出值的sigmoid的导数(第97行)。

        for layer in np.arange(len(A) - 2, 0, -1):
            delta = D[-1].dot(self.W[layer].T)
            delta = delta * self.sigmoid_deriv(A[layer])
            D.append(delta)

            # 在第103行,我们开始对网络中的每一层进行循环(忽略前面的两层,因为它们已经在第97行中被计入了),因为我们需要反向计算每一层的增量更新。当前层的delta等于前一层的delta,用当前层的权值矩阵点乘D[-1](第109行)。为了完成delta的计算,我们通过将层的激活通过我们的s形函数的导数(第110行)来乘以它。然后,我们用刚刚计算的增量更新增量D列表(第111行)。

            # 看看这段代码,我们可以看到反向传播步骤是迭代的——我们只是从上一层取delta,用当前层的权值点乘它,然后乘以激活的导数。这个过程不断重复,直到我们到达网络的第一层。

        # 翻转D矩阵
        D = D[::-1]

        for layer in np.arange(0, len(self.W)):
            self.W[layer] += -self.alpha * A[layer].T.dot(D[layer])


    # 定义预测函数
    def predict(self, x, addBias=True):
        p = np.atleast_2d(x)
        if addBias:
            p = np.c_[p, np.ones((p.shape[0]))]

        for layer in np.arange(0, len(self.W)):
            p = self.sigmoid(np.dot(p, self.W[layer]))


        return p

    # 定义的一个计算训练集中损失的函数
    def calculate_loss(self, X, targets):
        targets = np.atleast_2d(targets)
        predictions = self.predict(X, addBias=False)
        loss = 0.5 * np.sum((predictions - targets) ** 2)

        return loss

 

这里需要注意的是:XOR感知机一定需要bias层进行训练

反向传播的总结

  1. 前向传递,我们通过网络传递我们的输入来获得我们的输出分类。
  2. 向后传递(即权值更新阶段),在此我们计算损失函数的梯度,并使用此信息迭代地应用链式法则来更新我们网络中的权值。

无论我们使用的是简单的前馈神经网络还是复杂的深度卷积神经网络,都仍然使用反向传播算法来训练这些模型。这是通过确保网络内的激活函数是可微的来实现的,允许应用链式法则。此外,网络中任何其他需要更新权值/参数的层也必须与反向传播兼容。

在实践中,反向传播不仅可以挑战来实现(由于计算梯度)的bug,但也很难高效没有特别优化库,这就是为什么我们经常使用库,如Keras TensorFlow, mxnet已经反向传播(正确地)实现使用优化的策略。

//截止到2021.12.25日晚上21:41止

截止到P155页

2021.12.27日上午10:40开始看

10.1.4 使用keras的多层神经网络

在接下来的两个部分中,我将讨论如何实现前馈、多层网络,并将它们应用于MNIST和CIFAR-10数据集。这些结果很难达到“最先进”的水平,但有两个目的:

  1. 演示如何使用Keras库实现简单的神经网络。
  2. 使用标准神经网络获取基线,稍后我们将与卷积神经网络进行比较(注意到CNNS将显著优于我们之前的方法)。

MINIST数据集

因此,我们使用了数据集的一个样本。在本节中,我们将使用完整的MNIST数据集,它包含70000个数据点(每位数字包含7000个示例)。每个数据点用一个784维向量表示,对应MNIST数据集中的(平坦的)28 × 28幅图像。我们的目标是训练一个神经网络(使用Keras)在这个数据集上获得> 90%的准确性。

我们将会发现,使用Keras构建我们的网络架构比我们的纯Python版本要简单得多。实际上,实际的网络架构只需要4行代码——本例中剩下的代码只需要从磁盘加载数据,转换类标签,然后显示结果。

解释:

使用one - hot编码对数据集的标签进行编码之后,3标签变为了:

注意,只有数字3的索引被设置为1——向量中的所有其他条目都被设置为0。精明的读者可能会想,为什么要更新向量中的第四个条目而不是第三个条目?回想一下,标签中的第一个条目实际上是数字0。因此,数字3的条目实际上是列表中的第四个索引。

从0-9所有数组的编码如下:

这种编码可能看起来单调乏味,但许多机器学习算法(包括神经网络)都受益于这种标签表示。幸运的是,大多数机器学习软件包都提供了一种方法/功能来执行一次热编码,从而消除了很多繁琐的工作。

然后可以提供validation_data,这是我们的测试分割。在大多数情况下,例如当您调优超参数或决定一个模型架构时,您将希望您的验证集是一个真正的验证集,而不是您的测试数据。在本例中,我们只是演示了如何使用Keras从头开始训练一个神经网络,因此我们对我们的指导方针有点宽容。本书未来的章节,以及实践者包和ImageNet包中更高级的内容,在科学方法上要严格得多;但是,现在只需要关注代码并掌握如何训练网络。

事实上,如果您不熟悉MNIST数据集,您可能会认为92%的准确性是非常优秀的——这可能是20年前的事了。我们将在第14章中发现,使用卷积神经网络,我们可以轻松获得> 98%的准确率。目前最先进的方法甚至可以打破99%的准确率。

虽然从表面上看,我们(严格意义上)完全连接的网络表现良好,但实际上我们可以做得更好。正如我们将在下一节中看到的,严格的全连接网络应用于更有挑战性的数据集,在某些情况下比随机猜测好不到哪去。

CIFAR-10
对于更具挑战性的基准测试数据集,我们通常使用CIFAR-10,这是一个由60,000张32 × 32 RGB图像组成的集合,因此意味着数据集中的每张图像都由32 × 32 × 3 = 3,072个整数表示。顾名思义,CIFAR-10由飞机、汽车、鸟、猫、鹿、狗、青蛙、马、船、卡车等10个类组成。图10.15中可以看到每个类的CIFAR-10数据集的示例。

每个类用6000张图片平均表示。在CIFAR-10上训练和评估机器学习模型时,通常会使用作者预先定义的数据分割,使用50,000张图像进行训练,10,000张图像进行测试。

CIFAR-10比MNIST数据集要困难得多。挑战来自于物体外观的戏剧性变化。例如,我们不能再假设在给定(x,y)坐标上包含绿色像素的图像是青蛙。这个像素可以是包含一只鹿的森林的背景。也可能是绿色汽车或卡车的颜色。

这些假设与MNIST数据集形成鲜明对比,在MNIST数据集中,网络可以学习关于像素强度空间分布的假设。例如,1的前景像素的空间分布与0或5有本质上的不同。对象外观的这种类型的差异使得应用一系列完全连接的层更具挑战性。我们将在本节的其余部分发现,标准FC(全连接)层网络不适合这种类型的图像分类。

对网络的解释:

然后,我们添加第一个稠密层,其input_shape为3072,这是设计矩阵中3072个扁平像素值的每个节点——这一层负责学习1024个权值。我们还将用ReLU激活来替换过时的sigmoid,以期提高网络性能。

下一个全连接层(第39行)学习512个权值,而最后一层(第40行)学习对应于10种可能的输出分类的权值,以及一个softmax分类器,以获得每个类的最终输出概率。

使用标准的前馈神经网络会导致更有挑战性的cifar10数据集出现显著的过拟合(注意训练损失是如何下降的,而验证损失是如何急剧上升的)。为了在CIFAR-10挑战赛中取得成功,我们需要一种强大的技术——卷积神经网络。

事实是,具有严格全连接层的基本前馈网络不适合具有挑战性的图像数据集。为此,我们需要一种更先进的方法:卷积神经网络。幸运的是,cnn是本书剩余部分的主题。当您完成Starter Bundle时,您将能够在CIFAR-10上获得超过79%的准确度。如果您选择更深入地研究深度学习,实践者Bundle将演示如何将您的准确性提高到93%以上,使我们处于最先进的结果联盟[110]。

10.1.5 神经网络配方中的四种成分

在训练神经网络时,你可能已经开始注意到我们Python代码示例中的一个模式。要将自己的神经网络和深度学习算法组合在一起,你需要四个主要元素:数据集、模型/架构、损失函数和优化方法。我们将在下面回顾这些成分。

数据集

数据本身定义了需要解决的问题。

损失函数

截止到P166页

//2021.12.27日下午13:17截止

//2022.1.15日上午10:29开始学习笔记

Loss Function

给定我们的数据集和目标目标,我们需要定义一个与我们试图解决的问题相一致的损失函数。在几乎所有使用深度学习的图像分类问题中,我们将使用交叉熵损失。对于> 2类,我们称之为分类交叉熵。对于两类问题,我们称损失二元交叉熵。

Model architecture(网络架构)

网络架构的选择主要看以下几点:

  1. 有多少数据点;
  2. 类的数量;
  3. 类别的相似程度;
  4. 类内方差;

请记住,随着您进行越来越多的实验,网络体系结构中的层和节点的数量(以及任何类型的正则化)可能会发生变化。您收集的结果越多,您就越有能力对下一步尝试何种技术做出明智的决定。

优化方法

最后一个要素是定义一个优化方法。正如我们在本书中所看到的,随机梯度下降法(第9.2节)被经常使用。其他的优化方法包括RMSprop[90]、Adagrad[111]、Adadelta[112]和Adam [113];然而,这些是我们将在实践者包中介绍的更高级的优化方法。

尽管有这些更新的优化方法,SGD仍然是深度学习的主力——大多数神经网络都是通过SGD训练的,包括在具有挑战性的图像数据集(如ImageNet)上获得最先进的精度的网络。

在训练深度学习网络时,特别是当你刚开始学习的时候,SGD应该是你的优化器的选择。然后,您需要设置一个适当的学习速率和正则化强度,网络应该训练的epoch的总数,以及是否应该使用动量(如果是,则是哪个值)或Nesterov加速度。花点时间尽可能多地体验SGD,熟悉参数的调整。

请记住,即使是在小型/中型数据集上获得一个性能合理的神经网络,也可能需要10到100次的实验,即使对于高级深度学习用户来说也是如此——当你的网络一开始就表现得不是很好时,不要气馁。要精通深度学习需要你投入时间和大量实验——但一旦你掌握了这些要素是如何组合在一起的,这就值得了。

权重初始化

本节并不打算介绍全面的初始化技术;然而,它突出了流行的方法,但从神经网络文献和一般的经验法则。为了说明这些权重初始化方法是如何工作的,我在适当的时候包含了基本的Python/ numpy类伪代码。

常量初始化

当应用常数归一化时,神经网络中的所有权值都用一个常数C初始化。通常C等于0或1。

为了在伪代码中可视化这一点,让我们考虑一个神经网络的任意层,它有64个输入和32个输出(为了便于理解,排除任何偏见)。为了通过NumPy和0初始化(Caffe,一个流行的深度学习框架使用的默认值)来初始化这些权重,我们将执行:

尽管常量初始化很容易掌握和理解,但使用这种方法的问题是,我们几乎不可能打破激活的对称性[114]。因此,它很少被用作神经网络的权值初始化器。

均匀分布和正态分布

均匀分布从范围[上下]绘制一个随机值,在这个范围内的每个值都有相等的被绘制的概率。

再一次,让我们假设对于神经网络的给定层,我们有64个输入和32个输出。然后,我们希望将我们的权值初始化在下限=-0.05和上限=0.05的范围内。应用下面的Python + NumPy代码将允许我们实现预期的规范化:

然后我们得到一个正态分布,我们将高斯分布的概率密度定义为:

这里最重要的参数是均值(µ)和标准差(σ)。标准差的平方,σ 2,称为方差。

当使用Keras库时,RandomNormal类从µ= 0和σ = 0.05的正态分布中提取随机值。我们可以用下面的NumPy来模拟这种行为:

均匀分布和正态分布都可以用来初始化神经网络的权值;但是,我们通常采用各种启发式方法来创建“更好的”初始化方案(我们将在其余部分中讨论)。

LeCun 均匀正态

如果你曾经使用过Torch7或PyTorch框架,你可能会注意到默认的权重初始化方法叫做“Efficient Backprop”,它是由LeCun等人的工作派生出来的。

在这里,作者定义了一个参数Fin(称为“扇入”,或向层输入的数量)和Fout(“扇出”,或从层输出的数量)。使用这些值,我们可以通过以下方式进行统一初始化:

我们也可以用正态分布。Keras库在构造下限和上限时使用了截断的正态分布,并且均值为零:

Glorot/Xavier 均匀正态

Keras库中使用的默认权值初始化方法称为“Glorot初始化”或“Xavier初始化”,以论文《理解深度前馈神经网络训练的难度》的第一作者Xavier Glorot命名[115]。

对于正态分布,求Fin和Fout的平均值,然后取平方根,得到极限值[116]。然后使用零中心(µ= 0):

Glorot/Xavier初始化也可以通过一个统一的分布,我们在限制上设置更强的限制:

使用这种初始化方法学习往往是非常有效的,我推荐它用于大多数神经网络。

He et al./Kaiming/MSRA 均匀正态

该技术通常被称为“He等人初始化”、“kaim初始化”或简称为“MSRA初始化”,该技术以论文第一作者kaim He命名,《Deep into: exceed Human-Level Performance on ImageNet Classification》[117]。

当我们使用类似relu的激活函数(特别是“PReLU”,或参数整正线性单元)训练非常深度的神经网络时,我们通常使用这种方法。

为了使用He等人的均匀分布初始化方法初始化一层中的权值,我们将limit设为limit = p6/Fin,其中Fin为该层中输入单元的个数:

我们也可以通过设置µ= 0和sigma = p2/Fin来使用正态分布

我们将在本书的实践者Bundle和ImageNet Bundle中讨论这种初始化方法,在这些Bundle中,我们在大型图像数据集上训练非常深入的神经网络。

初始化实现的差异

LeCun均匀/正态分布、Xavier均匀/正态分布和He等的实际极限值可能会有所不同。统一的/正常。例如,当在Caffe中使用Xavier Uniform时,limit = -np。√(3 / F_in) [114];然而,Keras的默认Xaiver初始化使用np。√(6 / (F_in + F_out))[118]。没有哪种方法比其他方法“更正确”,但是您应该阅读各自的深度学习库的文档。

10.2 总结

从那以后,我们转向了人工神经网络,比如感知器算法。从历史的角度来看,感知器算法很重要,但它有一个主要的缺陷——它能准确地将非线性可分离点分类。为了处理更具挑战性的数据集,我们需要(1)非线性激活函数和(2)多层网络。

为了训练多层网络,我们必须使用反向传播算法。然后,我们手工实现了反向传播,并证明当用于训练具有非线性激活函数的多层网络时,我们可以建模非线性可分数据集,如异或。

当然,手工实现反向传播是一个艰巨的过程,容易出现bug——因此,我们经常依赖于现有的库,如Keras、Theano、TensorFlow等。这使我们能够专注于实际的体系结构,而不是用于训练网络的底层算法。

不幸的是,正如我们的一些结果所表明的那样(例如,CIFAR-10),当处理具有挑战性的图像数据集时,标准神经网络无法获得高分类精度,这些数据集在平移、旋转、视点等方面表现出变化。为了在这些数据集上获得合理的准确性,我们需要使用一种特殊类型的前馈神经网络,称为卷积神经网络(Convolutional neural networks, CNNs),这正是我们下一章的主题。

上述实验代码详见本人github地址:

TheWangYang/Code_For_Deep_Learning_for_Computer_Vision_with_Python: A code repository for Deep Learning for Computer Vision with Python. (github.com)

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wyypersist

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值