From 352fce1d783d5dafa2507af91d307782b043a203 Mon Sep 17 00:00:00 2001 From: Felix Hensel Date: Wed, 16 Feb 2022 10:08:02 +0100 Subject: [PATCH 01/31] ripsnet first commit --- src/python/gudhi/tensorflow/__init__.py | 3 + src/python/gudhi/tensorflow/ripsnet.py | 197 ++++++++++++++++++++++++ 2 files changed, 200 insertions(+) create mode 100644 src/python/gudhi/tensorflow/__init__.py create mode 100644 src/python/gudhi/tensorflow/ripsnet.py diff --git a/src/python/gudhi/tensorflow/__init__.py b/src/python/gudhi/tensorflow/__init__.py new file mode 100644 index 0000000000..829cf2174c --- /dev/null +++ b/src/python/gudhi/tensorflow/__init__.py @@ -0,0 +1,3 @@ +from .ripsnet import * + +__all__ = ["RipsNet", "PermopRagged", "TFBlock", "DenseRaggedBlock", "DenseRagged"] \ No newline at end of file diff --git a/src/python/gudhi/tensorflow/ripsnet.py b/src/python/gudhi/tensorflow/ripsnet.py new file mode 100644 index 0000000000..ef5b2427d8 --- /dev/null +++ b/src/python/gudhi/tensorflow/ripsnet.py @@ -0,0 +1,197 @@ +# This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. +# See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. +# Author(s): Felix Hensel, Mathieu Carrière +# +# Copyright (C) 2022 Inria +# +# Modification(s): +# - YYYY/MM Author: Description of the modification + +import tensorflow as tf + +class DenseRagged(tf.keras.layers.Layer): + """ + This is a class for the ragged layer in the RipsNet architecture, processing the input pointclouds. + """ + + def __init__(self, units, input_dim=None, use_bias=True, activation='gelu', **kwargs): + """ + Constructor for the DenseRagged class. + + Parameters: + units (int): number of units in the layer. + use_bias (bool): flag, indicating whether to use bias or not. + activation (string or function): identifier of a keras activation function, e.g. 'relu'. + """ + super().__init__(dynamic=True, **kwargs) + self._supports_ragged_inputs = True + self.units = units + self.use_bias = use_bias + self.activation = tf.keras.activations.get(activation) + + def build(self, input_shape): + last_dim = input_shape[-1] + self.kernel = self.add_weight('kernel', shape=[last_dim, self.units], trainable=True) + if self.use_bias: + self.bias = self.add_weight('bias', shape=[self.units, ], trainable=True) + else: + self.bias = None + super().build(input_shape) + + def call(self, inputs): + """ + Apply DenseRagged layer on a ragged input tensor. + + Parameters: + ragged tensor (e.g. containing a point cloud). + + Returns: + ragged tensor containing the output of the layer. + """ + outputs = tf.ragged.map_flat_values(tf.matmul, inputs, self.kernel) + if self.use_bias: + outputs = tf.ragged.map_flat_values(tf.nn.bias_add, outputs, self.bias) + outputs = tf.ragged.map_flat_values(self.activation, outputs) + return outputs + + +class DenseRaggedBlock(tf.keras.layers.Layer): + """ + This is a block of DenseRagged layers. + """ + + def __init__(self, dense_ragged_layers, **kwargs): + """ + Constructor for the DenseRagged class. + + Parameters: + dense_ragged_layers (list): a list of DenseRagged layers :class:`~gudhi.tensorflow.DenseRagged. + input_dim (int): dimension of the pointcloud, if the input consists of pointclouds. + """ + super().__init__(dynamic=True, **kwargs) + self._supports_ragged_inputs = True + self.dr_layers = dense_ragged_layers + + # self.input_dim = input_dim + + def build(self, input_shape): + return self + + def call(self, inputs): + """ + Apply the sequence of DenseRagged layers on a ragged input tensor. + + Parameters: + ragged tensor (e.g. containing a point cloud). + + Returns: + ragged tensor containing the output of the sequence of layers. + """ + outputs = inputs + for dr_layer in self.dr_layers: + outputs = dr_layer(outputs) + return outputs + + +class TFBlock(tf.keras.layers.Layer): + """ + This class is a block of tensorflow layers. + """ + + def __init__(self, layers, **kwargs): + """ + Constructor for the TFBlock class. + + Parameters: + dense_layers (list): a list of dense tensorflow layers. + """ + super().__init__(dynamic=True, **kwargs) + self.layers = layers + + def build(self, input_shape): + super().build(input_shape) + + def call(self, inputs): + """ + Apply the sequence of layers on an input tensor. + + Parameters: + inputs: any input tensor. + + Returns: + output tensor containing the output of the sequence of layers. + """ + outputs = inputs + for layer in self.layers: + outputs = layer(outputs) + return outputs + + +class PermopRagged(tf.keras.layers.Layer): + """ + This is a class for the permutation invariant layer in the RipsNet architecture. + """ + + def __init__(self, perm_op, **kwargs): + """ + Constructor for the PermopRagged class. + + Parameters: + perm_op: permutation invariant function, such as `tf.math.reduce_sum`, `tf.math.reduce_mean`, `tf.math.reduce_max`, `tf.math.reduce_min`, or a custom function. + """ + super().__init__(dynamic=True, **kwargs) + self._supports_ragged_inputs = True + self.pop = perm_op + + def build(self, input_shape): + super().build(input_shape) + + def call(self, inputs): + """ + Apply PermopRagged on an input tensor. + """ + out = self.pop(inputs, axis=1) + return out + + +class RipsNet(tf.keras.Model): + """ + This is a TensorFlow model for estimating vectorizations of persistence diagrams of point clouds. + This class implements the RipsNet described in the following article . + """ + + def __init__(self, phi_1, perm_op, phi_2, input_dim, **kwargs): + """ + Constructor for the RipsNet class. + + Parameters: + phi_1 (layers): any block of DenseRagged layers. Can be :class:`~gudhi.tensorflow.DenseRaggedBlock`, or a custom block built from :class:`~gudhi.tensorflow.DenseRagged` layers. + perm_op (layer): layer for the permutation invariant function. Can be :class:`~gudhi.tensorflow.PermopRagged`. + phi_2 (layers): Can be any (block of) TensorFlow layer(s), e.g. :class:`~gudhi.tensorflow.TFBlock`. + input_dim (int): dimension of the input point clouds. + """ + super().__init__(dynamic=True, **kwargs) + self.phi_1 = phi_1 + self.pop = perm_op + self.phi_2 = phi_2 + self.input_dim = input_dim + + def build(self, input_shape): + return self + + def call(self, pointclouds): + """ + Apply RipsNet on a ragged tensor containing a list of pointclouds. + + Parameters: + point clouds (n x None x input_dimension): ragged tensor containing n pointclouds in dimension `input_dimension`. The second dimension is ragged since point clouds can have different numbers of points. + + Returns: + output (n x output_shape): tensor containing predicted vectorizations of the persistence diagrams of pointclouds. + """ + inputs = tf.keras.layers.InputLayer(input_shape=(None, self.input_dim), dtype="float32", ragged=True)( + pointclouds) + output = self.phi_1(inputs) + output = self.pop(output) + output = self.phi_2(output) + return output \ No newline at end of file From 51eeb0317a27368af1ede794f31f466bc59498c1 Mon Sep 17 00:00:00 2001 From: Felix Hensel Date: Wed, 16 Feb 2022 10:47:14 +0100 Subject: [PATCH 02/31] ripsnet typos fixed --- src/python/gudhi/tensorflow/ripsnet.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/python/gudhi/tensorflow/ripsnet.py b/src/python/gudhi/tensorflow/ripsnet.py index ef5b2427d8..8c7f89882f 100644 --- a/src/python/gudhi/tensorflow/ripsnet.py +++ b/src/python/gudhi/tensorflow/ripsnet.py @@ -65,15 +65,13 @@ def __init__(self, dense_ragged_layers, **kwargs): Constructor for the DenseRagged class. Parameters: - dense_ragged_layers (list): a list of DenseRagged layers :class:`~gudhi.tensorflow.DenseRagged. + dense_ragged_layers (list): a list of DenseRagged layers :class:`~gudhi.tensorflow.DenseRagged`. input_dim (int): dimension of the pointcloud, if the input consists of pointclouds. """ super().__init__(dynamic=True, **kwargs) self._supports_ragged_inputs = True self.dr_layers = dense_ragged_layers - # self.input_dim = input_dim - def build(self, input_shape): return self From f879d1d8a65a940b473866f412eb49326fecd097 Mon Sep 17 00:00:00 2001 From: Felix Hensel Date: Tue, 22 Feb 2022 17:43:03 +0100 Subject: [PATCH 03/31] changed perm_op API and added documentation --- src/python/doc/ripsnet.inc | 15 ++++++++ src/python/doc/ripsnet.rst | 52 ++++++++++++++++++++++++++ src/python/gudhi/tensorflow/ripsnet.py | 20 +++++++--- 3 files changed, 82 insertions(+), 5 deletions(-) create mode 100644 src/python/doc/ripsnet.inc create mode 100644 src/python/doc/ripsnet.rst diff --git a/src/python/doc/ripsnet.inc b/src/python/doc/ripsnet.inc new file mode 100644 index 0000000000..9b07077439 --- /dev/null +++ b/src/python/doc/ripsnet.inc @@ -0,0 +1,15 @@ +.. table:: + :widths: 30 40 30 + + +----------------------------------------------------------------+-------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------+ + | | RipsNet is a general architecture for fast and robust estimation of the | :Author: Felix Hensel, Mathieu Carrière | + | | persistent homology of point clouds. | | + | | | :Since: GUDHI | + | | | | + | | | :License: MIT (`GPL v3 `_) | + | | | | + | | | | + | | | | + +----------------------------------------------------------------+-------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------+ + | * :doc:`ripsnet` | * :doc:`ripsnet` | + +----------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ diff --git a/src/python/doc/ripsnet.rst b/src/python/doc/ripsnet.rst new file mode 100644 index 0000000000..79852e11d9 --- /dev/null +++ b/src/python/doc/ripsnet.rst @@ -0,0 +1,52 @@ +:orphan: + +.. To get rid of WARNING: document isn't included in any toctree + +RipsNet user manual +========================= +Definition +---------- + +.. include:: ripsnet.inc + +:class:`~gudhi.ripsnet` constructs a Tensorflow model for fast and robust estimation of persistent homology of +point clouds. +RipsNet is based on a `Deep Sets `_ +architecture, for details see `RipsNet `_. + +Example +------------------- + +This example instantiates a RipsNet model which can then be trained as any tensorflow model. + +.. testcode:: + from gudhi import ripsnet + from tensorflow.keras import regularizers, layers + + ragged_layers_size = [30,20,10] + dense_layers_size = [50,100,200] + output_units = 2500 + activation_fct = 'gelu' + output_activation = 'sigmoid' + dropout = 0 + kernel_regularization = 0 + + ragged_layers = [] + dense_layers = [] + + for n_units in ragged_layers_size: + ragged_layers.append(DenseRagged(units=n_units, use_bias=True, activation=activation_fct)) + + for n_units in dense_layers_size: + dense_layers.append(tf.keras.layers.Dense(n_units, activation=activation_fct, + kernel_regularizer=regularizers.l2(kernel_regularization))) + dense_layers.append(tf.keras.layers.Dropout(dropout)) + + dense_layers.append(tf.keras.layers.Dense(output_units, activation=output_activation)) + + phi_1 = DenseRaggedBlock(ragged_layers) + perm_op = 'mean' # can also be 'sum'. + phi_2 = TFBlock(dense_layers) + input_dim = 2 + + RN = RipsNet(phi_1, phi_2, input_dim, perm_op=perm_op) diff --git a/src/python/gudhi/tensorflow/ripsnet.py b/src/python/gudhi/tensorflow/ripsnet.py index 8c7f89882f..789c8c658c 100644 --- a/src/python/gudhi/tensorflow/ripsnet.py +++ b/src/python/gudhi/tensorflow/ripsnet.py @@ -62,7 +62,7 @@ class DenseRaggedBlock(tf.keras.layers.Layer): def __init__(self, dense_ragged_layers, **kwargs): """ - Constructor for the DenseRagged class. + Constructor for the DenseRaggedBlock class. Parameters: dense_ragged_layers (list): a list of DenseRagged layers :class:`~gudhi.tensorflow.DenseRagged`. @@ -135,7 +135,7 @@ def __init__(self, perm_op, **kwargs): Constructor for the PermopRagged class. Parameters: - perm_op: permutation invariant function, such as `tf.math.reduce_sum`, `tf.math.reduce_mean`, `tf.math.reduce_max`, `tf.math.reduce_min`, or a custom function. + perm_op: permutation invariant function, such as `tf.math.reduce_sum`, `tf.math.reduce_mean`. """ super().__init__(dynamic=True, **kwargs) self._supports_ragged_inputs = True @@ -158,15 +158,15 @@ class RipsNet(tf.keras.Model): This class implements the RipsNet described in the following article . """ - def __init__(self, phi_1, perm_op, phi_2, input_dim, **kwargs): + def __init__(self, phi_1, phi_2, input_dim, perm_op='mean', **kwargs): """ Constructor for the RipsNet class. Parameters: phi_1 (layers): any block of DenseRagged layers. Can be :class:`~gudhi.tensorflow.DenseRaggedBlock`, or a custom block built from :class:`~gudhi.tensorflow.DenseRagged` layers. - perm_op (layer): layer for the permutation invariant function. Can be :class:`~gudhi.tensorflow.PermopRagged`. phi_2 (layers): Can be any (block of) TensorFlow layer(s), e.g. :class:`~gudhi.tensorflow.TFBlock`. input_dim (int): dimension of the input point clouds. + perm_op (str): Permutation invariant operation. Can be 'mean' or 'sum'. """ super().__init__(dynamic=True, **kwargs) self.phi_1 = phi_1 @@ -174,6 +174,9 @@ def __init__(self, phi_1, perm_op, phi_2, input_dim, **kwargs): self.phi_2 = phi_2 self.input_dim = input_dim + if perm_op not in ['mean', 'sum']: + raise ValueError(f'Permutation invariant operation: {self.pop} is not allowed, must be "mean" or "sum".') + def build(self, input_shape): return self @@ -187,9 +190,16 @@ def call(self, pointclouds): Returns: output (n x output_shape): tensor containing predicted vectorizations of the persistence diagrams of pointclouds. """ + if self.pop == 'mean': + pop_ragged = PermopRagged(tf.math.reduce_mean) + elif self.pop == 'sum': + pop_ragged = PermopRagged(tf.math.reduce_sum) + else: + raise ValueError(f'Permutation invariant operation: {self.pop} is not allowed, must be "mean" or "sum".') + inputs = tf.keras.layers.InputLayer(input_shape=(None, self.input_dim), dtype="float32", ragged=True)( pointclouds) output = self.phi_1(inputs) - output = self.pop(output) + output = pop_ragged(output) output = self.phi_2(output) return output \ No newline at end of file From 8ff14bfe368b2b529aa87acbe954b83097605e11 Mon Sep 17 00:00:00 2001 From: Felix Hensel Date: Tue, 1 Mar 2022 15:03:54 +0100 Subject: [PATCH 04/31] added test --- src/python/gudhi/tensorflow/ripsnet.py | 10 ++- src/python/test/test_ripsnet.py | 96 ++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 src/python/test/test_ripsnet.py diff --git a/src/python/gudhi/tensorflow/ripsnet.py b/src/python/gudhi/tensorflow/ripsnet.py index 789c8c658c..b8850ce764 100644 --- a/src/python/gudhi/tensorflow/ripsnet.py +++ b/src/python/gudhi/tensorflow/ripsnet.py @@ -14,7 +14,7 @@ class DenseRagged(tf.keras.layers.Layer): This is a class for the ragged layer in the RipsNet architecture, processing the input pointclouds. """ - def __init__(self, units, input_dim=None, use_bias=True, activation='gelu', **kwargs): + def __init__(self, units, input_dim=None, use_bias=True, activation='gelu', kernel_initializer=None, bias_initializer=None, **kwargs): """ Constructor for the DenseRagged class. @@ -22,18 +22,22 @@ def __init__(self, units, input_dim=None, use_bias=True, activation='gelu', **kw units (int): number of units in the layer. use_bias (bool): flag, indicating whether to use bias or not. activation (string or function): identifier of a keras activation function, e.g. 'relu'. + kernel_initializer: tensorflow kernel initializer. + bias_initializer: tensorflow bias initializer. """ super().__init__(dynamic=True, **kwargs) self._supports_ragged_inputs = True self.units = units self.use_bias = use_bias self.activation = tf.keras.activations.get(activation) + self.kernel_initializer = kernel_initializer + self.bias_initializer = bias_initializer def build(self, input_shape): last_dim = input_shape[-1] - self.kernel = self.add_weight('kernel', shape=[last_dim, self.units], trainable=True) + self.kernel = self.add_weight('kernel', shape=[last_dim, self.units], trainable=True, initializer=self.kernel_initializer) if self.use_bias: - self.bias = self.add_weight('bias', shape=[self.units, ], trainable=True) + self.bias = self.add_weight('bias', shape=[self.units, ], trainable=True, initializer=self.bias_initializer) else: self.bias = None super().build(input_shape) diff --git a/src/python/test/test_ripsnet.py b/src/python/test/test_ripsnet.py new file mode 100644 index 0000000000..39ccb686c9 --- /dev/null +++ b/src/python/test/test_ripsnet.py @@ -0,0 +1,96 @@ +""" This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + Author(s): Felix Hensel + Copyright (C) 2022 Inria + Modification(s): + - YYYY/MM Author: Description of the modification +""" + +import numpy as np +import tensorflow as tf +from gudhi.tensorflow.ripsnet import * + +def test_ripsnet(): + input_dim = 2 + ragged_layers_size = [10] + dense_layers_size = [10] + output_units = 2 + activation_fct = 'gelu' + output_activation = 'sigmoid' + dropout = 0 + kernel_regularization = 0 + + ragged_layers = [] + dense_layers = [] + for n_units in ragged_layers_size: + ragged_layers.append(DenseRagged(units=n_units, use_bias=True, activation=activation_fct)) + + for n_units in dense_layers_size: + dense_layers.append(tf.keras.layers.Dense(n_units, activation=activation_fct)) + + dense_layers.append(tf.keras.layers.Dense(output_units, activation=output_activation)) + + weights_vect = [np.array([[-0.82675004, 0.082853, -0.7522571, -0.86455333], + [0.01191106, 0.4168273, 0.13942415, -0.95728004]]), + np.array([-0.48964924, 0.07440309, -0.84914917, -0.13673241]), + np.array([[0.6735323, -0.86710656, -0.6601294, 0.7874811], + [-0.5002684, -0.36409453, 0.5563092, -0.16215993], + [0.31057122, -0.79817116, -0.40550056, 0.8173016], + [0.64593965, 0.5079483, -0.5176215, 0.5098056]]), + np.array([0.68127185, -0.03681624, -0.46403527, 0.42004192]), + np.array([[-0.23722765, 0.18152483, 0.42457554, 0.55383813], + [0.11030384, -0.72890997, 0.81839615, -0.26585487], + [-0.16284865, -0.22054555, -0.03428887, -0.10710541], + [-0.6388146, -0.6412578, -0.05625373, 0.6325129]]), + np.array([0.31346196, 0.32782924, 0.18332976, -0.4718462])] + + phi_1 = DenseRaggedBlock(ragged_layers) + perm_op = 'mean' + phi_2 = TFBlock(dense_layers) + input_dim = 2 + + model = RipsNet(phi_1, phi_2, input_dim, perm_op=perm_op) + + model.set_weights(weights_vect) + + clean_data_test = [np.array([[ 8.00690107, 5.84065138], + [-10.01871508, 5.34973559], + [ -9.65317915, 5.53776047], + [ 7.78598811, 7.76091515]]), + np.array([[ 9.95347807, 13.72474594], + [ -3.40168368, -15.23953774], + [ -8.73604688, 6.76905796]]), + np.array([[ 3.25849098, -4.01998912], + [ 2.23853692, -2.96113938], + [ 3.38496086, -4.47234084], + [ 2.13325439, -2.91673025]])] + + noisy_data_test = [np.array([[-15.49749451, -4.81412853], + [-10.02162025, 0.42705654], + [-14.41883655, 10.24555917], + [ -8.6865668 , 6.12261606]]), + np.array([[-3.14624955, 2.59008951], + [-3.88721637, 0.65461877], + [-3.16411703, 0.44306022], + [-2.75523434, 1.57533588]]), + np.array([[ 10.14006214, 10.92637587], + [-13.75312184, -2.09110089], + [ -7.83379779, 0.76465675]])] + + tf_clean_data_test = tf.ragged.constant([ + [list(c) for c in list(clean_data_test[i])] for i in range(len(clean_data_test))], ragged_rank=1) + tf_noisy_data_test = tf.ragged.constant([ + [list(c) for c in list(noisy_data_test[i])] for i in range(len(noisy_data_test))], ragged_rank=1) + + clean_prediction = np.array([[0.3942722, 0.95260733, 0.9447353, 0.00872418], + [0.70753145, 0.74972904, 0.45573318, 0.25137717], + [0.6459818, 0.5658171, 0.19202137, 0.6256107 ]]) + noisy_prediction = np.array([[0.5073361, 0.93115664, 0.89942205, 0.01895535], + [0.4248793, 0.8612615, 0.7404877, 0.09027195], + [0.36387908, 0.9295354, 0.90001035, 0.01976481]]) + + assert(clean_prediction.shape == model.predict(tf_clean_data_test).shape) + assert(noisy_prediction.shape == model.predict(tf_noisy_data_test).shape) + assert((clean_prediction == model.predict(tf_clean_data_test)).all()) + assert((noisy_prediction == model.predict(tf_noisy_data_test)).all()) + return From 9eb323102506800e00e67c7b35cb75250483dc37 Mon Sep 17 00:00:00 2001 From: Felix Hensel Date: Tue, 1 Mar 2022 15:56:24 +0100 Subject: [PATCH 05/31] added test --- src/python/doc/ripsnet.inc | 2 +- src/python/test/test_ripsnet.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/python/doc/ripsnet.inc b/src/python/doc/ripsnet.inc index 9b07077439..c70167e13c 100644 --- a/src/python/doc/ripsnet.inc +++ b/src/python/doc/ripsnet.inc @@ -11,5 +11,5 @@ | | | | | | | | +----------------------------------------------------------------+-------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------+ - | * :doc:`ripsnet` | * :doc:`ripsnet` | + | * :doc:`ripsnet` | | +----------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ diff --git a/src/python/test/test_ripsnet.py b/src/python/test/test_ripsnet.py index 39ccb686c9..637d98d330 100644 --- a/src/python/test/test_ripsnet.py +++ b/src/python/test/test_ripsnet.py @@ -93,4 +93,4 @@ def test_ripsnet(): assert(noisy_prediction.shape == model.predict(tf_noisy_data_test).shape) assert((clean_prediction == model.predict(tf_clean_data_test)).all()) assert((noisy_prediction == model.predict(tf_noisy_data_test)).all()) - return + return \ No newline at end of file From 8e5003f37d843d92b8fd6f59e275ac0d3ae607e5 Mon Sep 17 00:00:00 2001 From: Felix Hensel Date: Wed, 2 Mar 2022 11:28:18 +0100 Subject: [PATCH 06/31] updated test --- src/python/gudhi/tensorflow/ripsnet.py | 2 +- src/python/test/test_ripsnet.py | 122 ++++++++++++++----------- 2 files changed, 69 insertions(+), 55 deletions(-) diff --git a/src/python/gudhi/tensorflow/ripsnet.py b/src/python/gudhi/tensorflow/ripsnet.py index b8850ce764..95376bd5e8 100644 --- a/src/python/gudhi/tensorflow/ripsnet.py +++ b/src/python/gudhi/tensorflow/ripsnet.py @@ -206,4 +206,4 @@ def call(self, pointclouds): output = self.phi_1(inputs) output = pop_ragged(output) output = self.phi_2(output) - return output \ No newline at end of file + return output diff --git a/src/python/test/test_ripsnet.py b/src/python/test/test_ripsnet.py index 637d98d330..ca77d6b13b 100644 --- a/src/python/test/test_ripsnet.py +++ b/src/python/test/test_ripsnet.py @@ -10,39 +10,43 @@ import tensorflow as tf from gudhi.tensorflow.ripsnet import * + def test_ripsnet(): - input_dim = 2 - ragged_layers_size = [10] - dense_layers_size = [10] - output_units = 2 + ragged_layers_size = [4] + dense_layers_size = [4] + output_units = 4 activation_fct = 'gelu' output_activation = 'sigmoid' dropout = 0 kernel_regularization = 0 + initializer = None# tf.keras.initializers.Constant(value=1) ragged_layers = [] dense_layers = [] for n_units in ragged_layers_size: - ragged_layers.append(DenseRagged(units=n_units, use_bias=True, activation=activation_fct)) + ragged_layers.append(DenseRagged(units=n_units, use_bias=True, activation=activation_fct, + kernel_initializer=initializer, bias_initializer=initializer)) for n_units in dense_layers_size: - dense_layers.append(tf.keras.layers.Dense(n_units, activation=activation_fct)) - - dense_layers.append(tf.keras.layers.Dense(output_units, activation=output_activation)) - - weights_vect = [np.array([[-0.82675004, 0.082853, -0.7522571, -0.86455333], - [0.01191106, 0.4168273, 0.13942415, -0.95728004]]), - np.array([-0.48964924, 0.07440309, -0.84914917, -0.13673241]), - np.array([[0.6735323, -0.86710656, -0.6601294, 0.7874811], - [-0.5002684, -0.36409453, 0.5563092, -0.16215993], - [0.31057122, -0.79817116, -0.40550056, 0.8173016], - [0.64593965, 0.5079483, -0.5176215, 0.5098056]]), - np.array([0.68127185, -0.03681624, -0.46403527, 0.42004192]), - np.array([[-0.23722765, 0.18152483, 0.42457554, 0.55383813], - [0.11030384, -0.72890997, 0.81839615, -0.26585487], - [-0.16284865, -0.22054555, -0.03428887, -0.10710541], - [-0.6388146, -0.6412578, -0.05625373, 0.6325129]]), - np.array([0.31346196, 0.32782924, 0.18332976, -0.4718462])] + dense_layers.append(tf.keras.layers.Dense(n_units, activation=activation_fct, + kernel_initializer=initializer, bias_initializer=initializer)) + + dense_layers.append(tf.keras.layers.Dense(output_units, activation=output_activation, + kernel_initializer=initializer, bias_initializer=initializer)) + + weights_vect = [np.array([[-0.3868327 , 0.5431584 , 0.7523476 , 0.80209386], + [ 0.22491306, 0.4626178 , 0.34193814, -0.04737851]]), + np.array([ 0.5047069 , -0.11543324, -0.03882882, -0.16129738]), + np.array([[-0.7956421 , 0.2326832 , -0.5405302 , 0.096256964], + [ 0.06973686 , 0.0251764 , -0.05733281 , 0.3528394 ], + [-0.77462643 , 0.03330394 , -0.8688136 , -0.22296508 ], + [-0.5054477 , 0.7201048 , 0.1857564 , 0.65894866 ]]), + np.array([-0.30565566 , -0.77507186 , -0.049963538, 0.5765676 ]), + np.array([[-0.25560755 , 0.71504813 , 0.0047909063, -0.1595783 ], + [-0.71575665 , 0.6139034 , -0.47060093 , 0.087501734 ], + [ 0.1588738 , -0.593038 , 0.48378325 , -0.777213 ], + [ 0.6206032 , -0.20880768 , 0.14528894 , 0.18696047 ]]), + np.array([-0.17761804, -0.6905532 , 0.64367545, -0.2173939 ])] phi_1 = DenseRaggedBlock(ragged_layers) perm_op = 'mean' @@ -51,46 +55,56 @@ def test_ripsnet(): model = RipsNet(phi_1, phi_2, input_dim, perm_op=perm_op) + test_input_raw = [np.array([[1.,2.],[3.,4.]])] + + test_input = tf.ragged.constant([ + [list(c) for c in list(test_input_raw[i])] for i in range(len(test_input_raw))], ragged_rank=1) + + model.predict(test_input) + model.set_weights(weights_vect) - clean_data_test = [np.array([[ 8.00690107, 5.84065138], - [-10.01871508, 5.34973559], - [ -9.65317915, 5.53776047], - [ 7.78598811, 7.76091515]]), - np.array([[ 9.95347807, 13.72474594], - [ -3.40168368, -15.23953774], - [ -8.73604688, 6.76905796]]), - np.array([[ 3.25849098, -4.01998912], - [ 2.23853692, -2.96113938], - [ 3.38496086, -4.47234084], - [ 2.13325439, -2.91673025]])] - - noisy_data_test = [np.array([[-15.49749451, -4.81412853], - [-10.02162025, 0.42705654], - [-14.41883655, 10.24555917], - [ -8.6865668 , 6.12261606]]), - np.array([[-3.14624955, 2.59008951], - [-3.88721637, 0.65461877], - [-3.16411703, 0.44306022], - [-2.75523434, 1.57533588]]), - np.array([[ 10.14006214, 10.92637587], - [-13.75312184, -2.09110089], - [ -7.83379779, 0.76465675]])] + + clean_data_test = [np.array([[ -7.04493841, 9.60285858], + [-13.14389003, -13.21854157], + [ -3.21137961, -1.28593644]]), + np.array([[ 10.40324933, -0.80540584], + [ 16.54752459, 0.70355361], + [ 6.410207 , -10.63175183], + [ 2.96613799, -11.97463568]]), + np.array([[ 4.85041719, -2.93820024], + [ 2.15379915, -5.39669696], + [ 5.83968556, -5.67350982], + [ 5.25955172, -6.36860269]])] + + noisy_data_test = [np.array([[ -8.93311026, 1.52317533], + [-16.80344139, -3.76871298], + [-11.58448573, -2.76311122], + [-15.06107796, 5.05253587]]), + np.array([[-3.834947 , -5.1897498 ], + [-3.51701182, -4.23539191], + [-2.68678747, -1.63902703], + [-4.65070816, -3.96363227]]), + np.array([[ 4.7841113 , 19.2922069 ], + [10.5164214 , 5.50246605], + [-9.38163622, 7.03682948]])] tf_clean_data_test = tf.ragged.constant([ [list(c) for c in list(clean_data_test[i])] for i in range(len(clean_data_test))], ragged_rank=1) tf_noisy_data_test = tf.ragged.constant([ [list(c) for c in list(noisy_data_test[i])] for i in range(len(noisy_data_test))], ragged_rank=1) - clean_prediction = np.array([[0.3942722, 0.95260733, 0.9447353, 0.00872418], - [0.70753145, 0.74972904, 0.45573318, 0.25137717], - [0.6459818, 0.5658171, 0.19202137, 0.6256107 ]]) - noisy_prediction = np.array([[0.5073361, 0.93115664, 0.89942205, 0.01895535], - [0.4248793, 0.8612615, 0.7404877, 0.09027195], - [0.36387908, 0.9295354, 0.90001035, 0.01976481]]) + clean_prediction = np.array([[0.5736222, 0.3047213, 0.6746019, 0.49468565], + [0.4522748, 0.7521156, 0.3061385, 0.77519494], + [0.5349713, 0.49940312, 0.51753736, 0.6435147]]) + noisy_prediction = np.array([[0.53986603, 0.33934325, 0.64809155, 0.49939266], + [0.5637899, 0.28744557, 0.67097586, 0.50240993], + [0.5689339, 0.55574864, 0.47079712, 0.68721807]]) + + #print(np.linalg.norm(clean_prediction - model.predict(tf_clean_data_test))) assert(clean_prediction.shape == model.predict(tf_clean_data_test).shape) assert(noisy_prediction.shape == model.predict(tf_noisy_data_test).shape) - assert((clean_prediction == model.predict(tf_clean_data_test)).all()) - assert((noisy_prediction == model.predict(tf_noisy_data_test)).all()) - return \ No newline at end of file + assert(np.linalg.norm(clean_prediction - model.predict(tf_clean_data_test)) <= 1e-7) + assert(np.linalg.norm(noisy_prediction - model.predict(tf_noisy_data_test)) <= 1e-7) + return From 496a1416c383df1601c88cde47e682d48baceecb Mon Sep 17 00:00:00 2001 From: Felix Hensel <50261948+hensel-f@users.noreply.github.com> Date: Mon, 7 Mar 2022 12:06:42 +0100 Subject: [PATCH 07/31] Update src/python/doc/ripsnet.inc License update Co-authored-by: Vincent Rouvreau <10407034+VincentRouvreau@users.noreply.github.com> --- src/python/doc/ripsnet.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/doc/ripsnet.inc b/src/python/doc/ripsnet.inc index c70167e13c..8eb62429fe 100644 --- a/src/python/doc/ripsnet.inc +++ b/src/python/doc/ripsnet.inc @@ -6,7 +6,7 @@ | | persistent homology of point clouds. | | | | | :Since: GUDHI | | | | | - | | | :License: MIT (`GPL v3 `_) | + | | | :License: MIT | | | | | | | | | | | | | From fcf7edf74eaf5ea223d88057f6b254c776aa9725 Mon Sep 17 00:00:00 2001 From: Felix Hensel <50261948+hensel-f@users.noreply.github.com> Date: Mon, 7 Mar 2022 12:13:41 +0100 Subject: [PATCH 08/31] Fixed imports Co-authored-by: Vincent Rouvreau <10407034+VincentRouvreau@users.noreply.github.com> --- src/python/doc/ripsnet.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/python/doc/ripsnet.rst b/src/python/doc/ripsnet.rst index 79852e11d9..64061d925f 100644 --- a/src/python/doc/ripsnet.rst +++ b/src/python/doc/ripsnet.rst @@ -20,7 +20,7 @@ Example This example instantiates a RipsNet model which can then be trained as any tensorflow model. .. testcode:: - from gudhi import ripsnet + from gudhi.tensorflow import * from tensorflow.keras import regularizers, layers ragged_layers_size = [30,20,10] @@ -35,14 +35,14 @@ This example instantiates a RipsNet model which can then be trained as any tenso dense_layers = [] for n_units in ragged_layers_size: - ragged_layers.append(DenseRagged(units=n_units, use_bias=True, activation=activation_fct)) + ragged_layers.append(ripsnet.DenseRagged(units=n_units, use_bias=True, activation=activation_fct)) for n_units in dense_layers_size: - dense_layers.append(tf.keras.layers.Dense(n_units, activation=activation_fct, + dense_layers.append(layers.Dense(n_units, activation=activation_fct, kernel_regularizer=regularizers.l2(kernel_regularization))) - dense_layers.append(tf.keras.layers.Dropout(dropout)) + dense_layers.append(layers.Dropout(dropout)) - dense_layers.append(tf.keras.layers.Dense(output_units, activation=output_activation)) + dense_layers.append(layers.Dense(output_units, activation=output_activation)) phi_1 = DenseRaggedBlock(ragged_layers) perm_op = 'mean' # can also be 'sum'. From e23e929de2551c6ed72a62fa4a57fe0a8d750ddb Mon Sep 17 00:00:00 2001 From: Felix Hensel <50261948+hensel-f@users.noreply.github.com> Date: Mon, 7 Mar 2022 12:19:45 +0100 Subject: [PATCH 09/31] Fixed imports Co-authored-by: Vincent Rouvreau <10407034+VincentRouvreau@users.noreply.github.com> --- src/python/doc/ripsnet.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/python/doc/ripsnet.rst b/src/python/doc/ripsnet.rst index 64061d925f..e87a872792 100644 --- a/src/python/doc/ripsnet.rst +++ b/src/python/doc/ripsnet.rst @@ -44,9 +44,9 @@ This example instantiates a RipsNet model which can then be trained as any tenso dense_layers.append(layers.Dense(output_units, activation=output_activation)) - phi_1 = DenseRaggedBlock(ragged_layers) + phi_1 = ripsnet.DenseRaggedBlock(ragged_layers) perm_op = 'mean' # can also be 'sum'. phi_2 = TFBlock(dense_layers) input_dim = 2 - RN = RipsNet(phi_1, phi_2, input_dim, perm_op=perm_op) + RN = ripsnet.RipsNet(phi_1, phi_2, input_dim, perm_op=perm_op) From 25b842e606a9b25c9af894f9277273746aeb0e1c Mon Sep 17 00:00:00 2001 From: Felix Hensel Date: Mon, 7 Mar 2022 12:21:48 +0100 Subject: [PATCH 10/31] updated documentation --- src/python/doc/ripsnet.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/python/doc/ripsnet.rst b/src/python/doc/ripsnet.rst index e87a872792..f8c5e816ed 100644 --- a/src/python/doc/ripsnet.rst +++ b/src/python/doc/ripsnet.rst @@ -50,3 +50,11 @@ This example instantiates a RipsNet model which can then be trained as any tenso input_dim = 2 RN = ripsnet.RipsNet(phi_1, phi_2, input_dim, perm_op=perm_op) + + +Detailed documentation +---------------------- +.. automodule:: gudhi.tensorflow.ripsnet + :members: + :special-members: + :show-inheritance: From f01e3cc7639085c521e32f746f83df90eef267bb Mon Sep 17 00:00:00 2001 From: Felix Hensel Date: Mon, 7 Mar 2022 15:36:11 +0100 Subject: [PATCH 11/31] updated CMakeLists.txt --- src/python/CMakeLists.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt index 8eb7478ecc..94835e921f 100644 --- a/src/python/CMakeLists.txt +++ b/src/python/CMakeLists.txt @@ -70,6 +70,7 @@ if(PYTHONINTERP_FOUND) set(GUDHI_PYTHON_MODULES "${GUDHI_PYTHON_MODULES}'euclidean_strong_witness_complex', ") # Modules that should not be auto-imported in __init__.py set(GUDHI_PYTHON_MODULES_EXTRA "${GUDHI_PYTHON_MODULES_EXTRA}'representations', ") + set(GUDHI_PYTHON_MODULES_EXTRA "${GUDHI_PYTHON_MODULES_EXTRA}'tensorflow', ") set(GUDHI_PYTHON_MODULES_EXTRA "${GUDHI_PYTHON_MODULES_EXTRA}'wasserstein', ") set(GUDHI_PYTHON_MODULES_EXTRA "${GUDHI_PYTHON_MODULES_EXTRA}'point_cloud', ") set(GUDHI_PYTHON_MODULES_EXTRA "${GUDHI_PYTHON_MODULES_EXTRA}'weighted_rips_complex', ") @@ -285,6 +286,7 @@ if(PYTHONINTERP_FOUND) file(COPY "gudhi/dtm_rips_complex.py" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/gudhi") file(COPY "gudhi/hera/__init__.py" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/gudhi/hera") file(COPY "gudhi/datasets" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/gudhi" FILES_MATCHING PATTERN "*.py") + file(COPY "gudhi/tensorflow" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/gudhi") # Some files for pip package @@ -551,6 +553,11 @@ if(PYTHONINTERP_FOUND) add_gudhi_py_test(test_representations) endif() + # Differentiation + if(TENSORFLOW_FOUND) + add_gudhi_py_test(test_diff) + endif() + # Betti curves if(SKLEARN_FOUND AND SCIPY_FOUND) add_gudhi_py_test(test_betti_curve_representations) From 9281794fc40b187d5fb5ab3e31f5e45ec64dd3a9 Mon Sep 17 00:00:00 2001 From: Felix Hensel Date: Mon, 7 Mar 2022 16:17:52 +0100 Subject: [PATCH 12/31] updated documentation and bibliography --- biblio/bibliography.bib | 35 +++++++++++++++++++ src/python/doc/ripsnet.rst | 69 +++++++++++++++++++++++++++++--------- 2 files changed, 89 insertions(+), 15 deletions(-) diff --git a/biblio/bibliography.bib b/biblio/bibliography.bib index b5afff5202..20985daf07 100644 --- a/biblio/bibliography.bib +++ b/biblio/bibliography.bib @@ -1,3 +1,38 @@ +@article{RipsNet_arXiv, + author = {Thibault de Surrel and + Felix Hensel and + Mathieu Carri{\`{e}}re and + Th{\'{e}}o Lacombe and + Yuichi Ike and + Hiroaki Kurihara and + Marc Glisse and + Fr{\'{e}}d{\'{e}}ric Chazal}, + title = {RipsNet: a general architecture for fast and robust estimation of + the persistent homology of point clouds}, + journal = {CoRR}, + volume = {abs/2202.01725}, + year = {2022}, + url = {https://arxiv.org/abs/2202.01725}, + eprinttype = {arXiv}, + eprint = {2202.01725}, + timestamp = {Wed, 09 Feb 2022 15:43:35 +0100}, + biburl = {https://dblp.org/rec/journals/corr/abs-2202-01725.bib}, + bibsource = {dblp computer science bibliography, https://dblp.org} +} + +@inproceedings{DeepSets17, + author = {Zaheer, Manzil and Kottur, Satwik and Ravanbakhsh, Siamak and Poczos, Barnabas and Salakhutdinov, Russ R and Smola, Alexander J}, + booktitle = {Advances in Neural Information Processing Systems}, + editor = {I. Guyon and U. V. Luxburg and S. Bengio and H. Wallach and R. Fergus and S. Vishwanathan and R. Garnett}, + pages = {}, + publisher = {Curran Associates, Inc.}, + title = {Deep Sets}, + url = {https://proceedings.neurips.cc/paper/2017/file/f22e4747da1aa27e363d86d40ff442fe-Paper.pdf}, + volume = {30}, + year = {2017} +} + + @inproceedings{gudhilibrary_ICMS14, author = {Cl\'ement Maria and Jean-Daniel Boissonnat and Marc Glisse and Mariette Yvinec}, diff --git a/src/python/doc/ripsnet.rst b/src/python/doc/ripsnet.rst index f8c5e816ed..a0c839dca6 100644 --- a/src/python/doc/ripsnet.rst +++ b/src/python/doc/ripsnet.rst @@ -12,7 +12,7 @@ Definition :class:`~gudhi.ripsnet` constructs a Tensorflow model for fast and robust estimation of persistent homology of point clouds. RipsNet is based on a `Deep Sets `_ -architecture, for details see `RipsNet `_. +architecture :cite:`DeepSets17`, for details see the paper `RipsNet `_ :cite:`RipsNet_arXiv`. Example ------------------- @@ -21,36 +21,75 @@ This example instantiates a RipsNet model which can then be trained as any tenso .. testcode:: from gudhi.tensorflow import * + import tensorflow as tf from tensorflow.keras import regularizers, layers - - ragged_layers_size = [30,20,10] - dense_layers_size = [50,100,200] - output_units = 2500 - activation_fct = 'gelu' - output_activation = 'sigmoid' - dropout = 0 + import numpy as np + + ragged_layers_size = [20, 10] + dense_layers_size = [10, 20] + output_units = 25 + activation_fct = 'gelu' + output_activation = 'sigmoid' + dropout = 0 kernel_regularization = 0 ragged_layers = [] dense_layers = [] for n_units in ragged_layers_size: - ragged_layers.append(ripsnet.DenseRagged(units=n_units, use_bias=True, activation=activation_fct)) + ragged_layers.append(DenseRagged(units=n_units, use_bias=True, activation=activation_fct)) for n_units in dense_layers_size: dense_layers.append(layers.Dense(n_units, activation=activation_fct, - kernel_regularizer=regularizers.l2(kernel_regularization))) + kernel_regularizer=regularizers.l2(kernel_regularization))) dense_layers.append(layers.Dropout(dropout)) dense_layers.append(layers.Dense(output_units, activation=output_activation)) - phi_1 = ripsnet.DenseRaggedBlock(ragged_layers) - perm_op = 'mean' # can also be 'sum'. - phi_2 = TFBlock(dense_layers) + phi_1 = DenseRaggedBlock(ragged_layers) + perm_op = 'mean' # can also be 'sum'. + phi_2 = TFBlock(dense_layers) input_dim = 2 - RN = ripsnet.RipsNet(phi_1, phi_2, input_dim, perm_op=perm_op) - + RN = RipsNet(phi_1, phi_2, input_dim, perm_op=perm_op) + + data_test = [np.array([[-7.04493841, 9.60285858], + [-13.14389003, -13.21854157], + [-3.21137961, -1.28593644]]), + np.array([[10.40324933, -0.80540584], + [16.54752459, 0.70355361], + [6.410207, -10.63175183], + [2.96613799, -11.97463568]]), + np.array([[4.85041719, -2.93820024], + [2.15379915, -5.39669696], + [5.83968556, -5.67350982], + [5.25955172, -6.36860269]])] + + tf_data_test = tf.ragged.constant([ + [list(c) for c in list(data_test[i])] for i in range(len(data_test))], ragged_rank=1) + + print(RN.predict(tf_data_test)) + +Once RN is properly trained (which we skip in this documentation) it can be used to make predictions. +A possible output is: + +.. testoutput:: + + [[0.58554363 0.6054868 0.44672886 0.5216672 0.5814481 0.48068565 + 0.49626726 0.5285395 0.4805212 0.37918684 0.49745193 0.49247316 + 0.4706078 0.5491477 0.47016636 0.55804974 0.46501246 0.4065692 + 0.5386659 0.5660226 0.52014357 0.5329493 0.52178216 0.5156043 + 0.48742113] + [0.9446074 0.99024785 0.1316272 0.3013248 0.98174655 0.52285945 + 0.33727515 0.997285 0.3711884 0.00388432 0.63181967 0.5377489 + 0.22074646 0.7681194 0.04337704 0.80116796 0.02139336 0.04605395 + 0.8911999 0.9570045 0.5789719 0.8221929 0.7742506 0.4596561 + 0.08529088] + [0.8230771 0.9320036 0.25120026 0.48027694 0.8988322 0.5789062 + 0.38307947 0.9252455 0.39485127 0.06090912 0.5786307 0.51115406 + 0.28706372 0.70552015 0.16929033 0.7028084 0.12379596 0.1867683 + 0.6969584 0.84437454 0.6172329 0.66728634 0.630455 0.47643042 + 0.27172992]] Detailed documentation ---------------------- From 6b9ecce770e0e8bfcff73e3aa99979b411897ca6 Mon Sep 17 00:00:00 2001 From: Felix Hensel Date: Thu, 10 Mar 2022 10:38:51 +0100 Subject: [PATCH 13/31] updated CMakeLists.txt --- src/python/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt index 94835e921f..cc9aaaae9f 100644 --- a/src/python/CMakeLists.txt +++ b/src/python/CMakeLists.txt @@ -555,7 +555,7 @@ if(PYTHONINTERP_FOUND) # Differentiation if(TENSORFLOW_FOUND) - add_gudhi_py_test(test_diff) + add_gudhi_py_test(test_ripsnet) endif() # Betti curves From ec19f2db7fe1629f6f29865eb815a9d507538f92 Mon Sep 17 00:00:00 2001 From: Felix Hensel <50261948+hensel-f@users.noreply.github.com> Date: Thu, 10 Mar 2022 10:40:35 +0100 Subject: [PATCH 14/31] Update src/python/doc/ripsnet.inc Co-authored-by: Vincent Rouvreau <10407034+VincentRouvreau@users.noreply.github.com> --- src/python/doc/ripsnet.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/doc/ripsnet.inc b/src/python/doc/ripsnet.inc index 8eb62429fe..c2342749fe 100644 --- a/src/python/doc/ripsnet.inc +++ b/src/python/doc/ripsnet.inc @@ -8,7 +8,7 @@ | | | | | | | :License: MIT | | | | | - | | | | + | | | :Requires: `TensorFlow `_ | | | | | +----------------------------------------------------------------+-------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------+ | * :doc:`ripsnet` | | From aef67bd259963937f66cf1b143917f59e15be4d2 Mon Sep 17 00:00:00 2001 From: Felix Hensel Date: Thu, 10 Mar 2022 13:26:14 +0100 Subject: [PATCH 15/31] documentation fix --- src/python/doc/installation.rst | 2 ++ src/python/doc/ripsnet.rst | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/python/doc/installation.rst b/src/python/doc/installation.rst index 35c344e3b5..fee74fd531 100644 --- a/src/python/doc/installation.rst +++ b/src/python/doc/installation.rst @@ -395,6 +395,8 @@ TensorFlow `TensorFlow `_ is currently only used in some automatic differentiation tests. +:doc:`RipsNet ` module requires `TensorFlow `_. + Bug reports and contributions ***************************** diff --git a/src/python/doc/ripsnet.rst b/src/python/doc/ripsnet.rst index a0c839dca6..559027ed53 100644 --- a/src/python/doc/ripsnet.rst +++ b/src/python/doc/ripsnet.rst @@ -11,8 +11,7 @@ Definition :class:`~gudhi.ripsnet` constructs a Tensorflow model for fast and robust estimation of persistent homology of point clouds. -RipsNet is based on a `Deep Sets `_ -architecture :cite:`DeepSets17`, for details see the paper `RipsNet `_ :cite:`RipsNet_arXiv`. +RipsNet is based on a Deep Sets architecture :cite:`DeepSets17`, for details see the paper RipsNet :cite:`RipsNet_arXiv`. Example ------------------- From 28d7320a57c009a05f4bde1dc2945ff60a4be80d Mon Sep 17 00:00:00 2001 From: Felix Hensel Date: Thu, 10 Mar 2022 13:54:20 +0100 Subject: [PATCH 16/31] increased error margin from 1e-7 to 1e-6 --- src/python/test/test_ripsnet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/python/test/test_ripsnet.py b/src/python/test/test_ripsnet.py index ca77d6b13b..7367126fa9 100644 --- a/src/python/test/test_ripsnet.py +++ b/src/python/test/test_ripsnet.py @@ -105,6 +105,6 @@ def test_ripsnet(): assert(clean_prediction.shape == model.predict(tf_clean_data_test).shape) assert(noisy_prediction.shape == model.predict(tf_noisy_data_test).shape) - assert(np.linalg.norm(clean_prediction - model.predict(tf_clean_data_test)) <= 1e-7) - assert(np.linalg.norm(noisy_prediction - model.predict(tf_noisy_data_test)) <= 1e-7) + assert(np.linalg.norm(clean_prediction - model.predict(tf_clean_data_test)) <= 1e-6) + assert(np.linalg.norm(noisy_prediction - model.predict(tf_noisy_data_test)) <= 1e-6) return From 32ba96bf19fbe0ca99c6ed39a1cb5f7e1b212721 Mon Sep 17 00:00:00 2001 From: Felix Hensel Date: Thu, 10 Mar 2022 16:37:34 +0100 Subject: [PATCH 17/31] fixed imports --- src/python/doc/ripsnet.rst | 2 +- src/python/test/test_ripsnet.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/python/doc/ripsnet.rst b/src/python/doc/ripsnet.rst index 559027ed53..072897bd5a 100644 --- a/src/python/doc/ripsnet.rst +++ b/src/python/doc/ripsnet.rst @@ -19,7 +19,7 @@ Example This example instantiates a RipsNet model which can then be trained as any tensorflow model. .. testcode:: - from gudhi.tensorflow import * + from gudhi.tensorflow import DenseRagged, DenseRaggedBlock, RipsNet import tensorflow as tf from tensorflow.keras import regularizers, layers import numpy as np diff --git a/src/python/test/test_ripsnet.py b/src/python/test/test_ripsnet.py index 7367126fa9..28fcf3b156 100644 --- a/src/python/test/test_ripsnet.py +++ b/src/python/test/test_ripsnet.py @@ -8,7 +8,7 @@ import numpy as np import tensorflow as tf -from gudhi.tensorflow.ripsnet import * +from gudhi.tensorflow.ripsnet import DenseRagged, DenseRaggedBlock, RipsNet def test_ripsnet(): From 33424c8d6edf08d873b773fc348a97ab2e4fe1ad Mon Sep 17 00:00:00 2001 From: Felix Hensel Date: Thu, 10 Mar 2022 17:08:44 +0100 Subject: [PATCH 18/31] fixed imports --- src/python/doc/ripsnet.rst | 2 +- src/python/test/test_ripsnet.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/python/doc/ripsnet.rst b/src/python/doc/ripsnet.rst index 072897bd5a..81757076b7 100644 --- a/src/python/doc/ripsnet.rst +++ b/src/python/doc/ripsnet.rst @@ -19,7 +19,7 @@ Example This example instantiates a RipsNet model which can then be trained as any tensorflow model. .. testcode:: - from gudhi.tensorflow import DenseRagged, DenseRaggedBlock, RipsNet + from gudhi.tensorflow.ripsnet import DenseRagged, DenseRaggedBlock, RipsNet, TFBlock import tensorflow as tf from tensorflow.keras import regularizers, layers import numpy as np diff --git a/src/python/test/test_ripsnet.py b/src/python/test/test_ripsnet.py index 28fcf3b156..714fc404d0 100644 --- a/src/python/test/test_ripsnet.py +++ b/src/python/test/test_ripsnet.py @@ -8,7 +8,7 @@ import numpy as np import tensorflow as tf -from gudhi.tensorflow.ripsnet import DenseRagged, DenseRaggedBlock, RipsNet +from gudhi.tensorflow.ripsnet import DenseRagged, DenseRaggedBlock, RipsNet, TFBlock def test_ripsnet(): From 146b34e2d36df920a84add68ffb287609948f48e Mon Sep 17 00:00:00 2001 From: Felix Hensel Date: Thu, 10 Mar 2022 17:14:39 +0100 Subject: [PATCH 19/31] fixed imports --- src/python/doc/ripsnet.rst | 3 ++- src/python/test/test_ripsnet.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/python/doc/ripsnet.rst b/src/python/doc/ripsnet.rst index 81757076b7..18eeae64ea 100644 --- a/src/python/doc/ripsnet.rst +++ b/src/python/doc/ripsnet.rst @@ -19,7 +19,8 @@ Example This example instantiates a RipsNet model which can then be trained as any tensorflow model. .. testcode:: - from gudhi.tensorflow.ripsnet import DenseRagged, DenseRaggedBlock, RipsNet, TFBlock + + from gudhi.tensorflow import * import tensorflow as tf from tensorflow.keras import regularizers, layers import numpy as np diff --git a/src/python/test/test_ripsnet.py b/src/python/test/test_ripsnet.py index 714fc404d0..0fad46fdd4 100644 --- a/src/python/test/test_ripsnet.py +++ b/src/python/test/test_ripsnet.py @@ -8,7 +8,7 @@ import numpy as np import tensorflow as tf -from gudhi.tensorflow.ripsnet import DenseRagged, DenseRaggedBlock, RipsNet, TFBlock +from gudhi.tensorflow import * def test_ripsnet(): From f29ca81d2d6aa33c9901b026840c93e99be992aa Mon Sep 17 00:00:00 2001 From: Felix Hensel Date: Fri, 11 Mar 2022 10:45:21 +0100 Subject: [PATCH 20/31] fixed testoutput check --- src/python/doc/ripsnet.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/doc/ripsnet.rst b/src/python/doc/ripsnet.rst index 18eeae64ea..f448ac6288 100644 --- a/src/python/doc/ripsnet.rst +++ b/src/python/doc/ripsnet.rst @@ -73,7 +73,7 @@ This example instantiates a RipsNet model which can then be trained as any tenso Once RN is properly trained (which we skip in this documentation) it can be used to make predictions. A possible output is: -.. testoutput:: +.. code-block:: [[0.58554363 0.6054868 0.44672886 0.5216672 0.5814481 0.48068565 0.49626726 0.5285395 0.4805212 0.37918684 0.49745193 0.49247316 From 6edf4718f6b5637e891d1fba4439a326b07fd03a Mon Sep 17 00:00:00 2001 From: Felix Hensel Date: Fri, 11 Mar 2022 11:40:36 +0100 Subject: [PATCH 21/31] removed print statement --- src/python/doc/ripsnet.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/doc/ripsnet.rst b/src/python/doc/ripsnet.rst index f448ac6288..4f218f88e2 100644 --- a/src/python/doc/ripsnet.rst +++ b/src/python/doc/ripsnet.rst @@ -68,7 +68,7 @@ This example instantiates a RipsNet model which can then be trained as any tenso tf_data_test = tf.ragged.constant([ [list(c) for c in list(data_test[i])] for i in range(len(data_test))], ragged_rank=1) - print(RN.predict(tf_data_test)) + RN.predict(tf_data_test) Once RN is properly trained (which we skip in this documentation) it can be used to make predictions. A possible output is: From 59dc2562d397089466102e5925733bb7acd831c2 Mon Sep 17 00:00:00 2001 From: Felix Hensel Date: Tue, 26 Apr 2022 10:34:25 +0200 Subject: [PATCH 22/31] update of gitignore --- .gitignore | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.gitignore b/.gitignore index 6aab7337ed..58374ce5ed 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,11 @@ data/points/human.off_sc.txt .idea/ cmake-build-debug/ +# Poetry files +*.lock +*.toml + +# Ripsnet files +ripsnet_doc_test.py +src/python/gudhi/tensorflow/tutorial.ipynb + From db348e6e188c542238c3bd9f4455726be6090316 Mon Sep 17 00:00:00 2001 From: Felix Hensel Date: Tue, 26 Apr 2022 11:01:47 +0200 Subject: [PATCH 23/31] removede numpy import and added explanation --- src/python/doc/ripsnet.rst | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/python/doc/ripsnet.rst b/src/python/doc/ripsnet.rst index 4f218f88e2..538ace6a33 100644 --- a/src/python/doc/ripsnet.rst +++ b/src/python/doc/ripsnet.rst @@ -23,7 +23,6 @@ This example instantiates a RipsNet model which can then be trained as any tenso from gudhi.tensorflow import * import tensorflow as tf from tensorflow.keras import regularizers, layers - import numpy as np ragged_layers_size = [20, 10] dense_layers_size = [10, 20] @@ -53,24 +52,24 @@ This example instantiates a RipsNet model which can then be trained as any tenso RN = RipsNet(phi_1, phi_2, input_dim, perm_op=perm_op) - data_test = [np.array([[-7.04493841, 9.60285858], - [-13.14389003, -13.21854157], - [-3.21137961, -1.28593644]]), - np.array([[10.40324933, -0.80540584], - [16.54752459, 0.70355361], - [6.410207, -10.63175183], - [2.96613799, -11.97463568]]), - np.array([[4.85041719, -2.93820024], - [2.15379915, -5.39669696], - [5.83968556, -5.67350982], - [5.25955172, -6.36860269]])] - - tf_data_test = tf.ragged.constant([ - [list(c) for c in list(data_test[i])] for i in range(len(data_test))], ragged_rank=1) + data_test = [[[-7.04493841, 9.60285858], + [-13.14389003, -13.21854157], + [-3.21137961, -1.28593644]], + [[10.40324933, -0.80540584], + [16.54752459, 0.70355361], + [6.410207, -10.63175183], + [2.96613799, -11.97463568]], + [[4.85041719, -2.93820024], + [2.15379915, -5.39669696], + [5.83968556, -5.67350982], + [5.25955172, -6.36860269]]] + + tf_data_test = tf.ragged.constant(data_test, ragged_rank=1) RN.predict(tf_data_test) Once RN is properly trained (which we skip in this documentation) it can be used to make predictions. +In this example RipsNet estimates persistence vectorizations (of output size 25) of a list of point clouds (of 3 points) in 2D. A possible output is: .. code-block:: From dd5e94eca86beef86a35b96934dd0a72303d9485 Mon Sep 17 00:00:00 2001 From: Felix Hensel Date: Tue, 26 Apr 2022 11:10:33 +0200 Subject: [PATCH 24/31] changed name of pop to perm_op --- src/python/gudhi/tensorflow/ripsnet.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/python/gudhi/tensorflow/ripsnet.py b/src/python/gudhi/tensorflow/ripsnet.py index 95376bd5e8..d87b25cfc6 100644 --- a/src/python/gudhi/tensorflow/ripsnet.py +++ b/src/python/gudhi/tensorflow/ripsnet.py @@ -143,7 +143,7 @@ def __init__(self, perm_op, **kwargs): """ super().__init__(dynamic=True, **kwargs) self._supports_ragged_inputs = True - self.pop = perm_op + self.perm_op = perm_op def build(self, input_shape): super().build(input_shape) @@ -152,7 +152,7 @@ def call(self, inputs): """ Apply PermopRagged on an input tensor. """ - out = self.pop(inputs, axis=1) + out = self.perm_op(inputs, axis=1) return out @@ -174,12 +174,12 @@ def __init__(self, phi_1, phi_2, input_dim, perm_op='mean', **kwargs): """ super().__init__(dynamic=True, **kwargs) self.phi_1 = phi_1 - self.pop = perm_op + self.perm_op = perm_op self.phi_2 = phi_2 self.input_dim = input_dim if perm_op not in ['mean', 'sum']: - raise ValueError(f'Permutation invariant operation: {self.pop} is not allowed, must be "mean" or "sum".') + raise ValueError(f'Permutation invariant operation: {self.perm_op} is not allowed, must be "mean" or "sum".') def build(self, input_shape): return self @@ -194,16 +194,16 @@ def call(self, pointclouds): Returns: output (n x output_shape): tensor containing predicted vectorizations of the persistence diagrams of pointclouds. """ - if self.pop == 'mean': - pop_ragged = PermopRagged(tf.math.reduce_mean) - elif self.pop == 'sum': - pop_ragged = PermopRagged(tf.math.reduce_sum) + if self.perm_op == 'mean': + perm_op_ragged = PermopRagged(tf.math.reduce_mean) + elif self.perm_op == 'sum': + perm_op_ragged = PermopRagged(tf.math.reduce_sum) else: - raise ValueError(f'Permutation invariant operation: {self.pop} is not allowed, must be "mean" or "sum".') + raise ValueError(f'Permutation invariant operation: {self.perm_op} is not allowed, must be "mean" or "sum".') inputs = tf.keras.layers.InputLayer(input_shape=(None, self.input_dim), dtype="float32", ragged=True)( pointclouds) output = self.phi_1(inputs) - output = pop_ragged(output) + output = perm_op_ragged(output) output = self.phi_2(output) return output From 25222caa1ca85fc183e6bcb153733b0fd466cae9 Mon Sep 17 00:00:00 2001 From: Felix Hensel Date: Tue, 26 Apr 2022 11:32:57 +0200 Subject: [PATCH 25/31] changed TFBlock to support regged inputs and commented DenseRaggedBlock --- src/python/doc/ripsnet.rst | 2 +- src/python/gudhi/tensorflow/ripsnet.py | 78 ++++++++++++++------------ 2 files changed, 44 insertions(+), 36 deletions(-) diff --git a/src/python/doc/ripsnet.rst b/src/python/doc/ripsnet.rst index 538ace6a33..2da9b8f0ee 100644 --- a/src/python/doc/ripsnet.rst +++ b/src/python/doc/ripsnet.rst @@ -45,7 +45,7 @@ This example instantiates a RipsNet model which can then be trained as any tenso dense_layers.append(layers.Dense(output_units, activation=output_activation)) - phi_1 = DenseRaggedBlock(ragged_layers) + phi_1 = TFBlock(ragged_layers) perm_op = 'mean' # can also be 'sum'. phi_2 = TFBlock(dense_layers) input_dim = 2 diff --git a/src/python/gudhi/tensorflow/ripsnet.py b/src/python/gudhi/tensorflow/ripsnet.py index d87b25cfc6..2f20c8044b 100644 --- a/src/python/gudhi/tensorflow/ripsnet.py +++ b/src/python/gudhi/tensorflow/ripsnet.py @@ -59,45 +59,50 @@ def call(self, inputs): return outputs -class DenseRaggedBlock(tf.keras.layers.Layer): - """ - This is a block of DenseRagged layers. - """ - - def __init__(self, dense_ragged_layers, **kwargs): - """ - Constructor for the DenseRaggedBlock class. - - Parameters: - dense_ragged_layers (list): a list of DenseRagged layers :class:`~gudhi.tensorflow.DenseRagged`. - input_dim (int): dimension of the pointcloud, if the input consists of pointclouds. - """ - super().__init__(dynamic=True, **kwargs) - self._supports_ragged_inputs = True - self.dr_layers = dense_ragged_layers - - def build(self, input_shape): - return self - - def call(self, inputs): - """ - Apply the sequence of DenseRagged layers on a ragged input tensor. - - Parameters: - ragged tensor (e.g. containing a point cloud). - - Returns: - ragged tensor containing the output of the sequence of layers. - """ - outputs = inputs - for dr_layer in self.dr_layers: - outputs = dr_layer(outputs) - return outputs +# class DenseRaggedBlock(tf.keras.layers.Layer): +# """ +# This is a block of DenseRagged layers. +# """ +# +# def __init__(self, dense_ragged_layers, **kwargs): +# """ +# Constructor for the DenseRaggedBlock class. +# +# Parameters: +# dense_ragged_layers (list): a list of DenseRagged layers :class:`~gudhi.tensorflow.DenseRagged`. +# input_dim (int): dimension of the pointcloud, if the input consists of pointclouds. +# """ +# super().__init__(dynamic=True, **kwargs) +# self._supports_ragged_inputs = True +# self.dr_layers = dense_ragged_layers +# +# def build(self, input_shape): +# return self +# +# def call(self, inputs): +# """ +# Apply the sequence of DenseRagged layers on a ragged input tensor. +# +# Parameters: +# ragged tensor (e.g. containing a point cloud). +# +# Returns: +# ragged tensor containing the output of the sequence of layers. +# """ +# outputs = inputs +# for dr_layer in self.dr_layers: +# outputs = dr_layer(outputs) +# return outputs class TFBlock(tf.keras.layers.Layer): """ This class is a block of tensorflow layers. + If the first layer is an instance of DenseRagged, it will automatically support ragged inputs. + + Parameters: + layers (list): a list of either tensorflow layers or DenseRagged layers :class:`~gudhi.tensorflow.DenseRagged`. + input_dim (int): dimension of the point cloud, if the input consists of point clouds. """ def __init__(self, layers, **kwargs): @@ -109,9 +114,12 @@ def __init__(self, layers, **kwargs): """ super().__init__(dynamic=True, **kwargs) self.layers = layers + if isinstance(layers[0], DenseRagged): + self._supports_ragged_inputs = True def build(self, input_shape): - super().build(input_shape) + # super().build(input_shape) + return self def call(self, inputs): """ From 63edfc2145fb4195b12f965ee93db9bd740705da Mon Sep 17 00:00:00 2001 From: Felix Hensel Date: Tue, 26 Apr 2022 12:20:01 +0200 Subject: [PATCH 26/31] changed to TFBlock in test_ripsnet.py --- src/python/test/test_ripsnet.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/python/test/test_ripsnet.py b/src/python/test/test_ripsnet.py index 0fad46fdd4..c57ca8eade 100644 --- a/src/python/test/test_ripsnet.py +++ b/src/python/test/test_ripsnet.py @@ -48,17 +48,19 @@ def test_ripsnet(): [ 0.6206032 , -0.20880768 , 0.14528894 , 0.18696047 ]]), np.array([-0.17761804, -0.6905532 , 0.64367545, -0.2173939 ])] - phi_1 = DenseRaggedBlock(ragged_layers) + phi_1 = TFBlock(ragged_layers) #DenseRaggedBlock(ragged_layers) perm_op = 'mean' phi_2 = TFBlock(dense_layers) input_dim = 2 model = RipsNet(phi_1, phi_2, input_dim, perm_op=perm_op) - test_input_raw = [np.array([[1.,2.],[3.,4.]])] + #test_input_raw = [np.array([[1.,2.],[3.,4.]])] + # test_input = tf.ragged.constant([ + # [list(c) for c in list(test_input_raw[i])] for i in range(len(test_input_raw))], ragged_rank=1) - test_input = tf.ragged.constant([ - [list(c) for c in list(test_input_raw[i])] for i in range(len(test_input_raw))], ragged_rank=1) + test_input_raw = [[[1., 2.], [3., 4.]]] + test_input = tf.ragged.constant(test_input_raw, ragged_rank=1) model.predict(test_input) From 1cca679fe4c605d485e8e07a5644ecbfcef3622a Mon Sep 17 00:00:00 2001 From: Felix Hensel Date: Tue, 26 Apr 2022 16:37:40 +0200 Subject: [PATCH 27/31] fixed __init__.py --- src/python/gudhi/tensorflow/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/gudhi/tensorflow/__init__.py b/src/python/gudhi/tensorflow/__init__.py index 829cf2174c..7f806120af 100644 --- a/src/python/gudhi/tensorflow/__init__.py +++ b/src/python/gudhi/tensorflow/__init__.py @@ -1,3 +1,3 @@ from .ripsnet import * -__all__ = ["RipsNet", "PermopRagged", "TFBlock", "DenseRaggedBlock", "DenseRagged"] \ No newline at end of file +__all__ = ["RipsNet", "PermopRagged", "TFBlock", "DenseRagged"] \ No newline at end of file From 1122d1104477934cb90317818559a463e214efa6 Mon Sep 17 00:00:00 2001 From: Felix Hensel Date: Tue, 26 Apr 2022 16:48:07 +0200 Subject: [PATCH 28/31] fixed documentation --- src/python/gudhi/tensorflow/ripsnet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/python/gudhi/tensorflow/ripsnet.py b/src/python/gudhi/tensorflow/ripsnet.py index 2f20c8044b..95e4febb11 100644 --- a/src/python/gudhi/tensorflow/ripsnet.py +++ b/src/python/gudhi/tensorflow/ripsnet.py @@ -175,8 +175,8 @@ def __init__(self, phi_1, phi_2, input_dim, perm_op='mean', **kwargs): Constructor for the RipsNet class. Parameters: - phi_1 (layers): any block of DenseRagged layers. Can be :class:`~gudhi.tensorflow.DenseRaggedBlock`, or a custom block built from :class:`~gudhi.tensorflow.DenseRagged` layers. - phi_2 (layers): Can be any (block of) TensorFlow layer(s), e.g. :class:`~gudhi.tensorflow.TFBlock`. + phi_1 (layers): any block of DenseRagged layers. Can be a custom block built from :class:`~gudhi.tensorflow.DenseRagged` layers. + phi_2 (layers): Can be any (block of) TensorFlow layer(s), e.g. :class:`~gudhi.tensorflow.TFBlock`. input_dim (int): dimension of the input point clouds. perm_op (str): Permutation invariant operation. Can be 'mean' or 'sum'. """ From a25d3f8416c5228e68cb7e6483ed9d8e5435cdbb Mon Sep 17 00:00:00 2001 From: Felix Hensel Date: Tue, 7 Jun 2022 09:44:26 +0200 Subject: [PATCH 29/31] allowing user specified permop functions --- src/python/gudhi/tensorflow/ripsnet.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/python/gudhi/tensorflow/ripsnet.py b/src/python/gudhi/tensorflow/ripsnet.py index 95e4febb11..f6e75535f2 100644 --- a/src/python/gudhi/tensorflow/ripsnet.py +++ b/src/python/gudhi/tensorflow/ripsnet.py @@ -178,7 +178,8 @@ def __init__(self, phi_1, phi_2, input_dim, perm_op='mean', **kwargs): phi_1 (layers): any block of DenseRagged layers. Can be a custom block built from :class:`~gudhi.tensorflow.DenseRagged` layers. phi_2 (layers): Can be any (block of) TensorFlow layer(s), e.g. :class:`~gudhi.tensorflow.TFBlock`. input_dim (int): dimension of the input point clouds. - perm_op (str): Permutation invariant operation. Can be 'mean' or 'sum'. + perm_op (str or function): Permutation invariant operation. + Can be 'mean' or 'sum', or any user defined (permutation invariant) function. """ super().__init__(dynamic=True, **kwargs) self.phi_1 = phi_1 @@ -186,8 +187,8 @@ def __init__(self, phi_1, phi_2, input_dim, perm_op='mean', **kwargs): self.phi_2 = phi_2 self.input_dim = input_dim - if perm_op not in ['mean', 'sum']: - raise ValueError(f'Permutation invariant operation: {self.perm_op} is not allowed, must be "mean" or "sum".') + # if perm_op not in ['mean', 'sum']: + # raise ValueError(f'Permutation invariant operation: {self.perm_op} is not allowed, must be "mean" or "sum".') def build(self, input_shape): return self @@ -206,8 +207,8 @@ def call(self, pointclouds): perm_op_ragged = PermopRagged(tf.math.reduce_mean) elif self.perm_op == 'sum': perm_op_ragged = PermopRagged(tf.math.reduce_sum) - else: - raise ValueError(f'Permutation invariant operation: {self.perm_op} is not allowed, must be "mean" or "sum".') + # else: + # raise ValueError(f'Permutation invariant operation: {self.perm_op} is not allowed, must be "mean" or "sum".') inputs = tf.keras.layers.InputLayer(input_shape=(None, self.input_dim), dtype="float32", ragged=True)( pointclouds) From a05f8253e999f6840b53728b7ddc9736a35bedd0 Mon Sep 17 00:00:00 2001 From: Felix Hensel Date: Tue, 7 Jun 2022 10:09:46 +0200 Subject: [PATCH 30/31] updated comments in the documentation and changed imports --- src/python/doc/ripsnet.rst | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/python/doc/ripsnet.rst b/src/python/doc/ripsnet.rst index 2da9b8f0ee..c8f7ca307f 100644 --- a/src/python/doc/ripsnet.rst +++ b/src/python/doc/ripsnet.rst @@ -20,7 +20,7 @@ This example instantiates a RipsNet model which can then be trained as any tenso .. testcode:: - from gudhi.tensorflow import * + import gudhi.tensorflow as gtf import tensorflow as tf from tensorflow.keras import regularizers, layers @@ -36,7 +36,7 @@ This example instantiates a RipsNet model which can then be trained as any tenso dense_layers = [] for n_units in ragged_layers_size: - ragged_layers.append(DenseRagged(units=n_units, use_bias=True, activation=activation_fct)) + ragged_layers.append(gtf.DenseRagged(units=n_units, use_bias=True, activation=activation_fct)) for n_units in dense_layers_size: dense_layers.append(layers.Dense(n_units, activation=activation_fct, @@ -45,12 +45,12 @@ This example instantiates a RipsNet model which can then be trained as any tenso dense_layers.append(layers.Dense(output_units, activation=output_activation)) - phi_1 = TFBlock(ragged_layers) - perm_op = 'mean' # can also be 'sum'. - phi_2 = TFBlock(dense_layers) + phi_1 = gtf.TFBlock(ragged_layers) + perm_op = 'mean' # can also be 'sum' (or a user specified function). + phi_2 = gtf.TFBlock(dense_layers) input_dim = 2 - RN = RipsNet(phi_1, phi_2, input_dim, perm_op=perm_op) + RN = gtf.RipsNet(phi_1, phi_2, input_dim, perm_op=perm_op) data_test = [[[-7.04493841, 9.60285858], [-13.14389003, -13.21854157], @@ -69,7 +69,11 @@ This example instantiates a RipsNet model which can then be trained as any tenso RN.predict(tf_data_test) Once RN is properly trained (which we skip in this documentation) it can be used to make predictions. -In this example RipsNet estimates persistence vectorizations (of output size 25) of a list of point clouds (of 3 points) in 2D. +In this example RipsNet estimates persistence vectorizations (of output size 25) of a list of 3 point clouds +of 3 points each) in 2D. +It yields an output of shape 'nb_input_pointclouds x output_units'. +The 'ragged_layers_size' and 'dense_layers_size' define the architecture of the network. +To reach best performance, they should be tuned depending on the dataset. A possible output is: .. code-block:: From 67cb4d4088b48d83655024176a594e31e953fce0 Mon Sep 17 00:00:00 2001 From: Felix Hensel Date: Mon, 20 Jun 2022 12:08:40 +0200 Subject: [PATCH 31/31] added get_config() --- .gitignore | 1 + src/python/gudhi/tensorflow/ripsnet.py | 45 ++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/.gitignore b/.gitignore index 58374ce5ed..0e74960540 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ cmake-build-debug/ # Poetry files *.lock *.toml +./venv # Ripsnet files ripsnet_doc_test.py diff --git a/src/python/gudhi/tensorflow/ripsnet.py b/src/python/gudhi/tensorflow/ripsnet.py index f6e75535f2..8cb3e2df24 100644 --- a/src/python/gudhi/tensorflow/ripsnet.py +++ b/src/python/gudhi/tensorflow/ripsnet.py @@ -33,6 +33,20 @@ def __init__(self, units, input_dim=None, use_bias=True, activation='gelu', kern self.kernel_initializer = kernel_initializer self.bias_initializer = bias_initializer + def get_config(self): + config = super().get_config().copy() + config.update( + { + 'units': self.units, + 'use_bias': self.use_bias, + 'activation': self.activation, + '_supports_ragged_inputs': self._supports_ragged_inputs, + 'kernel_initializer': self.kernel_initializer, + 'bias_initializer': self.bias_initializer, + } + ) + return config + def build(self, input_shape): last_dim = input_shape[-1] self.kernel = self.add_weight('kernel', shape=[last_dim, self.units], trainable=True, initializer=self.kernel_initializer) @@ -117,6 +131,16 @@ def __init__(self, layers, **kwargs): if isinstance(layers[0], DenseRagged): self._supports_ragged_inputs = True + def get_config(self): + config = super().get_config().copy() + config.update( + { + 'layers': self.layers, + '_supports_ragged_inputs': self._supports_ragged_inputs, + } + ) + return config + def build(self, input_shape): # super().build(input_shape) return self @@ -153,6 +177,16 @@ def __init__(self, perm_op, **kwargs): self._supports_ragged_inputs = True self.perm_op = perm_op + def get_config(self): + config = super().get_config().copy() + config.update( + { + 'perm_op': self.perm_op, + '_supports_ragged_inputs': self._supports_ragged_inputs, + } + ) + return config + def build(self, input_shape): super().build(input_shape) @@ -189,6 +223,17 @@ def __init__(self, phi_1, phi_2, input_dim, perm_op='mean', **kwargs): # if perm_op not in ['mean', 'sum']: # raise ValueError(f'Permutation invariant operation: {self.perm_op} is not allowed, must be "mean" or "sum".') + def get_config(self): + config = super().get_config().copy() + config.update( + { + 'phi_1': self.phi_1, + 'phi_2': self.phi_2, + 'perm_op': self.perm_op, + 'input_dim': self.input_dim, + } + ) + return config def build(self, input_shape): return self