从零开始码一个皮卡丘检测器-CNN目标检测入门教程(下)

简介: 目标检测不同于分类任务,需要考虑的不只是全图尺度的单一分类,而是需要检测到不同大小,不同位置的物体,难度自然提升了许多,用扫窗之类的传统方法早已不适合神经网络这种需要大量计算需求的新结构。幸好我们可以用本章节介绍的方法,利用卷积网络的特性,一次推导得到全部的预测结果,相对来说快速且准确。

本文作者Zhreshold,原文载于其知乎主页,雷锋网(公众号:雷锋网(公众号:雷锋网))获其授权发布。

本文为大家介绍实验过程中训练、测试过程及结果。算法和数据集参见《从零开始码一个皮卡丘检测器-CNN目标检测入门教程(上)》

训练 Train

损失函数 Losses

通过定义损失函数,我们可以让网络收敛到我们希望得到的目标检测功能,也就是说,我们希望网络能正确预测物体的类别,同时能预测出准确的预设框偏移量,以正确地显示物体的真正大小和位置。

这个预测的类别和偏移量都是可以通过真实标签和网络的当前预测值得到,在这里我们用MultiBoxTarget层来计算,其中包含了预测框和真实标签的匹配,正类和负类的选择,就不一一详述了。(详情见论文 SSD: Single Shot MultiBox Detector)。

from mxnet.contrib.ndarray import MultiBoxTarget

def training_targets(default_anchors, class_predicts, labels):
   class_predicts = nd.transpose(class_predicts, axes=(0, 2, 1))
   z = MultiBoxTarget(*[default_anchors, labels, class_predicts])
   box_target = z[0]  # 预设框偏移量 (x, y, width, height)
   box_mask = z[1]  # box_mask用来把负类的偏移量置零,因为背景不需要位置!
   cls_target = z[2]  # 每个预设框应该对应的分类
   return box_target, box_mask, cls_target

gluon.loss中有很多预设的损失函数可以选择,当然我们也可以快速地手写一些损失函数。

首先,对于物体分类的概率,平时我们往往用交叉墒,不过在目标检测中,我们有大量非平衡的负类(背景),那么 Focal Loss会是一个很好的选择(详情见论文 Focal Loss for Dense Object Detection)。

class FocalLoss(gluon.loss.Loss):
   def __init__(self, axis=-1, alpha=0.25, gamma=2, batch_axis=0, **kwargs):
       super(FocalLoss, self).__init__(None, batch_axis, **kwargs)
       self._axis = axis
       self._alpha = alpha
       self._gamma = gamma

   def hybrid_forward(self, F, output, label):
       output = F.softmax(output)
       pt = F.pick(output, label, axis=self._axis, keepdims=True)
       loss = -self._alpha * ((1 - pt) ** self._gamma) * F.log(pt)
       return F.mean(loss, axis=self._batch_axis, exclude=True)


# cls_loss = gluon.loss.SoftmaxCrossEntropyLoss()

cls_loss = FocalLoss()

print(cls_loss)

FocalLoss(batch_axis=0, w=None)

接下来是一个流行的 SmoothL1 损失函数,用来惩罚不准确的预设框偏移量。

class SmoothL1Loss(gluon.loss.Loss):
   def __init__(self, batch_axis=0, **kwargs):
       super(SmoothL1Loss, self).__init__(None, batch_axis, **kwargs)

   def hybrid_forward(self, F, output, label, mask):
       loss = F.smooth_l1((output - label) * mask, scalar=1.0)
       return F.mean(loss, self._batch_axis, exclude=True)


box_loss = SmoothL1Loss()

print(box_loss)

SmoothL1Loss(batch_axis=0, w=None)

衡量性能指标 Evaluate metrics

我们在训练时需要一些指标来衡量训练是否顺利,我们这里用准确率衡量分类的性能,用平均绝对误差衡量偏移量的预测能力。这些指标对网络本身没有任何影响,只是用于观测。

cls_metric = mx.metric.Accuracy()

box_metric = mx.metric.MAE()  # measure absolute difference between prediction and target

选择训练用的设备 Set context for training

ctx = mx.gpu()  # 用GPU加速训练过程

try:
   _ = nd.zeros(1, ctx=ctx)
   # 为了更有效率,cuda实现需要少量的填充,不影响结果
   train_data.reshape(label_shape=(3, 5))
   train_data = test_data.sync_label_shape(train_data)

except mx.base.MXNetError as err:
   # 没有gpu也没关系,交给cpu慢慢跑
   print('No GPU enabled, fall back to CPU, sit back and be patient...')
   ctx = mx.cpu()

初始化网络参数 Initialize parameters

net=ToySSD(num_class)

net.initialize(mx.init.Xavier(magnitude=2),ctx=ctx)

用gluon.Trainer简化训练过程 Set up trainer

gluon.Trainer能简化优化网络参数的过程,免去对各个参数单独更新的痛苦。

net.collect_params().reset_ctx(ctx)

trainer=gluon.Trainer(net.collect_params(),'sgd',{'learning_rate':0.1,'wd':5e-4})

开始训练 Start training

既然是简单的示例,我们不想花费太多的时间来训练网络,所以会预加载训练过一段时间的网络参数继续训练。

如果你感兴趣的话,可以设置

from_scratch=True

这样网络就会从初始的随机参数开始训练。

一般从头训练用单个gpu会花费半个多小时。

epochs = 150  # 设大一点的值来得到更好的结果

log_interval = 20

from_scratch = False  # 设为True就可以从头开始训练

if from_scratch:
   start_epoch = 0

else:
   start_epoch = 148
   pretrained = 'ssd_pretrained.params'
   sha1 = 'fbb7d872d76355fff1790d864c2238decdb452bc'
   url = 'https://apache-mxnet.s3-accelerate.amazonaws.com/gluon/models/ssd_pikachu-fbb7d872.params'
   if not osp.exists(pretrained) or not verified(pretrained, sha1):
       print('Downloading', pretrained, url)
       download(url, fname=pretrained, overwrite=True)
   net.load_params(pretrained, ctx)

喝咖啡的时间

import time

from mxnet import autograd as ag

for epoch in range(start_epoch, epochs):
   # 重置iterator和时间戳
   train_data.reset()
   cls_metric.reset()
   box_metric.reset()
   tic = time.time()
   # 迭代每一个批次
   for i, batch in enumerate(train_data):
       btic = time.time()
       # 用autograd.record记录需要计算的梯度
       with ag.record():
           x = batch.data[0].as_in_context(ctx)
           y = batch.label[0].as_in_context(ctx)
           default_anchors, class_predictions, box_predictions = net(x)
           box_target, box_mask, cls_target = training_targets(default_anchors, class_predictions, y)
           # 损失函数计算
           loss1 = cls_loss(class_predictions, cls_target)
           loss2 = box_loss(box_predictions, box_target, box_mask)
           # 1比1叠加两个损失函数,也可以加权重
           loss = loss1 + loss2
           # 反向推导
           loss.backward()
       # 用trainer更新网络参数
       trainer.step(batch_size)
       # 更新下衡量的指标
       cls_metric.update([cls_target], [nd.transpose(class_predictions, (0, 2, 1))])
       box_metric.update([box_target], [box_predictions * box_mask])
       if (i + 1) % log_interval == 0:
           name1, val1 = cls_metric.get()
           name2, val2 = box_metric.get()
           print('[Epoch %d Batch %d] speed: %f samples/s, training: %s=%f, %s=%f'
                 %(epoch ,i, batch_size/(time.time()-btic), name1, val1, name2, val2))

   # 打印整个epoch的的指标
   name1, val1 = cls_metric.get()
   name2, val2 = box_metric.get()
   print('[Epoch %d] training: %s=%f, %s=%f'%(epoch, name1, val1, name2, val2))
   print('[Epoch %d] time cost: %f'%(epoch, time.time()-tic))


# 还可以把网络的参数存下来以便下次再用

net.save_params('ssd_%d.params' % epochs)

[Epoch 148 Batch 19] speed: 109.217423 samples/s, training: accuracy=0.997539, mae=0.001862
[Epoch 148] training: accuracy=0.997610, mae=0.001806
[Epoch 148] time cost: 17.762958
[Epoch 149 Batch 19] speed: 110.492729 samples/s, training: accuracy=0.997607, mae=0.001824
[Epoch 149] training: accuracy=0.997692, mae=0.001789
[Epoch 149] time cost: 15.353258

测试 Test

接下来就是  从零开始码一个皮卡丘检测器-CNN目标检测入门教程(下) 的时刻,我们用训练好的网络来测试一张图片。

网络推导的过程和训练很相似,只不过我们不再需要计算真值和损失函数,也不再需要更新网络的参数,一次推导就可以得到结果。

准备测试数据 Prepare the test data

我们需要读取一张图片,稍微调整到网络需要的结构,比如说我们需要调整图片通道的顺序,减去平均值等等惯用的方法。

import numpy as np

import cv2

def preprocess(image):
   """Takes an image and apply preprocess"""
   # 调整图片大小成网络的输入
   image = cv2.resize(image, (data_shape, data_shape))
   # 转换 BGR 到 RGB
   image = image[:, :, (2, 1, 0)]
   # 减mean之前先转成float
   image = image.astype(np.float32)
   # 减 mean
   image -= np.array([123, 117, 104])
   # 调成为 [batch-channel-height-width]
   image = np.transpose(image, (2, 0, 1))
   image = image[np.newaxis, :]
   # 转成 ndarray
   image = nd.array(image)
   return image


image = cv2.imread('img/pikachu.jpg')

x = preprocess(image)

print('x', x.shape)

x (1, 3, 256, 256)

网络推导 Network inference

只要一行代码,输入处理完的图片,输出我们要的所有预测值和预设框。

# 如果有预先训练好的网络参数,可以直接加载

# net.load_params('ssd_%d.params' % epochs, ctx)

anchors, cls_preds, box_preds = net(x.as_in_context(ctx))

print('anchors', anchors)

print('class predictions', cls_preds)

print('box delta predictions', box_preds)

anchors
[[[-0.084375 -0.084375 0.115625 0.115625 ]
[-0.12037501 -0.12037501 0.15162501 0.15162501]
[-0.12579636 -0.05508568 0.15704636 0.08633568]
...,
[ 0.01949999 0.01949999 0.98049998 0.98049998]
[-0.12225395 0.18887302 1.12225389 0.81112695]
[ 0.18887302 -0.12225395 0.81112695 1.12225389]]]
<NDArray 1x5444x4 @gpu(0)>
class predictions
[[[ 0.33754104 -1.64660323]
[ 1.15297699 -1.77257478]
[ 1.1535604 -0.98352218]
...,
[-0.27562004 -1.29400492]
[ 0.45524898 -0.88782215]
[ 0.20327765 -0.94481993]]]
<NDArray 1x5444x2 @gpu(0)>
box delta predictions
[[-0.16735925 -0.13083346 -0.68860865 ..., -0.18972112 0.11822788
-0.27067867]]
<NDArray 1x21776 @gpu(0)>

是不是看着还很奇怪,别着急,还差最后一步

转换为可读的输出 Convert predictions to real object detection results

要把网络输出转换成我们需要的坐标,还要最后一步,比如我们需要softmax把分类预测转换成概率,还需要把偏移量和预设框结合来得到物体的大小和位置。

非极大抑制(Non-Maximum Suppression)也是必要的一步,因为一个物体往往有不只一个检测框。

from mxnet.contrib.ndarray import MultiBoxDetection

# 跑一下softmax, 转成0-1的概率

cls_probs = nd.SoftmaxActivation(nd.transpose(cls_preds, (0, 2, 1)), mode='channel')

# 把偏移量加到预设框上,去掉得分很低的,跑一遍nms,得到最终的结果

output = MultiBoxDetection(*[cls_probs, box_preds, anchors], force_suppress=True, clip=False)

print(output)

[[[ 0. 0.61178613 0.51807499 0.5042429 0.67325425 0.70118797]
[-1. 0.59466797 0.52491206 0.50917625 0.66228026 0.70489514]
[-1. 0.5731774 0.53843218 0.50217044 0.66522425 0.7118448 ]
...,
[-1. -1. -1. -1. -1. -1. ]
[-1. -1. -1. -1. -1. -1. ]
[-1. -1. -1. -1. -1. -1. ]]]
<NDArray 1x5444x6 @gpu(0)>

结果中,每一行都是一个可能的结果框,表示为[类别id, 得分, 左边界,上边界,右边界,下边界],有很多-1的原因是网络预测到这些都是背景,或者作为被抑制的结果。

显示结果 Display results

数字永远不如图片来得直观

把得到的转换结果画在图上,就得到我们期待已久的几十万伏特图了!

def display(img, out, thresh=0.5):
   import random
   import matplotlib as mpl
   mpl.rcParams['figure.figsize'] = (10,10)
   pens = dict()
   plt.clf()
   plt.imshow(img)
   for det in out:
       cid = int(det[0])
       if cid < 0:
           continue
       score = det[1]
       if score < thresh:
           continue
       if cid not in pens:
           pens[cid] = (random.random(), random.random(), random.random())
       scales = [img.shape[1], img.shape[0]] * 2
       xmin, ymin, xmax, ymax = [int(p * s) for p, s in zip(det[2:6].tolist(), scales)]
       rect = plt.Rectangle((xmin, ymin), xmax - xmin, ymax - ymin, fill=False,
                            edgecolor=pens[cid], linewidth=3)
       plt.gca().add_patch(rect)
       text = class_names[cid]
       plt.gca().text(xmin, ymin-2, '{:s} {:.3f}'.format(text, score),
                      bbox=dict(facecolor=pens[cid], alpha=0.5),
                      fontsize=12, color='white')
   plt.show()


display(image[:, :, (2, 1, 0)], output[0].asnumpy(), thresh=0.45)

从零开始码一个皮卡丘检测器-CNN目标检测入门教程(下)

小结 Conclusion

目标检测不同于分类任务,需要考虑的不只是全图尺度的单一分类,而是需要检测到不同大小,不同位置的物体,难度自然提升了许多,用扫窗之类的传统方法早已不适合神经网络这种需要大量计算需求的新结构。幸好我们可以用本章节介绍的方法,利用卷积网络的特性,一次推导得到全部的预测结果,相对来说快速且准确。

我们希望能用较短的篇幅来描述一个足够简单的过程,但是难免会有疏漏,欢迎各种问题和建议,与此同时,我们会不断更新教程,并且会带来更多不同的算法,敬请期待。



本文作者:Non
本文转自雷锋网禁止二次转载,原文链接
相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
目录
相关文章
|
机器学习/深度学习 固态存储 算法
从零开始码一个皮卡丘检测器-CNN目标检测入门教程(上)
跟所有的图像相关的网络一样,我们需要一个主干网络来提取特征,同时也是作为第一个预测特征层。网络在当前层产生大量的预设框,和与之对应的每个方框的分类概率(背景,猫,狗等等)以及真正的物体和预设框的偏移量。
1238 0
|
28天前
|
机器学习/深度学习 人工智能 算法框架/工具
深度学习中的卷积神经网络(CNN)及其在图像识别中的应用
【10月更文挑战第7天】本文将深入探讨卷积神经网络(CNN)的基本原理,以及它如何在图像识别领域中大放异彩。我们将从CNN的核心组件出发,逐步解析其工作原理,并通过一个实际的代码示例,展示如何利用Python和深度学习框架实现一个简单的图像分类模型。文章旨在为初学者提供一个清晰的入门路径,同时为有经验的开发者提供一些深入理解的视角。
|
2月前
|
机器学习/深度学习 人工智能 自然语言处理
深度学习中的卷积神经网络(CNN)及其应用
【9月更文挑战第24天】本文将深入探讨深度学习中的一种重要模型——卷积神经网络(CNN)。我们将通过简单的代码示例,了解CNN的工作原理和应用场景。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的信息。
97 1
|
2月前
|
机器学习/深度学习 人工智能 自动驾驶
深度学习中的卷积神经网络(CNN)及其在图像识别中的应用
【9月更文挑战第19天】在人工智能的浩瀚星海中,卷积神经网络(CNN)如同一颗璀璨的星辰,照亮了图像处理的天空。本文将深入CNN的核心,揭示其在图像识别领域的强大力量。通过浅显易懂的语言和直观的比喻,我们将一同探索CNN的奥秘,并见证它如何在现实世界中大放异彩。
|
14天前
|
机器学习/深度学习 人工智能 自动驾驶
深度学习中的卷积神经网络(CNN)及其应用
【10月更文挑战第21天】本文旨在深入探讨深度学习领域的核心组成部分——卷积神经网络(CNN)。通过分析CNN的基本结构、工作原理以及在图像识别、语音处理等领域的广泛应用,我们不仅能够理解其背后的技术原理,还能把握其在现实世界问题解决中的强大能力。文章将用浅显的语言和生动的例子带领读者一步步走进CNN的世界,揭示这一技术如何改变我们的生活和工作方式。
|
21天前
|
机器学习/深度学习 人工智能 监控
深入理解深度学习中的卷积神经网络(CNN):从原理到实践
【10月更文挑战第14天】深入理解深度学习中的卷积神经网络(CNN):从原理到实践
64 1
|
3天前
|
机器学习/深度学习 人工智能 TensorFlow
深度学习中的卷积神经网络(CNN)及其在图像识别中的应用
【10月更文挑战第32天】本文将介绍深度学习中的一个重要分支——卷积神经网络(CNN),以及其在图像识别领域的应用。我们将通过一个简单的代码示例,展示如何使用Python和TensorFlow库构建一个基本的CNN模型,并对其进行训练和测试。
|
9天前
|
机器学习/深度学习 自然语言处理 TensorFlow
深度学习中的卷积神经网络(CNN)及其应用
【10月更文挑战第26天】在这篇文章中,我们将深入探讨卷积神经网络(CNN)的基本原理、结构和应用。CNN是深度学习领域的一个重要分支,广泛应用于图像识别、语音处理等领域。我们将通过代码示例和实际应用案例,帮助读者更好地理解CNN的概念和应用。
|
17天前
|
机器学习/深度学习 人工智能 自动驾驶
深入理解深度学习中的卷积神经网络(CNN)
【10月更文挑战第18天】深入理解深度学习中的卷积神经网络(CNN)
27 0
|
20天前
|
机器学习/深度学习 算法 数据安全/隐私保护
基于贝叶斯优化卷积神经网络(Bayes-CNN)的多因子数据分类识别算法matlab仿真
本项目展示了贝叶斯优化在CNN中的应用,包括优化过程、训练与识别效果对比,以及标准CNN的识别结果。使用Matlab2022a开发,提供完整代码及视频教程。贝叶斯优化通过构建代理模型指导超参数优化,显著提升模型性能,适用于复杂数据分类任务。

热门文章

最新文章