Repository: keras-team/keras

Issue:
Visualizing gradients via TensorBoard
I'd like to visualize weight grad distributions in TensorBoard. I tried to add grads to TB using `tf.summary.histogram` in TensorBoard class as follows (see full commit [here](https://github.com/gokceneraslan/keras/commit/9649bff94101a2b8eb184ad1f3b6c55b826a6e97)):

```patch
diff --git a/keras/callbacks.py b/keras/callbacks.py
index 49567f9..f605e7d 100644
--- a/keras/callbacks.py
+++ b/keras/callbacks.py
@@ -630,6 +630,9 @@ class TensorBoard(Callback):
 
                 for weight in layer.weights:
                     tf.summary.histogram(weight.name, weight)
+                    grads = model.optimizer.get_gradients(model.total_loss,
+                                                          weight)
+                    tf.summary.histogram('{}_grad'.format(weight.name), grads)
                     if self.write_images:
                         w_img = tf.squeeze(weight)
                         shape = w_img.get_shape()
```

But it seems targets and sample_weights are defined as placeholders, so they should be specified in the evaluation of grads. Is there a way to do this and add grad visualization to Keras?

I get the following trace when training starts:

```
Caused by op 'output_sample_weights', defined at:
  File "sample.py", line 9, in <module>
    learning_rate=0.001, reconstruct=False, activation='relu')
  File "api.py", line 24, in autoencode
    epochs=epochs, **kwargs)
  File "train.py", line 38, in train
    model.compile(loss=loss, optimizer=optimizer)
  File "/home/goekcen_eraslan/Code/keras-gokcen/keras/engine/training.py", line 858, in compile
    name=name + '_sample_weights'))
  File "/home/goekcen_eraslan/Code/keras-gokcen/keras/backend/tensorflow_backend.py", line 351, in placeholder
    x = tf.placeholder(dtype, shape=shape, name=name)
  File "/home/goekcen_eraslan/miniconda3/lib/python3.6/site-packages/tensorflow/python/ops/array_ops.py", line 1502, in placeholder
    name=name)
  File "/home/goekcen_eraslan/miniconda3/lib/python3.6/site-packages/tensorflow/python/ops/gen_array_ops.py", line 2149, in _placeholder
    name=name)
  File "/home/goekcen_eraslan/miniconda3/lib/python3.6/site-packages/tensorflow/python/framework/op_def_library.py", line 763, in apply_op
    op_def=op_def)
  File "/home/goekcen_eraslan/miniconda3/lib/python3.6/site-packages/tensorflow/python/framework/ops.py", line 2327, in create_op
    original_op=self._default_original_op, op_def=op_def)
  File "/home/goekcen_eraslan/miniconda3/lib/python3.6/site-packages/tensorflow/python/framework/ops.py", line 1226, in __init__
    self._traceback = _extract_stack()

InvalidArgumentError (see above for traceback): You must feed a value for placeholder tensor 'output_sample_weights' with dtype float
         [[Node: output_sample_weights = Placeholder[dtype=DT_FLOAT, shape=[], _device="/job:localhost/replica:0/task:0/gpu:0"]()]]
```

- [x] Check that you are up-to-date with the master branch of Keras. You can update with:
pip install git+git://github.com/fchollet/keras.git --upgrade --no-deps

- [x] If running on TensorFlow, check that you are up-to-date with the latest version. The installation instructions can be found [here](https://www.tensorflow.org/get_started/os_setup).

- [x] If running on Theano, check that you are up-to-date with the master branch of Theano. You can update with:
pip install git+git://github.com/Theano/Theano.git --upgrade --no-deps

- [x] Provide a link to a GitHub Gist of a Python script that can reproduce your issue (or just copy the script here if it is short).




Discussion:
In order to reproduce the issue, run `examples/mnist_mlp.py` from my Keras repository (https://github.com/gokceneraslan/keras/blob/master/examples/mnist_mlp.py) upon installing patched keras.
Follow this: https://github.com/fchollet/keras/issues/2226#issuecomment-259004640

Resolving patch:
diff --git a/keras/callbacks.py b/keras/callbacks.py
--- a/keras/callbacks.py
+++ b/keras/callbacks.py
@@ -582,15 +582,18 @@ class TensorBoard(Callback):
 
     # Arguments
         log_dir: the path of the directory where to save the log
-            files to be parsed by Tensorboard.
+            files to be parsed by TensorBoard.
         histogram_freq: frequency (in epochs) at which to compute activation
-            histograms for the layers of the model. If set to 0,
-            histograms won't be computed.
-        write_graph: whether to visualize the graph in Tensorboard.
+            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.
+        write_graph: whether to visualize the graph in TensorBoard.
             The log file can become quite large when
             write_graph is set to True.
+        write_grads: whether to visualize gradient histograms in TensorBoard.
+            `histogram_freq` must be greater than 0.
         write_images: whether to write model weights to visualize as
-            image in Tensorboard.
+            image in TensorBoard.
         embeddings_freq: frequency (in epochs) at which selected embedding
             layers will be saved.
         embeddings_layer_names: a list of names of layers to keep eye on. If
@@ -605,6 +608,7 @@ class TensorBoard(Callback):
     def __init__(self, log_dir='./logs',
                  histogram_freq=0,
                  write_graph=True,
+                 write_grads=False,
                  write_images=False,
                  embeddings_freq=0,
                  embeddings_layer_names=None,
@@ -617,6 +621,7 @@ def __init__(self, log_dir='./logs',
         self.histogram_freq = histogram_freq
         self.merged = None
         self.write_graph = write_graph
+        self.write_grads = write_grads
         self.write_images = write_images
         self.embeddings_freq = embeddings_freq
         self.embeddings_layer_names = embeddings_layer_names
@@ -630,6 +635,10 @@ def set_model(self, model):
 
                 for weight in layer.weights:
                     tf.summary.histogram(weight.name, weight)
+                    if self.write_grads:
+                        grads = model.optimizer.get_gradients(model.total_loss,
+                                                              weight)
+                        tf.summary.histogram('{}_grad'.format(weight.name), grads)
                     if self.write_images:
                         w_img = tf.squeeze(weight)
                         shape = K.int_shape(w_img)
@@ -718,13 +727,16 @@ def on_epoch_end(self, epoch, logs=None):
             if epoch % self.histogram_freq == 0:
                 # TODO: implement batched calls to sess.run
                 # (current call will likely go OOM on GPU)
+
+                val_data = self.validation_data
+                tensors = (self.model.inputs +
+                           self.model.targets +
+                           self.model.sample_weights)
+
                 if self.model.uses_learning_phase:
-                    cut_v_data = len(self.model.inputs)
-                    val_data = self.validation_data[:cut_v_data] + [0]
-                    tensors = self.model.inputs + [K.learning_phase()]
-                else:
-                    val_data = self.validation_data
-                    tensors = self.model.inputs
+                    tensors += [K.learning_phase()]
+
+                assert len(val_data) == len(tensors)
                 feed_dict = dict(zip(tensors, val_data))
                 result = self.sess.run([self.merged], feed_dict=feed_dict)
                 summary_str = result[0]
diff --git a/keras/engine/training.py b/keras/engine/training.py
--- a/keras/engine/training.py
+++ b/keras/engine/training.py
@@ -1839,8 +1839,11 @@ def generate_arrays_from_file(path):
                                  str(validation_data))
             val_x, val_y, val_sample_weights = self._standardize_user_data(
                 val_x, val_y, val_sample_weight)
+            val_data = val_x + val_y + val_sample_weights
+            if self.uses_learning_phase and not isinstance(K.learning_phase(), int):
+                val_data += [0.]
             for cbk in callbacks:
-                cbk.validation_data = val_x + [val_y, val_sample_weights]
+                cbk.validation_data = val_data
         enqueuer = None
 
         try:
diff --git a/keras/models.py b/keras/models.py
--- a/keras/models.py
+++ b/keras/models.py
@@ -786,11 +786,14 @@ def compile(self, optimizer, loss,
                            **kwargs)
         self.optimizer = self.model.optimizer
         self.loss = self.model.loss
+        self.total_loss = self.model.total_loss
         self.loss_weights = self.model.loss_weights
         self.metrics = self.model.metrics
         self.metrics_tensors = self.model.metrics_tensors
         self.metrics_names = self.model.metrics_names
         self.sample_weight_mode = self.model.sample_weight_mode
+        self.sample_weights = self.model.sample_weights
+        self.targets = self.model.targets
 
     def fit(self, x, y, batch_size=32, epochs=10, verbose=1, callbacks=None,
             validation_split=0., validation_data=None, shuffle=True,


Test patch:
diff --git a/tests/keras/test_callbacks.py b/tests/keras/test_callbacks.py
--- a/tests/keras/test_callbacks.py
+++ b/tests/keras/test_callbacks.py
@@ -8,7 +8,7 @@
 from keras import optimizers
 from keras import callbacks
 from keras.models import Sequential
-from keras.layers.core import Dense
+from keras.layers.core import Dense, Dropout
 from keras.layers.convolutional import Conv2D
 from keras.layers.pooling import MaxPooling2D, GlobalAveragePooling2D
 from keras.utils.test_utils import get_test_data
@@ -311,13 +311,14 @@ def data_generator_graph(train):
     # case 1 Sequential
     model = Sequential()
     model.add(Dense(num_hidden, input_dim=input_dim, activation='relu'))
+    model.add(Dropout(0.1))
     model.add(Dense(num_class, activation='softmax'))
     model.compile(loss='categorical_crossentropy',
                   optimizer='sgd',
                   metrics=['accuracy'])
 
     tsb = callbacks.TensorBoard(log_dir=filepath, histogram_freq=1,
-                                write_images=True)
+                                write_images=True, write_grads=True)
     cbks = [tsb]
 
     # fit with validation data
@@ -380,7 +381,7 @@ def test_TensorBoard_convnet():
                   optimizer='rmsprop',
                   metrics=['accuracy'])
     tsb = callbacks.TensorBoard(log_dir=filepath, histogram_freq=1,
-                                write_images=True)
+                                write_images=True, write_grads=True)
     cbks = [tsb]
     model.summary()
     history = model.fit(x_train, y_train, epochs=2, batch_size=16,
@@ -391,6 +392,55 @@ def test_TensorBoard_convnet():
     shutil.rmtree(filepath)
 
 
+@keras_test
+def test_CallbackValData():
+    np.random.seed(1337)
+    (X_train, y_train), (X_test, y_test) = get_test_data(num_train=train_samples,
+                                                         num_test=test_samples,
+                                                         input_shape=(input_dim,),
+                                                         classification=True,
+                                                         num_classes=num_class)
+    y_test = np_utils.to_categorical(y_test)
+    y_train = np_utils.to_categorical(y_train)
+    model = Sequential()
+    model.add(Dense(num_hidden, input_dim=input_dim, activation='relu'))
+    model.add(Dense(num_class, activation='softmax'))
+    model.compile(loss='categorical_crossentropy',
+                  optimizer='sgd',
+                  metrics=['accuracy'])
+
+    cbk = callbacks.LambdaCallback(on_train_end=lambda x: 1)
+    model.fit(X_train, y_train, batch_size=batch_size,
+              validation_data=(X_test, y_test), callbacks=[cbk], epochs=1)
+
+    def data_generator(train):
+        if train:
+            max_batch_index = len(X_train) // batch_size
+        else:
+            max_batch_index = len(X_test) // batch_size
+        i = 0
+        while 1:
+            if train:
+                yield (X_train[i * batch_size: (i + 1) * batch_size],
+                       y_train[i * batch_size: (i + 1) * batch_size])
+            else:
+                yield (X_test[i * batch_size: (i + 1) * batch_size],
+                       y_test[i * batch_size: (i + 1) * batch_size])
+            i += 1
+            i = i % max_batch_index
+
+    cbk2 = callbacks.LambdaCallback(on_train_end=lambda x: 1)
+    model.fit_generator(data_generator(True), len(X_train), epochs=1,
+                        validation_data=(X_test, y_test),
+                        callbacks=[cbk2])
+
+    # callback validation data should always have x, y, and sample weights
+    assert len(cbk.validation_data) == len(cbk2.validation_data) == 3
+    assert cbk.validation_data[0] is cbk2.validation_data[0]
+    assert cbk.validation_data[1] is cbk2.validation_data[1]
+    assert cbk.validation_data[2].shape == cbk2.validation_data[2].shape
+
+
 @keras_test
 def test_LambdaCallback():
     np.random.seed(1337)