文章目录
4、多层感知机( MLP)
4.1、多层感知机
加入一个或多个隐藏层+激活函数来克服线性模型的限制, 使其能处理更普遍的函数关系类型,这种架构通常称为多层感知机(multilayer perceptron)。
输入层不涉及任何计算,因此使用此网络产生输出只需要实现隐藏层和输出层的计算。
4.1.1、隐层
通用近似定理
多层感知机可以通过隐藏神经元,捕捉到输入之间复杂的相互作用, 这些神经元依赖于每个输入的值。多层感知机是通用近似器, 即使是网络只有一个隐藏层,给定足够的神经元和正确的权重, 可以对任意函数建模。
通过使用更深(而不是更广)的网络,可以更容易地逼近许多函数。
4.1.2、激活函数 σ
激活函数(activation function)通过计算加权和并加上偏置来确定神经元是否应该被激活,换句话说,激活函数的目的是引入非线性变化。
常见激活函数
1)ReLU
R e L U ( x ) = m a x ( x , 0 ) ReLU(x) = max(x,0) ReLU(x)=max(x,0)
使用 ReLU 的原因是,它求导表现得特别好:要么让参数消失,要么让参数通过。 这使得优化表现得更好,并且ReLU减轻了困扰以往神经网络的梯度消失问题目前还不理解,为什么这样优化表现更好?
2)Sigmoid
s i g m o i d ( x ) = 1 1 + e − x sigmoid(x) = \frac{1}{1+e^{-x}} sigmoid(x)=1+e−x1
它将范围(-inf, inf)中的任意输入压缩到区间(0, 1)中的某个值。
3)tanh
t a n h ( x ) = 1 − e − 2 x 1 + e − 2 x tanh(x) = \frac{1-e^{-2x}}{1+e^{-2x}} tanh(x)=1+e−2x1−e−2x
将其输入压缩转换到区间(-1, 1)上。
为什么要引入非线性变换?
非线性变换比线性变换有更强的表达能力。可逼近任意复杂函数,更加贴合真实世界问题,现实世界中单调、线性是极少存在的。
softmax 函数与隐层激活函数的区别?
softmax 函数主要用于输出层,而不是隐藏层。隐藏层的激活函数通常是为了引入非线性,而 softmax 函数则是为了将得分映射为概率,用于多分类问题的输出。
什么是层数塌陷?
梯度消失。
4.2、从零实现多层感知机
(损失函数、优化算法 来自 torch)
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
#生成了一个服从标准正态分布(均值为0,方差为1)的随机张量 大小(num_inputs, num_hiddens),作为 w 初始值。
W1 = nn.Parameter(torch.randn(
num_inputs, num_hiddens, requires_grad=True) * 0.01)
b1 = nn.Parameter(torch.zeros(num_hiddens, requires_grad=True))
# 大小为(num_hiddens,)的零张量 ,作为 b 的初始值
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):
# 创建了一个与输入张量X具有相同形状的全零张量a
a = torch.zeros_like(X)
return torch.max(X, a)
定义模型
def net(X):
# -1 表示该维度将根据张量的大小自动计算, 如:784, reshape(-1,28) 会得到28*28
X = X.reshape((-1, num_inputs))
H = relu(X@W1 + b1) # 这里“@”代表矩阵乘法
return (H@W2 + b2)
损失函数
# reduction='none':表示不进行降维,张量的形状通常与输入的标签张量的形状相同。
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)
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、模型选择、欠拟合、过拟合
模型选择
DL 的核心是,设计一个大的模型,控制它的容量,尽可能地降低泛化误差。
过拟合&欠拟合
-
低容量配简单数据时,高容量模型配复杂数据时,拟合正常。
-
高容量模型数据少,容易过拟合;低容量模型数据复杂,易欠拟合。
更多的,模型容量和数据复杂度是直观感受,不断积累调参得来的感受。
4.5、权重衰退
权重衰减是最广泛使用的正则化的技术之一, 它通常也被称为 L 2 正则化 L_2正则化 L2正则化。
参数更新法则
4.6、丢失法|暂退法(Dropout)
Dropout 是一种常用的正则化技术,正则技术就是用于防止神经网络过拟合。
丢弃法将一些输出项随机置0来控制模型复杂度,常作用在多层感知机的隐藏层输出上,丢弃概率是控制模型复杂度的超参数。
无偏差的加入噪音
对 x x x加入噪音得到 x ′ x' x′,我们希望
E [ x ′ ] = x E[x'] = x E[x′]=x
丢弃法对每个元素进行如下扰动
x i ′ = { 0 with probability p x i 1 − p otherise x_i' = \begin{cases} 0& \text{with probability p} \\ \frac{x_i}{1-p} &\text{otherise} \end{cases} xi′={01−pxiwith probability potherise
丢弃法的实际使用
通常将丢弃法作用于隐层的输出上。
h = σ ( W 1 x + b 1 ) h = σ(W_1x + b_1) h=σ(W1x+b1)
h ′ = d r o p o u t ( h ) h' = dropout(h) h′=dropout(h)
o = W 2 h ′ + b 2 o = W_2h' + b_2 o=W2h′+b2
y = s o f t m a x ( o ) y = softmax(o) y=softmax(o)
4.6.1、dropout 函数实现
import torch
from torch import nn
from d2l import torch as d2l
def dropout_layer(X,dropout):
#dropout只有在合理范围内,断言允许继续执行
assert 0 <= dropout <= 1
# 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)
4.6.2、简洁实现
在每个全连接层之后添加一个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)
4.7、数值稳定性
参数初始化重要性:影响梯度和参数本身的稳定性
梯度计算的是矩阵与梯度向量的乘积,最初矩阵可能具有各种各样的特征值,他们的乘积可能非常大也可能非常小。
不稳定梯度带来的风险不止在于数值表示; 不稳定梯度也威胁到我们优化算法的稳定性。 梯度爆炸,参数更新过大,破坏模型稳定收敛;梯度消失,参数更新过小,模型无法学习。
参数对称性
神经网络设计中的另一个问题是其参数化所固有的对称性。 假设我们有一个简单的多层感知机,它有一个隐藏层和两个隐藏单元。 在这种情况下,我们可以对第一层的权重 W ( 1 ) W^{(1)} W(1)进行重排列, 并且同样对输出层的权重进行重排列,可以获得相同的函数。 第一个隐藏单元与第二个隐藏单元没有什么特别的区别。 换句话说,我们在每一层的隐藏单元之间具有排列对称性。
如何让训练更加稳定?
让梯度值在一个合理的范围。
-
将乘法变加法【ResNet,LSTM】
-
归一化【梯度归一化,梯度裁剪】
-
合理的权重初始和激活函数
-
让每层的方差是一个常数