用于图像分类的预训练模型(PyTorch实现)
在本文中,我们将介绍一些使用 TorchVision 模块中存在的预训练网络的实践示例——用于图像分类的预训练模型。
1. 基于预训练模型进行图像分类
预训练模型是在 ImageNet 等大型基准数据集上训练的神经网络模型。深度学习社区从这些开源模型中受益匪浅。此外,预训练模型是计算机视觉研究快速发展的一个主要因素。其他研究人员和实践者可以使用这些最先进的模型,而不是从头开始重新发明一切。
下面给出了最先进模型如何随着时间的推移而改进的粗略时间表。我们仅包含 Torchvision 包中存在的那些模型。
在详细介绍如何使用预训练模型进行图像分类之前,让我们先看看可用的各种预训练模型是什么。我们将在这里讨论 AlexNet 和 ResNet101 作为两个主要示例。这两个网络都已在 ImageNet 数据集上进行了训练。
ImageNet 数据集拥有斯坦福大学维护的超过 1400 万张图像。它广泛用于各种与图像相关的深度学习项目。这些图像属于不同的类别或标签。 AlexNet 和 ResNet101 等预训练模型的目的是将图像作为输入并预测其类别。
这里的“预训练”一词意味着深度学习架构 AlexNet 和 ResNet101 已经在某些(巨大)数据集上进行了训练,因此带有最终的权重和偏差。
1.1. 模型预测过程
由于我们将重点关注如何使用预训练模型来预测输入的类别(标签),因此我们也讨论一下其中涉及的过程。此过程称为模型推理。整个过程由以下主要步骤组成。
- 读取输入图像
- 对图像进行变换。例如——调整大小、居中裁剪、标准化等。
- 前向传递:使用预先训练的权重来找出输出向量。该输出向量中的每个元素描述了模型预测输入图像属于特定类别的置信度。
- 根据获得的分数(步骤 3 中提到的输出向量的元素),显示预测。
1.2 使用TorchVision导入预训练模型
让我们从 torchvision 模块导入模型,看看我们可以使用的不同模型和架构。
from torchvision import models
print(dir(models))
仔细观察上面得到的输出。
['AlexNet', 'AlexNet_Weights', 'ConvNeXt', 'ConvNeXt_Base_Weights', 'ConvNeXt_Large_Weights', 'ConvNeXt_Small_Weights', 'ConvNeXt_Tiny_Weights', 'DenseNet', 'DenseNet121_Weights', 'DenseNet161_Weights', 'DenseNet169_Weights', 'DenseNet201_Weights', 'EfficientNet', 'EfficientNet_B0_Weights', 'EfficientNet_B1_Weights', 'EfficientNet_B2_Weights', 'EfficientNet_B3_Weights', 'EfficientNet_B4_Weights', 'EfficientNet_B5_Weights', 'EfficientNet_B6_Weights', 'EfficientNet_B7_Weights', 'EfficientNet_V2_L_Weights', 'EfficientNet_V2_M_Weights', 'EfficientNet_V2_S_Weights', 'GoogLeNet', 'GoogLeNetOutputs', 'GoogLeNet_Weights', 'Inception3', 'InceptionOutputs', 'Inception_V3_Weights', 'MNASNet', 'MNASNet0_5_Weights', 'MNASNet0_75_Weights', 'MNASNet1_0_Weights', 'MNASNet1_3_Weights', 'MobileNetV2', 'MobileNetV3', 'MobileNet_V2_Weights', 'MobileNet_V3_Large_Weights', 'MobileNet_V3_Small_Weights', 'RegNet', 'RegNet_X_16GF_Weights', 'RegNet_X_1_6GF_Weights', 'RegNet_X_32GF_Weights', 'RegNet_X_3_2GF_Weights', 'RegNet_X_400MF_Weights', 'RegNet_X_800MF_Weights', 'RegNet_X_8GF_Weights', 'RegNet_Y_128GF_Weights', 'RegNet_Y_16GF_Weights', 'RegNet_Y_1_6GF_Weights', 'RegNet_Y_32GF_Weights', 'RegNet_Y_3_2GF_Weights', 'RegNet_Y_400MF_Weights', 'RegNet_Y_800MF_Weights', 'RegNet_Y_8GF_Weights', 'ResNeXt101_32X8D_Weights', 'ResNeXt101_64X4D_Weights', 'ResNeXt50_32X4D_Weights', 'ResNet', 'ResNet101_Weights', 'ResNet152_Weights', 'ResNet18_Weights', 'ResNet34_Weights', 'ResNet50_Weights', 'ShuffleNetV2', 'ShuffleNet_V2_X0_5_Weights', 'ShuffleNet_V2_X1_0_Weights', 'ShuffleNet_V2_X1_5_Weights', 'ShuffleNet_V2_X2_0_Weights', 'SqueezeNet', 'SqueezeNet1_0_Weights', 'SqueezeNet1_1_Weights', 'SwinTransformer', 'Swin_B_Weights', 'Swin_S_Weights', 'Swin_T_Weights', 'VGG', 'VGG11_BN_Weights', 'VGG11_Weights', 'VGG13_BN_Weights', 'VGG13_Weights', 'VGG16_BN_Weights', 'VGG16_Weights', 'VGG19_BN_Weights', 'VGG19_Weights', 'ViT_B_16_Weights', 'ViT_B_32_Weights', 'ViT_H_14_Weights', 'ViT_L_16_Weights', 'ViT_L_32_Weights', 'VisionTransformer', 'Wide_ResNet101_2_Weights', 'Wide_ResNet50_2_Weights', '_GoogLeNetOutputs', '_InceptionOutputs', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '_api', '_meta', '_utils', 'alexnet', 'convnext', 'convnext_base', 'convnext_large', 'convnext_small', 'convnext_tiny', 'densenet', 'densenet121', 'densenet161', 'densenet169', 'densenet201', 'detection', 'efficientnet', 'efficientnet_b0', 'efficientnet_b1', 'efficientnet_b2', 'efficientnet_b3', 'efficientnet_b4', 'efficientnet_b5', 'efficientnet_b6', 'efficientnet_b7', 'efficientnet_v2_l', 'efficientnet_v2_m', 'efficientnet_v2_s', 'get_weight', 'googlenet', 'inception', 'inception_v3', 'mnasnet', 'mnasnet0_5', 'mnasnet0_75', 'mnasnet1_0', 'mnasnet1_3', 'mobilenet', 'mobilenet_v2', 'mobilenet_v3_large', 'mobilenet_v3_small', 'mobilenetv2', 'mobilenetv3', 'optical_flow', 'quantization', 'regnet', 'regnet_x_16gf', 'regnet_x_1_6gf', 'regnet_x_32gf', 'regnet_x_3_2gf', 'regnet_x_400mf', 'regnet_x_800mf', 'regnet_x_8gf', 'regnet_y_128gf', 'regnet_y_16gf', 'regnet_y_1_6gf', 'regnet_y_32gf', 'regnet_y_3_2gf', 'regnet_y_400mf', 'regnet_y_800mf', 'regnet_y_8gf', 'resnet', 'resnet101', 'resnet152', 'resnet18', 'resnet34', 'resnet50', 'resnext101_32x8d', 'resnext101_64x4d', 'resnext50_32x4d', 'segmentation', 'shufflenet_v2_x0_5', 'shufflenet_v2_x1_0', 'shufflenet_v2_x1_5', 'shufflenet_v2_x2_0', 'shufflenetv2', 'squeezenet', 'squeezenet1_0', 'squeezenet1_1', 'swin_b', 'swin_s', 'swin_t', 'swin_transformer', 'vgg', 'vgg11', 'vgg11_bn', 'vgg13', 'vgg13_bn', 'vgg16', 'vgg16_bn', 'vgg19', 'vgg19_bn', 'video', 'vision_transformer', 'vit_b_16', 'vit_b_32', 'vit_h_14', 'vit_l_16', 'vit_l_32', 'wide_resnet101_2', 'wide_resnet50_2']
请注意,有一个名为 AlexNet 的条目,还有一个名为 alexnet。大写的名称指的是 Python 类 (AlexNet),而 alexnet 是一个便捷函数,它返回从 AlexNet 类实例化的模型。这些便利函数也可能具有不同的参数集。例如,densenet121、densenet161、densenet169、densenet201 都是 DenseNet 类的实例,但具有不同的层数——分别为 121,161,169 和 201。
1.3 基于AlexNet进行图像分类
我们首先从 AlexNet 开始。它是图像识别领域早期突破性网络之一。如果您有兴趣了解 AlexNet 的架构,可以查看我们关于了解 AlexNet 的文章。
Step 1: 加载预训练模型
alexnet = models.alexnet(pretrained=True)
# You will see a similar output as below
# Downloading: "https://download.pytorch.org/models/alexnet-owt-7be5be79.pth" to C:\Users\Administrator/.cache\torch\hub\checkpoints\alexnet-owt-7be5be79.pth
请注意,通常 PyTorch 模型的扩展名为 .pt 或 .pth。
下载权重后,我们就可以继续其他步骤。我们还可以查看网络架构的一些细节,如下所示。
print(alexnet)
运行结果如下:
AlexNet(
(features): Sequential(
(0): Conv2d(3, 64, kernel_size=(11, 11), stride=(4, 4), padding=(2, 2))
(1): ReLU(inplace=True)
(2): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
(3): Conv2d(64, 192, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
(4): ReLU(inplace=True)
(5): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
(6): Conv2d(192, 384, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(7): ReLU(inplace=True)
(8): Conv2d(384, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(9): ReLU(inplace=True)
(10): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(11): ReLU(inplace=True)
(12): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(avgpool): AdaptiveAvgPool2d(output_size=(6, 6))
(classifier): Sequential(
(0): Dropout(p=0.5, inplace=False)
(1): Linear(in_features=9216, out_features=4096, bias=True)
(2): ReLU(inplace=True)
(3): Dropout(p=0.5, inplace=False)
(4): Linear(in_features=4096, out_features=4096, bias=True)
(5): ReLU(inplace=True)
(6): Linear(in_features=4096, out_features=1000, bias=True)
)
)
Step 2: 图像变换
一旦我们有了模型,下一步就是转换输入图像,使它们具有正确的形状和其他特征,例如平均值和标准差。这些值应与训练模型时使用的值类似。这确保网络能够产生有意义的答案。
from torchvision import transforms
transform = transforms.Compose([ # [1]
transforms.Resize(256), # [2]
transforms.CenterCrop(224), # [3]
transforms.ToTensor(), # [4]
transforms.Normalize( # [5]
mean=[0.485, 0.456, 0.406], # [6]
std=[0.229, 0.224, 0.225] # [7]
)
])
[1] 这里我们定义一个变量transform,它是对输入图像执行的所有图像变换的组合。
[2] 将图像大小调整为 256×256 像素。
[3] 将图像裁剪为中心周围的 224×224 像素。
[4] 将图像转换为 PyTorch Tensor 数据类型。
[5-7] 通过将图像的平均值和标准差设置为指定值来标准化图像。
Step 3: 输入图像及预处理
from PIL import Image
img = Image.open("dog.jpg")
img_t = transform(img)
batch_t = torch.unsqueeze(img_t, 0)
Step 4: 模型预测
alexnet.eval()
out = alexnet(batch_t)
print(out.shape) # torch.Size([1, 1000])
我们仍然没有得到图像的类别(或标签)。为此,我们首先从包含所有 1000 个标签的列表的json文件中读取并存储标签。
with open('imagenet_classes.json', 'r') as f:
classes = json.load(f)
现在,我们需要找到输出向量 out 中出现最大分数的索引。我们将使用这个索引来找出预测。
_, index = torch.max(out, 1)
index = index[0].item()
percentage = torch.nn.functional.softmax(out, dim=1)[0] * 100
print(classes[str(index)], percentage[index].item())
运行结果如下:
[‘Labrador retriever’] 42.46735763549805
该模型以 42.47% 的置信度预测该图像是拉布拉多猎犬。
但这看起来太低了。让我们看看模型认为图像属于哪些其他类别。
_, indices = torch.sort(out, descending=True)
for idx in indices[0][:5]:
print(classes[str(idx.item())], percentage[idx.item()].item())
运行结果如下:
['Labrador retriever'] 42.46735763549805
['golden retriever'] 16.6086483001709
['Saluki', 'gazelle hound'] 15.473832130432129
['whippet'] 2.7881932258605957
['Ibizan hound', 'Ibizan Podenco'] 2.3617053031921387
可以看出,这些都是狗品种。因此,模型以相当高的置信度成功地预测出这是一只狗,但对狗的品种却不太确定。
其他图像测试结果:
['strawberry'] 99.99411010742188
['banana'] 0.0008383149397559464
['custard apple'] 0.0008333750884048641
['orange'] 0.000746806850656867
['lemon'] 0.000567478418815881
1.4. 基于ResNet进行图像分类
我们将使用 resnet101 – 101 层卷积神经网络。 resnet101 在训练过程中调整了大约 4450 万个参数。
我们只需将模型改为:
resnet = models.resnet101(pretrained=True)
运行结果如下:
['Labrador retriever'] 48.86918640136719
['dingo', 'warrigal', 'warragal', 'Canis dingo'] 8.17878532409668
['golden retriever'] 6.944648742675781
['Eskimo dog', 'husky'] 3.563744306564331
['bull mastiff'] 3.0799121856689453
就像 AlexNet 一样,ResNet 以非常高的置信度预测这是一只狗,并以 48.25% 的置信度预测这是一只拉布拉多猎犬。
1.5 完整代码
from torchvision import models
import torch
from torchvision import transforms
from PIL import Image
import json
# alexnet = models.alexnet(pretrained=True)
resnet = models.resnet101(pretrained=True)
transform = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]
)
])
img = Image.open("../img/dog.jpg")
img_t = transform(img)
batch_t = torch.unsqueeze(img_t, 0)
resnet.eval()
out = resnet(batch_t)
with open('../json/imagenet_classes.json', 'r') as f:
classes = json.load(f)
_, index = torch.max(out, 1)
index = index[0].item()
percentage = torch.nn.functional.softmax(out, dim=1)[0] * 100
# top-1
print(classes[str(index)], percentage[index].item())
_, indices = torch.sort(out, descending=True)
for idx in indices[0][:5]:
# top-5
print(classes[str(idx.item())], percentage[idx.item()].item())
2. 模型比较
在本节中,我们将根据以下标准比较预训练模型:
- Top-1 Error: 最高预测类别与真实情况不同。
- Top-5 Error: 如果前 5 个预测类别均不正确,则预测将被分类为错误。
- Inference Time on CPU: Inference time is the time taken for the model inference step.
- Inference Time on GPU
- Model size: 这里的size代表PyTorch提供的预训练模型的.pth文件占用的物理空间
所有实验均在同一输入图像上进行多次,以便可以分析特定模型的所有结果的平均值。实验是在 Google Colab 上进行的。现在,让我们看看获得的结果。
2.1 模型准确率的比较
我们将讨论的第一个标准包括 Top-1 和 Top-5 错误。
从图中可以看出,两个误差都遵循相似的趋势。 AlexNet是基于深度学习的第一次尝试,此后误差得到了改善。值得注意的是 GoogLeNet、ResNet、VGGNet、ResNext。
2.2 预测时间比较
接下来,我们将根据模型预测所需的时间来比较模型。向每个模型多次提供一张图像,并对所有迭代的推理时间进行平均。在 Google Colab 上对 CPU 执行了类似的过程,然后对 GPU 执行了类似的过程。尽管顺序有所不同,但我们可以看到 SqueezeNet、ShuffleNet 和 ResNet-18 的推理时间非常短。
2.3 模型尺寸比较
很多时候,当我们在 Android 或 iOS 设备上使用深度学习模型时,模型大小成为决定因素,有时甚至比准确性更重要。 SqueezeNet 的模型大小最小 (5 MB),其次是 ShuffleNet V2 (6 MB) 和 MobileNet V2 (14 MB)。很明显,为什么这些模型在利用深度学习的移动应用程序中受到青睐。
2.4 整体比较
我们根据特定标准讨论了哪种模型表现更好。我们可以将所有这些重要细节压缩在一张气泡图中,然后我们可以参考这些细节来根据我们的要求决定采用哪种模型。
我们使用的 x 坐标是 Top-1 误差(越低越好)。 y 坐标是 GPU 上的预测时间(以毫秒为单位)(越低越好)。气泡大小代表模型大小(越低越好)。
注意:
- 就模型尺寸而言,气泡越小越好。
- 靠近原点的气泡在准确性和速度方面都更好。