Skip to content

Commit add83a8

Browse files
authored
Merge pull request #91 from deel-ai/fix/upgrade_tf_2.17
[BREAKING CHANGE] deel-lip upgrade to Keras 3.0 TensorFlow 2.16 version and above come with Keras 3: a lot of major changes and incompatibilities have been introduced with previous TF versions. We then propose a new deel-lip version 2.0.0 compatible with TensorFlow 2.16+ and Keras 3. This PR has two sets of commits: - a set of "fix" commits that resolves bugs introduced by TF2.16+ and Keras 3, compared to TF 2.15 and Keras 2. It is mainly API changes of tf.keras functions and classes. - a set of "feat" commits that removes all direct dependencies to TensorFlow and only use Keras 3 API. The new deel-lip code is now only based on Keras (with TensorFlow backend) but there is no direct calls to TensorFlow API anymore.
2 parents 51d693b + ed92ee3 commit add83a8

39 files changed

+3443
-2495
lines changed

.github/workflows/python-linters.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
strategy:
1414
max-parallel: 4
1515
matrix:
16-
python-version: [3.7, "3.10"]
16+
python-version: ["3.10"]
1717

1818
steps:
1919
- uses: actions/checkout@v4

.github/workflows/python-tests.yml

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,8 @@ jobs:
1616
max-parallel: 4
1717
matrix:
1818
include:
19-
- python-version: 3.7
20-
tf-version: 2.3
21-
- python-version: 3.9
22-
tf-version: 2.7
2319
- python-version: "3.10"
24-
tf-version: 2.11
25-
- python-version: "3.10"
26-
tf-version: 2.15
20+
tf-version: 2.17
2721

2822
steps:
2923
- uses: actions/checkout@v4

deel/lip/VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.5.0
1+
2.0.0

deel/lip/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
--------
2626
2727
DEEL-LIP provides a simple interface to build and train Lipschitz-constrained neural
28-
networks based on TensorFlow/Keras framework.
28+
networks based on Keras framework.
2929
"""
3030
from os import path
3131

deel/lip/callbacks.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66
This module contains callbacks that can be added to keras training process.
77
"""
88
import os
9-
from typing import Optional, Dict, Iterable
9+
from typing import Dict, Iterable, Optional
1010

11-
import tensorflow as tf
12-
from tensorflow.keras.callbacks import Callback
11+
import keras.ops as K
1312
import numpy as np
13+
from keras.callbacks import Callback
14+
1415
from .layers import Condensable
1516

1617

@@ -91,6 +92,8 @@ def __init__(
9192
assert what in {"max", "all"}
9293
self.what = what
9394
self.logdir = logdir
95+
import tensorflow as tf
96+
9497
self.file_writer = tf.summary.create_file_writer(
9598
os.path.join(logdir, "metrics")
9699
)
@@ -101,6 +104,8 @@ def __init__(
101104
super().__init__()
102105

103106
def _monitor(self, step):
107+
import tensorflow as tf
108+
104109
step = self.params["steps"] * self.epochs + step
105110
for layer_name in self.monitored_layers:
106111
layer = self.model.get_layer(layer_name)
@@ -113,11 +118,12 @@ def _monitor(self, step):
113118
elif hasattr(layer, self.target):
114119
kernel = getattr(layer, self.target)
115120
w_shape = kernel.shape.as_list()
116-
sigmas = tf.linalg.svd(
117-
tf.keras.backend.reshape(kernel, [-1, w_shape[-1]]),
121+
# TODO: compute_uv=False in next Keras version (3.6.0)
122+
sigmas = K.svd(
123+
K.reshape(kernel, [-1, w_shape[-1]]),
118124
full_matrices=False,
119-
compute_uv=False,
120-
).numpy()
125+
compute_uv=True,
126+
)[1].numpy()
121127
sig = sigmas[0]
122128
else:
123129
raise RuntimeWarning(
@@ -176,7 +182,7 @@ def __init__(self, param_name, fp, xp, step=0):
176182
177183
Args:
178184
param_name (str): name of the parameter of the loss to tune. Must be a
179-
tf.Variable.
185+
keras.Variable.
180186
fp (list): values of the loss parameter as steps given by the xp.
181187
xp (list): step where the parameter equals fp.
182188
step (int): step value, for serialization/deserialization purposes.
@@ -215,7 +221,7 @@ def __init__(self, param_name, rate=1):
215221

216222
def on_epoch_end(self, epoch: int, logs=None):
217223
if epoch % self.rate == 0:
218-
tf.print(
224+
print(
219225
"\n",
220226
self.model.loss.name,
221227
self.param_name,

deel/lip/compute_layer_sv.py

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
It returns a dictionary indicating for each layer name a tuple (min sv, max sv).
1515
"""
1616

17+
import keras
1718
import numpy as np
18-
import tensorflow as tf
1919

2020
from .layers import Condensable, GroupSort, MaxMin
2121
from .layers.unconstrained import PadConv2D
@@ -27,7 +27,7 @@ def _compute_sv_dense(layer, input_sizes=None):
2727
The singular values are computed using the SVD decomposition of the weight matrix.
2828
2929
Args:
30-
layer (tf.keras.Layer): the Dense layer.
30+
layer (keras.Layer): the Dense layer.
3131
input_sizes (tuple, optional): unused here.
3232
3333
Returns:
@@ -46,16 +46,14 @@ def _generate_conv_matrix(layer, input_sizes):
4646
dirac input.
4747
4848
Args:
49-
layer (tf.keras.Layer): the convolutional layer to convert to dense.
49+
layer (keras.Layer): the convolutional layer to convert to dense.
5050
input_sizes (tuple): the input shape of the layer (with batch dimension as first
5151
element).
5252
5353
Returns:
5454
np.array: the equivalent matrix of the convolutional layer.
5555
"""
56-
single_layer_model = tf.keras.models.Sequential(
57-
[tf.keras.layers.Input(input_sizes[1:]), layer]
58-
)
56+
single_layer_model = keras.Sequential([keras.Input(input_sizes[1:]), layer])
5957
dirac_inp = np.zeros((input_sizes[2],) + input_sizes[1:]) # Line by line generation
6058
in_size = input_sizes[1] * input_sizes[2]
6159
channel_in = input_sizes[-1]
@@ -69,8 +67,8 @@ def _generate_conv_matrix(layer, input_sizes):
6967
w_eqmatrix = np.zeros(
7068
(in_size * channel_in, np.prod(out_pred.shape[1:]))
7169
)
72-
w_eqmatrix[start_index : (start_index + input_sizes[2]), :] = tf.reshape(
73-
out_pred, (input_sizes[2], -1)
70+
w_eqmatrix[start_index : (start_index + input_sizes[2]), :] = (
71+
keras.ops.reshape(out_pred, (input_sizes[2], -1))
7472
)
7573
dirac_inp = 0.0 * dirac_inp
7674
start_index += input_sizes[2]
@@ -86,7 +84,7 @@ def _compute_sv_conv2d_layer(layer, input_sizes):
8684
the weight matrix.
8785
8886
Args:
89-
layer (tf.keras.Layer): the convolutional layer.
87+
layer (keras.Layer): the convolutional layer.
9088
input_sizes (tuple): the input shape of the layer (with batch dimension as first
9189
element).
9290
@@ -103,14 +101,14 @@ def _compute_sv_activation(layer, input_sizes=None):
103101
104102
Warning: This is not singular values for non-linear functions but gradient norm.
105103
"""
106-
if isinstance(layer, tf.keras.layers.Activation):
107-
function2SV = {tf.keras.activations.relu: (0, 1)}
104+
if isinstance(layer, keras.layers.Activation):
105+
function2SV = {keras.activations.relu: (0, 1)}
108106
if layer.activation in function2SV.keys():
109107
return function2SV[layer.activation]
110108
else:
111109
return (None, None)
112110
layer2SV = {
113-
tf.keras.layers.ReLU: (0, 1),
111+
keras.layers.ReLU: (0, 1),
114112
GroupSort: (1, 1),
115113
MaxMin: (1, 1),
116114
}
@@ -145,25 +143,25 @@ def compute_layer_sv(layer, supplementary_type2sv={}):
145143
ReLU, Activation, and deel-lip layers)
146144
147145
Args:
148-
layer (tf.keras.layers.Layer): a single tf.keras.layer
146+
layer (keras.layers.Layer): a single keras.layer
149147
supplementary_type2sv (dict, optional): a dictionary linking new layer type with
150148
user-defined function to compute the singular values. Defaults to {}.
151149
Returns:
152150
tuple: a 2-tuple with lowest and largest singular values.
153151
"""
154152
default_type2sv = {
155-
tf.keras.layers.Conv2D: _compute_sv_conv2d_layer,
156-
tf.keras.layers.Conv2DTranspose: _compute_sv_conv2d_layer,
153+
keras.layers.Conv2D: _compute_sv_conv2d_layer,
154+
keras.layers.Conv2DTranspose: _compute_sv_conv2d_layer,
157155
PadConv2D: _compute_sv_conv2d_layer,
158-
tf.keras.layers.Dense: _compute_sv_dense,
159-
tf.keras.layers.ReLU: _compute_sv_activation,
160-
tf.keras.layers.Activation: _compute_sv_activation,
156+
keras.layers.Dense: _compute_sv_dense,
157+
keras.layers.ReLU: _compute_sv_activation,
158+
keras.layers.Activation: _compute_sv_activation,
161159
GroupSort: _compute_sv_activation,
162160
MaxMin: _compute_sv_activation,
163-
tf.keras.layers.Add: _compute_sv_add,
164-
tf.keras.layers.BatchNormalization: _compute_sv_bn,
161+
keras.layers.Add: _compute_sv_add,
162+
keras.layers.BatchNormalization: _compute_sv_bn,
165163
}
166-
input_shape = layer.input_shape
164+
input_shape = layer.input.shape if hasattr(layer.input, "shape") else None
167165
if isinstance(layer, Condensable):
168166
layer.condense()
169167
layer = layer.vanilla_export()
@@ -179,7 +177,7 @@ def compute_model_sv(model, supplementary_type2sv={}):
179177
"""Compute the largest and lowest singular values of all layers in a model.
180178
181179
Args:
182-
model (tf.keras.Model): a tf.keras Model or Sequential.
180+
model (keras.Model): a keras Model or Sequential.
183181
supplementary_type2sv (dict, optional): a dictionary linking new layer type
184182
with user defined function to compute the min and max singular values.
185183
@@ -188,7 +186,7 @@ def compute_model_sv(model, supplementary_type2sv={}):
188186
"""
189187
list_sv = []
190188
for layer in model.layers:
191-
if isinstance(layer, tf.keras.Model):
189+
if isinstance(layer, keras.Model):
192190
list_sv.append((layer.name, (None, None)))
193191
list_sv += compute_model_sv(layer, supplementary_type2sv)
194192
else:

deel/lip/constraints.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,15 @@
66
This module contains extra constraint objects. These object can be added as params to
77
regular layers.
88
"""
9-
import tensorflow as tf
10-
from tensorflow.keras import backend as K
11-
from tensorflow.keras.constraints import Constraint
9+
import keras.ops as K
10+
from keras.constraints import Constraint
1211
from .normalizers import (
1312
reshaped_kernel_orthogonalization,
1413
DEFAULT_EPS_SPECTRAL,
1514
DEFAULT_EPS_BJORCK,
1615
DEFAULT_BETA_BJORCK,
1716
)
18-
from tensorflow.keras.utils import register_keras_serializable
17+
from keras.saving import register_keras_serializable
1918

2019

2120
@register_keras_serializable("deel-lip", "WeightClipConstraint")
@@ -49,8 +48,8 @@ def __init__(self, scale=1):
4948
self.scale = scale
5049

5150
def __call__(self, w):
52-
c = 1 / (tf.sqrt(tf.cast(tf.size(w), dtype=w.dtype)) * self.scale)
53-
return tf.clip_by_value(w, -c, c)
51+
c = 1 / (K.sqrt(K.cast(K.size(w), dtype=w.dtype)) * self.scale)
52+
return K.clip(w, -c, c)
5453

5554
def get_config(self):
5655
return {"scale": self.scale}
@@ -67,7 +66,7 @@ def __init__(self, eps=1e-7):
6766
self.eps = eps
6867

6968
def __call__(self, w):
70-
return w / (tf.sqrt(tf.reduce_sum(tf.square(w), keepdims=False)) + self.eps)
69+
return w / (K.sqrt(K.sum(K.square(w), keepdims=False)) + self.eps)
7170

7271
def get_config(self):
7372
return {"eps": self.eps}
@@ -95,15 +94,15 @@ def __init__(
9594
eps_spectral (float): stopping criterion for the iterative power algorithm.
9695
eps_bjorck (float): stopping criterion Bjorck algorithm.
9796
beta_bjorck (float): beta parameter in bjorck algorithm.
98-
u (tf.Tensor): vector used for iterated power method, can be set to None
97+
u (Tensor): vector used for iterated power method, can be set to None
9998
(used for serialization/deserialization purposes).
10099
"""
101100
self.eps_spectral = eps_spectral
102101
self.eps_bjorck = eps_bjorck
103102
self.beta_bjorck = beta_bjorck
104103
self.k_coef_lip = k_coef_lip
105-
if not (isinstance(u, tf.Tensor) or (u is None)):
106-
u = tf.convert_to_tensor(u)
104+
if not (K.is_tensor(u) or (u is None)):
105+
u = K.convert_to_tensor(u)
107106
self.u = u
108107
super(SpectralConstraint, self).__init__()
109108

deel/lip/initializers.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,19 @@
77
matrix initialization.
88
They can be used as kernel initializers in any Keras layer.
99
"""
10-
from tensorflow.keras.initializers import Initializer
11-
from tensorflow.keras import initializers
10+
import keras
11+
from keras.saving import register_keras_serializable
12+
1213
from .normalizers import (
1314
reshaped_kernel_orthogonalization,
1415
DEFAULT_EPS_SPECTRAL,
1516
DEFAULT_EPS_BJORCK,
1617
DEFAULT_BETA_BJORCK,
1718
)
18-
from tensorflow.keras.utils import register_keras_serializable
1919

2020

2121
@register_keras_serializable("deel-lip", "SpectralInitializer")
22-
class SpectralInitializer(Initializer):
22+
class SpectralInitializer(keras.Initializer):
2323
def __init__(
2424
self,
2525
eps_spectral=DEFAULT_EPS_SPECTRAL,
@@ -44,7 +44,7 @@ def __init__(
4444
self.eps_bjorck = eps_bjorck
4545
self.beta_bjorck = beta_bjorck
4646
self.k_coef_lip = k_coef_lip
47-
self.base_initializer = initializers.get(base_initializer)
47+
self.base_initializer = keras.initializers.get(base_initializer)
4848
super(SpectralInitializer, self).__init__()
4949

5050
def __call__(self, shape, dtype=None, partition_info=None):
@@ -65,5 +65,5 @@ def get_config(self):
6565
"eps_bjorck": self.eps_bjorck,
6666
"beta_bjorck": self.beta_bjorck,
6767
"k_coef_lip": self.k_coef_lip,
68-
"base_initializer": initializers.serialize(self.base_initializer),
68+
"base_initializer": keras.initializers.serialize(self.base_initializer),
6969
}

0 commit comments

Comments
 (0)