Approssimazione di una funzione reale di una variabile con TensorFlow

Questo post fa parte di una serie di post sull'approssimazione di oggetti matematici (funzioni, curve e superfici) tramite una rete neurale di tipo MLP (percettrone multistrato, dall'inglese Multi-Layer Perceptron); per una introduzione sull'argomento si veda il post Approssimazione con percettroni multistrato altamente configurabili.

L'argomento di questo post è l'approssimazione di una funzione reale continua e limitata a valori reali di una variabile reale in un intervallo chiuso $$f(x) \colon [a,b] \to \rm I\!R$$ con un MLP in modo che l'utilizzatore possa testare diverse combinazioni di architettura MLP, funzioni di attivazione, algoritmo di addestramento, metriche e funzione di loss senza scrivere codice ma agendo solamente sulla linea di comando di sei script Python che implementano le funzionalità di:

  • Creazione dei dataset
  • Definizione dell'architettura del MLP + Addestramento
  • Predizione
  • Visualizzazione del risultato
  • Diagnostica
Il codice descritto da questo post richiede la versione 3 di Python e utilizza la tecnologia TensorFlow 2 (sia per CPU che per GPU) con Keras (che è già integrato dentro TensorFlow 2); richiede inoltre le librerie NumPy, MatPlotLib e ImageIO.
Per ottenere il codice si veda il paragrafo Download del codice completo in fondo a questo post.

Lo stesso identico meccanismo è stato realizzato usando la tecnologia PyTorch; si veda il post Approssimazione di una funzione reale di una variabile con PyTorch pubblicato sempre su questo sito web.
Se si fosse invece interessati alla regressione di una funzione reale di una variabile con tecniche di machine learning si vedano i post Approssimazione di funzioni tramite un regressore XGBoost configurabile, Regressione polinomiale con Accord.NET e Regressione con SMO per SVM con kernel PUK in Weka.

Creazione dei dataset

Scopo del programma Python fx_gen.py è di generare i dataset (di training e/o di test) da utilizzare nelle fasi successive; prende in linea di comando la funzione da approssimare (in sintassi lambda body), l'intervallo della variabile indipendente (inizio, fine e passo di discretizzazione) e genera il dataset in un file nel formato csv applicando la funzione all'intervallo passato.
Il file csv in uscita ha infatti due colonne (con header): la prima colonna contiene i valori, ordinati in modo crescente, della variabile indipendente $x$ nell'intervallo desiderato con il passo di discretizzazione specificato; la seconda colonna contiene i valori della variabile dipendente, ovverosia i valori della funzione $f(x)$ corrispondenti al valore di $x$ della prima colonna.

Per ottenere l'usage del programma è sufficiente eseguire il seguente comando:

$ python fx_gen.py --help
e l'output ottenuto è:

usage: fx_gen.py [-h] 
-h, --help                  show this help message and exit
--dsout DS_OUTPUT_FILENAME  dataset output file (csv format)
--fx FUNC_X_BODY            f(x) body (body lamba format)
--rbegin RANGE_BEGIN        begin range (default:-5.0)
--rend RANGE_END            end range (default:+5.0)
--rstep RANGE_STEP          step range (default: 0.01)
Si rimanda alla lettura del file README.md per il dettaglio esaustivo della semantica dei parametri supportati in linea di comando.

Un esempio di uso del programma fx_gen.py

Si supponga di voler approssimare nell'intervallo $[-20.0,20.0]$ la seguente funzione $$f(x)=\frac{\sin 2x}{e^\frac{x}{5}}$$ Tenendo presente che np è l'alias della libreria NumPy, questa si traduce in sintassi lambda body Python così:

np.sin(2 * x) / np.exp(x / 5)
Per generare il dataset di training, si esegua quindi il seguente comando:

$ python fx_gen.py \
  --dsout mytrain.csv \
  --fx "np.sin(2 * x) / np.exp(x / 5)" \
  --rbegin -20.0 \
  --rend 20.0 \
  --rstep 0.01
mentre per generare il dataset di test, si esegua il seguente comando:

$ python fx_gen.py \
  --dsout mytest.csv \
  --fx "np.sin(2 * x) / np.exp(x / 5)" \
  --rbegin -20.0 \
  --rend 20.0 \
  --rstep 0.0475
Si osservi che il passo di discretizzazione del dataset di test è più grande di quello di training e questo è normale in quanto l'addestramento, per essere accurato, deve essere eseguito su una maggiore quantità di dati. Inoltre si osservi che è opportuno che il passo di discretizzazione del dataset di test non sia un multiplo di quello di training per garantire che il dataset di test contenga la maggior parte dei dati non presenti nel dataset di training, e questo rende più interessante la predizione.

Definizione dell'architettura del MLP + Addestramento

Scopo del programma Python fx_fit.py è, in accordo con i parametri passati in linea di comando, creare dinamicamente un MLP ed effettuare il suo addestramento. Per ottenere l'usage del programma è sufficiente eseguire il seguente comando:

$ python fx_fit.py --help
e l'output ottenuto è:

usage: fx_fit.py [-h]
--trainds TRAIN_DATASET_FILENAME
--modelout MODEL_PATH
[--valds VAL_DATASET_FILENAME]
[--bestmodelmonitor BEST_MODEL_MONITOR]
[--epochs EPOCHS]
[--batch_size BATCH_SIZE]
[--hlayers HIDDEN_LAYERS_LAYOUT [HIDDEN_LAYERS_LAYOUT ...]]
[--hactivations ACTIVATION_FUNCTIONS [ACTIVATION_FUNCTIONS ...]]
[--winitializers KERNEL_INITIALIZERS [KERNEL_INITIALIZERS ...]]
[--binitializers BIAS_INITIALIZERS [BIAS_INITIALIZERS ...]]
[--optimizer OPTIMIZER]
[--loss LOSS]
[--metrics METRICS [METRICS ...]]
[--dumpout DUMPOUT_PATH]
[--logsout LOGSOUT_PATH]
[--modelsnapout MODEL_SNAPSHOTS_PATH]
[--modelsnapfreq MODEL_SNAPSHOTS_FREQ]
Si rimanda alla lettura del file README.md per il dettaglio esaustivo della semantica dei parametri supportati in linea di comando.

Un esempio di uso del programma fx_fit.py

Si supponga di avere a disposizione un dataset di training (ad esempio generato tramite fx_gen.py come mostrato nel paragrafo precedente) e si voglia che il MLP abbia tre layer nascosti rispettivamente con 200, 300 e 200 neuroni e che si voglia usare la funzione di attivazione sigmoid in uscita da tutti e tre i layer; inoltre si vogliano eseguire 1000 epoche di training con un batch size di 200 elementi usando l'algoritmo di ottimizzazione Adamax con un learning rate di 0.02 e la MeanSquaredError quale funzione di loss. Per mettere in azione tutto questo si esegua il seguente comando:

$ python fx_fit.py \
  --trainds mytrain.csv \
  --modelout mymodel \
  --hlayers 200 300 200 \
  --hactivation sigmoid sigmoid sigmoid \
  --epochs 1000 \
  --batch_size 200 \
  --optimizer 'Adamax(learning_rate=0.02)' \
  --loss 'MeanSquaredError()'
al termine del quale la cartella mymodel conterrà il modello del MLP addestrato sul dataset mytrain.csv secondo i parametri passati in linea di comando.

Predizione

Scopo del programma Python fx_predict.py è quello di applicare il modello di MLP generato tramite fx_fit.py al dataset di test (ad esempio generato tramite fx_gen.py come mostrato in un paragrafo precedente); l'esecuzione del programma produce in uscita un file csv con due colonne (con header): la prima colonna contiene i valori della variabile indipendente $x$ presi dal dataset di test e la seconda colonna contiene i valori predetti della variabile dipendente, ovverosia i valori della predizione che approssimano la funzione $f(x)$ corrispondenti al valore di $x$ della prima colonna.
Per ottenere l'usage del programma è sufficiente eseguire il seguente comando:

$ python fx_predict.py --help
e l'output ottenuto è:

usage: fx_predict.py [-h]
--model MODEL_PATH
--ds DATASET_FILENAME
--predictionout PREDICTION_DATA_FILENAME
Si rimanda alla lettura del file README.md per il dettaglio esaustivo della semantica dei parametri supportati in linea di comando.

Un esempio di uso del programma fx_predict.py

Si supponga di avere a disposizione il dataset di test mytest.csv (ad esempio generato tramite fx_gen.py come mostrato in un paragrafo precedente) e il modello di MLP addestrato nella cartella mymodel (generato tramite fx_fit.py come mostrato nell'esempio del paragrafo precedente); si esegua quindi il seguente comando:

$ python fx_predict.py \
  --model mymodel \
  --ds mytest.csv \
  --predictionout myprediction.csv
al termine del quale il file myprediction.csv conterrà l'approssimazione della funzione iniziale.

Visualizzazione del risultato

Scopo del programma Python fx_scatter.py è quello di visualizzare la curva della predizione sovrapposta alla curva del dataset iniziale (che sia quello di test o di training) e questo consente la comparazione visuale delle due curve.
Per ottenere l'usage del programma è sufficiente eseguire il seguente comando:

$ python fx_scatter.py --help
e l'output ottenuto è:

usage: fx_scatter.py [-h]
--ds DATASET_FILENAME
--prediction PREDICTION_DATA_FILENAME
[--title FIGURE_TITLE]
[--savefig SAVE_FIGURE_FILENAME]
Si rimanda alla lettura del file README.md per il dettaglio esaustivo della semantica dei parametri supportati in linea di comando.

Un esempio di uso del programma fx_scatter.py

Avendo a disposizione il dataset di test mytest.csv (ad esempio generato tramite fx_gen.py come mostrato in un paragrafo precedente) e il file csv della predizione (generato tramite fx_predict.py come mostrato nel precedente paragrafo), per generare i due grafici si esegua il seguente comando:

$ python fx_scatter.py \
  --ds mytest.csv \
  --prediction myprediction.csv
che mostra le due curve sovrapposte: in blu quella del dataset di input, in rosso quella della predizione.
Nota: Data la natura stocastica della fase di addestramento, i singoli specifici risultati possono variare. Si consideri di eseguire la fase di addestramento più volte.

Grafico generato dal programma fx_scatter.py che mostra l'approssimazione effettuata dal MLP della funzione $f(x)=\frac{\sin 2x}{e^\frac{x}{5}}$

Diagnostica

La diagnostica consiste in una serie di tecniche per analizzare il comportamento del MLP durante la fase di training, onde per cui il programma fx_fit.py supporta i seguenti argomenti in linea di comando al fine di esportare delle informazioni affinché poi altri programmi possano analizzarle:

[--metrics METRICS [METRICS ...]]
[--dumpout DUMPOUT_PATH]
[--logsout LOGSOUT_PATH]
[--modelsnapout MODEL_SNAPSHOTS_PATH]
[--modelsnapfreq MODEL_SNAPSHOTS_FREQ]
--metrics è la lista di metriche da calcolare sul dataset di training e, se specificato tramite argomento --valds, anche sul dataset di validazione.
--dumpout è l'argomento per indicare la directory dove salvare in formato csv i valori della funzione di loss e delle metriche calcolate.
--logsout è l'argomento per indicare la directory dove salvare i log file per consentire a TensorBoard di analizzarli (sia durante la fase di addestramento che a fine addestramento).
--modelsnapout è l'argomento per indicare la directory dove salvare il modello delle epoche ogni --modelsnapfreq epoche; la prima e ultima epoca sono sempre salvate se questo argomento è specificato.
--modelsnapfreq indica ogni quante epoche salvare il modello nella directory indicata da --modelsnapout.

Il valore delle metriche calcolate e il valore della funzione di loss è disponibile sullo stream di output di fx_fit.py.
Le informazioni esportate da fx_fit.py sono utilizzabili dai seguentt 3 programmi:

  • TensorBoard
  • fx_diag.py
  • fx_video.py
Per eseguire TensorBoard lanciare il comando shell:

$ tensorboard --logdir path/to/logdir
dove la directory passata al parametro --logdir è la stessa cartella generata da fx_fit.py specificata tramite argomento--logsout.
Si veda TensorBoard per dettagli.

Il programma Python fx_diag.py genera una serie di grafici che mostrano la curva della funzione di loss e le curve delle metriche calcolate sul training dataset e anche sul validation dataset se questo è stato passato a fx_fit.py con l'opzione --valds.
Per ottenere l'usage del programma è sufficiente eseguire il seguente comando:

$ python fx_diag.py --help
e l'output ottenuto è:

usage: fx_diag.py [-h] [--help] 
--dump DUMP_PATH
[--savefigdir SAVE_FIGURE_DIRECTORY]
Si rimanda alla lettura del file README.md per il dettaglio esaustivo della semantica dei parametri supportati in linea di comando.

Il programma Python fx_video.py genera una gif animata che mostra la curva di predizione calcolata sul dataset di input con il passare delle epoche.
Per ottenere l'usage del programma è sufficiente eseguire il seguente comando:

$ python fx_video.py --help
e l'output ottenuto è:

usage: fx_videp.py [-h] [--help] 
--modelsnap MODEL_SNAPSHOTS_PATH
--ds DATASET_FILENAME
--savevideo SAVE_GIF_VIDEO
[--fps FPS]
[--width WIDTH]
[--height HEIGHT]
La directory passata all'argomento --modelsnap è la directory generata da fx_fit-py e specificata tramite il parametro --modelsnapout.
Si rimanda alla lettura del file README.md per il dettaglio esaustivo della semantica dei parametri supportati in linea di comando.

Esempi di uso in cascata di questi programmi

Nella cartella one-variable-function-fitting/examples ci sono nove script shell che mostrano l'uso di questi programmi in cascata in varie combinazioni di parametri (architettura del MLP, funzioni di attivazione, algoritmo, funzione di loss, metriche, best model, initializzatori, parametri di prodedura di training, diagnostica). Per mandare in esecuzione i nove esempi eseguire i seguenti comandi:

$ cd one-variable-function-fitting/examples
$ sh example1.sh
$ sh example2.sh
$ sh example3.sh
$ sh example4.sh
$ sh example5.sh
$ sh example6.sh
$ sh example7.sh
$ sh example8.sh
$ sh example9.sh
Nota: Data la natura stocastica di questi esempi (relativamente alla parte di training), i singoli specifici risultati possono variare. Si consideri di eseguire i singoli esempi più volte.

Media

Download del codice completo

Il codice completo è disponibile su GitHub.
Questo materiale è distribuito su licenza MIT; sentiti libero di usare, condividere, "forkare" e adattare tale materiale come credi.
Sentiti anche libero di pubblicare pull-request e bug-report su questo repository di GitHub oppure di contattarmi sui miei canali social disponibili nell'angolo in alto a destra di questa pagina.