如何发现可以泛化的模式(pattern),是机器学习的根本问题。
模型选择方法 通常在评估几个候选模型后再选择最终的模型,该过程称为模型选择。
例如,在训练多层感知机模型时,需要比较不同数量的隐藏层、不同数量的隐藏单元、不同的激活函数组合的模型,为确定候选模型中的最佳模型,通常使用验证数据集 。
验证数据集与测试数据集 验证数据集(validation dataset) :用于评估模型的数据集,一般取自50%的训练数据作为验证数据集,它不会参与训练,在一定程度下能够反映超参数的好坏测试数据集:只可以使用一次的数据集,eg:未来的考试、Kaggle私人排行榜中使用的数据集 K则交叉验证 当训练数据十分稀缺时,会难以提供足够数据来构成一个合适的验证集,此时可使用K则交叉验证。
算法如下:
将训练数据划分为K K K 个不重叠 的子集
虽然K K K 越大,效果越好,但一般取 $K = 5 $ 或者10 10 10
对于某一子集i ∈ { 1 , . . . , K } i \in \{1, ..., K\} i ∈ { 1 , ... , K } ,我们将其作为验证数据集,剩下的K − 1 K -1 K − 1 个子集用于训练
如上执行K K K 次模型训练和验证,报告这K K K 个验证集误差的平均值
最佳实践 训练误差与泛化误差 独立同分布假设(i.i.d. assumption):对数据进行采样的过程中没有进行“记忆”
也就是说,抽取的第2个样本和第3个样本的相关性,与抽取第2个样本和第200万个样本的相关性差不多。
训练误差 (training error):模型在训练数据集上计算得到的误差
泛化误差 (generalization error):模型在新数据上的误差
在历年考试真题取得好成绩(训练误差)并不能保证未来考试成绩好(泛化误差)
欠拟合和过拟合 定义 取决因素 模型复杂度 模型复杂度,即“可适应不同数据分布”的能力。
对于低容量模型,它难以适应训练集,若数据复杂度较高,则会导致“欠拟合”问题
对于高容量模型,它会将训练集“记忆”下来,若数据复杂度较低,则会导致“过拟合”问题
如下图,模型复杂度对欠拟合和过拟合的影响:
数据集大小 训练数据集中的样本越少,就越有可能(且更严重地)出现过拟合问题。
随着训练数据量的增加,泛化误差会减少。一般而言,更多的数据不会有什么坏处。
正则化模型技术:权重衰减 正则化,是处理过拟合的常用方法
实际上,正则是一种对搜索范围的约束定义
而 权重衰减(L 2 L_2 L 2 正则化),是最广泛使用的正则化技术之一
均方范数作为硬性限制 通过限制参数值w \mathbf{w} w 的选择范围(通常不限制偏置项b b b ),来控制模型的容量
min l ( w , b ) s . t . ∣ ∣ w ∣ ∣ 2 ≤ θ \min l(\mathbf{w}, b)\ \ \ s.t. ||\mathbf{w}||^2\le\theta min l ( w , b ) s . t .∣∣ w ∣ ∣ 2 ≤ θ
θ \theta θ 越小,意味着更强的正则化
均方范数作为柔性限制⭐ 对于每一个θ \theta θ ,均可找到对应的λ \lambda λ 使得硬性限制改写为柔性限制:
即将上述的限制项θ \theta θ 挪掉,而在损失函数中加入惩罚项。
使用平方范数,便于计算;λ \lambda λ 要除以 2 是为了求导时确保更新表达式整洁
min l ( w , b ) + λ 2 ∣ ∣ w ∣ ∣ 2 \min l(\mathbf{w}, b) + \frac{\lambda}{2}||\mathbf{w}||^2 min l ( w , b ) + 2 λ ∣∣ w ∣ ∣ 2
其中,非负超参数λ \lambda λ 平衡这个新的额外惩罚的损失:
若λ = 0 \lambda = 0 λ = 0 ,相当于没有效果 若λ → ∞ \lambda \rightarrow \infty λ → ∞ ,则w ∗ → 0 \mathbf{w}^*\rightarrow\mathbf{0} w ∗ → 0 当w ∗ \mathbf{w}^* w ∗ 可选择的范围越大时,会使用模型复杂度增大
惩罚项会对大数值的权值进行惩罚,鼓励权值分散,即将所有特征能够运用起来,而不是依赖其中的少量特征。
最佳实践: λ \lambda λ 一般取1 e − 3 1e-3 1 e − 3 或者1 e − 4 1e-4 1 e − 4
参数更新法则(柔性限制) 在计算梯度时,
∂ ∂ w ( l ( w , b ) + λ 2 ∣ ∣ w ∣ ∣ 2 ) = ∂ 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} ∂ w ∂ ( l ( w , b ) + 2 λ ∣∣ w ∣ ∣ 2 ) = ∂ w ∂ l ( w , b ) + λ w
时间t t t 更新参数:
w t + 1 = w t − α ∂ ∂ w ( l ( w , b ) + λ 2 ∣ ∣ w ∣ ∣ 2 ) = ( 1 − α λ ) w t − α ∂ l ( w t , b t ) ∂ w t \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} w t + 1 = w t − α ∂ w ∂ ( l ( w , b ) + 2 λ ∣∣ w ∣ ∣ 2 ) = ( 1 − α λ ) w t − α ∂ w t ∂ l ( w t , b t )
可见,加上了惩罚项后,在更新权重参数时,多了 $-\alpha\lambda $ 项(小于1 1 1 ),也就说,在更新权重参数时,先将权重参数缩小一些,再使其沿梯度下降的方向更新。
正因为− α λ < 1 -\alpha\lambda < 1 − α λ < 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 ): 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)并不是在输入中添加噪音,而是在层之间 加入噪音。
丢弃法对每个元素进行如下扰动:
h i ′ = { 0 , 一定概率 p 下 h 1 − p , 其他情况,即非概率 p h_i'=\begin{cases} 0, &一定概率p下 \\ \frac{h}{1-p}, &其他情况,即非概率p \end{cases} h i ′ = { 0 , 1 − p h , 一定概率 p 下 其他情况,即非概率 p
也就说,每个中间激活值,有一定概率p p p 变为0,有概率1 − p 1 - p 1 − p 会增大(因为1 − p < 1 1-p<1 1 − p < 1 )
此时,E [ h i ′ ] = p × 0 + ( 1 − p ) × h i 1 − p = h = E [ h i ] E[\mathbf{h}_i'] = p \times 0 + (1-p) \times \frac{\mathbf{h}_i}{1-p}=h=E[\mathbf{h}_i] E [ h i ′ ] = p × 0 + ( 1 − p ) × 1 − p h i = h = E [ h i ] ,即加入噪音依然满足期望不变
最佳实践 :作为超参数 的丢弃概率p p p ,一般取0.5 0.5 0.5 或0.9 0.9 0.9
丢弃法在模型训练过程中 丢弃法作用在多层感知机的隐藏 全连接层的输出 上,它将输出项随机置0 0 0 来控制模型复杂度。
丢弃的是前一层的输出,相当于丢弃后一层的输入。
如下图,输出计算不再依赖于h 2 h_2 h 2 或h 5 h_5 h 5 ,而且它们各自梯度在执行反向传播时也会消失,由此输出层计算不能过度依赖于h 1 , . . . , h 5 h_1, ... , h_5 h 1 , ... , h 5 的任何一个元素。
h = σ ( W 1 x + b 1 ) h ′ = d r o p o u t ( h ) o = W 2 h ′ + b 2 y = s o f t m a x ( 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}) h = σ ( W 1 x + b 1 ) h ′ = d ro p o u t ( h ) o = W 2 h ′ + b 2 y = so f t ma x ( o )
丢弃法在推断过程中 预测,即不需要对权重的进行更新时,不会开启 dropout。
dropout 是一个正则项,它仅用于模型训练,为了在更新权重时将模型复杂度降低一些
基于PyTorch框架的实现 使用 PyTorch 提供的 Dropout 函数
1 2 3 import torchfrom torch import nnfrom 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(), nn.Dropout(dropout1), nn.Linear(256 , 256 ), nn.ReLU(), 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)