过拟合、欠拟合及其解决方案
1. 过拟合和欠拟合
一些术语解释
训练集: 用于训练超参数和模型参数的数据 (平时作业)
验证集: 从初次划分的训练集中预留出的带有标签的用于模型选择的数据 (平时考试)
测试集: 用于评估模型好坏的数据集, 只能在所有超参数和模型参数选定后使用一次 (高考)
k折交叉验证 (K-fold cross-validation) : 为了充分利用所有训练数据, 把原始训练数据集分割成K个不重合的子数据集, 然后做K次模型训练和验证;每一次, 使用一个子数据集验证模型, 并使用其他K-1个子数据集来训练模型. 在这K次训练和验证中, 每次用来验证模型的子数据集都不同;最后, 对这K次训练误差和验证误差分别求平均. (这里应该一次性计算整个训练集的误差也行)
训练误差 (training error) : 指模型在训练数据集上表现出的误差
泛化误差 (generalization error) : 指模型在任意一个测试数据样本上表现出的误差的期望
测试误差: 模型在测试数据集上的误差, 常常用于近似泛化
欠拟合 (underfitting) : 模型无法得到较低的训练误差
过拟合 (overfitting) : 模型的训练误差远小于它在测试数据集上的误差
- 模型越简单, 容易欠拟合, 模型越复杂, 容易过拟合
- 训练数据越少越容易过拟合
2. 过拟合解决方法
2.1 权重衰减
权重衰减等价于 范数正则化 (regularization) . 正则化通过为模型损失函数添加惩罚项使学出的模型参数值较小, 是应对过拟合的常用手段. 范数正则化是在模型的原损失函数基础上添加范数惩罚项, 从而得到新的目标函数.
范数惩罚项是模型权重参数每个元素的平方和与一个常数的乘积. 以线性回归损失函数为例:
将权重参数用向量表示,带有范数惩罚项的新损失函数为
其中超参数. 当权重参数均为时, 惩罚项最小. 当较大时, 惩罚项在损失函数中的比重较大, 这通常会使学到的权重参数的元素较接近. 当设为0时, 惩罚项完全不起作用. 以下是权重参数的更新方式变为:
可见, 范数正则化令权重 和 先自乘小于1的数, 再减去不含惩罚项的梯度. 因此, 范数正则化又叫权重衰减. 权重衰减通过惩罚绝对值较大的模型参数为需要学习的模型增加了限制, 这可能对过拟合有效.
# 正则项
def l2_penalty(w):
return (w**2).sum() / 2
net, loss = d2l.linreg, d2l.squared_loss
l = loss(net(X, w, b), y) + lambd * l2_penalty(w) # 新损失项
# 或者torch.optim.SGD中自带了权重衰减参数
torch.optim.SGD(params=[net.weight], lr=lr, weight_decay=wd)
2.2 丢弃法
以前面学习过的多层感知机为例, 使用丢弃法. 当对该隐藏层使用丢弃法时, 该层的隐藏单元将有一定概率被丢弃掉.
设丢弃概率为, 那么有的概率会被清零, 有的概率会除以做拉伸. 丢弃概率是丢弃法的超参数. 具体来说, 设随机变量为和的概率分别为和 . 新的隐藏单元, 而, 则. 即丢弃法不改变其输入的期望值.
由于在训练中隐藏层神经元的丢弃是随机的,即 都有可能被清零,输出层的计算无法过度依赖中的任一个,从而在训练模型时起到正则化的作用,并可以用来应对过拟合。在测试模型时,我们为了拿到更加确定性的结果,一般不使用丢弃法
从头实现
def dropout(X, drop_prob):
X = X.float()
assert 0 <= drop_prob <= 1
keep_prob = 1 - drop_prob
# 这种情况下把全部元素都丢弃
if keep_prob == 0:
return torch.zeros_like(X)
mask = (torch.rand(X.shape) < keep_prob).float()
return mask * X / keep_prob
drop_prob1, drop_prob2 = 0.2, 0.5
def net(X, is_training=True):
X = X.view(-1, num_inputs)
H1 = (torch.matmul(X, W1) + b1).relu()
if is_training: # 只在训练模型时使用丢弃法
H1 = dropout(H1, drop_prob1) # 在第一层全连接后添加丢弃层
H2 = (torch.matmul(H1, W2) + b2).relu()
if is_training:
H2 = dropout(H2, drop_prob2) # 在第二层全连接后添加丢弃层
return torch.matmul(H2, W3) + b3
简洁实现
直接使用torch.nn模块中的Dropout类构建模型
d2l.FlattenLayer(),
nn.Linear(num_inputs, num_hiddens1),
nn.ReLU(),
nn.Dropout(drop_prob1),
nn.Linear(num_hiddens1, num_hiddens2),
nn.ReLU(),
nn.Dropout(drop_prob2),
nn.Linear(num_hiddens2, 10)
)