【深度学习笔记】07 多层感知机

多层感知机

在网络中加入隐藏层

我们可以通过在网络中加入一个或多个隐藏层来克服线性模型的限制,
使其能处理更普遍的函数关系类型。
要做到这一点,最简单的方法是将许多全连接层堆叠在一起。
每一层都输出到上面的层,直到生成最后的输出。
我们可以把前 L − 1 L-1 L1层看作表示,把最后一层看作线性预测器。
这种架构通常称为多层感知机(multilayer perceptron),通常缩写为MLP。

在这里插入图片描述

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

从线性到非线性

对于具有 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操作前)只是隐藏单元的仿射函数。仿射函数的仿射函数本身就是仿射函数,但是之前的线性模型已经能够表示任意仿射函数。

我们可以证明这一等价性,即对于任意权重值,
我们只需合并隐藏层,便可产生具有参数
W = W ( 1 ) W ( 2 ) \mathbf{W} = \mathbf{W}^{(1)}\mathbf{W}^{(2)} W=W(1)W(2)
b = b ( 1 ) W ( 2 ) + b ( 2 ) \mathbf{b} = \mathbf{b}^{(1)} \mathbf{W}^{(2)} + \mathbf{b}^{(2)} b=b(1)W(2)+b(2)
的等价单层模型:

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 σ。激活函数的输出称为活性值(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).

为了构建更通用的多层感知机,
我们可以继续堆叠这样的隐藏层,
例如 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))
一层叠一层,从而产生更有表达能力的模型。

激活函数

激活函数(activation function)通过计算加权并加上偏置来确定神经元是否应该被激活,它们将输入信号转换为输出的可微运算。

大多数激活函数都是非线性的。

%matplotlib inline
import torch
from d2l import torch as d2l

ReLU函数

修正线性单元(Rectified linear unit, 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,仅保留正元素并丢弃所有负元素。

x = torch.arange(-8.0, 8.0, 0.1, requires_grad = True)
y = torch.relu(x)
d2l.plot(x.detach(), y.detach(), 'x', 'relu(x)', figsize = (5, 2.5))


当输入为负时,ReLU函数的导数为0,而当输入为正时,ReLU函数的导数为1,当输入值精确等于0时,ReLU函数不可导,此时我们默认使用左侧的导数,即当输入为0时导数为0。

ReLU函数的导数:

y.backward(torch.ones_like(x), retain_graph = True)
d2l.plot(x.detach(), x.grad, 'x', 'grad of relu', figsize = (5, 2.5))


在这里插入图片描述

使用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).

sigmoid函数

对于一个定义域在 R \mathbb{R} R中的输入,
sigmoid函数将输入变换为区间(0, 1)上的输出。
因此,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可以视为softmax的特例。当输入接近0时,sigmoid函数接近线性变换。

y = torch.sigmoid(x)
d2l.plot(x.detach(), y.detach(), 'x', 'sigmoid(x)', figsize = (5, 2.5))


在这里插入图片描述

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函数的导数图像如下所示。
注意,当输入为0时,sigmoid函数的导数达到最大值0.25;
而输入在任一方向上越远离0点时,导数越接近0。

# 清除以前的梯度
x.grad.data.zero_()
y.backward(torch.ones_like(x), retain_graph = True)
d2l.plot(x.detach(), x.grad, 'x', 'grad of sigmoid', figsize = (5, 2.5))


在这里插入图片描述

tanh函数

与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).

当输入在0附近时,tanh函数接近线性变换。
函数的形状类似于sigmoid函数,
不同的是tanh函数关于坐标系原点中心对称。

y = torch.tanh(x)
d2l.plot(x.detach(), y.detach(), 'x', 'tanh(x)', figsize = (5, 2.5))


在这里插入图片描述

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函数的导数图像如下所示。
当输入接近0时,tanh函数的导数接近最大值1。
与我们在sigmoid函数图像中看到的类似,
输入在任一方向上越远离0点,导数越接近0。

# 清除以前的梯度
x.grad.data.zero_()
y.backward(torch.ones_like(x), retain_graph = True)
d2l.plot(x.detach(), x.grad, 'x', 'grad of tanh', figsize = (5, 2.5))


在这里插入图片描述

多层感知机的从零开始实现

import torch
from torch import nn
from d2l import torch as d2l
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)

初始化模型参数

Fashion-MNIST中每个图像由 28 × 28 = 784 28×28=784 28×28=784个灰度像素值组成,所有图像共分为10个类别。

忽略像素之间的空间结构,可以将每个图像视为具有784个输入特征和10个类的简单分类数据集。

首先,实现一个具有单隐藏层的多层感知机,它包含256个隐藏单元。我们可以将这两个变量都视为超参数。通常选择2的若干次幂作为层的宽度。因为内存在硬件中的分配和寻址方式,这样做往往可以在计算上更高效。

我们用几个张量来表示参数。注意,对于每一层都要记录一个权重矩阵和一个偏置向量,我们要为损失关于这些参数的梯度分配内存。

num_inputs, num_outputs, num_hiddens = 784, 10, 256

W1 = nn.Parameter(torch.randn(
    num_inputs, 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, requires_grad=True) * 0.01)
b2 = nn.Parameter(torch.zeros(num_outputs, requires_grad=True))

params = [W1, b1, W2, b2]

激活函数

实现ReLU激活函数

def relu(X):
    a = torch.zeros_like(X)
    return torch.max(X, a)

模型

使用reshape将每个二维图像转换为一个长度为num_inputs的向量。

def net(X):
    X = X.reshape((-1, num_inputs))
    H = relu(X@W1 + b1)  # 这里“@”代表矩阵乘法
    return(H@W2 + b2)

损失函数

使用高级API中的内置函数计算softmax和交叉熵损失。

loss = nn.CrossEntropyLoss()

训练

调用d2l包里的train_ch2函数,将迭代周期数设置为10,并将学习率设置为0.1。

num_epochs, lr = 10, 0.1
updater = torch.optim.SGD(params, lr = lr)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, updater)


在这里插入图片描述

在测试数据上应用模型。

d2l.predict_ch3(net, test_iter)


在这里插入图片描述

多层感知机的简洁实现

import torch
from torch import nn
from d2l import torch as d2l

模型

与softmax回归的简洁实现相比,
唯一的区别是我们添加了2个全连接层(之前我们只添加了1个全连接层)。
第一层是隐藏层,它包含256个隐藏单元,并使用了ReLU激活函数。
第二层是输出层。

net = nn.Sequential(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);
batch_size, lr, num_epochs = 256, 0.1, 10
loss = nn.CrossEntropyLoss()
trainer = torch.optim.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)

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值