深度学习—04—多层感知机

43 阅读5分钟

4.1 多层感知机

4.1.1 隐藏层

先回顾一下仿射变换

仿射变换的特点是通过加权和对特征进行线性变换(linear transformation), 并通过偏置项来进行平移(translation)。

image.png

结合softmax回归模型,该模型是通过单个仿射变换将输入直接映射到输出,然后进行softmax操作。

如果我们的标签通过仿射变换后确实与我们的输入数据相关,那么这种方法确实足够了。

但是,仿射变换中的线性是一个很强的假设。

4.1.1.1 线性模型可能会出错

比如:虽然收入与还款概率存在单调性,但它们不是线性相关的。 收入从0增加到5万,可能比从100万增加到105万带来更大的还款可能性。

所以,有些问题,看似存在线性,但都挺荒谬的。

对于深度神经网络,我们使用观测数据来联合学习隐藏层表示应用于该表示的线性预测器。

4.1.1.2 在网络中加入隐藏层

我们可以通过在网络中加入一个或多个隐藏层来克服线性模型的限制, 使其能处理更普遍的函数关系类型。 要做到这一点,最简单的方法是将许多全连接层堆叠在一起。 每一层都输出到上面的层,直到生成最后的输出。

我们可以把前L−1层看作表示,把最后一层看作线性预测器。 这种架构通常称为多层感知机(multilayer perceptron),通常缩写为MLP

image.png

注意,这两个层都是全连接的。 每个输入都会影响隐藏层中的每个神经元, 而隐藏层中的每个神经元又会影响输出层中的每个神经元。

4.1.1.3 从线性到非线性

image.png

  • W的维度—— 输入特征维度 * 输出特征维度
  • O(ouput)的维度—— 样本数n * 输出特征维度

在添加隐藏层之后,模型现在需要跟踪和更新额外的参数。可我们能从中得到什么好处呢? 在上面定义的模型里,我们没有好处!原因很简单:

上面的隐藏单元由输入的仿射函数给出, 而输出(softmax操作前)只是隐藏单元的仿射函数。 仿射函数的仿射函数本身就是仿射函数, 但是我们之前的线性模型已经能够表示任何仿射函数。

证明如下:

image.png

所以,我们还要对模型进行一些改进

image.png

这也是为什么我们需要激活函数的原因。

image.png

4.1.1.4 通用近似定理

多层感知机可以通过隐藏神经元,捕捉到输入之间复杂的相互作用, 这些神经元依赖于每个输入的值。 我们可以很容易地设计隐藏节点来执行任意计算。 例如,在一对输入上进行基本逻辑操作,多层感知机是通用近似器。即使是网络只有一个隐藏层,给定足够的神经元和正确的权重, 我们可以对任意函数建模,尽管实际中学习该函数是很困难的。

而且,虽然一个单隐层网络能学习任何函数, 但并不意味着我们应该尝试使用单隐藏层网络来解决所有问题。 事实上,通过使用更深(而不是更广)的网络,我们可以更容易地逼近许多函数。

4.1.2 激活函数

4.1.2.1 ReLU函数

最受欢迎的激活函数是修正线性单元(Rectified linear unit,ReLU), 因为它实现简单,同时在各种预测任务中表现良好。ReLU函数提供了一种非常简单的非线性变换。

image.png

通俗地说,ReLU函数通过将相应的活性值设为0,仅保留正元素并丢弃所有负元素。

image.png

4.1.2.2 sigmoid函数

image.png

image.png

4.1.2.3 tanh函数

image.png

image.png

4.1.3 小结

  • 多层感知机在输出层和输入层之间增加一个或多个全连接隐藏层,并通过激活函数转换隐藏层的输出。
  • 常用的激活函数包括ReLU函数、sigmoid函数和tanh函数。

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

4.2.1 初始化参数模型

image.png

层的宽度是自己定的

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]

4.2.2 激活函数

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

4.2.3 模型

def net(X):
    X = X.reshape(-1, num_inputs)
    H = relu(X@W1 + b1)  # @指的是 矩阵乘法   [3,5] @ [5,4] --> [3,4]
    return H@W2 + b2

4.2.4 损失函数

# nn.CrossEntropyLoss(reduction=‘none’) 返回的是一个向量。
# 这个向量的长度与输入的 batch 大小相同,每个元素表示对应样本的损失值。
loss = nn.CrossEntropyLoss(reduction='none')

4.2.5 训练

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)

4.2.6 预测

d2l.predict_ch3(net, test_iter)
d2l.plt.show()

4.2.7 小结

  • 手动实现一个简单的多层感知机是很容易的。然而如果有大量的层,从零开始实现多层感知机会变得很麻烦(例如,要命名和记录模型的参数)。

完整代码

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

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

def net(X):
    X = X.reshape(-1, num_inputs)
    H = relu(X@W1 + b1)  # @指的是 矩阵乘法   [3,5] @ [5,4] --> [3,4]
    return H@W2 + b2

if __name__ == '__main__':
    batch_size = 256
    train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
    # 初始化参数
    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]

    # nn.CrossEntropyLoss(reduction=‘none’) 返回的是一个向量,而不是矩阵。
    # 这个向量的长度与输入的 batch 大小相同,每个元素表示对应样本的损失值。
    loss = nn.CrossEntropyLoss(reduction='none')
    # 迭代周期和学习率   学习率太低,将导致 没训练完全(但也不能太大,那将会导致来回震荡)
    num_epochs, lr = 10, 0.1

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

    d2l.predict_ch3(net=net,
                    test_iter=test_iter)
    d2l.plt.show()

4.3 多层感知机的简洁实现

4.3.1 模型

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

def init_weights(m):
    if type(m) == nn.Linear:
        nn.init.normal_(m.weight, std=0.01)

if __name__ == '__main__':
    net = nn.Sequential(nn.Flatten(),  # 先经过展平层 [28,28] = [784]
                        nn.Linear(784, 256),  # 线性层
                        nn.ReLU(),  # 经过ReLU激活函数   非线性化
                        nn.Linear(256, 10))  # 线性层

    net.apply(init_weights)

    batch_size, lr, num_epochs = 256, 0.1, 10
    loss = nn.CrossEntropyLoss(reduction='none')
    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)
    d2l.plt.show()

4.3.2 小结

  • 我们可以使用高级API更简洁地实现多层感知机。
  • 对于相同的分类问题,多层感知机的实现与softmax回归的实现相同,只是多层感知机的实现里增加了带有激活函数的隐藏层。