timm教程翻译:(四)Model Architectures (WIP)以及 Models API and Pretrained weights

https://timm.fast.ai/model_architectures

1. Model Architectures (WIP)

所包含的模型架构来源广泛。以下列出了各种来源,包括论文、Ross 重写/改编的原始实现(“参考代码”)以及他直接利用的 PyTorch 实现(“代码”)。

大多数包含的模型都具有预训练权重。这些权重可能:

  • 来自原始来源
  • 我自己从原始实现移植到其他框架(例如 TensorFlow 模型)
  • 使用包含的训练脚本从头开始训练

预训练权重的验证结果可在此处查看。

2. Models API and Pretrained weights

2.2 List of models supported by timm

timm 支持多种预训练和非预训练模型,适用于多种基于图像的任务。

要获取完整的模型列表,请使用 timm 中的 list_models 函数,如下所示。list_models 函数返回 timm 支持的模型列表,按字母顺序排列。我们只查看以下排名前 5 的模型。

import timm 

timm.list_models()[:5]
['adv_inception_v3',
 'cspdarknet53',
 'cspdarknet53_iabn',
 'cspresnet50',
 'cspresnet50d']

一般来说,你总是希望在 timm 中使用工厂函数。具体来说,你希望使用 timm 中的 create_model 函数来创建任何模型。使用 create_model 函数可以创建 timm.list_models() 中列出的任何模型。还有一些很棒的额外功能,我们稍后会介绍。现在,我们来看一个简单的例子。

import random
import torch

random_model_to_create = random.choice(timm.list_models())
random_model_to_create
'resnet50d'
model = timm.create_model(random_model_to_create)
x     = torch.randn(1, 3, 224, 224)
model(x).shape
torch.Size([1, 1000])

在上面的例子中,我们在 timm.list_models() 中随机选择一个模型名称,创建它,并将一些虚拟输入数据传递给模型以获得输出。通常情况下,你不会想创建这样的随机模型,这只是一个例子,用来说明 timm.list_models() 中的所有模型都支持 timm.create_model() 函数。使用 timm 创建模型真的非常简单。
timm 是否为这些模型提供了预训练权重?

当然!timm 希望让研究人员和实践者能够非常轻松地进行实验,并支持大量具有预训练权重的模型。这些预训练权重包括:

  • 直接从原始代码中使用
  • 由 Ross 从其原始实现(例如 TensorFlow 模型)移植到其他框架
  • 使用内置的训练脚本 (train.py) 从头开始​​训练。训练这些模型的具体命令(包含超参数)在“训练脚本”部分有详细说明。

为了列出所有具有预训练权重的模型,timm 提供了一个便捷参数 pretrained,可以像下面这样传入 list_models 函数。我们只列出返回结果排名前 5 的模型。

timm.list_models(pretrained=True)[:5]
['adv_inception_v3',
 'cspdarknet53',
 'cspresnet50',
 'cspresnext50',
 'densenet121']

注意:仅列出排名前 5 的预训练模型,我们可以看出 timm 目前还没有 cspdarknet53_iabn 或 cspresnet50d 等模型的预训练权重。对于有硬件条件的新贡献者来说,这是一个绝佳的机会,可以使用训练脚本在 Imagenet 数据集上预训练模型并分享这些权重。

My dataset doesn’t consist of 3-channel images - what now?

您可能已经知道,ImageNet 数据由 3 通道 RGB 图像组成。因此,为了能够在大多数库中使用预训练权重,该模型需要 3 通道输入图像。

torchvision raises Exception

import torchvision

m = torchvision.models.resnet34(pretrained=True)

# single-channel image (maybe x-ray)
x = torch.randn(1, 1, 224, 224)

# `torchvision` raises error
try: m(x).shape
except Exception as e: print(e)
Given groups=1, weight of size [64, 3, 7, 7], expected input[1, 1, 224, 224] \
to have 3 channels, but got 1 channels instead

如上所示,torchvision 的这些预训练权重不适用于单通道输入图像。为了解决这个问题,大多数从业者会将单通道输入图像转换为三通道图像,方法是复制单通道像素来创建三通道图像。

基本上,上面的 torchvision 抱怨说它期望输入有 3 个通道,但得到的却是 1 个通道。

# 25-channel image (maybe satellite image)
x = torch.randn(1, 25, 224, 224)

# `torchvision` raises error
try: m(x).shape
except Exception as e: print(e)
Given groups=1, weight of size [64, 3, 7, 7], expected input[1, 25, 224, 224] \
to have 3 channels, but got 25 channels instead

Torchvision 再次引发错误,这次除了不使用预训练权重并从随机初始化的权重开始之外,没有其他解决方法可以解决此错误。

timm 有办法处理这些异常

m = timm.create_model('resnet34', pretrained=True, in_chans=1)

# single channel image
x = torch.randn(1, 1, 224, 224)

m(x).shape
torch.Size([1, 1000])

我们将参数 in_chans 传递给 timm.create_model 函数,结果神奇地成功了!让我们看看 25 通道图像会发生什么?

m = timm.create_model('resnet34', pretrained=True, in_chans=25)

# 25-channel image
x = torch.randn(1, 25, 224, 224)

m(x).shape
torch.Size([1, 1000])

This works again! 😃

timm 如何使用预训练权重并处理非 3 通道 RGB 图像?

timm 在 load_pretrained 函数中实现了所有这些神奇的功能,该函数用于加载模型的预训练权重。让我们看看 timm 是如何实现预训练权重加载的。

from timm.models.resnet import ResNet, BasicBlock, default_cfgs
from timm.models.helpers import load_pretrained
from copy import deepcopy

下面,我们创建一个简单的 resnet34 模型,该模型可以接收单通道图像作为输入。我们在创建模型时将 in_chans=1 传递给 ResNet 构造函数类来实现这一点。

resnet34_default_cfg = default_cfgs['resnet34']
resnet34 = ResNet(BasicBlock, layers=[3, 4, 6, 3], in_chans=1)
resnet34.default_cfg = deepcopy(resnet34_default_cfg)

resnet34.conv1
Conv2d(1, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
resnet34.conv1.weight.shape
torch.Size([64, 1, 7, 7])

从上图 resnet34 的第一次卷积可以看出,输入通道数设置为 1。卷积层 1 的权重形状为 [64, 1, 7, 7]。这意味着输入通道数为 1,输出通道数为 64,卷积核大小为 7x7。

那么预训练的权重呢?由于 ImageNet 数据集包含 3 通道输入图像,因此该卷积层 1 的预训练权重为 [64, 3, 7, 7]。我们来确认一下:

resnet34_default_cfg
{'url': 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/resnet34-43635321.pth',
 'num_classes': 1000,
 'input_size': (3, 224, 224),
 'pool_size': (7, 7),
 'crop_pct': 0.875,
 'interpolation': 'bilinear',
 'mean': (0.485, 0.456, 0.406),
 'std': (0.229, 0.224, 0.225),
 'first_conv': 'conv1',
 'classifier': 'fc'}

Let’s load the pretrained weights from the model and check the number of input channels that conv1 expects.
让我们从模型中加载预训练的权重并检查 conv1 期望的输入通道数量。

import torch
state_dict = torch.hub.load_state_dict_from_url(resnet34_default_cfg['url'])

太好了,我们已经从“https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/resnet34-43635321.pth”URL 加载了 resnet-34 的预训练权重,现在让我们检查下面 conv1 的权重形状:

state_dict['conv1.weight'].shape
torch.Size([64, 3, 7, 7])

所以这一层期望输入通道数为 3!
注意:我们知道这一点是因为 conv1.weight 的形状是 [64, 3, 7, 7],这意味着输入通道数为 3,输出通道数为 64,内核大小为 7x7。
注意:这就是为什么当我们尝试加载预训练权重时,torchvision 会报错,因为我们将输入通道数设置为 1,导致模型的 conv1 层权重形状为 [64, 1, 7, 7]。我希望现在我们上面看到的这个异常更加合理了:假设 groups=1,权重大小为 [64, 3, 7, 7],预期输入 [1, 1, 224, 224] 有 3 个通道,但实际得到的只有 1 个通道。

那么 timm 是如何加载这些权重的呢?

timm 内部的 load_pretrained 函数中有一个非常巧妙的机制。基本上,当预期输入通道数不等于 3 时,主要有两种情况需要考虑:要么输入通道数为 1,要么输入通道数不为 1。让我们来看看这两种情况下会发生什么。

当输入通道数不等于 3 时,timm 会相应地更新预训练权重的 conv1.weight,以便能够加载预训练权重。

情况 1:当输入通道数为 1 时

如果输入通道数为 1,timm 会简单地将 3 个通道的权重相加为一个通道,从而将 conv1.weight 的形状更新为 [64, 1, 7, 7]。这可以通过以下方式实现:

conv1_weight = state_dict['conv1.weight']
conv1_weight.sum(dim=1, keepdim=True).shape

>> torch.Size([64, 1, 7, 7])

因此,通过更新第一个 conv1 层的形状,我们现在可以安全地加载这些预训练权重。

情况 2:当输入通道数不为 1 时

在这种情况下,我们只需根据需要重复 conv1_weight 的次数,然后选择所需数量的输入通道权重即可。

预训练权重

如上图所示,假设我们的输入图像有 8 个通道。因此,输入通道数等于 8。

但是,我们知道预训练权重只有 3 个通道。那么,我们该如何利用预训练权重呢?

timm 中发生的情况已在上图中显示。我们将权重复制 3 次,使通道总数变为 9,然后选择前 8 个通道作为 conv1 层的权重。

所有这些都在 load_pretrained 函数中完成,如下所示:

conv1_name = cfg['first_conv']
conv1_weight = state_dict[conv1_name + '.weight']
conv1_type = conv1_weight.dtype
conv1_weight = conv1_weight.float()
repeat = int(math.ceil(in_chans / 3))
conv1_weight = conv1_weight.repeat(1, repeat, 1, 1)[:, :in_chans, :, :]
conv1_weight *= (3 / float(in_chans))
conv1_weight = conv1_weight.to(conv1_type)
state_dict[conv1_name + '.weight'] = conv1_weight

Thus, as can be seen above, we first repeat the conv1_weight and then select required number of in_chans from these copied weights.
因此,如上所示,我们首先重复 conv1_weight,然后从这些复制的权重中选择所需数量的 in_chans。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值