Anche quest'anno ho avuto il piacere di partecipare al Codemotion Milano, sia come membro del program committee per la selezione dei talk, che come speaker per parlare dell'attesissimo TensorFlow.js. Approfitto di questo breve post sul blog per riepilogare alcuni degli spunti principali del talk, dalla motivazione dell'unire reti neurali e Javascript al funzionamento della libreria stessa. Sul web trovate anche le slide dell'intervento ed il codice della demo.
Prima di tutto: perché mai deep learning nel browser?
Trattasi di una domanda meno scontata del previsto: l'offerta di API e linguaggi disponibili all'interno di TensorFlow è ormai enorme, e l'aggiunta di un nuovo linguaggio dedicato ai browser nel Maggio di quest'anno potrebbe sembrare di poco conto. In realtà, la possibilità di far girare modelli di deep learning direttamente nel browser apre numerose prospettive interessanti per una serie di ragioni. Alcune sono semplici da immaginare: lavorare on the edge pone meno problemi di privacy, oltre a semplificare la gestione di tutti i sensori e dati accessibili al browser (e a non richiedere alcuna installazione!). Altre motivazioni sono più interessanti: ad esempio, tramite l'accelerazione WebGL è possibile allenare modelli con qualsiasi GPU supportata dal browser, non dovendosi limitare ad un numero ristretto di schede grafiche.
Oltre a questo va ricordato che Javascript apre le porte ad un mondo di sviluppatori lontano dall'universo della data science, e quindi spesso avversi a Python o R, il che da solo rende questa libreria un tassello di enorme impatto nell'ecosistema TF!
Sui casi d'uso non vorrei soffermarmi troppo, ma vale la pena menzionare il settore dei giochi nel browser, che potrebbe uscirne decisamente ravvivato. O ricordiamo anche la possibilità di creare dashboard interattive per il debugging dei propri modelli, che rende TF.js appetibile anche a chi non avesse propensioni al frontend: ne abbiamo già visto un esempio molto condiviso con TensorSpace, e nuovi tool sono attesi con il rilascio di TensorFlow 2.0.
Ok, ma è fattibile?
Un'altra domanda piuttosto comune, visto che il mondo delle reti neurali è spesso associato ad ingenti spese di hardware, su cui lo stesso Google investe moltissimo (vedi capitolo TPU).
Prima di tutto, va sottolineato che i casi d'uso per TF.js sono molti, ed alcuni richiedono prestazioni computazionali scarne. È il caso ad esempio in cui volessimo importare un modello pre-allenato in un altro ambiente (es., tramite interfaccia Python, nel proprio data center). In generale, l'hardware a disposizione di un computer di fascia media si è alzato moltissimo negli ultimi anni, ed è più che sufficiente a coprire anche casi più interessanti. Per questo lasciamo parlare direttamente un'altra slide presa direttamente dalla presentazione ufficiale:
Infine sottolineiamo che da pochissimo TF.js supporta il rilascio su Node.js, con la possibilità di eseguire codice Javascript su qualsiasi piattaforma.
TensorFlow.js (core) in 10 minuti
Passiamo a vedere qualche aspetto dell'interfaccia di TF.js, che si compone di due parti: il core della libreria è un porting di una libreria precedente (deeplearn.js), ed implementa il "cuore" matematico dell'interfaccia, con le classiche operazioni per manipolare i tensori ed un meccanismo di differenziazione automatico. Oltre a questo, TF.js aggiunge una serie di API ad alto livello (fortemente ispirate a Keras) per costruire modelli ed ottimizzarli con più semplicità.
Al livello più basso, abbiamo la possibilità di creare e manipolare nativamente tensori di numeri. L'unica differenza se conoscete l'interfaccia Python, Javascript utilizza una notazione camelCase (senza underscore):
// Matrice di numeri estratti in maniera pseudo-casuale
const x = tf.randomUniform([3, 2]);
// Caricamento da array di Javascript
const y = tf.tensor([0.4, -0.2, 1.0]);
// Manipolazioni varie
const z = x.transpose().matMul(y.reshape([3, 1]));
Per rendere il codice più leggibile, TF.js introduce anche degli alias di tf.tensor
per array di dimensionalità nota: tf.tensor1d
, tf.tensor2d
, tf.tensor3d
, e tf.tensor4d
.
Sui tensori possiamo definire funzioni di vario tipo, e calcolarne automaticamente il gradiente:
// Definizione di una funzione
const axis = 1;
const f = x => tf.sin(x).sum(axis);
// fGrad calcola il gradiente di f
const fGrad = tf.grad(f);
Questo motore da solo permette già di importare ed eseguire modelli di vario tipo salvati in TensorFlow, replicandone le operazioni nel browser.
Prima di passare all'interfaccia più ad alto livello, menzioniamo rapidamente che, a differenza che in altri linguaggi, la gestione della memoria occupata dai tensori è un problema molto più essenziale all'interno di un browser. Per questo, TF.js mette a disposizione un semplice costrutto, tf.tidy
, per eseguire blocchi di codice eliminando al loro termine ogni variabile temporanea, molto utile per le fasi di caricamento dati:
const sum = tf.tidy(() => {
// a1 ed a2 sono solo variabili temporanee e saranno gestite da tf.tidy
const a1 = tf.randomUniform([3, 2]);
const a2 = tf.randomNormal([3, 2]);
return a1.square().add(a2);
});
TensorFlow.js, Keras-like
Come accennato prima, TF.js mette a disposizione anche un'interfaccia più ad alto livello (stile Keras), per definire ed ottimizzare i modelli, oltre ad importare modelli di Keras. La sintassi per definire un modello è praticamente equivalente:
// Inizializziamo un modello
const model = tf.sequential();
// Aggiungiamo due strati alla rete
model.add(tf.layers.dense({'units': 10, 'inputShape': 4}));
model.add(tf.layers.dense({'units': 1}));
Per rendere l'interfaccia il più simile possibile a Keras (in cui ogni parametro ha un nome), in TF.js tutti i parametri sono specificati all'interno di un oggetto di configurazione (un array associativo) tra parentesi graffe.
Anche la sintassi per allenare il modello è quasi equivalente alla sua versione in Python, con l'unica differenza che il passo di ottimizzazione è preferibile asincrono, per evitare di bloccare l'interfaccia dell'utente durante l'allenamento:
model.compile({'optimizer': 'sgd', loss: 'categoricalCrossentropy'});
await model.fit({x: xs, y: ys});
Ovviamente è possibile usare direttamente le classi che definiscono funzioni costo ed ottimizzatori per avere più controllo sulla fase di allenamento.
A fine allenamento, TF.js mette a disposizione una vasta gamma di possibilità per salvare e (ri)caricare il modello, o importarlo altrove.
Una demo, e conclusioni
Se l'argomento vi interessa, trovate il codice di una piccola demo online, con la spiegazione sulle slide (una rete ricorrente allenata a trovare il massimo in una sequenza di elementi). Oltre a questo, la libreria ha un discreto numero di tutorial ed esempi già implementati con cui sperimentare.
Se questo articolo ti è piaciuto e vuoi tenerti aggiornato sulle nostre attività, ricordati che l'iscrizione all'Italian Association for Machine Learning è gratuita! Puoi seguirci anche su Facebook, LinkedIn, e Twitter.