任何数学技巧都不能弥补信息的缺失。
——科尼利厄斯·兰佐斯(Cornelius Lanczos)匈牙利数学家、物理学家
深度神经网络在机器学习中应用时面临两类主要问题:优化问题和泛化问题。
优化问题:深度神经网络的优化具有挑战性。
泛化问题:由于深度神经网络的复杂度较高且具有强大的拟合能力,很容易在训练集上产生过拟合现象。因此,在训练深度神经网络时需要采用一定的正则化方法来提高网络的泛化能力。
目前,研究人员通过大量实践总结了一些经验方法,以在神经网络的表示能力、复杂度、学习效率和泛化能力之间取得良好的平衡,从而得到良好的网络模型。本系列文章将从网络优化和网络正则化两个方面来介绍如下方法:
本文将介绍基于自适应学习率的优化算法:Adagrad、Adadelta、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 |
import torch
import torch.nn.functional as F
from d2l import torch as d2l
from sklearn.datasets import load_iris
from torch.utils.data import Dataset, DataLoader
随机梯度下降(Stochastic Gradient Descent,SGD)是一种常用的优化算法,用于训练深度神经网络。在每次迭代中,SGD通过随机均匀采样一个数据样本的索引,并计算该样本的梯度来更新网络参数。具体而言,SGD的更新步骤如下:
Pytorch官方教程
optimizer = torch.optim.SGD(model.parameters(), lr=0.2)
【深度学习实验】前馈神经网络(final):自定义鸢尾花分类前馈神经网络模型并进行训练及评价
传统的SGD在某些情况下可能存在一些问题,例如学习率选择困难和梯度的不稳定性。为了改进这些问题,提出了一些随机梯度下降的改进方法,其中包括学习率的调整和梯度的优化。
【深度学习实验】网络优化与正则化(一):优化算法:使用动量优化的随机梯度下降算法(Stochastic Gradient Descent with Momentum)
Adagrad(Adaptive Gradient Algorithm)算法会为每个参数维护一个学习率,该学习率随着时间的推移会逐渐减小。它适用于稀疏数据集,能够有效地处理出现较少的特征。
def init_adagrad_states(feature_dim):
s_w = torch.zeros((feature_dim, 3))
s_b = torch.zeros(3)
return (s_w, s_b)
def adagrad(params, states, hyperparams):
eps = 1e-6
for p, s in zip(params, states):
with torch.no_grad():
s[:] += torch.square(p.grad)
p[:] -= hyperparams['lr'] * p.grad / torch.sqrt(s + eps)
p.grad.data.zero_()
init_adagrad_states
函数用于初始化Adagrad算法中的状态。
s_w
和 s_b
,分别用于保存权重参数和偏置参数的平方梯度累积和。这些状态张量的形状与对应的参数张量相同。adagrad
函数使用Adagrad算法来更新模型的参数。
params
表示模型的参数张量列表,states
表示Adagrad算法的状态张量列表,hyperparams
表示超参数字典,其中包含学习率 lr
。eps
,用于避免除零错误。p
和对应的状态张量 s
,算法执行以下操作:
s
中。p
。这里使用了累积的平方梯度来调整学习率的大小,以更好地适应不同参数的更新需求。p.grad.data.zero_()
将参数梯度置零,以便下一次迭代时重新计算梯度。Adadelta算法是Adagrad的改进版本,通过限制累积梯度的历史信息,解决了Adagrad学习率递减过快的问题。它对学习率的调整更加平滑,适合于长期训练的模型。
def init_adadelta_states(feature_dim):
s_w = torch.zeros((feature_dim, 3))
s_b = torch.zeros(3)
delta_w = torch.zeros((feature_dim, 3))
delta_b = torch.zeros(3)
return (s_w, s_b, delta_w, delta_b)
def adadelta(params, states, hyperparams):
rho, eps = hyperparams['rho'], 1e-6
for p, s, delta in zip(params, states[:2], states[2:]):
with torch.no_grad():
s[:] = rho * s + (1 - rho) * torch.square(p.grad)
update = (torch.sqrt(delta + eps) / torch.sqrt(s + eps)) * p.grad
p[:] -= update
delta[:] = rho * delta + (1 - rho) * torch.square(update)
p.grad.data.zero_()
init_adadelta_states
函数用于初始化Adadelta算法的状态。
s_w
、s_b
、delta_w
和 delta_b
,分别用于保存权重参数和偏置参数的梯度平方累积和以及参数更新的累积平方梯度。这些状态张量的形状与对应的参数张量相同。adadelta
函数使用Adadelta算法来更新模型的参数。
params
表示模型的参数张量列表,states
表示Adadelta算法的状态张量列表,hyperparams
表示超参数字典,其中包含衰减率 rho
。rho
表示衰减率,用于平衡历史梯度和当前梯度的贡献,eps
用于避免除零错误。p
和对应的状态张量 s
、delta
,算法执行以下操作:
rho
更新状态张量 s
:使用历史梯度和当前梯度的加权平均,以平衡参数更新的速度。update
:使用参数更新的累积平方梯度来调整更新的幅度。update
更新参数 p
:根据调整后的学习率大小来更新参数。rho
更新累积平方梯度 delta
。p.grad.data.zero_()
将参数梯度置零,以便下一次迭代时重新计算梯度。RMSprop(Root Mean Square Propagation)算法是一种针对Adagrad算法的改进方法,通过引入衰减系数来平衡历史梯度和当前梯度的贡献。它能够更好地适应不同参数的变化情况,对于非稀疏数据集表现较好。
def init_rmsprop_states(feature_dim):
s_w = torch.zeros((feature_dim, 3))
s_b = torch.zeros(3)
return (s_w, s_b)
def rmsprop(params, states, hyperparams):
gamma, eps = hyperparams['gamma'], 1e-6
for p, s in zip(params, states):
with torch.no_grad():
s[:] = gamma * s + (1 - gamma) * torch.square(p.grad)
p[:] -= hyperparams['lr'] * p.grad / torch.sqrt(s + eps)
p.grad.data.zero_()
init_rmsprop_states
函数用于初始化RMSprop算法中的状态。
s_w
和 s_b
,分别用于保存权重参数和偏置参数的梯度平方累积和。这些状态张量的形状与对应的参数张量相同。rmsprop
函数使用RMSprop算法来更新模型的参数。
params
表示模型的参数张量列表,states
表示RMSprop算法的状态张量列表,hyperparams
表示超参数字典,其中包含学习率 lr
和衰减率 gamma
。gamma
表示衰减率,用于平衡历史梯度和当前梯度的贡献,eps
用于避免除零错误。p
和对应的状态张量 s
,算法执行以下操作:
torch.square(p.grad)
计算参数梯度的平方。gamma
更新状态张量 s
:使用了历史梯度和当前梯度的加权平均,以平衡参数更新的速度。p
:使用了累积的梯度平方来调整学习率的大小,以更好地适应不同参数的更新需求。p.grad.data.zero_()
将参数梯度置零,以便下一次迭代时重新计算梯度。batch_size = 24
# 构建训练集
train_dataset = IrisDataset(mode='train')
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
lr = 0.02
train(adagrad, init_adagrad_states(4), {'lr': lr}, train_loader, 4)
# train(rmsprop, init_rmsprop_states(4), {'lr': lr, 'gamma': 0.9}, train_loader, 4)
IrisDataset类:
train函数:
import torch
from torch import nn
import torch.nn.functional as F
from d2l import torch as d2l
from sklearn.datasets import load_iris
from torch.utils.data import Dataset, DataLoader
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 evaluate_loss(net, data_iter, loss):
"""评估给定数据集上模型的损失
Defined in :numref:`sec_model_selection`"""
metric = d2l.Accumulator(2) # 损失的总和,样本数量
for X, y in data_iter:
X = X.to(torch.float32)
out = net(X)
# y = d2l.reshape(y, out.shape)
l = loss(out, y.long())
metric.add(d2l.reduce_sum(l), d2l.size(l))
return metric[0] / metric[1]
def train(trainer_fn, states, hyperparams, data_iter, feature_dim, num_epochs=2):
"""Defined in :numref:`sec_minibatches`"""
# 初始化模型
w = torch.normal(mean=0.0, std=0.01, size=(feature_dim, 3),
requires_grad=True)
b = torch.zeros((3), requires_grad=True)
# 训练模型
animator = d2l.Animator(xlabel='epoch', ylabel='loss',
xlim=[0, num_epochs], ylim=[0.9, 1.1])
n, timer = 0, d2l.Timer()
# 这是一个单层线性层
net = lambda X: d2l.linreg(X, w, b)
loss = F.cross_entropy
for _ in range(num_epochs):
for X, y in data_iter:
X = X.to(torch.float32)
l = loss(net(X), y.long()).mean()
l.backward()
trainer_fn([w, b], states, hyperparams)
n += X.shape[0]
if n % 48 == 0:
timer.stop()
animator.add(n / X.shape[0] / len(data_iter),
(evaluate_loss(net, data_iter, loss),))
timer.start()
print(f'loss: {animator.Y[0][-1]:.3f}, {timer.avg():.3f} sec/epoch')
return timer.cumsum(), animator.Y[0]
def load_data(shuffle=True):
x = torch.tensor(load_iris().data)
y = torch.tensor(load_iris().target)
# 数据归一化
x_min = torch.min(x, dim=0).values
x_max = torch.max(x, dim=0).values
x = (x - x_min) / (x_max - x_min)
if shuffle:
idx = torch.randperm(x.shape[0])
x = x[idx]
y = y[idx]
return x, y
class IrisDataset(Dataset):
def __init__(self, mode='train', num_train=120, num_dev=15):
super(IrisDataset, self).__init__()
x, y = load_data(shuffle=True)
if mode == 'train':
self.x, self.y = x[:num_train], y[:num_train]
elif mode == 'dev':
self.x, self.y = x[num_train:num_train + num_dev], y[num_train:num_train + num_dev]
else:
self.x, self.y = x[num_train + num_dev:], y[num_train + num_dev:]
def __getitem__(self, idx):
return self.x[idx], self.y[idx]
def __len__(self):
return len(self.x)
def init_rmsprop_states(feature_dim):
s_w = torch.zeros((feature_dim, 3))
s_b = torch.zeros(3)
return (s_w, s_b)
def rmsprop(params, states, hyperparams):
gamma, eps = hyperparams['gamma'], 1e-6
for p, s in zip(params, states):
with torch.no_grad():
s[:] = gamma * s + (1 - gamma) * torch.square(p.grad)
p[:] -= hyperparams['lr'] * p.grad / torch.sqrt(s + eps)
p.grad.data.zero_()
# batch_size = 1
batch_size = 24
# batch_size = 120
# 分别构建训练集、验证集和测试集
train_dataset = IrisDataset(mode='train')
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
lr = 0.02
train(rmsprop, init_rmsprop_states(4), {'lr': lr, 'gamma': 0.9}, train_loader, 4)