Le molte applicazioni della discesa graduale in TensorFlow
Pubblicato: 2022-03-11TensorFlow di Google è uno degli strumenti principali per la formazione e l'implementazione di modelli di deep learning. È in grado di ottimizzare architetture di rete neurale estremamente complesse con centinaia di milioni di parametri e viene fornito con un'ampia gamma di strumenti per l'accelerazione hardware, la formazione distribuita e i flussi di lavoro di produzione. Queste potenti funzionalità possono farlo sembrare intimidatorio e non necessario al di fuori del dominio del deep learning.
Ma TensorFlow può essere sia accessibile che utilizzabile per problemi più semplici non direttamente correlati all'addestramento di modelli di deep learning. In sostanza, TensorFlow è solo una libreria ottimizzata per le operazioni sui tensori (vettori, matrici, ecc.) e le operazioni di calcolo utilizzate per eseguire la discesa del gradiente su sequenze arbitrarie di calcoli. I data scientist esperti riconosceranno la "discendenza del gradiente" come uno strumento fondamentale per la matematica computazionale, ma di solito richiede l'implementazione di codice ed equazioni specifiche dell'applicazione. Come vedremo, è qui che entra in gioco la moderna architettura di "differenziazione automatica" di TensorFlow.
Casi d'uso di TensorFlow
- Esempio 1: regressione lineare con discesa gradiente in TensorFlow 2.0
- Cos'è la discesa graduale?
- Esempio 2: vettori unitari di massima diffusione
- Esempio 3: generazione di input IA contraddittori
- Considerazioni finali: ottimizzazione della discesa del gradiente
- Discesa graduale in TensorFlow: dalla ricerca dei minimi all'attacco ai sistemi di intelligenza artificiale
Esempio 1: regressione lineare con discesa gradiente in TensorFlow 2.0
Esempio 1 taccuino
Prima di arrivare al codice TensorFlow, è importante conoscere la discesa del gradiente e la regressione lineare.
Cos'è la discesa graduale?
In parole povere, è una tecnica numerica per trovare gli input di un sistema di equazioni che ne minimizzino l'output. Nel contesto dell'apprendimento automatico, quel sistema di equazioni è il nostro modello , gli input sono i parametri sconosciuti del modello e l'output è una funzione di perdita da minimizzare, che rappresenta l'errore che c'è tra il modello e i nostri dati. Per alcuni problemi (come la regressione lineare), ci sono equazioni per calcolare direttamente i parametri che minimizzano il nostro errore, ma per la maggior parte delle applicazioni pratiche, abbiamo bisogno di tecniche numeriche come la discesa del gradiente per arrivare a una soluzione soddisfacente.
Il punto più importante di questo articolo è che la discesa del gradiente di solito richiede la stesura delle nostre equazioni e l'uso del calcolo per derivare la relazione tra la nostra funzione di perdita e i nostri parametri. Con TensorFlow (e qualsiasi moderno strumento di differenziazione automatica), il calcolo viene gestito per noi, quindi possiamo concentrarci sulla progettazione della soluzione e non dover perdere tempo per la sua implementazione.
Ecco come appare su un semplice problema di regressione lineare. Abbiamo un campione delle altezze (h) e dei pesi (w) di 150 maschi adulti e iniziamo con un'ipotesi imperfetta della pendenza e della deviazione standard di questa linea. Dopo circa 15 iterazioni di discesa del gradiente, arriviamo a una soluzione quasi ottimale.
Vediamo come abbiamo prodotto la soluzione di cui sopra utilizzando TensorFlow 2.0.
Per la regressione lineare, diciamo che i pesi possono essere previsti da un'equazione lineare delle altezze.
Vogliamo trovare i parametri α e β (pendenza e intercetta) che minimizzino l'errore quadratico medio (perdita) tra le previsioni ei valori veri. Quindi la nostra funzione di perdita (in questo caso, "errore quadratico medio" o MSE) è simile a questa:
Possiamo vedere come l'errore quadratico medio cerca un paio di linee imperfette, e quindi con la soluzione esatta (α=6.04, β=-230.5).
Mettiamo in pratica questa idea con TensorFlow. La prima cosa da fare è codificare la funzione di perdita usando i tensori e le funzioni tf.*
.
def calc_mean_sq_error(heights, weights, slope, intercept): predicted_wgts = slope * heights + intercept errors = predicted_wgts - weights mse = tf.reduce_mean(errors**2) return mse
Questo sembra abbastanza semplice. Tutti gli operatori algebrici standard sono sovraccaricati per i tensori, quindi dobbiamo solo assicurarci che le variabili che stiamo ottimizzando siano tensori e usiamo i metodi tf.*
per qualsiasi altra cosa.
Quindi, tutto ciò che dobbiamo fare è inserirlo in un ciclo di discesa a gradiente:
def run_gradient_descent(heights, weights, init_slope, init_icept, learning_rate): # Any values to be part of gradient calcs need to be vars/tensors tf_slope = tf.Variable(init_slope, dtype='float32') tf_icept = tf.Variable(init_icept, dtype='float32') # Hardcoding 25 iterations of gradient descent for i in range(25): # Do all calculations under a "GradientTape" which tracks all gradients with tf.GradientTape() as tape: tape.watch((tf_slope, tf_icept)) # This is the same mean-squared-error calculation as before predictions = tf_slope * heights + tf_icept errors = predictions - weights loss = tf.reduce_mean(errors**2) # Auto-diff magic! Calcs gradients between loss calc and params dloss_dparams = tape.gradient(loss, [tf_slope, tf_icept]) # Gradients point towards +loss, so subtract to "descend" tf_slope = tf_slope - learning_rate * dloss_dparams[0] tf_icept = tf_icept - learning_rate * dloss_dparams[1]
Prendiamoci un momento per apprezzare quanto sia pulito. La discesa del gradiente richiede il calcolo delle derivate della funzione di perdita rispetto a tutte le variabili che stiamo cercando di ottimizzare. Il calcolo dovrebbe essere coinvolto, ma in realtà non l'abbiamo fatto. La magia sta nel fatto che:
- TensorFlow crea un grafico di calcolo di ogni calcolo eseguito in
tf.GradientTape()
. - TensorFlow sa come calcolare le derivate (gradienti) di ogni operazione, in modo da poter determinare in che modo qualsiasi variabile nel grafico di calcolo influisce su qualsiasi altra variabile.
Come appare il processo da diversi punti di partenza?
La discesa del gradiente si avvicina notevolmente all'MSE ottimale, ma in realtà converge a una pendenza e un'intercetta sostanzialmente diverse rispetto all'ottimo in entrambi gli esempi. In alcuni casi, si tratta semplicemente di una discesa del gradiente che converge al minimo locale, il che è una sfida intrinseca agli algoritmi di discesa del gradiente. Ma è dimostrato che la regressione lineare ha solo un minimo globale. Allora come siamo finiti sulla pista sbagliata e abbiamo intercettato?
In questo caso, il problema è che abbiamo semplificato eccessivamente il codice per motivi di dimostrazione. Non abbiamo normalizzato i nostri dati e il parametro di pendenza ha una caratteristica diversa dal parametro di intercettazione. Piccoli cambiamenti di pendenza possono produrre enormi cambiamenti nella perdita, mentre piccoli cambiamenti nell'intercettazione hanno scarso effetto. Questa enorme differenza di scala dei parametri allenabili porta la pendenza a dominare i calcoli del gradiente, con il parametro di intercetta quasi ignorato.
Quindi la discesa in pendenza trova effettivamente la pendenza migliore molto vicino all'ipotesi di intercettazione iniziale. E poiché l'errore è così vicino all'ottimo, i gradienti attorno ad esso sono minuscoli, quindi ogni iterazione successiva si sposta solo di un pochino. Normalizzare prima i nostri dati avrebbe migliorato notevolmente questo fenomeno, ma non lo avrebbe eliminato.
Questo era un esempio relativamente semplice, ma vedremo nelle prossime sezioni che questa capacità di "auto-differenziazione" può gestire alcune cose piuttosto complesse.
Esempio 2: vettori unitari di massima diffusione
Esempio 2 Taccuino
Questo prossimo esempio si basa su un divertente esercizio di deep learning in un corso di deep learning che ho seguito l'anno scorso.
L'essenza del problema è che abbiamo un "codificatore automatico variazionale" (VAE) in grado di produrre facce realistiche da un insieme di 32 numeri normalmente distribuiti. Per l'identificazione dei sospetti, vogliamo utilizzare il VAE per produrre un insieme diversificato di volti (teorici) tra i quali un testimone può scegliere, quindi restringere la ricerca producendo più volti simili a quelli scelti. Per questo esercizio è stato suggerito di randomizzare l'insieme iniziale di vettori, ma volevo trovare uno stato iniziale ottimale.
Possiamo formulare il problema in questo modo: dato uno spazio a 32 dimensioni, trova un insieme di X vettori unitari che sono distanziati al massimo. In due dimensioni, questo è facile da calcolare esattamente. Ma per tre dimensioni (o 32 dimensioni!), non c'è una risposta semplice. Tuttavia, se possiamo definire una corretta funzione di perdita che è al suo minimo quando abbiamo raggiunto il nostro stato target, forse la discesa del gradiente può aiutarci ad arrivarci.
Inizieremo con un insieme randomizzato di 20 vettori come mostrato sopra e sperimenteremo tre diverse funzioni di perdita, ognuna con complessità crescente, per dimostrare le capacità di TensorFlow.
Definiamo prima il nostro ciclo di formazione. Metteremo tutta la logica TensorFlow sotto il metodo self.calc_loss()
, quindi potremo semplicemente sovrascrivere quel metodo per ogni tecnica, riciclando questo ciclo.
# Define the framework for trying different loss functions # Base class implements loop, sub classes override self.calc_loss() class VectorSpreadAlgorithm: # ... def calc_loss(self, tensor2d): raise NotImplementedError("Define this in your derived class") def one_iter(self, i, learning_rate): # self.vecs is an 20x2 tensor, representing twenty 2D vectors tfvecs = tf.convert_to_tensor(self.vecs, dtype=tf.float32) with tf.GradientTape() as tape: tape.watch(tfvecs) loss = self.calc_loss(tfvecs) # Here's the magic again. Derivative of spread with respect to # input vectors gradients = tape.gradient(loss, tfvecs) self.vecs = self.vecs - learning_rate * gradients
La prima tecnica da provare è la più semplice. Definiamo una metrica di diffusione che è l'angolo dei vettori più vicini tra loro. Vogliamo massimizzare la diffusione, ma è convenzionale farne un problema di minimizzazione. Quindi prendiamo semplicemente il negativo della metrica dello spread:
class VectorSpread_Maximize_Min_Angle(VectorSpreadAlgorithm): def calc_loss(self, tensor2d): angle_pairs = tf.acos(tensor2d @ tf.transpose(tensor2d)) disable_diag = tf.eye(tensor2d.numpy().shape[0]) * 2 * np.pi spread_metric = tf.reduce_min(angle_pairs + disable_diag) # Convention is to return a quantity to be minimized, but we want # to maximize spread. So return negative spread return -spread_metric
Alcune magie di Matplotlib produrranno una visualizzazione.
Questo è goffo (letteralmente!) ma funziona. Solo due dei 20 vettori vengono aggiornati alla volta, aumentando lo spazio tra loro fino a quando non sono più i più vicini, quindi passando ad aumentare l'angolo tra i nuovi due vettori più vicini. La cosa importante da notare è che funziona . Vediamo che TensorFlow è stato in grado di passare i gradienti attraverso il metodo tf.reduce_min()
e il metodo tf.acos()
per fare la cosa giusta.
Proviamo qualcosa di un po' più elaborato. Sappiamo che nella soluzione ottimale, tutti i vettori dovrebbero avere lo stesso angolo rispetto ai loro vicini più prossimi. Quindi aggiungiamo "varianza degli angoli minimi" alla funzione di perdita.
class VectorSpread_MaxMinAngle_w_Variance(VectorSpreadAlgorithm): def spread_metric(self, tensor2d): """ Assumes all rows already normalized """ angle_pairs = tf.acos(tensor2d @ tf.transpose(tensor2d)) disable_diag = tf.eye(tensor2d.numpy().shape[0]) * 2 * np.pi all_mins = tf.reduce_min(angle_pairs + disable_diag, axis=1) # Same calculation as before: find the min-min angle min_min = tf.reduce_min(all_mins) # But now also calculate the variance of the min angles vector avg_min = tf.reduce_mean(all_mins) var_min = tf.reduce_sum(tf.square(all_mins - avg_min)) # Our spread metric now includes a term to minimize variance spread_metric = min_min - 0.4 * var_min # As before, want negative spread to keep it a minimization problem return -spread_metric
Quel vettore solitario verso nord ora si unisce rapidamente ai suoi pari, perché l'angolo rispetto al suo vicino più vicino è enorme e aumenta il termine di varianza che ora viene ridotto al minimo. Ma alla fine è ancora guidato dall'angolo minimo globale che rimane lento ad aumentare. Le idee che devo migliorare generalmente funzionano in questo caso 2D, ma non in dimensioni superiori.
Ma concentrarsi troppo sulla qualità di questo tentativo matematico non ha senso. Guarda quante operazioni sui tensori sono coinvolte nei calcoli della media e della varianza e come TensorFlow tiene traccia e differenzia con successo ogni calcolo per ogni componente nella matrice di input. E non abbiamo dovuto fare alcun calcolo manuale. Abbiamo appena messo insieme alcuni semplici calcoli e TensorFlow ha fatto il calcolo per noi.
Infine, proviamo un'altra cosa: una soluzione basata sulla forza. Immagina che ogni vettore sia un piccolo pianeta legato a un punto centrale. Ogni pianeta emette una forza che lo respinge dagli altri pianeti. Se dovessimo eseguire una simulazione fisica di questo modello, dovremmo arrivare alla soluzione desiderata.
La mia ipotesi è che anche la discesa del gradiente dovrebbe funzionare. Alla soluzione ottimale, la forza tangente su ogni pianeta da ogni altro pianeta dovrebbe annullarsi a una forza zero netta (se non fosse zero, i pianeti si muoverebbero). Quindi calcoliamo l'intensità della forza su ogni vettore e usiamo la discesa del gradiente per spingerlo verso zero.
Innanzitutto, dobbiamo definire il metodo che calcola la forza usando i metodi tf.*
:
class VectorSpread_Force(VectorSpreadAlgorithm): def force_a_onto_b(self, vec_a, vec_b): # Calc force assuming vec_b is constrained to the unit sphere diff = vec_b - vec_a norm = tf.sqrt(tf.reduce_sum(diff**2)) unit_force_dir = diff / norm force_magnitude = 1 / norm**2 force_vec = unit_force_dir * force_magnitude # Project force onto this vec, calculate how much is radial b_dot_f = tf.tensordot(vec_b, force_vec, axes=1) b_dot_b = tf.tensordot(vec_b, vec_b, axes=1) radial_component = (b_dot_f / b_dot_b) * vec_b # Subtract radial component and return result return force_vec - radial_component
Quindi, definiamo la nostra funzione di perdita usando la funzione di forza sopra. Accumuliamo la forza netta su ciascun vettore e calcoliamo la sua magnitudine. Alla nostra soluzione ottimale, tutte le forze dovrebbero annullarsi e dovremmo avere una forza zero.

def calc_loss(self, tensor2d): n_vec = tensor2d.numpy().shape[0] all_force_list = [] for this_idx in range(n_vec): # Accumulate force of all other vecs onto this one this_force_list = [] for other_idx in range(n_vec): if this_idx == other_idx: continue this_vec = tensor2d[this_idx, :] other_vec = tensor2d[other_idx, :] tangent_force_vec = self.force_a_onto_b(other_vec, this_vec) this_force_list.append(tangent_force_vec) # Use list of all N-dimensional force vecs. Stack and sum. sum_tangent_forces = tf.reduce_sum(tf.stack(this_force_list)) this_force_mag = tf.sqrt(tf.reduce_sum(sum_tangent_forces**2)) # Accumulate all magnitudes, should all be zero at optimal solution all_force_list.append(this_force_mag) # We want to minimize total force sum, so simply stack, sum, return return tf.reduce_sum(tf.stack(all_force_list))
Non solo la soluzione funziona magnificamente (a parte un po' di caos nei primi fotogrammi), ma il vero merito va a TensorFlow. Questa soluzione prevedeva più cicli for
, un'istruzione if
e un'enorme rete di calcoli e TensorFlow ha tracciato con successo i gradienti attraverso tutto per noi.
Esempio 3: generazione di input IA contraddittori
Esempio 3 Taccuino
A questo punto, i lettori potrebbero pensare: "Ehi! Questo post non doveva riguardare il deep learning!" Ma tecnicamente, l'introduzione si riferisce all'andare oltre la " formazione di modelli di deep learning". In questo caso, non ci stiamo allenando , ma stiamo invece sfruttando alcune proprietà matematiche di una rete neurale profonda pre-addestrata per ingannarla e farci ottenere risultati sbagliati. Questo si è rivelato molto più semplice ed efficace di quanto immaginato. E tutto ciò che è servito è stato un altro breve blob di codice TensorFlow 2.0.
Iniziamo trovando un classificatore di immagini da attaccare. Utilizzeremo una delle migliori soluzioni per la competizione Kaggle Dogs vs. Cats; in particolare, la soluzione presentata da Kaggler “uysimty”. Tutto merito loro per aver fornito un modello gatto contro cane efficace e per aver fornito un'ottima documentazione. Questo è un modello potente composto da 13 milioni di parametri su 18 livelli di rete neurale. (I lettori sono invitati a leggere di più a riguardo nel taccuino corrispondente.)
Si noti che l'obiettivo qui non è evidenziare alcuna carenza in questa particolare rete, ma mostrare come qualsiasi rete neurale standard con un numero elevato di input sia vulnerabile.
Con un po' di aggiustamento, sono riuscito a capire come caricare il modello e pre-elaborare le immagini per essere classificate da esso.
Questo sembra un classificatore davvero solido! Tutte le classificazioni dei campioni sono corrette e con una confidenza superiore al 95%. Attacchiamolo!
Vogliamo produrre un'immagine che sia ovviamente un gatto, ma fare in modo che il classificatore decida che si tratta di un cane con grande sicurezza. Come possiamo farlo?
Iniziamo con un'immagine di gatto che classifica correttamente, quindi scopriamo come piccole modifiche in ciascun canale di colore (valori 0-255) di un dato pixel di input influiscano sull'output del classificatore finale. La modifica di un pixel probabilmente non farà molto, ma forse le modifiche cumulative di tutti i valori 128x128x3 = 49.152 pixel raggiungeranno il nostro obiettivo.
Come facciamo a sapere in che modo spingere ogni pixel? Durante il normale addestramento della rete neurale, cerchiamo di ridurre al minimo la perdita tra l'etichetta di destinazione e l'etichetta prevista, utilizzando la discesa del gradiente in TensorFlow per aggiornare contemporaneamente tutti i 13 milioni di parametri liberi. In questo caso, invece, lasceremo fissi i 13 milioni di parametri e regoleremo i valori dei pixel dell'input stesso.
Qual è la nostra funzione di perdita? Bene, è quanto l'immagine assomiglia a un gatto! Se calcoliamo la derivata del valore cat rispetto a ciascun pixel di input, sappiamo in che modo spingerli per ridurre al minimo la probabilità di classificazione del gatto.
def adversarial_modify(victim_img, to_dog=False, to_cat=False): # We only need four gradient descent steps for i in range(4): tf_victim_img = tf.convert_to_tensor(victim_img, dtype='float32') with tf.GradientTape() as tape: tape.watch(tf_victim_img) # Run the image through the model model_output = model(tf_victim_img) # Minimize cat confidence and maximize dog confidence loss = (model_output[0] - model_output[1]) dloss_dimg = tape.gradient(loss, tf_victim_img) # Ignore gradient magnitudes, only care about sign, +1/255 or -1/255 pixels_w_pos_grad = tf.cast(dloss_dimg > 0.0, 'float32') / 255. pixels_w_neg_grad = tf.cast(dloss_dimg < 0.0, 'float32') / 255. victim_img = victim_img - pixels_w_pos_grad + pixels_w_neg_grad
Matplotlib magic aiuta di nuovo a visualizzare i risultati.
Oh! Per l'occhio umano, ognuna di queste immagini è identica. Eppure, dopo quattro iterazioni, abbiamo convinto il classificatore che si tratta di un cane, con una sicurezza del 99,4 percento!
Assicuriamoci che questo non sia un colpo di fortuna e funzioni anche nell'altra direzione.
Successo! Il classificatore originariamente lo aveva previsto correttamente come un cane con una sicurezza del 98,4% e ora crede che sia un gatto con una sicurezza del 99,8%.
Infine, diamo un'occhiata a una patch di immagine di esempio e vediamo come è cambiata.
Come previsto, la patch finale è molto simile all'originale, con ogni pixel che si sposta solo da -4 a +4 nel valore di intensità del canale rosso. Questo spostamento non è sufficiente per un essere umano per distinguere la differenza, ma cambia completamente l'output del classificatore.
Considerazioni finali: ottimizzazione della discesa del gradiente
In questo articolo, abbiamo esaminato l'applicazione manuale dei gradienti ai nostri parametri addestrabili per motivi di semplicità e trasparenza. Tuttavia, nel mondo reale, i data scientist dovrebbero iniziare subito a utilizzare gli ottimizzatori , perché tendono ad essere molto più efficaci, senza aggiungere alcun codice bloat.
Esistono molti ottimizzatori popolari, inclusi RMSprop, Adagrad e Adadelta, ma il più comune è probabilmente Adam . A volte, sono chiamati "metodi di apprendimento adattivo" perché mantengono dinamicamente un tasso di apprendimento diverso per ogni parametro. Molti di loro usano termini di quantità di moto e derivate approssimative di ordine superiore, con l'obiettivo di sfuggire ai minimi locali e ottenere una convergenza più rapida.
In un'animazione presa in prestito da Sebastian Ruder, possiamo vedere il percorso di vari ottimizzatori che scendono su una superficie di perdita. Le tecniche manuali che abbiamo dimostrato sono più paragonabili a "SGD". L'ottimizzatore con le migliori prestazioni non sarà lo stesso per ogni superficie di perdita; tuttavia, gli ottimizzatori più avanzati in genere funzionano meglio di quelli più semplici.
Tuttavia, raramente è utile essere un esperto di ottimizzatori, anche per coloro che desiderano fornire servizi di sviluppo di intelligenza artificiale. È un uso migliore del tempo degli sviluppatori per familiarizzare con una coppia, solo per capire come migliorano la discesa del gradiente in TensorFlow. Dopodiché, possono semplicemente utilizzare Adam per impostazione predefinita e provarne di diversi solo se i loro modelli non stanno convergendo.
Per i lettori che sono veramente interessati a come e perché funzionano questi ottimizzatori, la panoramica di Ruder, in cui appare l'animazione, è una delle risorse migliori e più esaurienti sull'argomento.
Aggiorniamo la nostra soluzione di regressione lineare dalla prima sezione per utilizzare gli ottimizzatori. Quello che segue è il codice di discesa del gradiente originale che utilizza gradienti manuali.
# Manual gradient descent operations def run_gradient_descent(heights, weights, init_slope, init_icept, learning_rate): tf_slope = tf.Variable(init_slope, dtype='float32') tf_icept = tf.Variable(init_icept, dtype='float32') for i in range(25): with tf.GradientTape() as tape: tape.watch((tf_slope, tf_icept)) predictions = tf_slope * heights + tf_icept errors = predictions - weights loss = tf.reduce_mean(errors**2) gradients = tape.gradient(loss, [tf_slope, tf_icept]) tf_slope = tf_slope - learning_rate * gradients[0] tf_icept = tf_icept - learning_rate * gradients[1]
Ora, ecco invece lo stesso codice che utilizza un ottimizzatore. Vedrai che non è quasi un codice extra (le righe modificate sono evidenziate in blu):
# Gradient descent with Optimizer (RMSprop) def run_gradient_descent (heights, weights, init_slope, init_icept, learning_rate) : tf_slope = tf.Variable(init_slope, dtype= 'float32' ) tf_icept = tf.Variable(init_icept, dtype= 'float32' ) # Group trainable parameters into a list trainable_params = [tf_slope, tf_icept] # Define your optimizer (RMSprop) outside of the training loop optimizer = keras.optimizers.RMSprop(learning_rate) for i in range( 25 ): # GradientTape loop is the same with tf.GradientTape() as tape: tape.watch( trainable_params ) predictions = tf_slope * heights + tf_icept errors = predictions - weights loss = tf.reduce_mean(errors** 2 ) # We can use the trainable parameters list directly in gradient calcs gradients = tape.gradient(loss, trainable_params ) # Optimizers always aim to *minimize* the loss function optimizer.apply_gradients(zip(gradients, trainable_params))
Questo è tutto! Abbiamo definito un ottimizzatore RMSprop
al di fuori del ciclo di discesa del gradiente, quindi abbiamo utilizzato il metodo optimizationr.apply_gradients optimizer.apply_gradients()
dopo ogni calcolo del gradiente per aggiornare i parametri addestrabili. L'ottimizzatore è definito al di fuori del ciclo perché terrà traccia dei gradienti storici per il calcolo di termini aggiuntivi come quantità di moto e derivati di ordine superiore.
Vediamo come appare con l'ottimizzatore RMSprop .
Sembra fantastico! Ora proviamolo con l'ottimizzatore Adam .
Whoa, cosa è successo qui? Sembra che la meccanica dello slancio in Adam gli faccia superare la soluzione ottimale e invertire la rotta più volte. Normalmente, questa meccanica del momentum aiuta con superfici di perdita complesse, ma in questo caso semplice ci fa male. Questo sottolinea il consiglio di fare della scelta dell'ottimizzatore uno degli iperparametri da mettere a punto durante l'allenamento del proprio modello.
Chiunque desideri esplorare il deep learning vorrà acquisire familiarità con questo modello, poiché è ampiamente utilizzato nelle architetture TensorFlow personalizzate, dove è necessario disporre di complessi meccanismi di perdita che non possono essere facilmente inseriti nel flusso di lavoro standard. In questo semplice esempio di discesa del gradiente TensorFlow, c'erano solo due parametri addestrabili, ma è necessario ottimizzare quando si lavora con architetture contenenti centinaia di milioni di parametri.
Discesa graduale in TensorFlow: dalla ricerca dei minimi all'attacco ai sistemi di intelligenza artificiale
Tutti i frammenti di codice e le immagini sono stati prodotti dai notebook nel repository GitHub corrispondente. Contiene inoltre un riepilogo di tutte le sezioni, con link ai singoli taccuini, per i lettori che vogliono vedere il codice completo. Per semplificare il messaggio, sono stati tralasciati molti dettagli che possono essere trovati nell'ampia documentazione in linea.
Spero che questo articolo sia stato perspicace e ti abbia fatto pensare a come utilizzare la discesa del gradiente in TensorFlow. Anche se non lo usi da solo, si spera che renda più chiaro come funzionano tutte le moderne architetture di rete neurale: crea un modello, definisci una funzione di perdita e usa la discesa del gradiente per adattare il modello al tuo set di dati.
In qualità di Google Cloud Partner, gli esperti certificati da Google di Toptal sono a disposizione delle aziende on demand per i loro progetti più importanti.