模型选择
训练误差和泛化误差
- 训练误差:模型在训练数据上的误差
- 泛化误差:模型在新数据上的误差
验证数据集和测试数据集
验证测试集合:用来评估模型好坏的数据集
不要和训练数据集搞混了,先训练后,用来验证一下超参数是否合适
测试数据集:只用一次的数据集
K-折交叉验证
大多数情况下没有足够多的数据来使用,可使用K-则交叉验证
将训练数据分割成K块
- 循环K次,当循环第i次时,使用第i块作为验证数据集,其余的作为训练数据集
- 报告K个验证集误差的平均
常用:K=5或K=10
过拟合和欠拟合
模型容量\数据 | 简单 | 复杂 |
---|---|---|
低 | 正常 | 欠拟合 |
高 | 过拟合 | 正常 |
模型容量
模型容量指拟合各种函数的能力,低容量的模型难以拟合训练数据,高容量的模型可以记住所有的训练数据。
模型容量的影响
估计模型容量
难以在不同的种类算法之间比较:树模型和神经网络
给定一个模型种类,将有两个主要因素:
- 参数的个数
- 参数值的选择范围
VC维
给定一个分类模型,VC等于一个最大的数据集的大小,不管如何给定标号(label),都存在一个模型来对它进行完美分类。
线性分类器的VC维
对于2维输入的感知机,VC维=3,能够分类任何3个点,但不能分类4个(异或问题)
支持N维输入的感知机的VC维是N+1
一些多层感知机的VC维是 O ( N l o g 2 N ) O(Nlog_2N) O(Nlog2N)
数据复杂度
样本个数,每个样本的元素个数,时间、空间结构,多样性
权重衰退(weight decay)
比较常见的处理过拟合的办法
控制模型的容量:
- 使用小模型,减少参数的个数
- 控制参数值的选择范围
权重衰退就是用过限制参数值的选择范围来控制模型容量
使用均方范数作为硬性限制
m i n l ( w , b ) s u b j e c t t o ∣ ∣ w ∣ ∣ 2 ≤ θ min\ l(w,b)\ \ subject\ to\ ||w||^2\le\theta min l(w,b) subject to ∣∣w∣∣2≤θ
- 通常不限制偏移b(限不限制都差不多)
- 小的 θ \theta θ意味着更强的正则项
使用均方范数作为柔性限制
对于每个 θ \theta θ,都可以找到 λ \lambda λ使得之前的目标韩式等价于
m i n l ( w , b ) + λ 2 ∣ ∣ w ∣ ∣ 2 min\ l(w,b)+\frac{\lambda}{2}||w||^2 min l(w,b)+2λ∣∣w∣∣2
超参数 λ \lambda λ控制了正则项的重要程度
- λ = 0 \lambda =0 λ=0:无作用
- λ → ∞ , w ∗ → 0 \lambda\rightarrow \infty,w^*\rightarrow 0 λ→∞,w∗→0
图示影响
将 λ 2 ∣ ∣ w ∣ ∣ 2 \frac \lambda 2 ||w||^2 2λ∣∣w∣∣2项叫做罚,假设只有两个参数,是二维的,等高线上的每一点值都是一样的。可以看出 w ^ ∗ \hat w ^* w^∗离原点很远,沿梯度下降后,再加上罚,可以到达 w ∗ w^* w∗,可以认为是平衡点。远离 w ∗ w^* w∗的值,梯度下降(远离)的强度没有罚的项大,则会逐渐拉回到 w ∗ w^* w∗,达到平衡。
可以再加上这个二次函数理解,离原点较远时,梯度下降很快,罚的值也很大,可以快速的回到原点,当接近原点时,梯度下降变慢了,罚的值也会相应变小,减缓下降速度,避免过拟合。
参数更新法则
计算梯度
∂ ∂ w ( l ( w , b ) + λ 2 ∣ ∣ w ∣ ∣ 2 = ∂ l ( w , b ) ∂ w + λ w \frac{\partial}{\partial w}(l(w,b)+\frac{\lambda}{2}||w||^2=\frac{\partial l(w,b)}{\partial w}+\lambda w ∂w∂(l(w,b)+2λ∣∣w∣∣2=∂w∂l(w,b)+λw
时间t更新参数( η \eta η是学习率):
w t + 1 = ( 1 − η λ ) w t − η ∂ l ( w t , b t ) ∂ w t w_{t+1}=(1-\eta \lambda)w_t -\eta \frac{\partial l(w_t,b_t)}{\partial w_t} wt+1=(1−ηλ)wt−η∂wt∂l(wt,bt)
注意到多了 η λ \eta\lambda ηλ这一项,通常 η λ < 1 \eta \lambda <1 ηλ<1,在深度学习中通常叫做权重衰退。
权重衰退通过L2正则项使得模型参数不会过大,从而控制模型复杂度。正则项权重是控制模型复杂度的超参数。
import torch
from torch import nn
from d2l import torch as d2l
# 生成一个人工数据集
# y = 0.05 + \sum 0.01x_i + 噪音 噪音满足N(0,0.0001)
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 # 每一项的权重都是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 # lambda临时函数
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)
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())
d2l.plt.show()
# train(lambd=0) #过拟合
train(lambd=6) # 还行的拟合
'''简洁写法'''
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}, #传入w和lambda,直接在算法中使用权重衰退
{"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())
没有正则化,由于数据很少,直接过拟合了
λ \lambda λ取6时候的值,有比较好的效果