动手学深度学习-第4章多层感知机

发布时间 2023-03-24 16:46:39作者: 学到老必须的

最简单的深度网络称为多层感知机。多层感知机由多层神经元组成, 每一层与它的上一层相连,从中接收输入; 同时每一层也与它的下一层相连,影响当前层的神经元。

4.1多层感知机

仿射变换中的线性是一个很强的假设.很容易找出违反单调性的例子.处理这一问题的一种方法是对我们的数据进行预处理, 使线性变得更合理
**对于深度神经网络,我们使用观测数据来联合学习隐藏层表示和应用于该表示的线性预测器。 **
可以通过在网络中加入一个或多个隐藏层来克服线性模型的限制, 使其能处理更普遍的函数关系类型。
image

为了发挥多层架构的潜力, 我们还需要一个额外的关键要素: 在仿射变换之后对每个隐藏单元应用非线性的激活函数(activation function)\(\sigma\)。 激活函数的输出(例如,\(\sigma(\cdot)\))被称为活性值(activations)。 一般来说,有了激活函数,就不可能再将我们的多层感知机退化成线性模型:
\(\begin{split}\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}\end{split}\)

多层感知机是通用近似器。 即使是网络只有一个隐藏层,给定足够的神经元和正确的权重, 我们可以对任意函数建模
激活函数(activation function)通过计算加权和并加上偏置来确定神经元是否应该被激活, 它们将输入信号转换为输出的可微运算。

  1. ReLU函数
    修正线性单元(Rectified linear unit,ReLU),给定元素x,ReLU函数被定义为该元素与0的最大值:
    \(\operatorname{ReLU}(x) = \max(x, 0).\)
    image
    导数图像
    image

  2. sigmoid函数
    sigmoid通常称为挤压函数(squashing function): 它将范围(-inf, inf)中的任意输入压缩到区间(0, 1)中的某个值:
    $\operatorname{sigmoid}(x)= \frac{1}{1+\exp(-x)}. $
    image

sigmoid函数的导数为下面的公式:
\(\frac{d}{dx} \operatorname{sigmoid}(x) = \frac{\exp(-x)}{(1 + \exp(-x))^2} = \operatorname{sigmoid}(x)\left(1-\operatorname{sigmoid}(x)\right).\)
image

  1. tanh函数
    tanh(双曲正切)函数也能将其输入压缩转换到区间(-1, 1)上
    $\operatorname{tanh}(x)=\frac{1-\exp(-2x)}{1+\exp(-2x)} $
    image

tanh函数的导数是:
\(\frac{d}{dx} \operatorname{tanh}(x) = 1 - \operatorname{tanh}^2(x).\)
image

4.2多层感知机算法实现知机

点击查看代码
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)
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]
def relu(X):
    a = torch.zeros_like(X)
    return torch.max(X, a)
def net(X):
    X = X.reshape((-1, num_inputs))
    H = relu(X@W1 + b1)  # 这里“@”代表矩阵乘法
    return (H@W2 + b2)
loss = nn.CrossEntropyLoss(reduction='none')

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)

在所有其他参数保持不变的情况下,更改超参数num_hiddens的值,并查看此超参数的变化对结果有何影响。确定此超参数的最佳值。
为了确定最佳的num_hiddens值,可以进行如下步骤:

初始化其它超参数;
定义神经网络模型,在其中指定num_hiddens的值,然后进行训练;
使用训练好的模型进行预测,并获取预测精度;
重复步骤2和步骤3,使用不同的num_hiddens值,以确定最佳值。

下面是一个示例代码,用于探索num_hiddens的最佳值:

点击查看代码
import torch
import torchvision
import torchvision.transforms as transforms

# 加载训练集和测试集
transform = transforms.Compose(
    [transforms.ToTensor(),
     transforms.Normalize((0.5,), (0.5,))])
trainset = torchvision.datasets.MNIST(root='./data', train=True,
                                        download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=64,
                                          shuffle=True, num_workers=2)
testset = torchvision.datasets.MNIST(root='./data', train=False,
                                       download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=64,
                                         shuffle=False, num_workers=2)

# 定义神经网络模型
class Net(torch.nn.Module):

    def __init__(self, num_hiddens):
        super(Net, self).__init__()
        self.fc1 = torch.nn.Linear(28 * 28, num_hiddens)
        self.fc2 = torch.nn.Linear(num_hiddens, 10)

    def forward(self, x):
        x = x.view(-1, 28 * 28)
        x = torch.relu(self.fc1(x))
        x = self.fc2(x)
        return x

# 初始化超参数
lr = 0.1
num_epochs = 15
num_hiddens_list = [100, 200, 300, 400, 500]

# 训练模型,并测试不同的 num_hiddens 值
for num_hiddens in num_hiddens_list:
    net = Net(num_hiddens)
    criterion = torch.nn.CrossEntropyLoss()
    optimizer = torch.optim.SGD(net.parameters(), lr=lr)
    for epoch in range(num_epochs):   
        running_loss = 0.0
        for i, data in enumerate(trainloader, 0):
            inputs, labels = data
            optimizer.zero_grad()
            outputs = net(inputs)
            loss = criterion(outputs, labels)
            loss.backward()
            optimizer.step()
            running_loss += loss.item()
        print('[%d, %5d] loss: %.3f' %
                      (epoch + 1, i + 1, running_loss / len(trainloader)))
        running_loss = 0.0
    print('Finished Training')
    # 测试模型
    correct = 0
    total = 0
    with torch.no_grad():
        for data in testloader:
            images, labels = data
            outputs = net(images)
            _, predicted = torch.max(outputs.data, 1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()

    print('Accuracy of the network on the 10000 test images: %d %%' % (
        100 * correct / total))

4.3多层感知机简洁算法实现知机

模块化设计使我们能够将与模型架构有关的内容独立出来

点击查看代码
import torch
from torch import nn
from d2l import torch as d2l

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(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)

4.4模型选择、欠拟合和过拟合

如果有足够多的神经元、层数和训练迭代周期, 模型最终可以在训练集上达到完美的精度,此时测试集的准确性却下降了。
影响模型泛化的因素:

  • 可调整参数的数量。当可调整参数的数量(有时称为自由度)很大时,模型往往更容易过拟合。
  • 参数采用的值。当权重的取值范围较大时,模型可能更容易过拟合。
  • 训练样本的数量。即使模型很简单,也很容易过拟合只包含一两个样本的数据集。而过拟合一个有数百万个样本的数据集则需要一个极其灵活的模型。

4.5权重衰减

4.6消退法

4.7前向传播、反向传播和计算图

4.8数值稳定性和模型初始化

4.9环境和分布偏移