Approssimazione di una funzione reale di una variabile con PyTorch

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 e funzione di loss senza scrivere codice ma agendo solamente sulla linea di comando di quattro script Python che implementano separatamente le funzionalità di:

  • Creazione dei dataset
  • Definizione dell'architettura del MLP + Addestramento
  • Predizione
  • Visualizzazione del risultato
Il codice descritto da questo post richiede la versione 3 di Python e utilizza la tecnologia PyTorch; richiede inoltre le librerie NumPy e MatPlotLib.
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 TensorFlow 2 (con Keras); si veda il post Approssimazione di una funzione reale di una variabile con TensorFlow 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 (senza 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 $[-5.0,5.0]$ la seguente funzione $$f(x)=\sqrt{|x|}$$ Tenendo presente che np è l'alias della libreria NumPy, questa si traduce in sintassi lambda body Python così:

np.sqrt(np.abs(x))
Per generare il dataset di training, si esegua quindi il seguente comando:

$ python fx_gen.py \
  --dsout mytrain.csv \
  --fx "np.sqrt(np.abs(x))" \
  --rbegin -5.0 \
  --rend 5.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.sqrt(np.abs(x))" \
  --rbegin -5.0 \
  --rend 5.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
[--epochs EPOCHS]
[--batch_size BATCH_SIZE]
[--hlayers HIDDEN_LAYERS_LAYOUT [HIDDEN_LAYERS_LAYOUT ...]]
[--hactivations ACTIVATION_FUNCTIONS [ACTIVATION_FUNCTIONS ...]]
[--optimizer OPTIMIZER]
[--loss LOSS]
[--device DEVICE]
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 in uscita dai tre layer rispettivamente Tanh, ReLU e Tanh; inoltre si vogliano eseguire 500 epoche di training con un batch size di 100 elementi usando l'algoritmo di ottimizzazione RMSprop con un learning rate di 0.01 e la SmoothL1Loss quale funzione di loss. Per mettere in azione tutto questo si esegua il seguente comando:

$ python fx_fit.py \
  --trainds mytrain.csv \
  --modelout mymodel.pth \
  --hlayers 200 300 200 \
  --hactivations 'Tanh()' 'ReLU()' 'Tanh()' \
  --loss 'SmoothL1Loss()' \
  --optimizer 'RMSprop(learning_rate=0.01)' \
  --epochs 500 \
  --batch_size 100 \
  --
al termine del quale il file mymodel.pth 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 (senza 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
[--device DEVICE]
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 nel file mymodel.pth (generato tramite fx_fit.py come mostrato nell'esempio del paragrafo precedente); si esegua quindi il seguente comando:

$ python fx_predict.py \
  --model mymodel.pth \
  --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_plot.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_plot.py --help
e l'output ottenuto è:

usage: fx_plot.py [-h]
--ds DATASET_FILENAME
--prediction PREDICTION_DATA_FILENAME
[--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_plot.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_plot.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_plot.py che mostra l'approssimazione effettuata dal MLP della funzione $f(x)=\sqrt{|x|}$

Esempi di uso in cascata dei quattro programmi

Nella cartella one-variable-function-fitting/examples ci sono nove script shell che mostrano l'uso dei quattro programmi in cascata in varie combinazioni di parametri (architettura del MLP, funzioni di attivazione, algoritmo, funzione di loss, parametri di prodedura di training). 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.

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.