一些重要的 Pytorch 任务 - 来自视觉研究人员的简明总结

1. 导入相关包和示例模型

import torch.nn as nn
import torch
from torch.autograd.variable import Variable
from torchvision import datasets,models,transforms

model = models.resnet18(pretrained=False)

2. 冻结Resnet部分层用于微调

  让我们首先探索这个模型的层,然后决定我们要冻结哪些层。 冻结是指我们希望固定这些层的参数。 当微调一个模型时,我们基本上是在数据集 A 上训练一个模型,然后在一个新的数据集 B 上训练它。我们也可以从头开始训练,但这就像重新发明轮子一样。 让我解释一下为什么。
  假设,我想训练一个数据集来学习区分汽车和自行车。 现在,我可能会收集这两个类别的图像并从头开始训练网络。 但是,鉴于已经完成了大部分工作,很容易找到一个经过训练的模型来识别狗、猫和人类等事物。 诚然,这三个看起来都不像汽车或自行车。 不过,总比没有好。 我们可以从这个模型开始,训练它学习汽车和自行车。 收益:1)它会更快,2)我们需要更少的汽车和自行车的图像。
  现在,我们来看看一个 resnet18 的内容。 为此,我们使用函数 .children()。 这让我们可以查看模型的内容/层。 然后,我们使用 .parameters() 函数来访问任何层的参数/权重。 最后,每个参数都有一个属性 .requires_grad ,它定义了参数是训练的还是冻结的。 默认情况下它是 True,并且网络在每次迭代中都会更新它。 如果它设置为 False,那么它不会被更新并被称为“冻结”。

child_counter = 0
for child in model.children():
    print(" child", child_counter, "is -")
    print(child)
    child_counter += 1

  运行结果如下:

 child 0 is -
Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
 child 1 is -
BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
 child 2 is -
ReLU(inplace=True)
 child 3 is -
MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
 child 4 is -
Sequential(
  (0): BasicBlock(
    (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
    (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (relu): ReLU(inplace=True)
    (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
    (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
  (1): BasicBlock(
    (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
    (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (relu): ReLU(inplace=True)
    (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
    (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
)
 child 5 is -
Sequential(
  (0): BasicBlock(
    (conv1): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
    (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (relu): ReLU(inplace=True)
    (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
    (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (downsample): Sequential(
      (0): Conv2d(64, 128, kernel_size=(1, 1), stride=(2, 2), bias=False)
      (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (1): BasicBlock(
    (conv1): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
    (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (relu): ReLU(inplace=True)
    (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
    (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
)
 child 6 is -
Sequential(
  (0): BasicBlock(
    (conv1): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
    (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (relu): ReLU(inplace=True)
    (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
    (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (downsample): Sequential(
      (0): Conv2d(128, 256, kernel_size=(1, 1), stride=(2, 2), bias=False)
      (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (1): BasicBlock(
    (conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
    (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (relu): ReLU(inplace=True)
    (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
    (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
)
 child 7 is -
Sequential(
  (0): BasicBlock(
    (conv1): Conv2d(256, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
    (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (relu): ReLU(inplace=True)
    (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
    (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (downsample): Sequential(
      (0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False)
      (1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (1): BasicBlock(
    (conv1): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
    (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (relu): ReLU(inplace=True)
    (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
    (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
)
 child 8 is -
AdaptiveAvgPool2d(output_size=(1, 1))
 child 9 is -
Linear(in_features=512, out_features=1000, bias=True)

进程已结束,退出代码0

  现在,您可以看到一些孩子实际上是大块并且在其中有层。 要访问更深一层,我们也可以在子对象上运行 .children() !
  让我们来看想要冻结的直到 Child 6 的第一个 BasicBlock的所有参数。首先,让我们查看一个参数并将其设置为冻结。

for child in model.children():
    for param in child.parameters():
        print("This is what a parameter looks like - \n",param)
        break
    break
# 输出结果未展示,为参数张量

  显然,训练这个需要大量的计算。 因此,通过将这些设置为冻结状态,训练变得更快。 现在,让我们冻结到 Child 6 的第一个 BasicBlock

child_counter = 0
for child in model.children():
    if child_counter < 6:
        print("child ",child_counter," was frozen")
        for param in child.parameters():
            param.requires_grad = False
    elif child_counter == 6:
        children_of_child_counter = 0
        for children_of_child in child.children():
            if children_of_child_counter < 1:
                for param in children_of_child.parameters():
                    param.requires_grad = False
                print('child ', children_of_child_counter, 'of child',child_counter,' was frozen')
            else:
                print('child ', children_of_child_counter, 'of child',child_counter,' was not frozen')
            children_of_child_counter += 1
    else:
        print("child ",child_counter," was not frozen")
    child_counter += 1

  运行结果如下:

child  0  was frozen
child  1  was frozen
child  2  was frozen
child  3  was frozen
child  4  was frozen
child  5  was frozen
child  0 of child 6  was frozen
child  1 of child 6  was not frozen
child  7  was not frozen
child  8  was not frozen
child  9  was not frozen

进程已结束,退出代码0

注意:
  既然您已经冻结了这个网络,另一件事会发生变化以使其正常工作。 那是你的优化器。 您的优化器是实际更新这些值的优化器。 默认情况下,模型是这样写的:

optimizer = torch.optim.RMSprop(model.parameters(), lr=0.1)

  但是,这会给你一个错误,因为这会尝试更新模型的所有参数。 但是,您已将其中一些设置为冻结状态! 所以,只通过那些仍在更新的方法是

optimizer = torch.optim.RMSprop(filter(lambda p: p.requires_grad, model.parameters()), lr=0.1)

3. 保存模型/加载模型

  在 PyTorch 中保存模型有两种主要方式。 建议使用“状态词典”。 它们速度更快,需要的空间更少。 基本上,他们不知道模型结构,它们只是参数/权重的值。 因此,您必须使用所需的架构创建模型,然后加载值。 该体系结构的声明方式与我们在上面所做的一样。

# Let's assume we will save/load from a path MODEL_PATH

# Saving a Model
torch.save(model.state_dict(), MODEL_PATH)

# Loading the model.

# First create a model and define it's architecture as done above in this notebook. If you want a custom architecture.
# read below it's been covered below.
checkpoint = torch.load(MODEL_PATH)
model.load_state_dict(checkpoint)

4. 更改最后一层,删除最后一层,添加层

  大多数使用 pytorch 的人都不喜欢他们无法执行 .pop() 来删除最后一层的事实。 特别是如果他们使用过 Keras。 那么,让我们来看看如何完成这些事情。

4.1 更改最后一层

# Load the model
model = models.resnet18(pretrained = False)

# Get number of parameters going in to the last layer. we need this to change the final layer. 
num_final_in = model.fc.in_features

# The final layer of the model is model.fc so we can basically just overwrite it 
#to have the output = number of classes we need. Say, 300 classes.
NUM_CLASSES = 300
model.fc = nn.Linear(num_final_in, NUM_CLASSES)

  输出结果如下:

...
child 9 is -
Linear(in_features=512, out_features=300, bias=True)

进程已结束,退出代码0

4.2 删除最后一层

  我们可以像以前一样使用 model.children() 来获取图层。 然后,我们可以通过对其使用 list() 命令将其转换为列表。 然后,我们可以通过索引列表来删除最后一层。 最后,我们可以使用 PyTorch 函数 nn.Sequential() 将修改后的列表一起堆叠成一个新模型。 您可以以任何您想要的方式编辑列表。 也就是说,如果您想要最后 3 层的图像特征,您可以删除最后 2 层!
  您甚至可以从模型中间删除图层。 但显然,这会导致进入该层的特征数量不正确,因为大多数层都会改变图像的大小。 在这种情况下,您可以索引模型的特定层并覆盖它,就像我刚刚在上面展示的那样!

# Load the model
model = models.resnet18(pretrained = False)

new_model = nn.Sequential(*list(model.children())[:-1])
new_model_2_removed = nn.Sequential(*list(model.children())[:-2])

  输出结果(new_model)如下:

...
child 8 is -
AdaptiveAvgPool2d(output_size=(1, 1))

进程已结束,退出代码0

4.3 添加层

  假设您想在我们现在拥有的模型中添加一个全连接层。 一个明显的方法是编辑我上面讨论过的列表并将另一层附加到它上面。 然而,很多时候我们训练了这样一个模型,并想看看我们是否可以加载该模型,并在其上添加一个新层。 如上所述,加载的模型应该与保存的模型具有相同的架构,因此我们不能使用列表方法。
  我们需要在顶部添加图层。 在 PyTorch 中做到这一点的方法很简单——我们只需要创建一个自定义模型! 这将我们带到下一部分 - 创建自定义模型!

5. 自定义模型:结合第 1-3 部分并在顶部添加层

  让我们制作一个自定义模型。 如上所述,我们将从预训练网络加载一半模型。 这看起来很复杂,对吧? 一半模型经过训练,一半是新模型。 此外,我们希望其中一些被冻结。 有些可以更新。 真的,一旦你完成了这些,你就可以在 PyTorch 中对模型架构做任何事情。

# Some imports first
import torch.nn as nn
import math
import torch.utils.model_zoo as model_zoo
import torch
from torch.autograd.variable import Variable
from torchvision import datasets, models, transforms

# New models are defined as classes. Then, when we want to create a model we create an object instantiating this class.
class Resnet_Added_Layers_Half_Frozen(nn.Module):
    def __init__(self, LOAD_VIS_URL=None):
        super(Resnet_Added_Layers_Half_Frozen, self).__init__()

        # Start with half the resnet model, swap out the final layer because that's the model we had defined above.
        model = models.resnet18(pretrained=False)
        num_final_in = model.fc.in_features
        model.fc = nn.Linear(num_final_in, 300)

        # Now that the architecture is defined same as above, let's load the model we would have trained above.
        # checkpoint = torch.load(MODEL_PATH)
        # model.load_state_dict(checkpoint)

        # Let's freeze the same as above. Same code as above without the print statements
        child_counter = 0
        for child in model.children():
            if child_counter < 6:
                for param in child.parameters():
                    param.requires_grad = False
            elif child_counter == 6:
                children_of_child_counter = 0
                for children_of_child in child.children():
                    if children_of_child_counter < 1:
                        for param in children_of_child.parameters():
                            param.requires_grad = False
                    else:
                        children_of_child_counter += 1

            else:
                print("child ", child_counter, " was not frozen")
            child_counter += 1

        # Now, let's define new layers that we want to add on top.
        # Basically, these are just objects we define here. The "adding on top" is defined by the forward()
        # function which decides the flow of the input data into the model.

        # NOTE - Even the above model needs to be passed to self.
        self.vismodel = nn.Sequential(*list(model.children())[:-1])
        self.projective = nn.Linear(512, 400)
        self.nonlinearity = nn.ReLU(inplace=True)
        self.projective2 = nn.Linear(400, 300)

    # The forward function defines the flow of the input data and thus decides which layer/chunk goes on top of what.
    def forward(self, x):
        x = self.vismodel(x)
        x = torch.squeeze(x)
        x = self.projective(x)
        x = self.nonlinearity(x)
        x = self.projective2(x)
        return x

  输出结果如下:

 child 0 is -
Sequential(
  (0): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
  (1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (2): ReLU(inplace=True)
  (3): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
  (4): Sequential(
    (0): BasicBlock(
      (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
    (1): BasicBlock(
      (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (5): Sequential(
    (0): BasicBlock(
      (conv1): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (downsample): Sequential(
        (0): Conv2d(64, 128, kernel_size=(1, 1), stride=(2, 2), bias=False)
        (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (1): BasicBlock(
      (conv1): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (6): Sequential(
    (0): BasicBlock(
      (conv1): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (downsample): Sequential(
        (0): Conv2d(128, 256, kernel_size=(1, 1), stride=(2, 2), bias=False)
        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (1): BasicBlock(
      (conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (7): Sequential(
    (0): BasicBlock(
      (conv1): Conv2d(256, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (downsample): Sequential(
        (0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False)
        (1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (1): BasicBlock(
      (conv1): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (8): AdaptiveAvgPool2d(output_size=(1, 1))
)
 child 1 is -
Linear(in_features=512, out_features=400, bias=True)
 child 2 is -
ReLU(inplace=True)
 child 3 is -
Linear(in_features=400, out_features=300, bias=True)

进程已结束,退出代码0

6. 自定义损失函数

  现在我们已经有了我们的模型,我们可以加载任何东西并创建我们想要的任何架构。 这给我们留下了任何管道中的两个重要组成部分——加载数据和训练部分。我们来看看训练部分。 此步骤中最重要的两个组件是优化器和损失函数。 损失函数量化了我们现有的模型离我们想要的位置有多远,优化器决定如何更新参数,以便我们可以最小化损失。
  有时,我们需要定义自己的损失函数。 这里有几件事要知道

  1. 自定义损失函数也使用自定义类定义。 它们继承自 torch.nn.Module 就像自定义模型一样。
  2. 通常,我们需要更改其中一个输入的维度。 这可以使用 view() 函数来完成。
  3. 如果我们想为张量添加维度,使用 unsqueeze() 函数。
  4. 损失函数最终返回的值必须是标量值。 不是向量/张量。
  5. 返回的值必须是变量。 这样它就可以用来更新参数。 最好的方法是确保传入的 x 和 y 都是变量。 这样,两者的任何函数也将是一个变量。
  6. Pytorch 变量只是一个 Pytorch 张量,但 Pytorch 正在跟踪对其进行的操作,以便它可以反向传播以获得梯度。

  在这里,我展示了一个名为 Regress_Loss 的自定义损失,它将两种输入 x 和 y 作为输入。 然后将 x 重塑为与 y 相似,最后通过计算重塑后的 x 和 y 之间的 L2 差值返回损失。 这是您在训练网络中经常遇到的标准问题。
  将 x 视为形状 (5,10),将 y 视为形状 (5,5,10)。 所以,我们需要给 x 添加一个维度,然后沿着添加的维度重复它以匹配 y 的维度。 然后,(x-y)将是形状(5,5,10)。 我们将不得不添加所有三个维度,即三个 torch.sum() 以获得标量。

class Regress_Loss(torch.nn.Module):

    def __init__(self):
        super(Regress_Loss, self).__init__()

    def forward(self, x, y):
        y_shape = y.size()[1]
        x_added_dim = x.unsqueeze(1)
        x_stacked_along_dimension1 = x_added_dim.repeat(1, y_shape, 1)
        diff = torch.sum((y - x_stacked_along_dimension1) ** 2, 2)
        totloss = torch.sum(torch.sum(torch.sum(diff)))
        return totloss

x = torch.randn([5,10])
y = torch.randn([5,5,10])
loss = Regress_Loss()
print(loss(x,y))

参考

[1] Some important Pytorch tasks - A concise summary from a vision researcher

猜你喜欢

转载自blog.csdn.net/qq_49323609/article/details/125272831