From 8be1b9ccf9d3e3437d9a89215abda245f4828c64 Mon Sep 17 00:00:00 2001 From: PatWie Date: Tue, 2 Oct 2018 13:44:32 +0200 Subject: [PATCH] add pooling, transpose convolution to layers.py and update example.py --- .github/inference_time.png | Bin 65047 -> 65212 bytes README.md | 40 +++---- example.py | 43 ++++++-- layers.py | 194 ++++++++++++++++++++++++++++++++-- user_ops/CMakeLists.txt | 2 +- user_ops/FindTensorFlow.cmake | 4 +- user_ops/__init__.py | 3 +- 7 files changed, 241 insertions(+), 45 deletions(-) diff --git a/.github/inference_time.png b/.github/inference_time.png index bd56ca2d1ce2c379189cf4e8760a7303c8ae10af..5d2b04b279cbf34c404b6d01c8532f5aa075053c 100644 GIT binary patch delta 24941 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 delta 24797 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 BQnUa7 diff --git a/README.md b/README.md index 09fe613..d2fe833 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ The following figure shows the *raw* network semantic segmentation prediction on

-This repository contains contains the source code of our FlexConv Layer from our 2018 ACCV paper "Flex-Convolution (Million-Scale Point-Cloud Learning Beyond Grid-Worlds)". +This repository contains the source code of our FlexConv Layer from our 2018 ACCV paper "Flex-Convolution (Million-Scale Point-Cloud Learning Beyond Grid-Worlds)".

@@ -22,7 +22,7 @@ This repository contains contains the source code of our FlexConv Layer from our Example - Usage ------------------- -We provide GPU-tailored cuda implementation of our novel FlexConv in TensorFlow. +We provide GPU-tailored CUDA implementations of our novel FlexConv, FlexPool, FlexDeconv operations in TensorFlow. ```console user@host $ cd user_ops @@ -35,13 +35,13 @@ user@host $ python example.py Experiments ------------------- -Deep learning on point-clouds is a complex matter and our codebase reflect that complexity. +Deep learning on point-clouds is a complex matter and our codebase reflects that complexity. We are currently working on refactoring our research implementation to ease the usage. Therefore, `layers.py` contains a Keras/tf.layers compatible implementation. We will add the models later. ### Benchmark -We benchmarked the inference time of entire network on the 2D-3D-S dataset and with recent tests on V100, we were able to process ~18 Million Points. +We benchmarked the inference time of *entire* network on the 2D-3D-S dataset and with a recent test on a NVIDIA V100 GPU, we were able to process ~18 Million Points.

@@ -50,14 +50,14 @@ We benchmarked the inference time of entire network on the 2D-3D-S dataset and w ShapeNet part segmentation results per category and mIoU (%) for different methods and inference speed (on a NVIDIA GeForce GTX 1080 Ti). -|| Airplane | Bag | Cap | Car | Chair | Earphones | Guitar | Knife | Lamp | Laptop | Motorbike | Mug | Pistol | Rocket | Skateboard | Table | mIoU | shapes/sec | -| ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | --- | ---- | ---- | --- | -|Kd-Network [4] | 80.1 | 74.6 | 74.3 | 70.3 | 88.6 | 73.5 | 90.2 | **87.2** | 81.0 | 94.9 | 57.4 | 86.7 | 78.1 | 51.8 | 69.9 | 80.3 | 77.4 | n.a.| -|PointNet [1] | 83.4 | 78.7 | 82.5 | 74.9 | 89.6 | 73.0 | 91.5 | 85.9 | 80.8 | 95.3 | 65.2 | 93.0 | 81.2 | 57.9 | 72.8 | 80.6 | 80.4 | n.a. | -|PointNet++ [2] | 82.4 | 79.0 | 87.7 | 77.3 | **90.8** | 71.8 | 91.0 | 85.9 | 83.7 | 95.3 | 71.6 | 94.1 | 81.3 | 58.7 | 76.4 | 82.6 | 81.9 | 2.7 | -|SPLATNet3D [3] | 81.9 | 83.9 | 88.6 | **79.5** | 90.1 | 73.5 | 91.3 | 84.7 | **84.5** | 96.3 | 69.7 | 95.0 | 81.7 | 59.2 | 70.4 | 81.3 | 82.0 | 9.4 | -|SGPN [5] | 80.4 | 78.6 | 78.8 | 71.5 | 88.6 | **78.0** | 90.9 | 83.0 | 78.8 | 95.8 | **77.8** | 93.8 | **87.4** | 60.1 | **92.3** | **89.4** | 82.8 | n.a. | -Ours | **83.6** | **91.2** | **96.7** | **79.5** | 84.7 | 71.7 | **92.0** | 86.5 | 83.2 | **96.6** | 71.7 | **95.7** | 86.1 | **74.8** | 81.4 | 84.5 | **85.0** | **489.3** | +| | mIoU | shapes/sec | Airplane | Bag | Cap | Car | Chair | Earphones | Guitar | Knife | Lamp | Laptop | Motorbike | Mug | Pistol | Rocket | Skateboard | Table | +| ---- | ---- | --- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | --- | ---- | +| Kd-Network [4] | 77.4 | n.a. | 80.1 | 74.6 | 74.3 | 70.3 | 88.6 | 73.5 | 90.2 | **87.2** | 81.0 | 94.9 | 57.4 | 86.7 | 78.1 | 51.8 | 69.9 | 80.3 | +| PointNet [1] | 80.4 | n.a. | 83.4 | 78.7 | 82.5 | 74.9 | 89.6 | 73.0 | 91.5 | 85.9 | 80.8 | 95.3 | 65.2 | 93.0 | 81.2 | 57.9 | 72.8 | 80.6 | +| PointNet++ [2] | 81.9 | 2.7 | 82.4 | 79.0 | 87.7 | 77.3 | **90.8** | 71.8 | 91.0 | 85.9 | 83.7 | 95.3 | 71.6 | 94.1 | 81.3 | 58.7 | 76.4 | 82.6 | +| SPLATNet3D [3] | 82.0 | 9.4 | 81.9 | 83.9 | 88.6 | **79.5** | 90.1 | 73.5 | 91.3 | 84.7 | **84.5** | 96.3 | 69.7 | 95.0 | 81.7 | 59.2 | 70.4 | 81.3 | +| SGPN [5] | 82.8 | n.a. | 80.4 | 78.6 | 78.8 | 71.5 | 88.6 | **78.0** | 90.9 | 83.0 | 78.8 | 95.8 | **77.8** | 93.8 | **87.4** | 60.1 | **92.3** | **89.4** | +| Ours | **85.0** | **489.3** | **83.6** | **91.2** | **96.7** | **79.5** | 84.7 | 71.7 | **92.0** | 86.5 | 83.2 | **96.6** | 71.7 | **95.7** | 86.1 | **74.8** | 81.4 | 84.5 | ![example segmentation](./.github/shapenet.png) @@ -65,14 +65,14 @@ Ours | **83.6** | **91.2** | **96.7** | **79.5** | 84. Class specific average precision (AP) on the 2D-3D-S dataset. -| | Table | Chair | Sofa | Bookcase | Board | Ceiling | Floor | Wall | Beam | Col. | Wind. | Door | mAP | -|---|---|---|---|---|---|---|---|---|---|---|---|---|---| -Armeni [6] | 46.02 | 16.15 | 6.78 | 54.71 | 3.91 | 71.61 | 88.70 | 72.86 | 66.67 | **91.77** | 25.92 | 54.11 | 49.93| -Armeni [6] | 39.87 | 11.43 | 4.91 | **57.76** | 3.73 | 50.74 | 80.48 | 65.59 | 68.53 | 85.08 | 21.17 | 45.39 | 44.19| -PointNet [1] | 46.67 | 33.80 | 4.76 | n.a. | 11.72 | n.a. | n.a. | n.a. | n.a. | n.a. | n.a. | n.a. | n.a. | -SGPN [5] | 46.90 | 40.77 | 6.38 | 47.61 | 11.05 | 79.44 | 66.29 | **88.77** | **77.98** | 60.71 | **66.62** | **56.75** | 54.35 | -Ours | 66.03 | 51.75 | 15.59 | 39.03 | 43.50 | 87.20 | 96.00 | 65.53 | 54.76 | 52.74 | 55.34 | 35.81 | 55.27| -Ours** | **67.02** | **52.75** | **16.61** | 39.26 | **47.68** | **87.33** | **96.10** | 65.52 | 56.83 | 55.10 | 57.66 | 36.76 | **56.55**| +| | mAP | Table | Chair | Sofa | Bookcase | Board | Ceiling | Floor | Wall | Beam | Col. | Wind. | Door | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| Armeni [6] | 49.93 | 46.02 | 16.15 | 6.78 | 54.71 | 3.91 | 71.61 | 88.70 | 72.86 | 66.67 | **91.77** | 25.92 | 54.11 | +| Armeni [6] | 44.19 | 39.87 | 11.43 | 4.91 | **57.76** | 3.73 | 50.74 | 80.48 | 65.59 | 68.53 | 85.08 | 21.17 | 45.39 | +| PointNet [1] | n.a. | 46.67 | 33.80 | 4.76 | n.a. | 11.72 | n.a. | n.a. | n.a. | n.a. | n.a. | n.a. | n.a. | +| SGPN [5] | 54.35 | 46.90 | 40.77 | 6.38 | 47.61 | 11.05 | 79.44 | 66.29 | **88.77** | **77.98** | 60.71 | **66.62** | **56.75** | +| Ours | 55.27 | 66.03 | 51.75 | 15.59 | 39.03 | 43.50 | 87.20 | 96.00 | 65.53 | 54.76 | 52.74 | 55.34 | 35.81 | +| Ours** | **56.55** | **67.02** | **52.75** | **16.61** | 39.26 | **47.68** | **87.33** | **96.10** | 65.52 | 56.83 | 55.10 | 57.66 | 36.76 | ![example segmentation](./.github/2d3ds-results-large-1.jpg) diff --git a/example.py b/example.py index 1d07355..fd411a5 100644 --- a/example.py +++ b/example.py @@ -19,29 +19,50 @@ """ -Demonstration of using FlexConvolution Layer. +Demonstration of using FlexConvolution, FlexPooling Layer. """ import numpy as np import tensorflow as tf -from layers import flex_convolution +from tabulate import tabulate +from layers import flex_convolution, flex_convolution_transpose, flex_pooling -B, Din, Dout, Dp, N, K = 1, 2, 4, 3, 10, 5 +B, Din, Dout, Dout2, Dp, N, N2, K, K2 = 1, 2, 4, 8, 3, 10, 5, 5, 3 features = np.random.randn(B, Din, N).astype(np.float32) positions = np.random.randn(B, Dp, N).astype(np.float32) neighbors = np.random.randint(0, N, [B, K, N]).astype(np.int32) +neighbors2 = np.random.randint(0, N, [B, K2, N2]).astype(np.int32) + +features = tf.convert_to_tensor(features, name='features') +positions = tf.convert_to_tensor(positions, name='positions') +neighbors = tf.convert_to_tensor(neighbors, name='neighbors') +neighbors2 = tf.convert_to_tensor(neighbors2, name='neighbors2') + +net = [features] +# use our FlexConv similar to a traditional convolution layer +net.append(flex_convolution(net[-1], positions, neighbors, Dout)) +# pool and sub-sampling are different operations +net.append(flex_pooling(net[-1], neighbors)) + +# when ordering the points beforehand sub-sampling is simply +features = net[-1][:, :, :N2] +positions = positions[:, :, :N2] + +net.append(features) +# we didn't notice any improvements using the transposed version vs. pooling +net.append(flex_convolution_transpose(net[-1], positions, neighbors2, Dout2)) +# of course any commonly used arguments work here as well +net.append(flex_convolution(net[-1], positions, + neighbors2, Dout2, trainable=False)) -features = tf.convert_to_tensor(features) -positions = tf.convert_to_tensor(positions) -neighbors = tf.convert_to_tensor(neighbors) -features2 = flex_convolution(features, positions, neighbors, Dout) -features3 = flex_convolution(features2, positions, neighbors, Dout, trainable=False) with tf.Session() as sess: sess.run(tf.global_variables_initializer()) - sess.run(features2) - sess.run(features3) + sess.run(net[-1]) - print(tf.trainable_variables()) + print(tabulate([[v.name, v.shape] for v in tf.trainable_variables()], + headers=["Name", "Shape"])) + + print(tabulate([[n.name, n.shape] for n in net], headers=["Name", "Shape"])) diff --git a/layers.py b/layers.py index ebbb13d..da84707 100644 --- a/layers.py +++ b/layers.py @@ -29,18 +29,88 @@ from tensorflow.python.util.tf_export import tf_export from tensorflow.python.framework import tensor_shape from tensorflow.python.framework import ops -flex_pooling = _flex_pooling -flex_convolution_transpose = _flex_convolution_transpose + +all = ['FlexPooling', 'FlexConvolution', 'FlexConvolutionTranspose', + 'flex_pooling', 'flex_convolution', 'flex_convolution_transpose'] -def _remove_dim(x, axis): - shape = x.shape.as_list() - assert shape[axis] == 1 - del shape[axis] - return tf.reshape(x, shape) +def _remove_dim(x, axis=2): + return tf.squeeze(x, axis=axis) -@tf_export('keras.layers.Dense') +@tf_export('keras.layers.FlexPooling') +class FlexPooling(Layer): + """flex pooling layer. + + This layer performs a max-pooling operation over elements in arbitrary + neighborhoods. When `data_format` is 'simple', the input shape should + have rank 3, otherwise rank 4 and dimension 2 should be 1. + + Remarks: + In contrast to traditional pooling, this operation has no option for + sub-sampling. + + Arguments: + features: A `Tensor` of the format [B, Din, (1), N]. + neighborhoods: A `Tensor` of the format [B, K, (1), N] (tf.int32). + name: A string, the name of the layer. + + """ + + def __init__(self, + features, + neighborhoods, + data_format='simple', + name=None): + + super(FlexPooling, self).__init__(name=name) + self.features = features + self.neighborhoods = neighborhoods + self.data_format = data_format + + def compute_output_shape(self, input_shape): + return tensor_shape.TensorShape(input_shape) + + def build(self, input_shape): + self.built = True + + def call(self, inputs): + if not isinstance(inputs, list): + raise ValueError('A flexconv layer should be called ' + 'on a list of inputs.') + + features = ops.convert_to_tensor(inputs[0], dtype=self.dtype) + neighborhoods = ops.convert_to_tensor(inputs[1], dtype=tf.int32) + + if self.data_format == 'expanded': + features = _remove_dim(inputs[0], 2) + neighborhoods = _remove_dim(inputs[1], 2) + else: + features = inputs[0] + neighborhoods = inputs[1] + + y, _ = _flex_pooling(features, neighborhoods) + + if self.data_format == 'expanded': + y = tf.expand_dims(y, axis=2) + + return y + + +def flex_pooling(features, + neighborhoods, + data_format='simple', + name=None): + + layer = FlexPooling(features, + neighborhoods, + data_format=data_format, + name=name) + + return layer.apply([features, neighborhoods]) + + +@tf_export('keras.layers.FlexConvolution') class FlexConvolution(Layer): """flex convolution layer. @@ -49,6 +119,8 @@ class FlexConvolution(Layer): If `use_feature_bias` is True (and a `features_bias_initializer` is provided), a bias vector is created and added to the outputs after te convolution. Finally, if `activation` is not `None`, it is applied to the outputs as well. + When `data_format` is 'simple', the input shape should have rank 3, + otherwise rank 4 and dimension 2 should be 1. Remarks: In contrast to traditional convolutions, this operation has two @@ -120,7 +192,6 @@ class FlexConvolution(Layer): else: features = self.features positions = self.positions - [B, Din, N] = features.shape Din = int(Din) N = int(N) @@ -213,3 +284,108 @@ def flex_convolution(features, name=name) return layer.apply([features, positions, neighborhoods]) + + +@tf_export('keras.layers.FlexConvolutionTranspose') +class FlexConvolutionTranspose(FlexConvolution): + """flex convolution-transpose layer. + + This layer applies a transpose convolution to elements in arbitrary + neighborhoods. + If `use_feature_bias` is True (and a `features_bias_initializer` is provided), + a bias vector is created and added to the outputs after te convolution. + Finally, if `activation` is not `None`, it is applied to the outputs as well. + When `data_format` is 'simple', the input shape should have rank 3, + otherwise rank 4 and dimension 2 should be 1. + + Remarks: + In contrast to traditional transposed convolutions, this operation has two + bias terms: + - bias term when dynamically computing the weight [Din, Dout] + - bias term which is added tot the features [Dout] + + Arguments: + features: A `Tensor` of the format [B, Din, (1), N]. + positions: A `Tensor` of the format [B, Dp, (1), N]. + neighborhoods: A `Tensor` of the format [B, K, (1), N] (tf.int32). + filters: Integer, the dimensionality of the output space (i.e. the number + of filters in the convolution). + activation: Activation function. Set it to None to maintain a + linear activation. + kernel_initializer: An initializer for the convolution kernel. + position_bias_initializer: An initializer for the bias vector within + the convolution. If None, the default initializer will be used. + features_bias_initializer: An initializer for the bias vector after + the convolution. If None, the default initializer will be used. + use_feature_bias: Boolean, whether the layer uses a bias. + data_format: A string, one of `simple` (default) or `expaned`. + If `simple` the shapes are [B, Din, N], when `expanded` the shapes + are assumed to be [B, Din, 1, N] to match `channels_first` in trad + convolutions. + trainable: Boolean, if `True` also add variables to the graph collection + `GraphKeys.TRAINABLE_VARIABLES` (see `tf.Variable`). + name: A string, the name of the layer. + + """ + + def call(self, inputs): + + if not isinstance(inputs, list): + raise ValueError('A flexconv layer should be called ' + 'on a list of inputs.') + + features = ops.convert_to_tensor(inputs[0], dtype=self.dtype) + positions = ops.convert_to_tensor(inputs[1], dtype=self.dtype) + neighborhoods = ops.convert_to_tensor(inputs[2], dtype=tf.int32) + + if self.data_format == 'expanded': + features = _remove_dim(inputs[0], 2) + positions = _remove_dim(inputs[1], 2) + neighborhoods = _remove_dim(inputs[2], 2) + else: + features = inputs[0] + positions = inputs[1] + neighborhoods = inputs[2] + + y = _flex_convolution_transpose(features, positions, neighborhoods, + self.position_theta, self.position_bias) + + if self.use_feature_bias: + y = tf.add(y, self.feature_bias) + + if self.activation is not None: + y = self.activation(y) + + if self.data_format == 'expanded': + y = tf.expand_dims(y, axis=2) + + return y + + +def flex_convolution_transpose(features, + positions, + neighborhoods, + filters, + activation=None, + kernel_initializer=None, + position_bias_initializer=tf.zeros_initializer(), + features_bias_initializer=tf.zeros_initializer(), + use_feature_bias=True, + data_format='simple', + trainable=True, + name=None): + + layer = FlexConvolutionTranspose(features, + positions, + neighborhoods, + filters, + activation=activation, + kernel_initializer=kernel_initializer, + position_bias_initializer=position_bias_initializer, + features_bias_initializer=features_bias_initializer, + use_feature_bias=use_feature_bias, + data_format=data_format, + trainable=trainable, + name=name) + + return layer.apply([features, positions, neighborhoods]) diff --git a/user_ops/CMakeLists.txt b/user_ops/CMakeLists.txt index 7b40ae9..33e0de0 100644 --- a/user_ops/CMakeLists.txt +++ b/user_ops/CMakeLists.txt @@ -10,7 +10,7 @@ project( FlexConv ) list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}) -find_package(CUDA 9 REQUIRED) +find_package(CUDA REQUIRED) find_package(TensorFlow REQUIRED) diff --git a/user_ops/FindTensorFlow.cmake b/user_ops/FindTensorFlow.cmake index d70a4b3..41c9b04 100644 --- a/user_ops/FindTensorFlow.cmake +++ b/user_ops/FindTensorFlow.cmake @@ -122,7 +122,7 @@ else() # However, only TensorFlow versions 1.9, 1.10 support all header files # for custom ops. set(_TensorFlow_KNOWN_VERSIONS ${TensorFlow_ADDITIONAL_VERSIONS} - "1.9" "1.9.0" "1.10" "1.10.0") + "1.9" "1.9.0" "1.10" "1.10.0", "1.11", "1.11.0") set(_TensorFlow_TEST_VERSIONS) if(TF_FIND_VERSION) @@ -270,4 +270,4 @@ SET(TensorFlow_INCLUDE_DIR ${TensorFlow_INCLUDE_DIR} CACHE PATH "path to tensorf SET(TensorFlow_VERSION ${TensorFlow_VERSION} CACHE INTERNAL "The Python executable Version") SET(TensorFlow_ABI ${TensorFlow_ABI} CACHE STRING "The Python executable Version") SET(TensorFlow_LIBRARY ${TensorFlow_LIBRARY} CACHE PATH "The Python executable Version") -SET(TensorFlow_FOUND ${TensorFlow_FOUND} CACHE BOOL "The Python executable Version") \ No newline at end of file +SET(TensorFlow_FOUND ${TensorFlow_FOUND} CACHE BOOL "The Python executable Version") diff --git a/user_ops/__init__.py b/user_ops/__init__.py index 5e1862b..78449c8 100644 --- a/user_ops/__init__.py +++ b/user_ops/__init__.py @@ -41,9 +41,8 @@ flex_pool_grad = _flex_pooling_op_so.flex_pool_grad flex_deconv = _flex_deconvolution_op_so.flex_deconv flex_deconv_grad = _flex_deconvolution_op_so.flex_deconv_grad + # pylint: disable=redefined-builtin - - def flex_convolution(features, position, neighborhood,