OPAM模型(细粒度图像分类)

OPAM模型(细粒度图像分类)

  • 摘要
  • Abstract
  • 1. OPAM
    • 1.1 文献摘要
    • 1.2 细粒度图像分类
    • 1.3 研究背景
    • 1.4 OPAM模型创新点
    • 1.5 OPAM模型
      • 1.5.1 补丁过滤
      • 1.5.2 显着性提取
      • 1.5.3 细粒度区域级注意模型
        • 对象-空间约束方法(Object spatial constraint)
        • 部分空间约束方法(Part spatial constraint)
        • 部分区域对齐(Part Alignment)
      • 1.5.4 最终预测
    • 1.6 实验
  • 2. Res2Net代码实现
    • 总结

摘要

在细粒度图像分类的背景下,寻找对象和有区别的部分可以被视为两级注意力过程,其中一个是对象级,另一个是部分级。 一个直观的想法是使用对象注释(即对象的边界框)进行对象级注意,使用零件注释(即零件位置)进行零件级注意。 大多数现有方法依赖于对象或部分注释来查找对象或有区别的部分,但这种标记非常耗费人力。 OPAM模型综合了两个层次的注意模型:对象层定位图像对象,局部层选择对象的区分部分。这两个层面的关注共同促进了多视角、多尺度的特征学习,增强了它们之间的相互促进作用。本文将详细介绍OPAM模型。

Abstract

In the context of fine-grained image categorization, finding objects and differentiated parts can be viewed as a two-level attentional process, where one is object-level and the other is part-level. An intuitive idea is to use object annotations (i.e., the bounding box of an object) for object-level attention and part annotations (i.e., the location of a part) for part-level attention. Most existing methods rely on object or part annotations to find objects or differentiated parts, but this kind of markup is very labor intensive. The OPAM model synthesizes a two-level attention model: the object level locates image objects, and the local level selects differentiated parts of objects. Together, these two levels of attention facilitate multi-view, multi-scale feature learning, enhancing their mutual reinforcement. In this paper, the OPAM model is described in detail.

1. OPAM

文献出处:Object-Part Attention Model for
Fine-grained Image Classification

1.1 文献摘要

细粒度图像分类是要识别属于同一基本类别的数百个子类别,例如属于鸟类的 200 个子类别,由于同一子类别内方差较大而不同子类别之间方差较小,因此具有很高的挑战性。 现有的方法通常首先定位对象或部分,然后判别图像属于哪个子类别。 然而,它们主要有两个局限性:

  1. 依赖对象或部分注释,这非常耗费人力。
  2. 忽略对象与其部分之间以及这些部分之间的空间关系。

本文提出了用于弱监督细粒度图像分类的对象部分注意模型(OPAM),其主要新颖之处在于:

  1. 对象部分注意模型集成了两个级别的注意:对象级注意定位图像的对象,部分- 水平注意力选择对象的有区别的部分。 两者共同学习多视图和多尺度特征,以增强它们的相互促进。
  2. 对象-部分空间约束模型结合了两种空间约束:对象空间约束确保所选部分具有高度代表性,部分空间约束消除冗余并增强所选部分的区分度。

两者共同用于利用细微的局部差异来区分子类别。

1.2 细粒度图像分类

细粒度图像分类非常具有挑战性,旨在识别同一基本级别类别下的数百个子类别,例如鸟类、汽车、宠物、花卉和 飞机。 而基础级图像分类只需要区分基础级类别,例如鸟或汽车。 基础级和细粒度图像分类之间的区别如下图所示。由于物体外观差异较小,细微的局部差异是细粒度图像分类的关键点,例如鸟类的背部颜色、喙形状和羽毛纹理。
在这里插入图片描述
由于这些细微和局部的差异位于判别对象和部分,因此大多数现有方法通常遵循定位图像中的对象或部分,然后判别图像属于哪个子类别的策略。

1.3 研究背景

为了定位有区别的对象和部分,通常首先执行通过自下而上的过程生成具有高对象性的图像块,这意味着生成的块包含有区别的对象或部分。 选择性搜索是一种无监督方法,可以生成数千个此类图像块。

自下而上和自上而下通常用来描述不同类型的信息处理或特征提取过程。

  • 自下而上(Bottom-up):这种过程从数据的原始表示或低级特征开始,逐渐构建更高级别的表示或特征。在图像处理中,可以从像素级别逐步提取边缘、角点等特征,然后组合成更复杂的形状和对象。
  • 自上而下(Top-down):这种过程从高级别的概念或语义开始,通过向下层级传播信息来指导和调整底层特征的提取。在目标检测中,可以先根据整体场景推测可能的对象位置,然后再在这些位置上进行细致的特征提取和分类。

由于自下而上的过程具有较高的召回率但精度较低,因此必须去除噪声图像块并保留包含对象或判别部分的图像块,这可以通过自上而下的注意模型来实现。

在深度学习中,召回率(Recall) 是衡量分类模型性能的指标之一,通常用于评估模型对于正样本的识别能力。召回率表示模型能够正确识别出的正样本数量占所有正样本数量的比例。其计算公式为:Recall = TP/(TP+FN),其中,TP(True Positive)表示模型将正样本正确地预测为正样本的数量,FN(False Negative)表示模型将正样本错误地预测为负样本的数量。召回率的取值范围是0到1之间,值越接近1表示模型对正样本的识别能力越强。

在细粒度图像分类的背景下,寻找对象和有区别的部分可以被视为两级注意力过程,其中一个是对象级,另一个是部分级。 一个直观的想法是使用对象注释(即对象的边界框)进行对象级注意,使用零件注释(即零件位置)进行零件级注意。 大多数现有方法依赖于对象或部分注释来查找对象或有区别的部分,但这种标记非常耗费人力。

1.4 OPAM模型创新点

本文提出了用于弱监督细粒度图像分类的对象部分注意模型(OPAM)。其主要创新点和贡献可概括如下:

  1. Object-Part Attention Model:大多数现有工作依赖于对象或部分注释,而标记非常耗费人力。 为了解决这个重要问题,作者提出了用于弱监督细粒度图像分类的对象部分注意模型,以避免使用对象和细粒度区域标注并走向实际应用。 它集成了两级注意力:对象级注意力模型利用CNN中的全局平均池化来提取显着图来定位图像的对象,即学习对象特征。 部分级注意力模型首先选择有判别性的部分,然后根据神经网络的聚类模式对部分进行对齐,即学习细微的局部特征。 对象级注意力模型侧重于代表性对象外观,部分级注意力模型侧重于区分子类别之间部分的具体差异。 两者联合运用,促进多视角、多尺度特征学习,增强相互促进,在细粒度图像分类方面取得良好的性能。
  2. Object-Part Spatial Constraint Model:大多数现有的弱监督方法忽略了对象与其部分之间以及这些部分之间的空间关系,这两者对于判别性部分选择非常有帮助。 为了解决这个问题,作者提出了由对象部分空间约束模型驱动的部分选择方法,该方法结合了两种类型的空间约束:(1)对象空间约束强制所选部分位于对象区域中并且具有高度代表性。 (2) 部分空间约束减少了细粒度区域之间的重叠,突出了部分区域的显着性,消除了冗余,增强了所选部分区域的区分度。 两种空间约束的结合不仅通过利用细微和局部的区别显着促进了有判别性的部分选择,而且在细粒度图像分类方面也取得了显着的改进。

1.5 OPAM模型

细粒度图像分类通常首先定位对象(对象级注意力),然后区分部分(部分级注意力)。 例如,识别包含田麻雀的图像遵循以下过程:首先找到一只鸟,然后关注将其与其他鸟类子类别区分开来的区分部分。

作者在本文提出了用于弱监督细粒度图像分类的对象部分注意模型OPAM,该模型在训练和测试阶段既不使用对象也不使用部分注释,而仅使用图像级子类别标签。 如下图所示,OPAM模型首先通过对象级注意模型来定位图像对象以学习对象特征,然后通过部分级注意模型选择有区别的部分以学习微妙和局部特征。
在这里插入图片描述
大多数现有的弱监督工作致力于判别部分选择,但忽略了目标定位,目标定位可以消除图像中背景噪声的影响,以学习有意义且有代表性的目标特征。 尽管有些方法同时考虑对象定位和细粒度区域的选择,但它们依赖于对象和细粒度目标区域的标注。

为了解决这个重要问题,作者提出了一种基于显着性提取的对象级注意力模型,仅使用图像级子类别标签自动定位图像对象,而不需要任何对象或细粒度区域标注。

该模型由两个部分组成:补丁过滤和显着性提取。 第一个组件是滤除噪声图像块并保留与目标相关的图像块,用于训练称为 ClassNet 的 CNN,以学习特定子类别的多视图和多尺度特征。 第二个部分是通过 CNN 中的全局平均池化来提取显着图,以定位图像内的对象。

1.5.1 补丁过滤

自下而上的过程可以通过将像素分组到可能包含对象的区域来生成数千个候选图像块。 由于这些图像块与对象的相关性,它们可以用作训练数据的扩展。 因此,采用选择性搜索来为给定图像生成 候选图像块,这是一种无监督且广泛使用的自下而上处理方法。 这些候选图像块提供了原始图像的多个视图和尺度,这有利于训练有效的 CNN 以实现更好的细粒度图像分类精度。 然而,这些补丁不能直接使用,因为召回率高但精度低,这意味着存在一些噪音。

作者通过CNN网络去除噪声补丁并且选择出正确的相关补丁,该 CNN 在 ImageNet 1K 数据集上进行预训练,然后对训练数据进行微调。

作者将属于输入图像子类别的softmax层中神经元的激活定义为选择置信度分数,然后设置阈值来决定是否应该选择给定的候选图像块。 然后我们获得与对象相关的图像块,具有多个视图和尺度。 如下图所示:
在这里插入图片描述

1.5.2 显着性提取

在这个阶段,采用CAM来获得子类别c的图像的显着图 M c M_c Mc 来定位对象。 显着图表示CNN用来识别图像子类别的代表区域,如下图的第二行所示。然后通过执行以下操作获得图像的对象区域,如下图的第三行所示 显着图上的二值化和连接区域提取。
在这里插入图片描述
通俗理解就是首先确定图像子类别的大致区域,然后根据大致区域再去锁定目标对象的位置。

给定图像 I,最后一个卷积层中神经元 u u u 在空间位置 ( x , y ) (x, y) (x,y) 处的激活定义为 f u ( x , y ) f_{u}(x, y) fu(x,y) w u c w^{c}_{u} wuc 定义神经元 u u u 对应于子类别 c c c 的权重。 子类别 c c c 的空间位置 ( x , y ) (x, y) (x,y) 处的显着性值计算如下:

在这里插入图片描述

M c ( x , y ) M_{c}(x, y) Mc(x,y) 直接指示了在空间位置 ( x , y ) (x, y) (x,y) 处的激活对将图像分类为子类别 c c c 的重要性。没有使用图像级别的子类别标签,而是使用预测结果作为每个图像中的子类别 c c c 来提取显著性。通过对象级别的注意力模型,我们在图像中定位对象以训练一个名为ObjectNet的CNN,以获取对象级别注意力的预测,如下图所示:通过卷积、池化、全连接,最终获取对象级预测结果
在这里插入图片描述

1.5.3 细粒度区域级注意模型

以往方法的缺陷

由于头部和身体等可区分部分是细粒度图像分类的关键,以前的研究都是从自下而上的选择性搜索等过程产生的候选图像块中选择可区分部分。然而,这些研究方法都依赖于区域特征标注,耗费了大量的人力。虽然一些工作开始集中于寻找有区别的部分,而不使用任何标注,但它们忽略了对象与其区域特征之间以及这些区域特征之间的空间关系。

作者提出的

作者提出了一种新的区域特征选择方法,该方法由图像区域级别的注意驱动,利用细微的局部区分来区分子类别,既不使用对象也不使用部分级标注。它由两部分组成:对象-部分空间约束模型和区域特征对齐。第一步是选择有区别的部分,第二步是根据语义将所选择的部分对齐成簇。

对象-部分空间约束模型

通过对象级注意力模型获取图像的目标区域,然后利用对象-部分空间约束模型从自下而上过程产生的候选图像块中选择可区分的部分。综合考虑了两个空间约束:对象空间约束定义了对象与其部分之间的空间关系,部分空间约束定义了这些部分之间的空间关系。对于给定的图像 I I I,通过目标级注意力模型得到其显著图 M M M 和目标区域 b b b

然后用 对象-部分空间约束模型 来驱动局部选择:设 P P P 表示所有候选图像块, P = p 1 , p 2 , … , p n P={p_1,p_2,…,p_n} P=p1p2pn表示从P中选择的n个部分作为每幅给定图像的区分部分。对象-部件空间约束模型通过解决以下优化问题来考虑两个空间约束的组合:
在这里插入图片描述
其中, ∆ ( P ) ∆(P) (P) 被定义为两个空间约束上的得分函数,定义了提出的对象-部件空间约束,保证了所选部件的代表性和区分性。它由两个约束组成:对象空间约束 ∆ b o x ( P ) ∆_{box}(P) box(P) 和部分空间约束 ∆ p a r t ( P ) ∆_{part}(P) part(P),这两个约束都应由所有选定的区域同时满足,作为利用乘积运算来优化两个约束的工作。如下所示:

在这里插入图片描述

对象-空间约束方法(Object spatial constraint)

忽略物体与其各部分之间的空间关系,使得所选部分可能具有较大的背景噪声面积和较小的区分区域面积,从而降低了所选部分的代表性。由于区分部分位于对象区域内,因此空间约束函数可以被定义为:

在这里插入图片描述
其中:
在这里插入图片描述
并且 I o U ( P i ) IoU_{(Pi)} IoU(Pi)定义零件区域和对象区域的并集交集(IOU)重叠的比例。

[注意]对象区域是通过对象级别注意力模型自动获得的,而不是由对象注释提供的。

对象空间约束旨在同时约束对象区域内的所有选定部分。当 I o U IoU IoU值等于0,将不被选为区分部分。

部分空间约束方法(Part spatial constraint)

忽略这些部分之间的空间关系会导致选择的部分可能会有很大的重叠,并且忽略了一些有区别性的部分。显著图表明了图像的区分性,有利于区分部分的选择。将显著度和部件之间的空间关系联合建模如下:
在这里插入图片描述
其中, A U A_U AU是n个部分的并集面积, A I A_I AI是n个部分的交集面积, A o A_o Ao是对象区域外的面积,平均值(MAU)定义如下:
在这里插入图片描述
其中像素 ( i , j ) (i,j) (ij)位于部件的并集区域中, M i j M_{ij} Mij是指像素 ( i , j ) (i,j) (ij)的显著性值,并且 ∣ A U ∣ |A_U| AU是指位于n个部件的并集区域中的像素数。

部分区域空间约束由两项组成:第一项旨在减少所选区域之间的重叠,通过 l o g ( A U − A I − A O ) log(A_U−A_I−A_O) log(AUAIAO)来实现,其中 − A I −A_I AI保证所选区域具有最小的重叠,而 − A O −A_O AO确保所选零件在目标区域内具有最大的面积。第二项以最大化选定部分的显著为目标,通过 l o g ( M e a n ( M A U ) ) log(Mean(M_{A_U})) log(Mean(MAU))来实现, l o g ( M e a n ( M A U ) ) log(Mean(M_{A_U})) log(Mean(MAU))表示选定部分的联合区域中所有像素的平均显著值。

部分区域对齐(Part Alignment)

如图下图所示,通过对象-部分空间约束模型选择的部分是杂乱无章的,并且其语义不对齐。
在这里插入图片描述
这些具有不同语义的部分对最终预测的贡献不同,直观的想法是将具有相同语义的部分排列在一起。 如上图所示,有几组神经元对鸟的头部有显著的反应,另一些神经元对鸟的身体有明显的反应,尽管它们可能对应于不同的姿势。因此,对ClassNet中中间层的神经元进行聚类,以构建用于对所选部件进行对齐的部件簇。

1.5.4 最终预测

作者使用局部对象和区分部分对ClassNet进行了微调,得到了两个分类器,分别称为对象网和部分网。ClassNet、ObjectNet和PartNet都是细粒度的图像分类器:ClassNet用于原始图像,ObjectNet用于对象,PartNet用于选定的区分部分。然而,它们的影响和优势是不同的,主要是因为它们关注的是形象的不同性质。如下图所示,对象级注意力模型首先驱动滤网选择与对象相关的具有多个视图和尺度的图像块。这些图像块驱动ClassNet学习更具代表性的特征,并通过显著提取定位对象区域。部件级注意模型选择包含细微和局部特征的区别性部分。不同层次的焦点(即原始图像、原始图像的对象和原始图像的部分)具有不同的表示形式,并相互补充以提高预测效果。最后,使用以下方程将三个不同级别的预测结果合并:
在这里插入图片描述

在这里插入图片描述
其中,原始分数、对象分数和部分分数分别是类网、对象网和部件网的Softmax值,并且α,β和γ是通过使用k倍交叉验证方法来选择的。最终得分最高的子类别被选为最终预测结果。

1.6 实验

作者给出了OPAM方法在4个广泛使用的细粒度图像分类数据集上的实验结果和分析,以及最新的方法。下表显示了在CUB-2002011数据集上的比较结果。
在这里插入图片描述

在训练阶段和测试阶段都不使用对象和部分标注的相同设置下,我们的方法是所有方法中最好的,并且获得了比FOAF的最佳比较结果(85.83%比84.63%)高1.20%的准确率。值得注意的是,FOAF中使用的CNN不仅在ImageNet 1K数据集[32]上进行了预训练,而且在Pascal VOC的数据集上进行了预训练,而我们的方法没有使用像Pascal VOC这样的外部数据集。与第二高的PD结果相比,作者的方法获得了1.29%的高准确率(85.83%比84.54%),验证了OPAM方法的进一步开发的有效性,该方法结合了对象级和部分级的注意模型,促进了多视角和多尺度的特征学习,并增强了它们的互补性。

2. Res2Net代码实现

文件结构:
在这里插入图片描述
main.py:

import torch
import torchvision
from torch import nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriterfrom res2net import *
from res2net import res2net101_26w_4s
from datasets import CUB200
# 准备数据集
train_data = CUB200("./CUB_200_2011", train=True)  # 共 5994 张图片
test_data = CUB200("./CUB_200_2011", train=False)  # 共 5794 张图片
train_data_size = len(train_data)
test_data_size = len(test_data)
print("训练数据集的长度为:{}".format(train_data_size))
print("测试数据集的长度为:{}".format(test_data_size))# 利用 DataLoader 来加载数据集
train_dataloader = DataLoader(train_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)# 创建网络模型
Test_module = res2net101_26w_4s(pretrained=True)
print(Test_module)# 创建损失函数
loss_fn = nn.CrossEntropyLoss()# 优化器
learning_rate = 0.01
optimizer = torch.optim.SGD(Test_module.parameters(), lr=learning_rate)# 设置训练网络的一些参数
# 记录训练的次数
total_train_step = 0
# 记录测试的次数
total_test_step = 0
# 训练的轮数
epoch = 200# 添加tensorboard
writer = SummaryWriter("./logs_train")
for i in range(epoch):print("------第 {} 轮训练开始------".format(i+1))for data in train_dataloader:# 训练步骤开始imgs, targets = dataoutputs = Test_module(imgs)loss = loss_fn(outputs, targets)# 优化器优化模型optimizer.zero_grad()loss.backward()optimizer.step()total_train_step += 1if total_train_step % 100 == 0:print("训练次数:{}, loss:{}".format(total_train_step, loss.item()))writer.add_scalar("train_loss", loss.item(), total_test_step)# 测试步骤开始total_test_loss = 0# 整体的正确率total_accuracy = 0with torch.no_grad():for data in test_dataloader:imgs, targets = datam = nn.Dropout2d(p=0.01)datas = m(imgs)outputs = Test_module(datas)loss = loss_fn(outputs, targets)total_test_loss = total_test_loss + loss.item()accuracy = (outputs.argmax(1) == targets).sum()total_accuracy = total_accuracy + accuracyprint("整体测试集上的Loss:{}".format(total_test_loss))print("整体测试集上的正确率:{}".format(total_accuracy/test_data_size))writer.add_scalar("test_loss", total_test_loss, total_test_step)writer.add_scalar("test_accuracy", total_accuracy/test_data_size, total_test_step)total_test_step += 1# 保存训练的模型torch.save(Test_module, "Test_module_{}.pth".format(i))print("模型已保存")writer.close()

datasets.py

import torchvision
from torch.utils.data import Dataset
import os
from PIL import Imageclass CUB200(Dataset):def __init__(self, root, image_size=64, train=True, transform=torchvision.transforms.ToTensor(), target_transform=None):'''从文件中读取图像,数据'''self.root = root  # 数据集路径self.image_size = image_size  # 图像大小(正方形)self.transform = transform  # 图像的 transformself.target_transform = target_transform  # 标签的 transform# 构造数据集参数的各文件路径self.classes_file = os.path.join(root, 'classes.txt')  # <class_id> <class_name>self.image_class_labels_file = os.path.join(root, 'image_class_labels.txt')  # <image_id> <class_id>self.images_file = os.path.join(root, 'images.txt')  # <image_id> <image_name>self.train_test_split_file = os.path.join(root, 'train_test_split.txt')  # <image_id> <is_training_image>self.bounding_boxes_file = os.path.join(root, 'bounding_boxes.txt')  # <image_id> <x> <y> <width> <height>imgs_name_train, imgs_name_test, imgs_label_train, imgs_label_test, imgs_bbox_train, imgs_bbox_test = self._get_img_attributes()if train: # 读取训练集self.data = self._get_imgs(imgs_name_train, imgs_bbox_train)self.label = imgs_label_trainelse: # 读取测试集self.data = self._get_imgs(imgs_name_test, imgs_bbox_test)self.label = imgs_label_testdef _get_img_id(self):''' 读取张图片的 id,并根据 id 划分为测试集和训练集 '''imgs_id_train, imgs_id_test = [], []file = open(self.train_test_split_file, "r")for line in file:img_id, is_train = line.split()if is_train == "1":imgs_id_train.append(img_id)elif is_train == "0":imgs_id_test.append(img_id)file.close()return imgs_id_train, imgs_id_testdef _get_img_class(self):''' 读取每张图片的 class 类别 '''imgs_class = []file = open(self.image_class_labels_file, 'r')for line in file:_, img_class = line.split()imgs_class.append(img_class)file.close()return imgs_classdef _get_bondingbox(self):''' 获取图像边框 '''bondingbox = []file = open(self.bounding_boxes_file)for line in file:_, x, y, w, h = line.split()x, y, w, h = float(x), float(y), float(w), float(h)bondingbox.append((x, y, x+w, y+h))# print(bondingbox)file.close()return bondingboxdef _get_img_attributes(self):''' 根据图片 id 读取每张图片的属性,包括名字(路径)、类别和边框,并分别按照训练集和测试集划分 '''imgs_name_train, imgs_name_test, imgs_label_train, imgs_label_test, imgs_bbox_train, imgs_bbox_test = [], [], [], [], [], []imgs_id_train, imgs_id_test = self._get_img_id()  # 获取训练集和测试集的 img_idimgs_bbox = self._get_bondingbox()  # 获取所有图像的 bondingboximgs_class = self._get_img_class()  # 获取所有图像类别标签,按照 img_id 存储file = open(self.images_file)for line in file:img_id, img_name = line.split()if img_id in imgs_id_train:img_id = int(img_id)imgs_name_train.append(img_name)imgs_label_train.append(imgs_class[img_id-1]) # 下标从 0 开始imgs_bbox_train.append(imgs_bbox[img_id-1])elif img_id in imgs_id_test:img_id = int(img_id)imgs_name_test.append(img_name)imgs_label_test.append(imgs_class[img_id-1])imgs_bbox_test.append(imgs_bbox[img_id-1])file.close()return imgs_name_train, imgs_name_test, imgs_label_train, imgs_label_test, imgs_bbox_train, imgs_bbox_testdef _get_imgs(self, imgs_name, imgs_bbox):''' 遍历每一张图片的路径,读取图片信息 '''data = []for i in range(len(imgs_name)):img_path = os.path.join(self.root, 'images', imgs_name[i])img = self._convert_and_resize(img_path, imgs_bbox[i])data.append(img)return datadef _convert_and_resize(self, img_path, img_bbox):''' 将不是 'RGB' 模式的图像变为 'RGB' 格式,更改图像大小 '''img = Image.open(img_path).resize((self.image_size, self.image_size))# img.show()if img.mode == 'L':img = img.convert('RGB')if self.transform is not None:img = self.transform(img)# print(img)return imgdef __getitem__(self, index):img, label = self.data[index], self.label[index]label = int(label) - 1  # 类别从 0 开始if self.target_transform is not None:label = self.target_transform(label)return img, labeldef __len__(self):return len(self.data)if __name__ == "__main__":train_set = CUB200("./CUB_200_2011", train=True)  # 共 5994 张图片test_set = CUB200("./CUB_200_2011", train=False)  # 共 5794 张图片

Res2Net.py:


import torch.nn as nn
import math
import torch.utils.model_zoo as model_zoo
import torch
import torch.nn.functional as F
__all__ = ['Res2Net', 'res2net50']model_urls = {'res2net50_26w_4s': 'https://shanghuagao.oss-cn-beijing.aliyuncs.com/res2net/res2net50_26w_4s-06e79181.pth','res2net50_48w_2s': 'https://shanghuagao.oss-cn-beijing.aliyuncs.com/res2net/res2net50_48w_2s-afed724a.pth','res2net50_14w_8s': 'https://shanghuagao.oss-cn-beijing.aliyuncs.com/res2net/res2net50_14w_8s-6527dddc.pth','res2net50_26w_6s': 'https://shanghuagao.oss-cn-beijing.aliyuncs.com/res2net/res2net50_26w_6s-19041792.pth','res2net50_26w_8s': 'https://shanghuagao.oss-cn-beijing.aliyuncs.com/res2net/res2net50_26w_8s-2c7c9f12.pth','res2net101_26w_4s': 'https://shanghuagao.oss-cn-beijing.aliyuncs.com/res2net/res2net101_26w_4s-02a759a1.pth',
}class Bottle2neck(nn.Module):expansion = 4def __init__(self, inplanes, planes, stride=1, downsample=None, baseWidth=26, scale=4, stype='normal'):""" 构造函数参数:inplanes: 输入通道维度planes: 输出通道维度stride: 卷积步长。替代池化层。downsample: 当stride = 1时为NonebaseWidth: conv3x3的基本宽度scale: 尺度数量。type: 'normal': 正常设置。 'stage': 新阶段的第一个块。"""super(Bottle2neck, self).__init__()# 计算卷积核的宽度width = int(math.floor(planes * (baseWidth / 64.0)))# 第一个1x1卷积层self.conv1 = nn.Conv2d(inplanes, width * scale, kernel_size=1, bias=False)self.bn1 = nn.BatchNorm2d(width * scale)# 计算重复次数if scale == 1:self.nums = 1else:self.nums = scale - 1# 如果是新阶段的第一个块,则使用平均池化层进行下采样if stype == 'stage':self.pool = nn.AvgPool2d(kernel_size=3, stride=stride, padding=1)# 定义重复的卷积层和BN层convs = []bns = []for i in range(self.nums):convs.append(nn.Conv2d(width, width, kernel_size=3, stride=stride, padding=1, bias=False))bns.append(nn.BatchNorm2d(width))# 创建了两个 nn.ModuleList 对象 self.convs 和 self.bns,用于存储多个卷积层和批量归一化层。self.convs = nn.ModuleList(convs)self.bns = nn.ModuleList(bns)# 最后一个1x1卷积层self.conv3 = nn.Conv2d(width * scale, planes * self.expansion, kernel_size=1, bias=False)self.bn3 = nn.BatchNorm2d(planes * self.expansion)# 激活函数self.relu = nn.ReLU(inplace=True)self.downsample = downsampleself.stype = stypeself.scale = scaleself.width = widthdef forward(self, x):residual = x# 第一个1x1卷积层的计算out = self.conv1(x)out = self.bn1(out)out = self.relu(out)# 将输出按照宽度进行分割spx = torch.split(out, self.width, 1)for i in range(self.nums):# 如果是第一个块或者是新阶段的第一个块,则直接取分割后的部分if i == 0 or self.stype == 'stage':sp = spx[i]else:# 否则,累加之前的部分sp = sp + spx[i]# 对部分进行卷积、BN和ReLU操作sp = self.convs[i](sp)sp = self.relu(self.bns[i](sp))if i == 0:out = spelse:# 将处理后的部分拼接起来out = torch.cat((out, sp), 1)# 如果尺度不为1且为正常设置,将最后一个部分拼接到一起if self.scale != 1 and self.stype == 'normal':out = torch.cat((out, spx[self.nums]), 1)# 如果尺度不为1且为新阶段的第一个块,则对最后一个部分进行平均池化并拼接elif self.scale != 1 and self.stype == 'stage':out = torch.cat((out, self.pool(spx[self.nums])), 1)# 最后一个1x1卷积层的计算out = self.conv3(out)out = self.bn3(out)# 如果存在下采样,则对输入进行下采样if self.downsample is not None:residual = self.downsample(x)# 残差连接并进行ReLU激活out += residualout = self.relu(out)return outclass Res2Net(nn.Module):def __init__(self, block, layers, baseWidth=26, scale=4, num_classes=1000):# 初始化Res2Net模型self.inplanes = 64  # 设置输入通道数为64self.baseWidth = baseWidthself.scale = scalesuper(Res2Net, self).__init__()  # 调用父类的构造函数# 定义网络的第一层:7x7的卷积层,输入通道数为3,输出通道数为64,步长为2,填充为3self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)# Batch Normalization层,对每个channel的数据进行标准化self.bn1 = nn.BatchNorm2d(64)# 激活函数ReLUself.relu = nn.ReLU(inplace=True)# 最大池化层,窗口大小为3x3,步长为2,填充为1self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)# 定义4个Res2Net的阶段(stage)self.layer1 = self._make_layer(block, 64, layers[0])  # 第一个阶段,输出通道数为64self.layer2 = self._make_layer(block, 128, layers[1], stride=2)  # 第二个阶段,输出通道数为128,步长为2self.layer3 = self._make_layer(block, 256, layers[2], stride=2)  # 第三个阶段,输出通道数为256,步长为2self.layer4 = self._make_layer(block, 512, layers[3], stride=2)  # 第四个阶段,输出通道数为512,步长为2# 全局平均池化层,将每个通道的特征图变成一个数self.avgpool = nn.AdaptiveAvgPool2d(1)# 全连接层,将512维的特征向量映射到num_classes维的向量,用于分类self.fc = nn.Linear(512 * block.expansion, num_classes)# 初始化网络参数for m in self.modules():if isinstance(m, nn.Conv2d):# 使用kaiming正态分布初始化卷积层参数nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')elif isinstance(m, nn.BatchNorm2d):# 将Batch Normalization层的权重初始化为1,偏置初始化为0nn.init.constant_(m.weight, 1)nn.init.constant_(m.bias, 0)def _make_layer(self, block, planes, blocks, stride=1):# 构建Res2Net的一个阶段(stage),包含多个blockdownsample = Noneif stride != 1 or self.inplanes != planes * block.expansion:# 如果输入输出通道数不一致,或者步长不为1,需要添加下采样层downsample = nn.Sequential(nn.Conv2d(self.inplanes, planes * block.expansion,kernel_size=1, stride=stride, bias=False),nn.BatchNorm2d(planes * block.expansion),)# 构建阶段的每个blocklayers = []layers.append(block(self.inplanes, planes, stride, downsample=downsample,stype='stage', baseWidth=self.baseWidth, scale=self.scale))self.inplanes = planes * block.expansionfor i in range(1, blocks):layers.append(block(self.inplanes, planes, baseWidth=self.baseWidth, scale=self.scale))return nn.Sequential(*layers)def forward(self, x):# 定义前向传播过程x = self.conv1(x)x = self.bn1(x)x = self.relu(x)x = self.maxpool(x)x = self.layer1(x)x = self.layer2(x)x = self.layer3(x)x = self.layer4(x)x = self.avgpool(x)x = x.view(x.size(0), -1)x = self.fc(x)return xdef res2net50(pretrained=False, **kwargs):"""Constructs a Res2Net-50 model.Res2Net-50 refers to the Res2Net-50_26w_4s.Args:pretrained (bool): If True, returns a model pre-trained on ImageNet"""model = Res2Net(Bottle2neck, [3, 4, 6, 3], baseWidth = 26, scale = 4, **kwargs)if pretrained:model.load_state_dict(model_zoo.load_url(model_urls['res2net50_26w_4s']))return modeldef res2net50_26w_4s(pretrained=False, **kwargs):"""Constructs a Res2Net-50_26w_4s model.Args:pretrained (bool): If True, returns a model pre-trained on ImageNet"""model = Res2Net(Bottle2neck, [3, 4, 6, 3], baseWidth = 26, scale = 4, **kwargs)if pretrained:model.load_state_dict(model_zoo.load_url(model_urls['res2net50_26w_4s']))return modeldef res2net101_26w_4s(pretrained=False, **kwargs):"""Constructs a Res2Net-50_26w_4s model.Args:pretrained (bool): If True, returns a model pre-trained on ImageNet"""model = Res2Net(Bottle2neck, [3, 4, 23, 3], baseWidth = 26, scale = 4, **kwargs)if pretrained:model.load_state_dict(model_zoo.load_url(model_urls['res2net101_26w_4s']))return modeldef res2net50_26w_6s(pretrained=False, **kwargs):"""Constructs a Res2Net-50_26w_4s model.Args:pretrained (bool): If True, returns a model pre-trained on ImageNet"""model = Res2Net(Bottle2neck, [3, 4, 6, 3], baseWidth = 26, scale = 6, **kwargs)if pretrained:model.load_state_dict(model_zoo.load_url(model_urls['res2net50_26w_6s']))return modeldef res2net50_26w_8s(pretrained=False, **kwargs):"""Constructs a Res2Net-50_26w_4s model.Args:pretrained (bool): If True, returns a model pre-trained on ImageNet"""model = Res2Net(Bottle2neck, [3, 4, 6, 3], baseWidth = 26, scale = 8, **kwargs)if pretrained:model.load_state_dict(model_zoo.load_url(model_urls['res2net50_26w_8s']))return modeldef res2net50_48w_2s(pretrained=False, **kwargs):"""Constructs a Res2Net-50_48w_2s model.Args:pretrained (bool): If True, returns a model pre-trained on ImageNet"""model = Res2Net(Bottle2neck, [3, 4, 6, 3], baseWidth = 48, scale = 2, **kwargs)if pretrained:model.load_state_dict(model_zoo.load_url(model_urls['res2net50_48w_2s']))return modeldef res2net50_14w_8s(pretrained=False, **kwargs):"""Constructs a Res2Net-50_14w_8s model.Args:pretrained (bool): If True, returns a model pre-trained on ImageNet"""model = Res2Net(Bottle2neck, [3, 4, 6, 3], baseWidth = 14, scale = 8, **kwargs)if pretrained:model.load_state_dict(model_zoo.load_url(model_urls['res2net50_14w_8s']))return modelif __name__ == '__main__':images = torch.rand(1, 3, 224, 224).cuda(0)model = res2net101_26w_4s(pretrained=True)model = model.cuda(0)# print(model(images).size())print(model)

总结

本周学习了OPAM模型,这篇文献提出了一种用于弱监督细粒度图像分类的OPAM方法,该方法综合了两个层次的注意模型:对象层定位图像对象,局部层选择对象的区分部分。这两个层面的关注共同促进了多视角、多尺度的特征学习,增强了它们之间的相互促进作用。此外,零件选择由对象-零件空间约束模型驱动,该模型结合了两个空间约束:对象空间约束保证了所选零件的高代表性,零件空间约束消除了冗余,增强了所选零件的区分性。这两个空间约束的结合促进了细微的、局部的歧视本土化。在4个广泛使用的数据集上的综合实验结果表明,OPAM方法与10多种最先进的方法相比是有效的。下周我将继续学习细粒度图像分类相关的论文和模型,同时我会进一步提高自己的pytorch代码能力。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/1850.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

WSL安装-问题解决

WslRegisterDistribution failed with error: 0x8004032d WslRegisterDistribution failed with error: 0x80080005 Error: 0x80080005 ??????? 解决&#xff1a; 1、 winr输入&#xff1a;optionalfeatures.exe 2、打开这两项

javaWeb项目-网吧网咖管理系统功能介绍

项目关键技术 开发工具&#xff1a;IDEA 、Eclipse 编程语言: Java 数据库: MySQL5.7 框架&#xff1a;ssm、Springboot 前端&#xff1a;Vue、ElementUI 关键技术&#xff1a;springboot、SSM、vue、MYSQL、MAVEN 数据库工具&#xff1a;Navicat、SQLyog 1、JAVA简介 JavaSc…

Vitis HLS 学习笔记--优化指令-BIND_OP_STORAGE

目录 1. BIND_OP_STORAGE 概述 1.1 BIND_OP 1.2 BIND_STORAGE 2. 语法解析 2.1 BIND_OP 2.2 BIND_OP 用法示例 2.3 BIND_STORAGE 2.4 BIND_STORAGE 示例 3. 实例演示 4. 总结 1. BIND_OP_STORAGE 概述 BIND_OP_STORAGE 其实是两个优化指令的合称&#xff1a;BIND_OP…

easyx库的学习(文字绘制)

前言 昨天刚刚写完了基本图形的制作&#xff0c;今天直接可以来看看&#xff0c;在easyx中使用文字 直接看代码吧 文字绘制 void drawTest() {printf("hello,EasyX");//指的是在控制台打印//设置字体大小&#xff0c;样式settextstyle(30, 0, "微软雅黑&quo…

模块三——二分:704.二分查找

文章目录 前言二分查找算法简介特点学习中的侧重点算法原理模板 题目描述算法原理解法一&#xff1a;暴力解法解法二&#xff1a;二分查找算法算法流程细节问题循环结束的条件为什么是正确的&#xff1f;时间复杂度 代码实现 前言 本系列博客是逐渐深入的过程&#xff0c;建议…

BCLinux8U6系统部署oceanbase分布式数据库社区版之一、准备 OBD 中控机

本文记录了在BCLinux8U6操作系统的虚拟服务器准备oceanbase开源数据库的 OBD 中控机的过程。 一、中控机环境 1、虚拟服务器硬件配置 2、操作系统版本信息 [rootlocalhost ~]# cat /etc/os-release NAME"BigCloud Enterprise Linux" VERSION"8.6 (Core)&qu…

【链表】Leetcode 两数相加

题目讲解 2. 两数相加 算法讲解 我们这里设置一个头结点&#xff0c;然后遍历两个链表&#xff0c;使用一个flag记录相加的结果和进位&#xff0c;如果两个链表没有走到最后或者进位不等于0&#xff0c;我们就继续遍历处理进位&#xff1b;如果当前的链表都遍历完成了&#x…

移动Web学习08-响应式布局bootstrap的使用

2、响应式布局 2.1、什么响应式布局 响应式布局是一种网页设计的方法&#xff0c;能够使网站在不同的设备上&#xff08;如桌面电脑、平板电脑、手机等&#xff09;呈现出最佳的用户体验。其核心思想是使网页能够根据用户的设备和屏幕尺寸自动调整布局和内容&#xff0c;以适…

代码随想录算法训练营第五十九天 | 503. 下一个更大元素 II、42. 接雨水

代码随想录算法训练营第五十九天 | 503. 下一个更大元素 II、42. 接雨水 503. 下一个更大元素 II题目解法 42. 接雨水题目解法 感悟 503. 下一个更大元素 II 题目 解法 题解链接 使用两个size class Solution { public:vector<int> nextGreaterElements(vector<in…

【网络原理】UDP协议的报文结构 及 校验和字段的错误检测机制(CRC算法、MD5算法)

目录 UDP协议 UDP协议的报文结构及注意事项 UDP报文结构中的校验和字段 1. 校验和主要校验的内容 2. UDP校验和的实现方式 3. CRC&#xff08;循环冗余校验&#xff09;算法 4. MD5&#xff08;Message Digest Algorithm 5&#xff09; UDP协议 上一篇文章提过&#xf…

计算机网络实验——学习记录五(TCP协议2)

一、TCP协议重传机制 TCP协议是一种面向连接、可靠的传输层协议。为了保证数据的可靠传输&#xff0c;TCP采用数据包重传的机制来应对网络传输过程中可能出现的丢包、错包和乱序等问题。 TCP协议的重传包括超时重传、快速重传、带选择确认SACK的重传和重复SACK重传四种。 二、…

grafana安装篇(1)

目录 grafana概念&#xff1a; 它具有以下主要特点&#xff1a; Grafana 常用于以下场景&#xff1a; 环境介绍&#xff1a; 前置环境&#xff1a; (1)保证可以连接外网 (2)防火墙和selinux已关闭 1.下载安装grafana10.0.1-1rpm包 2.启动grafana 3.浏览器访问 3.设置…

实验2 NFS部署和配置

一、实训目的 1.了解NFS基本概念 2.实现NFS的配置和部署 二、实训准备 1.准备一台能够安装OpenStack的实验用计算机&#xff0c;建议使用VMware虚拟机。 2.该计算机应安装CentOS 7&#xff0c;建议采用CentOS 7.8版本。 3.准备两台虚拟机机&#xff08;客户机和服务器机&…

Linux CPU 占用率 100% 排查

其他层面要考虑到的地方 mysql&#xff0c;有执行时间特别长的sql、死锁redis雪崩等相关问题并发导出数据量大Java定时器服务业务复杂&#xff0c;比如像每天要更新电商的统计表&#xff0c;每天发送优惠券等业务需要提前计算才能保证业务使用时的流畅性&#xff0c;我这个原因…

leetcode最大间距(桶排序+Python)

虽然直接排完序&#xff0c;再求最大差值更快&#xff0c;这里我们还是学一下桶排序。 桶排序主要维护一个bucket&#xff0c;初始bucket【i】 0&#xff0c;遍历nums&#xff0c;当i存在时&#xff0c;令bucket【i】 1&#xff0c;表示存在。遍历完nums&#xff0c;bucket中有…

DiT解读:当Diffusion遇上Transformer

前置知识 ViT Vision Transformer是一种基于Transformer架构的深度学习模型&#xff0c;专门用于处理计算机视觉任务。他的1出现给以往CNN base的图像工作带来了很多新的可能性 ViT的核心思想是将图像分割成均匀的图像块&#xff0c;然后将这些图像块转换为序列&#xff0c;…

【LAMMPS学习】八、基础知识(3.8)计算扩散系数

8. 基础知识 此部分描述了如何使用 LAMMPS 为用户和开发人员执行各种任务。术语表页面还列出了 MD 术语&#xff0c;以及相应 LAMMPS 手册页的链接。 LAMMPS 源代码分发的 examples 目录中包含的示例输入脚本以及示例脚本页面上突出显示的示例输入脚本还展示了如何设置和运行各…

4.点云数据的配准

1.点云配准ICP(Iterative Closest Point)算法 点云配准的原理及ICP(Iterative Closest Point)算法原理参照博客【PCL】—— 点云配准ICP(Iterative Closest Point)算法_icp点云配准-CSDN博客。 &#xff08;1&#xff09;点云配准原理&#xff1a;三维扫描仪设备对目标物体一…

Learn ComputeShader 02 Multiple kernels

前面已经成功创建了第一个compute shader&#xff0c;并且使用它替换掉quad的材质的纹理&#xff0c;现在我们将要在计算着色器中创建多个kernel。 首先调整上次的计算着色器&#xff0c;让它显示为红色。 然后再次创建一个kernel&#xff0c;显示为黄色。 结果应该是这样的…

mysql基础2——字段类型

整数类型 需要考虑存储空间和可靠性的平衡 浮点类型 浮点数类型不精准 将十进制数转换为二进制数存储 浮点数类型&#xff1a;float double real(默认是&#xff0c;double ) 如果需要将real设定为float &#xff0c;那么通过以下语句实现 set sql_mode "real_as…