Introduzione
In questa nuova serie affronteremo il tema del machine learning e degli strumenti oggi disponibili per la sua implementazione e gestione.
In questo ambito, storicamente, Python è il linguaggio più usato da sempre, anzitutto per la comodità di sviluppo. Sono infatti disponibili librerie ed estensioni già pronte all’uso che consentono, insieme alla facilità della scrittura del linguaggio, di realizzare soluzioni di buona qualità.
Ma anche Python presenta dei limiti e dei punti deboli: perché allora non adottare delle piattaforme diverse, specificamente progettate e messe a punto proprio per i compiti di machine learning? Cominciamo con questa puntata a vedere le caratteristiche di ML.NET.
ML.NET: un’alternativa a Python per il machine learning
Come rivelato recentemente, Microsoft Research ha iniziato a sviluppare una piattaforma interna di machine learning già nel lontano 2003, a pochi mesi dal rilascio ufficiale della prima versione del .NET Framework.
Frammenti della piattaforma — cioè modelli — sono stati utilizzati a più riprese in applicazioni Office, Bing e Xbox. Poi, poco più di un anno fa (2018) è arrivato l’annuncio ufficiale di una libreria nativa .NET per fare machine learning. Tale libreria, denominata ML.NET, è stata rilasciata nella versione 1.0 a maggio 2019 e, nel momento in cui questo articolo viene scritto, è giunta alla versione 1.4.
È stimato che entro la fine del 2020, ragionevolmente in concomitanza con il rilascio di .NET 5.0, ML.NET avrà raggiunto uno stadio tale da renderla a tutti gli effetti di legge (di mercato) una alternativa inattaccabile e affidabile a Python.
Dunque, non si vive (più) di solo Python!
Pregi e limiti di Python
Il linguaggio Python è stato creato da Guido Van Rossum alla fine degli anni Ottanta al National Research Institute for Mathematics and Computer Science di Amsterdam ed è stato inizialmente rilasciato nel 1991. Python è un linguaggio particolare: ha una curva di apprendimento relativamente bassa, è espressivo e object oriented, è interattivo e interpretato ed è progettato per essere estendibile.
Ci sono due aspetti interessanti in Python.
- È volutamente un linguaggio facile da estendere con librerie ad hoc. Proprio per questa caratteristica, per esempio, oggi Python è molto usato nello sviluppo di soluzioni IoT e Web, oltre che di machine learning.
- Nonostante sia un linguaggio interpretato, Python non è esattamente lento e farraginoso. Esso, infatti, mantiene un legame stretto con il linguaggio C e, in caso di necessità, il codice può essere compilato just-in-time in C o addirittura esteso con moduli scritti in C.
I problemi di Python, che nell’ambito del machine learning vengono direttamente affrontati, e risolti, da ML.NET, nascono proprio da qui: dalla stretta commistione con il linguaggio C.
Perché Python è così popolare in machine learning?
Python è di gran lunga il linguaggio più popolare per machine learning. Sì, ma perché? È una domanda chiave. La risposta non sta nella particolare flessibilità o velocità di esecuzione del linguaggio ma semplicemente nella comodità di sviluppo.
Pronte all’uso in Python vi sono, infatti, decine di librerie ed estensioni fatte appositamente per supportare il machine learning e testate sul campo nel corso degli ultimi anni. Ma, se il successo di Python è una faccenda di pura convenienza, allora nulla impedisce di avere un altro ecosistema centrato su un altro linguaggio di programmazione, magari più vicino agli ambienti di produzione che, nella stragrande maggioranza, dei casi sono Java e .NET.
Le peculiarità di ML.NET
ML.NET dunque è un ecosistema crescente di strumenti (algoritmi e librerie di servizio) per fare machine learning direttamente in .NET. Ciò significa comporre e trasformare dataset, selezionare e configurare algoritmi, eseguire training e serializzare modelli. Tutto in .NET senza bisogno di null’altro.
In aggiunta, un modello costruito con ML.NET può essere direttamente importato in un’applicazione client che ne faccia uso in produzione. Proprio l’asimmetria tra gli ambienti di training — perlopiù basati su Python — e quelli di produzione — perlopiù incentrati su Java e .NET — costituisce un ostacolo potenzialmente rilevante alla diffusione di machine learning su larghissima scala.
Limiti tecnologici per il machine learning con Python
Oggi il machine learning sta pian piano passando dallo stadio iniziale di scienza dei dati, esoterica e per pochi iniziati, a quello più diffuso di tecnologia disponibile per tutte le soluzioni software. Un utilizzo sempre più esteso del machine learning inizia a porre una serie di problemi tecnologici che non sempre in Python trovano un’adeguata soluzione.
In particolare, si tratta della gestione della memoria e del multithreading nella fase di training, e del modello di hosting per quanto attiene alla fase di messa in produzione di un modello allenato.
Gestione della memoria
Nelle varie implementazioni di Python, la gestione della memoria è affidata a un componente scritto solitamente in C. Storicamente, la memoria necessaria a un’applicazione Python è come l’entropia dell’universo, ossia cresce di continuo; tutto ciò che si può fare è rallentarne la crescita.
È fondamentalmente un problema di frammentazione della memoria dovuta al limitato allineamento tra blocchi allocati e dimensione degli oggetti creati dall’applicazione. Un altro fattore di crescita continua della memoria è l’interning delle stringhe. Questi fattori non sempre fanno male, ma sono d’altra parte ineliminabili. Il risultato è che, con dataset di diversi gigabyte, non è infrequente che l’applicazione di training sia poco gestibile o che richieda un upgrade del livello di cloud corrente.
Multithreading
Python passa per un linguaggio perfettamente capace di supportare il multithreading, ossia l’esecuzione parallela di pezzi di codice su core CPU diversi. In Python di fatto ciò non avviene per via del famigerato Global Interpreter Lock (GIL).
Quando attivo — cioè sempre per default — il GIL limita l’accesso alla memoria a un thread alla volta. A conti fatti, applicazioni multithreaded, da cui ci si aspetta un incremento di performance, finiscono per essere addirittura più lente. Da un lato, il GIL rende possibile supportare thread multipli; dall’altro invece la sua presenza obbliga i thread a richiedere autorizzazione al GIL prima di eseguire codice. E il GIL concede tale autorizzazione a un thread alla volta.
Hosting del modello allenato
Infine, vi è il problema dello hosting del modello allenato in produzione. Al momento esistono tre opzioni:
- il modello viene — più o meno automaticamente — incapsulato in un servizio web e invocato via web API;
- il modello viene incorporato in un oggetto nativo nel linguaggio dell’applicazione client;
- il modello viene prima convertito in un formato universale e poi caricato nativamente nell’ambiente target di produzione.
La prima opzione (servizio web) è la più usata al momento e funziona benissimo. Ha il problema, però, di richiedere una chiamata HTTP per ogni invocazione del modello. Per giunta è pure difficile ipotizzare uno strato di cache per migliorare la performance. Il problema non è tanto la performance in sé — impattata dalla latenza della chiamata — ma quanto la latenza possa pregiudicare l’uso effettivo del modello in produzione. Immaginiamo un modello di predizione mission-critical che debba dare indicazione in pochi secondi di cosa fare: perdere uno o due secondi in latenza — senza contare l’affidabilità complessiva della rete — può davvero essere un problema.
La seconda opzione è quella ideale ma non è sempre praticabile se il modello è prodotto da un framework Python. Tensorflow supporta binding nativi in vari linguaggi tra cui C# e Java, ma lo stesso non vale per modelli costruiti usando scikit-learn.
La terza opzione è una via di mezzo e richiede che il formato nativo del modello sia convertito in un formato intermedio ONNX che prima o poi diventerà uno standard sancito e de facto, ma ancora non lo è. Il risultato è che il modello — un grafo computazionale da calcolare a runtime — potrebbe perdere qualcosa se convertito in ONNX visto che ONNX al momento è un comune denominatore tra i formati dei vari modelli prodotti dai vari framework. ONNX spesso funziona, ma non è ancora affidabile al 100%.
Perché ML.NET?
Dunque, perché ML.NET? Perché è scritto in .NET e i modelli prodotti possono essere caricati nativamente in qualsiasi applicazione client .NET. Al tempo stesso, il modello può essere esposto come servizio web ASP.NET Core, ossia una delle piattaforme più veloci attualmente in circolazione.
L’infrastruttura ML.NET in uso per il training degli algoritmi si avvale di un linguaggio di programmazione (C#) pienamente multi-threaded e superottimizzato quanto a gestione della memoria. In aggiunta, gli algoritmi di machine learning implementati nativamente nella libreria (SVM, alberi, regressori, classificatori, bayesiani) supportano nativamente task paralleli e streaming dei dati. In ML.NET, infatti, il dataset (ben oltre 1 TB di dimensione) non è mai interamente caricato in memoria ma viene utilizzato a spezzoni e parallelamente.
Prestazioni vs. algoritmi
Se, da un punto di vista di performance, ML.NET non teme rivali, la gamma di algoritmi può non essere al momento identica a quella disponibile in Python. Sebbene si tratti di un problema destinato a sparire nel giro di poco tempo, va osservato che comunque la natura interattiva di Python e gli ambienti di visualizzazione intelligente (vedi Jupyter) restano strumenti potenti a cui non è facile rinunciare, senza contare poi il codice esistente. Va comunque detto che ML.NET non si limita al codice .NET ma, grazie al framework gemello NimbusML, consente di incapsulare soluzioni Python (anche basate su scikit-learn), nuove o esistenti, in una nuova soluzione che è in grado di serializzare il modello finale nel formato nativo supportato da ML.NET. Si ottiene così il meglio del training Python-based e il meglio dell’hosting nativo su ASP.NET Core o, di riflesso, su piattaforma Java.
Conclusioni
Machine learning è diventato popolare grazie al linguaggio Python ed è cresciuto talmente tanto da mettere a nudo i limiti strutturali di un linguaggio nato per fare altro. La maggior parte delle applicazioni finali usa Java o .NET e mancano librerie in grado di importare in tutti gli scenari possibili, nativamente e in-process, i modelli prodotti da training Python. ML.NET è la proposta end-to-end, training e produzione, per l’ambiente .NET e .NET Core.