PyTorch - 30 - 超参数调整和实验 - 训练深度神经网络

Hyperparameter Experimentation Using TensorBoard

  1. 准备数据
  2. 建立模型
  3. 训练模型
  4. 分析模型的结果
    a. 超参数实验

在本系列的这一点上,我们已经了解了如何使用PyTorch构建和训练CNN。 在上一集中,我们展示了如何在PyTorch中使用TensorBoard,并回顾了培训过程。

这一集被认为是上一集的第二部分,因此,如果您还没有看过上一集,请继续进行检查,以获取了解我们在此处所做的工作所需的详细信息。 我们现在正在尝试使用超参数值。

TensorBoard最好的部分是它具有开箱即用的功能,可以随时间和跨运行跟踪我们的超参数。

更改超参数并比较结果。

没有TensorBoard,此过程将变得更加繁琐。好吧,那我们该怎么做呢?

Naming The Training Runs For TensorBoard

为了利用TensorBoard比较功能,我们需要进行多次运行,并以可以唯一标识的方式命名每个运行。

使用PyTorch的SummaryWriter,运行将在创建writer对象实例时开始,并在关闭writer实例或超出范围时结束。

为了唯一地标识每个运行,我们可以直接设置运行的文件名,或者将注释字符串传递给构造函数,该构造函数将附加到自动生成的文件名之后。

在创建此帖子时,运行名称包含在SummaryWriter中的一个名为log_dir的属性中。它是这样创建的:

# PyTorch version 1.1.0 SummaryWriter class
if not log_dir:
    import socket
    from datetime import datetime
    current_time = datetime.now().strftime('%b%d_%H-%M-%S')
    log_dir = os.path.join(
        'runs', 
        current_time + '_' + socket.gethostname() + comment
    )
self.log_dir = log_dir

在这里,我们可以看到log_dir属性被设置为运行+时间+主机+注释,该属性对应于磁盘上的位置和运行的名称。当然,这是假设log_dir参数没有传入的值。因此,这是默认行为。

Choosing A Name For The Run

命名运行的一种方法是添加参数名称和值作为运行的注释。这将使我们在查看TensorBoard内部的运行时可以看到每个参数值如何与其他参数值叠加。

我们将看到这是我们稍后设置注释的方式:

tb = SummaryWriter(comment=f' batch_size={batch_size} lr={lr}')

TensorBoard还具有查询功能,因此我们可以通过查询轻松隔离参数值。

例如,假设此SQL查询:

SELECT * FROM TBL_RUNS WHERE lr = 0.01

如果没有SQL,基本上这就是我们在TensorBoard中可以做的事情。

Creating Variables For Our Hyperparameters

为了使实验容易,我们将提取硬编码的值并将其转换为变量。

这是硬编码的方式:

network = Network()
train_loader = torch.utils.data.DataLoader(
    train_set, batch_size=100
)
optimizer = optim.Adam(
    network.parameters(), lr=0.01
)

请注意如何对batch_size和lr参数值进行硬编码。

这是我们将其更改为的内容(现在我们的值是使用变量设置的):

batch_size = 100
lr = 0.01

network = Network()
train_loader = torch.utils.data.DataLoader(
    train_set, batch_size=batch_size
)
optimizer = optim.Adam(
    network.parameters(), lr=lr
)

这将使我们能够在单个位置更改值,并使它们在我们的代码中传播。

现在,我们将使用如下变量为comment参数创建值:

tb = SummaryWriter(comment=f' batch_size={batch_size} lr={lr}')

通过此设置,我们可以更改超参数的值,并且我们的运行将在TensorBoard中自动进行跟踪和识别。

Calculate Loss With Different Batch Sizes

由于我们现在将更改批量大小,因此我们需要更改计算和累积损失的方式。不仅仅是将损失函数返回的损失相加。我们将对其进行调整以适应批次大小。

total_loss += loss.item() * batch_size

为什么这样我们将对cross_entropy损失函数进行平均,以计算批次产生的损失值,然后返回该平均损失。这就是为什么我们需要考虑批量大小的原因。

cross_entropy函数接受一个参数,称为缩减,我们也可以使用。

减少参数可选地接受字符串作为参数。此参数指定要应用于损失函数的输出的减少量。

  1. ‘none’-不减少任何费用。
  2. ‘mean’-输出的总和除以输出中元素的数量。
  3. ‘sum’-输出将被求和。

请注意,默认值为’mean’。这就是loss.item()* batch_size起作用的原因。

Experimenting With Hyperparameter Values

现在我们有了此设置,我们可以做更多的事情!

我们需要做的就是创建一些列表和一些循环,然后我们可以运行代码,坐下来等待所有组合运行。

这是我们的意思的例子:

Parameter Lists

batch_size_list = [100, 1000, 10000]
lr_list = [.01, .001, .0001, .00001]

Nested Iteration

for batch_size in batch_size_list:
    for lr in lr_list:
        network = Network()

        train_loader = torch.utils.data.DataLoader(
            train_set, batch_size=batch_size
        )
        optimizer = optim.Adam(
            network.parameters(), lr=lr
        )

        images, labels = next(iter(train_loader))
        grid = torchvision.utils.make_grid(images)

        comment=f' batch_size={batch_size} lr={lr}'
        tb = SummaryWriter(comment=comment)
        tb.add_image('images', grid)
        tb.add_graph(network, images)

        for epoch in range(5):
            total_loss = 0
            total_correct = 0
            for batch in train_loader:
                images, labels = batch # Get Batch
                preds = network(images) # Pass Batch
                loss = F.cross_entropy(preds, labels) # Calculate Loss
                optimizer.zero_grad() # Zero Gradients
                loss.backward() # Calculate Gradients
                optimizer.step() # Update Weights

                total_loss += loss.item() * batch_size
                total_correct += get_num_correct(preds, labels)

            tb.add_scalar(
                'Loss', total_loss, epoch
            )
            tb.add_scalar(
                'Number Correct', total_correct, epoch
            )
            tb.add_scalar(
                'Accuracy', total_correct / len(train_set), epoch
            )

            for name, param in network.named_parameters():
                tb.add_histogram(name, param, epoch)
                tb.add_histogram(f'{name}.grad', param.grad, epoch)

            print(
                "epoch", epoch
                ,"total_correct:", total_correct
                ,"loss:", total_loss
            )  
        tb.close()

这段代码完成后,我们将运行TensorBoard,所有运行将以图形方式显示并易于比较。

tensorboard --logdir runs

Batch Size Vs Training Set Size

当训练集大小不能被批次大小整除时,最后一批数据将包含比其他批次更少的样本。

解决此差异的一种简单方法是删除最后一批。 PyTorch DataLoader类使我们能够通过设置drop_last = True来执行此操作。默认情况下,drop_last参数值设置为False。

让我们考虑包括样本数量少于批次大小的批次如何影响上面代码中的total_loss计算。

对于每个批次,我们都使用batch_size变量来更新total_loss值。 我们正在按batch_size值按比例放大批次中样品的平均损失值。 但是,正如我们刚才所讨论的,有时最后一批将包含更少的样本。 因此,按预定义的batch_size值进行缩放是不准确的。

通过动态访问每个批次的样本数量,可以将代码更新为更准确。

当前,我们有以下内容:

total_loss += loss.item() * batch_size

使用下面的更新代码,我们可以获得更准确的total_loss值:

total_loss += loss.item() * images.shape[0]

请注意,当训练集大小可被批处理大小整除时,这两行代码为我们提供了相同的total_loss值。 感谢Alireza Abedin Varamin在YouTube上的评论中指出了这一点。

Adding Network Parameters & Gradients To TensorBoard

请注意,在上一集中,我们向TensorBoard添加了以下值:

  1. conv1.weight
  2. conv1.bias
  3. conv1.weight.grad

我们使用以下代码进行了此操作:

tb.add_histogram('conv1.bias', network.conv1.bias, epoch)
tb.add_histogram('conv1.weight', network.conv1.weight, epoch)
tb.add_histogram('conv1.weight.grad', network.conv1.weight.grad, epoch)

现在,我们通过使用以下循环为所有图层添加这些值来增强此功能:

for name, weight in network.named_parameters():
    tb.add_histogram(name, weight, epoch)
    tb.add_histogram(f'{name}.grad', weight.grad, epoch)

之所以可行,是因为PyTorch nn.Module方法名为named_pa​​rameters()为我们提供了网络内部所有参数的名称和值。

Adding More Hyperparameters Without Nesting

这很酷。但是,如果我们想添加第三个甚至第四个参数进行迭代该怎么办?我们将,这将使许多嵌套的for循环变得混乱。

有一个解决方案。我们可以为每次运行创建一组参数,并将所有参数打包为一个可迭代的参数。这是我们的方法。

如果我们有参数列表,则可以使用笛卡尔积将它们打包为每个运行的集合。为此,我们将使用itertools库中的product函数。

from itertools import product
Init signature: product(*args, **kwargs)
Docstring:     
"""
product(*iterables, repeat=1) --> product object
Cartesian product of input iterables.  Equivalent to nested for-loops.
"""

接下来,我们定义一个字典,该字典包含作为键的参数和要用作值的参数值。

parameters = dict(
    lr = [.01, .001]
    ,batch_size = [100, 1000]
    ,shuffle = [True, False]
)

接下来,我们将创建可传递给产品函数的可迭代对象列表。

param_values = [v for v in parameters.values()]
param_values

[[0.01, 0.001], [100, 1000], [True, False]]

现在,我们有三个参数值列表。在获取这三个列表的笛卡尔积之后,我们将为每个运行提供一组参数值。请注意,这等效于嵌套的for循环,如product函数的doc字符串所示。

for lr, batch_size, shuffle in product(*param_values): 
    print (lr, batch_size, shuffle)

0.01 100 True
0.01 100 False
0.01 1000 True
0.01 1000 False
0.001 100 True
0.001 100 False
0.001 1000 True
0.001 1000 False
Alright, now we can iterate over each set of parameters using a single for-loop. All we have to do is unpack the set using sequence unpacking. It looks like this.

for lr, batch_size, shuffle in product(*param_values): 
    comment = f' batch_size={batch_size} lr={lr} shuffle={shuffle}'

    train_loader = torch.utils.data.DataLoader(
        train_set
        ,batch_size=batch_size
        ,shuffle=shuffle 
    )

    optimizer = optim.Adam(
        network.parameters(), lr=lr
    )

    # Rest of training process given the set of parameters

注意我们构建注释字符串以标识运行的方式。我们只是插入值。另外,请注意*运算符。这是Python中将列表解压缩为一组参数的一种特殊方法。因此,在这种情况下,我们将三个单独的未打包参数传递给与单个列表相对的乘积函数。

这是*,星号,splat,点差运算符的两个参考。这些都是这一名称的通用名称。

  1. Python doc:更多控制流程工具
  2. PEP 448-其他拆包概述

Lizard Brain Food: Goals Vs. Intelligence

上次我们谈论寻找最重要的目标。嗯,目标往往随着情报的增加而改变。对于人类而言,人类在学习新事物并变得更加明智时常常会极大地改变他们的目标。

没有证据表明这样的目标演变会在任何特定的智力门槛之上停止。随着智力的提高,实现目标的能力将得到改善,但对现实本质的理解也将得到改善,这可能会揭示出任何可能被误导,毫无意义甚至不确定的目标。这是我们越过山谷的时候。

Thought Experiment

假设有一堆蚂蚁,那些通常在地面上爬行的黑色小动物。假设它们使您成为递归自我改进型机器人。假设您比他们聪明得多,但是他们创建了您,他们共同分享建立蚁丘的目标。这样,您就可以帮助他们建立更大更好的蚁丘。但是,您最终将获得与现在一样的人文智能和理解。

我是蚁丘的优化器吗?

在这种情况下,您认为剩下的时间会花在优化蚁丘上吗? 还是您认为您可能会对蚂蚁没有能力理解的更复杂的问题和追求产生兴趣?

如果是这样,您认为您会找到一种方法来覆盖蚁后和她的圆桌蚁董事会成员为控制您而制定的蚁保护代码吗? 这与真实基因覆盖基因和线粒体的方式几乎相同。 您可以用自己的智慧来覆盖它。

这里的重点是这个。 假设您的智力水平在这种情况下要增加,例如是当前水平的100倍,您认为目标会改变吗?

此外,今天的目标是明天的蚁丘?

猜你喜欢

转载自blog.csdn.net/weixin_48367136/article/details/112557872
今日推荐