动手学深度学习(二)基础知识-下

一、多层感知机

1、隐藏层

多层感知机在单层神经网络的基础上引入了一到多个隐藏层(hidden layer)隐藏层位于输入层和输出层之间。

由于输入层不涉及计算,下图的多层感知机的层数为2(隐藏层+输出层)

多层感知机中的隐藏层和输出层都是全连接层

单隐藏层的多层感知机的设计,将隐藏层的输出直接作为输出层的输入。其输出O可以由如下得到:

从联立后的式子可以看出,虽然神经网络引入了隐藏层,却依然等价于一个单层神经网络:其中输出层权重参数为WhWo,偏差参数为bhWo+bo。

2、激活函数

①为什么要引入激活函数

虽然神经网络引入了隐藏层,却依然等价于一个单层神经网络这个问题的根源在于:全连接层只是对数据做仿射变换(affine transformation),而多个仿射变换的叠加仍然是一个仿射变换,无法增加模型的非线性表达能力,因此隐藏层的存在并不会提高网络的表达能力。

②引入了激活函数进行非线性变换的好处

激活函数通过引入非线性变换,增强了网络的表达能力,使其可以处理复杂的非线性关从而解决线性变换无法解决的问题,带来了模型更强的拟合能力和表现力。

例子:

如果数据点分布在两个非线性可分的区域内,例如两个同心圆。单层神经网络或仅包含线性层的神经网络无法通过线性分割平面将这些数据分开。原因在于,单纯的线性变换无论如何都无法划分这样的非线性边界。

然而,如果引入非线性激活函数,例如 ReLU 或 Sigmoid,隐藏层可以通过多次非线性变换,逐渐将这些非线性可分的区域转化为线性可分的空间,从而使得网络能够正确分类。这种能力来自于非线性激活函数的引入,使得模型可以学习到更复杂的边界。

③几个激活函数

(1)ReLU函数

ReLU(rectified linear unit)函数提供了一个很简单的非线性变换。给定元素x,该函数定义为:

可以看出,ReLU函数只保留正数元素,并将负数元素清零

ReLU(x)

当输入为负数时,ReLU函数的导数为0;当输入为正数时,ReLU函数的导数为1。尽管输入为0时ReLU函数不可导,但是我们可以取此处的导数为0。 

求导
(2)sigmoid函数

sigmoid函数可以将元素的值变换到0和1之间,当输入接近0时,sigmoid函数接近线性变换。

sigmoid函数

依据链式法则,sigmoid函数的导数为:

 当输入为0时,sigmoid函数的导数达到最大值0.25。

(3) tanh函数

tanh(双曲正切)函数可以将元素的值变换到-1和1之间,当输入接近0时,tanh函数接近线性变换:

tanh函数

依据链式法则,tanh函数的导数为:

当输入为0时,tanh函数的导数达到最大值1;当输入越偏离0时,tanh函数的导数越接近0:

tahn导数

 3、多层感知机

多层感知机就是含有至少一个隐藏层的由全连接层组成的神经网络,且每个隐藏层的输出通过激活函数进行变换。

多层感知机的层数各隐藏层中隐藏单元个数都是超参数

其中ϕ表示激活函数。

4、用MLP实现多类分类

二、多层感知器的实现

模型的定义如下:

num_inputs, num_outputs, num_hiddens = 784, 10, 256
    
net = nn.Sequential(
        d2l.FlattenLayer(),
        nn.Linear(num_inputs, num_hiddens),
        nn.ReLU(),
        nn.Linear(num_hiddens, num_outputs), 
        )
    
for params in net.parameters():
    init.normal_(params, mean=0, std=0.01)

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

1、训练误差和泛化误差

  • 训练误差(training error):模型在训练数据集上表现出的误差。
  • 泛化误差(generalization error):模型在任意一个测试数据样本上表现出的误差的期望,并常常通过测试数据集上的误差来近似。

计算训练误差和泛化误差可以使用之前介绍过的损失函数,例如线性回归用到的平方损失函数和softmax回归用到的交叉熵损失函数。

  • 一般情况下,由训练数据集学到的模型参数会使模型在训练数据集上的表现优于或等于在测试数据集上的表现。由于无法从训练误差估计泛化误差,一味地降低训练误差并不意味着泛化误差一定会降低。机器学习模型应关注降低泛化误差。

2、模型选择

在机器学习中,通常需要评估若干候选模型的表现并从中选择模型。这一过程称为模型选择(model selection)。可供选择的候选模型可以是有着不同超参数的同类模型。以多层感知机为例,我们可以选择隐藏层的个数,以及每个隐藏层中隐藏单元个数和激活函数。

3、验证数据集

从严格意义上讲,测试集只能在所有超参数和模型参数选定后使用一次。不可以使用测试数据选择模型,如调参。然而在实际应用中,由于数据不容易获取,测试数据极少只使用一次就丢弃。

验证集(validation set):可以预留一部分在训练数据集和测试数据集以外的数据来进行模型选择。例如,我们可以从给定的训练集中随机选取一小部分作为验证集,而将剩余部分作为真正的训练集。

可以通过在验证数据集上做实验来进行模型选择。

4、 k折交叉验证

kk折交叉验证(kk-fold cross-validation):把原始训练数据集分割成k个不重合的子数据集,然后我们做k次模型训练和验证。每一次使用一个子数据集验证模型,并使用其他k−1个子数据集来训练模型。在这k次训练和验证中,每次用来验证模型的子数据集都不同。最后,我们对这k次训练误差和验证误差分别求平均

5、欠拟合和过拟合

①定义

  • 欠拟合(underfitting):模型无法得到较低的训练误差。
  • 过拟合(overfitting):模型的训练误差远小于它在测试数据集上的误差。

②影响因素

重点讨论两个:模型复杂度和训练集的大小。

(1)模型复杂度

以K阶多项式举例:

可以看出,高阶多项式函数模型参数更多,模型函数的选择空间更大,所以高阶多项式函数比低阶多项式函数的复杂度更高一阶多项式拟合也被称为线性拟合。

高阶多项式函数比低阶多项式函数更容易在相同的训练数据集上得到更低的训练误差。给定训练数据集,如果模型的复杂度过低,很容易出现欠拟合;如果模型复杂度过高,很容易出现过拟合

(2)训练集大小

一般来说,如果训练数据集中样本数过少,特别是比模型参数数量(按元素计)更少时,过拟合更容易发生。

四、权重衰减

1、解决的问题

应对过拟合问题的常用方法:权重衰减(weight decay)。

2、方法

权重衰减等价于L2范数正则化(regularization)。

正则化通过为模型损失函数添加惩罚项使学出的模型参数值较小,是应对过拟合的常用手段。

①L2范数正则化和L2范数惩罚项

  • L2范数正则化:在模型原损失函数基础上添加L2L2范数惩罚项。
  • L2范数惩罚项:模型权重参数每个元素的平方和与一个正的常数的乘积。

举个例子,线性回归的loss如下:

增加一个L2惩罚项后:

其中超参数λ>0。当λ较大时,惩罚项在损失函数中的比重较大,这通常会使学到的权重参数的元素较接近0

②L2正则化为什么叫做权重衰减

加了惩罚项以后参数更新过程如下:

L2L2范数正则化令权重先自乘小于1的数,再减去不含惩罚项的梯度。

训练集样本数目少过拟合

使用权重衰减。可以看出,训练误差虽然有所提高,但测试集上的误差有所下降。过拟合现象得到一定程度的缓解。 

使用权重衰减

③代码实现

optimizer_w = torch.optim.SGD(params=[net.weight], lr=lr, weight_decay=wd)

通过设置weight_decay=wd实现。 

  for _ in range(num_epochs):
        for X, y in train_iter:
            l = loss(net(X), y).mean()
            optimizer_w.zero_grad()
            optimizer_b.zero_grad()
            
            l.backward()
            
            # 对两个optimizer实例分别调用step函数,从而分别更新权重和偏差
            optimizer_w.step()
            optimizer_b.step()
        train_ls.append(loss(net(train_features), train_labels).mean().item())
        test_ls.append(loss(net(test_features), test_labels).mean().item())
    d2l.semilogy(range(1, num_epochs + 1), train_ls, 'epochs', 'loss',
                 range(1, num_epochs + 1), test_ls, ['train', 'test'])
    print('L2 norm of w:', net.weight.data.norm().item())

五、丢弃法

1、解决的问题

深度学习模型常常使用丢弃法(dropout)来应对过拟合问题。

2、倒置丢弃法(inverted dropout)

一个单隐藏层的多层感知机。其中输入个数为4, 隐藏单元个数为5,且隐藏单元h_i(i=1,…,5i=1,…,5)的计算表达式为:

这里ϕ是激活函数,x1,…,x4是输入,隐藏单元i的权重参数为w1i,…,w4i,偏差参数为bi。

  • 对该隐藏层进行丢弃,丢弃的对象是该层的隐藏单元,并且设丢弃该隐藏单元h_i的概率为p。丢弃概率p是丢弃法的超参数
  • 隐藏单元h_i有p的概率会被清零,有1−p的概率i会除以1−p做拉伸
  • 丢弃法只在训练模型时使用。
  • 使用丢弃法时我们计算新的隐藏单元h_i':

对下图中的隐藏层使用丢弃法,一种可能的结果如下所示,其中h2和h5被清零。这时输出值的计算不再依赖h2和h5,在反向传播时,与这两个隐藏单元相关的权重的梯度均为0。由于在训练中隐藏层神经元的丢弃是随机的,即h1,…,h5都有可能被清零,输出层的计算无法过度依赖h1,…,h5中的任一个,从而在训练模型时起到正则化的作用,并可以用来应对过拟合。

3、代码实现

dropout实现:

def dropout(X, drop_prob):
    X = X.float()
    assert 0 <= drop_prob <= 1
    keep_prob = 1 - drop_prob
    # 这种情况下把全部元素都丢弃
    if keep_prob == 0:
        return torch.zeros_like(X)
    mask = (torch.rand(X.shape) < keep_prob).float()
    
    return mask * X / keep_prob

网络定义:

net = nn.Sequential(
        d2l.FlattenLayer(),
        nn.Linear(num_inputs, num_hiddens1),
        nn.ReLU(),
        nn.Dropout(drop_prob1),
        nn.Linear(num_hiddens1, num_hiddens2), 
        nn.ReLU(),
        nn.Dropout(drop_prob2),
        nn.Linear(num_hiddens2, 10)
        )

for param in net.parameters():
    nn.init.normal_(param, mean=0, std=0.01)

六、正向传播、反向传播和计算图

1、正向传播

①定义

正向传播(forward propagation):对神经网络沿着从输入层到输出层的顺序,依次计算并存储模型的中间变量(包括输出)。

  • 线性层计算

  • 隐藏层使用激活函数进行非线性转换

  • 线性层计算

  • 计算损失和惩罚项

  • 目标函数 

②正向传播的计算图

2、反向传播

反向传播(back-propagation)指的是计算神经网络参数梯度的方法

总的来说,反向传播依据微积分中的链式法则,沿着从输出层到输入层的顺序,依次计算并存储目标函数有关神经网络各层的中间变量以及参数的梯度

3、正向传播和反向传播之间相互依赖

一方面,正向传播的计算可能依赖于模型参数的当前值,而这些模型参数是在反向传播的梯度计算后通过优化算法迭代的。

另一方面,反向传播的梯度计算可能依赖于各变量的当前值,而这些变量的当前值是通过正向传播计算得到的。

在模型参数初始化完成后,我们交替地进行正向传播和反向传播,并根据反向传播计算的梯度迭代模型参数。既然我们在反向传播中使用了正向传播中计算得到的中间变量来避免重复计算,那么这个复用也导致正向传播结束后不能立即释放中间变量内存。这也是训练要比预测占用更多内存的一个重要原因。

这些中间变量的个数大体上与网络层数线性相关每个变量的大小批量大小和输入个数也是线性相关的,它们是导致较深的神经网络使用较大批量训练时更容易超内存的主要原因。

七、数值稳定性和模型初始化

1、衰减和爆炸

衰减爆炸通常指的是在模型训练过程中,梯度或者权重的数值过小或过大,从而导致训练困难的现象。

在深度神经网络中,随着网络层数的增加,误差通过反向传播逐层传递回去更新权重。在这个过程中,梯度的值可能会快速变得非常小(梯度消失)或非常大(梯度爆炸)。

2、随机初始化模型参数

通常对神经网络的模型参数,特别是权重参数,进行随机初始化。
Xavier随机初始化(也称为Glorot初始化)是一种权重初始化方法,特别适用于激活函数的导数在较大区间内相对恒定的情况,如sigmoid或tanh激活函数。Xavier初始化的目的是保持在网络的每一层,输入和输出的方差大致相同,从而避免在训练过程中出现梯度消失或梯度爆炸的问题。

假设某全连接层的输入个数为a,输出个数为b,Xavier随机初始化将使该层中权重参数的每个元素都随机采样于均匀分布。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值