La bellezza dei linguaggi funzionali nell’apprendimento profondo-Clojure e Haskell

L’apprendimento profondo è un sottoinsieme di metodi di apprendimento automatico basati su reti neurali artificiali. Questi sono ispirati dall’elaborazione delle informazioni e dai nodi di comunicazione distribuiti nei sistemi biologici come il cervello. Nel deep learning, ogni livello impara a trasformare i dati di input in una rappresentazione leggermente più astratta e composita. Ad esempio, in un sistema di riconoscimento facciale, i pixel potrebbero essere uno strato del sistema, mentre i bordi potrebbero essere un altro, gli occhi potrebbero essere un altro e il viso potrebbe essere un altro. La complessità dei metodi di apprendimento profondo rende l’utilizzo di pacchetti esistenti popolare nella comunità di programmazione. TensorFlow e PyTorch in Python sono popolari, così come il pacchetto Keras in R. Tuttavia, nella produzione di sistemi di deep learning, le prestazioni e la sicurezza sono due problemi che spingono le aziende a scegliere linguaggi di programmazione funzionali come Clojure e Haskell.

image

Le difficoltà delle implementazioni di deep learning

Nel mettere in produzione sistemi di deep learning, le reti neurali potrebbero contenere un milione di parametri. I dati possono esplodere rapidamente per addestrare questi parametri. Questa esplosione di dati richiede prestazioni che possono essere raggiunte solo da un linguaggio di programmazione efficiente con funzionalità di concorrenza e parallelismo sicure. A causa della complessità delle reti neurali, con i dati passati da un livello all’altro, la semplicità e la coerenza nel modo in cui il linguaggio di programmazione gestisce questi dati è importante. Sicurezza, in questo caso, significa la capacità di preservare lo stato dei dati originali in modo coerente, mentre semplicità significa essere in grado di leggere e mantenere facilmente la base di codice massimizzando le prestazioni.

Perché la programmazione funzionale è più adatta per l’apprendimento profondo

Nel tentativo di risolvere alcune delle difficoltà che possono verificarsi quando si implementa l’apprendimento profondo, i programmatori stanno scoprendo che i linguaggi di programmazione funzionali possono fornire soluzioni.

In informatica, la programmazione funzionale è un paradigma di programmazione che considera il calcolo come la valutazione di funzioni matematiche ed evita di cambiare stato e dati mutabili. È un modello di programmazione più vicino al pensiero matematico.

I modelli di deep learning sono essenzialmente modelli matematici. Ad esempio, le reti neurali artificiali comprendono nodi collegati, ognuno dei quali esegue semplici operazioni matematiche. Utilizzando un linguaggio di programmazione funzionale, i programmatori sono in grado di descrivere queste operazioni matematiche in un linguaggio più vicino alle operazioni stesse. Il modo esplicito in cui questi programmi sono scritti rende la lettura e il mantenimento della base di codice molto più facile.

Allo stesso tempo, la natura compositiva degli algoritmi di deep learning significa che, ad ogni livello del lavoro neurale, i livelli o le funzioni tendono a concatenarsi per eseguire compiti. Questo può essere facilmente implementato utilizzando il concatenamento funzionale di un linguaggio di programmazione funzionale.

image

Inoltre, nel deep learning, quando le funzioni vengono applicate ai dati, i dati non cambiano. I nuovi valori potrebbero essere emessi in sequenza lungo la linea, ma i dati stessi rimangono coerenti. La funzione di immutabilità di un linguaggio di programmazione funzionale consentirà al programmatore di creare un nuovo set di dati ogni volta che vengono generati nuovi valori senza alterare il set di dati immutabile originale. Questo rende più facile mantenere la coerenza dei dati in tutta la rete neurale.

Infine, l’elevato numero di parametri e dati di formazione coinvolti nell’implementazione del deep learning significa che parallelismo e concorrenza sono le chiavi per creare sistemi di deep learning a livello di produzione. Parallelismo significa eseguire thread su CPU diverse per accelerare il processo di apprendimento. Concorrenza significa la capacità di gestire i thread per evitare conflitti. La programmazione funzionale consente la concorrenza e il parallelismo a costo zero. Ciò significa che, per sua natura, la programmazione funzionale, in cui la funzione pura è stateless, produrrà sempre lo stesso output per un particolare input, porterà alla capacità di isolare qualsiasi funzione ed eseguire ogni volta che lo si desidera. Ciò rende la concorrenza e il parallelismo molto più facili da gestire. Non devi affrontare problemi come deadlock e condizioni di gara. Thread diversi che accedono a CPU diverse saranno in grado di funzionare indipendentemente senza contestazioni.

Clojure

Con la programmazione funzionale che sta guadagnando popolarità nell’apprendimento profondo e con i robusti pacchetti disponibili per l’apprendimento profondo, Clojure è ora favorito da aziende come Walmart e Facebook. È un linguaggio di programmazione funzionale dinamico di alto livello basato sul linguaggio di programmazione LISP e ha compilatori che rendono possibile l’esecuzione sia su Java che sull’ambiente di runtime.NET.

La potenza della programmazione concorrente in Clojure

Clojure non sostituisce il sistema di thread Java, piuttosto funziona con esso. Poiché le strutture di dati di base sono immutabili, possono essere condivise facilmente tra i thread. Allo stesso tempo, sono possibili cambiamenti di stato nel programma, ma Clojure fornisce meccanismi per garantire che gli stati rimangano coerenti. Se si verificano conflitti tra 2 transazioni che tentano di modificare lo stesso riferimento, una di esse andrà in pensione. Non c’è bisogno di un blocco esplicito.

(import '(java.util.concurrent Executors))(defn test-stm (let (fn (dotimes (dosync (doseq (alter r + 1 t)))))) (range nthreads))] (doseq (.get future)) (.shutdown pool) (map deref refs)))(test-stm 10 10 10000) -> (550000 550000 550000 550000 550000 550000 550000 550000 550000 550000)

Source

Il parallelismo in Clojure è economico

Nell’apprendimento profondo, i modelli devono allenarsi su grandi quantità di dati. Il parallelismo implica l’esecuzione di più thread su CPU diverse. Parallelismo che è a buon mercato significherà significativi miglioramenti delle prestazioni. Utilizzando partizione in combinazione con la mappa può raggiungere parallelismo che è meno costoso.

(defn calculate-pixels-2 (let (doall (map (fn (let (get-color (process-pixel (/ row (double *width*)) (/ col (double *height*)))))) x))) work)] (doall (apply concat result))))

Source

Funzioni di concatenamento in Clojure significa chiarezza

In Clojure, ci sono molte funzioni per pochissimi tipi di dati. Le funzioni possono anche essere passate come argomenti ad altre funzioni, il che rende possibili le funzioni di concatenamento nell’apprendimento profondo. Con l’implementazione più vicina al modello matematico attuale, il codice Clojure può essere semplice da leggere e mantenere.

;; pipe arg to function(-> "x" f1) ; "x1";; pipe. function chaining(-> "x" f1 f2) ; "x12"

Source

Identità e stato in Clojure forniscono sicurezza

In Clojure, l’identità di ogni modello ha uno stato in qualsiasi momento. Quello stato è un vero valore che non cambia mai. Se l’identità sembra cambiare, è perché è associata a uno stato diverso. I nuovi valori sono funzioni di vecchio. All’interno di ogni strato della rete neurale, lo stato dei dati originali viene sempre conservato. Ogni set di dati con nuovi valori che sono uscite di funzioni può funzionare in modo indipendente. Ciò significa che le azioni possono essere eseguite su questi set di dati in modo sicuro o senza riguardo alla contesa. Possiamo fare riferimento allo stato originale dei dati in qualsiasi momento. Pertanto, coerenza, in questo caso, significa sicurezza.

Librerie e limitazioni

Storicamente, la libreria di apprendimento automatico cortex contiene tutto ciò che serve per implementare algoritmi di apprendimento automatico in Clojure. Con la recente crescente popolarità del framework MXNet open-source per l’apprendimento profondo, è più facile implementare l’apprendimento profondo utilizzando l’API MXNet-Clojure.

Anche se ora ci sono diverse API e librerie di apprendimento automatico disponibili per Clojure, c’è ancora una curva di apprendimento ripida per diventare fluente in esso. I messaggi di errore possono essere criptici e le aziende dovranno essere disposte a investire in anticipo per usarlo per scalare i loro sistemi di apprendimento automatico. Man mano che altri esempi di sistemi pronti per la produzione sono scritti in Clojure, il linguaggio acquisirà maggiore popolarità nei prossimi anni, ma solo se il numero e le dimensioni delle librerie che accompagnano l’uso di Clojure cresceranno costantemente.

Haskell

Haskell è un linguaggio funzionale che viene tipizzato staticamente con inferenza di tipo e valutazione lazy. Si basa sulla semantica del linguaggio di programmazione Miranda e considerato più espressivo, più veloce e più sicuro per l’implementazione dell’apprendimento automatico.

La sicurezza di tipo in Haskell fornisce sicurezza e flessibilità

La sicurezza di tipo definisce i vincoli sui tipi di valori che una variabile può contenere. Ciò contribuirà a prevenire operazioni illegali, fornire una migliore sicurezza della memoria e portare a un minor numero di errori logici. Valutazione pigra significa che Haskell ritarderà la valutazione di un’espressione fino a quando non sarà necessario il suo valore. Evita anche valutazioni ripetute, il che farà risparmiare tempo di esecuzione. Allo stesso tempo, la valutazione pigra consente di definire infinite strutture di dati. Questo dà un programmatore possibilità matematiche illimitate.

Il semplice codice esplicito in Haskell fornisce implementazioni chiare

Uno dei maggiori vantaggi di Haskell è che può descrivere algoritmi in costrutti matematici molto espliciti. È possibile rappresentare un modello in poche righe di codice. Puoi anche leggere il codice nello stesso modo in cui puoi leggere un’equazione matematica. Questo può essere molto potente in algoritmi complessi come gli algoritmi di apprendimento profondo nell’apprendimento automatico. Ad esempio, la seguente implementazione di un singolo strato di una rete neurale feed-forward mostra quanto sia leggibile il codice.

import Numeric.LinearAlgebra.Static.Backproplogistic :: Floating a => a -> alogistic x = 1 / (1 + exp (-x))feedForwardLog :: (KnownNat i, KnownNat o) => Model (L o i :& R o) (R i) (R o)feedForwardLog (w :&& b) x = logistic (w #> x + b)

Source

Il parallelismo multicore in Haskell fornisce prestazioni

Nel deep learning, le reti neurali tipiche conterranno un milione di parametri che definiscono il modello. Inoltre, è necessaria una grande quantità di dati per apprendere questi parametri, che, computazionalmente, richiede molto tempo. Su una singola macchina, l’utilizzo di più core per condividere la memoria e il processo in parallelo può essere molto potente quando si tratta di implementare l’apprendimento profondo. In Haskell, tuttavia, l’implementazione del parallelismo multicore è facile.

Librerie e limitazioni

La libreria Hlearn di Haskell contiene implementazioni di algoritmi di apprendimento automatico, mentre l’associazione tensor-flow per Haskell può essere utilizzata per l’apprendimento profondo. Parallel e Concurrent, nel frattempo, vengono utilizzati per parallelismo e concorrenza.

Sebbene ci siano alcune librerie di apprendimento automatico sviluppate in Haskell, le implementazioni di base dovranno ancora essere eseguite per le implementazioni Haskell pronte per la produzione. Mentre le biblioteche pubbliche disponibili per specifiche attività di deep learning e machine learning sono limitate, anche l’utilizzo di Haskell in AI sarà limitato. Aziende come Aetion Technologies e Credit Suisse Global Modeling and Analytics Group utilizzano Haskell nelle loro implementazioni: ecco un elenco completo delle organizzazioni che lo utilizzano.

Conclusione

I modelli di deep learning sono modelli matematici complessi che richiedono una stratificazione specifica delle funzioni. Linguaggi di programmazione funzionali come Clojure e Haskell possono spesso rappresentare la complessità con un codice più pulito che è più vicino alla matematica del modello. Ciò porta a risparmi di tempo, efficienza e facile gestione della base di codice. Le proprietà specifiche della programmazione funzionale consentono alle implementazioni in questi linguaggi di essere più sicure di quelle di altri linguaggi. Man mano che lo sviluppo della tecnologia AI progredisce, la valutazione di questi linguaggi per le esigenze dei progetti di sviluppo di sistemi su larga scala in AI diventerà più prevalente.

Questo articolo fa parte di Behind the Code, il supporto per gli sviluppatori, dagli sviluppatori. Scopri altri articoli e video visitando Behind the Code!

Vuoi contribuire? Ottenere pubblicato!

Seguici su Twitter per rimanere sintonizzati!

Illustrazione di Victoria Roussel

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.