5

The documentation for tf.keras.callbacks.TensorBoard states the tool can do it:

This callback logs events for TensorBoard, including:

  • Metrics summary plots
  • Training graph visualization
  • Activation histograms
  • Sampled profiling

Also later:

histogram_freq: frequency (in epochs) at which to compute activation and weight histograms for the layers of the model. If set to 0, histograms won't be computed. Validation data (or split) must be specified for histogram visualizations.

However when using this parameter I don't see any activation summary written, only the weights themselves are written. Looking at the source code I don't see anything activation-related either.

So am I missing something? Is it possible to write activation summaries without custom code in TF2?

Community
  • 1
  • 1
SiLiKhon
  • 583
  • 4
  • 15
  • I'm looking for the same. I have tried to log them using custom code, it was very easy in tf1. But for tf2.0 both `tf.summary.histogram("activations_0", self.model.outputs[0], step=step)` and using `K.get_value(self.model.outputs[0])` as in the TB callback `_log_weights` fail (using a `tf.Tensor` as a Python `bool` is not allowed in Graph execution vs 'Tensor' object has no attribute 'numpy'). Did you find any solution? – ana Jun 02 '20 at 03:22
  • 2
    @ana in the end I had to [write my own callback](https://gist.github.com/SiLiKhon/3965c967c3283feccc79822e6252b34c), which is not very pretty but does work. – SiLiKhon Jun 02 '20 at 08:10
  • How did you manage to log the activations/outputs w/o tf complaining? Thanks! – ana Jun 03 '20 at 11:54
  • As shown in the github gist, I'm creating an additional model which reuses the layers of the model of interest, and I set the activations of the original model (`attention_tensors` in the gist - this is a list of activation tensors) as outputs for this extra model. Then, at the summary writing step I simply make a forward pass through the extra model and log the result. – SiLiKhon Jun 03 '20 at 13:54
  • Oh, I see, sorry, didn't see the gist before. My bad. Thanks! – ana Jun 04 '20 at 06:59
  • 2
    I have filed https://github.com/tensorflow/tensorflow/issues/42027 – bers Aug 04 '20 at 10:02
  • A nice aspect of @SiLiKhon's approach: `model.predict()` returns the output for the whole validation data set, irrespective of how large that is and in how many batches is has to be split. Other approaches resting on using `model.layers[...].output` need to do that manually (unless I am mistaken). – bers Oct 02 '20 at 08:23

1 Answers1

3

Based on my answer to Create keras callback to save model predictions and targets for each batch during training, I use the following code:

"""Demonstrate activation histograms."""
import tensorflow as tf
from tensorflow import keras


class ActivationHistogramCallback(keras.callbacks.Callback):
    """Output activation histograms."""

    def __init__(self, layers):
        """Initialize layer data."""
        super().__init__()

        self.layers = layers
        self.batch_layer_outputs = {}
        self.writer = tf.summary.create_file_writer("activations")
        self.step = tf.Variable(0, dtype=tf.int64)

    def set_model(self, _model):
        """Wrap layer calls to access layer activations."""
        for layer in self.layers:
            self.batch_layer_outputs[layer] = tf_nan(layer.output.dtype)

            def outer_call(inputs, layer=layer, layer_call=layer.call):
                outputs = layer_call(inputs)
                self.batch_layer_outputs[layer].assign(outputs)
                return outputs

            layer.call = outer_call

    def on_train_batch_end(self, _batch, _logs=None):
        """Write training batch histograms."""
        with self.writer.as_default():
            for layer, outputs in self.batch_layer_outputs.items():
                if isinstance(layer, keras.layers.InputLayer):
                    continue
                tf.summary.histogram(f"{layer.name}/output", outputs, step=self.step)

        self.step.assign_add(1)


def tf_nan(dtype):
    """Create NaN variable of proper dtype and variable shape for assign()."""
    return tf.Variable(float("nan"), dtype=dtype, shape=tf.TensorShape(None))


def main():
    """Run main."""
    model = keras.Sequential([keras.layers.Dense(1, input_shape=(2,))])

    callback = ActivationHistogramCallback(model.layers)

    model.compile(loss="mse", optimizer="adam")
    model.fit(
        x=tf.transpose(tf.range(7.0) + [[0.2], [0.4]]),
        y=tf.transpose(tf.range(7.0) + 10 + [[0.5]]),
        validation_data=(
            tf.transpose(tf.range(11.0) + 30 + [[0.6], [0.7]]),
            tf.transpose(tf.range(11.0) + 40 + [[0.9]]),
        ),
        shuffle=False,
        batch_size=3,
        epochs=2,
        verbose=0,
        callbacks=[callback],
    )


if __name__ == "__main__":
    main()

For the example training with 2 epochs and 3 batches (of unequal size due to the odd number of 7 training samples), one then sees the expected output (6 batches with 3, 3, 1, 3, 3, 1 peaks).

enter image description here

With 200 epochs (600 batches), one can also see training progress:

enter image description here

bers
  • 4,817
  • 2
  • 40
  • 59
  • If anyone has a great idea how to aggregate the histograms for all batches of an epoch (other than aggregating all the raw data before histogramming), I'd take it :) This seems hard to do because a histogram is always defined in terms of some buckets, and it's hard to know which buckets to use before the complete data is available... – bers Feb 03 '22 at 21:43