《动手学深度学习》(二)-- 多层感知机

1 多层感知机

1.1 从零开始实现

        本节将继续使⽤Fashion-MNIST图像分类数据集,数据导入步骤和上一节一样,本节将不再展示。

import torch
from d2l import torch as d2l
import torch.nn.functional as F
from torch.utils.data import DataLoader
from torch.utils import data
from torchvision import transforms
from torch import nn

mnist_train = FashionMnistDataset("../dataset/fashion-mnist", "train-images-idx3-ubyte.gz", "train-labels-idx1-ubyte.gz", transform=transforms.ToTensor())
mnist_test = FashionMnistDataset("../dataset/fashion-mnist", "t10k-images-idx3-ubyte.gz", "t10k-labels-idx1-ubyte.gz", transform=transforms.ToTensor())
train_loader = DataLoader(mnist_train, batch_size=256, shuffle=True, num_workers=4)
test_loader = DataLoader(mnist_test, batch_size=256, shuffle=False, num_workers=4)

1. 初始化模型参数

        ⾸先,我们将实现⼀个具有单隐藏层的多层感知机,它包含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]

2. 激活函数

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

3. 模型
        使⽤reshape将每个⼆维图像转换为⼀个⻓度为num_inputs的向量。

def net(X):
    X = X.reshape((-1, num_inputs))
    H = relu(X@W1 + b1)
    return (H@W2 + b2)

4. 损失函数

loss = nn.CrossEntropyLoss()

5. 训练

        多层感知机的训练过程与softmax回归的训练过程完全相同。可以直接调⽤d2l包的train_ch3函数,将迭代周期数设置为10,并将学习率设置为0.1。

num_epoches, lr = 10, 0.1
optimizer = torch.optim.SGD(params, lr=lr)
d2l.train_ch3(net, train_loader, test_loader, loss, num_epoches, optimizer)
《动手学深度学习》(二)-- 多层感知机_第1张图片

6. 评估模型

d2l.predict_ch3(net, test_loader)
《动手学深度学习》(二)-- 多层感知机_第2张图片

1.2 简洁实现

        这里和上一节的softmax回归的简洁实现相比,唯一的区别是这里使用了2个全连接层,第一层是隐藏层,包含256个隐藏单元,并使用ReLU激活函数。

net = nn.Sequential(nn.Flatten(), nn.Linear(784, 256), nn.ReLU(), nn.Linear(256, 10))
def init_weights(m):
    if isinstance(m, nn.Linear):
        nn.init.normal_(m.weight, std=0.01)

net.apply(init_weights)

lr, num_epoches = 0.1, 10
loss = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(net.parameters(), lr=lr)
d2l.train_ch3(net, train_loader, test_loader, loss, num_epoches, optimizer)
《动手学深度学习》(二)-- 多层感知机_第3张图片

2 多项式回归

        本节主要以不同阶数的多项式进行回归实验,从而体会过拟合和欠拟合。

import torch
import numpy as np
import torch
from torch import nn
from d2l import torch as d2l
import math

1. 生成数据集
        给定 x x x,使用以下三阶多项式来⽣成训练和测试数据的标签:
y = 5 + 1.2 x − 3.4 x 2 2 ! + 5.6 x 3 3 ! + ϵ  where  ϵ ∼ N ( 0 , 0. 1 2 ) y=5+1.2 x-3.4 \frac{x^{2}}{2 !}+5.6 \frac{x^{3}}{3 !}+\epsilon \text { where } \epsilon \sim \mathcal{N}\left(0,0.1^{2}\right) y=5+1.2x3.42!x2+5.63!x3+ϵ where ϵN(0,0.12)

        噪声 ϵ \epsilon ϵ项服从均值为0且标准差为0.1的正态分布。在优化的过程中,我们通常希望避免⾮常⼤的梯度值或损失值。这就是我们将特征从 x i x^i xi调整为 x i i ! \frac{x^i}{i!} i!xi的原因,这样可以避免很⼤的i带来的特别⼤的指数值。为训练集和测试集各⽣成100个样本。

max_degree = 20                 # 多项式回归的最大阶数
n_train, n_test = 100, 100      # 训练集和测试接大小
true_w = np.zeros(max_degree)   # 分配大量的空间
true_w[0:4] = np.array([5, 1.2, -3.4, 5.6])

features = np.random.normal(size=(n_train + n_test, 1))
np.random.shuffle(features)
poly_features = np.power(features, np.arange(max_degree).reshape(1, -1))        # 200 * 20 特征矩阵
for i in range(max_degree):
    poly_features[:, i] /= math.gamma(i+1)      # gamma(n) = (n-1)!
    # labels的维度:(n_train+n_test, )
labels = np.dot(poly_features, true_w)
labels += np.random.normal(scale=0.1, size=labels.shape)        # 加上误差

        同样,存储在poly_features中的单项式由gamma函数重新缩放,其中 Γ ( n ) = ( n − 1 ) ! \Gamma(n)=(n-1)! Γ(n)=(n1)!。从⽣成的数据集中查看⼀下前2个样本,第⼀个值是与偏置相对应的常量特征。

# NumPyndarray转换为tensor
true_w, features, poly_features, labels = [torch.tensor(x, dtype=torch.float32) for x in [true_w, features, poly_features, labels]]
features[:2], poly_features[:2, :], labels[:2]
《动手学深度学习》(二)-- 多层感知机_第4张图片

2. 训练和测试

        ⾸先实现⼀个函数来评估模型在给定数据集上的损失。

def evaluate_loss(net, data_loader, loss):
    """评估给定数据集上模型的损失"""
    metric = d2l.Accumulator(2)     # 损失的总和,样本数量
    for X, y in data_loader:
        out = net(X)
        y = y.reshape(out.shape)
        l = loss(out, y)
        metric.add(l.sum(), l.numel())
    return metric[0] / metric[1]

3. 定义训练函数

def train(train_features, test_features, train_labels, test_labels, num_epoches=400):
    loss = nn.MSELoss(reduction='none')
    input_shape = train_features.shape[-1]
    # 不设置偏置,因为已经在多项式特征中实现了它
    net = nn.Sequential(nn.Linear(input_shape, 1, bias=False))
    batch_size = min(10, train_labels.shape[0])
    train_iter = d2l.load_array((train_features, train_labels.reshape(-1, 1)), batch_size)
    test_iter = d2l.load_array((test_features, test_labels.reshape(-1, 1)), batch_size, is_train=False)
    trainer = torch.optim.SGD(net.parameters(), lr=0.001)
    animator = d2l.Animator(xlabel='epoch', ylabel='loss', yscale='log', xlim=[1, num_epoches], ylim=[1e-3, 1e2], legend=['train', 'test'])
    for epoch in range(num_epoches):
        d2l.train_epoch_ch3(net, train_iter, loss, trainer)
        if epoch == 0 or (epoch+1) % 20 == 0:
            animator.add(epoch+1, (evaluate_loss(net, train_iter, loss), (evaluate_loss(net, test_iter, loss))))
    print('weight:', net[0].weight.data.numpy())

        使⽤三阶多项式函数,它与数据⽣成函数的阶数相同。结果表明,该模型能有效降低训练损失和测试损失。学习到的模型参数也接近真实值 w = [ 5 , 1.2 , − 3.4 , 5.6 ] w = [5, 1.2, -3.4, 5.6] w=[5,1.2,3.4,5.6]

# 从多项式特征中选择前4个维度,即1,x,x^2/2!,x^3/3!
train(poly_features[:n_train, :4], poly_features[n_train:, :4], labels[:n_train], labels[n_train:])
《动手学深度学习》(二)-- 多层感知机_第5张图片

        再看看线性函数拟合,减少该模型的训练损失相对困难。在最后⼀个迭代周期完成后,训练损失仍然很⾼。当⽤来拟合⾮线性模式(如这⾥的三阶多项式函数)时,线性模型容易⽋拟合。

# 从多项式特征中选择前2个维度,即1,x
train(poly_features[:n_train, :2], poly_features[n_train:, :2], labels[:n_train], labels[n_train:])

        尝试使⽤⼀个阶数过⾼的多项式来训练模型。在这种情况下,没有⾜够的数据⽤于学到⾼阶系数应该具有接近于零的值。因此,这个过于复杂的模型会轻易受到训练数据中噪声的影响。虽然训练损失可以有效地降低,但测试损失仍然很⾼。结果表明,复杂模型对数据造成了过拟合。

《动手学深度学习》(二)-- 多层感知机_第6张图片
# 从多项式特征中选择所有维度
train(poly_features[:n_train, :], poly_features[n_train:, :], labels[:n_train], labels[n_train:], num_epoches=1500)
《动手学深度学习》(二)-- 多层感知机_第7张图片

3 权重衰减

3.1 高维线性回归

        这里的权重衰减也就是对权重加上惩罚项,这里以线性回归为例。

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

1. 生成数据

        生成公式如下:
y = 0.05 + ∑ i = 1 d 0.01 x i + ϵ  where  ϵ ∼ N ( 0 , 0.0 1 2 ) y=0.05+\sum_{i=1}^{d} 0.01 x_{i}+\epsilon \text { where } \epsilon \sim \mathcal{N}\left(0,0.01^{2}\right) y=0.05+i=1d0.01xi+ϵ where ϵN(0,0.012)

        选择标签是关于输⼊的线性函数。标签同时被均值为0,标准差为0.01⾼斯噪声破坏。为了使过拟合的效果更加明显,我们可以将问题的维数增加到d = 200,并使⽤⼀个只包含20个样本的小训练集。

n_train, n_test, num_inputs, batch_size = 20, 100, 200, 5
true_w, true_b = torch.ones((num_inputs, 1)) * 0.01, 0.05
train_data = d2l.synthetic_data(true_w, true_b, n_train)
train_iter = d2l.load_array(train_data, batch_size)
test_data = d2l.synthetic_data(true_w, true_b, n_test)
test_iter = d2l.load_array(test_data, batch_size, is_train=False)

2. 初始化模型参数

        定义⼀个函数来随机初始化模型参数。

def init_params():
    w = torch.normal(0, 1, size=(num_inputs, 1), requires_grad=True)
    b = torch.zeros(1, requires_grad=True)
    return [w, b]

3. 定义L2惩罚项

        实现这⼀惩罚最⽅便的⽅法是对所有项求平⽅后并将它们求和。

def l2_penalty(w):
    return torch.sum(w.pow(2)) / 2

4. 训练

def train(lambd):
    w, b = init_params()
    net, loss = lambda X: d2l.linreg(X, w, b), d2l.squared_loss
    num_epoches, lr = 100, 0.003
    animator = d2l.Animator(xlabel='epoches', ylabel='loss', yscale='log', xlim=[5, num_epoches], legend=['train', 'test'])
    for epoch in range(num_epoches):
        for X, y in train_iter:
            # 增加了L2范数惩罚项,
            # ⼴播机制使l2_penalty(w)成为⼀个⻓度为batch_size的向量
            l = loss(net(X), y) + lambd * l2_penalty(w)
            l.sum().backward()
            d2l.sgd([w, b], lr, batch_size)
            if (epoch+1) % 5 == 0:
                animator.add(epoch+1, (d2l.evaluate_loss(net, train_iter, loss), d2l.evaluate_loss(net, test_iter, loss)))
        print('w的L2范数是:', torch.norm(w).item())

        我们现在⽤lambd = 0禁⽤权重衰减后运⾏这个代码。注意,这⾥训练误差在减少,但测试误差没有减少,这意味着出现了严重的过拟合。

train(lambd=0)
《动手学深度学习》(二)-- 多层感知机_第8张图片

        我们使⽤权重衰减来运⾏代码。注意,在这⾥训练误差增⼤,但测试误差减小。这正是我们期望从正则化中得到的效果。

train(lambd=3)
《动手学深度学习》(二)-- 多层感知机_第9张图片

        由于权重衰减在神经⽹络优化中很常⽤,深度学习框架为了便于我们使⽤权重衰减,将权重衰减集成到优化算法中,以便与任何损失函数结合使⽤。此外,这种集成还有计算上的好处,允许在不增加任何额外的计算开销的情况下向算法中添加权重衰减。由于更新的权重衰减部分仅依赖于每个参数的当前值,因此优化器必须⾄少接触每个参数⼀次。

        在下⾯的代码中,我们在实例化优化器时直接通过weight_decay指定weight decay超参数。默认情况下,PyTorch同时衰减权重和偏移。这⾥我们只为权重设置了weight_decay,所以偏置参数b不会衰减。

def train_concise(wd):
    net = nn.Sequential(nn.Linear(num_inputs, 1))
    for param in net.parameters():
        param.data.normal_()
    loss = nn.MSELoss(reduction='none')
    num_epoches, lr = 100, 0.003
    # 偏置参数没有衰减
    trainer = torch.optim.SGD([{'params': net[0].weight, 'weight_decay': wd}, {"params": net[0].bias
    }], lr = lr)
    animator = d2l.Animator(xlabel='epoches', ylabel='loss', yscale='log', xlim=[5, num_epoches], legend=['train', 'test'])
    for epoch in range(num_epoches):
        for X, y in train_iter:
            trainer.zero_grad()
            l = loss(net(X), y)
            l.sum().backward()
            trainer.step()
        if (epoch+1) % 5 == 0:
            animator.add(epoch+1, (d2l.evaluate_loss(net, train_iter, loss), d2l.evaluate_loss(net, test_iter, loss)))

    print('w的L2范数:', net[0].weight.norm().item())
train_concise(0)
《动手学深度学习》(二)-- 多层感知机_第10张图片
train_concise(3)
《动手学深度学习》(二)-- 多层感知机_第11张图片

4 暂退法

4.1 从零开始实现

        在标准暂退法正则化中,通过按保留(未丢弃)的节点的分数进⾏规范化来消除每⼀层的偏差。换⾔之,每个中间活性值 h h h以暂退概率 p p p由随机变量 h ′ h' h替换,如下所⽰:
h ′ = { 0  概率为  p h 1 − p  其他情况  h^{\prime}= \begin{cases}0 & \text { 概率为 } p \\ \frac{h}{1-p} & \text { 其他情况 }\end{cases} h={01ph 概率为 p 其他情况 
        根据此模型的设计,其期望值保值不变,即 E [ h ′ ] = h E[h']=h E[h]=h

        要实现单层的暂退法函数,我们从均匀分布U[0, 1]中抽取样本,样本数与这层神经⽹络的维度⼀致。然后保留那些对应样本⼤于p的节点,把剩下的丢弃。

        实现dropout_layer函数,该函数以dropout的概率丢弃张量输⼊X中的元素,如上所述重新缩放剩余部分:将剩余部分除以1.0-dropout。

1. 随机失活

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

def dropout_layer(X, dropout):
    assert 0 <= dropout <= 1
    # 在本情况,所有元素都被抛弃
    if dropout == 1:
        return torch.zeros_like(X)
    # 在本情况中,所有元素都被保留
    if dropout == 0:
        return X
    mask = (torch.rand(X.shape) > dropout).float()
    return mask * X / (1.0 - dropout)

2. 定义模型

        定义具有两个隐藏层的多层感知机,每个隐藏层包含256个单元。将暂退法应⽤于每个隐藏层的输出(在激活函数之后),并且可以为每⼀层分别设置暂退概率:常⻅的技巧是在靠近输⼊层的地⽅设置较低的暂退概率。下⾯的模型将第⼀个和第⼆个隐藏层的暂退概率分别设置为0.2和0.5,并且暂退法只在训练期间有效。

num_inputs, num_outputs, num_hiddens1, num_hiddens2 = 784, 10, 256, 256
dropout1, dropout2 = 0.2, 0.5
class Net(nn.Module):
    def __init__(self, num_inputs, num_outputs, num_hiddens, num_hiddens2, is_training=True):
        super(Net, self).__init__()
        self.num_inputs = num_inputs
        self.training = is_training
        self.lin1 = nn.Linear(num_inputs, num_hiddens1)
        self.lin2 = nn.Linear(num_hiddens1, num_hiddens2)
        self.lin3 = nn.Linear(num_hiddens2, num_outputs)
        self.relu = nn.ReLU()

    def forward(self, X):
        H1 = self.relu(self.lin1(X.reshape((-1, self.num_inputs))))
        # 只有在训练模型时才使⽤dropout
        if self.training == True:
            # 在第⼀个全连接层之后添加⼀个dropout层
            H1 = dropout_layer(H1, dropout1)
        H2 = self.relu(self.lin2(H1))
        if self.training == True:
            # 在第⼆个全连接层之后添加⼀个dropout层
            H2 = dropout_layer(H2, dropout2)
        out = self.lin3(H2)
        return out

net = Net(num_inputs, num_outputs, num_hiddens1, num_hiddens2)

3. 训练和测试

num_epoches, lr, batch_size = 10, 0.5, 256
loss = nn.CrossEntropyLoss()
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
trainer = torch.optim.SGD(net.parameters(), lr=lr)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epoches, trainer)
《动手学深度学习》(二)-- 多层感知机_第12张图片

4.2 简洁实现

        对于深度学习框架的⾼级API,只需在每个全连接层之后添加⼀个Dropout层,将暂退概率作为唯⼀的参数传递给它的构造函数。在训练时,Dropout层将根据指定的暂退概率随机丢弃上⼀层的输出(相当于下⼀层的输⼊)。在测试时,Dropout层仅传递数据。

net = nn.Sequential(nn.Flatten(), 
                    nn.Linear(784, 256), 
                    nn.ReLU(), 
                    nn.Dropout(dropout1),   # 在第⼀个全连接层之后添加⼀个dropout层
                    nn.Linear(256, 256), 
                    nn.ReLU(), 
                    nn.Dropout(dropout2),   # 在第二个全连接层之后添加⼀个dropout层
                    nn.Linear(256, 10))
def init_weights(m):
    if isinstance(m, nn.Linear):
        nn.init.normal_(m.weights, std=0.01)

对模型进⾏训练和测试

net.apply(init_weights)
trainer = torch.optim.SGD(net.parameters(), lr=lr)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epoches, trainer)
《动手学深度学习》(二)-- 多层感知机_第13张图片

你可能感兴趣的:(Deep,Learning,深度学习,pytorch,人工智能,多层感知机,动手学深度学习)