PyTorch教程-14.7。单发多框检测

电子说

1.2w人已加入

描述

在第 14.3 节到第 14.6 节中,我们介绍了边界框、锚框、多尺度目标检测和目标检测数据集。现在我们准备使用这些背景知识来设计一个目标检测模型:单次多框检测(SSD)(Liu et al. , 2016)。该模型简单、快速、应用广泛。虽然这只是大量目标检测模型中的一种,但本节中的一些设计原则和实现细节也适用于其他模型。

14.7.1。模型

图 14.7.1提供了单次多框检测设计的概述。该模型主要由一个基础网络和几个多尺度特征图块组成。基础网络用于从输入图像中提取特征,因此可以使用深度 CNN。例如,原始的单次多框检测论文采用在分类层之前截断的VGG网络 (Liu et al. , 2016),而 ResNet 也被普遍使用。通过我们的设计,我们可以让基础网络输出更大的特征图,从而生成更多的锚框来检测更小的物体。随后,每个多尺度特征图块从前一个块减少(例如,减半)特征图的高度和宽度,并使特征图的每个单元增加其在输入图像上的感受野。

回想一下14.5 节中深度神经网络通过图像的分层表示进行多尺度目标检测的设计 。由于靠近图 14.7.1顶部的多尺度特征图较小但具有较大的感受野,因此它们适用于检测较少但较大的对象。

简而言之,通过其基础网络和多个多尺度特征图块,单次多框检测生成不同数量的不同大小的锚框,并通过预测这些锚框的类别和偏移量(因此边界盒);因此,这是一个多尺度目标检测模型。

检测

图 14.7.1作为多尺度目标检测模型,单次多框检测主要由一个基础网络和几个多尺度特征图块组成。

下面,我们将描述图14.7.1中不同块的实现细节。首先,我们讨论如何实现类和边界框预测。

14.7.1.1。类别预测层

让对象类的数量为q. 然后anchor boxes有 q+1类,其中类 0 是背景。在某种程度上,假设特征图的高度和宽度是h和w, 分别。什么时候a以这些特征图的每个空间位置为中心生成anchor boxes,一共 hwaanchor boxes需要分类。由于参数化成本可能很高,这通常会使完全连接层的分类变得不可行。回想一下我们在8.3 节中如何使用卷积层的通道来预测类别。单次多框检测使用相同的技术来降低模型的复杂性。

具体来说,类预测层使用卷积层而不改变特征图的宽度或高度。这样,在特征图的相同空间维度(宽度和高度)下,输出和输入之间可以存在一一对应关系。更具体地说,输出特征映射的通道在任何空间位置(x, y) 表示以 (x,y) 输入特征图。为了产生有效的预测,必须有a(q+1)输出通道,其中对于相同的空间位置,具有索引的输出通道i(q+1)+j 代表类别的预测j (0≤j≤q) 对于锚框i (0≤i

下面我们定义这样一个类预测层,指定a和 q分别通过参数num_anchors和num_classes。该层使用了3×3填充为1的卷积层。该卷积层的输入和输出的宽度和高度保持不变。

 

%matplotlib inline
import torch
import torchvision
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l


def cls_predictor(num_inputs, num_anchors, num_classes):
  return nn.Conv2d(num_inputs, num_anchors * (num_classes + 1),
           kernel_size=3, padding=1)

 

 

%matplotlib inline
from mxnet import autograd, gluon, image, init, np, npx
from mxnet.gluon import nn
from d2l import mxnet as d2l

npx.set_np()

def cls_predictor(num_anchors, num_classes):
  return nn.Conv2D(num_anchors * (num_classes + 1), kernel_size=3,
           padding=1)

 

14.7.1.2。边界框预测层

边界框预测层的设计与类预测层的设计类似。唯一的区别在于每个锚框的输出数量:这里我们需要预测四个偏移量而不是q+1类。

 

def bbox_predictor(num_inputs, num_anchors):
  return nn.Conv2d(num_inputs, num_anchors * 4, kernel_size=3, padding=1)

 

 

def bbox_predictor(num_anchors):
  return nn.Conv2D(num_anchors * 4, kernel_size=3, padding=1)

 

14.7.1.3。连接多个尺度的预测

正如我们提到的,单次多框检测使用多尺度特征图来生成锚框并预测它们的类别和偏移量。在不同的尺度下,特征图的形状或以同一单元为中心的锚框数量可能会有所不同。因此,不同尺度的预测输出的形状可能会有所不同。

在下面的例子中,我们构建了两种不同比例的特征图,Y1并且Y2,对于同一个小批量,其中 的高度和宽度Y2是 的一半Y1。让我们以类别预测为例。Y1假设分别为和中的每个单元生成 5 个和 3 个锚框Y2。进一步假设对象类的数量为 10。对于特征图Y1和Y2类预测输出中的通道数为5×(10+1)=55 和3×(10+1)=33,其中任一输出形状为(批量大小、通道数、高度、宽度)。

 

def forward(x, block):
  return block(x)

Y1 = forward(torch.zeros((2, 8, 20, 20)), cls_predictor(8, 5, 10))
Y2 = forward(torch.zeros((2, 16, 10, 10)), cls_predictor(16, 3, 10))
Y1.shape, Y2.shape

 

 

(torch.Size([2, 55, 20, 20]), torch.Size([2, 33, 10, 10]))

 

 

def forward(x, block):
  block.initialize()
  return block(x)

Y1 = forward(np.zeros((2, 8, 20, 20)), cls_predictor(5, 10))
Y2 = forward(np.zeros((2, 16, 10, 10)), cls_predictor(3, 10))
Y1.shape, Y2.shape

 

 

((2, 55, 20, 20), (2, 33, 10, 10))

 

我们可以看到,除了 batch size 维度,其他三个维度都有不同的大小。为了连接这两个预测输出以实现更高效的计算,我们将把这些张量转换为更一致的格式。

请注意,通道维度包含具有相同中心的锚框的预测。我们先把这个维度移到最里面。由于批量大小对于不同的尺度保持不变,我们可以将预测输出转换为具有形状的二维张量(批量大小,高度×宽度×通道数)。然后我们可以沿着维度 1 以不同的比例连接这些输出。

 

def flatten_pred(pred):
  return torch.flatten(pred.permute(0, 2, 3, 1), start_dim=1)

def concat_preds(preds):
  return torch.cat([flatten_pred(p) for p in preds], dim=1)

 

 

def flatten_pred(pred):
  return npx.batch_flatten(pred.transpose(0, 2, 3, 1))

def concat_preds(preds):
  return np.concatenate([flatten_pred(p) for p in preds], axis=1)

 

通过这种方式,即使 和Y1在Y2通道、高度和宽度方面具有不同的大小,我们仍然可以将这两个预测输出以两个不同的尺度连接起来,用于同一个小批量。

 

concat_preds([Y1, Y2]).shape

 

 

torch.Size([2, 25300])

 

 

concat_preds([Y1, Y2]).shape

 

 

(2, 25300)

 

14.7.1.4。下采样块

为了检测多个尺度的对象,我们定义了以下下采样块down_sample_blk,将输入特征图的高度和宽度减半。实际上,该块应用了8.2.1 节中 VGG 块的设计。更具体地说,每个下采样块由两个3×3填充为 1 的卷积层后跟一个2×2步幅为 2 的最大池化层。我们知道,3×3填充为 1 的卷积层不会改变特征图的形状。然而,随后的2×2max-pooling 将输入特征图的高度和宽度减半。对于这个下采样块的输入和输出特征图,因为1×2+(3−1)+(3−1)=6,输出中的每个单元都有一个6×6输入的感受野。因此,下采样块在其输出特征图中扩大了每个单元的感受野。

 

def down_sample_blk(in_channels, out_channels):
  blk = []
  for _ in range(2):
    blk.append(nn.Conv2d(in_channels, out_channels,
               kernel_size=3, padding=1))
    blk.append(nn.BatchNorm2d(out_channels))
    blk.append(nn.ReLU())
    in_channels = out_channels
  blk.append(nn.MaxPool2d(2))
  return nn.Sequential(*blk)

 

 

def down_sample_blk(num_channels):
  blk = nn.Sequential()
  for _ in range(2):
    blk.add(nn.Conv2D(num_channels, kernel_size=3, padding=1),
        nn.BatchNorm(in_channels=num_channels),
        nn.Activation('relu'))
  blk.add(nn.MaxPool2D(2))
  return blk

 

在下面的例子中,我们构建的下采样块改变了输入通道的数量,并将输入特征图的高度和宽度减半。

 

forward(torch.zeros((2, 3, 20, 20)), down_sample_blk(3, 10)).shape

 

 

torch.Size([2, 10, 10, 10])

 

 

forward(np.zeros((2, 3, 20, 20)), down_sample_blk(10)).shape

 

 

(2, 10, 10, 10)

 

14.7.1.5。基础网络块

基础网络块用于从输入图像中提取特征。为简单起见,我们构建了一个由三个下采样块组成的小型基础网络,每个块的通道数加倍。给定一个256×256输入图像,这个基础网络块输出32×32特征图(256/23=32).

 

def base_net():
  blk = []
  num_filters = [3, 16, 32, 64]
  for i in range(len(num_filters) - 1):
    blk.append(down_sample_blk(num_filters[i], num_filters[i+1]))
  return nn.Sequential(*blk)

forward(torch.zeros((2, 3, 256, 256)), base_net()).shape

 

 

torch.Size([2, 64, 32, 32])

 

 

def base_net():
  blk = nn.Sequential()
  for num_filters in [16, 32, 64]:
    blk.add(down_sample_blk(num_filters))
  return blk

forward(np.zeros((2, 3, 256, 256)), base_net()).shape

 

 

(2, 64, 32, 32)

 

14.7.1.6。完整模型

完整的单次多框检测模型由五个块组成。每个块生成的特征图用于 (i) 生成锚框和 (ii) 预测这些锚框的类别和偏移量。在这五个块中,第一个是基础网络块,第二到第四个是下采样块,最后一个块使用全局最大池化将高度和宽度都降低到1。从技术上讲,第二到第五个块是图 14.7.1中的所有那些多尺度特征图块。

 

def get_blk(i):
  if i == 0:
    blk = base_net()
  elif i == 1:
    blk = down_sample_blk(64, 128)
  elif i == 4:
    blk = nn.AdaptiveMaxPool2d((1,1))
  else:
    blk = down_sample_blk(128, 128)
  return blk

 

 

def get_blk(i):
  if i == 0:
    blk = base_net()
  elif i == 4:
    blk = nn.GlobalMaxPool2D()
  else:
    blk = down_sample_blk(128)
  return blk

 

现在我们为每个块定义前向传播。与图像分类任务不同,此处的输出包括 (i) CNN 特征图 Y,(ii) 在当前尺度下使用生成的锚框,以及 (iii)为这些锚框Y预测的类别和偏移量(基于)。Y

 

def blk_forward(X, blk, size, ratio, cls_predictor, bbox_predictor):
  Y = blk(X)
  anchors = d2l.multibox_prior(Y, sizes=size, ratios=ratio)
  cls_preds = cls_predictor(Y)
  bbox_preds = bbox_predictor(Y)
  return (Y, anchors, cls_preds, bbox_preds)

 

 

def blk_forward(X, blk, size, ratio, cls_predictor, bbox_predictor):
  Y = blk(X)
  anchors = d2l.multibox_prior(Y, sizes=size, ratios=ratio)
  cls_preds = cls_predictor(Y)
  bbox_preds = bbox_predictor(Y)
  return (Y, anchors, cls_preds, bbox_preds)

 

回想一下,在图 14.7.1中,靠近顶部的多尺度特征图块用于检测较大的对象;因此,它需要生成更大的锚框。sizes在上面的前向传播中,在每个多尺度特征图块中,我们通过调用函数的参数(在第 14.4 节multibox_prior中描述)传入一个包含两个尺度值的列表。下面将0.2和1.05之间的区间平均分为五个部分,以确定五个块处较小的刻度值:0.2、0.37、0.54、0.71和0.88。然后它们的较大比例值由下式给出 0.2×0.37=0.272, 0.37×0.54=0.447, 等等。

 

sizes = [[0.2, 0.272], [0.37, 0.447], [0.54, 0.619], [0.71, 0.79],
     [0.88, 0.961]]
ratios = [[1, 2, 0.5]] * 5
num_anchors = len(sizes[0]) + len(ratios[0]) - 1

 

 

sizes = [[0.2, 0.272], [0.37, 0.447], [0.54, 0.619], [0.71, 0.79],
     [0.88, 0.961]]
ratios = [[1, 2, 0.5]] * 5
num_anchors = len(sizes[0]) + len(ratios[0]) - 1

 

现在我们可以定义完整的模型TinySSD如下。

 

class TinySSD(nn.Module):
  def __init__(self, num_classes, **kwargs):
    super(TinySSD, self).__init__(**kwargs)
    self.num_classes = num_classes
    idx_to_in_channels = [64, 128, 128, 128, 128]
    for i in range(5):
      # Equivalent to the assignment statement `self.blk_i = get_blk(i)`
      setattr(self, f'blk_{i}', get_blk(i))
      setattr(self, f'cls_{i}', cls_predictor(idx_to_in_channels[i],
                          num_anchors, num_classes))
      setattr(self, f'bbox_{i}', bbox_predictor(idx_to_in_channels[i],
                           num_anchors))

  def forward(self, X):
    anchors, cls_preds, bbox_preds = [None] * 5, [None] * 5, [None] * 5
    for i in range(5):
      # Here `getattr(self, 'blk_%d' % i)` accesses `self.blk_i`
      X, anchors[i], cls_preds[i], bbox_preds[i] = blk_forward(
        X, getattr(self, f'blk_{i}'), sizes[i], ratios[i],
        getattr(self, f'cls_{i}'), getattr(self, f'bbox_{i}'))
    anchors = torch.cat(anchors, dim=1)
    cls_preds = concat_preds(cls_preds)
    cls_preds = cls_preds.reshape(
      cls_preds.shape[0], -1, self.num_classes + 1)
    bbox_preds = concat_preds(bbox_preds)
    return anchors, cls_preds, bbox_preds

 

 

class TinySSD(nn.Block):
  def __init__(self, num_classes, **kwargs):
    super(TinySSD, self).__init__(**kwargs)
    self.num_classes = num_classes
    for i in range(5):
      # Equivalent to the assignment statement `self.blk_i = get_blk(i)`
      setattr(self, f'blk_{i}', get_blk(i))
      setattr(self, f'cls_{i}', cls_predictor(num_anchors, num_classes))
      setattr(self, f'bbox_{i}', bbox_predictor(num_anchors))

  def forward(self, X):
    anchors, cls_preds, bbox_preds = [None] * 5, [None] * 5, [None] * 5
    for i in range(5):
      # Here `getattr(self, 'blk_%d' % i)` accesses `self.blk_i`
      X, anchors[i], cls_preds[i], bbox_preds[i] = blk_forward(
        X, getattr(self, f'blk_{i}'), sizes[i], ratios[i],
        getattr(self, f'cls_{i}'), getattr(self, f'bbox_{i}'))
    anchors = np.concatenate(anchors, axis=1)
    cls_preds = concat_preds(cls_preds)
    cls_preds = cls_preds.reshape(
      cls_preds.shape[0], -1, self.num_classes + 1)
    bbox_preds = concat_preds(bbox_preds)
    return anchors, cls_preds, bbox_preds

 

我们创建一个模型实例并使用它对小批量数据执行前向传播256×256图片X。

如本节前面所示,第一个块输出 32×32特征图。回想一下,第二到第四个下采样块将高度和宽度减半,第五个块使用全局池化。由于沿特征图的空间维度为每个单元生成 4 个锚框,因此在所有五个尺度上总共 (322+162+82+42+1)×4=5444为每个图像生成锚框。

 

net = TinySSD(num_classes=1)
X = torch.zeros((32, 3, 256, 256))
anchors, cls_preds, bbox_preds = net(X)

print('output anchors:', anchors.shape)
print('output class preds:', cls_preds.shape)
print('output bbox preds:', bbox_preds.shape)

 

 

output anchors: torch.Size([1, 5444, 4])
output class preds: torch.Size([32, 5444, 2])
output bbox preds: torch.Size([32, 21776])

 

 

net = TinySSD(num_classes=1)
net.initialize()
X = np.zeros((32, 3, 256, 256))
anchors, cls_preds, bbox_preds = net(X)

print('output anchors:', anchors.shape)
print('output class preds:', cls_preds.shape)
print('output bbox preds:', bbox_preds.shape)

 

 

output anchors: (1, 5444, 4)
output class preds: (32, 5444, 2)
output bbox preds: (32, 21776)

 

14.7.2。训练

现在我们将解释如何训练用于目标检测的单发多框检测模型。

14.7.2.1。读取数据集并初始化模型

首先,让我们阅读 第 14.6 节中描述的香蕉检测数据集。

 

batch_size = 32
train_iter, _ = d2l.load_data_bananas(batch_size)

 

 

Downloading ../data/banana-detection.zip from http://d2l-data.s3-accelerate.amazonaws.com/banana-detection.zip...
read 1000 training examples
read 100 validation examples

 

 

batch_size = 32
train_iter, _ = d2l.load_data_bananas(batch_size)

 

 

Downloading ../data/banana-detection.zip from http://d2l-data.s3-accelerate.amazonaws.com/banana-detection.zip...
read 1000 training examples
read 100 validation examples

 

香蕉检测数据集中只有一类。定义模型后,我们需要初始化其参数并定义优化算法。

 

device, net = d2l.try_gpu(), TinySSD(num_classes=1)
trainer = torch.optim.SGD(net.parameters(), lr=0.2, weight_decay=5e-4)

 

 

device, net = d2l.try_gpu(), TinySSD(num_classes=1)
net.initialize(init=init.Xavier(), ctx=device)
trainer = gluon.Trainer(net.collect_params(), 'sgd',
            {'learning_rate': 0.2, 'wd': 5e-4})

 

14.7.2.2。定义损失函数和评估函数

物体检测有两种类型的损失。第一个损失涉及锚框的类别:它的计算可以简单地重用我们用于图像分类的交叉熵损失函数。第二个损失涉及正(非背景)锚框的偏移:这是一个回归问题。然而,对于这个回归问题,这里我们不使用 第 3.1.3 节中描述的平方损失。相反,我们使用ℓ1范数损失,预测值与真实值之间差异的绝对值。掩码变量 bbox_masks在损失计算中过滤掉负锚框和非法(填充)锚框。最后,我们将anchor box class loss和anchor box offset loss相加得到模型的损失函数。

 

cls_loss = nn.CrossEntropyLoss(reduction='none')
bbox_loss = nn.L1Loss(reduction='none')

def calc_loss(cls_preds, cls_labels, bbox_preds, bbox_labels, bbox_masks):
  batch_size, num_classes = cls_preds.shape[0], cls_preds.shape[2]
  cls = cls_loss(cls_preds.reshape(-1, num_classes),
          cls_labels.reshape(-1)).reshape(batch_size, -1).mean(dim=1)
  bbox = bbox_loss(bbox_preds * bbox_masks,
           bbox_labels * bbox_masks).mean(dim=1)
  return cls + bbox

 

 

cls_loss = gluon.loss.SoftmaxCrossEntropyLoss()
bbox_loss = gluon.loss.L1Loss()

def calc_loss(cls_preds, cls_labels, bbox_preds, bbox_labels, bbox_masks):
  cls = cls_loss(cls_preds, cls_labels)
  bbox = bbox_loss(bbox_preds * bbox_masks, bbox_labels * bbox_masks)
  return cls + bbox

 

我们可以使用准确性来评估分类结果。由于使用ℓ1对于偏移量的范数损失,我们使用平均绝对误差来评估预测的边界框。这些预测结果是从生成的锚框和它们的预测偏移量中获得的。

 

def cls_eval(cls_preds, cls_labels):
  # Because the class prediction results are on the final dimension,
  # `argmax` needs to specify this dimension
  return float((cls_preds.argmax(dim=-1).type(
    cls_labels.dtype) == cls_labels).sum())

def bbox_eval(bbox_preds, bbox_labels, bbox_masks):
  return float((torch.abs((bbox_labels - bbox_preds) * bbox_masks)).sum())

 

 

def cls_eval(cls_preds, cls_labels):
  # Because the class prediction results are on the final dimension,
  # `argmax` needs to specify this dimension
  return float((cls_preds.argmax(axis=-1).astype(
    cls_labels.dtype) == cls_labels).sum())

def bbox_eval(bbox_preds, bbox_labels, bbox_masks):
  return float((np.abs((bbox_labels - bbox_preds) * bbox_masks)).sum())

 

14.7.2.3。训练模型

在训练模型时,我们需要生成多尺度锚框 ( anchors) 并在前向传播中预测它们的类别 ( cls_preds) 和偏移量 ( )。然后我们根据标签信息对生成的anchor boxes的bbox_predsclasses( cls_labels)和offsets( )进行标注。最后,我们使用类别和偏移量的预测值和标记值来计算损失函数。为了简洁的实现,这里省略了测试数据集的评估。bbox_labelsY

 

num_epochs, timer = 20, d2l.Timer()
animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs],
            legend=['class error', 'bbox mae'])
net = net.to(device)
for epoch in range(num_epochs):
  # Sum of training accuracy, no. of examples in sum of training accuracy,
  # Sum of absolute error, no. of examples in sum of absolute error
  metric = d2l.Accumulator(4)
  net.train()
  for features, target in train_iter:
    timer.start()
    trainer.zero_grad()
    X, Y = features.to(device), target.to(device)
    # Generate multiscale anchor boxes and predict their classes and
    # offsets
    anchors, cls_preds, bbox_preds = net(X)
    # Label the classes and offsets of these anchor boxes
    bbox_labels, bbox_masks, cls_labels = d2l.multibox_target(anchors, Y)
    # Calculate the loss function using the predicted and labeled values
    # of the classes and offsets
    l = calc_loss(cls_preds, cls_labels, bbox_preds, bbox_labels,
           bbox_masks)
    l.mean().backward()
    trainer.step()
    metric.add(cls_eval(cls_preds, cls_labels), cls_labels.numel(),
          bbox_eval(bbox_preds, bbox_labels, bbox_masks),
          bbox_labels.numel())
  cls_err, bbox_mae = 1 - metric[0] / metric[1], metric[2] / metric[3]
  animator.add(epoch + 1, (cls_err, bbox_mae))
print(f'class err {cls_err:.2e}, bbox mae {bbox_mae:.2e}')
print(f'{len(train_iter.dataset) / timer.stop():.1f} examples/sec on '
   f'{str(device)}')

 

 

class err 3.29e-03, bbox mae 3.08e-03
4339.3 examples/sec on cuda:0

 

检测

 

num_epochs, timer = 20, d2l.Timer()
animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs],
            legend=['class error', 'bbox mae'])
for epoch in range(num_epochs):
  # Sum of training accuracy, no. of examples in sum of training accuracy,
  # Sum of absolute error, no. of examples in sum of absolute error
  metric = d2l.Accumulator(4)
  for features, target in train_iter:
    timer.start()
    X = features.as_in_ctx(device)
    Y = target.as_in_ctx(device)
    with autograd.record():
      # Generate multiscale anchor boxes and predict their classes and
      # offsets
      anchors, cls_preds, bbox_preds = net(X)
      # Label the classes and offsets of these anchor boxes
      bbox_labels, bbox_masks, cls_labels = d2l.multibox_target(anchors,
                                   Y)
      # Calculate the loss function using the predicted and labeled
      # values of the classes and offsets
      l = calc_loss(cls_preds, cls_labels, bbox_preds, bbox_labels,
             bbox_masks)
    l.backward()
    trainer.step(batch_size)
    metric.add(cls_eval(cls_preds, cls_labels), cls_labels.size,
          bbox_eval(bbox_preds, bbox_labels, bbox_masks),
          bbox_labels.size)
  cls_err, bbox_mae = 1 - metric[0] / metric[1], metric[2] / metric[3]
  animator.add(epoch + 1, (cls_err, bbox_mae))
print(f'class err {cls_err:.2e}, bbox mae {bbox_mae:.2e}')
print(f'{len(train_iter._dataset) / timer.stop():.1f} examples/sec on '
   f'{str(device)}')

 

 

class err 3.56e-03, bbox mae 3.78e-03
966.8 examples/sec on gpu(0)

 

检测

14.7.3。预言

在预测期间,目标是检测图像上所有感兴趣的对象。下面我们读取并调整测试图像的大小,将其转换为卷积层所需的四维张量。

 

X = torchvision.io.read_image('../img/banana.jpg').unsqueeze(0).float()
img = X.squeeze(0).permute(1, 2, 0).long()

 

 

img = image.imread('../img/banana.jpg')
feature = image.imresize(img, 256, 256).astype('float32')
X = np.expand_dims(feature.transpose(2, 0, 1), axis=0)

 

使用multibox_detection下面的函数,预测的边界框是从锚框及其预测的偏移量中获得的。然后使用非最大抑制来去除相似的预测边界框。

 

def predict(X):
  net.eval()
  anchors, cls_preds, bbox_preds = net(X.to(device))
  cls_probs = F.softmax(cls_preds, dim=2).permute(0, 2, 1)
  output = d2l.multibox_detection(cls_probs, bbox_preds, anchors)
  idx = [i for i, row in enumerate(output[0]) if row[0] != -1]
  return output[0, idx]

output = predict(X)

 

 

def predict(X):
  anchors, cls_preds, bbox_preds = net(X.as_in_ctx(device))
  cls_probs = npx.softmax(cls_preds).transpose(0, 2, 1)
  output = d2l.multibox_detection(cls_probs, bbox_preds, anchors)
  idx = [i for i, row in enumerate(output[0]) if row[0] != -1]
  return output[0, idx]

output = predict(X)

 

 

[09:37:33] src/operator/nn/./cudnn/./cudnn_algoreg-inl.h:97: Running performance tests to find the best convolution algorithm, this can take a while... (set the environment variable MXNET_CUDNN_AUTOTUNE_DEFAULT to 0 to disable)

 

最后,我们将所有置信度为 0.9 或更高的预测边界框显示为输出。

 

def display(img, output, threshold):
  d2l.set_figsize((5, 5))
  fig = d2l.plt.imshow(img)
  for row in output:
    score = float(row[1])
    if score < threshold:
      continue
    h, w = img.shape[:2]
    bbox = [row[2:6] * torch.tensor((w, h, w, h), device=row.device)]
    d2l.show_bboxes(fig.axes, bbox, '%.2f' % score, 'w')

display(img, output.cpu(), threshold=0.9)

 

检测

 

def display(img, output, threshold):
  d2l.set_figsize((5, 5))
  fig = d2l.plt.imshow(img.asnumpy())
  for row in output:
    score = float(row[1])
    if score < threshold:
      continue
    h, w = img.shape[:2]
    bbox = [row[2:6] * np.array((w, h, w, h), ctx=row.ctx)]
    d2l.show_bboxes(fig.axes, bbox, '%.2f' % score, 'w')

display(img, output, threshold=0.9)

 

检测

14.7.4。概括

Single shot multibox detection是一种多尺度目标检测模型。通过其基础网络和多个多尺度特征图块,单次多框检测生成不同数量的不同大小的锚框,并通过预测这些锚框(即边界框)的类别和偏移量来检测不同大小的对象。

在训练单次多框检测模型时,损失函数是根据锚框类别和偏移量的预测值和标记值计算的。

14.7.5。练习

你能通过改进损失函数来改进单次多框检测吗?例如,替换ℓ1平滑的范数损失ℓ1预测偏移量的标准损失。此损失函数使用围绕零的平方函数来实现平滑度,它由超参数控制σ:

什么时候σ非常大,这个损失类似于 ℓ1规范损失。当它的值越小,损失函数越平滑。

此外,在实验中我们使用交叉熵损失进行类别预测:表示为pj真实类别的预测概率j,交叉熵损失是 −log⁡pj. 我们还可以使用焦点损失 (Lin等人,2017 年):给定超参数 γ>0和α>0,此损失定义为:

正如我们所见,增加γ可以有效地减少分类良好的例子的相对损失(例如,pj>0.5) 因此训练可以更多地关注那些被错误分类的困难示例。

由于篇幅限制,我们在本节中省略了单次多框检测模型的一些实现细节。能否在以下几个方面进一步改进模型:

当一个对象与图像相比小得多时,模型可以将输入图像调整得更大。

通常有大量的负锚框。为了使类别分布更加平衡,我们可以对负锚框进行下采样。

在损失函数中,为类损失和偏移损失分配不同的权重超参数。

使用其他方法来评估对象检测模型,例如单发多框检测论文 (Liu et al. , 2016)中的方法。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分