当前位置:   article > 正文

从零开始构建SSD网络实现目标识别项目_ssd项目

ssd项目

前言:

-- 由于此项目全过程过于繁杂,我前后做了三四个月,无法把所有内容融入这一篇文章之中,所以本文以逻辑串联为主,记录了我从零开始,构建vgg 300网络 -> 构建 SSD模型 -> 数据标注 -> 训练以及训练模型保存 -> 恢复模型进行预测 的全过程
-- 本文提供了一些核心代码,需要完整的代码,或者有任何问题的,可以私信我或者等候后续有时间再上传到github
-- 想读懂本文需要的基础:神经网络基础、CNN基础、VGG基础、语义分割基础、实例分割基础、YOLO原理等

一、SSD原理介绍

1、怎么理解SSD(此方法属于一步法):
--two-stage方法,如R-CNN系算法,即是两步法,第一步选取候选框,第二步对这些候选框分类或者回归
--one-stage方法,如Yolo和SSD,即是一步法,其主要思路是均匀地在图片的不同位置进行密集抽样,抽样时可以采用不同尺度和长宽比,然后利用CNN提取特征后直接进行分类与回归,整个过程只需要一步,所以其优势是速度快
--one-stage的一个重要缺点是训练比较困难,这主要是因为正样本与负样本(背景)极其不均衡(参见Focal Loss),导致模型准确度稍低。

2、SSD与YOLO
-- SSD好过YOLO(此论点参考文章:https://zhuanlan.zhihu.com/p/68151917)
-- Yolo和SSD,其主要思路是均匀地在图片的不同位置进行密集抽样,抽样时可以采用不同尺度和长宽比,然后利用CNN提取特征后直接进行分类与回归,整个过程只需要一步,所以其优势是速度快
-- SSD和YOLO一样都是直接使用CNN进行分类和回归,但其比YOLO的优点在于:
   -- 其使用用不同stride的卷积核生成了多尺度的feature map来进行检测,使用大尺度的feature map来检测小物体,使用小尺度的feature map来检测大物体
   -- 提取了feature map之后,SSD直接使用conv进行检测,而YOLO是进过特征提取之后,生成了一个特征集合的张量,使用全连接层进行检测
   -- 设置先验框为基准框,每个单元会设置多个先验框,其尺度和长宽比存在差异,在一定程度上减少训练难度
   -- SSD原理与实现(参考文章:https://zhuanlan.zhihu.com/p/68151917)

3、SSD架构图
架构图如下:


关于网络后面部分逐渐减小的feature map
-- 网络后面大小不一的卷积层,其实都是作为分类器
-- 大分类器检测小目标,小分类器检测大目标

二、数据标注部分

1、数据标注工具
-- labelimg
-- 使用参考文档:https://blog.csdn.net/wsp_1138886114/article/details/85017498

2、目前主流的标注方式是以xml格式存储
其中重要的信息有:
-- filename:图片的文件名
-- name:标注的物体名称
-- xmin、ymin、xmax、ymax:物体位置的左上角、右下角坐标

3、自动划分数据的脚本
此脚本功能为:shuffle+训练集划分+测试集划分+验证集划分
脚本内容如下:

  1. import os
  2. import random
  3. trainval_percent = 0.66
  4. train_percent = 0.5
  5. xmlfilepath = 'face_data\Annotations'
  6. txtsavepath = 'face_data\ImageSets\Main'
  7. total_xml = os.listdir(xmlfilepath)
  8. num=len(total_xml)
  9. list=range(num)
  10. tv=int(num*trainval_percent)
  11. tr=int(tv*train_percent)
  12. trainval= random.sample(list,tv)
  13. train=random.sample(trainval,tr)
  14. ftrainval = open('face_data/ImageSets/Main/trainval.txt', 'w')
  15. ftest = open('face_data/ImageSets/Main/test.txt', 'w')
  16. ftrain = open('face_data/ImageSets/Main/train.txt', 'w')
  17. fval = open('face_data/ImageSets/Main/val.txt', 'w')
  18. for i in list:
  19. name=total_xml[i][:-4]+'\n'
  20. if i in trainval:
  21. ftrainval.write(name)
  22. if i in train:
  23. ftrain.write(name)
  24. else:
  25. fval.write(name)
  26. else:
  27. ftest.write(name)
  28. ftrainval.close()
  29. ftrain.close()
  30. fval.close()
  31. ftest .close()

三、使用vgg网络作为主体结构搭建SSD模型

1、VGG网络原理

其最核心逻辑是使用卷积核分解的逻辑:
-- 用多个小卷积核分解一个大卷积核,对这部分内容的理解请参考我之前关于卷积核分解的文章;

采用连续的几个3x3的卷积核代替网络中的较大卷积核(11x11,7x7,5x5)。
-- 对于给定的感受野(与输出有关的输入图片的局部大小),采用堆积的小卷积核是优于采用大的卷积核,因为多层非线性层可以增加网络深度来保证学习更复杂的模式,而且代价还比较小(参数更少)。

2、构建vgg_300网络主体架构


3、使用vgg_300网络构建SSD模型

图中的VGG每一层主要实现:
-- 1、一张原始图片被resize到(224,224,3)。
-- 2、conv1两次[3,3]卷积网络,输出的特征层为64,输出为(224,224,64),再2X2最大池化,输出net为(112,112,64)。
-- 3、conv2两次[3,3]卷积网络,输出的特征层为128,输出net为(112,112,128),再2X2最大池化,输出net为(56,56,128)。
-- 4、conv3三次[3,3]卷积网络,输出的特征层为256,输出net为(56,56,256),再2X2最大池化,输出net为(28,28,256)。
-- 5、conv3三次[3,3]卷积网络,输出的特征层为256,输出net为(28,28,512),再2X2最大池化,输出net为(14,14,512)。
-- 6、conv3三次[3,3]卷积网络,输出的特征层为256,输出net为(14,14,512),再2X2最大池化,输出net为(7,7,512)。
-- 7、利用卷积的方式模拟全连接层,效果等同,输出net为(1,1,4096)。共进行两次。
-- 8、利用卷积的方式模拟全连接层,效果等同,输出net为(1,1,1000)。

最后输出的就是每个类的预测。

主干网络代码:

  1. import math
  2. from collections import namedtuple
  3. import numpy as np
  4. import tensorflow as tf
  5. import tf_extended as tfe
  6. from nets import custom_layers
  7. from nets import ssd_common
  8. slim = tf.contrib.slim
  9. # =========================================================================== #
  10. # SSD class definition.
  11. # =========================================================================== #
  12. SSDParams = namedtuple('SSDParameters', ['img_shape',
  13. 'num_classes',
  14. 'no_annotation_label',
  15. 'feat_layers',
  16. 'feat_shapes',
  17. 'anchor_size_bounds',
  18. 'anchor_sizes',
  19. 'anchor_ratios',
  20. 'anchor_steps',
  21. 'anchor_offset',
  22. 'normalizations',
  23. 'prior_scaling'
  24. ])
  25. class SSDNet(object):
  26. """Implementation of the SSD VGG-based 300 network.
  27. The default features layers with 300x300 image input are:
  28. conv4 ==> 38 x 38
  29. conv7 ==> 19 x 19
  30. conv8 ==> 10 x 10
  31. conv9 ==> 5 x 5
  32. conv10 ==> 3 x 3
  33. conv11 ==> 1 x 1
  34. The default image size used to train this network is 300x300.
  35. """
  36. default_params = SSDParams(
  37. img_shape=(300, 300),
  38. num_classes=21,
  39. # num_classes=2,
  40. no_annotation_label=21,
  41. # no_annotation_label=2,
  42. feat_layers=['block4', 'block7', 'block8', 'block9', 'block10', 'block11'],
  43. feat_shapes=[(38, 38), (19, 19), (10, 10), (5, 5), (3, 3), (1, 1)],
  44. anchor_size_bounds=[0.15, 0.90],
  45. # anchor_size_bounds=[0.20, 0.90],
  46. anchor_sizes=[(21., 45.),
  47. (45., 99.),
  48. (99., 153.),
  49. (153., 207.),
  50. (207., 261.),
  51. (261., 315.)],
  52. # anchor_sizes=[(30., 60.),
  53. # (60., 111.),
  54. # (111., 162.),
  55. # (162., 213.),
  56. # (213., 264.),
  57. # (264., 315.)],
  58. anchor_ratios=[[2, .5],
  59. [2, .5, 3, 1./3],
  60. [2, .5, 3, 1./3],
  61. [2, .5, 3, 1./3],
  62. [2, .5],
  63. [2, .5]],
  64. anchor_steps=[8, 16, 32, 64, 100, 300],
  65. anchor_offset=0.5,
  66. normalizations=[20, -1, -1, -1, -1, -1],
  67. prior_scaling=[0.1, 0.1, 0.2, 0.2]
  68. )
  69. def __init__(self, params=None):
  70. """Init the SSD net with some parameters. Use the default ones
  71. if none provided.
  72. """
  73. if isinstance(params, SSDParams):
  74. self.params = params
  75. else:
  76. self.params = SSDNet.default_params
  77. # ======================================================================= #
  78. def net(self, inputs,
  79. is_training=True,
  80. update_feat_shapes=True,
  81. dropout_keep_prob=0.5,
  82. prediction_fn=slim.softmax,
  83. reuse=None,
  84. scope='ssd_300_vgg'):
  85. """SSD network definition.
  86. """
  87. r = ssd_net(inputs,
  88. num_classes=self.params.num_classes,
  89. feat_layers=self.params.feat_layers,
  90. anchor_sizes=self.params.anchor_sizes,
  91. anchor_ratios=self.params.anchor_ratios,
  92. normalizations=self.params.normalizations,
  93. is_training=is_training,
  94. dropout_keep_prob=dropout_keep_prob,
  95. prediction_fn=prediction_fn,
  96. reuse=reuse,
  97. scope=scope)
  98. # Update feature shapes (try at least!)
  99. if update_feat_shapes:
  100. shapes = ssd_feat_shapes_from_net(r[0], self.params.feat_shapes)
  101. self.params = self.params._replace(feat_shapes=shapes)
  102. return r
  103. def arg_scope(self, weight_decay=0.0005, data_format='NHWC'):
  104. """Network arg_scope.
  105. """
  106. return ssd_arg_scope(weight_decay, data_format=data_format)
  107. def arg_scope_caffe(self, caffe_scope):
  108. """Caffe arg_scope used for weights importing.
  109. """
  110. return ssd_arg_scope_caffe(caffe_scope)
  111. # ======================================================================= #
  112. def update_feature_shapes(self, predictions):
  113. """Update feature shapes from predictions collection (Tensor or Numpy
  114. array).
  115. """
  116. shapes = ssd_feat_shapes_from_net(predictions, self.params.feat_shapes)
  117. self.params = self.params._replace(feat_shapes=shapes)
  118. def anchors(self, img_shape, dtype=np.float32):
  119. """Compute the default anchor boxes, given an image shape.
  120. """
  121. return ssd_anchors_all_layers(img_shape,
  122. self.params.feat_shapes,
  123. self.params.anchor_sizes,
  124. self.params.anchor_ratios,
  125. self.params.anchor_steps,
  126. self.params.anchor_offset,
  127. dtype)
  128. def bboxes_encode(self, labels, bboxes, anchors,
  129. scope=None):
  130. """Encode labels and bounding boxes.
  131. """
  132. return ssd_common.tf_ssd_bboxes_encode(
  133. labels, bboxes, anchors,
  134. self.params.num_classes,
  135. self.params.no_annotation_label,
  136. ignore_threshold=0.5,
  137. prior_scaling=self.params.prior_scaling,
  138. scope=scope)
  139. def bboxes_decode(self, feat_localizations, anchors,
  140. scope='ssd_bboxes_decode'):
  141. """Encode labels and bounding boxes.
  142. """
  143. return ssd_common.tf_ssd_bboxes_decode(
  144. feat_localizations, anchors,
  145. prior_scaling=self.params.prior_scaling,
  146. scope=scope)
  147. def detected_bboxes(self, predictions, localisations,
  148. select_threshold=None, nms_threshold=0.5,
  149. clipping_bbox=None, top_k=400, keep_top_k=200):
  150. """Get the detected bounding boxes from the SSD network output.
  151. """
  152. # Select top_k bboxes from predictions, and clip
  153. rscores, rbboxes = \
  154. ssd_common.tf_ssd_bboxes_select(predictions, localisations,
  155. select_threshold=select_threshold,
  156. num_classes=self.params.num_classes)
  157. rscores, rbboxes = \
  158. tfe.bboxes_sort(rscores, rbboxes, top_k=top_k)
  159. # Apply NMS algorithm.
  160. rscores, rbboxes = \
  161. tfe.bboxes_nms_batch(rscores, rbboxes,
  162. nms_threshold=nms_threshold,
  163. keep_top_k=keep_top_k)
  164. if clipping_bbox is not None:
  165. rbboxes = tfe.bboxes_clip(clipping_bbox, rbboxes)
  166. return rscores, rbboxes
  167. def losses(self, logits, localisations,
  168. gclasses, glocalisations, gscores,
  169. match_threshold=0.5,
  170. negative_ratio=3.,
  171. alpha=1.,
  172. label_smoothing=0.,
  173. scope='ssd_losses'):
  174. """Define the SSD network losses.
  175. """
  176. return ssd_losses(logits, localisations,
  177. gclasses, glocalisations, gscores,
  178. match_threshold=match_threshold,
  179. negative_ratio=negative_ratio,
  180. alpha=alpha,
  181. label_smoothing=label_smoothing,
  182. scope=scope)
  183. # =========================================================================== #
  184. # SSD tools...
  185. # =========================================================================== #
  186. def ssd_size_bounds_to_values(size_bounds,
  187. n_feat_layers,
  188. img_shape=(300, 300)):
  189. """Compute the reference sizes of the anchor boxes from relative bounds.
  190. The absolute values are measured in pixels, based on the network
  191. default size (300 pixels).
  192. This function follows the computation performed in the original
  193. implementation of SSD in Caffe.
  194. Return:
  195. list of list containing the absolute sizes at each scale. For each scale,
  196. the ratios only apply to the first value.
  197. """
  198. assert img_shape[0] == img_shape[1]
  199. img_size = img_shape[0]
  200. min_ratio = int(size_bounds[0] * 100)
  201. max_ratio = int(size_bounds[1] * 100)
  202. step = int(math.floor((max_ratio - min_ratio) / (n_feat_layers - 2)))
  203. # Start with the following smallest sizes.
  204. sizes = [[img_size * size_bounds[0] / 2, img_size * size_bounds[0]]]
  205. for ratio in range(min_ratio, max_ratio + 1, step):
  206. sizes.append((img_size * ratio / 100.,
  207. img_size * (ratio + step) / 100.))
  208. return sizes
  209. def ssd_feat_shapes_from_net(predictions, default_shapes=None):
  210. """Try to obtain the feature shapes from the prediction layers. The latter
  211. can be either a Tensor or Numpy ndarray.
  212. Return:
  213. list of feature shapes. Default values if predictions shape not fully
  214. determined.
  215. """
  216. feat_shapes = []
  217. for l in predictions:
  218. # Get the shape, from either a np array or a tensor.
  219. if isinstance(l, np.ndarray):
  220. shape = l.shape
  221. else:
  222. shape = l.get_shape().as_list()
  223. shape = shape[1:4]
  224. # Problem: undetermined shape...
  225. if None in shape:
  226. return default_shapes
  227. else:
  228. feat_shapes.append(shape)
  229. return feat_shapes
  230. def ssd_anchor_one_layer(img_shape,
  231. feat_shape,
  232. sizes,
  233. ratios,
  234. step,
  235. offset=0.5,
  236. dtype=np.float32):
  237. """Computer SSD default anchor boxes for one feature layer.
  238. Determine the relative position grid of the centers, and the relative
  239. width and height.
  240. Arguments:
  241. feat_shape: Feature shape, used for computing relative position grids;
  242. size: Absolute reference sizes;
  243. ratios: Ratios to use on these features;
  244. img_shape: Image shape, used for computing height, width relatively to the
  245. former;
  246. offset: Grid offset.
  247. Return:
  248. y, x, h, w: Relative x and y grids, and height and width.
  249. """
  250. # Compute the position grid: simple way.
  251. # y, x = np.mgrid[0:feat_shape[0], 0:feat_shape[1]]
  252. # y = (y.astype(dtype) + offset) / feat_shape[0]
  253. # x = (x.astype(dtype) + offset) / feat_shape[1]
  254. # Weird SSD-Caffe computation using steps values...
  255. y, x = np.mgrid[0:feat_shape[0], 0:feat_shape[1]]
  256. y = (y.astype(dtype) + offset) * step / img_shape[0]
  257. x = (x.astype(dtype) + offset) * step / img_shape[1]
  258. # Expand dims to support easy broadcasting.
  259. y = np.expand_dims(y, axis=-1)
  260. x = np.expand_dims(x, axis=-1)
  261. # Compute relative height and width.
  262. # Tries to follow the original implementation of SSD for the order.
  263. num_anchors = len(sizes) + len(ratios)
  264. h = np.zeros((num_anchors, ), dtype=dtype)
  265. w = np.zeros((num_anchors, ), dtype=dtype)
  266. # Add first anchor boxes with ratio=1.
  267. h[0] = sizes[0] / img_shape[0]
  268. w[0] = sizes[0] / img_shape[1]
  269. di = 1
  270. if len(sizes) > 1:
  271. h[1] = math.sqrt(sizes[0] * sizes[1]) / img_shape[0]
  272. w[1] = math.sqrt(sizes[0] * sizes[1]) / img_shape[1]
  273. di += 1
  274. for i, r in enumerate(ratios):
  275. h[i+di] = sizes[0] / img_shape[0] / math.sqrt(r)
  276. w[i+di] = sizes[0] / img_shape[1] * math.sqrt(r)
  277. return y, x, h, w
  278. def ssd_anchors_all_layers(img_shape,
  279. layers_shape,
  280. anchor_sizes,
  281. anchor_ratios,
  282. anchor_steps,
  283. offset=0.5,
  284. dtype=np.float32):
  285. """Compute anchor boxes for all feature layers.
  286. """
  287. layers_anchors = []
  288. for i, s in enumerate(layers_shape):
  289. anchor_bboxes = ssd_anchor_one_layer(img_shape, s,
  290. anchor_sizes[i],
  291. anchor_ratios[i],
  292. anchor_steps[i],
  293. offset=offset, dtype=dtype)
  294. layers_anchors.append(anchor_bboxes)
  295. return layers_anchors
  296. # =========================================================================== #
  297. # Functional definition of VGG-based SSD 300.
  298. # =========================================================================== #
  299. def tensor_shape(x, rank=3):
  300. """Returns the dimensions of a tensor.
  301. Args:
  302. image: A N-D Tensor of shape.
  303. Returns:
  304. A list of dimensions. Dimensions that are statically known are python
  305. integers,otherwise they are integer scalar tensors.
  306. """
  307. if x.get_shape().is_fully_defined():
  308. return x.get_shape().as_list()
  309. else:
  310. static_shape = x.get_shape().with_rank(rank).as_list()
  311. dynamic_shape = tf.unstack(tf.shape(x), rank)
  312. return [s if s is not None else d
  313. for s, d in zip(static_shape, dynamic_shape)]
  314. def ssd_multibox_layer(inputs,
  315. num_classes,
  316. sizes,
  317. ratios=[1],
  318. normalization=-1,
  319. bn_normalization=False):
  320. """Construct a multibox layer, return a class and localization predictions.
  321. """
  322. net = inputs
  323. if normalization > 0:
  324. net = custom_layers.l2_normalization(net, scaling=True)
  325. # Number of anchors.
  326. num_anchors = len(sizes) + len(ratios)
  327. # Location.
  328. num_loc_pred = num_anchors * 4
  329. loc_pred = slim.conv2d(net, num_loc_pred, [3, 3], activation_fn=None,
  330. scope='conv_loc')
  331. loc_pred = custom_layers.channel_to_last(loc_pred)
  332. loc_pred = tf.reshape(loc_pred,
  333. tensor_shape(loc_pred, 4)[:-1]+[num_anchors, 4])
  334. # Class prediction.
  335. num_cls_pred = num_anchors * num_classes
  336. cls_pred = slim.conv2d(net, num_cls_pred, [3, 3], activation_fn=None,
  337. scope='conv_cls')
  338. cls_pred = custom_layers.channel_to_last(cls_pred)
  339. cls_pred = tf.reshape(cls_pred,
  340. tensor_shape(cls_pred, 4)[:-1]+[num_anchors, num_classes])
  341. return cls_pred, loc_pred
  342. def ssd_net(inputs,
  343. num_classes=SSDNet.default_params.num_classes,
  344. feat_layers=SSDNet.default_params.feat_layers,
  345. anchor_sizes=SSDNet.default_params.anchor_sizes,
  346. anchor_ratios=SSDNet.default_params.anchor_ratios,
  347. normalizations=SSDNet.default_params.normalizations,
  348. is_training=True,
  349. dropout_keep_prob=0.5,
  350. prediction_fn=slim.softmax,
  351. reuse=None,
  352. scope='ssd_300_vgg'):
  353. """SSD net definition.
  354. """
  355. # if data_format == 'NCHW':
  356. # inputs = tf.transpose(inputs, perm=(0, 3, 1, 2))
  357. # End_points collect relevant activations for external use.
  358. end_points = {}
  359. with tf.variable_scope(scope, 'ssd_300_vgg', [inputs], reuse=reuse):
  360. # Original VGG-16 blocks.
  361. net = slim.repeat(inputs, 2, slim.conv2d, 64, [3, 3], scope='conv1')
  362. end_points['block1'] = net
  363. net = slim.max_pool2d(net, [2, 2], scope='pool1')
  364. # Block 2.
  365. net = slim.repeat(net, 2, slim.conv2d, 128, [3, 3], scope='conv2')
  366. end_points['block2'] = net
  367. net = slim.max_pool2d(net, [2, 2], scope='pool2')
  368. # Block 3.
  369. net = slim.repeat(net, 3, slim.conv2d, 256, [3, 3], scope='conv3')
  370. end_points['block3'] = net
  371. net = slim.max_pool2d(net, [2, 2], scope='pool3')
  372. # Block 4.
  373. net = slim.repeat(net, 3, slim.conv2d, 512, [3, 3], scope='conv4')
  374. end_points['block4'] = net
  375. net = slim.max_pool2d(net, [2, 2], scope='pool4')
  376. # Block 5.
  377. net = slim.repeat(net, 3, slim.conv2d, 512, [3, 3], scope='conv5')
  378. end_points['block5'] = net
  379. net = slim.max_pool2d(net, [3, 3], stride=1, scope='pool5')
  380. # Additional SSD blocks.
  381. # Block 6: let's dilate the hell out of it!
  382. net = slim.conv2d(net, 1024, [3, 3], rate=6, scope='conv6')
  383. end_points['block6'] = net
  384. net = tf.layers.dropout(net, rate=dropout_keep_prob, training=is_training)
  385. # Block 7: 1x1 conv. Because the fuck.
  386. net = slim.conv2d(net, 1024, [1, 1], scope='conv7')
  387. end_points['block7'] = net
  388. net = tf.layers.dropout(net, rate=dropout_keep_prob, training=is_training)
  389. # Block 8/9/10/11: 1x1 and 3x3 convolutions stride 2 (except lasts).
  390. end_point = 'block8'
  391. with tf.variable_scope(end_point):
  392. net = slim.conv2d(net, 256, [1, 1], scope='conv1x1')
  393. net = custom_layers.pad2d(net, pad=(1, 1))
  394. net = slim.conv2d(net, 512, [3, 3], stride=2, scope='conv3x3', padding='VALID')
  395. end_points[end_point] = net
  396. end_point = 'block9'
  397. with tf.variable_scope(end_point):
  398. net = slim.conv2d(net, 128, [1, 1], scope='conv1x1')
  399. net = custom_layers.pad2d(net, pad=(1, 1))
  400. net = slim.conv2d(net, 256, [3, 3], stride=2, scope='conv3x3', padding='VALID')
  401. end_points[end_point] = net
  402. end_point = 'block10'
  403. with tf.variable_scope(end_point):
  404. net = slim.conv2d(net, 128, [1, 1], scope='conv1x1')
  405. net = slim.conv2d(net, 256, [3, 3], scope='conv3x3', padding='VALID')
  406. end_points[end_point] = net
  407. end_point = 'block11'
  408. with tf.variable_scope(end_point):
  409. net = slim.conv2d(net, 128, [1, 1], scope='conv1x1')
  410. net = slim.conv2d(net, 256, [3, 3], scope='conv3x3', padding='VALID')
  411. end_points[end_point] = net
  412. # Prediction and localisations layers.
  413. predictions = []
  414. logits = []
  415. localisations = []
  416. for i, layer in enumerate(feat_layers):
  417. with tf.variable_scope(layer + '_box'):
  418. p, l = ssd_multibox_layer(end_points[layer],
  419. num_classes,
  420. anchor_sizes[i],
  421. anchor_ratios[i],
  422. normalizations[i])
  423. predictions.append(prediction_fn(p))
  424. logits.append(p)
  425. localisations.append(l)
  426. return predictions, localisations, logits, end_points
  427. ssd_net.default_image_size = 300
  428. def ssd_arg_scope(weight_decay=0.0005, data_format='NHWC'):
  429. """Defines the VGG arg scope.
  430. Args:
  431. weight_decay: The l2 regularization coefficient.
  432. Returns:
  433. An arg_scope.
  434. """
  435. with slim.arg_scope([slim.conv2d, slim.fully_connected],
  436. activation_fn=tf.nn.relu,
  437. weights_regularizer=slim.l2_regularizer(weight_decay),
  438. weights_initializer=tf.contrib.layers.xavier_initializer(),
  439. biases_initializer=tf.zeros_initializer()):
  440. with slim.arg_scope([slim.conv2d, slim.max_pool2d],
  441. padding='SAME',
  442. data_format=data_format):
  443. with slim.arg_scope([custom_layers.pad2d,
  444. custom_layers.l2_normalization,
  445. custom_layers.channel_to_last],
  446. data_format=data_format) as sc:
  447. return sc
  448. # =========================================================================== #
  449. # Caffe scope: importing weights at initialization.
  450. # =========================================================================== #
  451. def ssd_arg_scope_caffe(caffe_scope):
  452. """Caffe scope definition.
  453. Args:
  454. caffe_scope: Caffe scope object with loaded weights.
  455. Returns:
  456. An arg_scope.
  457. """
  458. # Default network arg scope.
  459. with slim.arg_scope([slim.conv2d],
  460. activation_fn=tf.nn.relu,
  461. weights_initializer=caffe_scope.conv_weights_init(),
  462. biases_initializer=caffe_scope.conv_biases_init()):
  463. with slim.arg_scope([slim.fully_connected],
  464. activation_fn=tf.nn.relu):
  465. with slim.arg_scope([custom_layers.l2_normalization],
  466. scale_initializer=caffe_scope.l2_norm_scale_init()):
  467. with slim.arg_scope([slim.conv2d, slim.max_pool2d],
  468. padding='SAME') as sc:
  469. return sc
  470. # =========================================================================== #
  471. # SSD loss function.
  472. # =========================================================================== #
  473. def ssd_losses(logits, localisations,
  474. gclasses, glocalisations, gscores,
  475. match_threshold=0.5,
  476. negative_ratio=3.,
  477. alpha=1.,
  478. label_smoothing=0.,
  479. device='/cpu:0',
  480. scope=None):
  481. with tf.name_scope(scope, 'ssd_losses'):
  482. lshape = tfe.get_shape(logits[0], 5)
  483. num_classes = lshape[-1]
  484. batch_size = lshape[0]
  485. # Flatten out all vectors!
  486. flogits = []
  487. fgclasses = []
  488. fgscores = []
  489. flocalisations = []
  490. fglocalisations = []
  491. for i in range(len(logits)):
  492. flogits.append(tf.reshape(logits[i], [-1, num_classes]))
  493. fgclasses.append(tf.reshape(gclasses[i], [-1]))
  494. fgscores.append(tf.reshape(gscores[i], [-1]))
  495. flocalisations.append(tf.reshape(localisations[i], [-1, 4]))
  496. fglocalisations.append(tf.reshape(glocalisations[i], [-1, 4]))
  497. # And concat the crap!
  498. logits = tf.concat(flogits, axis=0)
  499. gclasses = tf.concat(fgclasses, axis=0)
  500. gscores = tf.concat(fgscores, axis=0)
  501. localisations = tf.concat(flocalisations, axis=0)
  502. glocalisations = tf.concat(fglocalisations, axis=0)
  503. dtype = logits.dtype
  504. # Compute positive matching mask...
  505. pmask = gscores > match_threshold
  506. fpmask = tf.cast(pmask, dtype)
  507. n_positives = tf.reduce_sum(fpmask)
  508. # Hard negative mining...
  509. no_classes = tf.cast(pmask, tf.int32)
  510. predictions = slim.softmax(logits)
  511. nmask = tf.logical_and(tf.logical_not(pmask),
  512. gscores > -0.5)
  513. fnmask = tf.cast(nmask, dtype)
  514. nvalues = tf.where(nmask,
  515. predictions[:, 0],
  516. 1. - fnmask)
  517. nvalues_flat = tf.reshape(nvalues, [-1])
  518. # Number of negative entries to select.
  519. max_neg_entries = tf.cast(tf.reduce_sum(fnmask), tf.int32)
  520. n_neg = tf.cast(negative_ratio * n_positives, tf.int32) + batch_size
  521. n_neg = tf.minimum(n_neg, max_neg_entries)
  522. val, idxes = tf.nn.top_k(-nvalues_flat, k=n_neg)
  523. max_hard_pred = -val[-1]
  524. # Final negative mask.
  525. nmask = tf.logical_and(nmask, nvalues < max_hard_pred)
  526. fnmask = tf.cast(nmask, dtype)
  527. # Add cross-entropy loss.
  528. with tf.name_scope('cross_entropy_pos'):
  529. loss = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits,
  530. labels=gclasses)
  531. loss = tf.div(tf.reduce_sum(loss * fpmask), batch_size, name='value')
  532. tf.losses.add_loss(loss)
  533. with tf.name_scope('cross_entropy_neg'):
  534. loss = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits,
  535. labels=no_classes)
  536. loss = tf.div(tf.reduce_sum(loss * fnmask), batch_size, name='value')
  537. tf.losses.add_loss(loss)
  538. # Add localization loss: smooth L1, L2, ...
  539. with tf.name_scope('localization'):
  540. # Weights Tensor: positive mask + random negative.
  541. weights = tf.expand_dims(alpha * fpmask, axis=-1)
  542. loss = custom_layers.abs_smooth(localisations - glocalisations)
  543. loss = tf.div(tf.reduce_sum(loss * weights), batch_size, name='value')
  544. tf.losses.add_loss(loss)
  545. def ssd_losses_old(logits, localisations,
  546. gclasses, glocalisations, gscores,
  547. match_threshold=0.5,
  548. negative_ratio=3.,
  549. alpha=1.,
  550. label_smoothing=0.,
  551. device='/cpu:0',
  552. scope=None):
  553. """Loss functions for training the SSD 300 VGG network.
  554. This function defines the different loss components of the SSD, and
  555. adds them to the TF loss collection.
  556. Arguments:
  557. logits: (list of) predictions logits Tensors;
  558. localisations: (list of) localisations Tensors;
  559. gclasses: (list of) groundtruth labels Tensors;
  560. glocalisations: (list of) groundtruth localisations Tensors;
  561. gscores: (list of) groundtruth score Tensors;
  562. """
  563. with tf.device(device):
  564. with tf.name_scope(scope, 'ssd_losses'):
  565. l_cross_pos = []
  566. l_cross_neg = []
  567. l_loc = []
  568. for i in range(len(logits)):
  569. dtype = logits[i].dtype
  570. with tf.name_scope('block_%i' % i):
  571. # Sizing weight...
  572. wsize = tfe.get_shape(logits[i], rank=5)
  573. wsize = wsize[1] * wsize[2] * wsize[3]
  574. # Positive mask.
  575. pmask = gscores[i] > match_threshold
  576. fpmask = tf.cast(pmask, dtype)
  577. n_positives = tf.reduce_sum(fpmask)
  578. # Select some random negative entries.
  579. # n_entries = np.prod(gclasses[i].get_shape().as_list())
  580. # r_positive = n_positives / n_entries
  581. # r_negative = negative_ratio * n_positives / (n_entries - n_positives)
  582. # Negative mask.
  583. no_classes = tf.cast(pmask, tf.int32)
  584. predictions = slim.softmax(logits[i])
  585. nmask = tf.logical_and(tf.logical_not(pmask),
  586. gscores[i] > -0.5)
  587. fnmask = tf.cast(nmask, dtype)
  588. nvalues = tf.where(nmask,
  589. predictions[:, :, :, :, 0],
  590. 1. - fnmask)
  591. nvalues_flat = tf.reshape(nvalues, [-1])
  592. # Number of negative entries to select.
  593. n_neg = tf.cast(negative_ratio * n_positives, tf.int32)
  594. n_neg = tf.maximum(n_neg, tf.size(nvalues_flat) // 8)
  595. n_neg = tf.maximum(n_neg, tf.shape(nvalues)[0] * 4)
  596. max_neg_entries = 1 + tf.cast(tf.reduce_sum(fnmask), tf.int32)
  597. n_neg = tf.minimum(n_neg, max_neg_entries)
  598. val, idxes = tf.nn.top_k(-nvalues_flat, k=n_neg)
  599. max_hard_pred = -val[-1]
  600. # Final negative mask.
  601. nmask = tf.logical_and(nmask, nvalues < max_hard_pred)
  602. fnmask = tf.cast(nmask, dtype)
  603. # Add cross-entropy loss.
  604. with tf.name_scope('cross_entropy_pos'):
  605. fpmask = wsize * fpmask
  606. loss = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits[i],
  607. labels=gclasses[i])
  608. loss = tf.losses.compute_weighted_loss(loss, fpmask)
  609. l_cross_pos.append(loss)
  610. with tf.name_scope('cross_entropy_neg'):
  611. fnmask = wsize * fnmask
  612. loss = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits[i],
  613. labels=no_classes)
  614. loss = tf.losses.compute_weighted_loss(loss, fnmask)
  615. l_cross_neg.append(loss)
  616. # Add localization loss: smooth L1, L2, ...
  617. with tf.name_scope('localization'):
  618. # Weights Tensor: positive mask + random negative.
  619. weights = tf.expand_dims(alpha * fpmask, axis=-1)
  620. loss = custom_layers.abs_smooth(localisations[i] - glocalisations[i])
  621. loss = tf.losses.compute_weighted_loss(loss, weights)
  622. l_loc.append(loss)
  623. # Additional total losses...
  624. with tf.name_scope('total'):
  625. total_cross_pos = tf.add_n(l_cross_pos, 'cross_entropy_pos')
  626. total_cross_neg = tf.add_n(l_cross_neg, 'cross_entropy_neg')
  627. total_cross = tf.add(total_cross_pos, total_cross_neg, 'cross_entropy')
  628. total_loc = tf.add_n(l_loc, 'localization')
  629. # Add to EXTRA LOSSES TF.collection
  630. tf.add_to_collection('EXTRA_LOSSES', total_cross_pos)
  631. tf.add_to_collection('EXTRA_LOSSES', total_cross_neg)
  632. tf.add_to_collection('EXTRA_LOSSES', total_cross)
  633. tf.add_to_collection('EXTRA_LOSSES', total_loc)

四、将标注完的数据类型转换为网络可接受的格式

转换代码如下:

  1. import tensorflow as tf
  2. from datasets import pascalvoc_to_tfrecords
  3. FLAGS = tf.app.flags.FLAGS
  4. tf.app.flags.DEFINE_string(
  5. 'dataset_name', 'pascalvoc',
  6. 'The name of the dataset to convert.')
  7. tf.app.flags.DEFINE_string(
  8. 'dataset_dir', './face_data/',
  9. 'Directory where the original dataset is stored.')
  10. tf.app.flags.DEFINE_string(
  11. 'output_name', 'voc_2007_train',
  12. 'Basename used for TFRecords output files.')
  13. tf.app.flags.DEFINE_string(
  14. 'output_dir', './face_tfrecord/',
  15. 'Output directory where to store TFRecords files.')
  16. def main(_):
  17. if not FLAGS.dataset_dir:
  18. raise ValueError('You must supply the dataset directory with --dataset_dir')
  19. print('Dataset directory:', FLAGS.dataset_dir)
  20. print('Output directory:', FLAGS.output_dir)
  21. if FLAGS.dataset_name == 'pascalvoc':
  22. pascalvoc_to_tfrecords.run(FLAGS.dataset_dir, FLAGS.output_dir, FLAGS.output_name)
  23. else:
  24. raise ValueError('Dataset [%s] was not recognized.' % FLAGS.dataset_name)
  25. if __name__ == '__main__':
  26. tf.app.run()

五、训练程序设计

训练代码如下:

  1. import tensorflow as tf
  2. from tensorflow.python.ops import control_flow_ops
  3. from datasets import dataset_factory
  4. from deployment import model_deploy
  5. from nets import nets_factory
  6. from preprocessing import preprocessing_factory
  7. import tf_utils
  8. tf.reset_default_graph()
  9. import os
  10. # os.environ['CUDA_VISIBLE_DEVICES']='0'
  11. slim = tf.contrib.slim
  12. DATA_FORMAT = 'NHWC'
  13. # DATA_FORMAT = 'NCHW'
  14. # =========================================================================== #
  15. # SSD Network flags.
  16. # =========================================================================== #
  17. tf.app.flags.DEFINE_float(
  18. 'loss_alpha', 1., 'Alpha parameter in the loss function.')
  19. tf.app.flags.DEFINE_float(
  20. 'negative_ratio', 3., 'Negative ratio in the loss function.')
  21. tf.app.flags.DEFINE_float(
  22. 'match_threshold', 0.5, 'Matching threshold in the loss function.')
  23. # =========================================================================== #
  24. # General Flags.
  25. # =========================================================================== #
  26. tf.app.flags.DEFINE_string(
  27. 'train_dir', './wuyang_model/',
  28. 'Directory where checkpoints and event logs are written to.')
  29. tf.app.flags.DEFINE_integer('num_clones', 1,
  30. 'Number of model clones to deploy.')
  31. tf.app.flags.DEFINE_boolean('clone_on_cpu', False,#True
  32. 'Use CPUs to deploy clones.')
  33. tf.app.flags.DEFINE_integer(
  34. 'num_readers', 4,
  35. 'The number of parallel readers that read data from the dataset.')
  36. tf.app.flags.DEFINE_integer(
  37. 'num_preprocessing_threads', 4,
  38. 'The number of threads used to create the batches.')
  39. tf.app.flags.DEFINE_integer(
  40. 'log_every_n_steps', 1,#10
  41. 'The frequency with which logs are print.')
  42. tf.app.flags.DEFINE_integer(
  43. 'save_summaries_secs', 600,
  44. 'The frequency with which summaries are saved, in seconds.')
  45. tf.app.flags.DEFINE_integer(
  46. 'save_interval_secs', 600,
  47. 'The frequency with which the model is saved, in seconds.')
  48. tf.app.flags.DEFINE_float(
  49. 'gpu_memory_fraction', 0.8, 'GPU memory fraction to use.')
  50. # =========================================================================== #
  51. # Optimization Flags.
  52. # =========================================================================== #
  53. tf.app.flags.DEFINE_float(
  54. 'weight_decay', 0.4, 'The weight decay on the model weights.')#0.00004
  55. tf.app.flags.DEFINE_string(
  56. 'optimizer', 'rmsprop',
  57. 'The name of the optimizer, one of "adadelta", "adagrad", "adam",'
  58. '"ftrl", "momentum", "sgd" or "rmsprop".')
  59. tf.app.flags.DEFINE_float(
  60. 'adadelta_rho', 0.95,
  61. 'The decay rate for adadelta.')
  62. tf.app.flags.DEFINE_float(
  63. 'adagrad_initial_accumulator_value', 0.01,
  64. 'Starting value for the AdaGrad accumulators.')
  65. tf.app.flags.DEFINE_float(
  66. 'adam_beta1', 0.9,
  67. 'The exponential decay rate for the 1st moment estimates.')
  68. tf.app.flags.DEFINE_float(
  69. 'adam_beta2', 0.999,
  70. 'The exponential decay rate for the 2nd moment estimates.')
  71. tf.app.flags.DEFINE_float('opt_epsilon', 1.0, 'Epsilon term for the optimizer.')
  72. tf.app.flags.DEFINE_float('ftrl_learning_rate_power', -0.5,
  73. 'The learning rate power.')
  74. tf.app.flags.DEFINE_float(
  75. 'ftrl_initial_accumulator_value', 0.1,
  76. 'Starting value for the FTRL accumulators.')
  77. tf.app.flags.DEFINE_float(
  78. 'ftrl_l1', 0.0, 'The FTRL l1 regularization strength.')
  79. tf.app.flags.DEFINE_float(
  80. 'ftrl_l2', 0.0, 'The FTRL l2 regularization strength.')
  81. tf.app.flags.DEFINE_float(
  82. 'momentum', 0.9,
  83. 'The momentum for the MomentumOptimizer and RMSPropOptimizer.')
  84. tf.app.flags.DEFINE_float('rmsprop_momentum', 0.9, 'Momentum.')
  85. tf.app.flags.DEFINE_float('rmsprop_decay', 0.9, 'Decay term for RMSProp.')
  86. # =========================================================================== #
  87. # Learning Rate Flags.
  88. # =========================================================================== #
  89. tf.app.flags.DEFINE_string(
  90. 'learning_rate_decay_type',
  91. 'exponential',
  92. 'Specifies how the learning rate is decayed. One of "fixed", "exponential",'
  93. ' or "polynomial"')
  94. tf.app.flags.DEFINE_float('learning_rate', 0.0001, 'Initial learning rate.')#0.01
  95. tf.app.flags.DEFINE_float(
  96. 'end_learning_rate', 0.01,# 0.0001
  97. 'The minimal end learning rate used by a polynomial decay learning rate.')
  98. tf.app.flags.DEFINE_float(
  99. 'label_smoothing', 0.0, 'The amount of label smoothing.')
  100. tf.app.flags.DEFINE_float(
  101. 'learning_rate_decay_factor', 0.94, 'Learning rate decay factor.')
  102. tf.app.flags.DEFINE_float(
  103. 'num_epochs_per_decay', 2.0,
  104. 'Number of epochs after which learning rate decays.')
  105. tf.app.flags.DEFINE_float(
  106. 'moving_average_decay', None,
  107. 'The decay to use for the moving average.'
  108. 'If left as None, then moving averages are not used.')
  109. # =========================================================================== #
  110. # Dataset Flags.
  111. # =========================================================================== #
  112. tf.app.flags.DEFINE_string(
  113. 'dataset_name', 'pascalvoc_2007', 'The name of the dataset to load.')
  114. tf.app.flags.DEFINE_integer(
  115. 'num_classes', 2, 'Number of classes to use in the dataset.')
  116. tf.app.flags.DEFINE_string(
  117. 'dataset_split_name', 'train', 'The name of the train/test split.')
  118. tf.app.flags.DEFINE_string(
  119. 'dataset_dir', './face_tfrecord/', 'The directory where the dataset files are stored.')
  120. tf.app.flags.DEFINE_integer(
  121. 'labels_offset', 0,
  122. 'An offset for the labels in the dataset. This flag is primarily used to '
  123. 'evaluate the VGG and ResNet architectures which do not use a background '
  124. 'class for the ImageNet dataset.')
  125. tf.app.flags.DEFINE_string(
  126. 'model_name', 'ssd_300_vgg', 'The name of the architecture to train.')
  127. tf.app.flags.DEFINE_string(
  128. 'preprocessing_name', None, 'The name of the preprocessing to use. If left '
  129. 'as `None`, then the model_name flag is used.')
  130. tf.app.flags.DEFINE_integer(
  131. 'batch_size', 4, 'The number of samples in each batch.')#32
  132. tf.app.flags.DEFINE_integer(
  133. 'train_image_size', None, 'Train image size')
  134. tf.app.flags.DEFINE_integer('max_number_of_steps', 20,#10
  135. 'The maximum number of training steps.')
  136. # =========================================================================== #
  137. # Fine-Tuning Flags.
  138. # =========================================================================== #
  139. tf.app.flags.DEFINE_string(
  140. 'checkpoint_path', './预训练模型/VGG_VOC0712_SSD_300x300_ft_iter_120000.ckpt',
  141. 'The path to a checkpoint from which to fine-tune.')
  142. tf.app.flags.DEFINE_string(
  143. 'checkpoint_model_scope', None,
  144. 'Model scope in the checkpoint. None if the same as the trained model.')
  145. tf.app.flags.DEFINE_string(
  146. 'checkpoint_exclude_scopes', None,
  147. 'Comma-separated list of scopes of variables to exclude when restoring '
  148. 'from a checkpoint.')
  149. tf.app.flags.DEFINE_string(
  150. 'trainable_scopes', None,
  151. 'Comma-separated list of scopes to filter the set of variables to train.'
  152. 'By default, None would train all the variables.')
  153. tf.app.flags.DEFINE_boolean(
  154. 'ignore_missing_vars', False,
  155. 'When restoring a checkpoint would ignore missing variables.')
  156. FLAGS = tf.app.flags.FLAGS
  157. # =========================================================================== #
  158. # Main training routine.
  159. # =========================================================================== #
  160. def main(_):
  161. if not FLAGS.dataset_dir:
  162. raise ValueError('You must supply the dataset directory with --dataset_dir')
  163. tf.logging.set_verbosity(tf.logging.DEBUG)
  164. with tf.Graph().as_default():
  165. # Config model_deploy. Keep TF Slim Models structure.
  166. # Useful if want to need multiple GPUs and/or servers in the future.
  167. deploy_config = model_deploy.DeploymentConfig(
  168. num_clones=FLAGS.num_clones,
  169. clone_on_cpu=FLAGS.clone_on_cpu,
  170. replica_id=0,
  171. num_replicas=1,
  172. num_ps_tasks=0)
  173. # Create global_step.
  174. with tf.device(deploy_config.variables_device()):
  175. global_step = slim.create_global_step()
  176. # Select the dataset.
  177. dataset = dataset_factory.get_dataset(
  178. FLAGS.dataset_name, FLAGS.dataset_split_name, FLAGS.dataset_dir)
  179. # Get the SSD network and its anchors.
  180. ssd_class = nets_factory.get_network(FLAGS.model_name)
  181. ssd_params = ssd_class.default_params._replace(num_classes=FLAGS.num_classes)
  182. ssd_net = ssd_class(ssd_params)
  183. ssd_shape = ssd_net.params.img_shape
  184. ssd_anchors = ssd_net.anchors(ssd_shape)
  185. # Select the preprocessing function.
  186. preprocessing_name = FLAGS.preprocessing_name or FLAGS.model_name
  187. image_preprocessing_fn = preprocessing_factory.get_preprocessing(
  188. preprocessing_name, is_training=True)
  189. tf_utils.print_configuration(FLAGS.__flags, ssd_params,
  190. dataset.data_sources, FLAGS.train_dir)
  191. # =================================================================== #
  192. # Create a dataset provider and batches.
  193. # =================================================================== #
  194. with tf.device(deploy_config.inputs_device()):
  195. with tf.name_scope(FLAGS.dataset_name + '_data_provider'):
  196. provider = slim.dataset_data_provider.DatasetDataProvider(
  197. dataset,
  198. num_readers=FLAGS.num_readers,
  199. common_queue_capacity=20 * FLAGS.batch_size,
  200. common_queue_min=10 * FLAGS.batch_size,
  201. shuffle=True)
  202. # Get for SSD network: image, labels, bboxes.
  203. [image, shape, glabels, gbboxes] = provider.get(['image', 'shape',
  204. 'object/label',
  205. 'object/bbox'])
  206. # Pre-processing image, labels and bboxes.
  207. image, glabels, gbboxes = \
  208. image_preprocessing_fn(image, glabels, gbboxes,
  209. out_shape=ssd_shape,
  210. data_format=DATA_FORMAT)
  211. # Encode groundtruth labels and bboxes.
  212. gclasses, glocalisations, gscores = \
  213. ssd_net.bboxes_encode(glabels, gbboxes, ssd_anchors)
  214. batch_shape = [1] + [len(ssd_anchors)] * 3
  215. # Training batches and queue.
  216. r = tf.train.batch(
  217. tf_utils.reshape_list([image, gclasses, glocalisations, gscores]),
  218. batch_size=FLAGS.batch_size,
  219. num_threads=FLAGS.num_preprocessing_threads,
  220. capacity=5 * FLAGS.batch_size)
  221. b_image, b_gclasses, b_glocalisations, b_gscores = \
  222. tf_utils.reshape_list(r, batch_shape)
  223. # Intermediate queueing: unique batch computation pipeline for all
  224. # GPUs running the training.
  225. batch_queue = slim.prefetch_queue.prefetch_queue(
  226. tf_utils.reshape_list([b_image, b_gclasses, b_glocalisations, b_gscores]),
  227. capacity=2 * deploy_config.num_clones)
  228. # =================================================================== #
  229. # Define the model running on every GPU.
  230. # =================================================================== #
  231. def clone_fn(batch_queue):
  232. """Allows data parallelism by creating multiple
  233. clones of network_fn."""
  234. # Dequeue batch.
  235. b_image, b_gclasses, b_glocalisations, b_gscores = \
  236. tf_utils.reshape_list(batch_queue.dequeue(), batch_shape)
  237. # Construct SSD network.
  238. arg_scope = ssd_net.arg_scope(weight_decay=FLAGS.weight_decay,
  239. data_format=DATA_FORMAT)
  240. with slim.arg_scope(arg_scope):
  241. predictions, localisations, logits, end_points = \
  242. ssd_net.net(b_image, is_training=True)
  243. # Add loss function.
  244. ssd_net.losses(logits, localisations,
  245. b_gclasses, b_glocalisations, b_gscores,
  246. match_threshold=FLAGS.match_threshold,
  247. negative_ratio=FLAGS.negative_ratio,
  248. alpha=FLAGS.loss_alpha,
  249. label_smoothing=FLAGS.label_smoothing)
  250. return end_points
  251. # Gather initial summaries.
  252. summaries = set(tf.get_collection(tf.GraphKeys.SUMMARIES))
  253. # =================================================================== #
  254. # Add summaries from first clone.
  255. # =================================================================== #
  256. clones = model_deploy.create_clones(deploy_config, clone_fn, [batch_queue])
  257. first_clone_scope = deploy_config.clone_scope(0)
  258. # Gather update_ops from the first clone. These contain, for example,
  259. # the updates for the batch_norm variables created by network_fn.
  260. update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS, first_clone_scope)
  261. # Add summaries for end_points.
  262. end_points = clones[0].outputs
  263. for end_point in end_points:
  264. x = end_points[end_point]
  265. summaries.add(tf.summary.histogram('activations/' + end_point, x))
  266. summaries.add(tf.summary.scalar('sparsity/' + end_point,
  267. tf.nn.zero_fraction(x)))
  268. # Add summaries for losses and extra losses.
  269. for loss in tf.get_collection(tf.GraphKeys.LOSSES, first_clone_scope):
  270. summaries.add(tf.summary.scalar(loss.op.name, loss))
  271. for loss in tf.get_collection('EXTRA_LOSSES', first_clone_scope):
  272. summaries.add(tf.summary.scalar(loss.op.name, loss))
  273. # Add summaries for variables.
  274. for variable in slim.get_model_variables():
  275. summaries.add(tf.summary.histogram(variable.op.name, variable))
  276. # =================================================================== #
  277. # Configure the moving averages.
  278. # =================================================================== #
  279. if FLAGS.moving_average_decay:
  280. moving_average_variables = slim.get_model_variables()
  281. variable_averages = tf.train.ExponentialMovingAverage(
  282. FLAGS.moving_average_decay, global_step)
  283. else:
  284. moving_average_variables, variable_averages = None, None
  285. # =================================================================== #
  286. # Configure the optimization procedure.
  287. # =================================================================== #
  288. with tf.device(deploy_config.optimizer_device()):
  289. learning_rate = tf_utils.configure_learning_rate(FLAGS,
  290. dataset.num_samples,
  291. global_step)
  292. optimizer = tf_utils.configure_optimizer(FLAGS, learning_rate)
  293. summaries.add(tf.summary.scalar('learning_rate', learning_rate))
  294. if FLAGS.moving_average_decay:
  295. # Update ops executed locally by trainer.
  296. update_ops.append(variable_averages.apply(moving_average_variables))
  297. # Variables to train.
  298. variables_to_train = tf_utils.get_variables_to_train(FLAGS)
  299. # and returns a train_tensor and summary_op
  300. total_loss, clones_gradients = model_deploy.optimize_clones(
  301. clones,
  302. optimizer,
  303. var_list=variables_to_train)
  304. # Add total_loss to summary.
  305. summaries.add(tf.summary.scalar('total_loss', total_loss))
  306. # Create gradient updates.
  307. grad_updates = optimizer.apply_gradients(clones_gradients,
  308. global_step=global_step)
  309. update_ops.append(grad_updates)
  310. update_op = tf.group(*update_ops)
  311. train_tensor = control_flow_ops.with_dependencies([update_op], total_loss,
  312. name='train_op')
  313. # Add the summaries from the first clone. These contain the summaries
  314. summaries |= set(tf.get_collection(tf.GraphKeys.SUMMARIES,
  315. first_clone_scope))
  316. # Merge all summaries together.
  317. summary_op = tf.summary.merge(list(summaries), name='summary_op')
  318. # =================================================================== #
  319. # Kicks off the training.
  320. # =================================================================== #
  321. gpu_options = tf.GPUOptions(per_process_gpu_memory_fraction=FLAGS.gpu_memory_fraction)
  322. config = tf.ConfigProto(log_device_placement=False,
  323. gpu_options=gpu_options)
  324. saver = tf.train.Saver(max_to_keep=5,
  325. keep_checkpoint_every_n_hours=1.0,
  326. write_version=2,
  327. pad_step_number=False)
  328. slim.learning.train(
  329. train_tensor,
  330. logdir=FLAGS.train_dir,
  331. master='',
  332. is_chief=True,
  333. # init_fn=tf_utils.get_init_fn(FLAGS),
  334. summary_op=summary_op,
  335. number_of_steps=FLAGS.max_number_of_steps,
  336. log_every_n_steps=FLAGS.log_every_n_steps,
  337. save_summaries_secs=FLAGS.save_summaries_secs,
  338. saver=saver
  339. ,save_interval_secs=FLAGS.save_interval_secs
  340. ,session_config=config
  341. ,sync_optimizer=None
  342. )
  343. if __name__ == '__main__':
  344. tf.app.run()

六、训练过程报错处理

1、训练进度停顿
-- 检查转换后的tfrecord数据,很大可能是数据格式转换失败

2、梯度爆炸
-- 检查损失函数逻辑,有可能是损失函数中符号错误
-- 样本维度和网络输入维度不一致等问题

3、梯度消失
-- 梯度消失的原因很多,需要根据具体问题具体定位
-- 比如可以尝试在网络的最开始添加批量归一化层

4、损失不收敛
-- 这个一般是学习率和优化器的配合使用不熟练导致的
-- 一般刚开始可以设置一个较大的学习率,然后缓慢减小
-- 优化器使用:Adam+随机梯度下降 ,使用Adam粗调,使用随机梯度下降进行精调

5、训练缓慢
-- 修改训练批次数量,一般批次数量越大,训练越缓慢
-- 也可以无脑添加drop out层

七、验证过程设计

验证代码:

  1. import os
  2. import math
  3. import random
  4. import numpy as np
  5. import tensorflow as tf
  6. import cv2
  7. slim = tf.contrib.slim
  8. # %matplotlib inline
  9. import matplotlib.pyplot as plt
  10. import matplotlib.image as mpimg
  11. import sys
  12. sys.path.append('../')
  13. from nets import ssd_vgg_300, ssd_common, np_methods
  14. from preprocessing import ssd_vgg_preprocessing
  15. from notebooks import visualization
  16. isess = sess = tf.Session()
  17. # Input placeholder.
  18. net_shape = (300, 300)
  19. data_format = 'NHWC'
  20. img_input = tf.placeholder(tf.uint8, shape=(None, None, 3))
  21. # Evaluation pre-processing: resize to SSD net shape.
  22. image_pre, labels_pre, bboxes_pre, bbox_img = ssd_vgg_preprocessing.preprocess_for_eval(
  23. img_input, None, None, net_shape, data_format, resize=ssd_vgg_preprocessing.Resize.WARP_RESIZE)
  24. image_4d = tf.expand_dims(image_pre, 0)
  25. print("part_1 sucessed")
  26. # Define the SSD model.
  27. reuse = True if 'ssd_net' in locals() else None
  28. ssd_net = ssd_vgg_300.SSDNet()
  29. with slim.arg_scope(ssd_net.arg_scope(data_format=data_format)):
  30. predictions, localisations, _, _ = ssd_net.net(image_4d, is_training=False, reuse=reuse)
  31. print("part_2 sucessed")
  32. # Restore SSD model.
  33. # ckpt_filename = 'F:/时间简史/人脸识别项目_wuyang/SSD源码/SSD-Tensorflow-master/预训练模型'
  34. # ckpt_filename = './wuyang_model/model.ckpt-20'
  35. ckpt_filename = './预训练模型/VGG_VOC0712_SSD_300x300_ft_iter_120000.ckpt'
  36. print("part_3 sucessed")
  37. isess.run(tf.global_variables_initializer())
  38. print("part_4 sucessed")
  39. saver = tf.train.Saver()
  40. saver.restore(isess, ckpt_filename)
  41. print("part_5 sucessed")
  42. # SSD default anchor boxes.
  43. ssd_anchors = ssd_net.anchors(net_shape)
  44. print("all part sucessed")
  45. # Main image processing routine.
  46. def process_image(img, select_threshold=0.5, nms_threshold=.45, net_shape=(300, 300)):
  47. # Run SSD network.
  48. rimg, rpredictions, rlocalisations, rbbox_img = isess.run([image_4d, predictions, localisations, bbox_img],
  49. feed_dict={img_input: img})
  50. # Get classes and bboxes from the net outputs.
  51. rclasses, rscores, rbboxes = np_methods.ssd_bboxes_select(
  52. rpredictions, rlocalisations, ssd_anchors,
  53. select_threshold=select_threshold, img_shape=net_shape, num_classes=21, decode=True)
  54. rbboxes = np_methods.bboxes_clip(rbbox_img, rbboxes)
  55. rclasses, rscores, rbboxes = np_methods.bboxes_sort(rclasses, rscores, rbboxes, top_k=400)
  56. rclasses, rscores, rbboxes = np_methods.bboxes_nms(rclasses, rscores, rbboxes, nms_threshold=nms_threshold)
  57. # Resize bboxes to original image shape. Note: useless for Resize.WARP!
  58. rbboxes = np_methods.bboxes_resize(rbbox_img, rbboxes)
  59. return rclasses, rscores, rbboxes
  60. # Test on some demo image and visualize output.
  61. path = 'F:/test-image/'
  62. image_names = sorted(os.listdir(path))
  63. img = mpimg.imread(path + image_names[0])
  64. rclasses, rscores, rbboxes = process_image(img)
  65. # visualization.bboxes_draw_on_img(img, rclasses, rscores, rbboxes, visualization.colors_plasma)
  66. visualization.plt_bboxes(img, rclasses, rscores, rbboxes)

训练完成之后:
-- 恢复保存的预训练模型
-- 使用预训练模型,对相关照片进行预测

测试过程可能报错:
-- 模型恢复报维度不匹配:
    -- 根据实际维度,修改vgg网络输入维度
    
-- 读取checkpoint路径错误:
    -- 要选取到check实际存在的文件夹
    -- 然后还要定位到具体的ckpt文件
    -- 例如我的路径为:./wuyang_model/model.ckpt-20000
    
八、最终结果

各数值对应类别如下:
-- 0:背景板
-- 1,2:飞机
-- 3:鸟
-- 4:船
-- 5:瓶子
-- 6:公交
-- 7:汽车
-- 8:猫
-- 9:椅子
-- 10:牛
-- 11:桌子
-- 12:熊猫
-- 13:马
-- 14:摩托
-- 15:人
-- 16:茶几
-- 17:羊
-- 18:沙发
-- 19:火车
-- 20:液晶屏幕

用训练完的模型,进行目标识别测试。
-- 由于没有来得及设计爬虫程序,所有样本都是手动抓取手动标注,所以有些类别学习不充分
-- 下面展示几个分类效果好的例子

对电脑的预测:

对熊猫的预测:

对飞机的预测:

以及对大美女、女神王祖贤的预测:

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/291161
推荐阅读
相关标签
  

闽ICP备14008679号