小白知识点
训练误差和泛化误差
-
训练误差:模型在训练数据集上表现出的误差
-
泛化误差:模型在任意一个测试数据样本上表现出的误差的期望,并常常通过测试数据集上的误差来近似。
验证数据集、测试数据集和训练数据集三者的功能
- 训练集:模型训练的过程其实就是在求参数,我们通过训练集来分别对模型进行训练,学习到每一个模型中对应的最优的参数。
- 验证集:我们要考察不同结构的模型在数据上的优劣程度,而模型的结构是由其中的超参数决定的,所以我们引入验证集来对这些超参数进行选择,以求找到最优效果的超参数。
- 测试集:当前两步完成后,我们学习到了好的模型参数与超参数,模型结构由此定了下来,最后再引入一些数据来测试这个模型在新的数据上的效果。
总的来说:训练集是训练参数用的,验证集是选择函数模型用的,测试集是测试训练好的参数在新的数据集中的效果
数据集太少的解决办法——K折交叉验证
在K折交叉验证中,我们把原始训练数据集分割成K个不重合的子数据集,然后我们做K次模型训练和验证。每一次,我们使用一个子数据集验证模型,并使用其他K-1个子数据集来训练模型。在这K次训练和验证中,每次用来验证模型的子数据集都不同。最后,我们对这K次训练误差和验证误差分别求平均。
函数设计:
def get_k_fold_data(k, i, X, y):
# 返回第i折交叉验证时所需要的训练和验证数据
assert k > 1
fold_size = X.shape[0] // k
X_train, y_train = None, None
for j in range(k):
idx = slice(j * fold_size, (j + 1) * fold_size)
X_part, y_part = X[idx, :], y[idx]
if j == i:
X_valid, y_valid = X_part, y_part
elif X_train is None:
X_train, y_train = X_part, y_part
else:
X_train = torch.cat((X_train, X_part), dim=0)
y_train = torch.cat((y_train, y_part), dim=0)
return X_train, y_train, X_valid, y_valid
在K折交叉验证中我们训练K次并返回训练和验证的平均误差:
def k_fold(k, X_train, y_train, num_epochs,
learning_rate, weight_decay, batch_size):
train_l_sum, valid_l_sum = 0, 0
for i in range(k):
data = get_k_fold_data(k, i, X_train, y_train)
net = get_net(X_train.shape[1])
train_ls, valid_ls = train(net, *data, num_epochs, learning_rate,
weight_decay, batch_size)
train_l_sum += train_ls[-1]
valid_l_sum += valid_ls[-1]
if i == 0:
d2l.semilogy(range(1, num_epochs + 1), train_ls, 'epochs', 'rmse',
range(1, num_epochs + 1), valid_ls,
['train', 'valid'])
print('fold %d, train rmse %f, valid rmse %f' % (i, train_ls[-1], valid_ls[-1]))
return train_l_sum / k, valid_l_sum / k
模型训练中经常出现的两类典型问题:
- 欠拟合:模型无法得到较低的训练误差,大部分是因为模型复杂度较低引起。
- 过拟合:模型的训练误差远小于它在测试数据集上的误差。训练误差较低但是泛化误差依然较高,二者相差较大。
导致过拟合与欠拟合的两种因素
-
模型复杂度:模型中参数的数量多少,参数数量与模型复杂度成正比
以下为模型复杂度与误差之间的关系:
-
训练集大小:训练数据集中样本数过少,特别是比模型参数数量(按元素计)更少时,过拟合更容易发生。
以下是一个三阶多项式函数的训练实验结果图
正常拟合
解决过拟合的两种办法:
-
权重衰减法(L2范数正则化):在模型原损失函数基础上添加L2范数惩罚项,从而得到训练所需要最小化的函数。通过惩罚绝对值较大的模型参数为需要学习的模型增加了限制,这可能对过拟合有效。
定义L2范数正则化:
def l2_penalty(w): return (w**2).sum() / 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
以一个单隐藏层的多层感知机为例:
多层感知机使用丢弃法随机丢弃了h2和h5隐藏单元。
虽然丢弃法可以用来应对过拟合。在测试模型时,我们为了拿到更加确定性的结果,一般不使用丢弃法。
两种方法的原理其实是一样的,都是为了减少特征数量。不过丢弃法是随机丢弃,而正则法是人为抑制和丢弃。