好的初始值会使得网络收敛到一个泛化能力高的局部最优解!
深度神经网络在机器学习中应用时面临两类主要问题:优化问题和泛化问题。
优化问题:深度神经网络的优化具有挑战性。
泛化问题:由于深度神经网络的复杂度较高且具有强大的拟合能力,很容易在训练集上产生过拟合现象。因此,在训练深度神经网络时需要采用一定的正则化方法来提高网络的泛化能力。
目前,研究人员通过大量实践总结了一些经验方法,以在神经网络的表示能力、复杂度、学习效率和泛化能力之间取得良好的平衡,从而得到良好的网络模型。本系列文章将从网络优化和网络正则化两个方面来介绍如下方法:
本文将介绍基于自适应学习率的优化算法:Adam算法详解(Adam≈梯度方向优化Momentum+自适应学习率RMSprop)
本系列实验使用了PyTorch深度学习框架,相关操作如下:
conda create -n DL python=3.7
conda activate DL
pip install torch==1.8.1+cu102 torchvision==0.9.1+cu102 torchaudio==0.8.1 -f https://download.pytorch.org/whl/torch_stable.html
conda install matplotlib
conda install scikit-learn
软件包 | 本实验版本 | 目前最新版 |
---|---|---|
matplotlib | 3.5.3 | 3.8.0 |
numpy | 1.21.6 | 1.26.0 |
python | 3.7.16 | |
scikit-learn | 0.22.1 | 1.3.0 |
torch | 1.8.1+cu102 | 2.0.1 |
torchaudio | 0.8.1 | 2.0.2 |
torchvision | 0.9.1+cu102 | 0.15.2 |
神经网络的参数学习是一个非凸优化问题.当使用梯度下降法来进行优化网络参数时,参数初始值的选取十分关键,关系到网络的优化效率和泛化能力.参数初始化的方式通常有以下三种:
from torch import nn
随机梯度下降(Stochastic Gradient Descent,SGD)是一种常用的优化算法,用于训练深度神经网络。在每次迭代中,SGD通过随机均匀采样一个数据样本的索引,并计算该样本的梯度来更新网络参数。具体而言,SGD的更新步骤如下:
Pytorch官方教程
optimizer = torch.optim.SGD(model.parameters(), lr=0.2)
【深度学习实验】前馈神经网络(final):自定义鸢尾花分类前馈神经网络模型并进行训练及评价
传统的SGD在某些情况下可能存在一些问题,例如学习率选择困难和梯度的不稳定性。为了改进这些问题,提出了一些随机梯度下降的改进方法,其中包括学习率的调整和梯度的优化。
【深度学习实验】网络优化与正则化(一):优化算法:使用动量优化的随机梯度下降算法(Stochastic Gradient Descent with Momentum)
【深度学习实验】网络优化与正则化(二):基于自适应学习率的优化算法详解:Adagrad、Adadelta、RMSprop
Adam算法(Adaptive Moment Estimation Algorithm)[Kingma et al., 2015]可以看作动量法和 RMSprop 算法的结合,不但使用动量作为参数更新方向,而且可以自适应调整学习率。
【深度学习实验】网络优化与正则化(三):随机梯度下降的改进——Adam算法详解(Adam≈梯度方向优化Momentum+自适应学习率RMSprop)
在神经网络中,参数学习是通过最小化损失函数来进行的,而这通常涉及到解决一个非凸优化问题。非凸优化问题的特点是存在多个局部最小值,而全局最小值不容易找到。梯度下降法是一种常用的优化算法,但容易陷入局部最小值。参数的初始值选择对训练的效果有显著影响,以下是常见的参数初始化方式:
预训练初始化(Pretraining Initialization):
随机初始化(Random Initialization):
固定值初始化(Fixed Value Initialization):
在实践中,通常结合使用不同的技术来初始化网络参数。此外,一些高级的初始化方法,如He初始化、Xavier初始化等,针对不同的激活函数和网络结构进行了优化,以提高训练的效果。选择合适的初始化方法往往需要根据具体的任务和网络结构进行实验和调整。
它假设参数的分布服从高斯分布(也称为正态分布),其中均值为0,方差为^2。通过从这个分布中随机采样,可以得到参数的初始值。高斯分布初始化在实践中表现良好,尤其适用于深度神经网络的参数初始化。
def init_gaussian(m, mean=0, std=0.01):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, mean=mean, std=std)
nn.init.zeros_(m.bias)
它假设参数的分布服从均匀分布,范围为[-, ],其中是根据方差2计算得到的适当的范围。通过从这个范围内均匀采样,可以得到参数的初始值。均匀分布初始化在某些情况下可能比高斯分布初始化更适用,例如在某些激活函数(如ReLU)和某些网络架构中。
def init_uniform(m, a=0, b=1):
if type(m) == nn.Linear:
nn.init.uniform_(m.weight, a=a, b=b)
nn.init.zeros_(m.bias)
基于方差缩放的参数初始化方法旨在根据神经网络的结构和激活函数的特性来选择合适的方差,以更好地初始化参数。两种常见的方差缩放初始化方法是Xavier初始化和He初始化。
Xavier初始化是一种广泛使用的参数初始化方法,适用于使用双曲正切(tanh)或S型(sigmoid)激活函数的神经网络。它的目标是使每个神经元的输出具有相同的方差。对于具有n个输入和m个输出的全连接层,Xavier初始化将参数从均值为0的高斯分布中随机采样,并使用方差^2 = 1/(n+m)进行缩放。对于具有ReLU激活函数的网络,Xavier初始化可能不是最佳选择。
def init_xavier(m):
if type(m) == nn.Linear:
nn.init.xavier_uniform_(m.weight)
nn.init.zeros_(m.bias)
He初始化是专为使用ReLU(修正线性单元)激活函数的神经网络设计的参数初始化方法。与Xavier初始化类似,He初始化也从均值为0的高斯分布中随机采样,但是使用方差^2 = 2/n进行缩放,其中n是输入的数量。He初始化通过适当增加方差来解决ReLU激活函数的梯度消失问题,从而更好地初始化参数。
def init_he(m):
if type(m) == nn.Linear:
nn.init.kaiming_uniform_(m.weight, nonlinearity='relu')
nn.init.zeros_(m.bias)
正交初始化旨在使神经网络的权重矩阵具有正交性。正交初始化的目标是减少权重之间的冗余性,促进梯度的有效传播,从而改善网络的训练效果。
在正交初始化中,权重矩阵被初始化为一个正交矩阵或其变体。一种常见的方法是使用QR分解或SVD分解来生成正交矩阵。具体步骤如下:
对于具有输入维度为n和输出维度为m的权重矩阵W,从均值为0、方差较小的高斯分布中随机初始化W。
对W进行QR分解或SVD分解,得到正交矩阵Q和对角矩阵D。
将Q作为初始化后的权重矩阵,即W = Q。
正交初始化的优点之一是它可以减少参数量,因为正交矩阵具有特殊的结构,其中元素之间存在较强的相关性。这对于具有大量参数的神经网络来说尤为重要,可以减少过拟合的风险,特别是在循环神经网络(RNN)和卷积神经网络(CNN)中。它有助于缓解梯度消失和梯度爆炸问题,促进梯度的传播,从而改善网络的训练稳定性和收敛速度。
def init_orthogonal(m):
if type(m) == nn.RNN:
for name, param in m.named_parameters():
if 'weight' in name:
nn.init.orthogonal_(param)
elif 'bias' in name:
nn.init.zeros_(param)
from torch import nn
class FeedForward(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(FeedForward, self).__init__()
self.fc1 = nn.Linear(input_size, hidden_size)
self.fc2 = nn.Linear(hidden_size, output_size)
self.act = nn.Sigmoid()
def forward(self, inputs):
outputs = self.fc1(inputs)
outputs = self.act(outputs)
outputs = self.fc2(outputs)
return outputs
def init_constant(m):
if type(m) == nn.Linear:
nn.init.constant_(m.weight, 1)
nn.init.zeros_(m.bias)
def init_normal(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, mean=0, std=0.01)
nn.init.zeros_(m.bias)
def init_gaussian(m, mean=0, std=0.01):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, mean=mean, std=std)
nn.init.zeros_(m.bias)
def init_uniform(m, a=0, b=1):
if type(m) == nn.Linear:
nn.init.uniform_(m.weight, a=a, b=b)
nn.init.zeros_(m.bias)
def init_xavier(m):
if type(m) == nn.Linear:
nn.init.xavier_uniform_(m.weight)
nn.init.zeros_(m.bias)
def init_he(m):
if type(m) == nn.Linear:
nn.init.kaiming_uniform_(m.weight, nonlinearity='relu')
nn.init.zeros_(m.bias)
def init_orthogonal(m):
if type(m) == nn.RNN:
for name, param in m.named_parameters():
if 'weight' in name:
nn.init.orthogonal_(param)
elif 'bias' in name:
nn.init.zeros_(param)
net = FeedForward(4, 6, 3)
net.apply(init_constant)
print(net.fc1.weight.data)
print(net.fc2.weight.data)