如何发现可以泛化的模式(pattern),是机器学习的根本问题。

模型选择方法

通常在评估几个候选模型后再选择最终的模型,该过程称为模型选择。

例如,在训练多层感知机模型时,需要比较不同数量的隐藏层、不同数量的隐藏单元、不同的激活函数组合的模型,为确定候选模型中的最佳模型,通常使用验证数据集

验证数据集与测试数据集

  • 验证数据集(validation dataset):用于评估模型的数据集,一般取自50%的训练数据作为验证数据集,它不会参与训练,在一定程度下能够反映超参数的好坏
  • 测试数据集:只可以使用一次的数据集,eg:未来的考试、Kaggle私人排行榜中使用的数据集

K则交叉验证

当训练数据十分稀缺时,会难以提供足够数据来构成一个合适的验证集,此时可使用K则交叉验证。

算法如下:

  • 将训练数据划分为KK不重叠的子集

    虽然KK 越大,效果越好,但一般取 $K = 5 $ 或者1010

  • 对于某一子集i{1,...,K}i \in \{1, ..., K\},我们将其作为验证数据集,剩下的K1K -1 个子集用于训练

  • 如上执行KK 次模型训练和验证,报告这KK 个验证集误差的平均值

最佳实践

  • 若数据集不够大,则将数据集的30%作为测试数据集,70%作为训练数据,然后在训练数据中做 5 则交叉验证(即每次取训练数据的 20%,作为验证数据集,总共做5次)

  • 若数据集足够多,则一般将数据集的 50% 作为训练数据

训练误差与泛化误差

  • 独立同分布假设(i.i.d. assumption):对数据进行采样的过程中没有进行“记忆”

    也就是说,抽取的第2个样本和第3个样本的相关性,与抽取第2个样本和第200万个样本的相关性差不多。

  • 训练误差(training error):模型在训练数据集上计算得到的误差

  • 泛化误差(generalization error):模型在新数据上的误差

    在历年考试真题取得好成绩(训练误差)并不能保证未来考试成绩好(泛化误差)

欠拟合和过拟合

定义

  • 欠拟合(underfitting):训练误差和泛化误差都比较大,并且两者仅有一点差距。

    若模型不能降低训练误差,有可能模型过于简单(即表达能力不足),无法捕获我们试图学习的模式

  • 过拟合(overfitting):训练误差明显小于泛化误差

取决因素

模型复杂度

模型复杂度,即“可适应不同数据分布”的能力。

对于低容量模型,它难以适应训练集,若数据复杂度较高,则会导致“欠拟合”问题

对于高容量模型,它会将训练集“记忆”下来,若数据复杂度较低,则会导致“过拟合”问题

如下图,模型复杂度对欠拟合和过拟合的影响:

数据集大小

训练数据集中的样本越少,就越有可能(且更严重地)出现过拟合问题。

随着训练数据量的增加,泛化误差会减少。一般而言,更多的数据不会有什么坏处。

正则化模型技术:权重衰减

正则化,是处理过拟合的常用方法

实际上,正则是一种对搜索范围的约束定义

而 权重衰减(L2L_2 正则化),是最广泛使用的正则化技术之一

均方范数作为硬性限制

通过限制参数值w\mathbf{w} 的选择范围(通常不限制偏置项bb),来控制模型的容量

minl(w,b)   s.t.w2θ\min l(\mathbf{w}, b)\ \ \ s.t. ||\mathbf{w}||^2\le\theta

θ\theta 越小,意味着更强的正则化

均方范数作为柔性限制⭐

对于每一个θ\theta ,均可找到对应的λ\lambda 使得硬性限制改写为柔性限制:

即将上述的限制项θ\theta 挪掉,而在损失函数中加入惩罚项。

使用平方范数,便于计算;λ\lambda 要除以 2 是为了求导时确保更新表达式整洁

minl(w,b)+λ2w2\min l(\mathbf{w}, b) + \frac{\lambda}{2}||\mathbf{w}||^2

其中,非负超参数λ\lambda 平衡这个新的额外惩罚的损失:

  • λ=0\lambda = 0,相当于没有效果
  • λ\lambda \rightarrow \infty,则w0\mathbf{w}^*\rightarrow\mathbf{0}

w\mathbf{w}^* 可选择的范围越大时,会使用模型复杂度增大

惩罚项会对大数值的权值进行惩罚,鼓励权值分散,即将所有特征能够运用起来,而不是依赖其中的少量特征。

最佳实践:λ\lambda 一般取1e31e-3 或者1e41e-4

参数更新法则(柔性限制)

在计算梯度时,

w( l(w,b)+λ2w2)=l(w,b)w+λw\frac{\partial}{\partial\mathbf{w}}(\ l(\mathbf{w}, b) + \frac{\lambda}{2}||\mathbf{w}||^2 ) \\ = \frac{\partial l(\mathbf{w}, b)}{\partial\mathbf{w}} + \lambda\mathbf{w}

时间tt 更新参数:

wt+1=wtαw( l(w,b)+λ2w2)=(1αλ)wtαl(wt,bt)wt\mathbf{w}_{t+1} = \mathbf{w}_t - \alpha\frac{\partial}{\partial\mathbf{w}}(\ l(\mathbf{w}, b) + \frac{\lambda}{2}||\mathbf{w}||^2 ) \\ = (1-\alpha\lambda)\mathbf{w}_t - \alpha \frac{\partial l(\mathbf{w}_t, b_t)}{\partial\mathbf{w}_t}

可见,加上了惩罚项后,在更新权重参数时,多了 $-\alpha\lambda $ 项(小于11),也就说,在更新权重参数时,先将权重参数缩小一些,再使其沿梯度下降的方向更新。

正因为αλ<1-\alpha\lambda < 1,故在深度学习中通常称为权重衰退。

基于PyTorch框架的实现

PyTorch 为便于使用权重衰减,便将权重衰减集成到优化算法中,以便与任何损失函数结合使用,并且不需增加任何额外的计算开销

其中,在实例化优化器时,通过 weight_decay 来指定超参数λ\lambda

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def train_concise(wd): # wd 即是 lambda
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)

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()

train_concise(0.001)

正则化模型技术:丢弃法

为了缩小训练和测试性能之间的差距,应以简单的模型为目标。

除了参数的范数作为简单性度量,另一有用的角度是平滑性:函数不应该对其输入的微小变化敏感。例如,当我们对图像进行分类时,预计向像素添加一些随机噪音基本上没有影响的。

相较于权重衰减,丢弃法(dropout)并不是在输入中添加噪音,而是在层之间加入噪音。

丢弃法对每个元素进行如下扰动:

hi={0,一定概率ph1p,其他情况,即非概率ph_i'=\begin{cases} 0, &一定概率p下 \\ \frac{h}{1-p}, &其他情况,即非概率p \end{cases}

也就说,每个中间激活值,有一定概率pp 变为0,有概率1p1 - p 会增大(因为1p<11-p<1

此时,E[hi]=p×0+(1p)×hi1p=h=E[hi]E[\mathbf{h}_i'] = p \times 0 + (1-p) \times \frac{\mathbf{h}_i}{1-p}=h=E[\mathbf{h}_i] ,即加入噪音依然满足期望不变

最佳实践:作为超参数的丢弃概率pp,一般取0.50.50.90.9

丢弃法在模型训练过程中

丢弃法作用在多层感知机的隐藏全连接层的输出上,它将输出项随机置00 来控制模型复杂度。

丢弃的是前一层的输出,相当于丢弃后一层的输入。

如下图,输出计算不再依赖于h2h_2h5h_5 ,而且它们各自梯度在执行反向传播时也会消失,由此输出层计算不能过度依赖于h1,...,h5h_1, ... , h_5 的任何一个元素。

h=σ(W1x+b1)h=dropout(h)o=W2h+b2y=softmax(o)\mathbf{h} = \sigma(\mathbf{W}_1\mathbf{x} + \mathbf{b}_1) \\ \mathbf{h}' = dropout(\mathbf{h}) \\ \mathbf{o} = \mathbf{W}_2\mathbf{h}' + \mathbf{b}_2 \\ \mathbf{y} = softmax(\mathbf{o})

丢弃法在推断过程中

预测,即不需要对权重的进行更新时,不会开启 dropout。

dropout 是一个正则项,它仅用于模型训练,为了在更新权重时将模型复杂度降低一些

基于PyTorch框架的实现

使用 PyTorch 提供的 Dropout 函数

1
2
3
import torch
from torch import nn
from d2l import torch as d2l

定义模型参数

使用之前引入的 Fashion_MNIST 数据集,并且定义一个具有两个隐藏层的多层感知机,每个隐藏层包含 256 个单元。

1
num_inputs, num_outputs, num_hiddens1, num_hiddens2 = 784, 10, 256, 256

同时为每一层分别设置丢弃概率 dropout

一般而言,在靠近输入层的地方设置较低的丢弃概率

1
dropout1, dropout2 = 0.2, 0.5

定义模型

在每个全连接层之后添加一个 Dropout 层,将丢弃概率 dropout 作为唯一参数,传递至对应的构造方法

在训练过程中,Dropout 层将根据指定的丢弃概率 dropout ,随机地丢弃上一层的输出(即下一层输入)

当不处于训练模式时,Dropout 层仅在测试时传递数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
net = nn.Sequential(nn.Flatten(), 
nn.Linear(784, 256),
nn.ReLU(),
# 在第一个全连接层之后添加Dropout层
nn.Dropout(dropout1),
nn.Linear(256, 256),
nn.ReLU(),
# 在第二个全连接层之后添加Dropout层
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)
1
2
3
4
5
6
7
8
9
10
Sequential(
(0): Flatten(start_dim=1, end_dim=-1)
(1): Linear(in_features=784, out_features=256, bias=True)
(2): ReLU()
(3): Dropout(p=0.2, inplace=False)
(4): Linear(in_features=256, out_features=256, bias=True)
(5): ReLU()
(6): Dropout(p=0.5, inplace=False)
(7): Linear(in_features=256, out_features=10, bias=True)
)

训练与测试

1
2
3
4
5
num_epochs, lr, batch_size = 10, 0.5, 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
trainer = torch.optim.SGD(net.parameters(), lr=lr)
loss = nn.CrossEntropyLoss()
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)