<jupyter_start><jupyter_text>Measuring Learning in Intermediate Layers In this notebook, we take a simple fully connected model for classifying MNIST digits and we examine the speed of learning at various hidden layers within the model. [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/jonkrohn/DLTFpT/blob/master/notebooks/measuring_speed_of_learning.ipynb)  Set seed for reproducibility<jupyter_code>import numpy as np
np.random.seed(42)<jupyter_output><empty_output><jupyter_text>Load dependencies<jupyter_code>import tensorflow
from tensorflow.keras.datasets import mnist
from tensorflow.keras.utils import to_categorical
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout # new!
from tensorflow.keras import backend as K
import matplotlib.pyplot as plt
from scipy.ndimage.filters import gaussian_filter1d
%matplotlib inline<jupyter_output><empty_output><jupyter_text>Define a custom callbackThis callback grabs the weights at the end of each epoch and converts them back to gradients. The gradients are saved in a dictionary and at the end of training the Euclidean norm is taken as a measure of how fast training is moving in any given layer during any given epoch.<jupyter_code>class GradHistory(tensorflow.keras.callbacks.Callback):

    def on_train_begin(self, logs={}):
        # Initialize list of hidden layers
        self.layers = [layer for layer in self.model.layers if 'hidden_' in layer.name]
        
        # Initialize grads dict with empty list
        self.grads = {layer.name:[] for layer in self.layers}
        
        # Grab the initial weights from the model
        self.prev_w = [layer.get_weights()[0] for layer in self.layers]

    def on_epoch_end(self, batch, logs={}):
        # Get the weights at the end of the epoch
        curr_w = [layer.get_weights()[0] for layer in self.layers]
        
        # Get the LR at the end of the epoch
        lr = K.get_value(self.model.optimizer.lr)
        
        # Convert the previous and currents weights to gradients
        grads_ = [(prev - curr) for curr, prev in zip(curr_w, self.prev_w)]
        
        # Move the grads into the self.grads dict
        for i,layer in enumerate(grads_):
            self.grads[self.layers[i].name].append(layer)
        self.prev_w = curr_w
    
    def on_train_end(self, logs={}):
        # At the end of training, take the euclidean norm of each array of gradients in each layer at each epoch.
        self.norms = {k:[np.sqrt(np.sum([x*x for x in epoch])) for epoch in v] for k,v in self.grads.items()}<jupyter_output><empty_output><jupyter_text>Load data<jupyter_code>(X_train, y_train), (X_test, y_test) = mnist.load_data()<jupyter_output><empty_output><jupyter_text>Preprocess data<jupyter_code>X_train = X_train.reshape(60000, 784).astype('float32')
X_test = X_test.reshape(10000, 784).astype('float32')
X_train /= 255
X_test /= 255
n_classes = 10
y_train = to_categorical(y_train, n_classes)
y_test = to_categorical(y_test, n_classes)<jupyter_output><empty_output><jupyter_text>Design neural network architectureThis function builds and returns a model.<jupyter_code>def build_model(hidden = 2):
    
    model = Sequential()
    model.add(Dense(30, activation='sigmoid', input_shape=(784,),name='input'))
    
    for h in range(hidden):
        model.add(Dense(30, activation='sigmoid',name='hidden_{}'.format(h)))
    
    model.add(Dense(10, activation='softmax'))
    
    return model<jupyter_output><empty_output><jupyter_text>Build, compile and train modelHere we actually call the `build_model()` function, compile the model, and return the history object (where the gradients will besaved after training).<jupyter_code>def build_and_train(hidden = 1, epochs=200):
    
    model = build_model(hidden)
    history = GradHistory()
    model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy'])
    model.fit(X_train[:1000], y_train[:1000], batch_size=1, epochs=epochs, verbose=0, callbacks=[history])
    
    return history<jupyter_output><empty_output><jupyter_text>`history.grads.norms` is a dict, with keys for each of the layers in the model. Each key contains a list of gradient norm values for that layer over all the epochs in training.<jupyter_code># Call the method to build and train the model, returning the history object
# Note, here we use five hidden layers and 200 epochs
norms_5 = build_and_train(5, 200)<jupyter_output><empty_output><jupyter_text>Define a plotting function<jupyter_code>def plot(history, hidden, log=False):   
    fig = plt.figure(figsize=(8,6),dpi=300)
    
    layers = [layer for layer in history.norms.keys()]
    values = [history.norms[layer] for layer in layers]
    
    for layer,values in zip(layers[::-1],values[::-1]):
        ys = np.array(values[:])
        xs = np.array(range(ys.shape[0]))
        ys_smooth = gaussian_filter1d(ys, sigma=3)
        plt.plot(xs, ys_smooth, label=layer)
        
    plt.title('Learning speed with {} hidden layers'.format(hidden))
    plt.ylabel('Learning speed')
    if log:
        plt.yscale('log')
    plt.xlabel('Epoch')
    plt.legend(loc='upper right')
    plt.show()<jupyter_output><empty_output><jupyter_text>Plot the results<jupyter_code>plot(norms_5, 5, True)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10296034258052504, 'fraction_numerical': 0.014336250232731334, 'mean_word_length': 4.277013752455796, 'pattern_counts': {'":': 0, '<': 41, '<?xml version=': 0, '>': 41, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '10', 'dir': 'jupyter-structured-clean-dedup', 'id': '102695', 'license': 'MIT', 'n_tokens_mistral': 1798, 'n_tokens_neox': 1703, 'n_words': 477, 'path': 'notebooks/measuring_speed_of_learning.ipynb', 'repo_name': 'data-catalysis/DLTFpT', 'size': '5371'}