Skip to content

Commit 0701b48

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 76dc6f3 commit 0701b48

3 files changed

Lines changed: 43 additions & 29 deletions

File tree

bindings/pyroot/pythonizations/test/generate_keras_functional.py

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

35
import numpy as np
@@ -16,8 +18,18 @@ def train_and_save(model, name):
1618

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

2335

@@ -211,7 +223,7 @@ def train_and_save(model, name):
211223
sub = layers.Subtract()([d1, d2])
212224
mul = layers.Multiply()([d1, d2])
213225
merged = layers.Concatenate()([add, sub, mul])
214-
merged = layers.LeakyReLU(alpha=0.1)(merged)
226+
merged = layers.LeakyReLU(negative_slope=0.1)(merged)
215227
out = layers.Dense(4, activation="softmax")(merged)
216228
model = models.Model([inp1, inp2], out)
217229
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,3 +1,5 @@
1+
import warnings
2+
13
def generate_keras_sequential(dst_dir):
24

35
import numpy as np
@@ -9,10 +11,19 @@ def train_and_save(model, name):
911
x_train = np.random.rand(32, *model.input_shape[1:])
1012
y_train = np.random.rand(32, *model.output_shape[1:])
1113
model.compile(optimizer='adam', loss='mean_squared_error', metrics=['mae'])
12-
model.fit(x_train, y_train, epochs=1, verbose=0)
14+
if len(model.trainable_weights) > 0:
15+
model.fit(x_train, y_train, epochs=1, verbose=0)
1316
model.summary()
1417
print("fitting sequential model",name)
15-
model.save(f"{dst_dir}/Sequential_{name}_test.keras")
18+
19+
with warnings.catch_warnings():
20+
# Some object inside TensorFlow/Keras has an outdated __array__ implementation
21+
warnings.filterwarnings(
22+
"ignore",
23+
category=DeprecationWarning,
24+
message=".*__array__.*copy keyword.*"
25+
)
26+
model.save(f"{dst_dir}/Sequential_{name}_test.keras")
1627

1728

1829
# Binary Ops: Add, Subtract, Multiply are not typical in Sequential - skipping those
@@ -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=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)