书籍链接:动手学深度学习
笔记是从第四章开始,前面三章为基础知道,有需要的可以自己去看看
关于本系列笔记: 书里为了让读者更好的理解,有大篇幅的描述性的文字,内容很多,笔记只保留主要内容,同时也是对之前知识的查漏补缺
《动手学深度学习》学习笔记 第4章 多层感知机
《动手学深度学习》学习笔记 第5章 深度学习计算
《动手学深度学习》学习笔记 第6章 卷积神经网络
《动手学深度学习》学习笔记 第7章 现代卷积神经网络
??最简单的深度网络称为多层感知机。多层感知机由多层神经元组成,每一层与它的上一层相连,从中接收输入;同时每一层也与它的下一层相连,影响当前层的神经元。
??仿射变换,它是一种带有偏置项的线性变换。但是,仿射变换中的线性是一个很强的假设。
??例如,线性意味着单调假设:任何特征的增大都会导致模型输出的增大(如果对应的权重为正),或者导致模
型输出的减小(如果对应的权重为负)。
有时这是有道理的。例如,如果我们试图预测一个人是否会偿还贷款。
我们可以认为,在其他条件不变的情况下,收入较高的申请人比收入较低的申请人更有可能偿还贷款。
但它们不是线性相关的。收入从0增加到5万,可能比从100万增加到105万 带来更大的还款可能性。
??对于深度神经网络,我们使用观测数据来联合学习隐藏层表示和应用于该表示的线性预测器。
??可以通过在网络中加入一个或多个隐藏层来克服线性模型的限制,下面,以图的方式描述了多层感知机(multilayerperceptron),通常缩写为MLP。
??这个多层感知机有4个输入,3个输出,其隐藏层包含5个隐藏单元。
??具有全连接层的多层感知机的参数开销可能会高得令人望而却步。即使在不改变输入或输出大小的情况下,可能在参数节约和模型有效性之间进行权衡
对于具有 h h h个隐藏单元的单隐藏层多层感知机:
??在数学或代码中, H H H也被称为隐藏层变量(hidden‐layer variable)或隐藏变量(hidden variable)。
因为隐藏层和输出层都是全连接的,所以我们有:
形式上,我们按如下方式计算单隐藏层多层感知机的输出 O ∈ R n × q O ∈ R^{n×q} O∈Rn×q:
H = X W ( 1 ) + b ( 1 ) , O = H W ( 2 ) + b ( 2 ) . H = XW^{(1)} + b^{(1)}, O = HW^{(2)} + b^{(2)}. H=XW(1)+b(1),O=HW(2)+b(2).
注意在添加隐藏层之后,模型现在需要跟踪和更新额外的参数。
但上面的隐藏单元由输入的仿射函数给出,而输出(softmax操作前)只是隐藏单元的仿射函数。
仿射函数的仿射函数本身就是仿射函数,但是我们之前的线性模型已经能够表示任何仿射函数。
总之,目前除了增加额外的参数,性能没有任何提升(相较于线性模型)
??还需要一个额外的关键要素:在仿射变换之后对每个隐藏单元应用非线性的激活函数(activation function)
σ
σ
σ。激活函数的输出(例如,
σ
(
?
)
σ(·)
σ(?))被称为活性值(activations)。
一般来说,有了激活函数,就不可能再将我们的多层感知机退化成线性模型:
??虽然一个单隐层网络能学习任何函数,但并不意味着我们应该尝试使用单隐藏层网络来解决所有问题。事实上,通过使用更深(而不是更广)的网络,我们可以更容易地逼近许多函数。
??激活函数(activation function)通过计算加权和并加上偏置来确定神经元是否应该被激活??,它们将输入信号转换为输出的可微运算。
??大多数激活函数都是非线性的。
R
e
L
U
(
x
)
=
m
a
x
(
x
,
0
)
.
ReLU(x) = max(x, 0).
ReLU(x)=max(x,0).
ReLU函数的导数。
??使用ReLU的原因是,它求导表现得特别好:**要么让参数消失,要么让参数通过。**这使得优化表现得更好,并
且ReLU减轻了困扰以往神经网络的梯度消失问题。
??ReLU函数有许多变体,包括参数化ReLU(Parameterized ReLU,pReLU)函数 。该变体为ReLU添加了一个线性项,因此即使参数是负的,某些信息仍然可以通过:
p
R
e
L
U
(
x
)
=
m
a
x
(
0
,
x
)
+
α
m
i
n
(
0
,
x
)
.
pReLU(x) = max(0, x) + α min(0, x).
pReLU(x)=max(0,x)+αmin(0,x).
??sigmoid通常称为挤压函数(squashing function):它将范围(‐inf, inf)中的任意输入压缩到区间(0, 1)中的某个值:
s
i
g
m
o
i
d
(
x
)
=
1
1
+
e
x
p
(
?
x
)
sigmoid(x) = \frac{1}{1 + exp(?x)}
sigmoid(x)=1+exp(?x)1?
??sigmoid函数是一个自然的选择,因为它是一个平滑的、可微的阈值单元近似。
??sigmoid函数的导数图像如下所示。
??然而,sigmoid在隐藏层中已经较少使用,它在大部分时候被更简单、更容易训练的ReLU所取代。
??与sigmoid函数类似,tanh(双曲正切)函数也能将其输入压缩转换到区间(‐1, 1)上。tanh函数的公式如下:
t
a
n
h
(
x
)
=
1
?
e
x
p
(
?
2
x
)
1
+
e
x
p
(
?
2
x
)
.
tanh(x) = \frac{ 1 ? exp(?2x)}{1 + exp(?2x)}.
tanh(x)=1+exp(?2x)1?exp(?2x)?.
??注意,当输入在0附近时,tanh函数接近线性变换。函数的形状类似于sigmoid函数,不同的是tanh函数关于坐标系原点中心对称。
tanh函数的导数图像如下所示:
??使用Fashion‐MNIST图像分类数据集:
??Fashion‐MNIST中的每个图像由 28 × 28 = 784个灰度像素值组成。
所有图像共分为10个类别。
??忽略像素之间的空间结构,我们可以将每个图像视为具有784个输入特征和10个类的简单分类数据集。
??首先,我们将实现一个具有单隐藏层的多层感知机,它包含256个隐藏单元。
import torch
from torch import nn
from d2l import torch as d2l
# =========激活函数=========
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')
包含256个隐藏单元
#=========读取数据集============
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
# =======超参数: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]
#=======训练=======
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)
import torch
from torch import nn
from d2l import torch as d2l
#torch.nn.Sequential类来实现简单的顺序连接模型。这个模型也是继承自Module类
net = nn.Sequential(nn.Flatten(),
nn.Linear(784, 256),
nn.ReLU(),
nn.Linear(256, 10))
def init_weights(m):
if type(m) == nn.Linear:
# 给tensor初始化,一般是给网络中参数weight初始化,初始化参数值符合正态分布。
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)
( 我们永远不能准确地计算出泛化误差。这是因为无限多的数据样本是一个虚构的对象。在实际中,我们只能通过将模型应用于一个独立的测试集来估计泛化误差,该测试集由随机选取的、未曾在训练集中出现的数据样本构成。)
??独立同分布假设(i.i.d. assumption):们假设训练数据和测试数据都是从相同的分布中独立提取的
这意味着对数据进行采样的过程没有进行“记忆”。换句话说,抽取的第2个样本和第3个样本的相关性,并不比抽取的第2个样本和第200万个样本的相关性更强。
模型复杂性由什么构成是一个复杂的问题。一个模型是否能很好地泛化取决于很多因素。
我们很难比较本质上不同大类的模型之间(例如,决策树与神经网络)的复杂性。
就目前而言,一条简单的经验法则相当有用:统计学家认为,能够轻松解释任意事实的模型是复杂的,而表达能力有限但仍能很好地解释数据的模型可能更有现实用途。
在哲学上,这与波普尔的科学理论的可证伪性标准密切相关:**如果一个理论能拟合数据,且有具体的测试可以用来证明它是错误的,那么它就是好的。**这一点很重要,因为所有的统计估计都是事后归纳。也就是说,我们在观察事实之后进行估计,因此容易受到相关谬误的影响。
??验证集: 是将数据分成三份,除了训练和测试数据集之外,还增加一个验证数据集(val‐idation dataset),也叫验证集(validation set)。
??K折交叉验证: 当训练数据稀缺时,我们甚至可能无法提供足够的数据来构成一个合适的验证集。这个问题的一个流行的解决方案是采用K折交叉验证。
图4.4.1: 模型复杂度对欠拟合和过拟合的影响
import math
import numpy as np
import torch
from torch import nn
from d2l import torch as d2l
#=================生成数据集========================
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)
# 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]
(tensor([[ 1.6580],[-1.6392]]),
tensor([[ 1.0000e+00, 1.6580e+00, 1.3745e+00, 7.5967e-01, 3.1489e-01,
1.0442e-01, 2.8855e-02, 6.8346e-03, 1.4165e-03, 2.6096e-04,
4.3267e-05, 6.5217e-06, 9.0110e-07, 1.1493e-07, 1.3611e-08,
1.5045e-09, 1.5590e-10, 1.5206e-11, 1.4006e-12, 1.2223e-13],
[ 1.0000e+00, -1.6392e+00, 1.3435e+00, -7.3408e-01, 3.0082e-01,
-9.8622e-02, 2.6944e-02, -6.3094e-03, 1.2928e-03, -2.3546e-04,
3.8597e-05, -5.7516e-06, 7.8567e-07, -9.9066e-08, 1.1599e-08,
-1.2676e-09, 1.2986e-10, -1.2522e-11, 1.1403e-12, -9.8378e-14]]),
tensor([ 6.6262, -5.4505]))
'''
首先让我们实现一个函数来评估模型在给定数据集上的损失。
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(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.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())
三阶多项式函数拟合(正常)
# 从多项式特征中选择前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)
小结:
本节我们将介绍一些正则化模型的技术
我们总是可以通过去收集更多的训练数据来缓解过拟合。但这可能成本很高,耗时颇多,或者完全超出我们的控制,因而在短期内不可能做到。假设我们已经拥有尽可能多的高质量数据,我们便可以将重点放在正则化技术上。
权重衰减(weight decay) 是最广泛使用的正则化的技术之一,它通常也被称为L2正则化。
这项技术通过函数与零的距离来衡量函数的复杂度,因为在所有函数f中,函数f =0(所有输入都得到值0)在某种意义上是最简单的。但是我们应该如何精确地测量一个函数和零之间的距离呢?没有一个正确的答案。事实上,函数分析和巴拿赫空间理论的研究,都在致力于回答这个问题。
一种简单的方法是通过线性函数
f
(
x
)
=
w
?
x
f(x) = w^?x
f(x)=w?x 中的权重向量的某个范数来度量其复杂性,例
∣
∣
w
∥
2
||w∥^2
∣∣w∥2。要保证权重向量比较小,最常用方法是将其范数作为惩罚项加到最小化损失的问题中。将原来的训练目标最小化训练标签上的预测损失,调整为最小化预测损失和惩罚项之和。
具体来说:
损失函数为
L
(
W
,
b
)
=
1
n
∑
i
=
1
n
1
2
(
w
?
x
(
i
)
+
b
?
y
(
i
)
)
2
L(W,b) = \frac{1}{n}\sum_{i=1}^{n} {\frac{1}{2}}(w^?x^{(i)}+ b ? y^{(i)})^2
L(W,b)=n1?i=1∑n?21?(w?x(i)+b?y(i))2
其中:
为了惩罚权重向量的大小,必须以某种方式在损失函数中添加 ∣ ∣ w ∥ 2 ||w∥^2 ∣∣w∥2.
通过正则化常数λ来描述这种权衡,这是一个非负超参数,使用验证数据拟合:
L
(
W
,
b
)
+
λ
2
∣
∣
w
∥
2
L(W,b) + \frac{λ}{2}||w∥^2
L(W,b)+2λ?∣∣w∥2
为什么在这里我们使用平方范数而不是标准范数(即欧几里得距离)?
我们这样做是为了便于计算(处处可导)。
通过平方L2范数,我们去掉平方根,留下权重向量每个分量的平方和。这使得惩罚的导数很容易计算:导数的和等于和的导数。
为什么我们使用L2范数,而不是L1范数?
使用L2范数的一个原因是它对权重向量的大分量施加了巨大的惩罚。这使得我们的学习算法偏向于在大量特征上均匀分布权重的模型。在实践中,这可能使它们对单个变量中的观测误差更为稳定。相比之下,L1惩罚会导致模型将权重集中在一小部分特征上,而将其他权重清除为零。
为什么称为权重衰减?
根据估计值与观测值之间的差异来更新w。同时也在试图将w的大小缩小到零。
与特征选择相比,权重衰减提供了一种连续的机制来调整函数的复杂度。较小的λ值对应较少约束的w,而较大的λ值对w的约束更大。
代码实现:
trainer = torch.optim.SGD([
{"params":net[0].weight,'weight_decay': wd},
{"params":net[0].bias}], lr=lr)
们在实例化优化器时直接通过weight_decay指定weight decay超参数。默认情况下,Py‐Torch同时衰减权重和偏移。上述代码只为权重设置了weight_decay,所以偏置参数b不会衰减。
小结
线性模型:
泛化性和灵活性之间的这种基本权衡被描述为偏差-方差权衡(bias‐variance tradeoff)。
与线性模型不同,神经网络并不局限于单独查看每个特征,而是学习特征之间的交互。
深度网络的泛化性质令人费解,而这种泛化性质的数学基础仍然是悬而未决的研究问题。
暂退法(dropout):训练过程中,在计算后续层之前向网络的每一层注入噪声。因为当训练一个有多层的深层网络时,注入噪声只会在输入‐输出映射上增强平滑性。
下面是关于这个方法的来源,有助于理解:
- 经典泛化理论认为,为了缩小训练和测试性能之间的差距,应该以简单的模型为目标。
- 简单性的另一个角度是平滑性,即函数不应该对其输入的微小变化敏感。(例如,当我们对图像进行分类时,我们预计向像素添加一些随机噪声应该是基本无影响的。)
- 1995年,克里斯托弗·毕晓普证明了具有输入噪声的训练等价于Tikhonov正则化 (Bishop, 1995)。
- 然后在2014年,斯里瓦斯塔瓦等人 (Srivastava et al., 2014) 就如何将毕晓普的想法应用于网络的内部层提出了一个想法:在训练过程中,他们建议在计算后续层之前向网络的每一层注入噪声。因为当训练一个有多层的深层网络时,注入噪声只会在输入‐输出映射上增强平滑性。
当我们将暂退法应用到隐藏层,以p的概率将隐藏单元置为零时,结果可以看作一个只包含原始神经元子集的网络。
通常,我们在测试时不用暂退法。
给定一个训练好的模型和一个新的样本,我们不会丢弃任何节点,因此不需要标准化。
然而也有一些例外:一些研究人员在测试时使用暂退法,用于估计神经网络预测的“不确定性”:
如果通过许多不同的暂退法遮盖后得到的预测结果都是一致的,那么我们可以说网络发挥更稳定。
定义 dropout_layer 函数:该函数以dropout的概率丢弃张量输入X中的元素,如上所述重新缩放剩余部分:将剩余部分除以1.0-dropout。
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)
使用dropout_layer 函数
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)
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);
小结
为了简单起见,我们假设
上述简单网络相对应的计算图:
反向传播(backward propagation或backpropagation)指的是计算神经网络参数梯度的方法。(简言之,该方
法根据微积分中的链式规则,按相反的顺序从输出层到输入层遍历网络。)
应用链式法则,依次计算每个中间变量和参数的梯度。计算的顺序与前向传播中执行的顺序相反,因为我们需要从计算图的结果开始,并朝着参数的方向努力。
在计算上述梯度的过程中还需要其他梯度的计算,可以根据计算图推倒出来:
文中计算过程如下(书
P
164
P_{164}
P164?):
在训练神经网络时,前向传播和反向传播相互依赖。
以上述简单网络为例:
另一方面,反向传播期间参数 (4.7.11)的梯度计算,取决于由前向传播给出的隐藏变量h的当前值。
小结
初始化方案的选择在神经网络学习中起着举足轻重的作用,它对保持数值稳定性至关重要。
此外,这些初始化方案的选择可以与非线性激活函数的选择有趣的结合在一起。
我们选择哪个函数以及如何初始化参数可以决定优化算法收敛的速度有多快。糟糕选择可能会导致我们在训练时遇到梯度爆炸或梯度消失。
考虑一个具有
L
L
L层、输入
x
x
x和输出
o
o
o的深层网络。每一层l由变换
f
l
f_l
fl?定义,该变换的参数为权重
W
(
l
)
W^{(l)}
W(l),其隐藏变量是
h
(
l
)
(令
h
(
0
)
=
x
)
h^{(l)}(令 h^{(0)} = x)
h(l)(令h(0)=x)。网络可以表示为:
h
(
l
)
=
f
l
(
h
(
l
?
1
)
)
因此
o
=
f
L
?
.
.
.
?
f
1
(
x
)
.
h^{(l)} = f_l(h^{(l-1)}) 因此o = f_L ? . . . ? f_1(x).
h(l)=fl?(h(l?1))因此o=fL??...?f1?(x).
如果所有隐藏变量和输入都是向量,我们可以将
o
o
o关于任何一组参数
W
(
l
)
W^{(l)}
W(l)的梯度写为下式:
换言之,该梯度是
L
?
l
L ? l
L?l个矩阵
M
(
L
)
?
.
.
.
?
M
(
l
+
1
)
M^{(L)}· . . . · M^{(l+1)}
M(L)?...?M(l+1)与梯度向量
v
(
l
)
v^{(l)}
v(l)的乘积。
梯度消失
由于早期的人工神经网络受到生物神经网络的启发,神经元要么完全激活要么完全不激活(就像生物神经元)的想法很有吸引力。所以,sigmoid函数
1
/
(
1
+
e
x
p
(
?
x
)
)
1/(1 + exp(?x))
1/(1+exp(?x))很流行,因为它类似于阈值函数。然而,它却是导致梯度消失问题的一个常见的原因,
正如上图,当
s
i
g
m
o
i
d
sigmoid
sigmoid函数的输入很大或是很小时,它的梯度都会消失。因此,更稳定的
R
e
L
U
ReLU
ReLU系列函数已经成为从业者的默认选择(虽然在神经科学的角度看起来不太合理)。
梯度爆炸
相反,梯度爆炸可能同样令人烦恼。矩阵乘积发生爆炸。当这种情况是由于深度网络的初始化所导致时,我们没有机会让梯度下降优化器收敛。
打破对称性
神经网络设计中的另一个问题是其参数化所固有的对称性。假设我们有一个简单的多层感知机,它有一个隐
藏层和两个隐藏单元。在这种情况下,我们可以对第一层的权重W(1)进行重排列,并且同样对输出层的权重
进行重排列,可以获得相同的函数。第一个隐藏单元与第二个隐藏单元没有什么特别的区别。换句话说,我
们在每一层的隐藏单元之间具有排列对称性。
解决(或至少减轻)上述问题的一种方法是进行参数初始化,优化期间的注意和适当的正则化也可以进一步
提高稳定性。
默认初始化
可以使用正态分布来初始化权重值。如果我们不指定初始化方法,框架将使用默认的随机初始化方法,对于中等难度的问题,这种方法通常很有效。
Xavier初始化
考虑一个简单的全连接层,该层接受
n
i
n
n_{in}
nin?个输入并产生
n
o
u
t
n_{out}
nout?个输出。每个输出
o
i
o_i
oi?可以表示为:
o
i
=
∑
j
=
1
n
i
n
w
i
j
x
j
o_i = \sum_{j=1}^{n_{in}} w_{ij}x_j
oi?=j=1∑nin??wij?xj?
权重 w i j w_{ij} wij?都是从同一分布中独立抽取的。
此外,让我们假设该分布具有零均值和方差 σ 2 σ^2 σ2。请注意,这并不意味着分布必须是高斯的,只是均值和方差需要存在。
现在,让我们假设层 x j x_j xj?的输入也具有零均值和方差 γ 2 γ^2 γ2,并且它们独立于 w i j w_{ij} wij?并且彼此独立。
在这种情况下,我们可以按如下方式计算
o
i
o_i
oi?的平均值和方差(忽略偏执和激活函数):
Xavier 初始化试图使得每一层的输出的方差接近于其输入的方差。具体地,它设置权重
w
i
j
w_{ij}
wij?的初始方差为:
1
2
(
n
i
n
+
n
o
u
t
)
σ
2
=
1
\frac{1}{2}(n_{in} + n_{out})σ2 = 1
21?(nin?+nout?)σ2=1
这样,无论 和 的大小如何,这一层的输出方差都接近于其输入方差。
作用: