Skip to content

Commit d36e226

Browse files
AdrianAlanjmduarte
andauthored
Speed up Keras profiling (fastmachinelearning#863)
* Speed up Keras profiling * update function name --------- Co-authored-by: Javier Duarte <[email protected]>
1 parent cd8329f commit d36e226

File tree

2 files changed

+48
-51
lines changed

2 files changed

+48
-51
lines changed

hls4ml/model/profiling.py

Lines changed: 42 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -343,21 +343,22 @@ def activations_keras(model, X, fmt='longform', plot='boxplot'):
343343
# return summary statistics for matplotlib.axes.Axes.bxp
344344
# or histogram bin edges and heights
345345
data = []
346-
347-
for layer in model.layers:
348-
print(f" {layer.name}")
349-
if not isinstance(layer, keras.layers.InputLayer):
350-
y = _get_output(layer, X, model.input).flatten()
351-
y = abs(y[y != 0])
352-
if len(y) == 0:
353-
print(f'Activations for {layer.name} are only zeros, ignoring.')
354-
continue
355-
if fmt == 'longform':
356-
data['x'].extend(y.tolist())
357-
data['weight'].extend([layer.name for i in range(len(y))])
358-
elif fmt == 'summary':
359-
data.append(array_to_summary(y, fmt=plot))
360-
data[-1]['weight'] = layer.name
346+
outputs = _get_outputs(
347+
[layer for layer in model.layers if not isinstance(layer, keras.layers.InputLayer)], X, model.input
348+
)
349+
for layer_name, y in outputs.items():
350+
print(f" {layer_name}")
351+
y = y.flatten()
352+
y = abs(y[y != 0])
353+
if len(y) == 0:
354+
print(f'Activations for {layer_name} are only zeros, ignoring.')
355+
continue
356+
if fmt == 'longform':
357+
data['x'].extend(y.tolist())
358+
data['weight'].extend([layer_name for i in range(len(y))])
359+
elif fmt == 'summary':
360+
data.append(array_to_summary(y, fmt=plot))
361+
data[-1]['weight'] = layer_name
361362

362363
if fmt == 'longform':
363364
data = pandas.DataFrame(data)
@@ -544,10 +545,10 @@ def _is_ignored_layer(layer):
544545
return False
545546

546547

547-
def _get_output(layer, X, model_input):
548-
"""Get output of partial model"""
549-
partial_model = keras.models.Model(inputs=model_input, outputs=layer.output)
550-
y = partial_model.predict(X)
548+
def _get_outputs(layers, X, model_input):
549+
"""Get outputs of intermediate layers"""
550+
partial_models = keras.models.Model(inputs=model_input, outputs=[layer.output for layer in layers])
551+
y = partial_models.predict(X)
551552
return y
552553

553554

@@ -562,37 +563,30 @@ def get_ymodel_keras(keras_model, X):
562563
Returns:
563564
dict: A dictionary in the form {"layer_name": ouput array of layer}.
564565
"""
565-
566566
ymodel = {}
567-
567+
traced_layers = []
568+
layer_names = []
568569
for layer in keras_model.layers:
569-
print(f"Processing {layer.name} in Keras model...")
570-
if not _is_ignored_layer(layer):
571-
# If the layer has activation integrated then separate them
572-
# Note that if the layer is a standalone activation layer then skip this
573-
if hasattr(layer, 'activation') and not (
574-
isinstance(layer, keras.layers.Activation) or isinstance(layer, qkeras.qlayers.QActivation)
575-
):
576-
if layer.activation:
577-
if layer.activation.__class__.__name__ == "linear":
578-
ymodel[layer.name] = _get_output(layer, X, keras_model.input)
579-
580-
else:
581-
temp_activation = layer.activation
582-
layer.activation = None
583-
# Get output for layer without activation
584-
ymodel[layer.name] = _get_output(layer, X, keras_model.input)
585-
586-
# Add the activation back
587-
layer.activation = temp_activation
588-
# Get ouput for activation
589-
ymodel[layer.name + f"_{temp_activation.__class__.__name__}"] = _get_output(
590-
layer, X, keras_model.input
591-
)
592-
else:
593-
ymodel[layer.name] = _get_output(layer, X, keras_model.input)
594-
else:
595-
ymodel[layer.name] = _get_output(layer, X, keras_model.input)
570+
if _is_ignored_layer(layer):
571+
continue
572+
# If the layer has activation integrated then separate them
573+
# Note that if the layer is a standalone activation layer then skip this
574+
name = layer.name
575+
if (
576+
hasattr(layer, "activation")
577+
and layer.activation.__name__ != "linear"
578+
and not isinstance(layer, (keras.layers.Activation, qkeras.qlayers.QActivation))
579+
):
580+
tmp_activation = layer.activation
581+
layer.activation = None
582+
ymodel.update({layer.name: _get_outputs([layer], X, keras_model.input)})
583+
layer.activation = tmp_activation
584+
name = layer.name + f"_{tmp_activation.__name__}"
585+
traced_layers.append(layer)
586+
layer_names.append(name)
587+
outputs = _get_outputs(traced_layers, X, keras_model.input)
588+
for name, output in zip(layer_names, outputs):
589+
ymodel[name] = output
596590
print("Done taking outputs for Keras model.")
597591
return ymodel
598592

test/pytest/test_trace.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,16 @@
1212

1313

1414
@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus'])
15-
def test_trace(backend):
15+
@pytest.mark.parametrize('activation', ['relu', None])
16+
def test_trace(backend, activation):
1617
'''Test the tracing feature with a simple Keras model.'''
1718
model = tf.keras.models.Sequential()
1819
model.add(
1920
Dense(
2021
2,
2122
input_shape=(1,),
2223
name='Dense',
24+
activation=activation,
2325
use_bias=True,
2426
kernel_initializer=tf.keras.initializers.RandomUniform(minval=1, maxval=10),
2527
bias_initializer='zeros',
@@ -48,6 +50,7 @@ def test_trace(backend):
4850
hls_model.compile()
4951
hls4ml_pred, hls4ml_trace = hls_model.trace(X_input)
5052
keras_trace = hls4ml.model.profiling.get_ymodel_keras(model, X_input)
51-
52-
np.testing.assert_allclose(hls4ml_trace['Dense'], keras_trace['Dense'], rtol=1e-2, atol=0.01)
53+
assert keras_trace.keys() == hls4ml_trace.keys()
54+
for key in hls4ml_trace.keys():
55+
np.testing.assert_allclose(hls4ml_trace[key], keras_trace[key], rtol=1e-2, atol=0.01)
5356
np.testing.assert_allclose(hls4ml_pred, keras_prediction, rtol=1e-2, atol=0.01)

0 commit comments

Comments
 (0)