分类 TensorFlow & Keras 下的文章

使用RNN和CNN处理序列

击球手将球击出。外野手立即开始奔跑,预测球的轨迹。他跟踪它,调整自己的运动,最后抓住球。不管你是在听完朋友的话还是在早餐时期待咖啡的味道,预测都是你一直在做的事情。循环神经网络(RNN),这是一类可以预测未来的网络(在一定程度上)。它们可以分析时间序列数据(例如股票价格),并告诉你何时进行买卖。在自动驾驶系统中,它们可以预测汽车的行驶轨迹并帮助避免发生事故。更笼统地说,它们可以处理任意长度的序列,而不是像到目前为止的所有网络一样,用于固定大小的输入。例如,它们可以将句子、文档或音频样本作为输入,使其对于自然语言处理应用非常有用,例如自动翻译或语音转文本。

RNN面临的两个主要困难:

  • 不稳定的梯度,可以使用多种技术解决,包括递归dropout和递归层归一化
  • (非常)有限的短期记忆,可以使用LSTM和GRU单元进行拓展

RNN并不是唯一能够处理顺序数据的神经网络类型:对于较小的序列,常规的密集网络可以解决问题。对于很长的序列,例如音频样本或文本,卷积神经网络实际上也可以很好地工作。

语义分割

在语义分割中,每个像素根据其所属物体的类别(例如,道路、汽车、行人、建筑物等)进行分类。例如分割图像右侧的所有自行车都变成了一大块像素。此任务的主要困难在于,当图像通过常规CNN时,它们会逐渐失去其空间分辨率(由于步幅大于1的层),因此常规的CNN可能最终会知道在图像的左下角有一个人,但不会比这更精确了。

就像物体检测一样,有许多种方法可以解决此问题,有些方法非常复杂。但是,Jonathan Long等人在2015年的论文中提出了一个相当简单的解决方法。作者首先采用经过预训练的CNN,然后将其转换为FCN。CNN对输入图像应用的总步幅为32(如果所有步幅的总和都大于1),则意味着最后一层输出的特征图是输入图像的1/32。这显然太粗糙了,因此他们添加了一个单独的上采样层把分辨率乘以32.

有几种解决方法可用于上采样(增加图像的大小),例如双线性插值,但仅在X4或X8时才有效。取而代之的是,他们使用转置的卷积层:等效于首先插入空的行和列(充满零)来拉伸图像,然后执行常规的卷积。可以对转置的卷积层进行初始化以执行接近于线性插值的操作,但是由于它是可训练的层,因此在训练过程中会学习的更好。在tf.keras中,可以使用Conv2DTranspose层。

在转置的卷积层中,步幅定义了输入将被拉伸的程度,而不是滤波步长的大小,因此步幅越大,输出就越大。

这个解决方法可以,但仍然不够精确。为了做得更好,作者添加了来自较低层的跳过连接:例如,他们将输出图像上采样2倍(而不是32倍),并添加了具有两倍分辨率的较低层的输出。然后,他们对结果进行16倍的上采样,从而得到32倍的总上采样。这样可以恢复一些早期池化层丢失的空间分辨率。在他们的最佳架构中,他们使用了第二个类似的跳过连接来从更底层恢复甚至更精细的细节。简而言之,原始CNN的输出经过以下额外步骤:放大X2,添加较低层(适当比例)的输出,放大X2,添加甚至更底层的输出,最后方法X8。甚至有可能扩大到超出原始图像的大小:这可用于提高图像的分辨率,这是一种称为超分辨率的技术。

TensorFlow卷积运算

  • keras.layers.Conv1D

为一维输入(例如时间序列或文本(字母或单词的序列))创建卷积层。

  • keras.layers.Conv3D

为3D输入(例如3D PET扫描)创建卷积层

  • dilaton_rate

将任何卷积层的dilation_rate超参数设置为2或更大的值会创建“a-trous卷积层”。这等效于使用常规卷积层,并通过插入为零的行和列像孔一样来扩大滤波器。例如,可以使用4来扩展[[1,2,3]]的1X3滤波器,从而得到[[1,0,0,0,2,0,0,0,3]]来扩展滤波器。这使得卷积层无需任何计算费用,也无需使用任何额外参数就具有更大的接受视野

  • tf.nn.depthwise_conv2d()

可用于创建深度卷积层(但需要自己创建变量)。它将每个滤波器独立地应用于每个单独的输入通道。因此,如果有$f_n$个滤波器和$f_{n'}$个输入通道,则将输出$f_n\times f_{n'}$特征图

YOLO

YOLO是Joseph Redmon等人在2015年的论文中提出的一种极其快速、准确的物体检测架构随后在2016年(YOLOv2)和2018(YOLOv3)中进行了改进。

YOLOv3的架构和全卷积网络的架构非常相似,但有一些重要的区别:

  • 它为每个网格单元输出5个边界框(而不是一个),并且每个边界框都带有一个客观分数。由于它在包含20个类的PASCAL VOC数据集上进行了训练,因此每个网格单元还输出20个类别概率。每个网格单元共有45个数字:5个边界框,每个边界框具有4个坐标,再加上5个客观分数和20个类概率。
  • YOLOv3不预测边界框中心的绝对坐标,而是预测相对于网格单元坐标的偏移,其中(0,0)表示在该单元格左上角,而(1,1)表示右下角。对于每个网格单元,YOLOv3被训练只能预测其中心位于该单元内的边界框(但是边界框本身通常会远远超出网格单元)。YOLOv3将逻辑激活函数应用于边界框坐标,以确保它们在0到1的范围内。
  • 在训练神经网络之前,YOLOv3会找到5个具有代表性的边界框,称为锚框(或先验边界框)。它通过K-Means算法应用于训练集边界框的高度和宽度来实现。例如,如果训练图像包含许多行人,则锚框之一将可能具有典型的行人尺寸。然后,当神经网络预测每个网格单元的5个边界框时,它实际上预测了重新缩放每个锚定框的幅度。例如,假设一个锚框的高度为100像素,宽度为50像素,并且网络预测(对于一个网格单元)垂直缩放因子为1.5,水平缩放因子为0.9。那预测的边界框大小为150X45。更准确地说,对于每个网格单元和每个锚框,网络都将预测垂直和水平缩放比例的对数。拥有这些先验知识使网络更有可能预测出适当尺寸的边界框,并且由于它能更快地学习到合理边界框的样子,因此也加快了训练速度。
  • 该网络使用不同比例的图像进行训练:在训练过程中每隔几批,网络会随机选择一个训的图像尺寸。这使得网络能够学习检测不同比例的物体。还可以在不同的比例下使用YOLOv3:较小比例的准确率较低,但速度比较大比例的要快,因此可以根据实际情况选择合适的折衷方案。

物体检测

在图像中对多个物体进行分类和定位的任务称为物体检测。一种通用的方法是采用经过训练的CNN来对单个物体进行分类和定位,然后将其在图像上滑动。

这项技术非常简单直观,但是它将多次检测同一物体,但位置略有不同。然后需要进行一些后期处理,以消除所有不必要的边界框。一种常见的方法称为非极大抑制。以下是操作方式:

  1. 首先需要在CNN中添加一个额外的客观分数(置信度)输出,以估计图像中确实存在花朵的可能性(或者可以添加“无花朵”类,但这通常不起好的作用)。它必须使用sigmoid激活函数,而且可以使用二元交叉熵损失对其进行训练。然后删除所有置信度得分低于某个阈值的边界框:这将删除所有实际上不包含花的边界框。
  2. 找到具有最大客观分数的边界框,并删除与其重叠很多的所有其他边界框(例如IoU大于60%)
  3. 重复第二步,直到没有更多的边界框可以删除

这种简单的物体检测方法效果很好,但是它需要多次运行CNN,因此速度很慢。幸运的是,有一种更快的方法可以在图像上滑动CNN:使用全卷积网络(FCN)。

分类与定位

定位图片中物体可以表示为回归任务:预测物体周围的边界框,一种常见的方法是预测物体中心的水平坐标和垂直坐标,还有其高度和宽度。这意味着有四个数字要预测。它不需要对模型进行太多修改,只需要添加四个具有单位的第二个密集输出层(通常在全局平均池化层之上),就可以使用MSE损失对其进行训练:

import tensorflow as tf
from tensorflow import keras
import tensorflow_datasets as tfds

(test_set, valid_set, train_set), info = tfds.load('tf_flowers', split=['train[:10%]', 'train[10%:25%]', 'train[25%:]'],
                                                   as_supervised=True, with_info=True)
dataset_size = info.splits['train'].num_examples
class_names = info.features['label'].names
n_classes = info.features['label'].num_classes


def preprocess(image, label):
    resize_image = tf.image.resize(image, [224, 224])
    final_image = keras.applications.xception.preprocess_input(resize_image)
    return final_image, label


batch_size = 16
train_set = train_set.shuffle(1000)
train_set = train_set.map(preprocess).batch(batch_size).prefetch(1)
valid_set = valid_set.map(preprocess).batch(batch_size).prefetch(1)
test_set = test_set.map(preprocess).batch(batch_size).prefetch(1)
base_model = keras.applications.xception.Xception(weights='imagenet', include_top=False)
avg = keras.layers.GlobalAveragePooling2D()(base_model.output)
class_output = keras.layers.Dense(n_classes, activation='softmax')(avg)
loc_output = keras.layers.Dense(4)(avg)
optimizer = keras.optimizers.SGD(lr=.2, momentum=.9, decay=.01)
model = keras.Model(input=base_model.input, outputs=[class_output, loc_output])
model.compile(loss=['sparse_categorical_entropy', 'mse'], loss_weights=[0.8, 0.2],
              optimizer=optimizer, metrics=['accuracy'])

花朵数据集在花朵周围没有边界框,因此需要自己添加。这通常是机器学习项目中最难、最昂贵的部分之一:获取标签。花时间寻找合适的工具是一个办法。要使用边界框标注图像,可能需要使用开源图像标记工具,例如VGG Image Annotator、LabelImg、OpenLabeler或ImageLab,或者使用商业工具(例如LabelBox或Supervisely)。如果需要标注大量图像,则可能还需要考虑众包平台,例如Amazon Mechanical Turk。但是建立众包平台,需要准备发送给工人的表格,对其进行监督并确保他们产生的边界框的质量是很好的,因此要保证这样做是值得的。如果只有几千张图像要标记,最好自己动手做。

如果已经获得了花朵数据集中每个图像的边界框(假设每个图像有一个边界框)。那么需要创建一个数据集,其数据项将是经过预处理的图像的批量处理,以及它们的类标签和边界框。每个数据集都应为一下形式的元组:(image,(class_labels,bounding_boxes))。

对边界框应该进行归一化,以便水平坐标和垂直坐标以及高度和宽度都在0到1的范围内。而且通常要预测高度和宽度的平方根,而不是直接的高度和宽度值:通过这种方式,对于大边框的10像素错误将不会像对小边框的10像素错误一样受惩罚

MSE通常作为成本函数可以很好的训练模型,但是评估模型对边界框的预测能力不是一个很好的指标。最常用的度量指标是“交并比”(Intersection over Union IoU):预测边界框和目标边界框之间的重叠面积除以它们的联合面积。在tf.keras中,它是由tf.keras.metrics.MeanIoU类实现的。