Dalla risoluzione di equazioni all'apprendimento profondo: un tutorial Python di TensorFlow
Pubblicato: 2022-03-11Ultimamente ci sono stati alcuni notevoli sviluppi nel mondo dell'intelligenza artificiale, dai progressi molto pubblicizzati con le auto a guida autonoma alle macchine che ora compongono imitazioni di Chopin o semplicemente sono davvero brave nei videogiochi.
Al centro di questi progressi ci sono una serie di strumenti disponibili per aiutare a derivare il deep learning e altri modelli di machine learning, con Torch, Caffe e Theano tra i primi. Tuttavia, da quando Google Brain è diventato open source nel novembre 2015 con il proprio framework, TensorFlow, abbiamo visto la popolarità di questa libreria software salire alle stelle per essere il framework di deep learning più popolare.
Perché è successo? I motivi includono la ricchezza di supporto e documentazione disponibile, la sua prontezza di produzione, la facilità di distribuzione dei calcoli su una gamma di dispositivi e un eccellente strumento di visualizzazione: TensorBoard.
In definitiva, TensorFlow riesce a combinare un insieme completo e flessibile di caratteristiche tecniche con una grande facilità d'uso.
In questo articolo, acquisirai una comprensione della meccanica di questo strumento utilizzandolo per risolvere un problema numerico generale, molto al di fuori di ciò che normalmente implica l'apprendimento automatico, prima di introdurne gli usi nell'apprendimento profondo con una semplice implementazione di rete neurale.
Prima di iniziare
Si presuppone una conoscenza di base dei metodi di machine learning. Se hai bisogno di recuperare, dai un'occhiata a questo post molto utile.
Poiché dimostreremo l'API Python, anche la comprensione di Numpy è vantaggiosa.
Per configurare TensorFlow, seguire le istruzioni che si trovano qui.
Se stai usando Windows, va notato che, al momento della scrittura, devi usare Python 3.4+, non 2.7.
Quindi, quando sei pronto, dovresti essere in grado di importare la libreria con:
import tensorflow as tf
Passaggio 1 di 2 a una soluzione TensorFlow: creare un grafico
La costruzione dei programmi TensorFlow consiste generalmente in due passaggi principali, il primo dei quali è costruire un grafico computazionale, che descriverà i calcoli che si desidera eseguire, ma non li eseguirà o conterrà alcun valore.
Come con qualsiasi grafo, abbiamo nodi e archi. Gli archi rappresentano tensori, un tensore che rappresenta un array n-dimensionale. Ad esempio, un tensore con dimensione (o rango in TensorFlow parla) 0 è uno scalare, rango 1 un vettore, rango 2 una matrice e così via.
I nodi rappresentano operazioni che producono un tensore di output, prendendo i tensori come input se necessario. Tali operazioni includono addizioni ( tf.add
), moltiplicazioni di matrici ( tf.matmul
) e anche la creazione di costanti ( tf.constant
).
Quindi, combiniamo alcuni di questi per il nostro primo grafico.
a = tf.constant([2.5, 2]) b = tf.constant([3, 6], dtype=tf.float32) total = tf.add(a, b)
Qui abbiamo creato tre operazioni, due delle quali per creare array 1-d costanti.
I tipi di dati vengono dedotti dall'argomento values passato, oppure puoi denotarli con l'argomento dtype
. Se non l'avessi fatto per b
, sarebbe stato dedotto un int32
e un errore generato come tf.add
avrebbe cercato di definire un'aggiunta su due tipi diversi.
Passaggio 2 di 2 a una soluzione TensorFlow: eseguire le operazioni
Il grafico è definito, ma per eseguire effettivamente i calcoli su di esso (o su qualsiasi parte di esso) dobbiamo impostare una sessione di TensorFlow.
sess = tf.Session()
In alternativa, se stiamo eseguendo una sessione in una shell interattiva, come IPython, utilizziamo:
sess = tf.InteractiveSession()
Il metodo run
sull'oggetto sessione è un modo per valutare un Tensor.
Pertanto, per valutare il calcolo dell'addizione sopra definito, si passa 'total', il Tensor da recuperare, che rappresenta l'output del tf.add
op.
print(sess.run(total)) # [ 5.5 8. ]
A questo punto introduciamo la classe Variable di TensorFlow. Mentre le costanti sono una parte fissa della definizione del grafico, le variabili possono essere aggiornate. Il costruttore di classe richiede un valore iniziale, ma anche in questo caso, le variabili necessitano di un'operazione per inizializzarle in modo esplicito prima che venga eseguita qualsiasi altra operazione su di esse.
Le variabili mantengono lo stato del grafico in una particolare sessione, quindi dovremmo osservare cosa succede con più sessioni utilizzando lo stesso grafico per comprendere meglio le variabili.
# Create a variable with an initial value of 1 some_var = tf.Variable(1) # Create op to run variable initializers init_op = tf.global_variables_initializer() # Create an op to replace the value held by some_var to 3 assign_op = some_var.assign(3) # Set up two instances of a session sess1 = tf.Session() sess2 = tf.Session() # Initialize variables in both sessions sess1.run(init_op) sess2.run(init_op) print(sess1.run(some_var)) # Outputs 1 # Change some_var in session1 sess1.run(assign_op) print(sess1.run(some_var)) # Outputs 3 print(sess2.run(some_var)) # Outputs 1 # Close sessions sess1.close() sess2.close()
Abbiamo impostato il grafico e due sessioni.
Dopo aver eseguito l'inizializzazione su entrambe le sessioni (se non lo eseguiamo e quindi valutiamo la variabile abbiamo riscontrato un errore) eseguiamo l'operazione di assegnazione solo su una sessione. Come si può vedere, il valore della variabile persiste, ma non tra le sessioni.
Alimentare il grafico per affrontare problemi numerici
Un altro concetto importante di TensorFlow è il segnaposto. Mentre le variabili mantengono lo stato, i segnaposto vengono utilizzati per definire quali input può aspettarsi il grafico e il loro tipo di dati (e, facoltativamente, la loro forma). Quindi possiamo inserire i dati nel grafico tramite questi segnaposto quando eseguiamo il calcolo.
Il grafico TensorFlow sta cominciando ad assomigliare alle reti neurali che vorremmo eventualmente addestrare, ma prima di ciò, utilizziamo i concetti per risolvere un problema numerico comune dal mondo finanziario.
Supponiamo di voler trovare y
in un'equazione come questa:
per un dato v
(con C
e P
costanti).
Questa è una formula per calcolare il rendimento alla scadenza ( y
) di un'obbligazione con valore di mercato v
, capitale P
e cedola C
pagata semestralmente ma con flussi di cassa scontati con composizione continua.
Fondamentalmente dobbiamo risolvere un'equazione come questa con tentativi ed errori e sceglieremo il metodo di bisezione per azzerare il nostro valore finale per y
.
Per prima cosa, modelleremo questo problema come un grafico TensorFlow.
C
e P
sono costanti fisse e fanno parte della definizione del nostro grafico. Desideriamo avere un processo che raffina i limiti inferiore e superiore di y
. Pertanto, questi limiti (indicati a
b
) sono buoni candidati per variabili che devono essere modificate dopo ogni ipotesi di y
(considerata il punto medio a
b
).
# Specify the values our constant ops will output C = tf.constant(5.0) P = tf.constant(100.0) # We specify the initial values that our lower and upper bounds will be when initialised. # Obviously the ultimate success of this algorithm depends on decent start points a = tf.Variable(-10.0) b = tf.Variable(10.0) # We expect a floating number to be inserted into the graph v_target = tf.placeholder("float") # Remember the following operations are definitions, # none are carried out until an operation is evaluated in a session! y = (a+b)/2 v_guess = C*tf.exp(-0.5*y) + C*tf.exp(-y) + C*tf.exp(-1.5*y) + (C + P)*tf.exp(-2*y) # Operations to set temporary values (a_ and b_) intended to be the next values of a and b. # eg if the guess results in av greater than the target v, # we will set a_ to be the current value of y a_ = tf.where(v_guess > v_target, y, a) b_ = tf.where(v_guess < v_target, y, b) # The last stage of our graph is to assign the two temporary vals to our variables step = tf.group( a.assign(a_), b.assign(b_) )
Quindi ora abbiamo un elenco di operazioni e variabili, ognuna delle quali può essere valutata rispetto a una particolare sessione. Alcune di queste operazioni si basano su altre operazioni da eseguire, quindi l'esecuzione, ad esempio, v_guess
avvierà una reazione a catena per avere altri tensori, come C
e P
, da valutare per primi.

Alcune di queste operazioni si basano su un segnaposto per il quale è necessario specificare un valore, quindi come inseriamo effettivamente quel valore?
Questo viene fatto tramite l'argomento feed_dict
nella funzione di run
stessa.
Se vogliamo valutare a_
, inseriamo il valore per il nostro segnaposto v_target
, in questo modo:
sess.run(a_, feed_dict={v_target: 100})
dandoci 0.0.
Collega un v_target
di 130 e otteniamo -10.0.
È la nostra operazione "passo" che in realtà richiede che tutte le altre operazioni vengano eseguite come prerequisito e in effetti esegue l'intero grafico. È anche un'operazione che cambia effettivamente lo stato attuale della nostra sessione. Pertanto, più eseguiamo il passaggio, più b
a
il valore effettivo di y
.
Quindi, diciamo che il nostro valore per v
nella nostra equazione è uguale a 95. Impostiamo una sessione ed eseguiamo il nostro grafico su di essa 100 volte.
# Set up a session and initialize variables sess = tf.Session() tf.global_variables_initializer().run() # Run the step operation (and therefore whole graph) 100 times for i in range (100): sess.run(step, feed_dict={v_target:95})
Se valutiamo ora il tensore y
, otteniamo qualcosa che assomiglia a una risposta desiderabile
print(sess.run(y)) # 0.125163
Reti neurali
Ora che abbiamo una comprensione dei meccanismi di TensorFlow, possiamo combinarli con alcune operazioni di machine learning aggiuntive integrate in TensorFlow per addestrare una semplice rete neurale.
Qui, vorremmo classificare i punti dati su un sistema di coordinate 2d a seconda che rientrino in una particolare regione, un cerchio di raggio 0,5 centrato all'origine.
Naturalmente, questo può essere concretamente verificato semplicemente verificando un dato punto (a,b)
se a^2 + b^2 < 0.5
, ma ai fini di questo esperimento di machine learning, vorremmo invece passare a set di allenamento: una serie di punti casuali e se rientrano nella regione prevista. Ecco un modo per crearlo:
import numpy as np NO_OF_RANDOM_POINTS = 100 CIRCLE_RADIUS = 0.5 random_spots = np.random.rand(NO_OF_RANDOM_POINTS, 2) * 2 - 1 is_inside_circle = (np.power(random_spots[:,0],2) + np.power(random_spots[:,1],2) < CIRCLE_RADIUS).astype(int)
Realizzeremo una rete neurale con le seguenti caratteristiche:
- Consiste in uno strato di input con due nodi, in cui alimentiamo la nostra serie di vettori bidimensionali contenuti all'interno di "random_spots". Questo sarà rappresentato da un segnaposto in attesa dei dati di addestramento.
- Il livello di output avrà anche due nodi, quindi dobbiamo inserire la nostra serie di etichette di addestramento ("is_inside_circle") in un segnaposto per uno scalare, quindi convertire quei valori in un vettore bidimensionale a caldo.
- Avremo uno strato nascosto composto da tre nodi, quindi dovremo utilizzare variabili per le nostre matrici di pesi e vettori di bias, poiché questi sono i valori che devono essere perfezionati durante l'esecuzione dell'allenamento.
INPUT_LAYER_SIZE = 2 HIDDEN_LAYER_SIZE = 3 OUTPUT_LAYER_SIZE = 2 # Starting values for weights and biases are drawn randomly and uniformly from [-1, 1] # For example W1 is a matrix of shape 2x3 W1 = tf.Variable(tf.random_uniform([INPUT_LAYER_SIZE, HIDDEN_LAYER_SIZE], -1, 1)) b1 = tf.Variable(tf.random_uniform([HIDDEN_LAYER_SIZE], -1, 1)) W2 = tf.Variable(tf.random_uniform([HIDDEN_LAYER_SIZE, OUTPUT_LAYER_SIZE], -1, 1)) b2 = tf.Variable(tf.random_uniform([OUTPUT_LAYER_SIZE], -1, 1)) # Specifying that the placeholder X can expect a matrix of 2 columns (but any number of rows) # representing random spots X = tf.placeholder(tf.float32, [None, INPUT_LAYER_SIZE]) # Placeholder Y can expect integers representing whether corresponding point is in the circle # or not (no shape specified) Y = tf.placeholder(tf.uint8) # An op to convert to a one hot vector onehot_output = tf.one_hot(Y, OUTPUT_LAYER_SIZE)
Per completare la definizione del nostro grafico, definiamo alcune operazioni che ci aiuteranno ad addestrare le variabili per raggiungere un classificatore migliore. Questi includono i calcoli della matrice, le funzioni di attivazione e l'ottimizzatore.
LEARNING_RATE = 0.01 # Op to perform matrix calculation X*W1 + b1 hidden_layer = tf.add(tf.matmul(X, W1), b1) # Use sigmoid activation function on the outcome activated_hidden_layer = tf.sigmoid(hidden_layer) # Apply next weights and bias (W2, b2) to hidden layer and then apply softmax function # to get our output layer (each vector adding up to 1) output_layer = tf.nn.softmax(tf.add(tf.matmul(activated_hidden_layer, W2), b2)) # Calculate cross entropy for our loss function loss = -tf.reduce_sum(onehot_output * tf.log(output_layer)) # Use gradient descent optimizer at specified learning rate to minimize value given by loss tensor train_step = tf.train.GradientDescentOptimizer(LEARNING_RATE).minimize(loss)
Dopo aver impostato il nostro grafico, è il momento di impostare una sessione ed eseguire il "train_step" (che esegue anche eventuali operazioni preliminari). Alcune di queste operazioni utilizzano segnaposto, quindi è necessario fornire valori per tali operazioni. Questa fase di addestramento rappresenta un'epoca nel nostro algoritmo di apprendimento e, come tale, viene ripetuta sul numero di epoche che desideriamo eseguire. Possiamo eseguire altre parti del grafico, come il tensore di "perdita" a scopo informativo.
EPOCH_COUNT = 1000 sess = tf.Session() tf.global_variables_initializer().run() for i in range(EPOCH_COUNT): if i%100 == 0: print('Loss after %d runs: %f' % (i, sess.run(loss, feed_dict={X: random_spots, Y: is_inside_circle}))) sess.run(train_step, feed_dict={X: random_spots, Y: is_inside_circle}) print('Final loss after %d runs: %f' % (i, sess.run(loss, feed_dict={X: random_spots, Y: is_inside_circle})))
Una volta che abbiamo addestrato l'algoritmo, possiamo alimentare un punto e ottenere l'output della rete neurale in questo modo:
sess.run(output_layer, feed_dict={X: [[1, 1]]}) # Hopefully something close to [1, 0] sess.run(output_layer, feed_dict={X: [[0, 0]]}) # Hopefully something close to [0, 1]
Possiamo classificare il punto come fuori dal cerchio se il primo membro del vettore di output è maggiore di 0,5, altrimenti dentro.
Eseguendo il tensore output_layer
per molti punti, possiamo avere un'idea di come lo studente immagina la regione contenente i punti classificati positivamente. Vale la pena giocare con le dimensioni del set di allenamento, il tasso di apprendimento e altri parametri per vedere quanto possiamo avvicinarci al cerchio che intendevamo.
Tasso di apprendimento: 0,01
Epoche: 1000
Tasso di apprendimento: 0,01
Epoche: 1000
Tasso di apprendimento: 0,01
Epoche: 10000
Tasso di apprendimento: 0,001
Epoche: 10000
Incartare
Questa è una buona lezione sul fatto che un set di allenamento o un importo di epoca aumentati non sono una garanzia per un buon studente: il tasso di apprendimento dovrebbe essere regolato in modo appropriato.
Si spera che queste dimostrazioni ti abbiano fornito una buona visione dei principi fondamentali di TensorFlow e forniscano una solida base in cui implementare tecniche più complesse.
Non abbiamo trattato concetti come Tensorboard o addestrato i nostri modelli su GPU, ma questi sono ben trattati nella documentazione di TensorFlow. Nella documentazione è possibile trovare una serie di ricette che possono aiutarti ad aggiornarti con l'affrontare entusiasmanti attività di deep learning utilizzando questo potente framework!