深度学习实验(二)

实验二 多层感知机、欠拟合、过拟合

一、实验目的

多层感知机综述

隐藏层

首先,回想一下 softmax 回归的模型结构。该模型通过单个仿射变换将我们的输入直接映射到输出,然后进行 softmax 操作。如果我们的标签通过仿射变换后确实与我们的输入数据相关,那么这种方法就足够了。但是,仿射变换中的线性是一个很强的假设。

线性模型可能会出错

线性意味着单调假设:特征的任何增大都会导致模型输出增大(如果对应的权重为正),或者导致模型输出减少(如果对应的权重为负)。我们的数据可能会有一种表示,这种表示会考虑到我们的特征之间的相关交互作用。在此表示的基础上建立一个线性模型可能会是合适的,但我们不知道如何手动计算这么一种表示。对于深度神经网络,我们使用观测数据来联合学习隐藏层表示和应用于该表示的线性预测器。

合并隐藏层

我们可以通过合并一个或多个隐藏层来克服线性模型的限制,并处理更一般化的函数。要做到这一点,最简单的方法是将许多全连接层堆叠在一起。每一层都输出到上面的层,直到生成最后的输出。我们可以把前 L − 1 L-1 L1层看作表示,把最后一层看作线性预测器。这种架构通常称为多层感知机(multilayer perceptron),通常缩写为MLP。然而,具有全连接层的多层感知机的参数开销可能会高得令人望而却步,即使在不改变输入或输出大小的情况下,也可能促使在参数节约和模型有效性之间进行权衡。

从线性到非线性

跟之前的章节一样,我们通过矩阵 X ∈ R n × d \mathbf{X} \in \mathbb{R}^{n \times d} XRn×d 来表示 n n n个样本的小批量,其中每个样本具有 d d d个输入(特征)。对于具有 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).

对于任意权重值,我们只需合并隐藏层,便可产生具有参数 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 σ。激活函数的输出(例如, σ ( ⋅ ) \sigma(\cdot) σ())被称为激活值(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).

由于 X \mathbf{X} X中的每一行对应于小批量中的一个样本,出于记号习惯的考量,我们定义非线性函数 σ \sigma σ也以按行的方式作用于其输入,即一次计算一个样本。

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

欠拟合、过拟合综述

我们的目标是发现模式,这些模式捕捉到了我们训练集所来自的潜在总体的规律。如果成功做到了这点,即使是对我们以前从未遇到过的个体,我们也可以成功地评估风险。如何发现可以泛化的模式是机器学习的根本问题。

困难在于,当我们训练模型时,我们只能访问数据中的小部分样本。最大的公开图像数据集包含大约一百万张图像。而在大部分时候,我们只能从数千或数万个数据样本中学习。在大型医院系统中,我们可能会访问数十万份医疗记录。当我们使用有限的样本时,可能会遇到这样的问题:当收集到更多的数据时,会发现之前找到的明显关系并不成立。

将模型在训练数据上拟合得比在潜在分布中更接近的现象称为过拟合(overfitting),用于对抗过拟合的技术称为 正则化(regularization)

训练误差和泛化误差

为了进一步讨论这一现象,我们需要了解训练误差和泛化误差。训练误差(training error)是指,我们的模型在训练数据集上计算得到的误差。泛化误差(generalization error)是指,当我们将模型应用在同样从原始样本的分布中抽取的无限多的数据样本时,我们模型误差的期望。

问题是,我们永远不能准确地计算出泛化误差。这是因为无限多的数据样本是一个虚构的对象。在实际中,我们只能通过将模型应用于一个独立的测试集来估计泛化误差,该测试集由随机选取的、未曾在训练集中出现的数据样本构成。

统计学习理论

当我们训练模型时,我们试图找到一个能够尽可能拟合训练数据的函数。如果该函数灵活到可以像捕捉真实模式一样容易地捕捉到干扰的模式,那么它可能执行得“太好了”,而不能产生一个对看不见的数据做到很好泛化的模型。这种情况正是我们想要避免,或起码控制的。深度学习中有许多启发式的技术旨在防止过拟合。

模型复杂性

当我们有简单的模型和大量的数据时,我们期望泛化误差与训练误差相近。当我们有更复杂的模型和更少的样本时,我们预计训练误差会下降,但泛化误差会增大。模型复杂性由什么构成是一个复杂的问题。一个模型是否能很好地泛化取决于很多因素。例如,具有更多参数的模型可能被认为更复杂。参数有更大取值范围的模型可能更为复杂。通常,对于神经网络,我们认为需要更多训练迭代的模型比较复杂,而需要“提前停止”(early stopping)的模型(意味着具有较少训练迭代周期)就不那么复杂。

很难比较本质上不同大类的模型之间(例如,决策树与神经网络)的复杂性。就目前而言,一条简单的经验法则相当有用:统计学家认为,能够轻松解释任意事实的模型是复杂的,而表达能力有限但仍能很好地解释数据的模型可能更有现实用途。在哲学上,这与波普尔的科学理论的可证伪性标准密切相关:如果一个理论能拟合数据,且有具体的测试可以用来证明它是错误的,那么它就是好的。这一点很重要,因为所有的统计估计都是事后归纳,也就是说,我们在观察事实之后进行估计,因此容易受到相关谬误的影响。目前,我们将把哲学放在一边,坚持更切实的问题。

在本节中,为了给你一些直观的印象,我们将重点介绍几个倾向于影响模型泛化的因素:

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

在机器学习中,我们通常在评估几个候选模型后选择最终的模型。这个过程叫做模型选择。有时,需要进行比较的模型在本质上是完全不同的(比如,决策树与线性模型)。又有时,我们需要比较不同的超参数设置下的同一类模型。

例如,训练多层感知机模型时,我们可能希望比较具有不同数量的隐藏层、不同数量的隐藏单元以及不同的的激活函数组合的模型。为了确定候选模型中的最佳模型,我们通常会使用验证集。

验证集

原则上,在我们确定所有的超参数之前,我们不应该用到测试集。如果我们在模型选择过程中使用测试数据,可能会有过拟合测试数据的风险。那我们就麻烦大了。如果我们过拟合了训练数据,还有在测试数据上的评估来判断过拟合。但是如果我们过拟合了测试数据,我们又该怎么知道呢?

因此,我们决不能依靠测试数据进行模型选择。然而,我们也不能仅仅依靠训练数据来选择模型,因为我们无法估计训练数据的泛化误差。

在实际应用中,情况变得更加复杂。虽然理想情况下我们只会使用测试数据一次,以评估最好的模型或比较一些模型效果,但现实是,测试数据很少在使用一次后被丢弃。我们很少能有充足的数据来对每一轮实验采用全新测试集。

解决此问题的常见做法是将我们的数据分成三份,除了训练和测试数据集之外,还增加一个验证数据集(validation dataset),也叫验证集(validation set)。但现实是验证数据和测试数据之间的边界模糊得令人担忧。除非另有明确说明,否则在这本书的实验中,我们实际上是在使用应该被正确地称为训练数据和验证数据的东西,并没有真正的测试数据集。因此,书中每次实验报告的准确度都是验证集准确度,而不是测试集准确度。

K K K折交叉验证

当训练数据稀缺时,我们甚至可能无法提供足够的数据来构成一个合适的验证集。这个问题的一个流行的解决方案是采用 K K K折交叉验证。这里,原始训练数据被分成 K K K个不重叠的子集。然后执行 K K K次模型训练和验证,每次在 K − 1 K-1 K1个子集上进行训练,并在剩余的一个子集(在该轮中没有用于训练的子集)上进行验证。最后,通过对 K K K次实验的结果取平均来估计训练和验证误差。

欠拟合还是过拟合?

当我们比较训练和验证误差时,我们要注意两种常见的情况。首先,我们要注意这样的情况:训练误差和验证误差都很严重,但它们之间仅有一点差距。如果模型不能降低训练误差,这可能意味着我们的模型过于简单(即表达能力不足),无法捕获我们试图学习的模式。此外,由于我们的训练和验证误差之间的泛化误差很小,我们有理由相信可以用一个更复杂的模型降低训练误差。这种现象被称为欠拟合(underfitting)。

另一方面,当我们的训练误差明显低于验证误差时要小心,这表明严重的过拟合(overfitting)。注意,过拟合并不总是一件坏事。特别是在深度学习领域,众所周知,最好的预测模型在训练数据上的表现往往比在保留数据上好得多。最终,我们通常更关心验证误差,而不是训练误差和验证误差之间的差距。

我们是否过拟合或欠拟合可能取决于模型复杂性和可用训练数据集的大小,这两个点将在下面进行讨论。

模型复杂性

为了说明一些关于过拟合和模型复杂性的经典的直觉,我们给出一个多项式的例子。给定由单个特征 x x x和对应实数标签 y y y组成的训练数据,我们试图找到下面的 d d d阶多项式来估计标签 y y y

y ^ = ∑ i = 0 d x i w i \hat{y}= \sum_{i=0}^d x^i w_i y^=i=0dxiwi
这只是一个线性回归问题,我们的特征是 x x x的幂给出的,模型的权重是 w i w_i wi给出的,偏置是 w 0 w_0 w0给出的(因为对于所有的 x x x都有 x 0 = 1 x^0 = 1 x0=1)。由于这只是一个线性回归问题,我们可以使用平方误差作为我们的损失函数。

高阶多项式函数比低阶多项式函数复杂得多。高阶多项式的参数较多,模型函数的选择范围较广。因此在固定训练数据集的情况下,高阶多项式函数相对于低阶多项式的训练误差应该始终更低(最坏也是相等)。事实上,当数据样本包含了 x x x的不同值时,函数阶数等于数据样本数量的多项式函数可以完美拟合训练集。

数据集大小

另一个需要牢记的重要因素是数据集的大小。训练数据集中的样本越少,我们就越有可能(且更严重地)遇到过拟合。随着训练数据量的增加,泛化误差通常会减小。此外,一般来说,更多的数据不会有什么坏处。对于固定的任务和数据分布,模型复杂性和数据集大小之间通常存在关系。给出更多的数据,我们可能会尝试拟合一个更复杂的模型。能够拟合更复杂的模型可能是有益的。如果没有足够的数据,简单的模型可能更有用。对于许多任务,深度学习只有在有数千个训练样本时才优于线性模型。从一定程度上来说,深度学习目前的成功要归功于互联网公司、廉价存储、互联设备以及数字化经济带来的海量数据集。

dropout

在标准dropout正则化中,通过按保留(未丢弃)的节点的分数进行归一化来消除每一层的偏差。换言之,每个中间激活值 h h h丢弃概率 p p p由随机变量 h ′ h' h替换,如下所示:

h ′ = { 0  概率为  p h 1 − p  其他情况 \begin{aligned} h' = \begin{cases} 0 & \text{ 概率为 } p \\ \frac{h}{1-p} & \text{ 其他情况} \end{cases} \end{aligned} h={01ph 概率为 p 其他情况

根据设计,期望值保持不变,即 E [ h ′ ] = h E[h'] = h E[h]=h

二、实验要求

基于任意一种深度学习框架,对相关理论进行代码实现。

三、实验步骤

多层感知机

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

我们尝试从 0 0 0 开始自己实现一个多层感知机。

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 \times 28 = 784 28×28=784个灰度像素值组成。所有图像共分为10个类别。忽略像素之间的空间结构,我们可以将每个图像视为具有784个输入特征和10个类的简单分类数据集。首先,实现一个具有单隐藏层的多层感知机,它包含256个隐藏单元。

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

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)

实现模型

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

为了确保数值稳定性,同时由于我们已经从零实现过 softmax 函数(sec_softmax_scratch ),因此在这里我们直接使用高级 API 中的内置函数来计算 softmax 和交叉熵损失。回想一下我们之前在 :numref:subsec_softmax-implementation-revisited 中对这些复杂问题的讨论。我们鼓励感兴趣的读者查看损失函数的源代码,以加深对实现细节的了解。

loss = nn.CrossEntropyLoss()
训练

多层感知机的训练过程与softmax回归的训练过程完全相同。将迭代周期数设置为 10 10 10,并将学习率设置为 0.1 0.1 0.1

num_epochs, lr = 10, 0.1
updater = torch.optim.SGD(params, lr=lr)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q8Z1qH2v-1636467102470)(C:\Users\Lunatic\Desktop\深度学习实验报告\实验2\1.jpg)]

d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, updater)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0TLX4m0Z-1636467102471)(C:\Users\Lunatic\Desktop\深度学习实验报告\实验2\2.jpg)]

多层感知机的简洁实现

我们可以通过高级API更简洁地实现多层感知机。

import torch
from torch import nn
from d2l import torch as d2l
模型

与softmax回归的简洁实现相比,唯一的区别是我们添加了2个全连接层(之前我们只添加了1个全连接层)。第一层是隐藏层,它包含 256 256 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);
训练

训练过程的实现与我们实现 softmax 回归时完全相同,这种模块化设计使我们能够将与和模型架构有关的内容独立出来。

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)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f8g4L91i-1636467102473)(C:\Users\Lunatic\Desktop\深度学习实验报告\实验2\3.jpg)]

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

多项式回归

我们可以通过多项式拟合来交互地探索这些概念。

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

生成数据集

首先,我们需要数据。给定 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.2x - 3.4\frac{x^2}{2!} + 5.6 \frac{x^3}{3!} + \epsilon \text{ where } \epsilon \sim \mathcal{N}(0, 0.1^2) 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 i 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))
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个样本。第一个值是与偏置相对应的常量特征。

# NumPy ndarray转换为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]
对模型进行训练和测试

首先让我们实现一个函数来评估模型在给定数据集上的损失。

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

现在定义训练函数。

def train(train_features, test_features, train_labels, test_labels,
          num_epochs=400):
    loss = nn.MSELoss()
    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.01)
    animator = d2l.Animator(xlabel='epoch', ylabel='loss', yscale='log',
                            xlim=[1, num_epochs], ylim=[1e-3, 1e2],
                            legend=['train', 'test'])
    for epoch in range(num_epochs):
        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:])

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

高阶多项式函数拟合(过拟合)

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

# 从多项式特征中选取所有维度
train(poly_features[:n_train, :], poly_features[n_train:, :],
      labels[:n_train], labels[n_train:], num_epochs=1500)

dropout

dropout从零开始实现

要实现单层的dropout函数,我们必须从伯努利(二元)随机变量中提取与我们的层的维度一样多的样本,其中随机变量以概率 1 − p 1-p 1p取值 1 1 1(保持),以概率 p p p取值 0 0 0(丢弃)。实现这一点的一种简单方式是首先从均匀分布 U [ 0 , 1 ] U[0, 1] U[0,1]中抽取样本。那么我们可以保留那些对应样本大于 p p p的节点,把剩下的丢弃。

在下面的代码中,我们实现 dropout_layer 函数,该函数以dropout的概率丢弃张量输入X中的元素,如上所述重新缩放剩余部分:将剩余部分除以1.0-dropout

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.Tensor(X.shape).uniform_(0, 1) > dropout).float()
    return mask * X / (1.0 - dropout)

测试函数

X = torch.arange(16, dtype=torch.float32).reshape((2, 8))
print(X)
print(dropout_layer(X, 0.))
print(dropout_layer(X, 0.5))
print(dropout_layer(X, 1.))

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PWa1MXpO-1636467102474)(C:\Users\Lunatic\Desktop\深度学习实验报告\实验2\4.jpg)]

定义模型
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_hiddens1, 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)
训练与测试
num_epochs, 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_epochs, trainer)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0mWelZ7C-1636467102475)(C:\Users\Lunatic\Desktop\深度学习实验报告\实验2\5.jpg)]

dropout 简洁实现
net = nn.Sequential(nn.Flatten(), nn.Linear(784, 256), nn.ReLU(),
                    # 在第一个全连接层之后添加一个dropout层
                    nn.Dropout(dropout1), nn.Linear(256, 256), nn.ReLU(),
                    # 在第二个全连接层之后添加一个dropout层
                    nn.Dropout(dropout2), 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)

接下来进行测试

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5oVDsZnw-1636467102475)(C:\Users\Lunatic\Desktop\深度学习实验报告\实验2\6.jpg)]

四、实验结论

  1. 我们可以使用高级 API 更简洁地实现多层感知机;

  2. 对于相同的分类问题,多层感知机的实现与 softmax 回归的实现相同,只是多层感知机的实现里增加了带有激活函数的隐藏层;

  3. 由于不能基于训练误差来估计泛化误差,因此简单地最小化训练误差并不一定意味着泛化误差的减小,欠拟合是指模型无法继续减少训练误差。过拟合是指训练误差远小于验证误差。我们应该选择一个复杂度适当的模型,避免使用数量不足的训练样本;

  4. dropout将激活值ℎh替换为具有期望值ℎh的随机变量,除了控制权重向量的维数和大小之外,dropout也是避免过拟合的另一种工具。它们通常是联合使用的。

五、实验拓展

  1. 尝试添加更多的隐藏层,并查看它对结果有何影响。

    隐藏层的意义就是把输入数据的特征,抽象到另一个维度空间,来展现其更抽象化的特征,这些特征能更好的进行线性划分。

    增加隐藏层能有效提升分类效果,但隐藏层过多也可能导致分类过细,类别之间的区别和分类依据不明显。

  2. 为什么涉及多个超参数更具挑战性。

    由于深度学习的复杂性,单个超参数的影响是难以“定量”的,可能需要多个超参数的“共同努力”才能看到模型训练的效果得到显著改进。

  3. 如果你不对多项式特征 x i x_i xi进⾏标准化( 1 i ! \frac{1}{i!} i!1),会发⽣什么事情?你能⽤其他⽅法解决这个问题吗?

    不进行标准化可能导致特征指标因为不同指标量纲的影响,而无法进行相互的比较。

    若不进行标准化,在某些情况下可以用归一化代替标准化(使用如 f ( x ) = 2 π arctan ⁡ x f(x) = \frac{2}{\pi}\arctan x f(x)=π2arctanx 的函数将数值压缩到 ( 0 , 1 ) (0,1) (0,1) 范围内)

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值