resnet_v2_test.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. # Copyright 2016 The TensorFlow Authors. All Rights Reserved.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. # ==============================================================================
  15. """Tests for slim.nets.resnet_v2."""
  16. from __future__ import absolute_import
  17. from __future__ import division
  18. from __future__ import print_function
  19. import numpy as np
  20. import tensorflow as tf
  21. from nets import resnet_utils
  22. from nets import resnet_v2
  23. slim = tf.contrib.slim
  24. def create_test_input(batch_size, height, width, channels):
  25. """Create test input tensor.
  26. Args:
  27. batch_size: The number of images per batch or `None` if unknown.
  28. height: The height of each image or `None` if unknown.
  29. width: The width of each image or `None` if unknown.
  30. channels: The number of channels per image or `None` if unknown.
  31. Returns:
  32. Either a placeholder `Tensor` of dimension
  33. [batch_size, height, width, channels] if any of the inputs are `None` or a
  34. constant `Tensor` with the mesh grid values along the spatial dimensions.
  35. """
  36. if None in [batch_size, height, width, channels]:
  37. return tf.placeholder(tf.float32, (batch_size, height, width, channels))
  38. else:
  39. return tf.to_float(
  40. np.tile(
  41. np.reshape(
  42. np.reshape(np.arange(height), [height, 1]) +
  43. np.reshape(np.arange(width), [1, width]),
  44. [1, height, width, 1]),
  45. [batch_size, 1, 1, channels]))
  46. class ResnetUtilsTest(tf.test.TestCase):
  47. def testSubsampleThreeByThree(self):
  48. x = tf.reshape(tf.to_float(tf.range(9)), [1, 3, 3, 1])
  49. x = resnet_utils.subsample(x, 2)
  50. expected = tf.reshape(tf.constant([0, 2, 6, 8]), [1, 2, 2, 1])
  51. with self.test_session():
  52. self.assertAllClose(x.eval(), expected.eval())
  53. def testSubsampleFourByFour(self):
  54. x = tf.reshape(tf.to_float(tf.range(16)), [1, 4, 4, 1])
  55. x = resnet_utils.subsample(x, 2)
  56. expected = tf.reshape(tf.constant([0, 2, 8, 10]), [1, 2, 2, 1])
  57. with self.test_session():
  58. self.assertAllClose(x.eval(), expected.eval())
  59. def testConv2DSameEven(self):
  60. n, n2 = 4, 2
  61. # Input image.
  62. x = create_test_input(1, n, n, 1)
  63. # Convolution kernel.
  64. w = create_test_input(1, 3, 3, 1)
  65. w = tf.reshape(w, [3, 3, 1, 1])
  66. tf.get_variable('Conv/weights', initializer=w)
  67. tf.get_variable('Conv/biases', initializer=tf.zeros([1]))
  68. tf.get_variable_scope().reuse_variables()
  69. y1 = slim.conv2d(x, 1, [3, 3], stride=1, scope='Conv')
  70. y1_expected = tf.to_float([[14, 28, 43, 26],
  71. [28, 48, 66, 37],
  72. [43, 66, 84, 46],
  73. [26, 37, 46, 22]])
  74. y1_expected = tf.reshape(y1_expected, [1, n, n, 1])
  75. y2 = resnet_utils.subsample(y1, 2)
  76. y2_expected = tf.to_float([[14, 43],
  77. [43, 84]])
  78. y2_expected = tf.reshape(y2_expected, [1, n2, n2, 1])
  79. y3 = resnet_utils.conv2d_same(x, 1, 3, stride=2, scope='Conv')
  80. y3_expected = y2_expected
  81. y4 = slim.conv2d(x, 1, [3, 3], stride=2, scope='Conv')
  82. y4_expected = tf.to_float([[48, 37],
  83. [37, 22]])
  84. y4_expected = tf.reshape(y4_expected, [1, n2, n2, 1])
  85. with self.test_session() as sess:
  86. sess.run(tf.global_variables_initializer())
  87. self.assertAllClose(y1.eval(), y1_expected.eval())
  88. self.assertAllClose(y2.eval(), y2_expected.eval())
  89. self.assertAllClose(y3.eval(), y3_expected.eval())
  90. self.assertAllClose(y4.eval(), y4_expected.eval())
  91. def testConv2DSameOdd(self):
  92. n, n2 = 5, 3
  93. # Input image.
  94. x = create_test_input(1, n, n, 1)
  95. # Convolution kernel.
  96. w = create_test_input(1, 3, 3, 1)
  97. w = tf.reshape(w, [3, 3, 1, 1])
  98. tf.get_variable('Conv/weights', initializer=w)
  99. tf.get_variable('Conv/biases', initializer=tf.zeros([1]))
  100. tf.get_variable_scope().reuse_variables()
  101. y1 = slim.conv2d(x, 1, [3, 3], stride=1, scope='Conv')
  102. y1_expected = tf.to_float([[14, 28, 43, 58, 34],
  103. [28, 48, 66, 84, 46],
  104. [43, 66, 84, 102, 55],
  105. [58, 84, 102, 120, 64],
  106. [34, 46, 55, 64, 30]])
  107. y1_expected = tf.reshape(y1_expected, [1, n, n, 1])
  108. y2 = resnet_utils.subsample(y1, 2)
  109. y2_expected = tf.to_float([[14, 43, 34],
  110. [43, 84, 55],
  111. [34, 55, 30]])
  112. y2_expected = tf.reshape(y2_expected, [1, n2, n2, 1])
  113. y3 = resnet_utils.conv2d_same(x, 1, 3, stride=2, scope='Conv')
  114. y3_expected = y2_expected
  115. y4 = slim.conv2d(x, 1, [3, 3], stride=2, scope='Conv')
  116. y4_expected = y2_expected
  117. with self.test_session() as sess:
  118. sess.run(tf.global_variables_initializer())
  119. self.assertAllClose(y1.eval(), y1_expected.eval())
  120. self.assertAllClose(y2.eval(), y2_expected.eval())
  121. self.assertAllClose(y3.eval(), y3_expected.eval())
  122. self.assertAllClose(y4.eval(), y4_expected.eval())
  123. def _resnet_plain(self, inputs, blocks, output_stride=None, scope=None):
  124. """A plain ResNet without extra layers before or after the ResNet blocks."""
  125. with tf.variable_scope(scope, values=[inputs]):
  126. with slim.arg_scope([slim.conv2d], outputs_collections='end_points'):
  127. net = resnet_utils.stack_blocks_dense(inputs, blocks, output_stride)
  128. end_points = slim.utils.convert_collection_to_dict('end_points')
  129. return net, end_points
  130. def testEndPointsV2(self):
  131. """Test the end points of a tiny v2 bottleneck network."""
  132. blocks = [
  133. resnet_v2.resnet_v2_block(
  134. 'block1', base_depth=1, num_units=2, stride=2),
  135. resnet_v2.resnet_v2_block(
  136. 'block2', base_depth=2, num_units=2, stride=1),
  137. ]
  138. inputs = create_test_input(2, 32, 16, 3)
  139. with slim.arg_scope(resnet_utils.resnet_arg_scope()):
  140. _, end_points = self._resnet_plain(inputs, blocks, scope='tiny')
  141. expected = [
  142. 'tiny/block1/unit_1/bottleneck_v2/shortcut',
  143. 'tiny/block1/unit_1/bottleneck_v2/conv1',
  144. 'tiny/block1/unit_1/bottleneck_v2/conv2',
  145. 'tiny/block1/unit_1/bottleneck_v2/conv3',
  146. 'tiny/block1/unit_2/bottleneck_v2/conv1',
  147. 'tiny/block1/unit_2/bottleneck_v2/conv2',
  148. 'tiny/block1/unit_2/bottleneck_v2/conv3',
  149. 'tiny/block2/unit_1/bottleneck_v2/shortcut',
  150. 'tiny/block2/unit_1/bottleneck_v2/conv1',
  151. 'tiny/block2/unit_1/bottleneck_v2/conv2',
  152. 'tiny/block2/unit_1/bottleneck_v2/conv3',
  153. 'tiny/block2/unit_2/bottleneck_v2/conv1',
  154. 'tiny/block2/unit_2/bottleneck_v2/conv2',
  155. 'tiny/block2/unit_2/bottleneck_v2/conv3']
  156. self.assertItemsEqual(expected, end_points)
  157. def _stack_blocks_nondense(self, net, blocks):
  158. """A simplified ResNet Block stacker without output stride control."""
  159. for block in blocks:
  160. with tf.variable_scope(block.scope, 'block', [net]):
  161. for i, unit in enumerate(block.args):
  162. with tf.variable_scope('unit_%d' % (i + 1), values=[net]):
  163. net = block.unit_fn(net, rate=1, **unit)
  164. return net
  165. def testAtrousValuesBottleneck(self):
  166. """Verify the values of dense feature extraction by atrous convolution.
  167. Make sure that dense feature extraction by stack_blocks_dense() followed by
  168. subsampling gives identical results to feature extraction at the nominal
  169. network output stride using the simple self._stack_blocks_nondense() above.
  170. """
  171. block = resnet_v2.resnet_v2_block
  172. blocks = [
  173. block('block1', base_depth=1, num_units=2, stride=2),
  174. block('block2', base_depth=2, num_units=2, stride=2),
  175. block('block3', base_depth=4, num_units=2, stride=2),
  176. block('block4', base_depth=8, num_units=2, stride=1),
  177. ]
  178. nominal_stride = 8
  179. # Test both odd and even input dimensions.
  180. height = 30
  181. width = 31
  182. with slim.arg_scope(resnet_utils.resnet_arg_scope()):
  183. with slim.arg_scope([slim.batch_norm], is_training=False):
  184. for output_stride in [1, 2, 4, 8, None]:
  185. with tf.Graph().as_default():
  186. with self.test_session() as sess:
  187. tf.set_random_seed(0)
  188. inputs = create_test_input(1, height, width, 3)
  189. # Dense feature extraction followed by subsampling.
  190. output = resnet_utils.stack_blocks_dense(inputs,
  191. blocks,
  192. output_stride)
  193. if output_stride is None:
  194. factor = 1
  195. else:
  196. factor = nominal_stride // output_stride
  197. output = resnet_utils.subsample(output, factor)
  198. # Make the two networks use the same weights.
  199. tf.get_variable_scope().reuse_variables()
  200. # Feature extraction at the nominal network rate.
  201. expected = self._stack_blocks_nondense(inputs, blocks)
  202. sess.run(tf.global_variables_initializer())
  203. output, expected = sess.run([output, expected])
  204. self.assertAllClose(output, expected, atol=1e-4, rtol=1e-4)
  205. class ResnetCompleteNetworkTest(tf.test.TestCase):
  206. """Tests with complete small ResNet v2 networks."""
  207. def _resnet_small(self,
  208. inputs,
  209. num_classes=None,
  210. is_training=True,
  211. global_pool=True,
  212. output_stride=None,
  213. include_root_block=True,
  214. spatial_squeeze=True,
  215. reuse=None,
  216. scope='resnet_v2_small'):
  217. """A shallow and thin ResNet v2 for faster tests."""
  218. block = resnet_v2.resnet_v2_block
  219. blocks = [
  220. block('block1', base_depth=1, num_units=3, stride=2),
  221. block('block2', base_depth=2, num_units=3, stride=2),
  222. block('block3', base_depth=4, num_units=3, stride=2),
  223. block('block4', base_depth=8, num_units=2, stride=1),
  224. ]
  225. return resnet_v2.resnet_v2(inputs, blocks, num_classes,
  226. is_training=is_training,
  227. global_pool=global_pool,
  228. output_stride=output_stride,
  229. include_root_block=include_root_block,
  230. spatial_squeeze=spatial_squeeze,
  231. reuse=reuse,
  232. scope=scope)
  233. def testClassificationEndPoints(self):
  234. global_pool = True
  235. num_classes = 10
  236. inputs = create_test_input(2, 224, 224, 3)
  237. with slim.arg_scope(resnet_utils.resnet_arg_scope()):
  238. logits, end_points = self._resnet_small(inputs, num_classes,
  239. global_pool=global_pool,
  240. spatial_squeeze=False,
  241. scope='resnet')
  242. self.assertTrue(logits.op.name.startswith('resnet/logits'))
  243. self.assertListEqual(logits.get_shape().as_list(), [2, 1, 1, num_classes])
  244. self.assertTrue('predictions' in end_points)
  245. self.assertListEqual(end_points['predictions'].get_shape().as_list(),
  246. [2, 1, 1, num_classes])
  247. self.assertTrue('global_pool' in end_points)
  248. self.assertListEqual(end_points['global_pool'].get_shape().as_list(),
  249. [2, 1, 1, 32])
  250. def testEndpointNames(self):
  251. # Like ResnetUtilsTest.testEndPointsV2(), but for the public API.
  252. global_pool = True
  253. num_classes = 10
  254. inputs = create_test_input(2, 224, 224, 3)
  255. with slim.arg_scope(resnet_utils.resnet_arg_scope()):
  256. _, end_points = self._resnet_small(inputs, num_classes,
  257. global_pool=global_pool,
  258. scope='resnet')
  259. expected = ['resnet/conv1']
  260. for block in range(1, 5):
  261. for unit in range(1, 4 if block < 4 else 3):
  262. for conv in range(1, 4):
  263. expected.append('resnet/block%d/unit_%d/bottleneck_v2/conv%d' %
  264. (block, unit, conv))
  265. expected.append('resnet/block%d/unit_%d/bottleneck_v2' % (block, unit))
  266. expected.append('resnet/block%d/unit_1/bottleneck_v2/shortcut' % block)
  267. expected.append('resnet/block%d' % block)
  268. expected.extend(['global_pool', 'resnet/logits', 'resnet/spatial_squeeze',
  269. 'predictions'])
  270. self.assertItemsEqual(end_points.keys(), expected)
  271. def testClassificationShapes(self):
  272. global_pool = True
  273. num_classes = 10
  274. inputs = create_test_input(2, 224, 224, 3)
  275. with slim.arg_scope(resnet_utils.resnet_arg_scope()):
  276. _, end_points = self._resnet_small(inputs, num_classes,
  277. global_pool=global_pool,
  278. scope='resnet')
  279. endpoint_to_shape = {
  280. 'resnet/block1': [2, 28, 28, 4],
  281. 'resnet/block2': [2, 14, 14, 8],
  282. 'resnet/block3': [2, 7, 7, 16],
  283. 'resnet/block4': [2, 7, 7, 32]}
  284. for endpoint in endpoint_to_shape:
  285. shape = endpoint_to_shape[endpoint]
  286. self.assertListEqual(end_points[endpoint].get_shape().as_list(), shape)
  287. def testFullyConvolutionalEndpointShapes(self):
  288. global_pool = False
  289. num_classes = 10
  290. inputs = create_test_input(2, 321, 321, 3)
  291. with slim.arg_scope(resnet_utils.resnet_arg_scope()):
  292. _, end_points = self._resnet_small(inputs, num_classes,
  293. global_pool=global_pool,
  294. spatial_squeeze=False,
  295. scope='resnet')
  296. endpoint_to_shape = {
  297. 'resnet/block1': [2, 41, 41, 4],
  298. 'resnet/block2': [2, 21, 21, 8],
  299. 'resnet/block3': [2, 11, 11, 16],
  300. 'resnet/block4': [2, 11, 11, 32]}
  301. for endpoint in endpoint_to_shape:
  302. shape = endpoint_to_shape[endpoint]
  303. self.assertListEqual(end_points[endpoint].get_shape().as_list(), shape)
  304. def testRootlessFullyConvolutionalEndpointShapes(self):
  305. global_pool = False
  306. num_classes = 10
  307. inputs = create_test_input(2, 128, 128, 3)
  308. with slim.arg_scope(resnet_utils.resnet_arg_scope()):
  309. _, end_points = self._resnet_small(inputs, num_classes,
  310. global_pool=global_pool,
  311. include_root_block=False,
  312. spatial_squeeze=False,
  313. scope='resnet')
  314. endpoint_to_shape = {
  315. 'resnet/block1': [2, 64, 64, 4],
  316. 'resnet/block2': [2, 32, 32, 8],
  317. 'resnet/block3': [2, 16, 16, 16],
  318. 'resnet/block4': [2, 16, 16, 32]}
  319. for endpoint in endpoint_to_shape:
  320. shape = endpoint_to_shape[endpoint]
  321. self.assertListEqual(end_points[endpoint].get_shape().as_list(), shape)
  322. def testAtrousFullyConvolutionalEndpointShapes(self):
  323. global_pool = False
  324. num_classes = 10
  325. output_stride = 8
  326. inputs = create_test_input(2, 321, 321, 3)
  327. with slim.arg_scope(resnet_utils.resnet_arg_scope()):
  328. _, end_points = self._resnet_small(inputs,
  329. num_classes,
  330. global_pool=global_pool,
  331. output_stride=output_stride,
  332. spatial_squeeze=False,
  333. scope='resnet')
  334. endpoint_to_shape = {
  335. 'resnet/block1': [2, 41, 41, 4],
  336. 'resnet/block2': [2, 41, 41, 8],
  337. 'resnet/block3': [2, 41, 41, 16],
  338. 'resnet/block4': [2, 41, 41, 32]}
  339. for endpoint in endpoint_to_shape:
  340. shape = endpoint_to_shape[endpoint]
  341. self.assertListEqual(end_points[endpoint].get_shape().as_list(), shape)
  342. def testAtrousFullyConvolutionalValues(self):
  343. """Verify dense feature extraction with atrous convolution."""
  344. nominal_stride = 32
  345. for output_stride in [4, 8, 16, 32, None]:
  346. with slim.arg_scope(resnet_utils.resnet_arg_scope()):
  347. with tf.Graph().as_default():
  348. with self.test_session() as sess:
  349. tf.set_random_seed(0)
  350. inputs = create_test_input(2, 81, 81, 3)
  351. # Dense feature extraction followed by subsampling.
  352. output, _ = self._resnet_small(inputs, None,
  353. is_training=False,
  354. global_pool=False,
  355. output_stride=output_stride)
  356. if output_stride is None:
  357. factor = 1
  358. else:
  359. factor = nominal_stride // output_stride
  360. output = resnet_utils.subsample(output, factor)
  361. # Make the two networks use the same weights.
  362. tf.get_variable_scope().reuse_variables()
  363. # Feature extraction at the nominal network rate.
  364. expected, _ = self._resnet_small(inputs, None,
  365. is_training=False,
  366. global_pool=False)
  367. sess.run(tf.global_variables_initializer())
  368. self.assertAllClose(output.eval(), expected.eval(),
  369. atol=1e-4, rtol=1e-4)
  370. def testUnknownBatchSize(self):
  371. batch = 2
  372. height, width = 65, 65
  373. global_pool = True
  374. num_classes = 10
  375. inputs = create_test_input(None, height, width, 3)
  376. with slim.arg_scope(resnet_utils.resnet_arg_scope()):
  377. logits, _ = self._resnet_small(inputs, num_classes,
  378. global_pool=global_pool,
  379. spatial_squeeze=False,
  380. scope='resnet')
  381. self.assertTrue(logits.op.name.startswith('resnet/logits'))
  382. self.assertListEqual(logits.get_shape().as_list(),
  383. [None, 1, 1, num_classes])
  384. images = create_test_input(batch, height, width, 3)
  385. with self.test_session() as sess:
  386. sess.run(tf.global_variables_initializer())
  387. output = sess.run(logits, {inputs: images.eval()})
  388. self.assertEqual(output.shape, (batch, 1, 1, num_classes))
  389. def testFullyConvolutionalUnknownHeightWidth(self):
  390. batch = 2
  391. height, width = 65, 65
  392. global_pool = False
  393. inputs = create_test_input(batch, None, None, 3)
  394. with slim.arg_scope(resnet_utils.resnet_arg_scope()):
  395. output, _ = self._resnet_small(inputs, None,
  396. global_pool=global_pool)
  397. self.assertListEqual(output.get_shape().as_list(),
  398. [batch, None, None, 32])
  399. images = create_test_input(batch, height, width, 3)
  400. with self.test_session() as sess:
  401. sess.run(tf.global_variables_initializer())
  402. output = sess.run(output, {inputs: images.eval()})
  403. self.assertEqual(output.shape, (batch, 3, 3, 32))
  404. def testAtrousFullyConvolutionalUnknownHeightWidth(self):
  405. batch = 2
  406. height, width = 65, 65
  407. global_pool = False
  408. output_stride = 8
  409. inputs = create_test_input(batch, None, None, 3)
  410. with slim.arg_scope(resnet_utils.resnet_arg_scope()):
  411. output, _ = self._resnet_small(inputs,
  412. None,
  413. global_pool=global_pool,
  414. output_stride=output_stride)
  415. self.assertListEqual(output.get_shape().as_list(),
  416. [batch, None, None, 32])
  417. images = create_test_input(batch, height, width, 3)
  418. with self.test_session() as sess:
  419. sess.run(tf.global_variables_initializer())
  420. output = sess.run(output, {inputs: images.eval()})
  421. self.assertEqual(output.shape, (batch, 9, 9, 32))
  422. if __name__ == '__main__':
  423. tf.test.main()