Skip to content

Commit 1fbee0a

Browse files
committed
[tmva][sofie] Improve Keras test robustness and diagnostics
* Skip training for models without trainable weights to avoid spurious Keras warnings about "no trainable weights" * Suppress NumPy 2.0 __array__ DeprecationWarning during model.save() (upstream TF/Keras compatibility issue) using a scoped warnings context * Fix Keras inference call for single-input models by unwrapping singleton input lists (Keras expects a tensor, not [tensor]) * Remove redundant load_weights() after load_model() * Replace custom is_accurate() with np.testing.assert_allclose() for clearer diagnostics and standardized comparison * use atol=1e-2 and rtol=0 to preserve previous absolute tolerance behavior * Update LeakyReLU argument (alpha -> negative_slope) to avoid deprecation warnings These changes make the tests more robust, reduce noise from external dependencies, and significantly improve failure diagnostics.
1 parent 9dc0b0e commit 1fbee0a

3 files changed

Lines changed: 44 additions & 29 deletions

File tree

bindings/pyroot/pythonizations/test/generate_keras_functional.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
import warnings
2+
13
def generate_keras_functional(dst_dir):
24

35
import numpy as np
6+
import keras
47
from keras import layers, models
58
from parser_test_function import is_channels_first_supported
69

@@ -16,8 +19,14 @@ def train_and_save(model, name):
1619

1720
model.compile(optimizer='adam', loss='mean_squared_error', metrics=['mae'])
1821
model.summary()
19-
model.fit(x_train, y_train, epochs=1, verbose=0)
20-
model.save(f"{dst_dir}/Functional_{name}_test.keras")
22+
if len(model.trainable_weights) > 0:
23+
model.fit(x_train, y_train, epochs=1, verbose=0)
24+
25+
with warnings.catch_warnings():
26+
# Some object inside TensorFlow/Keras has an outdated __array__ implementation
27+
warnings.filterwarnings("ignore", category=DeprecationWarning, message=".*__array__.*copy keyword.*")
28+
model.save(f"{dst_dir}/Functional_{name}_test.keras")
29+
2130
print("generated and saved functional model",name)
2231

2332

@@ -211,7 +220,11 @@ def train_and_save(model, name):
211220
sub = layers.Subtract()([d1, d2])
212221
mul = layers.Multiply()([d1, d2])
213222
merged = layers.Concatenate()([add, sub, mul])
214-
merged = layers.LeakyReLU(alpha=0.1)(merged)
223+
# `alpha` was renamed to `negative_slope` in Keras 3
224+
if keras.__version__ >= "3.0":
225+
merged = layers.LeakyReLU(negative_slope=0.1)(merged)
226+
else:
227+
merged = layers.LeakyReLU(alpha=0.1)(merged)
215228
out = layers.Dense(4, activation="softmax")(merged)
216229
model = models.Model([inp1, inp2], out)
217230
train_and_save(model, "Layer_Combination_3")

bindings/pyroot/pythonizations/test/generate_keras_sequential.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import warnings
2+
13
def generate_keras_sequential(dst_dir):
24

5+
import keras
36
import numpy as np
47
from keras import layers, models
58
from parser_test_function import is_channels_first_supported
@@ -9,10 +12,15 @@ def train_and_save(model, name):
912
x_train = np.random.rand(32, *model.input_shape[1:])
1013
y_train = np.random.rand(32, *model.output_shape[1:])
1114
model.compile(optimizer='adam', loss='mean_squared_error', metrics=['mae'])
12-
model.fit(x_train, y_train, epochs=1, verbose=0)
15+
if len(model.trainable_weights) > 0:
16+
model.fit(x_train, y_train, epochs=1, verbose=0)
1317
model.summary()
1418
print("fitting sequential model",name)
15-
model.save(f"{dst_dir}/Sequential_{name}_test.keras")
19+
20+
with warnings.catch_warnings():
21+
# Some object inside TensorFlow/Keras has an outdated __array__ implementation
22+
warnings.filterwarnings("ignore", category=DeprecationWarning, message=".*__array__.*copy keyword.*")
23+
model.save(f"{dst_dir}/Sequential_{name}_test.keras")
1624

1725

1826
# Binary Ops: Add, Subtract, Multiply are not typical in Sequential - skipping those
@@ -183,6 +191,9 @@ def train_and_save(model, name):
183191
])
184192
train_and_save(modelA, "Layer_Combination_1")
185193

194+
# `alpha` was renamed to `negative_slope` in Keras 3
195+
negative_slope_key = "negative_slope" if keras.__version__ >= "3.0" else "alpha"
196+
186197
modelB = models.Sequential([
187198
layers.Input(shape=(32,32,3)),
188199
layers.Conv2D(8, (3,3), padding='valid', data_format='channels_last', activation='relu'),
@@ -193,7 +204,7 @@ def train_and_save(model, name):
193204
layers.Permute((2, 1)),
194205
layers.Flatten(),
195206
layers.Dense(32),
196-
layers.LeakyReLU(alpha=0.1),
207+
layers.LeakyReLU(**{negative_slope_key : 0.1}),
197208
layers.Dense(10, activation='softmax'),
198209
])
199210
train_and_save(modelB, "Layer_Combination_2")
@@ -210,4 +221,4 @@ def train_and_save(model, name):
210221
layers.Dense(8, activation='swish'),
211222
layers.Dense(3, activation='softmax'),
212223
])
213-
train_and_save(modelC, "Layer_Combination_3")
224+
train_and_save(modelC, "Layer_Combination_3")

bindings/pyroot/pythonizations/test/parser_test_function.py

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,6 @@
22

33
'''
44
The test file contains two types of functions:
5-
is_accurate:
6-
- This function checks whether the inference results from SOFIE and Keras are accurate within a specified
7-
tolerance. Since the inference result from Keras is not flattened, the function flattens both tensors before
8-
performing the comparison.
95
106
generate_and_test_inference:
117
- This function accepts the following inputs:
@@ -29,7 +25,7 @@
2925
shape from the model object.
3026
- Convert the inference results to NumPy arrays:
3127
The SOFIE result is of type vector<float>, and the Keras result is a TensorFlow tensor. Both are converted to
32-
NumPy arrays before being passed to the is_accurate function for comparison.
28+
NumPy arrays before being passed to the np.testing.assert_allclose function for comparison.
3329
3430
'''
3531
def is_channels_first_supported() :
@@ -42,16 +38,6 @@ def is_channels_first_supported() :
4238

4339
return True
4440

45-
def is_accurate(tensor_a, tensor_b, tolerance=1e-2):
46-
tensor_a = tensor_a.flatten()
47-
tensor_b = tensor_b.flatten()
48-
for i in range(len(tensor_a)):
49-
difference = abs(tensor_a[i] - tensor_b[i])
50-
if difference > tolerance:
51-
print(tensor_a[i], tensor_b[i])
52-
return False
53-
return True
54-
5541
def generate_and_test_inference(model_file_path: str, generated_header_file_dir: str = None, batch_size=1):
5642

5743
import keras
@@ -81,7 +67,6 @@ def generate_and_test_inference(model_file_path: str, generated_header_file_dir:
8167
sofie_model_namespace = getattr(ROOT, "TMVA_SOFIE_" + model_name)
8268
inference_session = sofie_model_namespace.Session(generated_header_file_path.removesuffix(".hxx") + ".dat")
8369
keras_model = keras.models.load_model(model_file_path)
84-
keras_model.load_weights(model_file_path)
8570

8671
input_tensors = []
8772
for model_input in keras_model.inputs:
@@ -91,11 +76,17 @@ def generate_and_test_inference(model_file_path: str, generated_header_file_dir:
9176
sofie_inference_result = inference_session.infer(*input_tensors)
9277
sofie_output_tensor_shape = list(rmodel.GetTensorShape(rmodel.GetOutputTensorNames()[0])) # get output shape
9378
# from SOFIE
94-
keras_inference_result = keras_model(input_tensors)
79+
# Keras explicitly forbids input tensor lists of size 1
80+
if len(keras_model.inputs) == 1:
81+
keras_inference_result = keras_model(input_tensors[0])
82+
else:
83+
keras_inference_result = keras_model(input_tensors)
9584
if sofie_output_tensor_shape != list(keras_inference_result.shape):
9685
raise AssertionError("Output tensor dimensions from SOFIE and Keras do not match")
97-
sofie_inference_result = np.asarray(sofie_inference_result)
98-
keras_inference_result = np.asarray(keras_inference_result)
99-
is_inference_accurate = is_accurate(sofie_inference_result, keras_inference_result)
100-
if not is_inference_accurate:
101-
raise AssertionError("Inference results from SOFIE and Keras do not match")
86+
87+
np.testing.assert_allclose(
88+
np.asarray(sofie_inference_result).flatten(),
89+
np.asarray(keras_inference_result).flatten(),
90+
atol=1e-2,
91+
rtol=0. # explicitly disable relative tolerance (NumPy uses |a - b| <= atol + rtol * |b|)
92+
)

0 commit comments

Comments
 (0)