Visualizzare statistiche con la TensorBoard

Prerequisiti per il tutorial:

Contenuti del tutorial:

  1. Struttura della TensorBoard.
  2. Esempio di utilizzo della TensorBoard.

Come funziona la TensorBoard

Come abbiamo visto nei tutorial precedenti, gestire quanto succede nelle fasi di allenamento delle reti neurali è un'operazione complessa, resa ulteriormente difficile dalla natura simbolica dei grafi computazionali.

La TensorBoard è un'applicazione web, a sua volta rilasciata in open source da Google, che mira a rendere più semplice la raccolta e la visualizzazione dei dati ai fini dell'analisi e del debugging. La TensorBoard opera aggiungendo operazioni speciali al grafo computazionale, dette summary, che permettono di raccogliere dati di vario tipo (scalari, istogrammi, e così via) e salvarli su file di log tramite un writer. Attraverso una dashboard web, infine, è possibile visualizzare interattivamente i dati contenuti nei log.

Vediamo di seguito un esempio di utilizzo della TensorBoard allenando una rete neurale su un problema di classificazione, collezionando al contempo l'evoluzione della funzione costo e diverse altre informazioni utili ad analizzarne il comportamento.

Impostiamo il problema

Carichiamo un dataset di benchmark di classificazione tra quelli presenti di default su scikit-learn. Per questo tutorial, useremo l'intero dataset per l'ottimizzazione, senza preoccuparci di overfitting o di valutare l'accuratezza.


In [1]:
from sklearn import datasets, model_selection
data = datasets.load_breast_cancer()
X = data['data']
y = data['target'].reshape(-1, 1)

Inizializziamo una rete neurale con uno strato nascosto, allo stesso modo di quanto fatto nei tutorial precedenti, cominciando dai placeholder per input ed output desiderato:


In [2]:
import tensorflow as tf
X_tf = tf.placeholder(tf.float32, [None, X.shape[1]])
y_tf = tf.placeholder(tf.float32, [None, 1])

Creiamo il primo strato della rete:


In [3]:
import numpy as np
with tf.name_scope('hidden_layer'):
    W1 = tf.Variable(np.random.randn(X.shape[1], 15)*0.01, dtype=tf.float32)
    b1 = tf.Variable(np.ones([15])*0.01, dtype=tf.float32)
    h = tf.nn.tanh(tf.matmul(X_tf, W1) + b1)

Ricordiamo che definire le operazioni all'interno di dei name scope permette di visualizzarli in maniera più semplice sul grafo. Continuiamo con lo strato di output:


In [4]:
with tf.name_scope('output_layer'):
    w2 = tf.Variable(np.random.randn(15, 1)*0.01, dtype=tf.float32)
    b2 = tf.Variable([0.01], dtype=tf.float32)
    f_pre = tf.matmul(h, w2) + b2
    f = tf.nn.sigmoid(f_pre)

E, per finire, la funzione costo:


In [5]:
loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(labels=y_tf, logits=f_pre))

Summary e FileWriter

Per ciascuna quantità che vogliamo monitorare, dobbiamo definire un'appropriata operazione sul grafo, ovvero una summary. Esistono summary di vario tipo, elencate qui:
https://www.tensorflow.org/api_guides/python/summary

In questa fase, siamo principalmente interessati a summary scalari ed istogrammi. In particolare, siamo interessati a monitorare l'andamento della funzione costo, oltre a due istogrammi relativi ai pesi della rete neurale, tutte quantità importanti per il debug. La prima permette di valutare la convergenza del processo di ottimizzazione, mentre gli istogrammi danno un'idea complessiva dei valori assunti dalla rete neurale, e permettono di identificare situazioni patologiche quali, per esempio, distribuzioni di pesi fortemente concentrate attorno ad un singolo valore.

Iniziamo dal definire un'operazione di summary per la funzione costo:


In [6]:
summary_loss = tf.summary.scalar('loss', loss)

Aggiungiamo poi i summary per le due matrici di pesi:


In [7]:
summary_w1 = tf.summary.histogram('W1', W1)
summary_w2 = tf.summary.histogram('w2', w2)

Eseguire i summary individualmente uno per uno è un'operazione tediosa. A questo scopo, TensorFlow mette a disposizione una funzione che ritorna una nuova operazione, la quale permette di calcolare tutti i summary del nostro grafo contemporaneamente:


In [8]:
merged = tf.summary.merge_all()

Oltre a questo, come già detto, abbiamo bisogno di un FileWriter per scrivere i dati risultanti su un file di log:


In [9]:
sess = tf.InteractiveSession()
train_writer = tf.summary.FileWriter('logs', sess.graph)

Adesso possiamo procedere, come in precedenza, inizializzando un algoritmo di ottimizzazione:


In [10]:
with tf.name_scope('train'):
    train_step = tf.train.AdamOptimizer().minimize(loss)

Adam è in assoluto uno degli algoritmi più popolari per ottimizzare reti neurali; ne parleremo più avanti. Per ora, continuiamo inizializzando tutte le variabili del grafo:


In [11]:
tf.global_variables_initializer().run()

Ad ogni iterazione, eseguiamo sia l'operazione di training (train_step), che i nostri summary, tramite l'operazione merged. Usiamo quindi il FileWriter per scrivere i risultati di quest'ultima su disco:


In [12]:
for i in range(300):
    summary, _ = sess.run([merged, train_step], feed_dict={X_tf: X, y_tf: y})
    train_writer.add_summary(summary, i)

Si noti la sintassi: possiamo eseguire più operazioni in una sola chiamata unendole in una lista. La chiamata restituisce quindi due valori, uno per ciascuna operazione. In questo caso, il risultato di merged viene passato al writer (insieme all'indice dell'iterazione), mentre il secondo valore viene ignorato.

Prima di passare alla TensorBoard, possiamo anche salvare il nostro grafo per visualizzarlo a sua volta interattivamente:


In [13]:
train_writer.add_graph(tf.get_default_graph())

Lanciamo la TensorBoard

Per lanciare la TensorBoard, eseguiamo il seguente comando da terminale (supponendo di stare nella stessa cartella di questo notebook):

tensorboard --logdir=logs

Se la pagina non viene immediatamente visualizzata, possiamo raggiungerla da questo indirizzo:

http://localhost:6006

Nella prima pagina, possiamo visualizzare tutti i summary relativi a scalari:

Se nella cartella sono presenti più file, vengono visualizzati insieme e possono essere selezionati dal menù in basso a sinistra. Dal grafico, notiamo ad esempio che l'algoritmo è ancora lontano dalla convergenza. Sulla sinistra possiamo inoltre aggiustare il livello di smoothing applicato alla curva.

Spostandoci nella sezione GRAPHS, possiamo visualizzare il grafo computazionele

Cliccando sul '+' in ciascun name scope, possiamo estenderlo ed esplorare le operazioni al suo interno:

Nella sezione DISTRIBUTIONS, possiamo valutare l'istogramma dei pesi:

La linea centrale rappresenta la mediana dei pesi (per ogni iterazione), mentre le zone colorate raggruppano in sequenza il 68esimo percentile, il 95esimo, il 99.7esimo, ed il 100% dei valori. Ad esempio, in questo caso possiamo vedere che i valori si sono spostati da una distribuzione molto vicina a zero (come da inizializzazione), fino ad una distribuzione sempre centrata in zero, ma con valori nell'intervallo [-0.4, +0.4], leggermente asimmetrica verso i valori negativi. Per una visualizzazione più completa, possiamo vedere gli istogrammi nella sezione HISTOGRAMS:

Per questo tutorial è tutto! Introdurremo altre sezioni più avanzate della TensorBoard nei tutorial successivi.