编者按:环信开源国内首本免费深度学习理论和实战专著《深度学习理论与实战:提高篇 》,内容涵盖听觉,视觉,语言和强化学习,七百多页详尽的理论和代码分析。本文节选自《深度学习理论与实战:提高篇 》一书,原文链接http://fancyerii.github.io/2019/03/14/dl-book/ 。作者李理,环信人工智能研发中心vp,有十多年自然语言处理和人工智能研发经验,主持研发过多款智能硬件的问答和对话系统,负责环信中文语义分析开放平台和环信智能机器人的设计与研发。

深度学习.jpg

3、创建模型

model = modellib.MaskRCNN(mode="training", config=config,
model_dir=MODEL_DIR)

因为我们的训练数据不多,因此使用预训练的模型进行Transfer Learning会效果更好。

# 默认使用coco模型来初始化
init_with = "coco" # imagenet, coco, or last

if init_with == "imagenet":
model.load_weights(model.get_imagenet_weights(), by_name=True)
elif init_with == "coco":
# 加载COCO模型的参数,去掉全连接层(mrcnn_bbox_fc),
# logits(mrcnn_class_logits)
# 输出的boudning box(mrcnn_bbox)和Mask(mrcnn_mask)
model.load_weights(COCO_MODEL_PATH, by_name=True,
exclude=["mrcnn_class_logits", "mrcnn_bbox_fc",
"mrcnn_bbox", "mrcnn_mask"])
elif init_with == "last":
# 加载我们最近训练的模型来初始化
model.load_weights(model.find_last(), by_name=True)

4、训练

训练分为两个阶段:

  • heads 只训练上面没有初始化的4层网络的参数,适合训练数据较少(比如本例子)的情况

  • all 训练所有的参数

我们这里值训练heads就够了。

model.train(dataset_train, dataset_val,
learning_rate=config.LEARNING_RATE,
epochs=1,
layers='heads')

保存模型参数:

# 手动保存参数,这通常是不需要的,
# 因为每次epoch介绍会自动保存,所以这里是注释掉的。
# model_path = os.path.join(MODEL_DIR, "mask_rcnn_shapes.h5")
# model.keras_model.save_weights(model_path)

5、检测

我们首先需要构造预测的Config并且加载模型参数。

class InferenceConfig(ShapesConfig):
GPU_COUNT = 1
IMAGES_PER_GPU = 1inference_config = InferenceConfig()# 重新构建用于inference的模型 model = modellib.MaskRCNN(mode="inference",
config=inference_config,
model_dir=MODEL_DIR)# 加载模型参数,可以手动指定也可以让它自己找最近的模型参数文件 # model_path = os.path.join(ROOT_DIR, ".h5 file name here")model_path = model.find_last()# 加载模型参数 print("Loading weights from ", model_path)model.load_weights(model_path, by_name=True)

我们随机寻找一个图片来检测:

# 随机选择验证集的一张图片。
image_id = random.choice(dataset_val.image_ids)
original_image, image_meta, gt_class_id, gt_bbox, gt_mask =\
modellib.load_image_gt(dataset_val, inference_config,
image_id, use_mini_mask=False)

log("original_image", original_image)
log("image_meta", image_meta)
log("gt_class_id", gt_class_id)
log("gt_bbox", gt_bbox)
log("gt_mask", gt_mask)

visualize.display_instances(original_image, gt_bbox, gt_mask, gt_class_id,
dataset_train.class_names, figsize=(8, 8))

上面的代码加载一张图片,结果如下图所示,它显示的是真正的(gold/ground-truth) Bounding box和Mask。

潮科技行业入门指南 | 深度学习理论与实战:提高篇(14)——Mask R-CNN代码简介

图:随机挑选的测试图片

接下来我们用模型来预测一下:

results = model.detect([original_image], verbose=1)

r = results[0]
visualize.display_instances(original_image, r['rois'], r['masks'], r['class_ids'],
dataset_val.class_names, r['scores'], ax=get_ax())

模型预测的结果如下图所示,可以对比看成模型预测的非常准确。

潮科技行业入门指南 | 深度学习理论与实战:提高篇(14)——Mask R-CNN代码简介

图:模型预测的结果

6、测试

前面我们只是测试了一个例子,我们需要更加全面的评测。

image_ids = np.random.choice(dataset_val.image_ids, 10)
APs = []
for image_id in image_ids:
# 加载图片和正确的Bounding box以及mask
image, image_meta, gt_class_id, gt_bbox, gt_mask =\
modellib.load_image_gt(dataset_val, inference_config,
image_id, use_mini_mask=False)
molded_images = np.expand_dims(modellib.mold_image(image, inference_config), 0)
# 进行检测
results = model.detect([image], verbose=0)
r = results[0]
# 计算AP
AP, precisions, recalls, overlaps =\
utils.compute_ap(gt_bbox, gt_class_id, gt_mask,
r["rois"], r["class_ids"], r["scores"], r['masks'])
APs.append(AP)

print("mAP: ", np.mean(APs))
# 输出0.95

inspect_data.ipynb

这个notebook演示了Mask R-CNN的数据预处理过程。这个notebook可以用COCO数据集或者我们之前介绍的shape数据集进行演示,为了避免下载大量的COCO数据集,我们这里用shape数据集。

1、选择数据集

config = ShapesConfig()

# 我们把下面的代码注释掉
# MS COCO Dataset
#import coco
#config = coco.CocoConfig()
#COCO_DIR = "path to COCO dataset" # TODO: enter value here

2、加载Dataset

# Load dataset
if config.NAME == 'shapes':
dataset = ShapesDataset()
dataset.load_shapes(500, config.IMAGE_SHAPE[0], config.IMAGE_SHAPE[1])
elif config.NAME == "coco":
dataset = coco.CocoDataset()
dataset.load_coco(COCO_DIR, "train")

# 使用dataset之前必须调用prepare()
dataset.prepare()

print("Image Count: {}".format(len(dataset.image_ids)))
print("Class Count: {}".format(dataset.num_classes))
for i, info in enumerate(dataset.class_info):
print("{:3}. {:50}".format(i, info['name']))

# 运行后的结果为:
Image Count: 500
Class Count: 4
0. BG
1. square
2. circle
3. triangle

3、显示样本

我们可以显示一些样本。

image_ids = np.random.choice(dataset.image_ids, 4)
for image_id in image_ids:
image = dataset.load_image(image_id)
mask, class_ids = dataset.load_mask(image_id)
visualize.display_top_masks(image, mask, class_ids, dataset.class_names)

结果如下图所示。

潮科技行业入门指南 | 深度学习理论与实战:提高篇(14)——Mask R-CNN代码简介

图:Mask 显示4个样本

4、Bounding Box

一般的数据集同时提供Bounding box和Mask,但是为了简单,我们只需要数据集提供Mask,我们可以通过Mask计算出Bounding box来。这样还有一个好处,那就是如果我们对目标物体进行旋转缩放等操作,计算Mask会比较容易,我们可以用新的Mask重新计算新的Bounding Box。否则我们就得对Bounding box进行相应的旋转缩放,这通常比较麻烦。

# 随机加载一个图片和它对应的mask.
image_id = random.choice(dataset.image_ids)
image = dataset.load_image(image_id)
mask, class_ids = dataset.load_mask(image_id)
# 计算Bounding box
bbox = utils.extract_bboxes(mask)

# 显示图片其它的统计信息
print("image_id ", image_id, dataset.image_reference(image_id))
log("image", image)
log("mask", mask)
log("class_ids", class_ids)
log("bbox", bbox)
# 显示图片
visualize.display_instances(image, bbox, mask, class_ids, dataset.class_names)

最重要的代码就是bbox = utils.extract_bboxes(mask)。最终得到的图片如下图所示。

潮科技行业入门指南 | 深度学习理论与实战:提高篇(14)——Mask R-CNN代码简介

图:显示样本

\subsubsection{缩放图片} 我们需要把图片都缩放成1024x1024(shape数据是生成的,都是固定大小,但实际数据集肯定不是这样)。我们会保持宽高比比最大的缩放成1024,比如原来是512x256,那么就会缩放成1024x512。然后我们把不足的维度两边补零,比如把1024x512padding成1024x1024,height维度上下各补256个0(256个0+512个真实数据+256个0)。

# 随机加载一个图片和它的mask
image_id = np.random.choice(dataset.image_ids, 1)[0]
image = dataset.load_image(image_id)
mask, class_ids = dataset.load_mask(image_id)
original_shape = image.shape
# 缩放图片,
image, window, scale, padding, _ = utils.resize_image(
image,
min_dim=config.IMAGE_MIN_DIM,
max_dim=config.IMAGE_MAX_DIM,
mode=config.IMAGE_RESIZE_MODE)

# 缩放图片后一定要缩放mask,否则就不一致了
mask = utils.resize_mask(mask, scale, padding)
# 计算Bounding box
bbox = utils.extract_bboxes(mask)

# 显示图片的其它统计信息
print("image_id: ", image_id, dataset.image_reference(image_id))
print("Original shape: ", original_shape)
log("image", image)
log("mask", mask)
log("class_ids", class_ids)
log("bbox", bbox)

# 显示图片
visualize.display_instances(image, bbox, mask, class_ids, dataset.class_names)

5、Mini Masks

一个图片可能有多个目标物体,每个物体的Mask是一个bool数组,大小是[width, height]。很显然,Bounding box之外的Mask肯定都是False,如果物体的比较小的话,这么存储是比较浪费空间的。因此我们有如下改进方法:

  • 我们只存储Bounding Box里的坐标对应的Mask值

  • 我们把Mask缩小(比如56x56),用的时候在放大回去,这对大的目标物体会有误差。但是由于我们的(人工)标注本来就没那么准。

为了可视化Mask缩放,我们来看几个例子。

image_id = np.random.choice(dataset.image_ids, 1)[0]
image, image_meta, class_ids, bbox, mask = modellib.load_image_gt(
dataset, config, image_id, use_mini_mask=False)

log("image", image)
log("image_meta", image_meta)
log("class_ids", class_ids)
log("bbox", bbox)
log("mask", mask)

display_images([image]+[mask[:,:,i] for i in range(min(mask.shape[-1], 7))])

# 输出
image shape: (128, 128, 3) min: 4.00000 max: 241.00000 uint8
image_meta shape: (16,) min: 0.00000 max: 409.00000 int64
class_ids shape: (2,) min: 1.00000 max: 3.00000 int32
bbox shape: (2, 4) min: 14.00000 max: 128.00000 int32
mask shape: (128, 128, 2) min: 0.00000 max: 1.00000 bool

如下图所示,这个图片有一个正方形和一个三角形。

潮科技行业入门指南 | 深度学习理论与实战:提高篇(14)——Mask R-CNN代码简介

图:显示样本

接下来我们对图片进行增强,比如镜像。

image, image_meta, class_ids, bbox, mask = modellib.load_image_gt(
dataset, config, image_id, augment=True, use_mini_mask=True)
log("mask", mask)
display_images([image]+[mask[:,:,i] for i in range(min(mask.shape[-1], 7))])

上面调用函数modellib.load_image_gt,参数use_mini_mask设置为True。效果如下图所示。首先做了镜像对称变化,另外我们可以看到mask的shape从(128, 128, 2)变成了(56, 56, 2),而且mask都是Bounding Box里的mask。

潮科技行业入门指南 | 深度学习理论与实战:提高篇(14)——Mask R-CNN代码简介

图:mini mask和增强