六、与学习相关的技巧(下)

发布于:2025-09-10 ⋅ 阅读:(25) ⋅ 点赞:(0)

6.4 正则化

机器学习的问题中,过拟合是一个很常见的问题。过拟合指的是只能拟合训练数据,但不能很好地拟合不包含在训练数据中的其他数据的状态。抑制过拟合的技巧也很重要。

6.4.1 过拟合

发生过拟合的原因,主要有以下两个。

• 模型拥有大量参数、表现力强。

• 训练数据少。

这里,我们故意满足这两个条件,制造过拟合现象。为此,要从MNIST数据集原本的60000个训练数据中只选定300个,并且,为了增加网络的复杂度,使用7层网络(每层有100个神经元,激活函数为ReLU)。

下面是用于实验的部分代码

接着是进行训练的代码。和之前的代码一样,按epoch分别算出所有训练数据和所有测试数据的识别精度。

# 1. 加载MNIST手写数字数据集,并做归一化处理
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True)

# 2. 【核心操作】为了人为再现过拟合现象,故意将训练数据从6万张减少到仅300张
#    这使得模型很容易“记住”这300个样本,但无法学会泛化到新数据的规则。
x_train = x_train[:300]
t_train = t_train[:300]

# 3. 初始化一个“大”模型
#    输入层784个神经元(28x28像素),5个隐藏层,每层100个神经元,输出层10个神经元(10个类别)
#    这个模型的参数很多,容量很大,在数据量很少时极易过拟合。
network = MultiLayerNet(input_size=784, hidden_size_list=[100, 100, 100, 100, 100], output_size=10)

# 4. 选择优化器:使用随机梯度下降(SGD),学习率设为0.01
optimizer = SGD(lr=0.01)

# 5. 设置训练参数
max_epochs = 201       # 最大训练轮数(Epoch)
train_size = x_train.shape[0] # 当前训练集大小(300)
batch_size = 100       # 每个批次的样本数
train_loss_list = []   # 用于记录每个Epoch后的训练损失
train_acc_list = []    # 用于记录每个Epoch后的训练集准确率
test_acc_list = []     # 用于记录每个Epoch后的测试集准确率

# 6. 计算每个Epoch包含多少个迭代(Iteration)
#    因为训练数据只有300个,批次大小为100,所以每3个迭代就遍历了一遍全部数据,算作一个Epoch。
iter_per_epoch = max(train_size / batch_size, 1)
epoch_cnt = 0

# 这是一个无限的训练循环 (for i in range(1000000000)),由后面的 break条件来控制退出。
for i in range(1000000000):
    # 7. 随机抽取一个批次(Batch)的数据
    #    batch_mask 是一个包含100个随机索引的数组,用于从300个训练样本中随机选取100个。
    batch_mask = np.random.choice(train_size, batch_size)
    x_batch = x_train[batch_mask] # 获取批次的输入数据
    t_batch = t_train[batch_mask] # 获取批次的目标标签

# 8. 【核心计算】计算梯度
#    这是最关键的一步:通过网络的反向传播功能,计算当前批次数据下的损失函数关于所有参数(W, b)的梯度。
grads = network.gradient(x_batch, t_batch)

# 9. 【更新参数】使用优化器根据计算出的梯度更新网络参数
#    optimizer.update() 方法会执行:params = params - lr * grads
optimizer.update(network.params, grads)

# 10. 检查是否完成了一个Epoch(即是否达到了每iter_per_epoch次迭代)
if i % iter_per_epoch == 0:
    # 11. 评估模型性能
    #    计算模型在当前所有300个训练数据上的准确率
    train_acc = network.accuracy(x_train, t_train)
    #    计算模型在所有10000个测试数据上的准确率
    test_acc = network.accuracy(x_test, t_test)

    # 12. 记录准确率,用于后续绘制学习曲线,观察过拟合
    train_acc_list.append(train_acc)
    test_acc_list.append(test_acc)

    # 13. 增加Epoch计数器
    epoch_cnt += 1

    # 14. 退出条件:如果训练的Epoch数达到了预设的最大值,就跳出循环,结束训练。
    if epoch_cnt >= max_epochs:
        break

现在,我们将这些列表(train_acc_list、test_acc_list)绘成图,结果如图6-20所示。

过了 100 个 epoch 左右后,用训练数据测量到的识别精度几乎都为100%。但是,对于测试数据,离100%的识别精度还有较大的差距。如此大的识别精度差距,是只拟合了训练数据的结果。从图中可知,模型对训练时没有使用的一般数据(测试数据)拟合得不是很好。

6.4.2 权值衰减

权值衰减是一直以来经常被使用的一种抑制过拟合的方法。该方法通过在学习的过程中对大的权重进行惩罚,来抑制过拟合。很多过拟合原本就是因为权重参数取值过大才发生的。

复习一下,神经网络的学习目的是减小损失函数的值。这时,例如为损失函数加上权重的平方范数(L2范数)。这样一来,就可以抑制权重变大。用符号表示的话,如果将权重记为W,L2范数的权值衰减就是

,然后将这个

加到损失函数上。这里,λ是控制正则化强度的超参数。λ设置得越大,对大的权重施加的惩罚就越重。

对于所有权重,权值衰减方法都会为损失函数加上

。因此,在求权重梯度的计算中,要为之前的误差反向传播法的结果加上正则化项的导数λW

L2范数相当于各个元素的平方和。用数学式表示的话,假设有权重W = (w1, w2, ... , wn),则 L2范数可用

计算出来。除了 L2范数,还有 L1范数、L∞范数等。L1范数是各个元素的绝对值之和,相当于|w1| + |w2| + ... + |wn|。L∞范数也称为Max范数,相当于各个元素的绝对值中最大的那一个。L2范数、L1范数、L∞范数都可以用作正则化项,它们各有各的特点,不过这里我们要实现的是比较常用的 L2范数。

现在我们来进行实验。对于刚刚进行的实验,应用λ = 0.1的权值衰减,结果如图6-21所示

如图6-21所示,虽然训练数据的识别精度和测试数据的识别精度之间有差距,但是与没有使用权值衰减的图6-20的结果相比,差距变小了。这说明过拟合受到了抑制。此外,还要注意,训练数据的识别精度没有达到100%(1.0)。

6.4.3 Dropout

作为抑制过拟合的方法,前面我们介绍了为损失函数加上权重的L2范数的权值衰减方法。该方法可以简单地实现,在某种程度上能够抑制过拟合。 但是,如果网络的模型变得很复杂,只用权值衰减就难以应对了。在这种情况下,我们经常会使用Dropout

Dropout是一种在学习的过程中随机删除神经元的方法。训练时,随机选出隐藏层的神经元,然后将其删除。被删除的神经元不再进行信号的传递,如图6-22所示。训练时,每传递一次数据,就会随机选择要删除的神经元。然后,测试时,虽然会传递所有的神经元信号,但是对于各个神经元的输出,要乘上训练时的删除比例后再输出。

下面我们来实现Dropout。这里的实现重视易理解性。不过,因为训练时如果进行恰当的计算的话,正向传播时单纯地传递数据就可以了(不用乘以删除比例),所以深度学习的框架中进行了这样的实现。关于高效的实现,可以参考Chainer中实现的Dropout。

class Dropout:
    def __init__(self, dropout_ratio=0.5):
        self.dropout_ratio = dropout_ratio # 丢弃概率,默认为50%
        self.mask = None # 用于保存前向传播时生成的随机掩码
    
    def forward(self, x, train_flg=True):
        if train_flg: # 训练模式
            # 核心步骤1:生成随机掩码。随机数大于dropout_ratio的位置为True(保留),否则为False(丢弃)
            self.mask = np.random.rand(*x.shape) > self.dropout_ratio
            # 核心步骤2:将输入x与掩码相乘。被丢弃的神经元输出为0,保留的神经元输出保持不变
            return x * self.mask
        else: # 测试/推理模式
            # 不丢弃任何神经元,但对所有输出进行缩放,乘以保留概率 (1 - dropout_ratio)
            return x * (1.0 - self.dropout_ratio)
            
    def backward(self, dout):
        # 将上游传来的梯度 dout 与训练时保存的掩码相乘
        # 这意味着,在前向传播中被丢弃的神经元(mask对应位置为False),其梯度也将被置为0
        # 只有前向传播时被保留的神经元,才会参与误差的反向传播和参数更新
        return dout * self.mask

这里的要点是,每次正向传播时,self.mask中都会以False的形式保存要删除的神经元。self.mask会随机生成和x形状相同的数组,并将值比dropout_ratio大的元素设为True。反向传播时的行为和ReLU相同。也就是说,正向传播时传递了信号的神经元,反向传播时按原样传递信号;正向传播时没有传递信号的神经元,反向传播时信号将停在那里。

现在,我们使用MNIST数据集进行验证,以确认Dropout的效果。

图6-23中,通过使用Dropout,训练数据和测试数据的识别精度的差距变小了。并且,训练数据也没有到达100%的识别精度。像这样,通过使用Dropout,即便是表现力强的网络,也可以抑制过拟合

6.5 超参数的验证

神经网络中,除了权重和偏置等参数,超参数(hyper-parameter)也经常出现。这里所说的超参数是指,比如各层的神经元数量、batch大小、参数更新时的学习率或权值衰减等。如果这些超参数没有设置合适的值,模型的性能就会很差。虽然超参数的取值非常重要,但是在决定超参数的过程中一般会伴随很多的试错。

6.5.1 验证数据

之前我们使用的数据集分成了训练数据和测试数据,训练数据用于学习,测试数据用于评估泛化能力。由此,就可以评估是否只过度拟合了训练数据(是否发生了过拟合),以及泛化能力如何等

用测试数据确认超参数的值的“好坏”,就会导致超参数的值被调整为只拟合测试数据。这样的话,可能就会得到不能拟合其他数据、泛化能力低的模型。

因此,调整超参数时,必须使用超参数专用的确认数据。用于调整超参数的数据,一般称为验证数据(validation data)。

训练数据用于参数(权重和偏置)的学习,验证数据用于超参数的性能评估。为了确认泛化能力,要在最后使用(比较理想的是只用一次)测试数据。

如果是MNIST数据集,获得验证数据的最简单的方法就是从训练数据中事先分割20%作为验证数据,代码如下所示。

这里,分割训练数据前,先打乱了输入数据和教师标签。这是因为数据集的数据可能存在偏向(比如,数据从“0”到“10”按顺序排列等)。这里使用的shuffle_dataset函数利用了np.random.shuffle

接下来,我们使用验证数据观察超参数的最优化方法

6.5.2 超参数的最优化

进行超参数的最优化时,逐渐缩小超参数的“好值”的存在范围非常重要。所谓逐渐缩小范围,是指一开始先大致设定一个范围,从这个范围中随机选出一个超参数(采样),用这个采样到的值进行识别精度的评估;然后,多次重复该操作,观察识别精度的结果,根据这个结果缩小超参数的“好值”的范围。

通过重复这一操作,就可以逐渐确定超参数的合适范围。

超参数的范围只要“大致地指定”就可以了。所谓“大致地指定”,是指像0.001(103 )到1000(103 )这样,以“10的阶乘”的尺度指定范围

在超参数的最优化中,要注意的是深度学习需要很长时间(比如,几天或几周)。因此,在超参数的搜索中,需要尽早放弃那些不符合逻辑的超参数。于是,在超参数的最优化中,减少学习的epoch,缩短一次评估所需的时间是一个不错的办法。

以上就是超参数的最优化的内容,简单归纳一下,如下所示

反复进行上述操作,不断缩小超参数的范围,在缩小到一定程度时,从该范围中选出一个超参数的值。这就是进行超参数的最优化的一种方法。

6.5.3 超参数最优化的实现

现在,我们使用MNIST数据集进行超参数的最优化。这里我们将学习率和控制权值衰减强度的系数(下文称为“权值衰减系数”)这两个超参数的搜索问题作为对象

如前所述,通过从 0.001(103 )到 1000(103 )这样的对数尺度的范围中随机采样进行超参数的验证。这在Python中可以写成10 ** np.random.uniform(-3, 3)。在该实验中,权值衰减系数的初始范围为108 到104 ,学习率的初始范围为106 到102 。此时,超参数的随机采样的代码如下所示。

像这样进行随机采样后,再使用那些值进行学习。之后,多次使用各种超参数的值重复进行学习,观察合乎逻辑的超参数在哪里。

下面进行实验,结果如图6-24所示。

图6-24中,按识别精度从高到低的顺序排列了验证数据的学习的变化。

从图中可知,直到“Best-5”左右,学习进行得都很顺利。因此,我们来观察一下“Best-5”之前的超参数的值(学习率和权值衰减系数),结果如下所示。

从这个结果可以看出,学习率在0.001到0.01、权值衰减系数在108 到 106 之间时,学习可以顺利进行。像这样,观察可以使学习顺利进行的超参数的范围,从而缩小值的范围。然后,在这个缩小的范围中重复相同的操作。这样就能缩小到合适的超参数的存在范围。

6.6 小结

•参 数 的 更 新 方 法,除 了 SGD 之 外,还 有 Momentum、AdaGrad、Adam等方法。

• 权重初始值的赋值方法对进行正确的学习非常重要。

• 作为权重初始值,Xavier初始值、He初始值等比较有效。

• 通过使用Batch Normalization,可以加速学习,并且对初始值变得健壮。

• 抑制过拟合的正则化技术有权值衰减、Dropout等。

• 逐渐缩小“好值”存在的范围是搜索超参数的一个有效方法。


网站公告

今日签到

点亮在社区的每一天
去签到