动手学深度学习-10
多层感知机+代码实现
我们将第一次介绍真正的深度网络。最简单的深度网络称为多层感知机。多层感知机由多层神经元组成,每一层与它的上一层相连,从中接收输入;同时每一层也与它的下一层相连,影响当前层的神经元
多层感知机
隐藏层
- 如果我们的标签通过仿射变换后确实与我们的输入数据相关,那么softmax回归确实足够了。但是,仿射变换中的线性是一个很强的假设
线性模型可能会出错
- 线性意味着单调假设
- 我们的数据可能会有一种表示,这种表示会考虑到我们在特征之间的相关交互作用。在此表示的基础上建立一个线性模型可能会是合适的,但我们不知道如何手动计算这么一种表示。对于深度神经网络,我们使用观测数据来联合学习
在网络中加入隐藏层
- 我们可以通过在网络中加入一个或多个隐藏层来克服线性模型的限制,使其能处理更普遍的函数关系类型。
- 要做到这一点,最简单的方法是将许多全连接层堆叠在一起。每一层都输出到上面的层,直到生成最后的输出
- 我们可以把前L-1层看作表示,把最后一层看作线性预测器。这种架构通常称为多层感知机,缩写为MLP
- 具有全连接层的多层感知机的参数开销可能会高得令人望而却步。但是即使在不改变输入或输出大小的情况下,可能在参数节约和模型有效性之间进行权衡
从线性到非线性
- 为了发挥多层架构的潜力,我们还需要一个额外的关键要素:在仿射变换之后对每个隐藏单元应用非线性的激活函数。激活函数的输出被称为活性值。
- 一般来说,有了激活函数,就不可能再将我们的多层感知机退化线性模型
通用近似定理
- 多层感知机可以通过隐藏神经元,捕捉到输入之间复杂的相互作用,这些神经元依赖于每个输入的值。我们可以很容易地设计隐藏节点来执行任意计算。
- 即使是网络只有一个隐藏层,给定足够的神经元和正确的权重,我们可以对任意函数建模,尽管实际中学习该函数是很困难的
- 神经网络有点像C语言,C语言和任何其他现代编程语言一样,能够表达任何可计算的程序。但实际上,想出一个符合规范的程序才是最困难的部分
- 虽然一个单隐藏层网络能学习任何函数,但并不意味着我们应该尝试使用单隐藏层来解决所有问题。事实上,通过使用更深(而不是更广)的网络,我们可以更容易地逼近许多函数
激活函数
- 激活函数通过计算加权和并加上偏置来确定神经元是否应该被激活,它们将输入信号转换为输出的可微运算。大多数激活函数都是非线性的
ReLU函数
- 最受欢迎的激活函数是修正线性单元,因为它实现简单,同时在各种预测任务中表现良好。
- ReLU提供了一种非常简单的非线性变换
- 使用ReLU的原因是,它求导表现得特别好:要么让参数消失,要么让参数通过。这使得优化表现很好,并且ReLU减轻了困扰以往神经网络的梯度消失问题
- ReLU函数有许多变体,包括参数化ReLU函数。该变体为ReLU添加一个线性项,因此即使参数是负的,某些信息仍然可以通过
sigmoid函数
- 对于一个定义域在R中的输入,sigmoid函数将输入变换为区间(0, 1)上的输出。因此,sigmoid通常称为挤压函数
- 当人们逐渐关注到基于梯度的学习时,sigmoid函数是一个自然的选择,因为它是一个平滑的、可微的阈值单元近似
- 当我们想要将输出视作二元分类问题的概率时,sigmoid函数仍然被广泛用作输出单元上的激活函数(sigmoid可以视为softmax的特例)
- 然而,sigmoid在隐藏层中已经较少使用,它在大部分时候被更简单、更容易训练的ReLU所取代
- 当输入接近0时,sigmoid函数接近线性变换
- sigmoid函数的导数为下面的公式:
tanh函数
- 与sigmoid函数类似,tanh(双曲正切)函数也能将其输入压缩转换到区间(-1, 1)上。tanh函数的公式如下:
- tanh函数的导数是:
多层感知机的从零开始实现
1 | import torch |
初始化模型参数
我们将实现一个具有单隐藏层的多层感知机,它包含256个隐藏单元
通常,我们选择2的若干次幂作为层的宽度。因为内存在硬件中的分配和寻址方式,这么做往往可以在计算上更高效
1
2
3
4
5
6
7
8num_inputs, num_outputs, num_hiddens = 784, 10, 256
W1 = nn.Parameter(torch.randn(num_inouts, num_hiddens, requires_grad=True) * 0.01)
b1 = nn.Parameter(torch.zeros(num_hiddens, requires_grad=True))
W2 = nn.Parameter(torch.randn(num_hiddens, num_outputs, require_gard=True))
b2 = nn.Parameter(torch.zeros(num_outputs, require_grad=True))
params = [W1, b1, W2, b2]
激活函数
实现了ReLU激活函数
1
2
3def relu(X):
a = torch.zeros_like(X)
return torch.max(X, a)
模型
因为我们忽略了空间结构,所以我们使用
reshape
将每个二维图像转换为一个长度为num_inputs
的向量1
2
3
4def net(X):
X = X.reshape((-1, num_inputs))
H = relu(X@W1 + b1) # 这里的@代表矩阵乘法
return (H@W2 + b2)
损失函数
1
loss = nn.CrossEntropyLoss(reduction='none')
训练
多层感知机的训练过程与sotftmax回归的训练过程完全相同
1
2
3num_epochs, le = 10, 0.1
updater = torch.optim.SGD(params, lr=lr)
d2l.train_ch3(net, train_iter, test_iterm loss, num_epochs, updater)
多层感知机的简洁实现
通过高级API更简洁地实现多层感知机
1
2
3import torch
from torch import nn
from d2l import torch as d2l
模型
与softmax回归的简洁实现相比,唯一的区别是我们添加了2个全连接层。第一层是隐藏层,包含了256个隐藏单元,并使用了ReLU激活函数;第二层是输出层
1
2
3
4
5
6
7
8
9
10net = nn.Sequrntial(nn.Flatten(),
nn.Linear(784, 256),
nn.ReLU(),
nn.Linear(256, 10))
def init_weights(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, std=0.01)
net.apply(init_weights);训练过程
1
2
3
4
5
6batch_size, lr, num_epochs = 256, 0.1, 10
loss = nn.CrossEntropyLoss(reduction='none')
trainer = torch.opptim.SGD(net.parameters(), lr=lr)
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)对于相同的分类问题,多层感知机的实现与softmax回归的实现相同,只是多层感知机的实现里增加了带有激活函数的隐藏层
QA
- 标题: 动手学深度学习-10
- 作者: 敖炜
- 创建于 : 2023-08-11 15:35:25
- 更新于 : 2024-04-19 09:28:09
- 链接: https://ao-wei.github.io/2023/08/11/动手学深度学习-10/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论