概论
在第一部分中,我们深入探讨了人工智能的兴衰简史以及推动人工智能发展的努力。我们研究了一个简单的感知器,以了解其组件以及简单的 ANN 如何处理数据和权重层。在简单的 ANN 中,不会对数据执行特定操作。ANN 中的激活函数是一个线性函数,它将输入数据带到输出,因此它与传统模型中的回归任务没有太大区别。
但是,如果我们想要处理更大的数据集并实现诸如人脸识别或自然语言处理之类的结果,那么仅使用线性激活函数是不切实际的,因为它不执行特定的操作。在所有非线性激活函数中,哪一个表现最佳?激活函数存在哪些问题,我们如何才能最小化这些问题?信息在神经元中是如何处理?顺序处理还是并行处理更好?我们可以将它们结合起来吗?神经元必须能够学习。但是我们有哪些类型的学习?数据可能是带标签的,也可能是未带标签的,就像我们在传统的机器学习模型中一样。当神经网络中的层数增加并且我们深入研究时,这是如何做到的?反向传播如何工作?它可能存在哪些问题,我们如何才能最小化这些问题?链式法则!?让我们一起打开大门,深入研究它……
人工神经网络如何学习?
了解所有生物体的学习过程一直是人类感兴趣的课题。甚至人类学习自己的方式也是如此。人类大脑可以并行处理信息并同时执行多项活动,这意味着它可以同时分析不同的信息。然而,一些心理活动需要顺序处理来解决更复杂的问题,这些问题需要更详细的分析和理解。当然,这个过程究竟如何在大脑中进行,仍然是科学家和神经科学家的研究课题。
在人工神经网络中,我们可以根据问题的类型来确定架构和学习的类型。例如,在自然语言处理 (NLP) 中,数据具有特定的顺序,循环神经网络 (RNN) 被广泛使用,其中的信息处理是按顺序进行的。每个输入数据都按特定顺序输入网络。当它们到达网络的每一层时,对每个输入执行各种处理(我们将在本文中进一步讨论),然后生成与每个单词或字符相关的输出。
另一种处理类型是并行处理,它通常用于深度神经网络 (DNN) 以提高速度和效率,因为它可以显著减少任务执行时间。例如,在通常用于图像处理的卷积神经网络 (CNN) 中,会使用诸如三维图像或大型矩阵之类的复杂操作。这些网络广泛使用并行处理进行矩阵操作,例如组合、分离和成像。这就是GPU(图形处理单元)发挥作用的地方,因为 GPU 中的处理是并行完成的。这意味着多个计算操作可以同时并行执行。这允许同时执行数百万个计算任务。
然而,在很多情况下,可以使用多种处理方法的组合。事实上,使用顺序处理和并行处理的组合或在系统中使用不同的处理方法可以帮助提高性能和效率。这种方法被称为混合处理。下面,我们将深入探讨传统方法和人工神经网络中使用的机器学习方法类型。
神经网络中的各种学习类型
在人工神经网络的所有类型的学习中,目标都是让网络识别输入数据中的模式并为新数据提供准确的预测。这些方法中的每一种都可以用于特定的任务。这些方法之一是监督学习,用于模式识别、分类、分类和预测等任务。在这种类型的学习中,网络的输入数据在训练阶段被标记。例如,在 ImageNet 数据集中,您已标记图像以进行图像处理,这使网络能够在训练阶段使用其拥有的标签检测模式并在测试阶段获得最佳结果。
下一种方法是无监督学习,通常用于发现隐藏模式、探索数据、聚类和分类数据。输入数据在没有标签的情况下进行训练。最好的例子是自动编码器,旨在从未标记的数据中学习。主要目标是让网络重现数据的基本特征。自动编码器是神经网络中无监督学习的著名例子之一,目前被广泛使用。
另一种方法是半监督学习,它可以被认为是监督学习和无监督学习的结合,因为它具有有限数量的标记数据以及丰富的未标记数据。这种类型的学习通常用于标记数据访问有限但未标记数据丰富的情况。半监督学习的目标是同时使用标记数据和未标记数据来改进学习并提高模型准确性。例如,在医学成像中,标记数据(例如,显示特定疾病的图像)可能有限。通过利用未标记数据,模型可以学习图像中的重要结构。
强化学习用于计算机游戏、机器人技术和复杂的决策。在每个时间步骤中,代理都处于特定状态,并根据其状态(奖励或惩罚)选择特定操作。强化学习的主要目标是尽量减少惩罚并增加奖励。代理会尝试通过学习其在不同状态下采取的操作与其从环境中获得的奖励或惩罚之间的关系来选择最佳行为。
强化学习有多种算法,包括 Q 学习、深度 Q 网络 (DQN)、策略梯度和 Actor-Critic。这些算法都基于类似的原理,但通过使用不同的技术和变体,它们可以解决不同的问题。强化学习的缺点之一是需要大量反复试验才能学习,算法复杂,并且对参数调整敏感。
接下来,我们将深入研究激活的工作原理以及网络的运作方式。请继续关注这个无限美丽的探索。
1.激活函数
在 ANN 中,激活函数用于在层之间传输信息(从输入到输出,或者在循环模型中从输出到输入)。这些函数用于网络的不同层以及从处理到加权、损失减少和最终结果的各个阶段。本质上,这些函数导致神经元的整体行为变得非线性。
训练深度网络的主要挑战之一是梯度消失问题,即当信息从一层传播到下一更高层时,梯度趋于接近于零。随着训练期间学习到的权重深入网络,梯度通常趋于消失,导致网络内的信息传输不足。这可能导致更深层中梯度的减少或消失。因此,使用适当的激活函数可以帮助缓解梯度消失问题并提高深度网络的性能。
当然,激活函数的选择取决于问题的类型和期望的结果。我们将讨论各种类型的线性函数作为不同激活函数的示例。
线性激活函数
我使用 Python 代码创建了线性激活函数。
线性激活函数,也称为“恒等”或“无激活”函数,不执行任何输入转换。这些函数只是将输入值不加修改地传递到输出。它们通常用于特定情况和特殊问题。在某些情况下,例如回归任务,它们可用于输出层来计算预测值。
线性激活函数的公式为:
F(x) = x
线性激活函数有两个主要缺点。首先,它们不能用于反向传播;其次,它们会将神经网络的所有层折叠成一层。换句话说,无论神经网络有多少层,最后一层仍然是第一层的线性函数。这本质上将神经网络变成了单层网络。
在大多数模型中,非线性激活函数受到优先考虑,因为它们更适合解决复杂问题。
非线性激活函数
人工神经网络 (ANN) 中激活函数的非线性使得它们能够对复杂的关系进行建模。事实上,许多应用,尤其是当今世界,如人脸识别、手写识别等许多问题,都具有决策边界和非线性模式。当然,线性函数也可以用于神经网络,尤其是在输出层。然而,对于复杂网络,我们有大量的非线性激活函数,每个激活函数都应用于神经网络的特定部分。最常用的包括S 型函数 (logistic)、双曲正切函数 (tanh)、更流行且包含各种组件的整流线性单元 (ReLU)以及 Softmax。让我们深入研究神经网络最前沿的激活函数世界吧!
1.1. Sigmoid 激活函数
我使用 Python 代码创建了 sigmoid 函数。
Sigmoid 函数是一种非线性函数,形状像“S”,通常用于神经网络中各层的输出。默认情况下,此函数输出(0,1)之间的值,这意味着它始终有界。换句话说,它的输出不能小于 0 或大于 1,并且它具有正数性质。因此,它适用于分类问题和需要概率的任务。对于某些特定应用,可以使用简单的变换来更改输出范围。
其数学定义由以下公式给出:
σ(x) = 1 / ( 1 + exp(-x))
这里,exp表示欧拉数(自然对数的底数),x是函数的输入。
Sigmoid 函数的弱点之一是梯度消失问题。当 Sigmoid 函数的输入变得非常大时,输出趋向于 1,而当输入非常小时,输出趋向于 0。这导致 Sigmoid 函数的导数在这些点趋近于零,这意味着梯度变得非常小。结果,深度网络的训练过程可能会变得缓慢甚至停止。
1.2. 双曲正切函数(tanh)
我使用 Python 代码创建了双曲正切函数。
双曲正切函数 (tanh) 是一种非线性函数,类似于 S 型函数,但范围在(-1,1)之间。这意味着它的中间值以0为中心,也包括负值。
其数学公式为:
tanh(x) = (e^x - e^(-x)) / (e^x + e^(-x))
这里,e表示欧拉数,x是函数的输入。
tanh 和 sigmoid 激活函数都对输出值有限制,这有助于控制梯度爆炸问题。当神经网络的输出值趋向于非常大或非常小的值时,梯度可能会很快变大或变小,从而导致训练过程中出现问题。
在 tanh 函数中,输出范围在 (-1,1) 之间,这导致“数据居中”。这意味着它们的平均值被调整到零左右。这可以潜在地提高神经网络的性能,因为权重不会趋向于零的任何一个极端。
在此图中,您可以更好地理解 Sigmoid 和 Tanh 之间的区别。
我使用 Python 代码创建了 Sigmoid 与 Tanh
是我使用 Python 代码创建的。
由于sigmoid函数的输出范围有限(0,1),神经网络可能会遇到梯度不稳定的问题,尤其是在深度网络中。通常,tanh因其控制梯度和数据归一化的特性而受到青睐。
1.3. 整流线性单元(ReLU)
我使用 Python 代码创建了整流线性单元 (ReLU)。
整流线性单元 (ReLU) 是人工神经网络中最常见、最流行的非线性激活函数之一。其最主要、最重要的特点是简单。
ReLU的计算公式如下:
f(x)=最大值(x,0)
该公式表示,如果输入值为正,ReLU 将输出输入值。否则,输出值为零,并且任何具有 ReLU 输出为零的神经元都被视为不活跃或“死亡”。换句话说,对于小于零的输入,ReLU 输出一个常数零,这被称为“稀疏性”。
该特征通过减少相互适应(当特征检测器不是学习不同的特征,而是变得越来越相互依赖时发生)得到改进,特征检测器(是人工神经网络的一部分,负责识别和从数据中提取特定模式。)这种改进有助于减少过度拟合。
与 sigmoid 或 tanh 等更复杂的激活函数相比,ReLU 具有较高的计算效率,尤其是对于梯度迅速趋近于零的大值而言。ReLU 通过反向传播提供最佳性能,因为它的梯度始终为 0 或 1,这使得训练期间的优化更容易,并导致梯度下降。
然而,需要注意的是,ReLU 面临“ ReLU 死亡”的问题,尤其是在网络训练的初始阶段。“ReLU 死亡”问题是指神经网络中的神经元使用 ReLU 激活函数进行调整,使其梯度始终为零,导致训练期间没有权重更新的情况。这可能导致某些神经元的训练不足或无效,从而显著降低网络的性能,甚至导致网络的某些部分完全不活跃。
1.4. 泄漏的 ReLU
在此图中,我们可以清楚地看到 Relu 和 Leaky ReLU 之间的区别。由我使用 Python 代码创建
专为解决“Dying ReLU”问题而设计的 ReLU 激活函数的修改版本称为 Leaky ReLU。在 Leaky ReLU 中,对于负值,使用非常小的斜率(例如 0.01)而不是零。因此,如果值非零,则不会发生 Dying ReLU。这使得神经网络具有更好的训练能力和性能,尤其是在可能遇到具有负面模式的数据时。
其数学公式为:
f (x) = { x,如果x ≥ 0 ax,如果x < 0
其中a通常是一个较小的正数,一般介于0和0.01或 0.1之间
当深度神经网络遇到不稳定梯度时,使用 Leaky ReLU 会很有帮助,因为它对负输入的梯度很小。由于计算负输入的斜率,Leaky ReLU 可能需要比 ReLU 更多的计算。例如,为了提高卷积神经网络 (CNN) 在黑暗或低光图像中的物体检测性能(这些区域可能会丢失重要信息),或者在可能面临稀疏梯度的生成对抗网络 (GAN) 中,使用 Leaky ReLU 可能非常有用。
1.5.参数化 ReLU (PReLU)
在此图中,我们可以清楚地看到 Leaky ReLU 和参数化 ReLU (PReLU) 之间的区别。此图是我使用 Python 代码生成的。
ReLU 不会对带有参数的负输入输出零;相反,它会为它们分配一个可学习的斜率。由于需要计算负输入的斜率,它需要比标准 ReLU 和 Leaky ReLU 更多的计算,这增加了它的复杂性。对于灵活性和高精度至关重要的任务(例如复杂的深度神经网络),它是有益的。
它的公式与Leaky ReLU类似,不同之处在于a的值由神经网络在训练过程中确定。
1.6.指数线性单元(ELU)
我使用 Python 代码创建了 ELU 函数。
指数线性单元 (ELU) 是一种非线性激活函数,对于负值,它比 ReLU 更柔和。ELU 不需要参数来调整斜率;相反,它只有一个参数来调节函数的平滑度。这使得 ELU 在原点附近呈线性,并且平均而言快速接近负值,这有助于减少偏差偏移效应。这意味着模型对输入数据和训练偏差的响应更准确,从而获得更好的性能。在其负部分,ELU 使用指数函数。
其计算公式如下:
f(x) = { x ,如果x ≥ 0 a(e^(x^) - 1 ) ,如果x < 0
这里,a是一个正参数,充当 ELU 的斜率。此函数对于正输入的行为类似于 ReLU,但对于负输入则使用非线性函数。
ELU 相对于其他函数的优势之一是可以防止Dying ReLU问题。ELU 是一种强大而灵活的激活函数,可用于广泛的深度学习任务。
为了缓解“消亡 ReLU”的问题,可以使用诸如Leaky ReLU、参数 ReLU (PReLU)和指数线性单元 (ELU)等变体,通过引入有限的负斜率来缓解消亡 ReLU 的问题。
1.7. 缩放指数线性单元 (SELU)
我使用 Python 代码创建了 SELU(缩放指数线性单位)函数。
SELU(Scaled Exponential Linear Unit,缩放指数线性单元)是另一个从 ELU 派生出来的激活函数。SELU 的一个显著优点是它的输出是由方差为常数的分布生成的。这一特性使得使用 SELU 的小型神经网络能够更好地应对深度网络中常见的梯度不稳定等问题。
其数学公式如下:
如果 x ≥ 0,则 f(x) = λ * x
如果 x < 0,则 f(x) = λ * α * (exp(x) — 1)
λ ≈ 1.0507
α ≈ 1.6733
λ 和 α是为了优化 SELU 的性能而选择的常数值。
SELU比 ReLU 或 Leaky ReLU 等简单激活函数更复杂。它涉及指数运算,可能需要更多计算,计算成本高昂。SELU 的参数值(例如指数系数)非常敏感,需要仔细调整。
如图所示,SELU 在接近零时几乎呈线性。此属性有助于使用 SELU 训练的神经网络减少遇到非线性激活函数中常见的梯度消失问题等问题。
1.8. Softmax
我使用 Python 代码创建了 softmax 函数
softmax 函数,也称为 softargmax 或正则化指数函数,将神经网络的原始输出转换为概率向量。Softmax间接执行正则化。softmax 的主要目标是将输出值转换为概率分布,但这也隐式地对值进行了正则化。
在应用 softmax 之前,向量的某些分量可能为负或大于 1,并且它们的总和可能不等于 1。但是,在应用 softmax 之后,每个分量都落在 (0, 1) 范围内,并且分量的总和为 1,因此它们可以解释为概率。此外,输入分量越大,概率也就越大。
标准softmax函数的公式为:
σ(z)_j = e^(z_j) / (∑_(k= 1 )^K e^(z_k))
标准 softmax 函数通常用于神经网络分类器的最后一层。实际上,您可以将 softmax 视为 sigmoid 激活函数的矢量化扩展。softmax 和 sigmoid 函数都是可微分的,这对于优化算法和训练神经网络至关重要。例如,在反向传播算法中,激活函数的导数用于计算梯度以更新网络的权重和其他参数。因此,这些函数的可微分性至关重要。
S 型函数通常用于二分类问题,而 Softmax 函数用于多分类问题。但是,如果类别数为 2,则 Softmax函数会简化为 S 型函数。
激活函数实现
在 PyTorch 或 TensorFlow 中实现激活函数时,由于常用的激活函数是预定义的,因此通常只需指定其名称即可。这是一般规则,实现细节可能因每个激活函数而异。
在PyTorch中,激活函数使用import torch.nn as nn实现,如下面的伪代码所示:
import torch.nn as nn # 定义一个带有 ReLU 激活的层 relu_layer = nn.ReLU() ###################################### # 定义一个带有 Sigmoid 激活的层 sigmoid_layer = nn.Sigmoid() ###################################### # 定义一个带有 Tanh 激活的层 tanh_layer = nn.Tanh()
在TensorFlow中,激活函数使用“tf.keras.layers.Activation”实现,如下面的伪代码所示:
import tensorflow as tf # 使用 ReLU 激活函数的示例 activation = tf.keras.layers.Activation( 'relu' ) # 使用 Sigmoid 激活函数的示例 activation = tf.keras.layers.Activation( 'sigmoid' ) # 使用 Tanh 激活函数的示例 activation = tf.keras.layers.Activation( 'tanh' ) # 使用 Leaky ReLU 激活函数的示例 activation = tf.keras.layers.LeakyReLU(alpha= 0.01 ) # 根据需要调整 alpha 值
2.反向传播
反向传播是“误差反向传播”的简称,由 Frank Rosenblatt 于 1962 年提出。如上文历史所述,Paul Werbos 于 1974 年对其进行了进一步开发,但多年来一直未引起人们的注意。反向传播于 1986 年首次被正式描述。
反向传播是深度学习中的一种基本算法,它使用网络中使用的函数的偏导数(使用链式法则计算)来计算损失函数相对于网络权重的梯度。它是一种更新权重(W)的方法,以使损失函数最小化。通常,这是使用优化算法(例如随机梯度下降(SGD)或类似变体)来完成的。
“反向传播”是指在更新权重的同时将误差从输出层反向传播到输入层。但反向传播到底是什么?为什么这种方法在神经网络学习中至关重要?
反向传播算法由两个阶段组成:前向传播和后向传播。这里我先描述前向传播阶段。
2.1. 前向传播
反向传播算法中的前向传递阶段是数据从输入层传播到输出层并计算输出的阶段。在前向传递阶段,非线性激活函数应用于神经网络的不同层。例如,通常使用 ReLU(整流线性单元)。
此属性使 ReLU 停用活动较弱的神经元(即负输出),从而将神经网络的注意力更多地引向数据的重要和突出特征。在前向传递阶段,输入值通过每层所需的激活函数,然后转发到下一层。例如,在 PyTorch 中使用诸如“torch.relu”(用于 ReLU)之类的模块执行此操作。使用以下伪代码,我们可以执行其中将输入值 x 传递给 torch.relu 函数并打印计算的输出。
导入torch x = torch.tensor([- 1.0 , 0.0 , 1.0 , 2.0 ]) 输出 = torch.relu(x) 打印(输出)
或者,使用 torch.sigmoid 模块(用于 Sigmoid),可以使用以下代码轻松实现。此代码将输入值 x 传递给 torch.sigmoid 函数并打印计算出的输出。
导入torch x = torch.tensor([- 1.0 , 0.0 , 1.0 , 2.0 ]) 输出 = torch.sigmoid(x) 打印(输出)
2.2. 损失计算
可以说,损失计算是在前向传递过程中进行的。数据已经通过网络,需要为后向传递做好准备。因此,需要使用损失计算来计算误差值。我们通常使用损失函数来计算这些误差,它衡量模型预测值与实际值之间的差异。我们的目标是最小化这些误差,以便获得更好的数据预测模型。
在 PyTorch 中,可以使用损失函数之一轻松计算损失,例如用于分类任务的交叉熵损失。这些损失函数定义为神经网络中的一层,计算损失值,然后作为输入传递到后向传递阶段以更新权重和偏差。值得注意的是,在 PyTorch 中,损失函数是自动计算的,您无需执行手动计算。
2.3.后向传递(反向传播)
执行前向传递后,我们进入一个称为反向传递的新阶段。在反向传递(反向传播)阶段,误差从输出层传播到输入层。此误差传播使用链式法则从网络末端向输入进行。链式法则允许我们使用复合函数的组成函数的导数来计算复合函数的导数。此算法在训练期间用于计算相对于网络权重的梯度(偏导数),然后使用梯度下降等优化方法更新梯度。
总之,反向传播有几个阶段,它使用链式法则将错误从输出层传播到输入层,并计算梯度来更新权重。
您可以使用 PyTorch 库来实现反向传播算法。在 PyTorch 中,此过程是自动完成的,您无需手动实现。使用 PyTorch 的自动微分函数,您可以简单地指示神经网络如何计算损失。这段代码将成本函数引入神经网络。
# 定义损失函数 criterion = nn.MSELoss()
然后 PyTorch 会自动计算必要的导数并更新权重。例如,您可以使用类 torch.nn.Module 和 torch.optim 来定义神经网络并选择优化方法。然后,通过定义损失函数并使用 PyTorch 的自动微分函数,您可以训练网络。
在torch.nn.Module类中:
import torch.nn as nn # 定义一个简单的神经网络模型 class SimpleModel (nn.Module): def __init__ ( self ): super (SimpleModel, self).__init__() self.fc1 = nn.Linear( 64 , 32 ) # 定义一个输入大小为 64、输出大小为 32 的线性层 self.fc2 = nn.Linear( 32 , 10 ) # 定义另一个输入大小为 32、输出大小为 10 的线性层 def forward ( self, x ): x = nn. functional.relu(self.fc1(x)) # 将 ReLU 激活函数应用于第一层的输出 x = self.fc2(x) # 获取第二层的输出 return x # 创建 SimpleModel 的一个实例 model = SimpleModel()
在torch.optim类中:
import torch.optim as optim # 定义模型 model = SimpleModel() # 定义优化器 optimizer = optim.SGD(model.parameters(), lr= 0.01 , influence= 0.9 ) # 定义损失函数 criterion = nn.CrossEntropyLoss() # 训练循环内部: # optimizer.zero_grad() # 清除梯度 # output = model(inputs) # 前向传播 # loss = criterion(outputs, labels) # 计算损失 # loss.backward() # 后向传播 # optimizer.step() # 更新权重
2.4. 更新权重
在经过反向传播的所有阶段,包括误差计算、从输出层到输入层的误差反向传播以及梯度计算之后,需要更新权重。
深度学习模型中的权重是重要的参数,其值在模型训练期间更新以提高性能。权重更新涉及以模型表现更好的方式更改权重值。此更新过程通常使用优化算法(例如梯度下降)或更高级的优化方法(例如 Adam)来完成。在某些情况下,可能已经进行了优化,并且可能不需要更新权重。
在 PyTorch 库中,权重被表示为 ` torch.nn.Parameter`的对象,并且可以在代码中轻松定义和管理。
import torch import torch.nn as nn # 定义一个有两个输入和一个输出的线性层 class LinearLayer (nn.Module): def __init__ ( self ): super (LinearLayer, self).__init__() self.weight = nn.Parameter(torch.randn( 2 , 1 )) # 将权重定义为参数对象 self.bias = nn.Parameter(torch.randn( 1 )) # 将偏差定义为参数对象 def forward ( self, x ): return torch.matmul(x, self.weight) + self.bias
在此代码中,我们定义了一个具有两个输入和一个输出的线性层。权重和偏差在 __init__ 函数内定义为 torch.nn.Parameter 对象。
总之,反向传播是计算梯度和更新神经网络权重的过程,从输出层观察到的误差开始,向输入层移动,以更新网络权重并提高其性能。
在这一部分中,我们能够探索重要的激活函数并了解它们背后的逻辑。事实上,当我们使用这些函数时,我们只是调用它们,所有的计算都在后台进行。选择合适的激活函数有助于在网络中实现所需的结果。当我们理解它们背后的逻辑时,我们可以更好地决定使用哪一个。我们还研究了反向传播算法并了解了它的不同阶段,熟悉了链式法则,它本质上是权重的导数,构成了反向传播的基础。反向传播是前馈和反馈的组合,它们是网络学习过程中的两个重要阶段。反向传播的阶段分为四个连续的阶段。我们可以说,在所有神经网络中,都存在前馈阶段,因为输入到网络的数据的权重必须有这个阶段才能进行计算并达到输出。