mirror of
https://github.com/wassname/Flex-Convolution.git
synced 2026-08-21 11:10:02 +08:00
381 lines
14 KiB
Python
381 lines
14 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright 2017 ComputerGraphics Tuebingen. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
# ==============================================================================
|
|
# Authors: Fabian Groh, Patrick Wieschollek, Hendrik P.A. Lensch
|
|
|
|
|
|
import tensorflow as tf
|
|
from user_ops import flex_convolution as _flex_convolution
|
|
from user_ops import flex_pooling as _flex_pooling
|
|
from user_ops import flex_convolution_transpose as _flex_convolution_transpose
|
|
|
|
from tensorflow.python.keras import activations
|
|
from tensorflow.python.keras import initializers
|
|
from tensorflow.python.keras.engine.base_layer import Layer
|
|
from tensorflow.python.util.tf_export import tf_export
|
|
from tensorflow.python.framework import tensor_shape
|
|
from tensorflow.python.framework import ops
|
|
|
|
|
|
all = ['FlexPooling', 'FlexConvolution', 'FlexConvolutionTranspose',
|
|
'flex_pooling', 'flex_convolution', 'flex_convolution_transpose']
|
|
|
|
|
|
def _remove_dim(x, axis=2):
|
|
return tf.squeeze(x, axis=axis)
|
|
|
|
|
|
@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(features, 2)
|
|
neighborhoods = _remove_dim(neighborhoods, 2)
|
|
|
|
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.
|
|
|
|
This layer convolves elements in arbitrary neighborhoods with a kernel to
|
|
produce a tensor of outputs.
|
|
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
|
|
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 __init__(self,
|
|
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):
|
|
|
|
super(FlexConvolution, self).__init__(trainable=trainable,
|
|
name=name)
|
|
self.features = features
|
|
self.positions = positions
|
|
self.neighborhoods = neighborhoods
|
|
|
|
self.filters = int(filters)
|
|
self.activation = activations.get(activation)
|
|
self.use_feature_bias = use_feature_bias
|
|
self.data_format = data_format
|
|
self.kernel_initializer = initializers.get(kernel_initializer)
|
|
self.position_bias_initializer = initializers.get(position_bias_initializer)
|
|
self.features_bias_initializer = initializers.get(features_bias_initializer)
|
|
|
|
def compute_output_shape(self, input_shape):
|
|
input_shape = tensor_shape.TensorShape(input_shape)
|
|
input_shape[1] = self.filters
|
|
return input_shape
|
|
|
|
def build(self, input_shape):
|
|
if self.data_format == 'expanded':
|
|
features = _remove_dim(self.features, 2)
|
|
positions = _remove_dim(self.positions, 2)
|
|
else:
|
|
features = self.features
|
|
positions = self.positions
|
|
[B, Din, N] = features.shape
|
|
Din = int(Din)
|
|
N = int(N)
|
|
Dp = int(positions.shape[1])
|
|
Dout = self.filters
|
|
|
|
self.position_theta = self.add_weight(
|
|
'position_theta',
|
|
shape=[1, Dp, Din, Dout],
|
|
initializer=self.kernel_initializer,
|
|
dtype=self.dtype,
|
|
trainable=True)
|
|
|
|
self.position_bias = self.add_weight(
|
|
'position_bias',
|
|
shape=[Din, Dout],
|
|
initializer=self.position_bias_initializer,
|
|
dtype=self.dtype,
|
|
trainable=True)
|
|
|
|
if self.use_feature_bias:
|
|
self.feature_bias = self.add_weight(
|
|
'feature_bias',
|
|
shape=[Dout, 1],
|
|
initializer=self.features_bias_initializer,
|
|
dtype=self.dtype,
|
|
trainable=True)
|
|
else:
|
|
self.feature_bias = None
|
|
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)
|
|
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(features, 2)
|
|
positions = _remove_dim(positions, 2)
|
|
neighborhoods = _remove_dim(neighborhoods, 2)
|
|
|
|
y = _flex_convolution(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(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 = FlexConvolution(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])
|
|
|
|
|
|
@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(features, 2)
|
|
positions = _remove_dim(positions, 2)
|
|
neighborhoods = _remove_dim(neighborhoods, 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])
|