TensorFlow 2 和 Keras 高级深度学习:11~13(1)https://developer.aliyun.com/article/1426963
5. SSD 模型架构
“图 11.5.1”显示了 SSD 的模型架构,该模型实现了多尺度单发目标检测的概念框架。 网络接受 RGB 图像,并输出几个预测级别。 基本或骨干网络提取用于分类和偏移量预测的下游任务的特征。 ResNet50 是骨干网络的一个很好的例子,它类似于“第 2 章”,“深度神经网络”中讨论,实现和评估的内容。 在骨干网络之后,对象检测任务由执行其余的网络,我们将其称为 SSD 头。
骨干网络可以是具有冻结权重的预训练网络(例如,以前为 ImageNet 分类而训练),也可以是与对象检测一起训练的网络。 如果使用预先训练的基础网络,则可以利用重用以前从大型数据集中学习的特征提取过滤器的优势。 此外,由于冻结了骨干网参数,因此可以加快学习速度。 仅训练对象检测中的顶层。 在这本书中,骨干网是与对象检测联合训练的,因为我们假设我们不一定需要访问预先训练的骨干网。
骨干网网络通常使用跨步 2 或通过最大池化实现几轮下采样。 对于 ResNet50,这是 4 倍。 基本网络变为(w/2^4, h/2^4) = (w/16, h/16)
之后,特征映射的结果尺寸。 如果图像的宽度和高度均可以被 16 整除,则尺寸是精确的。
例如,对于640 x 480
的图像,生成的特征映射的尺寸为40 x 30 = 1200
。 如前几节所述,这是基础网络之后长宽比等于 1 的锚点框的数量。 此数字乘以每个锚定框的大小数。 在前面的部分中,由于长宽比,有 6 种不同的尺寸,而长宽比为 1 时,还有一个其他尺寸。
在本书中,我们将纵横比限制为a[i ∈ {0, 1, 3}] = 1, 2, 1/2
。 因此,将只有 4 种不同的大小。 对于640 x 480
图像,第一组锚框的锚框总数为n[1] = 4,800
。
在“图 11.5.1”中,指示密集网格以表明对于第一组预测,存在大量预测(例如:40 x 30 x 4
),从而导致大量补丁 。 尽管每个锚点框有 4 种尺寸,但为清楚起见,仅显示了与宽高比 1 对应的16 x 16
锚点框。
此锚框也是40 x 30 x n_filter
特征映射中每个元素的接受字段大小。n_filter
是骨干网最后卷积层中过滤器的数量。 对于每个锚框,都将预测类别和偏移量。
总共有n[1]
类和n[1]
偏移量预测。 单热类预测的维数等于要检测的对象类别的数量,背景为 1。 每个偏移量变量预测的尺寸为 4,对应于(x, y)
到预测边界框的 2 个角的偏移量。
类预测器由卷积层组成,该卷积层由使用 softmax 进行分类交叉熵损失的激活层终止。 偏移量预测值是具有线性激活的独立卷积层。
在基础网络之后可以应用其他特征提取模块。 每个特征提取器块都是Conv2D(strides=2)-BN-ELU
的形式。 在特征提取块之后,特征映射的大小减半,并且过滤器的数量增加一倍。 例如,基本网络之后的第一个特征提取器块具有20 x 15 x 2 n_filter
特征映射。 根据该特征映射,使用卷积层进行n[2]
类和n[2]
偏移量预测。n[2] = 20 x 15 x 4 = 1,200
可以继续添加具有类和偏移量预测变量的特征提取块的过程。 在前面的部分中,对于640 x 480
的图像,最大可达2 x 1 x 2^5 n_filter
特征映射产生n[6]
类和n[6]
抵消了其中n[6] = 2 x 1 x 4 = 8
的预测。 到 6 层特征提取和预测块。 在第 6 个块之后,一个640 x 480
图像的锚点映射预测总数为 9,648。
在前面的部分中,锚定框的比例因子大小按降序排列:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Kwi0SDYz-1681704403414)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_11_050.png)] Equation 11.5.1)
这样做是为了使讨论清晰。 在本节中,应该意识到比例因子的大小实际上是从骨干网之后的特征映射大小开始的。 实际上,缩放因子应按升序排列:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RijKrcyO-1681704403414)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_11_051.png)] (Equation 11.5.2)
这意味着如果将特征提取块的数量减少到 4,则缩放因子为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p2TVElVq-1681704403414)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_11_052.png)] (Equation 11.5.3)
如果特征映射的宽度或高度不能被 2 整除(例如:15),则将应用天花板函数(例如:ceil(15/2) = 8
)。 但是,在原始的 SSD [2]实现中,所使用的缩放因子被简化为[0.2, 0.9]
范围,该范围通过缩放因子的数量或特征提取块的数量n_layers
进行线性缩放:
s = np.linspace(0.2, 0.9, n_layers + 1)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z9eIC3eZ-1681704403414)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_11_09.png)]
图 11.5.1 SSD 模型架构。请注意,对于w/16 x h/16
网格,锚框的数量可能不准确。 网格显示了锚框的紧密包装。
讨论了 SSD 模型架构之后,现在让我们看一下如何在 Keras 中实现 SSD 模型架构。
6. Keras 中的 SSD 模型架构
与前面章节中的代码示例不同,SSD 的tf.keras
实现更加复杂。 与 SSD 的其他tf.keras
实现相比,本章中提供的代码示例重点介绍多尺度目标检测的关键概念。 可以进一步优化代码实现的某些部分,例如缓存地面真锚框类,偏移量和掩码。 在我们的示例中,每次从文件系统加载图像时,线程都会计算出地面真实值。
“图 11.6.1”显示了包含 SSD 的tf.keras
实现的代码块的概述。 ssd-11.6.1.py
中的 SSD 对象可以构建,训练和评估 SSD 模型。 它借助model.py
和resnet.py
以及data_generator.py
中的多线程数据生成器,位于 SSD 模型创建器的顶部。 SSD 模型实现了“图 11.5.1”中所示的 SSD 架构。 每个主要模块的实现将在后续部分中详细讨论。
SSD 模型使用 ResNet 作为其骨干网络。 它在resnet.py
中调用 ResNet V1 或 V2 模型创建者。 与前几章中的示例不同,SSD 使用的数据集由数千个高分辨率图像组成。 多线程数据生成器将加载文件,并且将这些文件从文件系统排队。 它还计算锚点箱的地面真值标签。 如果没有多线程数据生成器,则在训练期间图像的加载和排队以及地面真值的计算将非常缓慢。
有许多小的但重要的例程在后台运行。 这些都集中存储在工具块中。 这些例程创建锚框,计算 IoU,建立真实情况标签,运行非最大抑制,绘制标签和框,在视频帧上显示检测到的对象,提供损失函数等。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ohoB0hD1-1681704403415)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_11_10.png)]
图 11.6.1 实现 SSD 的代码块。
7. Keras 中的 SSD 对象
“列表 11.7.1”(很快显示)显示了 SSD 类。 说明了两个主要例程:
- 使用
build_model()
创建 SSD 模型 - 通过
build_generator()
实例化数据生成器
build_model
首先根据训练标签创建数据字典。 字典存储图像文件名以及每个图像中每个对象的地面真实边界框坐标和类。 之后,构建骨干网和 SSD 网络模型。 模型创建的最重要产品是self.ssd
– SSD 的网络模型。
标签存储在 csv 文件中。 对于本书中使用的示例训练图像,标签以以下格式保存在dataset/drinks/labels_train.csv
中:
frame,xmin,xmax,ymin,ymax,class_id 0001000.jpg,310,445,104,443,1 0000999.jpg,194,354,96,478,1 0000998.jpg,105,383,134,244,1 0000997.jpg,157,493,89,194,1 0000996.jpg,51,435,207,347,1 0000995.jpg,183,536,156,283,1 0000994.jpg,156,392,178,266,2 0000993.jpg,207,449,119,213,2 0000992.jpg,47,348,213,346,2 …
“列表 11.7.1”:ssd-11.6.1.py
class SSD: """Made of an ssd network model and a dataset generator. SSD defines functions to train and validate an ssd network model.
Arguments: args: User-defined configurations
Attributes: ssd (model): SSD network model train_generator: Multi-threaded data generator for training """ def __init__(self, args): """Copy user-defined configs. Build backbone and ssd network models. """ self.args = args self.ssd = None self.train_generator = None self.build_model()
def build_model(self): """Build backbone and SSD models.""" # store in a dictionary the list of image files and labels self.build_dictionary() # input shape is (480, 640, 3) by default self.input_shape = (self.args.height, self.args.width, self.args.channels)
# build the backbone network (eg ResNet50) # the number of feature layers is equal to n_layers # feature layers are inputs to SSD network heads # for class and offsets predictions self.backbone = self.args.backbone(self.input_shape, n_layers=self.args.layers)
# using the backbone, build ssd network # outputs of ssd are class and offsets predictions anchors, features, ssd = build_ssd(self.input_shape, self.backbone, n_layers=self.args.layers, n_classes=self.n_classes) # n_anchors = num of anchors per feature point (eg 4) self.n_anchors = anchors # feature_shapes is a list of feature map shapes # per output layer - used for computing anchor boxes sizes self.feature_shapes = features # ssd network model self.ssd = ssd
def build_dictionary(self): """Read input image filenames and obj detection labels from a csv file and store in a dictionary. """ # train dataset path path = os.path.join(self.args.data_path, self.args.train_labels)
# build dictionary: # key=image filaname, value=box coords + class label # self.classes is a list of class labels self.dictionary, self.classes = build_label_dictionary(path) self.n_classes = len(self.classes) self.keys = np.array(list(self.dictionary.keys()))
def build_generator(self): """Build a multi-thread train data generator."""
self.train_generator = \ DataGenerator(args=self.args, dictionary=self.dictionary, n_classes=self.n_classes, feature_shapes=self.feature_shapes, n_anchors=self.n_anchors, shuffle=True)
“列表 11.7.2”显示了 SSD 对象中的另一种重要方法train()
。 指示了使用默认损失函数或改进的损失函数的选项,如先前部分所述。 还有一个选项可以选择仅平滑 L1。
self.ssd.fit_generator()
是此函数中最重要的调用。 它借助多线程数据生成器启动有监督的训练。 在每个周期,都会执行两个回调函数。 首先,将模型权重保存到文件中。 然后,对于 ResNet 模型,以与“第 2 章”,“深度神经网络”相同的方式使用的改进的学习率调度器称为:
“列表 11.7.2”:ssd-11.6.1.py
def train(self): """Train an ssd network.""" # build the train data generator if self.train_generator is None: self.build_generator()
optimizer = Adam(lr=1e-3) # choice of loss functions via args if self.args.improved_loss: print_log("Focal loss and smooth L1", self.args.verbose) loss = [focal_loss_categorical, smooth_l1_loss] elif self.args.smooth_l1: print_log("Smooth L1", self.args.verbose) loss = ['categorical_crossentropy', smooth_l1_loss] else: print_log("Cross-entropy and L1", self.args.verbose) loss = ['categorical_crossentropy', l1_loss]
self.ssd.compile(optimizer=optimizer, loss=loss)
# prepare callbacks for saving model weights # and learning rate scheduler # learning rate decreases by 50% every 20 epochs # after 60th epoch checkpoint = ModelCheckpoint(filepath=filepath, verbose=1, save_weights_only=True) scheduler = LearningRateScheduler(lr_scheduler)
callbacks = [checkpoint, scheduler] # train the ssd network self.ssd.fit_generator(generator=self.train_generator, use_multiprocessing=True, callbacks=callbacks, epochs=self.args.epochs, workers=self.args.workers)
在下一部分中,我们将讨论 Keras 中 SSD 架构实现的其他详细信息。 特别是 SSD 模型和多线程数据生成器的实现。
8. Keras 中的 SSD 模型
“列表 11.8.1”显示了 SSD 模型创建函数build_ssd()
。 该模型在“图 11.5.1”中进行了说明。 该函数通过调用base_outputs = backbone(inputs)
从骨干网或基础网络检索输出特征的n_layers
。
在本书中,backbone()
是build_resnet()
。 build_resnet()
可以生成的 ResNet 模型类似于“第 2 章”,“深度神经网络”中讨论的残差网络。 build_resnet()
函数可以由构建基础网络的任何函数名称代替。
如图“图 11.5.1”所示,返回值base_outputs
是输出特征的列表,这些特征将作为类别和偏移预测层的输入。 例如,第一输出base_outputs[0]
用于生成n[1]
类预测和n[1]
偏移量预测。
在build_ssd()
的for
循环中,类别预测是classes
变量,而偏移量预测是offsets
变量。 在for
循环迭代之后,将类别预测连接,并最终合并为一个具有以下尺寸的classes
变量:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XSiT2ATp-1681704403415)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_11_055.png)]
对offsets
变量执行相同的过程。 结果尺寸为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hILIvRYc-1681704403415)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_11_056.png)]
其中n_mini_batch
是迷你批量大小,n_anchor_box
是锚定框的数量。 for
循环迭代的次数等于n_layers
。 该数目也等于锚定框缩放因子的所需数目或 SSD 头的特征提取块的数目。
函数build_ssd()
返回每个特征点或区域的锚框数量,每个前类的特征形状,偏移量预测层以及 SSD 模型本身。
“列表 11.8.1”:model.py
def build_ssd(input_shape, backbone, n_layers=4, n_classes=4, aspect_ratios=(1, 2, 0.5)): """Build SSD model given a backbone Arguments: input_shape (list): input image shape backbone (model): Keras backbone model n_layers (int): Number of layers of ssd head n_classes (int): Number of obj classes aspect_ratios (list): annchor box aspect ratios Returns: n_anchors (int): Number of anchor boxes per feature pt feature_shape (tensor): SSD head feature maps model (Keras model): SSD model """ # number of anchor boxes per feature map pt n_anchors = len(aspect_ratios) + 1
inputs = Input(shape=input_shape) # no. of base_outputs depends on n_layers base_outputs = backbone(inputs) outputs = [] feature_shapes = [] out_cls = [] out_off = []
for i in range(n_layers): # each conv layer from backbone is used # as feature maps for class and offset predictions # also known as multi-scale predictions conv = base_outputs if n_layers==1 else base_outputs[i] name = "cls" + str(i+1) classes = conv2d(conv, n_anchors*n_classes, kernel_size=3, name=name)
# offsets: (batch, height, width, n_anchors * 4) name = "off" + str(i+1) offsets = conv2d(conv, n_anchors*4, kernel_size=3, name=name)
shape = np.array(K.int_shape(offsets))[1:] feature_shapes.append(shape) # reshape the class predictions, yielding 3D tensors of # shape (batch, height * width * n_anchors, n_classes) # last axis to perform softmax on them name = "cls_res" + str(i+1) classes = Reshape((-1, n_classes), name=name)(classes)
# reshape the offset predictions, yielding 3D tensors of # shape (batch, height * width * n_anchors, 4) # last axis to compute the (smooth) L1 or L2 loss name = "off_res" + str(i+1) offsets = Reshape((-1, 4), name=name)(offsets) # concat for alignment with ground truth size # made of ground truth offsets and mask of same dim # needed during loss computation offsets = [offsets, offsets] name = "off_cat" + str(i+1) offsets = Concatenate(axis=-1, name=name)(offsets)
# collect offset prediction per scale out_off.append(offsets)
name = "cls_out" + str(i+1)
#activation = 'sigmoid' if n_classes==1 else 'softmax' #print("Activation:", activation)
classes = Activation('softmax', name=name)(classes)
# collect class prediction per scale out_cls.append(classes)
if n_layers > 1: # concat all class and offset from each scale name = "offsets" offsets = Concatenate(axis=1, name=name)(out_off) name = "classes" classes = Concatenate(axis=1, name=name)(out_cls) else: offsets = out_off[0] classes = out_cls[0]
outputs = [classes, offsets] model = Model(inputs=inputs, outputs=outputs, name='ssd_head')
return n_anchors, feature_shapes, model
如前面所述,与 MNIST 和 CIFAR-10 等小型数据集不同,SSD 中使用的映像很大。 因此,不可能将图像加载到张量变量中。 在下一节中,我们将介绍一个多线程数据生成器,该生成器将使我们能够从文件系统并发加载图像,并避免内存瓶颈。
9. Keras 中的数据生成器模型
SSD 需要大量带标签的高分辨率图像来进行对象检测。 与之前的章节中使用的数据集可以加载到到内存中以训练模型不同,SSD 实现了多线程数据生成器。 多线程生成器的任务是加载图像的多个迷你批量及其相应的标签。 由于具有多线程,GPU 可以保持繁忙,因为一个线程向其提供数据,而其余的 CPU 线程处于队列中,准备从文件系统中馈入另一批数据或加载一批图像并计算基本真值 。“列表 11.9.1”显示了 Keras 中的数据生成器模型。
DataGenerator
类继承自 Keras 的Sequence
类,以确保它支持多处理。 DataGenerator
保证在一个周期内使用整个数据集。
给定批量大小的整个周期的长度由__len__()
方法返回。 对小批量数据的每个请求都可以通过__getitem__()
方法来满足。 在每个周期之后,如果self.shuffle
为True
,则调用on_epoch_end()
方法以随机播放整个批量。
“列表 11.9.1”:data_generator.py
class DataGenerator(Sequence): """Multi-threaded data generator. Each thread reads a batch of images and their object labels
Arguments: args: User-defined configuration dictionary: Dictionary of image filenames and object labels n_classes (int): Number of object classes feature_shapes (tensor): Shapes of ssd head feature maps n_anchors (int): Number of anchor boxes per feature map pt shuffle (Bool): If dataset should be shuffled bef sampling """ def __init__(self, args, dictionary, n_classes, feature_shapes=[], n_anchors=4, shuffle=True): self.args = args self.dictionary = dictionary self.n_classes = n_classes self.keys = np.array(list(self.dictionary.keys())) self.input_shape = (args.height, args.width, args.channels) self.feature_shapes = feature_shapes self.n_anchors = n_anchors self.shuffle = shuffle self.on_epoch_end() self.get_n_boxes()
def __len__(self): """Number of batches per epoch""" blen = np.floor(len(self.dictionary) / self.args.batch_size) return int(blen)
def __getitem__(self, index): """Get a batch of data""" start_index = index * self.args.batch_size end_index = (index+1) * self.args.batch_size keys = self.keys[start_index: end_index] x, y = self.__data_generation(keys) return x, y
def on_epoch_end(self): """Shuffle after each epoch""" if self.shuffle == True: np.random.shuffle(self.keys)
def get_n_boxes(self): """Total number of bounding boxes""" self.n_boxes = 0 for shape in self.feature_shapes: self.n_boxes += np.prod(shape) // self.n_anchors return self.n_boxes
数据生成器的大部分工作都是通过__data_generation()
方法完成的,如“列表 11.9.2”所示。 给定一个小批量,该方法执行:
imread()
从文件系统读取图像。labels = self.dictionary[key]
访问词典中存储的边界框和类标签。 前四个项目是边界框偏移量。 最后一个是类标签。anchor_boxes()
生成锚框。iou()
计算相对于地面真值边界框的每个锚点框的 IoU。get_gt_data()
为每个锚框分配地面真实等级和偏移量。
样本数据扩充函数也包括在内,但此处不再讨论,例如添加随机噪声,强度重新缩放和曝光调整。 __data_generation()
返回输入x
和输出y
对,其中张量x
存储输入图像,而张量y
捆绑类,偏移量 ,和面具一起。
“列表 11.9.2”:data_generator.py
import layer_utils
from skimage.io import imread def __data_generation(self, keys): """Generate train data: images and object detection ground truth labels
Arguments: keys (array): Randomly sampled keys (key is image filename)
Returns: x (tensor): Batch images y (tensor): Batch classes, offsets, and masks """ # train input data x = np.zeros((self.args.batch_size, *self.input_shape)) dim = (self.args.batch_size, self.n_boxes, self.n_classes) # class ground truth gt_class = np.zeros(dim) dim = (self.args.batch_size, self.n_boxes, 4) # offsets ground truth gt_offset = np.zeros(dim) # masks of valid bounding boxes gt_mask = np.zeros(dim)
for i, key in enumerate(keys): # images are assumed to be stored in self.args.data_path # key is the image filename image_path = os.path.join(self.args.data_path, key) image = skimage.img_as_float(imread(image_path)) # assign image to a batch index x[i] = image # a label entry is made of 4-dim bounding box coords # and 1-dim class label labels = self.dictionary[key] labels = np.array(labels) # 4 bounding box coords are 1st four items of labels # last item is object class label boxes = labels[:,0:-1] for index, feature_shape in enumerate(self.feature_shapes): # generate anchor boxes anchors = anchor_boxes(feature_shape, image.shape, index=index, n_layers=self.args.layers) # each feature layer has a row of anchor boxes anchors = np.reshape(anchors, [-1, 4]) # compute IoU of each anchor box # with respect to each bounding boxes iou = layer_utils.iou(anchors, boxes)
# generate ground truth class, offsets & mask gt = get_gt_data(iou, n_classes=self.n_classes, anchors=anchors, labels=labels, normalize=self.args.normalize, threshold=self.args.threshold) gt_cls, gt_off, gt_msk = gt if index == 0: cls = np.array(gt_cls) off = np.array(gt_off) msk = np.array(gt_msk) else: cls = np.append(cls, gt_cls, axis=0) off = np.append(off, gt_off, axis=0) msk = np.append(msk, gt_msk, axis=0)
gt_class[i] = cls gt_offset[i] = off gt_mask[i] = msk
y = [gt_class, np.concatenate((gt_offset, gt_mask), axis=-1)]
return x, y
现在我们有一个多线程生成器,我们可以用它来从文件系统加载图像。 在下一节中,我们将演示如何通过拍摄目标对象的图像并对其进行标记来构建自定义数据集。
10. 示例数据集
使用便宜的 USB 相机(A4TECH PK-635G)收集了一个由 1,000 640 X 480
RGB 训练图像和 50 640 X 480
RGB 测试图像组成的小型数据集。 使用 VGG 图像标注器(VIA)[5]标记数据集图像,以检测三个对象:1)汽水罐,2)果汁罐和 3)水瓶。“图 11.10.1”显示了标记过程的示例 UI。
可以在GitHub
存储库的utils/video_capture.py
中找到用于收集图像的工具脚本。 该脚本每 5 秒自动捕获一次图像,因此可以加快数据收集过程。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DXlDpg8P-1681704403415)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_11_11.png)]
图 11.10.1 使用 VGG 图像标注器(VIA)进行数据集标记的过程
数据收集和标记是一项耗时的活动。 在行业中,通常将其外包给第三方标注公司。 使用自动数据标记软件是加快数据标记任务的另一种选择。
有了这个示例数据集,我们现在可以训练我们的对象检测网络。
11. SSD 模型训练
可以从以下链接下载包含 csv 格式标签的 train 和测试数据集。
在顶层文件夹(即“第 11 章”,“对象检测”)中,创建数据集文件夹,将下载的文件复制到此处,然后运行以下命令将其解压缩:
mkdir dataset cp drinks.tar.gz dataset cd dataset tar zxvf drinks.tar.gz cd..
通过执行以下步骤,将 SSD 模型训练 200 个周期:
python3 ssd-11.6.1.py --train
可以根据 GPU 内存调整默认的批量大小--batch-size=4
。 在 1080Ti 上,批量大小为 2。在 32GB V100 上,每个 GPU 可以为 4 或 8。 --train
代表模型训练选项。
为了支持边界框偏移量的归一化,包含--normalize
选项。 为了使用改进的损失函数,添加了--improved_loss
选项。 如果仅需要平滑的 L1(无焦点损失),请使用–smooth-l1
。 为了显示:
- L1,无规范化:
python3 ssd-11.1.1.py –-train
- 改进的损失函数,无规范化:
python3 ssd-11.1.1.py –-train --improved-loss
- 改进的损失函数,具有规范化:
python3 ssd-11.1.1.py –-train –improved-loss --normalize
- 平滑 L1,具有规范化:
python3 ssd-11.1.1.py –-train –-smooth-l1 --normalize
训练完 SSD 网络之后,我们需要解决另一个问题。 我们如何处理给定对象的多个预测? 在测试训练好的模型之前,我们将首先讨论非最大抑制(NMS)算法。
12. 非最大抑制(NMS)算法
模型训练完成后,网络将预测边界框偏移量和相应的类别。 在某些情况下,两个或更多边界框引用同一对象,从而创建冗余预测。 图 11.12.1 中的汽水罐表示了这种情况。 为了删除多余的预测,将调用 NMS 算法。 本书涵盖了经典 NMS 和软 NMS [6],如“算法 11.12.1”中所示。 两种算法都假定边界框和相应的置信度得分或概率是已知的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RQWgzBKI-1681704403416)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_11_12.png)]
图 11.12.1 网络预测了汽水罐对象的两个重叠边界框。 只选择一个有效的边界框,即得分为 0.99 的边界框。
在经典 NMS 中,基于概率选择最终边界框,并将其存储在列表D
中,并带有相应的分数S
。 所有边界框和相应的概率都存储在初始列表B
和P
中。 在第 3 行和第 4 行中,将具有最高分数p[m]
的边界框用作参考,b[m]
。
参考边界框被添加到最终选择的边界框D
的列表中,并从列表B
中删除,如第 5 行所示。 并且列表S
从P
中删除。 对于其余边界框,如果 IoU 与b[m]
大于或等于设置的阈值N[t]
,将其从B
中删除。 其相应的分数也从P
中删除。
步骤在第 6 和 9-11 行中显示。 这些步骤将删除所有分数较小的冗余边界框。 在检查完所有其余的边界框之后,重复从第 3 行开始的过程。 该过程继续进行,直到边界框B
的列表为空。 该算法返回选定的边界框D
和相应的分数S
。
经典 NMS 的问题是边界盒包含另一个对象,但其中的 IoU 和b[m]
会从列表中删除。 Soft NMS [6]提出,与其从列表中彻底删除,不如以b[m]
,如第 8 行所示。
重叠的边界框具有第二次机会。 IoU 较小的边界框在将来的迭代中具有更高的生存机会。 在将来的选择中,它实际上可能证明它包含一个与b[m]
不同的对象。 如“算法 11.12.1”中所示,Soft NMS 是传统 NMS 的便捷替代。 无需重新训练 SSD 网络。 与经典 NMS 相比,Soft NMS 具有更高的平均精度。
“列表 11.12.1”说明了经典 NMS 和软 NMS。 除了最终的边界框和相应的分数外,还返回相应的对象。 当其余边界框的最大分数小于某个阈值(例如:0.2)时,该代码将实现 NMS 的提前终止。
TensorFlow 2 和 Keras 高级深度学习:11~13(3)https://developer.aliyun.com/article/1426965