pytorch-权重衰减、Dropout
正则化:
正则化:凡是能够减少泛化误差,而不是减少训练误差的方法就是正则化方法,也就是说能够减少过拟合的方法。
在训练参数化机器学习模型时,权重衰减(weight decay)是广泛使用的正则化的技术之一,它通常也被
称为L2正则化。
权重衰减
在神经网络中我们有参数w和b,w是权重,b是偏置。这个b不会改变你的模型的曲线的,只能改变模型平移的情况。L1和L2正则化,其实主要改变的是w。其实要想减少过拟合w才是重点。
我们在训练神经网络的时候。你最后训练出来的w和b的值是什么非常依赖于初始值是什么。也就是说你给w和b一个不一样初始值,最后得到的损失函数一样的情况下,你的w和b的的绝对值很可能不一样。你得到的损失函数一样的情况下,你可能得到一组很大的w和b,也可能得到一组很小的w和b。这时候就会出现问题了。
就比如说你取了一个数值比较大的参数,这个时候来了一个新的数据,那个新的数据和这个大参数相乘之后就会得到一个比较大的数值。如果说没有误差,没有噪声的话那完全没有问题,但是这个是不可能的。那这个误差和噪声经过大参数相乘之后也会被放大,误差和噪声被放大之后最后判断的结果更容易出现问题。本来是一只猫,但是经过误差和噪声放大之后,最后判断出来是一只狗那肯定不行。
所以既然参数太大会带来一定的问题,那我们是不是需要控制一下这个参数让他不那么大。那我们可不可以让这个参数不那么大呢?
其实也简单我们就是人为的给这个参数画一个框框,画一个取值范围,参数只有在这个取值范围之内才有效。超过这个范围,即使这个损失函数在小我们也不考虑。这个就相当于给定一个可行域的取值范围,我们在这个可行域的取值范围之内求最值。
使用均方范数作为硬性限制
通过限制参数值的选择范围来控制模型容量
通常不限制偏移b (限不限制都差不多)
小的意味着更强的正则项
使用均方范数作为柔性限制
对每个,都可以找到使得之前的目标函数等价于下面
可以通过拉格朗日乘子来证明
超参数控制了正则项的重要程度
·
·→oo,
参数更新法则
计算梯度
时间t更新参数
通常,在深度学习中通常叫做权重衰退
总结
·权重衰退通过L2正则项使得模型参数不会过大,从而控制模型复杂度
·正则项权重是控制模型复杂度的超参数
代码演示
我们通过一个简单的例子来演示权重衰减。
%matplotlib inline
import torch
from torch import nn
from d2l import torch as d2l
像以前一样生成一些数据
我们选择标签是关于输入的线性函数。标签同时被均值为0,标准差为0.01高斯噪声破坏。为了使过拟合的效
果更加明显,我们可以将问题的维数增加到d = 200,并使用一个只包含20个样本的小训练集。
n_train, n_test, num_inputs, batch_size = 20, 100, 200, 5
true_w, true_b = torch.ones((num_inputs, 1)) * 0.01, 0.05
train_data = d2l.synthetic_data(true_w, true_b, n_train)
train_iter = d2l.load_array(train_data, batch_size)
test_data = d2l.synthetic_data(true_w, true_b, n_test)
test_iter = d2l.load_array(test_data, batch_size, is_train=False)
- 初始化模型参数
def init_params():
w = torch.normal(0, 1, size=(num_inputs, 1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
return [w, b]
- 定义L2范数惩罚
def l2_penalty(w):
return torch.sum(w.pow(2)) / 2
- 定义训练代码实现
def train(lambd):
w, b = init_params()
net, loss = lambda X: d2l.linreg(X, w, b), d2l.squared_loss
num_epochs, lr = 100, 0.003
animator = d2l.Animator(xlabel='epochs', ylabel='loss', yscale='log',
xlim=[5, num_epochs], legend=['train', 'test'])
for epoch in range(num_epochs):
for X, y in train_iter:
with torch.enable_grad():
l = loss(net(X), y) + lambd * l2_penalty(w)
l.sum().backward()
d2l.sgd([w, b], lr, batch_size)
if (epoch + 1) % 5 == 0:
animator.add(epoch + 1, (d2l.evaluate_loss(net, train_iter, loss),
d2l.evaluate_loss(net, test_iter, loss)))
print('w的L2范数是:', torch.norm(w).item())
- 忽略正则化直接训练
train(lambd=0)
w的L2范数是: 13.346659660339355
- 使用权重衰减
train(lambd=3)
代码演示-简介实现
def train_concise(wd):
net = nn.Sequential(nn.Linear(num_inputs, 1))
for param in net.parameters():
param.data.normal_()
loss = nn.MSELoss()
num_epochs, lr = 100, 0.003
trainer = torch.optim.SGD([{
"params": net[0].weight,
'weight_decay': wd}, {
"params": net[0].bias}], lr=lr)
animator = d2l.Animator(xlabel='epochs', ylabel='loss', yscale='log',
xlim=[5, num_epochs], legend=['train', 'test'])
for epoch in range(num_epochs):
for X, y in train_iter:
with torch.enable_grad():
trainer.zero_grad()
l = loss(net(X), y)
l.backward()
trainer.step()
if (epoch + 1) % 5 == 0:
animator.add(epoch + 1, (d2l.evaluate_loss(net, train_iter, loss),
d2l.evaluate_loss(net, test_iter, loss)))
print('w的L2范数:', net[0].weight.norm().item())
这个其实就是在这里定义的:
class torch.optim.SGD(params,
lr,
momentum=0,
dampening=0,
weight_decay=0,
nesterov=False)params(可迭代的) -可迭代的参数以优化或 dicts 定义参数组
lr(float) -学习率
momentum(float,可选的) -动量因子(默认值:0)
weight_decay(float,可选的) -权重衰减(L2 惩罚)(默认值:0)
dampening(float,可选的) -动量阻尼(默认值:0)
nesterov(bool,可选的) -启用 Nesterov 动量(默认值:False)
ps:在实际中权重衰退一般是取到,其实权重衰退效果会有一点,但是不是很好,不要过多的指望,如果你的模型很大的话,可能会很好。
Dropout
暂退法在前向传播过程中,计算每一内部层的同时注入噪声,这已经成为训练神经网络的常用技术。这种方法之所以被称为暂退法,因为我们从表面上看是在训练过程中丢弃(dropout)⼀些神经元。在整个训练过程的每⼀次迭代中,标准暂退法包括在计算下⼀层之前将当前层中的⼀些节
点置零。
我们需要无偏差的加入噪音:
对x加入噪音得到x',我们希望即x'的期望等于x。
丢弃法对每个元素进行如下扰动:
根据此模型的设计,其期望值保持不变,即。
也就是在某一输入有一定概率将它置为0,然后其余的地方将它变大,置为,从而使得其期望(或者均值)不变,这样使得它预测的时候不会受到影响。
注意:BN是给卷积神经网络用的,Dropout是给全连接层用的,卷积层不用Dropout。
实际中的暂退法
当我们将暂退法应用到隐藏层,以p的概率将隐藏单元置为零时,结果可以看作⼀个只包含原始神经元子集的网络。比如在下面图中,删除了h2和h5,因此输出的计算不再依赖于h2或h5,并且它们各自的梯度在执行反向传播时也会消失。这样,输出层的计算不能过度依赖于的任何⼀个元素。
通常,我们在测试时不用暂退法。给定⼀个训练好的模型和⼀个新的样本,我们不会丢弃任何节点,因此不需要标准化。然而也有一些例外:⼀些研究人员在测试时使用暂退法,用于估计神经网络预测的“不确定性”:如果通过许多不同的暂退法遮盖后得到的预测结果都是一致的,那么我们可以说网络发挥更稳定。
从零开始实现
要实现单层的暂退法函数,我们从均匀分布中抽取样本,样本数与这层神经网络的维度一致。然后我们保留那些对应样本大于p的节点,把剩下的丢弃。在下面的代码中,我们实现dropout_layer函数,该函数以dropout的概率丢弃张量输入X中的元素,如上所述重新缩放剩余部分:将剩余部分除以1.0 - dropout。
import torch
from torch import nn
from d2l import torch as d2l
def dropout_layer(X, dropout):
assert 0 <= dropout <= 1
if dropout == 1:
return torch.zeros_like(X)
if dropout == 0:
return X
mask = (torch.Tensor(X.shape).uniform_(0, 1) > dropout).float()
return mask * X / (1.0 - dropout)
对于这个函数:
(torch.Tensor(X.shape).uniform_(0, 1)
就是生成一个和X形状相同随机分布的[0,1]的tensor,
(torch.Tensor(X.shape).uniform_(0, 1)>dropout
这个就是对于你生成的[0,1]的tensor里面的数值大于dropout,就是True,否则就是False,其中
我们可以看一下:
我们可以通过下面几个例子来测试dropout_layer函数。我们将输⼊X通过暂退法操作,暂退概率分别为0、0.5和1。
X= torch.arange(16, dtype = torch.float32).reshape((2, 8))
print(X)
print(dropout_layer(X, 0.))
print(dropout_layer(X, 0.5))
print(dropout_layer(X, 1.)) #这个是全0,因为[0,1]里面没有大于1的
- 定义模型
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)
- 测试
num_epochs, lr, batch_size = 10, 0.5, 256
loss = nn.CrossEntropyLoss()
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
trainer = torch.optim.SGD(net.parameters(), lr=lr)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs,trainer)
简介实现
net = nn.Sequential(nn.Flatten(), nn.Linear(784, 256), nn.ReLU(),
nn.Dropout(dropout1), nn.Linear(256, 256), nn.ReLU(),
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_2(net, train_iter, test_iter, loss, num_epochs, trainer)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)