SSD实现

[复制链接]
发表于 2026-2-22 05:19:22 | 显示全部楼层 |阅读模式
一、模子

        此模子紧张由底子网络构成,厥后是几个多尺度特性块。根本网络用于从输入图像中提取特性,因此它可以使用深度卷积神经网络。
        单发多框检测选用了在分类层之前截断的VGG,如今也常用ResNet更换;可以筹划底子网络,使它输出的高和宽较大,使基于该特性图天生的锚框数目较多,可以用来检测尺寸较小的目的。 接下来的每个多尺度特性块将上一层提供的特性图的高和宽缩小(如减半),并使特性图中每个单位在输入图像上的感受野变得更广阔。
1、种别推测层
  1. %matplotlib inline
  2. import torch
  3. import torchvision
  4. from torch import nn
  5. from torch.nn import functional as F
  6. from d2l import torch as d2l
  7. def cls_predictor(num_inputs, num_anchors, num_classes):
  8.     #类别+1是因为加上了背景类别
  9.     return nn.Conv2d(num_inputs, num_anchors * (num_classes + 1),
  10.                      kernel_size=3, padding=1)
复制代码
2、界限框推测层
  1. def bbox_predictor(num_inputs, num_anchors):
  2.     #边界框预测四个偏移,四条边
  3.     return nn.Conv2d(num_inputs, num_anchors * 4, kernel_size=3, padding=1)
复制代码
3、连结多尺度的推测
        单发多框检测使用多尺度特性图来天生锚框并推测其种别和偏移量。 在差别的尺度下,特性图的形状或以同一单位为中心的锚框的数目大概会有所差别。 因此,差别尺度下推测输出的形状大概会有所差别。
  1. def forward(x, block):
  2.     return block(x)
复制代码
        为了将这两个推测输出链接起来以进步盘算服从,我们将把这些张量转换为更同等的格式。
  1. def flatten_pred(pred):
  2.     #将通道维移到最后一维,是为了做展平时候同一个像素的框所判断的类是集中在一起的
  3.     return torch.flatten(pred.permute(0, 2, 3, 1), start_dim=1)
  4. def concat_preds(preds):
  5.     return torch.cat([flatten_pred(p) for p in preds], dim=1)
复制代码
4、高和宽减半块
  1. def down_sample_blk(in_channels, out_channels):
  2.     blk = []
  3.     for _ in range(2):
  4.         blk.append(nn.Conv2d(in_channels, out_channels,
  5.                              kernel_size=3, padding=1))
  6.         blk.append(nn.BatchNorm2d(out_channels))
  7.         blk.append(nn.ReLU())
  8.         in_channels = out_channels
  9.     blk.append(nn.MaxPool2d(2))
  10.     return nn.Sequential(*blk)
复制代码
5、根本网络块
  1. def base_net():
  2.     blk = []
  3.     num_filters = [3, 16, 32, 64]
  4.     for i in range(len(num_filters) - 1):
  5.         blk.append(down_sample_blk(num_filters[i], num_filters[i+1]))
  6.     return nn.Sequential(*blk)
  7. forward(torch.zeros((2, 3, 256, 256)), base_net()).shape
复制代码
6、完备的模子
  1. def get_blk(i):
  2.     if i == 0:
  3.         blk = base_net()
  4.     elif i == 1:
  5.         blk = down_sample_blk(64, 128)
  6.     elif i == 4:
  7.         blk = nn.AdaptiveMaxPool2d((1,1))
  8.     else:
  9.         blk = down_sample_blk(128, 128)
  10.     return blk
复制代码
  1. def blk_forward(X, blk, size, ratio, cls_predictor, bbox_predictor):
  2.     Y = blk(X)
  3.     anchors = d2l.multibox_prior(Y, sizes=size, ratios=ratio)
  4.     cls_preds = cls_predictor(Y)
  5.     bbox_preds = bbox_predictor(Y)
  6.     return (Y, anchors, cls_preds, bbox_preds)
复制代码
  1. sizes = [[0.2, 0.272], [0.37, 0.447], [0.54, 0.619], [0.71, 0.79],
  2.          [0.88, 0.961]]
  3. ratios = [[1, 2, 0.5]] * 5
  4. num_anchors = len(sizes[0]) + len(ratios[0]) - 1
复制代码
  1. class TinySSD(nn.Module):
  2.     def __init__(self, num_classes, **kwargs):
  3.         super(TinySSD, self).__init__(**kwargs)
  4.         self.num_classes = num_classes
  5.         idx_to_in_channels = [64, 128, 128, 128, 128]
  6.         for i in range(5):
  7.             # 即赋值语句self.blk_i=get_blk(i)
  8.             setattr(self, f'blk_{i}', get_blk(i))
  9.             setattr(self, f'cls_{i}', cls_predictor(idx_to_in_channels[i],
  10.                                                     num_anchors, num_classes))
  11.             setattr(self, f'bbox_{i}', bbox_predictor(idx_to_in_channels[i],
  12.                                                       num_anchors))
  13.     def forward(self, X):
  14.         anchors, cls_preds, bbox_preds = [None] * 5, [None] * 5, [None] * 5
  15.         for i in range(5):
  16.             # getattr(self,'blk_%d'%i)即访问self.blk_i
  17.             X, anchors[i], cls_preds[i], bbox_preds[i] = blk_forward(
  18.                 X, getattr(self, f'blk_{i}'), sizes[i], ratios[i],
  19.                 getattr(self, f'cls_{i}'), getattr(self, f'bbox_{i}'))
  20.         anchors = torch.cat(anchors, dim=1)
  21.         cls_preds = concat_preds(cls_preds)
  22.         cls_preds = cls_preds.reshape(
  23.             cls_preds.shape[0], -1, self.num_classes + 1)
  24.         bbox_preds = concat_preds(bbox_preds)
  25.         return anchors, cls_preds, bbox_preds
复制代码
  1. net = TinySSD(num_classes=1)
  2. X = torch.zeros((32, 3, 256, 256))
  3. anchors, cls_preds, bbox_preds = net(X)
  4. print('output anchors:', anchors.shape)
  5. print('output class preds:', cls_preds.shape)
  6. print('output bbox preds:', bbox_preds.shape)
复制代码
二、练习模子
1、界说丧失函数和评价函数
         第一种有关锚框种别的丧失,可以简单地复用之前图像分类题目里不绝使用的交织熵丧失函数来盘算; 第二种有关正类锚框偏移量的丧失:推测偏移量是一个回归题目
  1. cls_loss = nn.CrossEntropyLoss(reduction='none')
  2. #因为L2是平方,可能会特别大,我们就用绝对值的L1
  3. bbox_loss = nn.L1Loss(reduction='none')
  4. def calc_loss(cls_preds, cls_labels, bbox_preds, bbox_labels, bbox_masks):
  5.     batch_size, num_classes = cls_preds.shape[0], cls_preds.shape[2]
  6.     cls = cls_loss(cls_preds.reshape(-1, num_classes),
  7.                    cls_labels.reshape(-1)).reshape(batch_size, -1).mean(dim=1)
  8.     bbox = bbox_loss(bbox_preds * bbox_masks,
  9.                      bbox_labels * bbox_masks).mean(dim=1)
  10.     return cls + bbox
复制代码
  1. def cls_eval(cls_preds, cls_labels):
  2.     # 由于类别预测结果放在最后一维,argmax需要指定最后一维。
  3.     return float((cls_preds.argmax(dim=-1).type(
  4.         cls_labels.dtype) == cls_labels).sum())
  5. def bbox_eval(bbox_preds, bbox_labels, bbox_masks):
  6.     return float((torch.abs((bbox_labels - bbox_preds) * bbox_masks)).sum())
复制代码
2、练习模子
  1. num_epochs, timer = 20, d2l.Timer()
  2. animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs],
  3.                         legend=['class error', 'bbox mae'])
  4. net = net.to(device)
  5. for epoch in range(num_epochs):
  6.     # 训练精确度的和,训练精确度的和中的示例数
  7.     # 绝对误差的和,绝对误差的和中的示例数
  8.     metric = d2l.Accumulator(4)
  9.     net.train()
  10.     for features, target in train_iter:
  11.         timer.start()
  12.         trainer.zero_grad()
  13.         X, Y = features.to(device), target.to(device)
  14.         # 生成多尺度的锚框,为每个锚框预测类别和偏移量
  15.         anchors, cls_preds, bbox_preds = net(X)
  16.         # 为每个锚框标注类别和偏移量
  17.         bbox_labels, bbox_masks, cls_labels = d2l.multibox_target(anchors, Y)
  18.         # 根据类别和偏移量的预测和标注值计算损失函数
  19.         l = calc_loss(cls_preds, cls_labels, bbox_preds, bbox_labels,
  20.                       bbox_masks)
  21.         l.mean().backward()
  22.         trainer.step()
  23.         metric.add(cls_eval(cls_preds, cls_labels), cls_labels.numel(),
  24.                    bbox_eval(bbox_preds, bbox_labels, bbox_masks),
  25.                    bbox_labels.numel())
  26.     cls_err, bbox_mae = 1 - metric[0] / metric[1], metric[2] / metric[3]
  27.     animator.add(epoch + 1, (cls_err, bbox_mae))
  28. print(f'class err {cls_err:.2e}, bbox mae {bbox_mae:.2e}')
  29. print(f'{len(train_iter.dataset) / timer.stop():.1f} examples/sec on '
  30.       f'{str(device)}')
复制代码
三、推测目的
  1. #根据锚框及其预测偏移量得到预测边界框,通过非极大值抑制来移除相似的预测边界框
  2. def predict(X):
  3.     net.eval()
  4.     anchors, cls_preds, bbox_preds = net(X.to(device))
  5.     cls_probs = F.softmax(cls_preds, dim=2).permute(0, 2, 1)
  6.     output = d2l.multibox_detection(cls_probs, bbox_preds, anchors)
  7.     #筛选有效结果
  8.     idx = [i for i, row in enumerate(output[0]) if row[0] != -1]
  9.     return output[0, idx]
  10. output = predict(X)
复制代码
  1. #筛选所有置信度不低于0.9的边界框
  2. def display(img, output, threshold):
  3.     d2l.set_figsize((5, 5))
  4.     fig = d2l.plt.imshow(img)
  5.     for row in output:
  6.         score = float(row[1])
  7.         if score < threshold:
  8.             continue
  9.         h, w = img.shape[0:2]
  10.         bbox = [row[2:6] * torch.tensor((w, h, w, h), device=row.device)]
  11.         d2l.show_bboxes(fig.axes, bbox, '%.2f' % score, 'w')
  12. display(img, output.cpu(), threshold=0.9)
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!qidao123.com:ToB企服之家,中国第一个企服评测及软件市场,开放入驻,技术点评得现金

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表