#GKGNet

多标签分类遇上图卷积网络ViG

本文提出了第一个完全图卷积模型,基于分组K近邻的图卷积网络GKGNet,该模型在灵活且统一的图结构中,同时建模语义标签嵌入与图像块之间的连接。实验表明,GKGNet在具有挑战性的多标签数据集(即MS-COCO和VOC2007数据集)上,以显著较低的计算成本实现了最先进的性能。

多标签分类是一个具有挑战性的任务,旨在预测单个图像中的多个对象标签,同时建模标签与图像区域之间的复杂关系。尽管卷积神经网络CNN和视觉转换器Transformer在将图像处理为规则的像素块网格(patch)方面取得了成功,但这些表示对于捕捉不规则和不连续的兴趣区域来说并不理想。在这项工作中,我们提出了第一个完全图卷积模型,基于分组K近邻的图卷积网络(Group K-Nearest Neighbor based Graph Convolutional Network for Multi-Label Image Recognition, GKGNet),该模型在灵活且统一的图结构中,同时建模语义标签嵌入与图像块之间的连接。为了应对不同对象的尺度差异并从多个角度捕捉信息,我们提出了用于动态图构建和信息传递的Group KGCN模块。我们的实验表明,GKGNet在具有挑战性的多标签数据集(即MS-COCO和VOC2007数据集)上,以显著较低的计算成本实现了最先进的性能。

论文链接:https://arxiv.org/abs/2308.14378

代码地址:https://github.com/jin-s13/GKGNet

灵活图结构解决多标签分类任务

51c视觉~合集13_视觉AI

图1 CNN, Transformers, GCN对图片特征的提取

在多标签分类中,与某个标签相关的区域可能是复杂的,甚至是不连续的。例如,为了识别图像中狗的存在,需要关注多个区域,因为可能存在多只狗。卷积神经网络(CNN)将图像视为像素网格,并根据空间位置应用滑动卷积核。CNN可以很好地处理连续区域,但局限的感受野让它难以适应不规则的兴趣区域。

相比之下,视觉转换器(Vision Transformer)将图像视为一系列图像块(patch),具有全局感受野,并通过全局注意力从图像块中提取视觉特征。然而,对于小尺寸目标对象,大多数图像块属于背景,因此它们的注意力得分之和不能忽略,让特征提取受到来自背景噪声的干扰。而图方法(如 Vision GNN)将视觉图像块视为节点,通过将图像表示为图结构Graph来形成一种灵活的特征提取方法。

具体而言:

1. 感受野是全局的,根据语义特征相似性寻找邻居节点,能够灵活在全图寻找目标区域。

2. 特征传递是局部的,只发生在邻居节点之间。减少了背景区域的干扰。

这让图结构可以很好的适应目标区域的分布复杂,大小不一的难点。

方法介绍

51c视觉~合集13_视觉AI_02

图2 GKGNet网络结构

在这项工作中,我们提出了第一个用于多标签分类任务的完全图卷积网络(GCN),即基于分组K近邻的图卷积网络GKGNet。GKGNet将图像块和目标标签都视为图节点,并在统一的图结构中处理它们。

GKGNet构建了两种不同的图:一种是跨层次图(cross-level),建模目标标签和图像块之间的标签-对象关系;另一种是图像块层次图(patch-level),处理和更新图像块之间的图像特征。

这样,GKGNet能够自适应地整合兴趣补的特征,即使在存在不规则和不连续区域的情况下,也能有效更新视觉特征和标签嵌入的统一图表示。

如图2所示,图像块节点和标签节点通过四个分层阶段进行处理。

51c视觉~合集13_视觉AI_03

图3 KNN无法自适应地处理不同尺度的对象

图构建是GCN成功的关键。但是目前广泛使用的K近邻(KNN)构图方法只能会选择固定数目的邻居节点,无法自适应地处理不同尺度的对象。如图3,邻居数量K控制了提取和聚合区域特征的区域大小。过大的K会导致特征过度平滑,并涉及无效背景的干扰,而过小的K又难以选取足够的目标区域,影响特征提取和消息传递。

51c视觉~合集13_视觉AI_04

图4 Group KNN的分组和可重叠策略

因此,我们提出了基于Group KNN的GCN(Group KGCN)模块,该模块将节点特征拆分为多个组,并在每个组之间构建连接,如图4。通过设计每个组选择的邻居节点可以重叠,Group KNN方法允许目标节点与动态数量的源节点进行交互,并处理不同尺度的对象。

利用不同的组允许目标节点与不同数量(从𝐾个到𝐾 × 𝐺个)的源节点建立连接。如图4所示,子目标节点𝐷11连接到子源节点𝑆11 和𝑆21,而子目标节点𝐷12连接到子源节点𝑆31和𝑆41。这使得目标节点𝐷1能够与四个源节点进行交互:𝑆1,𝑆2,𝑆3 和 𝑆4。这种情况通常发生在目标节点涉及到更广泛的兴趣区域时, 例如大型目标对象。

相反,当不同组的邻居重叠时,所选择的源节点数量会减少。例如,𝐷2的两个子目标节点都连接到源节点𝑆2,导致仅与三个源节点进行交互:𝑆1,𝑆2和𝑆4。这种情况通常发生在目标节点仅涉及到小的兴趣区域时,例如小型目标对象,从而有助于在信息传递过程中绕过无关信息。

最终基于Group KNN的Group KGCN模块如图5所示。

51c视觉~合集13_视觉AI_05

图5 Group KGCN模块结构

实验结果

GKGNet在具有挑战性的多标签数据集(即MS-COCO和VOC2007数据集)上,以显著较低的计算成本实现了SOTA的性能。

51c视觉~合集13_视觉AI_06

表1 MS-COCO数据集结果

51c视觉~合集13_视觉AI_07

表2 VOC数据集结果

下表3证明了GKGNet模型各个组件的效果,P、C和G分别代表Patch-Level Graph、Cross-Level Graph和Group KNN。

51c视觉~合集13_视觉AI_08

表3 GKGNet模型组件的效果(MS-COCO数据集)

为了探索Group KNN在通用图像识别中的效果,我们将 Group KNN 应用于经典的图像分类模型ViG-Tiny,并在ImageNet-1K、CIFAR-10、 CIFAR-100和Flowers等数据集上进行实验,可以看到top-1准确率显著提高。

51c视觉~合集13_视觉AI_09

表4 Group KNN在通用图像识别上的性能

可视化展示

51c视觉~合集13_视觉AI_10

图6 GKGNet的可视化

在图6的可视化中,彩色色块表示图像块与标签的连接。我们可以观察到Group KNN有效地适应了对象的大小。对于较小的对象,最终选择的标签节点集中在包含目标对象的区域。对于较大的对象,最终选择的标签节点覆盖了足够的区域,并关注对象的不同部分。此外,通过利用不同的子组,Group KNN灵活地提取共现关系,例如汽车和交通信号灯之间的关系。

总结

在本文中,我们提出了GKGNet,一种新颖的全图卷积模型,用于多标签图像识别任务。我们首次研究了视觉特征和标签嵌入的统一图表示。提出的Group KGCN模块用于动态图构建和消息传递,有效处理不同对象的尺度变化,从不同角度捕捉信息,并建模不同对象的共现关系。在MS-COCO和VOC2007等公共基准数据集上的综合实验验证了我们方法的有效性。我们希望将多模态特征与动态图表示相结合的理念能够广泛应用,并引起社区对这一有前途方向的关注。未来,我们计划将我们的工作扩展到更广泛的基于图学习的问题,如点云和社交网络。视觉AI 开发板商城 天皓智联 whaoosoft aiot -- gitee可找到我



#YOLOv5+DeepSORT多目标跟踪

主要介绍如何使用Yolo-V5 + DeepSORT实现多目标检测与跟踪。

 目标跟踪是一种利用检测到对象的空间和时间特征在整个视频帧中跟踪检测到对象的方法。本文中,我们将与YOLOv5一起实现一种最流行的跟踪算法DeepSORT,并使用MOTA和其他指标在MOT17数据集上进行测试。目标跟踪简介

    深度学习中的跟踪是使用对象的空间和时间特征预测整个视频中对象位置的任务。从技术上讲,跟踪是获取初始检测集,分配唯一的 id,并在整个视频源的帧中跟踪它们,同时保持分配的 id。目标跟踪通常可以分为两步:

  • 目标定位检测模块:

    该模块负责使用一些对象检测器(如 YOLOv4、CenterNet 等)检测和定位画面中的对象。

  • 运动预测器:

    该模块负责使用其过去的信息预测对象的未来运动

【1】目标跟踪的必要性

    您可能会疑惑,为什么我们需要对象跟踪?为什么我们不能只使用物体检测?需要目标跟踪的原因很多,例如:

  • 对象检测失败时的跟踪:
    在许多情况下,对象检测器可能会失败。但是如果我们有一个对象跟踪器,它仍然能够预测帧中的对象。例如,考虑一个视频,其中一辆摩托车穿过树林,我们应用检测器来检测摩托车。这是在这种情况下会发生的情况,每当自行车被树遮挡或重叠时,检测器就会失效。但是,如果我们有一个跟踪器,我们仍然能够预测和跟踪摩托车。
  • ID 分配:
    在使用检测器时,它只显示对象的位置,如果我们只查看输出数组,我们将不知道哪个坐标属于哪个框。另一方面,跟踪器为其跟踪的每个对象分配一个 ID,并保持该 ID 直到该对象在该帧中的生命周期。
  • 实时预测:
    跟踪器非常快,通常比检测器快。由于这个特性,跟踪器可以在实时场景中使用,并且在现实世界中有很多应用。

【2】目标跟踪的应用    目标跟踪可以有许多实际应用:

  • 交通监控
    跟踪器可用于监控交通和跟踪道路上的车辆。它们可用于判断交通、检测违规行为等等。
  • 体育运动/赛事:
    跟踪器也可用于体育运动,如球跟踪或球员跟踪。这反过来又可以用来检测犯规、比赛中的得分手等等。
  • 多摄像头监控:
  • 在跟踪中,可以应用多摄像头监控。在这方面,核心思想是重新识别。如果一个人在一个带有 id 的摄像机中被跟踪,并且该人走出画面并在另一台摄像机中返回。然后该人将保留他们以前的相同身份。此应用程序可以帮助重新识别重新出现在不同相机中的对象,并可用于入侵检测。

【3】目标跟踪的类型 跟踪器可以根据许多类别进行分类,例如跟踪方法或要跟踪的对象数量。下面我们将通过一些示例来了解不同的跟踪器类型。

  • 单目标跟踪
    即使帧中存在许多其他对象,这些类型的跟踪器也仅跟踪单个对象。它们首先在第一帧中初始化对象的位置,然后在整个帧序列中跟踪它。这些类型的跟踪方法非常快。其中一些是使用传统计算机视觉构建的 CSRT、KCF 等等。然而,现在证明基于深度衰减的跟踪器比传统跟踪器准确得多。例如,GOTURN 和 SiamRPN 是基于深度学习的单目标跟踪器的示例。
  • 多目标跟踪:
    这些类型的跟踪器可以跟踪帧中存在的多个对象。与传统跟踪器不同,多个对象跟踪器或 MOT 是在大量数据上训练的。因此,它们被证明更准确,因为它们可以同时跟踪多个对象甚至不同类别的对象,同时保持高速。其中一些算法包括 DeepSORT、JDE 和 CenterTrack,它们是非常强大的算法,可以处理跟踪器面临的大部分挑战。
  • 通过检测跟踪
  • 目标检测器检测帧中的对象,然后跨帧执行数据关联以生成轨迹从而跟踪对象的跟踪算法类型。这些类型的算法有助于跟踪多个对象并跟踪框架中引入的新对象。最重要的是,即使对象检测失败,它们也有助于跟踪对象。
  • 无检测跟踪
  • 跟踪算法的类型,其中手动初始化对象的坐标,然后在进一步的帧中跟踪对象。如前所述,这种类型主要用于传统的计算机视觉算法。

DeepSORT简介

    DeepSORT 是一种计算机视觉跟踪算法,用于在为每个对象分配 ID 的同时跟踪对象。DeepSORT 是 SORT(简单在线实时跟踪)算法的扩展。DeepSORT 将深度学习引入到 SORT 算法中,通过添加外观描述符来减少身份切换,从而提高跟踪效率。要了解 DeepSORT,首先让我们看看 SORT 算法是如何工作的。

【1】简单的在线实时跟踪(SORT)    SORT 是一种对象跟踪方法,其中使用卡尔曼滤波器和匈牙利算法等基本方法来跟踪对象,并声称比许多在线跟踪器更好。SORT 由以下 4 个关键组件组成:

  • 检测
    这是跟踪模块的第一步。在此步骤中,对象检测器检测帧中要跟踪的对象。然后将这些检测传递到下一步。FrRCNN、YOLO 等检测器是最常用的。
  • 估计:
    在这一步中,我们将检测从当前帧传播到下一帧,使用恒速模型估计下一帧中目标的位置。当检测与目标相关联时,检测到的边界框用于更新目标状态,其中速度分量通过卡尔曼滤波器框架得到最优解。
  • 数据关联
    我们现在有了目标边界框和检测到的边界框。因此,成本矩阵被计算为每个检测与来自现有目标的所有预测边界框之间的交并比 (IOU) 距离。分配是使用匈牙利算法优化解决的。如果检测和目标的 IOU 小于称为 IOUmin 的某个阈值,则该分配被拒绝。该技术解决了遮挡问题并有助于维护 ID。
  • 跟踪目标ID的创建与删除:
    该模块负责ID的创建和删除。根据 IOUmin 创建和销毁唯一身份。如果检测和目标的重叠小于IOUmin,则表示未跟踪目标。如果没有为 TLost 帧检测到轨道,则会终止轨道,您可以指定 TLost 的帧数。如果一个对象重新出现,跟踪将隐含地在一个新的身份下恢复。

    SORT 算法做目标跟踪非常成功,可以击败许多最先进算法 。目标检测器为我们提供检测,卡尔曼滤波器为我们提供跟踪,匈牙利算法执行数据关联。那么,为什么我们还需要 DeepSORT? 

【2】深度排序

 SORT 在跟踪精度和准确度方面表现非常出色。但是 SORT 返回具有大量 ID 开关的轨道,并且在遮挡的情况下失败。这是因为使用了关联矩阵。            DeepSORT 使用结合了运动和外观描述符的更好的关联度量。DeepSORT 可以定义为跟踪算法,它不仅基于对象的速度和运动,而且还基于对象的外观来跟踪对象。

  •     出于上述目的,在实施跟踪之前离线训练一个具有良好区分性的特征嵌入。该网络在大规模人员重新识别数据集上进行训练,使其适用于跟踪上下文。在 DeepSORT余弦度量学习方法中训练深度关联度量模型。根据 DeepSORT 的论文,“余弦距离考虑了外观信息,当运动的判别力较低时,这对于在长期遮挡后恢复身份特别有用。” 这意味着余弦距离是一种度量,可帮助模型在长期遮挡和运动估计失败的情况下恢复身份。使用这些简单的东西可以使跟踪器更加强大和准确。
  • 【3】DeepSORT实现    DeepSORT 可以用于各种现实生活中的应用程序,其中之一就是体育运动。在本节中,我们将在足球和 100m短跑等运动中实现 DeepSORT。与 DeepSORT一起,YOLOv5 将用作检测器来检测所需的对象。该代码是在 Tesla T4 GPU 上的 Google Colab 上实现的。 
  • YOLOv5实现:首先,我们将克隆 YOLOv5 官方存储库以访问功能和预训练的权重。
!mkdir ./yolov5_deepsort
!git clone https://github.com/ultralytics/yolov5.git

安装requirements.

%cd ./yolov5
!pip install -r requirements.txt

现在我们已经准备好 YOLOv5,让我们将DeepSORT与它集成。

集成DeepSORT

同样,我们将克隆 DeepSORT 的官方实现以访问其代码和功能。

!git clone https://github.com/nwojke/deep_sort.git

   最后,一切就绪!但是 DeepSORT 将如何与检测器集成呢?YOLOv5detect.py file负责推理。我们将使用该detect.py文件并将使用它的 DeepSORT 功能添加到新文件中detect_track.py.

%%writefile detect_track.py
# YOLOv5 🚀 by Ultralytics, GPL-3.0 license.
"""
Run inference on images, videos, directories, streams, etc.

Usage - sources:
    $ python path/to/detect.py --weights yolov5s.pt --source 0             
 # webcam
                                                           
 img.jpg      # image
                                                           
 vid.mp4       # video
                                                            
path/         # directory
                                                            
path/*.jpg    # glob
                                                            
'https://youtu.be/Zgi9g1ksQHc'  # YouTube
                                                            
'rtsp://example.com/media.mp4'  # RTSP, RTMP, HTTP stream
"""

import argparse
import os
import sys
from pathlib import Path

import torch
import torch.backends.cudnn as cudnn

FILE = Path(__file__).resolve()
ROOT = FILE.parents[0]  # YOLOv5 root directory.
if str(ROOT) not in sys.path:
    sys.path.append(str(ROOT))  # Add ROOT to PATH.
ROOT = Path(os.path.relpath(ROOT, Path.cwd()))  # Relative.

# DeepSORT -> Importing DeepSORT.
from deep_sort.application_util import preprocessing
from deep_sort.deep_sort import nn_matching
from deep_sort.deep_sort.detection import Detection
from deep_sort.deep_sort.tracker import Tracker
from deep_sort.tools import generate_detections as gdet


from models.common import DetectMultiBackend
from utils.dataloaders import IMG_FORMATS, VID_FORMATS, LoadImages, LoadStreams
from utils.general import (LOGGER, check_file, check_img_size, check_imshow, check_requirements, colorstr, cv2,
                          increment_path, non_max_suppression, print_args, scale_coords, strip_optimizer, xyxy2xywh)
from utils.plots import Annotator, colors, save_one_box
from utils.torch_utils import select_device, time_sync


@torch.no_grad()
def run(
        weights=ROOT / 'yolov5s.pt',  # model.pt path(s)
        source=ROOT / 'data/images',  # file/dir/URL/glob, 0 for webcam
        data=ROOT / 'data/coco128.yaml',  # dataset.yaml path.
        imgsz=(640, 640),  # Inference size (height, width).
        conf_thres=0.25,  # Confidence threshold.
        iou_thres=0.45,  # NMS IOU threshold.
        max_det=1000,  # Maximum detections per image.
        device='',  # Cuda device, i.e. 0 or 0,1,2,3 or cpu.
        view_img=False,  # Show results.
        save_txt=False,  # Save results to *.txt.
        save_conf=False,  # Save confidences in --save-txt labels.
        save_crop=False,  # Save cropped prediction boxes.
        nosave=False,  # Do not save images/videos.
        classes=None,  # Filter by class: --class 0, or --class 0 2 3.
        agnostic_nms=False,  # Class-agnostic NMS.
        augment=False,  # Augmented inference.
        visualize=False,  # Visualize features.
        update=False,  # Update all models.
        project=ROOT / 'runs/detect',  # Save results to project/name.
        name='exp',  # Save results to project/name.
        exist_ok=False,  # Existing project/name ok, do not increment.
        line_thickness=3,  # Bounding box thickness (pixels).
        hide_labels=False,  # Hide labels.
        hide_conf=False,  # Hide confidences.
        half=False,  # Use FP16 half-precision inference.
        dnn=False,  # Use OpenCV DNN for ONNX inference.
):
    source = str(source)
    save_img = not nosave and not source.endswith('.txt')  # Save inference images.
    is_file = Path(source).suffix[1:] in (IMG_FORMATS + VID_FORMATS)
    is_url = source.lower().startswith(('rtsp://', 'rtmp://', 'http://', 'https://'))
    webcam = source.isnumeric() or source.endswith('.txt') or (is_url and not is_file)
    if is_url and is_file:
        source = check_file(source)  # Download.

    # DeepSORT -> Initializing tracker.
    max_cosine_distance = 0.4
    nn_budget = None
    model_filename = './model_data/mars-small128.pb'
    encoder = gdet.create_box_encoder(model_filename, batch_size=1)
    metric = nn_matching.NearestNeighborDistanceMetric("cosine", max_cosine_distance, nn_budget)
    tracker = Tracker(metric)
   
    # Directories.
    if not os.path.isdir('./runs/'):
      os.mkdir('./runs/')
    save_dir = os.path.join(os.getcwd(), "runs")
    print(save_dir)
    '''save_dir = increment_path(Path(project) / name, exist_ok=exist_ok)  # increment run
    (save_dir / 'labels' if save_txt else save_dir).mkdir(parents=True, exist_ok=True)  # make dir'''

    # Load model.
    device = select_device(device)
    model = DetectMultiBackend(weights, device=device, dnn=dnn, data=data, fp16=half)
    stride, names, pt = model.stride, model.names, model.pt
    imgsz = check_img_size(imgsz, s=stride)  # Check image size.

    # Dataloader.
    if webcam:
        view_img = check_imshow()
        cudnn.benchmark = True  # Set True to speed up constant image size inference.
        dataset = LoadStreams(source, img_size=imgsz, stride=stride, auto=pt)
        bs = len(dataset)  # batch_size.
    else:
        dataset = LoadImages(source, img_size=imgsz, stride=stride, auto=pt)
        bs = 1  # batch_size.
    vid_path, vid_writer = [None] * bs, [None] * bs

    # Run inference.
    model.warmup(imgsz=(1 if pt else bs, 3, *imgsz))  # Warmup.
    dt, seen = [0.0, 0.0, 0.0], 0
    frame_idx=0
    for path, im, im0s, vid_cap, s in dataset:
        t1 =  time_sync()
        im = torch.from_numpy(im).to(device)
        im = im.half() if model.fp16 else im.float()  # uint8 to fp16/32.        im /= 255  # 0 - 255 to 0.0 - 1.0
        if len(im.shape) == 3:
            im = im[None]  # Expand for batch dim.
        t2 = time_sync()
        dt[0] += t2 - t1

        # Inference.
        visualize = increment_path(save_dir / Path(path).stem, mkdir=True) if visualize else False
        pred = model(im, augment=augment, visualize=visualize)
        t3 = time_sync()
        dt[1] += t3 - t2

        # NMS.
        pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det)
        dt[2] += time_sync() - t3

        # Second-stage classifier (optional).
        # pred = utils.general.apply_classifier(pred, classifier_model, im, im0s)

        frame_idx=frame_idx+1
        # Process predictions.
        for i, det in enumerate(pred):  # Per image.
            seen += 1
            if webcam:  # batch_size >= 1
                p, im0, frame = path[i], im0s[i].copy(), dataset.count
                s += f'{i}: '
            else:
                p, im0, frame = path, im0s.copy(), getattr(dataset, 'frame', 0)

            p = Path(p)  # To Path.
            print("stem", p.stem)
            print("dir", save_dir)
            save_path = os.path.join(save_dir, p.name)  # im.jpg
            txt_path = os.path.join(save_dir , p.stem) # im.txt
            s += '%gx%g ' % im.shape[2:]  # Print string.
            gn = torch.tensor(im0.shape)[[1, 0, 1, 0]]  # Normalization gain whwh.
            imc = im0.copy() if save_crop else im0  # For save_crop.
            annotator = Annotator(im0, line_width=line_thickness, example=str(names))

            if len(det):
                # Rescale boxes from img_size to im0 size.
                det[:, :4] = scale_coords(im.shape[2:], det[:, :4], im0.shape).round()

                # Print results.
                for c in det[:, -1].unique():
                    n = (det[:, -1] == c).sum()  # Detections per class.
                    s += f"{n} {names[int(c)]}{'s' * (n > 1)}, "  # Add to string.

                # DeepSORT -> Extracting Bounding boxes and its confidence scores.
                bboxes = []
                scores = []
                for *boxes, conf, cls in det:
                  bbox_left = min([boxes[0].item(), boxes[2].item()])
                  bbox_top = min([boxes[1].item(), boxes[3].item()])
                  bbox_w = abs(boxes[0].item() - boxes[2].item())
                  bbox_h = abs(boxes[1].item() - boxes[3].item())
                  box = [bbox_left, bbox_top, bbox_w, bbox_h]
                  bboxes.append(box)
                  scores.append(conf.item())
               
                # DeepSORT -> Getting appearance features of the object.
                features = encoder(im0, bboxes)
                # DeepSORT -> Storing all the required info in a list.
                detections = [Detection(bbox, score, feature) for bbox, score, feature in zip(bboxes, scores, features)]

                # DeepSORT -> Predicting Tracks.
                tracker.predict()
                tracker.update(detections)
                #track_time = time.time() - prev_time

                # DeepSORT -> Plotting the tracks.
                for track in tracker.tracks:
                  if not track.is_confirmed() or track.time_since_update > 1:
                      continue

                  # DeepSORT -> Changing track bbox to top left, bottom right coordinates.
                  bbox = list(track.to_tlbr())

                  # DeepSORT -> Writing Track bounding box and ID on the frame using OpenCV.
                  txt = 'id:' + str(track.track_id)
                  (label_width,label_height), baseline = cv2.getTextSize(txt , cv2.FONT_HERSHEY_SIMPLEX,1,1)
                  top_left = tuple(map(int,[int(bbox[0]),int(bbox[1])-(label_height+baseline)]))
                  top_right = tuple(map(int,[int(bbox[0])+label_width,int(bbox[1])]))
                  org = tuple(map(int,[int(bbox[0]),int(bbox[1])-baseline]))

                  cv2.rectangle(im0, (int(bbox[0]), int(bbox[1])), (int(bbox[2]), int(bbox[3])), (255,0,0), 1)
                  cv2.rectangle(im0, top_left, top_right, (255,0,0), -1)
                  cv2.putText(im0, txt, org, cv2.FONT_HERSHEY_SIMPLEX, 1, (255,255,255), 1)

                  # DeepSORT -> Saving Track predictions into a text file.
                  save_format = '{frame},{id},{x1},{y1},{w},{h},{x},{y},{z}\n'
                  print("txt: ", txt_path, '.txt')
                  with open(txt_path + '.txt', 'a') as f:
                    line = save_format.format(frame=frame_idx, id=track.track_id, x1=int(bbox[0]), y1=int(bbox[1]), w=int(bbox[2]- bbox[0]), h=int(bbox[3]-bbox[1]), x = -1, y = -1, z = -1)
                    f.write(line)

            # Stream results.
            im0 = annotator.result()

            # Save results (image with detections and tracks).
            if save_img:
                if dataset.mode == 'image':
                    cv2.imwrite(save_path, im0)
                else:  # 'video' or 'stream'
                    if vid_path[i] != save_path:  # New video.
                        vid_path[i] = save_path
                        if isinstance(vid_writer[i], cv2.VideoWriter):
                            vid_writer[i].release()  # Release previous video writer.
                        if vid_cap:  # video
                            fps = vid_cap.get(cv2.CAP_PROP_FPS)
                            w = int(vid_cap.get(cv2.CAP_PROP_FRAME_WIDTH))
                            h = int(vid_cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
                        else:  # Stream.
                            fps, w, h = 30, im0.shape[1], im0.shape[0]
                        save_path = str(Path(save_path).with_suffix('.mp4'))  # Force *.mp4 suffix on results videos.
                        vid_writer[i] = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*'mp4v'), fps, (w, h))
                    vid_writer[i].write(im0)

        # Print time (inference-only).
        LOGGER.info(f'{s}Done. ({t3 - t2:.3f}s)')

    # Print results.
    t = tuple(x / seen * 1E3 for x in dt)  # Speeds per image.
    LOGGER.info(f'Speed: %.1fms pre-process, %.1fms inference, %.1fms NMS per image at shape {(1, 3, *imgsz)}' % t)
    if update:
        strip_optimizer(weights)  # Update model (to fix SourceChangeWarning).


def parse_opt():
    parser = argparse.ArgumentParser()
    parser.add_argument('--weights', nargs='+', type=str, default=ROOT / 'yolov5s.pt', help='model path(s)')
    parser.add_argument('--source', type=str, default=ROOT / 'data/images', help='file/dir/URL/glob, 0 for webcam')
    parser.add_argument('--data', type=str, default=ROOT / 'data/coco128.yaml', help='(optional) dataset.yaml path')
    parser.add_argument('--imgsz', '--img', '--img-size', nargs='+', type=int, default=[640], help='inference size h,w')
    parser.add_argument('--conf-thres', type=float, default=0.25, help='confidence threshold')
    parser.add_argument('--iou-thres', type=float, default=0.45, help='NMS IoU threshold')
    parser.add_argument('--max-det', type=int, default=1000, help='maximum detections per image')
    parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
    parser.add_argument('--view-img', action='store_true', help='show results')
    parser.add_argument('--save-txt', action='store_true', help='save results to *.txt')
    parser.add_argument('--save-conf', action='store_true', help='save confidences in --save-txt labels')
    parser.add_argument('--save-crop', action='store_true', help='save cropped prediction boxes')
    parser.add_argument('--nosave', action='store_true', help='do not save images/videos')
    parser.add_argument('--classes', nargs='+', type=int, help='filter by class: --classes 0, or --classes 0 2 3')
    parser.add_argument('--agnostic-nms', action='store_true', help='class-agnostic NMS')
    parser.add_argument('--augment', action='store_true', help='augmented inference')
    parser.add_argument('--visualize', action='store_true', help='visualize features')
    parser.add_argument('--update', action='store_true', help='update all models')
    parser.add_argument('--project', default=ROOT / 'runs/detect', help='save results to project/name')
    parser.add_argument('--name', default='exp', help='save results to project/name')
    parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')
    parser.add_argument('--line-thickness', default=3, type=int, help='bounding box thickness (pixels)')
    parser.add_argument('--hide-labels', default=False, action='store_true', help='hide labels')
    parser.add_argument('--hide-conf', default=False, action='store_true', help='hide confidences')
    parser.add_argument('--half', action='store_true', help='use FP16 half-precision inference')
    parser.add_argument('--dnn', action='store_true', help='use OpenCV DNN for ONNX inference')
    opt = parser.parse_args()
    opt.imgsz *= 2 if len(opt.imgsz) == 1 else 1  # Expand.
    print_args(vars(opt))
    return opt

def main(opt):
    check_requirements(exclude=('tensorboard', 'thop'))
    run(**vars(opt))

if __name__ == "__main__":
    opt = parse_opt()
    main(opt)
  •  只需在检测器代码中添加几行代码,我们就集成了可供使用的 DeepSORT。根据 YOLOv5 的官方实现,将结果保存到一个名为 runs 的新文件夹中,跟踪器结果和输出视频也将保存在同一文件夹中。让我们运行这个脚本,看看它是如何执行的。
  • 【4】推理   如前所述,此追踪器将在运动场景中进行测试。detect_track 脚本接受许多参数,但其中一些参数需要传递。--weights: 这里传递的权重名称会自动下载。我们将使用 YOLOv5m 媒体网络。--img: 指定图片大小,默认大小为 640--source:指定图像或视频文件、目录、网络摄像头或链接的路径。--classes:指定类的索引。例如 0 代表人,32 代表足球。更多类参考yolov5/data/coco.yaml。--line-thickness:指定边界框宽度。 首先,让我们在下面的视频中进行测试。 
!python detect_track.py --weights yolov5m.pt  --img 640  --source ./football-video.mp4 --classes 0 32 --line-thickness 1
  •     速度很快,每张图像的推理时间为 20.6 毫秒,大致相当于 50 FPS。甚至输出看起来也不错。输出如下所示。
  •     检测器的工作做得非常好。但是在球的情况下以及球员的情况下,遮挡都没有得到很好的处理。此外,还有许多 ID 开关。视频中的球员被很好地跟踪,但由于运动模糊,球甚至没有被检测到,也没有被正确跟踪。也许 DeepSORT 在密度较低的视频上会表现更好。
  •     让我们在另一个短跑比赛的体育视频上对其进行测试。
!python detect_track.py --weights yolov5m.pt  --img 640  --source ./sprint.mp4 --save-txt --classes 0 --line-thickness 1

    速度仍然同样快,大约 52 FPS。输出还是很准确的,ID开关非常少,但是在遮挡时无法保持。输出可以在下面查看。

  •    因此,我们可以从这些结果中得出结论,DeepSORT 不能很好地处理遮挡并可能导致 ID 切换。 

评价


  •   在试验和测试新事物时,评估总是扮演着重要的角色。同样,对于 DeepSORT,我们将根据一些标准指标来判断其性能。众所周知,DeepSORT 是一种多目标跟踪算法,因此要判断其性能,我们需要特殊的指标和基准数据集。我们将使用 CLEARMOT 指标来判断我们的 DeepSORT 在 MOT17 数据集上的性能。让我们在下一节中更深入地研究这些内容。

【1】MOT挑战基准    MOT Challenge benchmark是一个框架,它提供了大量具有挑战性的真实世界序列、准确注释和许多指标的数据集。MOT Challenge 由各种数据集组成,例如人、对象、2D、3D 等等。更具体地说,每年都会发布数据集的几种变体,例如 MOT15、MOT17 和 MOT20,用于衡量多个对象跟踪器的性能。MOT15,以及过去几年提交的众多最先进的结果MOT16,其中包含新的具有挑战性的视频MOT17,用更精确的标签扩展了 MOT16 序列MOT20,其中包含自上而下视图的视频    对于我们的评估,我们将使用 MOT17 数据集的一个子集。数据集格式:    所有数据集变体都具有相同的格式,为我们提供视频文件seqinfo.ini – 关于文件的信息gt.txt - 跟踪的地面实况注释det.txt - 检测的地面实况注释    输出格式:        为了评估性能,跟踪器的输出应采用特定格式,即     frame,id,x1,y1,x2,y2,1,-1,-1,-1 其中:帧:帧序号id:被跟踪对象的idx1, y1:左上坐标x2, y2:右下坐标    Ground Truth格式:        我们将需要注释 zip 文件中的 gt.txt 文件进行评估。它可以从这里下载。格式如下:Frame、ID、bbox、是否忽略、classes、occlusion帧:视频的帧数ID:被跟踪对象的IDbbox:物体的边界框坐标是否忽略:是否忽略对象,0表示忽略classes:包含行人、汽车、静态人等类别。遮挡:显示对象是否被其他对象覆盖或切割


【2】ClearMOT指标    它是用于评估跟踪器在不同参数上的性能的框架。总共给出了 8 个不同的指标来评估对象检测、定位和跟踪性能。它还为我们提供了两个新颖的指标:多目标跟踪精度(MOTP)多目标跟踪精度(MOTA)    这些指标有助于评估跟踪器的整体优势并判断其总体性能。其他措施如下:

  对于人员跟踪,我们将基于 MOTA 评估我们的性能,它告诉我们检测、未命中和 ID 切换的性能。跟踪器的准确度,MOTA(多目标跟踪准确度)计算公式为:

 其中 FN 是误报的数量,FP 是误报的数量,IDS 是时间 t 的身份转换数量,GT 是基本事实。MOTA 也可以是负面的。

【3】性能评估    如前所述,MOT17 将用于测试,您可以从此处下载视频序列。让我们一个一个地运行序列。

 为了评估性能,我们将通过克隆来引用以下存储库。

!git clone https://github.com/shenh10/mot_evaluation.git
%cd ./mot_evaluation

  视频分辨率为 960×540,注释分辨率为 1920×1080。因此,需要根据视频分辨率修改注释。您可以从此处直接下载调整大小的地面实况注释。     我们将使用evaluate_tracking.py下面的文件mot_evaluation来评估结果。该脚本采用三个参数,如下所示:--seqmap: 这里,指定要进行评估的视频文件名。--track:指定跟踪结果文件夹的路径。--gt:指定ground truth文件路径。

!python evaluate_tracking.py --seqmap './MOT17/videos' --track './yolov5/runs/' --gt './label_960x540'

    这给出了所有三个视频的准确性,并给出了所有视频的平均结果。这是结果。

结论

    DeepSORT 在视觉上的表现相当不错。然而,这些指标显示的结果并不那么好。DeepSORT 有许多缺点,例如 ID 切换、不良遮挡处理、运动模糊等等。我们得到的平均准确率是 28.6,非常低。但是,这里好的一件事是速度。所有的事情都可以通过使用最新的算法来解决。FairMOT、CentreTrack 等算法非常先进,可以显着减少 ID 切换并很好地处理遮挡。因此,请多尝试 DeepSORT 一段时间。尝试不同的场景,使用 YOLOv5 模型的其他变体或在自定义对象检测器上使用 DeepSORT。

参考链接:

https://learnopencv.com/understanding-multiple-object-tracking-using-deepsort/



#用「画家流水线」的方式理解Transformer中间层

Transformer架构层层堆叠,包含十几亿甚至几十亿个参数,这些层到底是如何工作的?当一个新奇的比喻——「画家流水线」,被用于类比并理解Transformer架构的中间层,情况突然变得明朗起来,并引出了一些有趣的发现。

尽管Transformer架构已经主宰了当今几乎所有的大模型,但我们依旧对它的工作原理知之甚少。

而且,基于Transformer的预训练LLM动辄有几十亿参数,很难直接对模型进行可解释性分析。

同时,模型中间层由N个相同的块堆叠在一起,它们之间唯一的区别只有层次位置和权重值,这就让理解中间层更加困难。

然而,最近发表的一篇论文却给出了一个十分通俗易懂的比喻——「画家流水线」。

论文地址:https://arxiv.org/pdf/2407.09298v1

有着「东京AI梦之队」之称的Sakana AI,联合IBM前AI负责人Satya Nitta创始的Emergence AI,两个团队的研究人员用一种新的「打开方式」来解释Transformer架构的中间层。

值得一提的是,这篇论文作者之一Llion Jones同样也是当年Transformer架构的共同创建者之一。

那么,「画家流水线」这个比喻该如何理解呢?

首先,输入被看作是一张画布,输入通过N个组成中间层的块的过程,就像是画布在「画家流水线」上进行传递的过程。

有些画家擅长画鸟,而有些画家则更擅长画鱼。每个画家从前面的画家手中接过画布,然后决定是在画上添几笔,还是直接传给后面的画家。

在这个类比中,非常重要的一点是,每个画家都使用相同的「词汇」来理解画作,因此一个画家可以在流水线上从前一个画家手中接过画作,但不会因为对画面理解不同而造成灾难。

画家们也可以重新排序(调整图层的前后顺序),甚至可以同时添加笔触,就像N个块可以并行运行。

这个类比并不是一个严谨的理论,但可以提供一个帮助我们思考Transformer层的有趣视角。

在这个类比的启发下,研究人员提出了一些假设,并通过实验来验证这些假设是否成立——

  • 不同层是否使用相同的表征空间?
  • 所有的层都是有必要的吗?
  • 中间层是否都在执行相同的功能?
  • 层的顺序重要吗?
  • 我们能并行运行各层吗?
  • 顺序是否对与某些特定任务而言更重要
  • 循环是否有助于并行层?
  • 哪些变体对性能的损害最小?

实验

主要用于实验包括两种预训练LLM,分别是decoder-only架构的Llama2-7B,以及encoder-only架构的BERT。Llama2-7B有70亿个参数和32层(每层含2.02亿个参数),BERT仅有24层和3.4亿个参数。

在下述所有实验过程中,模型都是冻结的。除了对BERT进行GLUE基准测试时进行了标准的微调步骤,参数没有经过任何修改。

评估过程采用了ARC(科学考试题)、HellaSwag(常识)、GSM8K(数学应用题)、LAMBADA(单词预测)等常用基准。

其中LAMBADA任务可以衡量模型困惑度(perplexity),任务最接近预训练时的原始token预测。

结果发现,Transformer的中间层有一定程度的一致性,但不冗余,而且对数学、推理任务而言,各层的运行顺序比在语义任务中有更重要的影响。

各层「说同一种语言」?

Transformer中的不同层是否共享相同的表示空间?

为了回答这个问题,论文采用的方法是让模型跳过特定层或调换相邻层的顺序,观察会不会出现灾难性后果。

图2中展示了Llama 2 7B在跳过或调换一些层后,模型整体在Open-LAMADA基准上的表现。

可以看到,除了起始和末端的几层,模型对这两种架构修改都表现出了相当强的鲁棒性。

因此可以得出初步结论:1)中间层共享同一个表示空间,2)表示空间与「外层」(第一层和最后几层)不同。

为了进一步验证,论文还进入模型内部,测量了不同层中隐藏状态内激活函数的余弦相似度(图3),表明这种一致性在三个模型的所有中间层都成立。

上图还可以很清晰看到,模型各层自然形成了4~5个不同的相似组,比如Llama 2 13B模型中分别是:第0层,1-3层、中间层,以及最后的1层或2层。

据此,Transformer中的所有层可以被大致分为三类:起始层、中间层和结束层。

此外,图3中的矩阵也能和图2中的模型分数相对应,更能有力证明,中间层之间共享语义表达空间。

所有层都必要?

为了进一步检验中间层的重定向空间是否真正共享(除了具有接近的余弦相似性),研究人员尝试跳过多个层。

也就是说,将第N层的输出直接送入第N+M层的输入(其中M>1),从而「跳过」M-1层。

在不进行任何微调的情况下,这个实验是要看看N+M层能否理解来自N层的激活,尽管它在训练中只接受了来自N+M-1层的输入。

结果显示,Llama2-7B和BERT-Large的许多基准性能都出现了一定程度的下降。

那么,所有层都有必要吗?这一问题已经有了答案。

No! 并非所有层都是必要的,至少有几个中间层可以跳过,而不会发生灾难性故障。

左图:Llama2-7B跳过N层~32-N层的基准测试结果(归一化);右图:BERT跳过N层~24-N 层的基准测试结果(未归一化)

中间层功能相同吗?

如果中间层共享一个共同的表征空间,这是否意味着这些层是多余的呢?

为了验证这一点,研究人员重新进行了上一小节的「跳过」实验。

但不同的是,这次不是直接跳过M个中间层,而是用模型最中心的的一层代替全部M个层(Llama是第16层,BERT是第12层),相当于在这一层上循环T-2N+1次,其中T是层的总数。

结果表明,随着被替换层数M的增加,基准测试结果迅速下降。

在研究人员所尝试的所有测试中,这一项测试的变化是最严重的,比直接跳过一些层还要严重得多。

因此,中间层功能相同吗?这一问题的答案是——

No! 在中间层之间共享权重是灾难性的,这表明中间层在执行不同的功能。

用中心层替换M个中间层(左侧经过归一化,右侧未经归一化)

顺序重要吗?

之前的实验表明,中间层共享一个表征空间,但对这个空间执行不同的操作。

那么另一个问题来了——这些操作的执行顺序有多重要?

论文进行了两组实验来检验这个问题。首先,以与预训练完全相反的顺序运行中间层,如下图所示:

第二组则是以随机顺序运行中间层,最终结果是取10个随机种子进行实验后的均值。

图6和图7分别展示了中间层完全翻转和随机顺序的结果,虽然都出现了一定程度的性能下降,但两者的结果都优于直接跳过的情况。

所以,中间层顺序重要吗?这一问题的答案是——

比较重要。改变中间层的执行顺序,无论是随机打乱或者完全翻转,都会导致模型性能退化。

并行运行

如果层本身的存在比它们的执行顺序更重要,那么我们是否可以独立运行各层,最后合并它们的结果呢?

比如像下图中,将原本堆叠在一起的中间层展开,并行运行后取各层输出的平均值,传递给最后的N个层。

实验结果显示,GSM8K(数学应用题)基准中,模型性能有剧烈的变化,直线下降,其他基准分数的下滑则平缓得多。

我们暂且可以下这样一个结论:并行运行是可行的,但解决数学问题除外。

要理解这种性能下降,可以用我们的「画家流水线」进行类比:某些中间层只有在看到合适输入时,才能对结果有所贡献,就像一个擅长画车轮的画家,只有在画面上看到汽车车身时,才更有可能画出轮子。

如果是这种情况,将中间层并行运行的过程迭代多次应该会提高性能。

如下图所示,论文将多个并行层的平均输出再作为输入反馈回去,如此进行一定次数的循环。

图9显示了循环3次的结果,与图8中没有循环的方案相比,性能曲线的确相对平缓,尤其是在图右BERT模型未经归一化的分数上更加明显。

图10更清楚直观地展示了,并行的中间层数和循环次数如何影响性能,其中红框圈出了每列上的最高值。

除了29层和31层(接近Llama 2 7B的总层数32)得出例外的结果,从5层到27层都呈现出一致的趋势:最佳迭代次数大致与并行化层数呈线性比例。

实验结果总结

将上述所有实验结果放到同一张图中(图11),我们就能比较不同变体对模型性能的影响程度

左图(Llama2)取各基准的中值,右图(BERT)取各基准的平均值

「随机化层顺序」和「循环并行」分别在Llama2和BERT-Large上造成了最少的性能下降,「中间重复」方案(用中心层运行多次代替整个中间层)则在两个模型上都造成了最严重的滑坡。

讨论

自从Transformer发布后,大多数工作都在关注架构的修改和优化,以达到性能提升或参数减少。这篇论文则提供了另一种视角,调查了层并行化和重用的影响。

基于「Transformer层即画家」这个类比,我们开头提出的几个问题都通过实验得到了答案,最后得到了3个有趣的发现:

  • 所有Transformer层可以大致分为三类:起始层、中间层和结束层,其中中间层占比最大;
  • 中间层具有一定程度的一致性,但并不冗余;
  • 与语义任务相比,各层的执行顺序对数学和推理任务更为重要。

为什么Transformer架构面对各种架构修改时能表现出如此强大的鲁棒性?作者表示将在之后的工作中再深入研究。

一个可能的假设是,训练过程中的残差连接是各层共享相同表征的必要条件。

我们已经知道,残差连接有助于解决梯度消失问题,然而相比没有残差连接的Transformer,加上残差会降低性能。

如果能在没有残差的Transformer上重新运行上述架构的变体,看看是否会破坏完全无残差模型所取得的微薄收益,那将会非常有趣。

对于未来的其他工作,研究人员还计划「解冻」模型,并研究Transformer是否需要(以及需要多长时间)通过微调来适应上述的架构变化。

虽然本文的目的是更好地理解Transformer的中间层,而非引入新模型,但根据实验结果,中间层并行或者干脆跳过都可以用适度的准确性损失换取更低的推理延迟。

作者团队

本文作者分别来自两家AI初创公司:Sakana AI和Emergence AI。

Sakana AI在今年年初刚刚获得3000万美元的种子轮融资,由Lux Capital领投,并得到了硅谷顶级风投公司Khosla Ventures以及Jeaf Dean、Alexandr Wang等大佬的支持。

公司研发的重点是基于自然启发的新型基础模型,创始团队也是星光熠熠,一半成员来自「AI黄埔军校」——谷歌大脑和DeepMind。

相比于关注基础研究的Sakana,Emergence AI更关注应用,专门从事LLM驱动的multi-agent系统研发。

公司联合创始Satya Nitta曾担任IBM研究院「AI解决方案」领域的全球主管,其中的许多研究人员和工程师也同样来自谷歌、Meta、微软、亚马逊和Allen AI等顶尖机构。

Emergence上个月刚刚从Learn Capital获得9720万美元的资金,以及额外的总计超过一亿美元的信贷额度,未来的发展也是前途可期。

参考资料:

https://arxiv.org/abs/2407.09298v1



#CFPT

干翻一众 FPN,专攻小目标检测!CFPT:无需上采样的新型特征金字塔网络

在本文中,作者提出了跨层特征金字塔 Transformer (CFPT),这是一种无需上采样的新型特征金字塔网络,专门为航拍图像中的小目标检测而设计。 

目标检测一直是一项具有挑战性的任务。大多数当前检测器优先考虑新颖的检测框架,却常常忽视了对基本组成部分(如特征金字塔网络)的研究。在本文中,作者提出了跨层特征金字塔 Transformer (CFPT),这是一种无需上采样的新型特征金字塔网络,专门为航拍图像中的小目标检测而设计。CFPT采用了两个精心设计的、具有线性计算复杂度的注意力模块:跨层通道注意力(CCA)和跨层空间注意力(CSA)。


CCA通过划分通道 Token 组来实现跨层交互,以沿空间维度感知跨层全局信息;而CSA则通过划分空间 Token 组来完成跨层交互,以沿通道维度感知跨层全局信息。通过整合这些模块,CFPT一步实现跨层交互,从而避免了元素逐点加和以及层与层之间传递引起的语义差距和信息丢失。此外,CFPT融入了全局上下文信息,这增强了小目标的检测性能。为了在跨层交互中进一步提升位置感知,作者基于层间相互感受野提出了跨层一致相对位置编码(CCPE)。作者在两个具有挑战性的航拍图像目标检测数据集上评估了CFPT的有效性,分别是VisDrone2019-DET和TinyPerson。大量实验证明了CFPT的有效性,它在计算成本更低的情况下,性能优于现有的特征金字塔网络。


相关代码将在https://github.com/duzw9311/CFPT发布。

I Introduction

得益于卷积神经网络(CNNs)和视觉 Transformer (ViTs)的进步,现有的目标检测器已经取得了显著的发展,并在自动驾驶、人脸检测、医学图像分析和工业质量检查等众多应用领域奠定了其基本解决方案的地位。

作为目标检测的一个子领域,小目标检测由于在卷积和池化操作过程中小目标特征可能会被大目标特征所淹没或遮盖,因此相较于传统的目标检测任务面临更大的挑战。如图3所示,作者展示了两个经典的小目标检测数据集在航拍图像中的数据分布箱线图:VisDrone2019-DET [2] 和 TinyPerson [3]。箱线图凸显了 VisDrone2019-DET 数据集不仅包含大量小目标(20到30像素),而且存在显著的尺度变化。相比之下,TinyPerson 数据集相比于 VisDrone2019-DET,主要包含更小尺寸的目标,大多数目标的大小不超过20像素。无人机飞行高度和拍摄角度显著影响目标尺度分布,导致在航拍图像上的目标检测性能相对较差。

51c视觉~合集13_视觉AI_11

图1:在VisDrone2019-DET数据集上,各种最先进特征金字塔网络的性能比较。作者通过替换RetinaNet [1]中的 Neck 组件来评估它们的性能。

51c视觉~合集13_视觉AI_12

为了应对这些挑战,已经连续提出了许多研究。鉴于无人机场景中前景的比例较小,现有的解决方案通常采用由粗到精的检测方案[4, 5, 6]。在粗略预测阶段,通常使用一个通用检测器来检测目标并预测密集的目标簇。随后,在细化阶段,通常会修剪、上采样该簇,并将其重新输入检测器以进行精细搜索。尽管上述模型架构可以有效地适应无人机视角,并且在较低的计算成本下比直接输入高分辨率图像增强了各种检测器的性能,但它仍然缺少专为空中图像中目标检测定制的基本组件,例如特征金字塔网络。特征金字塔网络作为图像金字塔的低计算成本替代方案,在多种检测器中被广泛使用,并已成为每个检测器的基本组成部分。最早的FPN [7]使用自上而下的单向路径将语义信息整合到浅层特征图中,有效增强了模型在多尺度目标检测方面的能力。由于逐层传递的单向路径不可避免地造成信息丢失[8],随后的特征金字塔网络逐渐过渡到层与层之间的直接交互。

作者提出CFPT,一种新的跨层特征金字塔结构,它通过在不同方向(即空间方向和通道方向)促进跨层信息交互来增强模型的表达能力。

通过整合这两种交互,CFPT能够有效地捕获小型目标的必要全局上下文信息,同时保持较低的计算成本。

  • 作者提出CCPE,一种基于层间互惠感受野的新型位置编码方法,旨在增强模型在跨层交互过程中对空间和通道位置的认识。
  • 通过在VisDrone2019-DET和TinyPerson数据集上的大量实验,作者证明了CFPT在航拍图像中小型目标检测的有效性。

II Related Work

Small Object Detection in Aerial Images

现代的目标检测器通常通过连续的卷积和池化层降低输入图像的分辨率,力求在性能与计算复杂度之间达到最佳平衡。因此,检测小物体本质上比常见目标检测更具挑战性,因为它们的小尺寸增加了在下采样过程中信息丢失的风险。

对于航拍图像中的小目标检测,ClusDet [17]采用了一种从粗到精的方案,首先检测密集的目标簇,然后在这些簇内进行搜索细化,以提高模型检测小物体的能力。DMNet [18]简化了ClusDet的训练过程,通过采用密度图生成网络来为簇预测生成密度图。遵循类似的检测流程,CRENet [19]和GLSAN [4]进一步增强了聚类预测算法,并优化了细粒度预测方案。UFPMP-Det [6]采用UFP模块和MPNet预测子区域,并将它们组装成单一图像以实现高效的单一推理,从而提高了检测的准确性和效率。CEASC [20]利用稀疏卷积优化了航拍图像中目标检测的传统检测器,降低了计算需求同时保持了竞争力的性能。DTSNNet [21]在Backbone和Neck之间引入了人工设计的块,以提高模型对多尺度特征的敏感性,并采用了专门针对小物体的训练样本选择方法。

上述解决方案优化了各种检测器,以适应航拍图像中的目标检测场景,而作者提出了一种专门针对这一背景下小目标检测的新型特征金字塔网络。

Feature Pyramid Network

为了减轻图像金字塔带来的巨大计算成本,特征金字塔网络(FPN)作为一种有效且高效的替代方法出现,它提高了各种检测器的性能。FPN [7] 利用一系列自顶向下的快捷连接来增强浅层特征图中缺乏的语义信息。基于FPN,PAFPN [12] 提出使用自底向上的快捷连接来解决深层特征图中细节信息的不足。Libra-RCNN [22] 通过结合非局部块来改进原始特征,以获得平衡的交互特征。为了减轻多尺度特征图中的语义差距,AugFPN [23] 引入了连贯的监督分支,并提出了ASF,用于跨多尺度动态特征融合。FPG [8] 使用规则网格表示特征尺度空间,并通过平行路径之间的多方向横向连接进行融合,从而增强了模型的特征表示能力。AFPN [11] 通过深层和浅层特征图的跨 Level 融合迭代改进多尺度特征,在具有常见尺度分布的目标检测中取得了竞争性的性能。

与先前方法不同,作者提出了CFPT,它利用全局上下文信息,并策略性地强调浅层特征图,以增强航拍图像中小型目标的检测。

Vision Transformer

作为计算机视觉中Transformer[24]的扩展,Vision Transformer (ViT)[25]在各种各样的视觉场景中[26, 27, 28]展示了显著的潜力。由于传统ViT与图像分辨率相关的二次计算复杂度,后续研究主要集中在开发轻量级的替代方案。Swin Transformer[29]通过限制特定窗口内的交互,并在交互过程中移动这些窗口以实现全局感受野。局部ViT[30, 31, 32]通过局部窗口内的交互引入局部诱导偏差,有效降低了模型的计算复杂度并加快了收敛速度。轴向注意力[33]通过将交互限制在图像宽度和高度上的条带,减少了计算复杂度。

遵循类似的轻量级概念,作者设计了两款具有线性复杂度(即CCA和CSA)的注意力块,以跨层捕捉沿各种方向(即空间方向和通道方向)的全局上下文信息,从而增强了模型对小物体的检测能力。

III Methodology

在本节中,作者将详细介绍所提出的跨层特征金字塔 Transformer (CFPT)。在第三节A部分,作者首先概述了所提出CFPT的整体架构。随后,在第三节B和C部分,作者介绍了CFPT的两个关键组成部分,即跨层通道注意力(CCA)和跨层空间注意力(CSA)。在第三节D部分,作者提出了一种新颖的跨层一致相对位置编码(CCPE),旨在增强模型的跨层位置感知能力。

Overview

如图4所示,CFPT采用多个并行的CBR块来构建跨层特征交互的输入,这些输入来自特征提取网络(如ResNet[34])的多级特征图输出,从而降低了计算复杂度,满足了大多数检测器在架构上的要求。通过利用堆叠的跨层注意力模块(CAMs),CFPT增强了模型利用全局上下文信息和跨层多尺度信息的能力。

51c视觉~合集13_视觉AI_13

具体来说,CAM模块由一系列跨层通道注意力(CCA)和跨层空间注意力(CSA)组成。CCA沿着通道维度促进局部跨层交互,从而通过每个通道 Token 组的交互在空间维度上建立一个全局感受野。相反,CSA沿着空间维度促进局部跨层交互,通过每个空间 Token 组的交互捕捉通道维度的全局上下文信息。此外,作者通过在CAM的输入和输出之间使用捷径分支来进一步提高梯度增益。

假设经过CBR块后每个尺度的特征图可以表示为 , 其中  是输入层的数量, 每个特征图的空间分辨率  随橧加而增大, 同时保持通道数  不变。上述过程可以描述为

其中是一组经过跨层交互的多尺度特征图,其形状与相应的输入特征图保持一致。

值得注意的是,作者的CFPT消除了复杂特征上采样操作和逐层信息传输机制,这些机制在层间传输过程中容易造成信息丢失,并导致计算负载增加和内存访问延迟。相反,作者通过利用尺度间相互感受野大小的局部分组操作,对多尺度特征图执行一步跨层邻近交互操作,从而促进尺度间的信息混合。这种方法使得每个尺度的特征能够平衡地从其他层获取信息(即使这些层相隔较远),同时促进自我修正,并从局部交互提供的归纳偏置中受益[32]。

跨层通道注意力

假设CCA的输入特征图集合为  。如图5(a)所示, CCA沿着通道维度执行跨层的多尺度邻近交互, 从而为每个通道 Token 提供空间维度的全局上下文信息。为了构建交互式输入, 作者首先在每个尺度上对特征图执行通道重建 (CR), 以确保它们具有相同的空间分辨率, 从而得到  。CR是一个与YOLOv5中的Focus类似的操作符,但不同之处在于它不使用额外的操作进行特征映射。相反, CR将空间维度的特征值堆叠到通道维度, 从而在保持效率的同时实现一致的空间分辨率。上述过程可以描述为

51c视觉~合集13_视觉AI_14

接下来, 作者执行重叠通道式 Patch 划分 (OCP) 以形成通道式标记组, 这可以看作是沿着通道维度在局部区域具有重叠区域的 Patch 嵌入[25], 其中不同尺度的特征图上的 Patch 大小是不同的。具体来说, 根据多尺度特征的形状,  中相邻特征图的通道大小相差一个 4 倍因子(即,  )。为了构建重叠的邻近交互组, 作者引入一个扩展因子  对  执行OCP, 从而得到  。上述过程可以描述为:


以第  层的特征图为例, 在获得  之后, 作者采用跨层一致的多头注意力机制来捕获沿空间维度的全局依赖性, 从而得到交互结果  。


其中  是线性投影矩阵。  分别表示连接的键和值, 其中  代表连接操作。 表示第  个跨层一致相对位置编码(CCPE), 具体细节将在第三节D中介绍。注意, 为了简化, 作者只考虑头数为 1 的情况。在实践中, 作者采用多头机制来捕获每个通道式标记的全局依赖性。

在为每个尺度的特征图获得交互结果  之后, 作者应用反向重叠通道式 Patch 划分 (ROCP) 来恢复 OCP 的影响, 并得到  。作为 OCP 的反向操作, ROCP旨在使用与OCP相同的核大小和步长来恢复原始的空间分辨率。

作者最终使用空间重建 (SR) 来获得与输入X形状相匹配的结果  。

Cross-layer Spatial-wise Attention

同样,将CSA的输入特征图集合表示为 。如图5(b)所示,CSA沿着空间维度跨层执行多尺度邻近交互,为每个空间标记提供沿通道维度的全局上下文信息。

由于输入特征图的通道大小在CBR块后匹配(例如,256),无需使用CR和SR等方法来调整它们的大小, 正如在CCA中所做的那样。因此, 作者可以直接执行重叠空间划分 (OSP) 来形成空间标记组, 这可以看作是在不同尺度特征图上使用不同大小的矩形框进行滑动裁剪。假设OSP的扩展因子为 , 通过上述操作, 作者可以得到  。上述过程可以表示为


然后, 作者在跨层空间标记组内执行局部交互, 并使用跨层一致性多头注意力来捕获沿通道维度的全局依赖性, 从而得到  。对于第  层的特征图, 这个过程可以表示如下:


其中  是线性投影矩阵。和  。 表示第  层的跨层一致性相对位置编码 (CCPE) 。

接下来, 作者使用反向重叠空间划分(ROSP)来反转OSP的效果, 并获得交互结果集  。

Cross-layer Consistent Relative Positional Encoding

由于在交互过程中,它们各自的跨层标记组内的每个标记都保持着特定的位置关系。然而,传统的多头注意力机制统一处理所有交互标记,这对于像目标检测这样对位置敏感的任务来说会导致次优结果。因此,作者引入了跨层一致相对位置编码(CCPE),以增强CFPT在交互过程中的跨层位置感知。

CCPE的主要解决方案是基于对多个尺度上的相互感受野进行对齐, 这由卷积的特性决定。以CSA 为例, 每对空间标记组之间的注意力图集合其中  是头的数量, , 如方程 9 中定义。为了简化, 作者忽略  和 , 并定义  和 , 其中  和  分别表示第  层和第  层空间标记组的高度和宽度。因此, 注意力图集合可以重新表示为  。

CCGE的过程如图6所示。作者定义了一个可学习的码本 , 并通过计算它们的跨层一致相对位置索引, 从码本中获取任意两个标记之间的相对位置信息。为了简化, 考虑来自第  层和第  层的空间标记组的交互,其中  和  分别表示它们各自的绝对坐标矩阵。

51c视觉~合集13_视觉AI_15

51c视觉~合集13_视觉AI_16

为了获得  相对于  的相对位置信息, 作者首先使用它们各自的空间标记组大小来中心化它们的坐标, 以获得  和  。

Complexity Analysis

在本节中,作者将分析典型相关分析(CCA)和通道分割注意力(CSA)的计算复杂性。此外,由于在训练和测试阶段,空间域和通道域的标记组大小保持不变,它们的计算复杂性与输入特征图的空间分辨率成线性关系。

Iii-E1 Cross-layer Channel-wise Attention

考虑一组输入特征图集合, 记作  。此外, 令  表示在CCA中使用的扩展因子。CCA的整体计算复杂度包括线性投影的 , 注意力交互的 , 以及FFNs的  。

Iii-E2 Cross-layer Spatial-wise Attention

假设输入特征图集合为  。此外, 令  表示在CSA中使用的扩展因子。CSA的整体计算复杂度包括线性投影的 , 注意力交互的 , 以及FFNs的  。

IV Experiments

Datasets

作者通过将提出的CFPT应用于两个特别为从小型无人机视角进行小目标检测而设计的挑战性数据集来评估其有效性:VisDrone2019-DET [2] 和 TinyPerson [3]。

Iv-A1 VisDrone2019-DET

这个数据集包含了7,019张由无人机拍摄的图片,其中6,471张用于训练,548张用于验证。数据集涵盖了十个类别:自行车、三轮车、三轮机动车、货车、公交车、卡车、摩托车、行人、人以及汽车。这些图片的分辨率大约为像素。

Iv-A2 TinyPerson

这个数据集由无人机收集,主要用于远距离场景中的小目标检测,因为目标目标的平均长度小于20像素。它包含1,610张图片,其中794张用于训练,816张用于测试。数据集包含72,651个标记实例,分为两组:“海上行人”和“陆地行人”。为了简化,作者将上述两个类别合并为一个类别,命名为“行人”。

Implementation Details

作者使用PyTorch [48] 和MMdetection工具箱 [49] 实现了所提出的CFPT。所有模型都在单块RTX 3090上进行训练和测试,批处理大小为2。在模型训练中,作者使用SGD作为优化器,学习率为0.0025,动量为0.9,权重衰减为0.0001。作者在VisDrone2019-DET数据集上进行了消融研究,并比较了各种最先进的特征金字塔网络性能,输入分辨率为,并采用计划(12个周期)。为了加速模型收敛,作者在训练开始时采用了线性预热策略。为了比较在VisDrone2019-DET数据集上各种最先进检测器的性能,作者训练模型15个周期,以确保按照CEASC [20]的方法完全收敛。

在TinyPerson数据集 [3] 的实验中,为了减少内存使用过度,作者将高分辨率图像划分为大小均匀的块,并设有30%的重叠比例。每个块按比例缩放,以确保最短边长为512像素。为了全面评估模型性能,作者在模型训练中设置了批处理大小为1,并采用计划,同时实施多尺度训练和多尺度测试。

Comparison with Other Feature Pyramid Networks

作者最初在VisDrone2019-DET数据集上,将提出的CFPT与基于RetinaNet [1]的各种最先进特征金字塔网络性能进行了比较。如表格I所示,作者的CFPT在不同的 Backbone 网络上,包括ResNet-18、ResNet-50和ResNet-101,均取得了RetinaNet的最佳结果,同时在性能与计算复杂度之间取得了最优平衡。此外,与专注于航拍图像中小目标检测的SSFPN相比,作者的CFPT在参数更少(分别为-3.8M、-3.5M和-3.5M)和FLOPs更低(分别为-55.5G)的情况下,实现了更好的性能(+0.8 AP、+0.5 AP和+0.4 AP)。这证明了CFPT在航拍图像中小目标检测的应用潜力。

Comparison with State-of-the-Art Methods

为了进一步验证CFPT的有效性,作者将当前最先进检测器中的特征金字塔网络替换为CFPT,并在VisDrone2019-DET和TinyPerson数据集上比较其性能表现。

Iv-D1 VisDrone2019-DET

作者将GFL [43]中的特征金字塔替换为CFPT,并将其性能与各种最先进的检测器进行比较。如表格II所示,应用作者的CFPT分别基于ResNet-18、ResNet-50和ResNet-101,将GFL的性能提升了0.8 AP、0.7 AP和0.7 AP。尽管参数数量略有增加,分别为0.3 M、0.2 M和0.2 M。与CEASC [20]相比,作者的参数仅增加了0.1 M,却实现了显著的性能提升(分别提升了+0.7 AP、+0.6 AP和+0.6 AP),证明了作者CFPT的有效性。

Iv-D2 TinyPerson

在TinyPerson数据集上的比较中,作者采用了文献[3]中定义的评价指标来全面评估模型的性能。作者观察到,GFL [43]在细粒度检测方面表现出色,这体现在其在AP-tiny指标上的优越性能上;而FSAF [41]在粗粒度预测方面更为有效,这从其在AP-tiny和AP-tiny指标上的更好性能可以证明。因此,作者将CFPT分别整合到GFL和FSAF中,以评估其在两种场景下的适应性。如表格III所示,CFPT带来了显著的性能提升,包括为GFL提高了2.4个AP-tiny(44.2 AP-tiny对比41.8 AP-tiny),以及为FSAF提高了2.0个AP-tiny(44.5 AP-tiny对比42.5 AP-tiny),所有性能指标均有增强。因此,有效整合CFPT可显著提升模型对小物体的检测性能,证明了其在航拍图像中小目标检测的有效性。

Ablation Study

在消融研究部分的开头。

V-E1 Order of CCA and CSA

作者研究了CCA(典型相关分析)和CSA(通道空间注意力)应用的顺序对模型性能的影响。具体来说,作者比较了如图7所示的三种解决方案的性能,包括先应用CCA后应用CSA(CCACSA)、先应用CSA后应用CCA(CSACCA)以及同时应用CCA和CSA(CCACSA)。如表4所示,CCACSA的模式取得了最优性能,平均精度(AP)为22.2。作者认为这是因为CCA沿着空间方向提供了全局感受野,使得CSA能够利用全局上下文信息来预测更准确的注意力图,并获取更优的邻近细节信息。然而,由于CSA沿着通道方向具有全局感受野,首先应用它可能会破坏局部性,阻止CCA准确关注空间上的邻近信息。此外,CCACSA将导致CCA和CSA之间没有交互,使得无法利用彼此的信息进行精细的信息聚合。

51c视觉~合集13_视觉AI_17

51c视觉~合集13_视觉AI_18

V-E2 Effectiveness of each proposed component

作者通过逐步将提出的模块整合到 Baseline 模型(即没有FPN的RetinaNet)中来评估每个组件的有效性。如表5所示,将CCA和CSA分别整合到 Baseline 模型中,显著提升了模型性能,分别提高了3.5个AP和3.4个AP。将CCA和CSA综合整合到CAM中,模型实现了3.9个AP的提升(22.0 AP对比18.1 AP)。随后,应用CCPE进一步增强了模型性能,最终AP达到了22.2。值得注意的是,与表1中的大多数特征金字塔网络相比,仅整合CCA或CSA就能实现更优的性能,这突显了它们在航空图像中小目标检测的潜力。

51c视觉~合集13_视觉AI_19

作者还报告了每个组件对模型计算复杂度、参数数量和推理速度的影响,具体见表5。当仅使用单一组件(例如CCA)时,与 Baseline 模型相比,CFPT引入了额外的1.4M参数、7.4G FLOPs和每张图像0.004秒的推理延迟,同时实现了显著性能提升(+3.5 AP)。当使用所有组件时,CFPT引入了额外的2.8M参数、14.8G FLOPs和每张图像0.01秒的推理延迟,同时实现了显著性能提升(+4.1 AP)。因此,CFPT能够在性能与计算复杂度之间实现更好的平衡。

Iv-D3 Number of CAMs

作者评估了CAMs数量对模型性能的影响。如表格VI所示,增加CAMs的数量一致性地提升了模型的性能。当使用三个CAMs时,模型达到了22.5的AP,相较于 Baseline 模型提高了4.4个AP点(22.5 AP对比18.1 AP)。为了更好地平衡计算复杂度与性能,作者在所有其他实验中将CAM的堆叠数量设置为1,尽管更多的CAMs会带来更多的好处。

Iv-D4 Channel Size reduction factor and MLP ratio

作者研究了不同的通道尺寸缩减因子(即特征图通道在注意力交互中的压缩比)和多层感知器(MLP)比例(即在FFN中通道尺寸的扩展比)的影响,旨在找出能够平衡计算复杂度和模型性能的最佳组合。如图9所示,当通道尺寸缩减因子设为4,MLP比例设为2时,模型在计算复杂度和性能之间达到了最佳平衡。因此,在VisDrone2019-DET和TinyPerson数据集上进行的所有实验中,作者均采用这一组合方案。

51c视觉~合集13_视觉AI_20

Qualitative Analysis

作者通过在VisDrone2019-DET和TinyPerson数据集上可视化检测结果来对CFPT进行定性分析,所有可视化的置信度阈值设置为0.3。如图8所示,作者将CFPT应用于GFL,并将其与 Baseline 模型(即GFL)和CEASC在VisDrone2019-DET数据集上进行定性比较。应用CFPT有效降低了模型的漏检率(第一行和第三行)和误检率(第二行),从而提升了整体性能。此外,图8的第三行展示了CFPT在小目标检测方面的有效性。如图10所示,在TinyPerson数据集上的检测结果进一步验证了上述解释,表明CFPT在降低漏检和误检率的同时,有效提升了模型对小目标的检测能力。

51c视觉~合集13_视觉AI_21

51c视觉~合集13_视觉AI_22

V Conclusion

在本论文中,作者引入了CFPT,这是一种新型的无需上采样的特征金字塔网络,

CFPT能够明确更多地关注浅层特征图,并摒弃基于静态核的交互方案,以减轻尺度差异对模型性能的影响,这使得它特别适合于航拍图像中的目标检测。

具体来说,CFPT由两个精心设计的、具有线性计算复杂度的注意力模块组成,分别为CCA和CSA。这两个模块从不同角度捕捉上下文信息,它们的融合为模型提供了对检测小目标至关重要的全局上下文建模能力。

此外,为了在跨层交互中增强位置感知,作者提出了一种新的位置编码方法CCPE。在两个具有挑战性的航拍数据集上的大量实验表明,CFPT在优于现有最先进的特征金字塔网络的同时,也降低了计算成本。

在未来的工作中,作者计划探索可变形的跨层交互解决方案,并研究更有效的实现策略。



#DSCL

本文通过解耦训练目标来解决SCL的这两个问题,将SCL中的原正样本和增强正样本解耦并针对不同目标来优化他们的关系,从而减轻数据集不平衡的影响。北京大学提出解耦对比损失

监督对比损失(SCL)在视觉表征学习中很流行。但在长尾识别场景中,由于每类样本数量不平衡,对两类正样本进行同等对待会导致类内距离的优化出现偏差。此外,SCL忽略了负样本之间的相似关系的语义线索。为了提高长尾识别的性能,论文通过解耦训练目标来解决SCL的这两个问题,将SCL中的原正样本和增强正样本解耦并针对不同目标来优化他们的关系,从而减轻数据集不平衡的影响。论文进一步提出了一种基于块的自蒸馏方法,将知识从头类转移到尾类,以缓解尾类表征不足的问题。该方法挖掘不同实例之间共享的视觉模式,并利用自蒸馏过程来传输此类知识

论文地址: https://arxiv.org/abs/2403.06151

论文代码: https://github.com/SY-Xuan/DSCL

Introduction

在实际应用中,训练样本通常呈现长尾分布,其中少数头类贡献了大部分观察结果,而许多尾类仅与少数样本相关。长尾分布给视觉识别带来了两个挑战:

  • 为平衡数据集设计的损失函数很容易偏向头部类别。
  • 每个尾部类别包含的样本太少,无法表示视觉差异,导致尾部类别的代表性不足。

通过优化类内和类间距离,监督对比损失(SCL)在平衡数据集上取得了很不错的性能。给定锚定图像,SCL将两种正样本聚在一起,即(a)由数据增强生成的锚定图像的不同视图,以及(b)来自同一类的其他图像。这两种类型的正样本监督模型学习不同的表征:(a)来自相同类别的图像强制学习语义线索,而(b)通过外观差异增强的样本主要导致低级外观线索的学习。

51c视觉~合集13_视觉AI_23

如图 1(a)所示,SCL有效地学习了头类的语义特征,例如,学习到的语义“蜜蜂”对于杂乱的背景具有鲁棒性。而如图 1 (b) 所示,SCL学习的尾部类别表征对于形状、纹理和颜色等低级外观线索更具辨别力。

通过对SCL的梯度进行分析后,论文提出了解耦监督对比损失(DSCL)来处理这个问题。具体来说,DSCL解耦了两种正样本,重新制定了类内距离的优化策略,缓解了两种正样本的梯度不平衡。如在图 1(b)所示,DSCL学习到的特征对语义线索具有区分性,并且大大提高了尾部类别的检索性能。

为了进一步缓解长尾分布的挑战,论文提出了基于图像块的自蒸馏(PBSD),利用头类来促进尾类中的表征学习。PBSD采用自蒸馏策略,通过挖掘不同类之间的共享视觉模式并将知识从头类迁移到尾类,更好地优化类间距离。论文引入块特征来表示目标的视觉模式,计算块特征和实例级特征之间的相似度以挖掘共享视觉模式。如果实例与基于块特征共享视觉模式,则它们将具有很高的相似性,然后利用自蒸馏损失来维持样本之间的相似关系,并将知识融入到训练中。

Analysis of SCL

后面的分析有点长,总结起来,论文发现了SCL的三个问题:

  • 过于关注头类的训练。
  • 原样本和增强样本之间的梯度存在差异。
  • 负样本可以更好地处理。

给定训练数据集 . , 其中  表示图像,  是其类标签。假设  表示  中  类的数量, 并且类的索引按数量降序排序, 即如果 $a<b$, 则="" $n^a="" \geq="" n^b$="" 。在长尾识别中,="" 训练数据集是不平衡的,="" 即="" $n^1="" \gg="" n^k$,="" 不平衡比的计算为="" n^k$="" 。<="" p="">

对于图像分类任务, 算法旨在学习特征提取主干  和线性分类器, 先将图像  映射为全局特征图  并使用全局池化来获取  维特征向量, 随后将特征向量分为  维分类分数。通常来说, 测试数据集是平衡的。

特征提取主干一般采用监督对比学习(SCL)来训练。给定锚定图像 , 定义 为用主干和额外投影头  提取的归一化特征, 为正样本  通过数据增强生成的图片的归一化特征。定义  为可通过内存队列获取的一组样本特征,  为从  中抽取的  的正样本特征集。

SCL通过拉近锚定图像与其它正样本来减小类间距离,同时通过将具有不同类别标签的图像推开来扩大类间距离,即

51c视觉~合集13_视觉AI_24

其中  是  的数量。使用  来表示预定义的温度参数, 条件概率  的计算如下:

51c视觉~合集13_视觉AI_25

公式 1 可以表示为分布对齐任务,

51c视觉~合集13_视觉AI_26

其中  是目标分布的概率。对于增强 和原  将它们同等地视为正样本,并将其目标概率设置为  。对于  中其它具有不同类标签的图像,SCL 将它们视为负样本,并将其目标概率设置为 0 。

对于锚定图像  的特征  的梯度为:

51c视觉~合集13_视觉AI_27

其中  是  的负集, 包含从  中提取的特征。

SCL 包含两种类型的正样本 和 , 锚定图像对于两种正样本的梯度计算分别为:

51c视觉~合集13_视觉AI_28

训练开始时,两种正样本的梯度L2范数之比为,

51c视觉~合集13_视觉AI_29

SCL收敛时,的最优条件概率为:

51c视觉~合集13_视觉AI_30

在 SCL 中, 内存队列  是从训练集中均匀采样的, 这导致  。在平衡数据集中, , 不同类别的  数量是平衡的。对于具有不平衡  的长尾数据集, SCL 则会更加关注将头部类的针点  与从  得到的特征拉在一起, 因为梯度由公式 4 中的第三项主导。

51c视觉~合集13_视觉AI_31

另外, SCL 还存在两种正样本的梯度的 L2 范数的比例不平衡的问题, 如图 2 所示。当 SC L 的训练收玫时,  的最优值也受到  的影响, 如公式 7 所示。此外, 如图 1(a) 和 (b) 所示, 跨类别学习到的特征也不一致。

等式 4 还表明,SCL均匀地推开所有负样本,从而扩大了类间距离。这种策略忽略了不同类别之间有价值的相似性线索。为了寻求更好的方法来优化类内和类间距离,论文提出了解耦监督对比损失(DSCL)来解耦两种正样本以防止有偏差的优化,以及基于补丁的自蒸馏(PBSD)来利用类之间的相似性线索。

Decoupled Supervised Contrastive Loss

DSCL的提出是为了确保不同类别的类内距离进行更平衡的优化,将两种正样本解耦并添加不同的权重,使梯度L2范数比和的最优值不受类别的样本数量影响。

DSCL可表示为:

51c视觉~合集13_视觉AI_32

其中  是预定义的超参数。DSCL 是 SCL 在平衡环境和不平衡环境的统一范式。如果数据集是平衡的, 通过设置  可以使得 DSCL 与 SCL 相同。

训练开始时,两种正样本的梯度L2范数比为:

51c视觉~合集13_视觉AI_33

当 DSCL 收玫时, z 的最优条件概率为  。

如公式 10 可以看出,两种正样本的梯度比不受  的影响。DSCL 也保证了  的最优值不受  的影响, 从而缓解了头部类和尾部类之间特征学习不一致的问题。

Patch-based Self Distillation

51c视觉~合集13_视觉AI_34

视觉模式可以在不同的类之间共享,例如视觉模式“车轮”在“卡车”、“汽车”和“公共汽车”共享。因此,尾类中的许多视觉特征也可以从共享这些视觉模式的头类中学习,从而降低了尾类表征学习的难度。SCL将来自不同类的两个实例在特征空间中推开,不管它们是否共享有意义的视觉模式。如图 4 所示,从黄色边界框中提取查询块特征,并从数据集中检索前 3 个相似样本。由w/o PBSD标记的SCL检索结果在语义上与查询块无关,表明SCL在学习和利用图像块级语义线索方面无效。

受细粒度图像识别中基于图像块的方法的启发, 论文引入了基于图像块的特征来编码视觉模式。给定主干提取的图像  的全局特征图 , 首先随机生成块 , 其中  是块的数量。根据这些块的坐标应用 ROI 池化并将池化特征发送到投影头中, 得到归一化的嵌入特征  :

51c视觉~合集13_视觉AI_35

然后,类似于公式 2 利用条件概率计算实例之间的相似关系:

51c视觉~合集13_视觉AI_36

如果  对应的图像与基于块的特征共享视觉模式, 则  和  将具有很高的相似度。因此, 使用公式 12 可对每对实例之间的相似性线索进行编码。

基于上述定义, 将相似性线索作为知识来监督训练过程。为了保持这些知识, 论文还根据  额外从图像中裁剪多个图像块(前面直接从整图的全局特征做 ROI, 这里剪图过网络),并使用主干网络提取其特征嵌入  :

51c视觉~合集13_视觉AI_37

PBSD强制图像块的特征嵌入通过以下损失,产生与基于块的特征相同的相似度分布,

51c视觉~合集13_视觉AI_38

请注意,  与计算图分离以阻止梯度。

物体的局部视觉模式可以由不同类共享, 因此可以使用基于块的特征来表示视觉模式。 是为了挖掘图像之间共享模式的关系而计算的, 通过最小化公式 14 来传递知识给 , 缓解尾类表征性不足的问题。图 4 所示的检索结果表明, PBSD 有效地加强了块级特征和图像块与图像相似性的学习, 使得挖掘不同类别的共享视觉模式成为可能。

Multi-crop技巧通常用于自监督学习中以生成更多锚图像的增强样本,采用低分辨率截图以降低计算复杂性。与Multi-crop策略不同,PBSD的动机是利用头类和尾类之间的共享模式来帮助尾类的学习,通过ROI池化得到基于块的特征来获得共享模式。公式 14 执行自蒸馏以维持共享模式。论文通过用Multi-crop技巧代替PBSD进行了对比实验,ImageNet-LT上的性能从 57.7% 下降到 56.1% ,表明PBSDMulti-crop策略更有效。

Training Pipeline

51c视觉~合集13_视觉AI_39

整体训练逻辑如图 3 所示,为了维护内存队列,使用动量更新模型。训练由两个损失来监督,即解耦监督对比损失和基于块的自蒸馏损失:

51c视觉~合集13_视觉AI_40

论文的方法专注于表征学习,并且可以通过添加对应的损失来在不同的任务中使用。在主干训练之后,丢弃学习的投影头 并使用标准交叉熵损失在预训练的主干之上基于类平衡抽样策略训练线性分类器。

Experiments

51c视觉~合集13_视觉AI_41

51c视觉~合集13_视觉AI_42

51c视觉~合集13_视觉AI_43

51c视觉~合集13_视觉AI_44