中国能源建设招标网站,兰州seo培训,wordpress演示数据库,wordpress伪静态html注#xff1a;书中对代码的讲解并不详细#xff0c;本文对很多细节做了详细注释。另外#xff0c;书上的源代码是在Jupyter Notebook上运行的#xff0c;较为分散#xff0c;本文将代码集中起来#xff0c;并加以完善#xff0c;全部用vscode在python 3.9.18下测试通过。…注书中对代码的讲解并不详细本文对很多细节做了详细注释。另外书上的源代码是在Jupyter Notebook上运行的较为分散本文将代码集中起来并加以完善全部用vscode在python 3.9.18下测试通过。
Chapter4 Multilayer Perceptron
4.1 Basic Concepts
4.1.1 Hidden Layer
我们在第三章中描述了仿射变换它是一种带有偏置项的线性变换。如果我们的标签通过仿射变换后确实与我们的输入数据相关那么这种方法确实足够了。但是仿射变换中的线性是一个很强的假设。线性意味着单调假设任何特征的增大都会导致模型输出的增大如果对应的权重为正或者导致模型输出的减小如果对应的权重为负。有时这是有道理的。例如如果我们试图预测一个人是否会偿还贷款。我们可以认为在其他条件不变的情况下收入较高的申请人比收入较低的申请人更有可能偿还贷款。但是虽然收入与还款概率存在单调性但它们不是线性相关的。收入从0增加到5万可能比从100万增加到105万带来更大的还款可能性。处理这一问题的一种方法是对我们的数据进行预处理使线性变得更合理如使用收入的对数作为我们的特征。
然而我们可以很容易找出违反单调性的例子。例如我们想要根据体温预测死亡率。对体温高于37摄氏度的人来说温度越高风险越大。然而对体温低于37摄氏度的人来说温度越高风险就越低。在这种情况下我们也可以通过一些巧妙的预处理来解决问题。例如我们可以使用与37摄氏度的距离作为特征。
与我们前面的例子相比这里的线性很荒谬而且我们难以通过简单的预处理来解决这个问题。我们的数据可能会有一种表示这种表示会考虑到我们在特征之间的相关交互作用。在此表示的基础上建立一个线性模型可能会是合适的但我们不知道如何手动计算这么一种表示。对于深度神经网络我们使用观测数据来联合学习隐藏层表示和应用于该表示的线性预测器。
我们可以通过在网络中加入一个或多个隐藏层来克服线性模型的限制使其能处理更普遍的函数关系类型。要做到这一点最简单的方法是将许多全连接层堆叠在一起。每一层都输出到上面的层直到生成最后的输出。我们可以把前 L − 1 L-1 L−1层看作表示把最后一层看作线性预测器。这种架构通常称为多层感知机multilayer perceptron通常缩写为MLP下面我们以图的方式描述了多层感知机。 这个多层感知机有4个输入3个输出其隐藏层包含5个隐藏单元。输入层不涉及任何计算因此使用此网络产生输出只需要实现隐藏层和输出层的计算。因此这个多层感知机中的层数为2。注意这两个层都是全连接的。每个输入都会影响隐藏层中的每个神经元而隐藏层中的每个神经元又会影响输出层中的每个神经元。
然而具有全连接层的多层感知机的参数开销可能会高得令人望而却步即使在不改变输入或输出大小的情况下可能在参数节约和模型有效性之间进行权衡。
同之前的章节一样我们通过矩阵 X ∈ R n × d \mathbf{X} \in \mathbb{R}^{n \times d} X∈Rn×d来表示 n n n个样本的小批量其中每个样本具有 d d d个输入特征。对于具有 h h h个隐藏单元的单隐藏层多层感知机用 H ∈ R n × h \mathbf{H} \in \mathbb{R}^{n \times h} H∈Rn×h表示隐藏层的输出称为隐藏表示hidden representations。在数学或代码中 H \mathbf{H} H也被称为隐藏层变量hidden-layer variable或隐藏变量hidden variable。因为隐藏层和输出层都是全连接的所以我们有隐藏层权重 W ( 1 ) ∈ R d × h \mathbf{W}^{(1)} \in \mathbb{R}^{d \times h} W(1)∈Rd×h和隐藏层偏置 b ( 1 ) ∈ R 1 × h \mathbf{b}^{(1)} \in \mathbb{R}^{1 \times h} b(1)∈R1×h以及输出层权重 W ( 2 ) ∈ R h × q \mathbf{W}^{(2)} \in \mathbb{R}^{h \times q} W(2)∈Rh×q和输出层偏置 b ( 2 ) ∈ R 1 × q \mathbf{b}^{(2)} \in \mathbb{R}^{1 \times q} b(2)∈R1×q。形式上我们按如下方式计算单隐藏层多层感知机的输出 O ∈ R n × q \mathbf{O} \in \mathbb{R}^{n \times q} O∈Rn×q H X W ( 1 ) b ( 1 ) , O H W ( 2 ) b ( 2 ) . \begin{aligned} \mathbf{H} \mathbf{X} \mathbf{W}^{(1)} \mathbf{b}^{(1)}, \\ \mathbf{O} \mathbf{H}\mathbf{W}^{(2)} \mathbf{b}^{(2)}. \end{aligned} HOXW(1)b(1),HW(2)b(2).
注意在添加隐藏层之后模型现在需要跟踪和更新额外的参数。可我们能从中得到什么好处呢在上面定义的模型里我们没有好处原因很简单上面的隐藏单元由输入的仿射函数给出而输出softmax操作前只是隐藏单元的仿射函数。仿射函数的仿射函数本身就是仿射函数但是我们之前的线性模型已经能够表示任何仿射函数。对于这个例子证明如下 O ( X W ( 1 ) b ( 1 ) ) W ( 2 ) b ( 2 ) X W ( 1 ) W ( 2 ) b ( 1 ) W ( 2 ) b ( 2 ) X W b . \mathbf{O} (\mathbf{X} \mathbf{W}^{(1)} \mathbf{b}^{(1)})\mathbf{W}^{(2)} \mathbf{b}^{(2)} \mathbf{X} \mathbf{W}^{(1)}\mathbf{W}^{(2)} \mathbf{b}^{(1)} \mathbf{W}^{(2)} \mathbf{b}^{(2)} \mathbf{X} \mathbf{W} \mathbf{b}. O(XW(1)b(1))W(2)b(2)XW(1)W(2)b(1)W(2)b(2)XWb.
为了发挥多层架构的潜力我们还需要一个额外的关键要素在仿射变换之后对每个隐藏单元应用非线性的激活函数activation function σ \sigma σ。激活函数的输出例如 σ ( ⋅ ) \sigma(\cdot) σ(⋅)被称为活性值activations。一般来说有了激活函数就不可能再将我们的多层感知机退化成线性模型 H σ ( X W ( 1 ) b ( 1 ) ) , O H W ( 2 ) b ( 2 ) . \begin{aligned} \mathbf{H} \sigma(\mathbf{X} \mathbf{W}^{(1)} \mathbf{b}^{(1)}), \\ \mathbf{O} \mathbf{H}\mathbf{W}^{(2)} \mathbf{b}^{(2)}.\\ \end{aligned} HOσ(XW(1)b(1)),HW(2)b(2).
由于 X \mathbf{X} X中的每一行对应于小批量中的一个样本出于记号习惯的考量我们定义非线性函数 σ \sigma σ也以按行的方式作用于其输入即一次计算一个样本。本节应用于隐藏层的激活函数通常不仅按行操作也按元素操作。这意味着在计算每一层的线性部分之后我们可以计算每个活性值而不需要查看其他隐藏单元所取的值对于大多数激活函数都是这样。
为了构建更通用的多层感知机我们可以继续堆叠这样的隐藏层例如 H ( 1 ) σ 1 ( X W ( 1 ) b ( 1 ) ) \mathbf{H}^{(1)} \sigma_1(\mathbf{X} \mathbf{W}^{(1)} \mathbf{b}^{(1)}) H(1)σ1(XW(1)b(1))和 H ( 2 ) σ 2 ( H ( 1 ) W ( 2 ) b ( 2 ) ) \mathbf{H}^{(2)} \sigma_2(\mathbf{H}^{(1)} \mathbf{W}^{(2)} \mathbf{b}^{(2)}) H(2)σ2(H(1)W(2)b(2))一层叠一层从而产生更有表达能力的模型。
多层感知机可以通过隐藏神经元捕捉到输入之间复杂的相互作用这些神经元依赖于每个输入的值。我们可以很容易地设计隐藏节点来执行任意计算。例如在一对输入上进行基本逻辑操作多层感知机是通用近似器。即使是网络只有一个隐藏层给定足够的神经元和正确的权重我们可以对任意函数建模尽管实际中学习该函数是很困难的(通用近似定理) 虽然一个单隐层网络能学习任何函数但并不意味着我们应该尝试使用单隐藏层网络来解决所有问题。事实上通过使用更深而不是更广的网络我们可以更容易地逼近许多函数。我们将在后面的章节中进行更细致的讨论。
4.1.2 Activation Function
激活函数activation function通过计算加权和并加上偏置来确定神经元是否应该被激活它们将输入信号转换为输出的可微运算。大多数激活函数都是非线性的。激活函数是深度学习的基础下面介绍一些常见的激活函数。
4.1.2.1 ReLU function
最受欢迎的激活函数是修正线性单元Rectified linear unitReLU因为它实现简单同时在各种预测任务中表现良好。ReLU提供了一种非常简单的非线性变换给定元素 x x xReLU函数被定义为该元素与 0 0 0的最大值 ReLU ( x ) max ( x , 0 ) . \operatorname{ReLU}(x) \max(x, 0). ReLU(x)max(x,0).
如图激活函数是分段线性的。
当输入为负时ReLU函数的导数为0而当输入为正时ReLU函数的导数为1。注意当输入值精确等于0时ReLU函数不可导。在此时我们默认使用左侧的导数即当输入为0时导数为0。我们可以忽略这种情况因为输入可能永远都不会是0正如一句名言所说“如果微妙的边界条件很重要我们很可能是在研究数学而非工程”。ReLU函数的导数图像如下
使用ReLU的原因是它求导表现得特别好要么让参数消失要么让参数通过。这使得优化表现得更好并且ReLU减轻了困扰以往神经网络的梯度消失问题稍后将详细介绍。
ReLU函数有许多变体包括参数化ReLUParameterized ReLUpReLU函数该变体为ReLU添加了一个线性项因此即使参数是负的某些信息仍然可以通过 pReLU ( x ) max ( 0 , x ) α min ( 0 , x ) . \operatorname{pReLU}(x) \max(0, x) \alpha \min(0, x). pReLU(x)max(0,x)αmin(0,x).
4.1.2.2 Sigmoid function
sigmoid通常称为挤压函数squashing function因为它将范围-inf, inf中的任意输入压缩到区间0, 1中的某个值 sigmoid ( x ) 1 1 exp ( − x ) . \operatorname{sigmoid}(x) \frac{1}{1 \exp(-x)}. sigmoid(x)1exp(−x)1.
当人们逐渐关注到到基于梯度的学习时sigmoid函数是一个自然的选择因为它是一个平滑的、可微的阈值单元近似。当我们想要将输出视作二元分类问题的概率时sigmoid仍然被广泛用作输出单元上的激活函数sigmoid可以视为softmax的特例。然而sigmoid在隐藏层中已经较少使用 它在大部分时候被更简单、更容易训练的ReLU所取代。在后面关于循环神经网络的章节中我们将描述利用sigmoid单元来控制时序信息流的架构。
sigmoid函数图像如下 sigmoid函数的导数为 d d x sigmoid ( x ) exp ( − x ) ( 1 exp ( − x ) ) 2 sigmoid ( x ) ( 1 − sigmoid ( x ) ) . \frac{d}{dx} \operatorname{sigmoid}(x) \frac{\exp(-x)}{(1 \exp(-x))^2} \operatorname{sigmoid}(x)\left(1-\operatorname{sigmoid}(x)\right). dxdsigmoid(x)(1exp(−x))2exp(−x)sigmoid(x)(1−sigmoid(x)).
sigmoid函数的导数图像如下
4.1.2.3 tanh function
与sigmoid函数类似tanh(双曲正切)函数能将其输入压缩转换到区间(-1, 1)上。tanh函数的公式如下 tanh ( x ) 1 − exp ( − 2 x ) 1 exp ( − 2 x ) . \operatorname{tanh}(x) \frac{1 - \exp(-2x)}{1 \exp(-2x)}. tanh(x)1exp(−2x)1−exp(−2x).
tanh函数的形状类似于sigmoid函数不同的是tanh函数关于坐标系原点中心对称。其函数图像如下
tanh函数的导数是 d d x tanh ( x ) 1 − tanh 2 ( x ) . \frac{d}{dx} \operatorname{tanh}(x) 1 - \operatorname{tanh}^2(x). dxdtanh(x)1−tanh2(x).
tanh函数的导数图像如下 本节代码如下
import matplotlib.pyplot as plt
import torch
from d2l import torch as d2l#绘制ReLU函数图像
xtorch.arange(-8,8,0.1,requires_gradTrue)
ytorch.relu(x)
d2l.plot(x.detach(),y.detach(),x,relu(x),figsize(5,2.5))
#detach() is used to create a new tensor that shares the same data with x but doesnt have a computation graph
plt.show()#绘制ReLU函数的导数图像
y.backward(torch.ones_like(x),retain_graphTrue)
d2l.plot(x.detach(),x.grad,x,grad of relu(x),figsize(5,2.5))
#torch.ones_like(x): creates a tensor of the same shape as x but filled with ones. This tensor is used as the gradient of the output y with respect to x during backpropagation
#retain_graphTrue: retains the computational graph after performing the backward pass
plt.show()#绘制sigmoid函数图像
ytorch.sigmoid(x)
d2l.plot(x.detach(),y.detach(),x,sigmoid(x),figsize(5,2.5))
plt.show()#绘制sigmoid函数的导数图像
x.grad.data.zero_()
y.backward(torch.ones_like(x),retain_graphTrue)
d2l.plot(x.detach(),x.grad,x,grad of sigmoid(x),figsize(5,2.5))
plt.show()#绘制tanh函数图像
ytorch.tanh(x)
d2l.plot(x.detach(),y.detach(),x,tanh(x),figsize(5,2.5))
plt.show()#绘制tanh函数的导数图像
x.grad.data.zero_()
y.backward(torch.ones_like(x),retain_graphTrue)
d2l.plot(x.detach(),x.grad,x,grad of tanh(x),figsize(5,2.5))
plt.show()