1.模型选择、过拟合、欠拟合
1.1模型选择
一般来说,简单的数据使用简单的模型,复杂的数据使用复杂的模型。
- 数据的复杂度,取决于样本个数、每个样本的元素个数、时间和空间结构、多样性。
- 模型的复杂度,取决于参数个数、参数值范围。
高阶多项式函数比低阶多项式函数复杂得多。 高阶多项式的参数较多,模型函数的选择范围较广。 因此在固定训练数据集的情况下, 高阶多项式函数相对于低阶多项式的训练误差应该始终更低(最坏也是相等)。
同时,随着训练数据量的增加,泛化误差通常会减小。
1.2数据集和误差
通常把一个数据集分为训练数据集、验证数据集、测试数据。
模型的超参数(除了权重和偏置外的参数)根据模型在验证数据集上的性能进行调整,最后再用于测试数据集。
训练误差(training error)是指, 模型在训练数据集上计算得到的误差。 泛化误差(generalization error)是指, 模型应用在验证集上的误差。
1.3K-折交叉验证
当训练数据集样本较少,可以采用该方法,具体操作:原始训练数据被分成K个不重叠的子集。 然后执行K次模型训练和验证,每次在K-1个子集上进行训练, 并在剩余的一个子集(在该轮中没有用于训练的子集)上进行验证。 最后,通过对K次实验的结果取平均来估计训练和验证误差。
该方法不是用来确定超参数,而是评估该超参数下该模型的性能。
1.4过拟合、欠拟合
欠拟合:模型太简单了,根本预测不正确。(做奥数题,我只会1+1=2,对于高等数据的题我只会乱写)
- 训练误差和验证误差都很严重, 但它们之间仅有一点差距。 如果模型不能降低训练误差,这可能意味着模型过于简单(即表达能力不足), 无法捕获试图学习的模式。 此外,由于我们的训练和验证误差之间的泛化误差很小, 我们有理由相信可以用一个更复杂的模型降低训练误差。 这种现象被称为欠拟合(underfitting)。
过拟合:我需要抽象派、简笔画。你给我整个写实派。
- 当我们的训练误差明显低于验证误差时要小心, 这表明严重的过拟合(overfitting)。 注意,过拟合并不总是一件坏事。 特别是在深度学习领域,众所周知, 最好的预测模型在训练数据上的表现往往比在保留(验证)数据上好得多。 最终,我们通常更关心验证误差,而不是训练误差和验证误差之间的差距。
2.权重衰减
正则化是处理过拟合的常用方法:在训练集的损失函数中加入惩罚项,以降低学习到的模型的复杂度。
保持模型简单的一个特别的选择是使用L2惩罚的权重衰减,这会导致学习算法更新步骤中的权重衰减。(L2范数:向量平方和的平方根)
要保证权重向量比较小, 最常用方法是将其范数作为惩罚项加到最小化损失的问题中。 将原来的训练目标最小化训练标签上的预测损失, 调整为最小化预测损失和惩罚项之和。 (后面加入的惩罚项没有特别的意义,只是为了美观和简单。其中为超参数)
2.1案例
设计一个过拟合的案例,以查看惩罚项是否有效。
2.1.1生成数据
%matplotlib inline
import torch
from torch import nn
from d2l import torch as d2l
#为了使过拟合的效果更加明显,我们可以将问题的维数增加到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)
2.1.2定义初始化模型参数和惩罚项
#随机初始化模型参数
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]
#定义范数惩罚
def l2_penalty(w):
return torch.sum(w.pow(2)) / 2
2.1.3定义训练代码
def train(lambd): #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:
# 增加了L2范数惩罚项,
# 广播机制使l2_penalty(w)成为一个长度为batch_size的向量
l = loss(net(X), y) + lambd * l2_penalty(w)
l.sum().backward()
d2l.sgd([w, b], lr, batch_size) #sgd随机梯度下降优化器
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())
2.1.4开始训练
惩罚项无效时:训练误差一直在减小,而测试误差基本不变,说明了过拟合。
train(lambd=0)
惩罚项有效时:可以明显看到测试误差逐渐减小。
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(reduction='none')
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:
trainer.zero_grad()
l = loss(net(X), y)
l.mean().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())
3.暂退法(dropout)
暂退法又叫丢弃法,可以理解为将一部分输出结果丢弃掉,再进入下一层。这是用于防止模型过拟合,它通常与控制权重向量的维数和大小结合使用的。
暂退法将活性值(h)替换为具有期望值(h)的随机变量。(因此,隐藏层的输出要么是0,要么要除以1-p,确保期望不变)
-
暂退法常作用在多层感知机的隐藏层输出上。
-
丢弃概率是控制模型复杂度的超参数。(常为0.1,0.5,0.9)
-
丢弃法是一种正则化,只在训练中使用:他们影响模型参数的更新。而不用于预测。
3.1案例
案列:在隐藏层后面加入一个丢弃层。
定义一个丢弃层。
# torch.rand(X.shape) 随机生成X形状的0~1之间的值
# torch.rand(X.shape) > dropout,这些值若大于dropout则为True,否则为False
# 为了满足期望不变,所以x/(1-p)
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.rand(X.shape) > dropout).float()
return mask * X / (1.0 - dropout)
训练时才使用dropout。
X= torch.arange(16, dtype = torch.float32).reshape((2, 8))
num_inputs, num_outputs, num_hiddens1, num_hiddens2 = 784, 10, 256, 256
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)
进行训练,上边是dropout都为0,即不丢弃的情况,下边是丢弃的情况。
可以看到,原来训练误差很小,之后训练误差稍微大了点。确实有防止过拟合的效果。