《动手学深度学习(PyTorch版)》笔记4.1

注:书中对代码的讲解并不详细,本文对很多细节做了详细注释。另外,书上的源代码是在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 L1层看作表示,把最后一层看作线性预测器。这种架构通常称为多层感知机(multilayer perceptron),通常缩写为MLP,下面我们以图的方式描述了多层感知机。

在这里插入图片描述

这个多层感知机有4个输入,3个输出,其隐藏层包含5个隐藏单元。输入层不涉及任何计算,因此使用此网络产生输出只需要实现隐藏层和输出层的计算。因此,这个多层感知机中的层数为2。注意,这两个层都是全连接的。每个输入都会影响隐藏层中的每个神经元,而隐藏层中的每个神经元又会影响输出层中的每个神经元。

然而,具有全连接层的多层感知机的参数开销可能会高得令人望而却步,即使在不改变输入或输出大小的情况下,可能在参数节约和模型有效性之间进行权衡。

同之前的章节一样,我们通过矩阵 X ∈ R n × d \mathbf{X} \in \mathbb{R}^{n \times d} XRn×d来表示 n n n个样本的小批量,其中每个样本具有 d d d个输入特征。对于具有 h h h个隐藏单元的单隐藏层多层感知机,用 H ∈ R n × h \mathbf{H} \in \mathbb{R}^{n \times h} HRn×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} ORn×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} HO=XW(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)=XW+b.

为了发挥多层架构的潜力,我们还需要一个额外的关键要素:在仿射变换之后对每个隐藏单元应用非线性的激活函数(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 unit,ReLU),因为它实现简单,同时在各种预测任务中表现良好。ReLU提供了一种非常简单的非线性变换,给定元素 x x x,ReLU函数被定义为该元素与 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函数有许多变体,包括参数化ReLU(Parameterized ReLU,pReLU)函数,该变体为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)=1+exp(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)=(1+exp(x))2exp(x)=sigmoid(x)(1sigmoid(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)=1+exp(2x)1exp(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)=1tanh2(x).

tanh函数的导数图像如下:

在这里插入图片描述

本节代码如下:

import matplotlib.pyplot as plt
import torch
from d2l import torch as d2l#绘制ReLU函数图像
x=torch.arange(-8,8,0.1,requires_grad=True)
y=torch.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 doesn't have a computation graph
plt.show()#绘制ReLU函数的导数图像
y.backward(torch.ones_like(x),retain_graph=True)
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_graph=True: retains the computational graph after performing the backward pass
plt.show()#绘制sigmoid函数图像
y=torch.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_graph=True)
d2l.plot(x.detach(),x.grad,'x','grad of sigmoid(x)',figsize=(5,2.5))
plt.show()#绘制tanh函数图像
y=torch.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_graph=True)
d2l.plot(x.detach(),x.grad,'x','grad of tanh(x)',figsize=(5,2.5))
plt.show()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/433248.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

node学习过程中的终端命令

冷的哥们手真tm冷,打字都是僵的,屮 目录 一、在学习nodejs过程中用到的终端命令总结 一、在学习nodejs过程中用到的终端命令 node -v nvm install 20.11.0 nvm list nvm list available nvm on nvm -v nvm use 20.11.0 node加要运行的js文件路径 ps&a…

OSPF协议LSDB同步过程和邻居状态机

知识改变命运,技术就是要分享,有问题随时联系,免费答疑,欢迎联系! 厦门微思网络​​​​​​ https://www.xmws.cn 华为认证\华为HCIA-Datacom\华为HCIP-Datacom\华为HCIE-Datacom Linux\RHCE\RHCE 9.0\RHCA\ Oracle O…

前端工程化之:CSS工程化+Less

一、什么是Less? Less官网 Less中文网 Less 是一种更加简洁的样式代码,它非常像 CSS ,但又不太一样,它让编写样式变得更容易。 Less 代码虽好,但它无法被浏览器识别,因此需要一个工具将其转换为纯正的 CS…

鸿蒙开发(Harmonyos兼容与Harmonyos适配)

布局的实现 Layout_ability_main.xml布局&#xff1a; <?xml version"1.0" encoding"utf-8"?> <DirectionalLayoutxmlns:ohos"http://schemas.huawei.com/res/ohos"ohos:height"match_parent"ohos:width"match_pare…

docker容器生命周期管理命令

文章目录 前言1、docker create2、docker run2.1、常用选项2.2、系统2.3、网络2.4、健康检查 3、docker start/stop/restart4、docker kill5、docker rm6、docker pause/unpause总结 前言 在云原生时代&#xff0c;Docker已成为必不可少的容器管理工具。通过掌握Docker常用的容…

2. MySQL 多实例

重点&#xff1a; MySQL 的 三种安装方式&#xff1a;包安装&#xff0c;二进制安装&#xff0c;源码编译安装。 MySQL 的 基本使用 MySQL 多实例 DDLcreate alter drop DML insert update delete DQL select 2.5&#xff09;通用 二进制格式安装 MySQL 2.5.1&#xff…

立创EDA学习:设计收尾工作

布线整理 ShiftM&#xff0c;关闭铺铜显示 调整结束后再使用快捷键”ShiftM“打开铺铜 过孔 在空白区域加上一些GND过孔&#xff0c;连接顶层与底层的铺铜。放置好”过孔“后&#xff0c;隐藏铺铜&#xff0c;观察刚才放置的过孔有没有妨碍到其他器件 调整铺铜 先打开铺铜区&…

用可视化案例讲Rust编程4. 用泛型和特性实现自适配shapefile的读取

本节已经涉及Rust学习曲线上的一个大坑&#xff1a;泛型和特性了&#xff0c;属于语言的深水区&#xff0c;如果初学者&#xff0c;建议看一眼知道有这个功能即可。 如果我们立足于功能实现&#xff0c;那么做到像上一节那样就可以了&#xff0c;从原理上来说&#xff0c;每个…

TCP 状态转换以及半关闭

TCP 状态转换&#xff1a; 上图中还没有进行握手的时候状态是关闭的。 三次握手状态的改变&#xff1a; 客户端发起握手。 调用 connect() 函数时状态转化为&#xff1a;SYN_SENT。调用 listen() 函数时状态转换为&#xff1a;LISTEN。ESTABLISHED是被连接的状态。 四次挥手…

C++核心编程:C++ 中的引用 笔记

2.引用 2.1 引用的基本使用 - 作用&#xff1a;给变量起别名 - 语法&#xff1a;数据类型 &别名 原名 #include<iostream> using namespace std; int main() {// 引用基本语法// 数据类型 &别名 原名int a 10;// 创建引用int &ref_a a;cout<<&qu…

代码随想录刷题笔记 DAY15 | 翻转二叉树 No.226 | 对称二叉树 No.101

Day 15 01. 翻转二叉树&#xff08;No. 226&#xff09; 题目链接 代码随想录题解 1.1 题目 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 1&#xff1a; 输入&#xff1a;root [4,2,7,1,3,6,9] 输出&#xff1a;[4,7,2,9…

PCB制板基础知识

一、PCB概念 PCB&#xff08;PrintedCircuitBoard&#xff09;&#xff0c;中文名称为印制电路板&#xff0c;又称印刷电路板、印刷线路板&#xff0c;是重要的电子部件&#xff0c;是电子元器件的支撑体&#xff0c;是电子元器件电气连接的提供者。由于它是采用电子印刷术制作…