Processing math: 100%

Algoritmo del simplesso

Martedì, 12 Novembre 2019 | Math&Algebra |

L'algoritmo del simplesso, sviluppato nel 1947 da Dantzig, è probabilmente l'algoritmo più noto per la risoluzione di problemi di Programmazione Lineare (PL).

Cerchiamo di sintetizzare i ragionamenti a monte e i passaggi fondamentali

Il problema

Consideriamo il problema di PL  in forma standard, dove le diseguaglianze sono già state convertite in eguaglianze con l'aggiunta di variabili di slack:

{mincTxAx=bx0

Il problema consta nella ricerca del minimo della funzione su un poliedro nell'iperspazio, del quale ogni punto x può essere rappresentato come combinazione convessa dei suoi punti estremi e combinazione conica delle sue direzioni estreme, quindi

x=kj=1λjxj+lj=1μjdj dove λj,μj0 e kj=1λj=1

Valore ottimo limitato e illimitato

Per quanto detto sopra, il problema potrebbe quindi essere trasformato nella ricerca dei valori di λ,μ del seguente problema:

{min[kj=1(cTxj)λj+lj=1(cTdj)μj]λj,μj0kj=1λj=1

Si nota che, potendo essere gli μj arbitrariamente grandi, è sufficiente anche un solo  cTdj<0 per rendere l'ottimo illimitato.

Vediamo quindi che condizione necessaria e sufficiente per avere un valore ottimo limitato del problema è che cTdj0 per ogni direzione estrema dj , e di conseguenza andremo a scegliere tutti i μj nulli.

In questo caso, vista la condizionekj=1λj=1  risulta immediato che l'ottimo è indicato proprio dal minore dei cTxj al quale assegneremo λ=1 e zero a tutti gli altri.

In conclusione, se l'ottimo è finito, si trova proprio su uno dei punti estremi del poliedro.

Soluzione di base ammissibile

Il concetto di punto estremo è comunque un concetto geometrico. Abbiamo bisogno di ricavare questi punti in modo algebrico per poterli implementare in un algoritmo. Introduciamo. Consideriamo il sistema

{Ax=bx0

dove  rank(A(m×n),b(m×1))=rank(A(m×n))=m

A questo punto riorganizziamo le colonne di Ain modo che A=[B,N] dove B(m×m) è invertibile B(m×(nm))  .  Riorganizziamo di conseguenza il vettore delle incognite x=[xBxN]. Abbiamo quindi:

BxB+NxN=b   in virtù dell'invertibilità di B:

B1BxB+B1NxN=B1b  ,  e ponendo arbitrariamente xN=0  abbiamo infine 

xB=B1b  e xN=0 che definiamo soluzione di base. 

Se xB0 parliamo di soluzione di base ammissibile. 

In particolare se xB>0  parliamo di soluzione di base ammissibile non-degenere, mentre se esiste almeno una  componente di xB nulla parliamo di soluzione di base ammissibile degenere.

Senza fornire una dimostrazione rigorosa, intuiamo che ad ogni punto estremo corrisponde una soluzione di base ammissibile e viceversa. Difatti, ogni punto estremo in Rn è dato proprio dall'incrocio di n iperpiani, ricavabili dai iperpiani di  Ax=b , fissando come ulteriore condizione a zero (n-m) componenti di x per muoverci sul bordo del poliedro.

Il simplesso: teoria

Potremmo quindi pensare di verificare per ogni punto estremo (al massimo(nm) tenendo conto delle soluzioni non ammissibili e dei punti estremi degeneri ai quali possono corrispondere più basi degeneri) il valore della nostra funzione obiettivo e individuare il minimo.

Ciò non è comunque computazionalmente accettabile al crescere delle variabili - e comunque non terrebbe in conto l'eventuale esistenza di aree / minimi illimitati.

Il metodo del simplesso ci consente di muoverci da un punto estremo all'altro in modo intelligente, rilevando anche eventuali situazioni di minimo illimitato od aree ammissibili nulle, svolgendo valutazioni locali senza dover testare tutti i punto estremi/soluzioni di base ammissibili.

Supponiamo di avere una soluzione di base ammissibile [B1b0] con corrispondente valore della funzione obiettivo z0

z0=c[B1b0]=[cbcn][B1b0]=cBB1b

E in quanto soluzione di base ammissibile abbiamo:

Ax=b

BxB+NxN=b

xB=B1bB1NxN

xB=B1bjJB1ajxj  dove asono le colonne della attuale matrice fuori base e xj le rispettive variabili fuori base

xB=¯bjJyjxj dove  yj=B-1a è un vettore colonna di dimensione m x 1 

Sostituiamo ora nella equazione di costo:

z=cTBxB+cTNxN

z=cTB(B1bjJB1ajxj)+jJcjxj
z=z0jJ(zjcj)xj  dove abbiamo posto zj=cTBB1aj per ogni variabile fuori base.

Pertanto il nostro problema di PL, data la ns. soluzione di base ammissibile, può essere riscritto in funzione delle xj fuori base come:

{minz=z0jJ(zjcj)xjjJyjxj+xB=¯bxj0,jJxB0

A questo punto notiamo che xgioca un ruolo da variabile di slack e possiamo riscrivere in forma canonica il problema come:

{minz=z0jJ(zjcj)xjjJyjxj¯bxj0,jJ

Da qui si deduce la condizione di ottimalità della ns. soluzione, ovvero che (zjcj)0    jJ

Se la ns. soluzione non è ottima, porremo a zero tutti gli xj tranne  xk al quale è associato il maggiore dei (zjcj) , assegnandogli il valore più grande possibile che rispetti il vincolo ykxk¯b  ovvero

[y1ky2kymk]xk[¯b1¯b2¯bm] che per la singola componente risulta sempre verificata per valori di yik0  , mentre per valori di yik>0  la xk potrà al massimo essere ¯biyik.  Quindi il valore massimo che assegneremo ad xk=¯bryrk=mini{¯biyik:yik>0}

Risulta evidente che se tutti gli yik0 ci troviamo di fronte ad un ottimo illimitato, in quanto diviene possibile associare ad xk un valore arbitrariamente grande .

Se invece esiste almeno un  yik>0 possiamo ricavare la nuova soluzione di base ammissibile da:

xB=¯bjJyjxj=¯byk¯bryrk  , vettore nel quale vedremo azzerare la componente r-esima, che uscirà dalla base, mentre vi entrerà la componente k-esima prima fuori base con il valore ¯bryrk 

Il simplesso: step dell'algoritmo

Ricapitoliamo in step i passaggi visti nello sviluppo teorico:

1.  Partendo da una  soluzione di base di base ammissibile, ricavando xB=B1b=¯b,xN=0,z=cTBxB

2. Calcoliamo i costi per tutte le variabili fuori base  zjcj=cTBB1ajcj , se tutti costi sono minori o eguali a zero, abbiamo trovato un ottimo limitato e la soluzione del problema di PL.  Altrimenti proseguiamo scegliendo di introdurre in base la xk=maxiJ(zici)

3.  Calcoliamo yk=B1ak e verifichiamo se yk0 su ogni componente. In questo caso ci troveremmo di fronte a una soluzione ottima illimitata. Se almeno una componente di ykè maggiore di zero proseguiamo:

4. La nuova componente potrà entrare in base con il valore xk=¯bryrk=mini{¯biyik:yik>0}  , mentra la precedente base sarà aggiornata come da formula xB=¯byk¯bryrk  causando l'uscita della r-ma componente dalla base.

Si considera quindi l'ingresso in base della k-ma componente e l'uscita della r-ma componente e si riparte dal punto 1.

 

Il metodo della discesa del gradiente è un metodo iterativo per trovare minimi (locali) di una generica f:RnR utilizzando il gradiente f=[fx1fxn]

Visto che il gradiente f ci indica per definizione direzione e intensità di massima crescita della funzione, il metodo prevede di spostarsi per passi verso il minimo nella direzione opposta f    

Algoritmo

La discesa del gradiente può ricondursi a:

  • Inizializzazione: Si sceglie un punto di partenza x(0)R e un tasso di apprendimento α>0
  • Iterazione: Si calcolano iterativamente i sucessivi punti x(i)=x(i1)αf(x(i1)) sino a che f(x(i)) non converge in modo soddisfacente verso il minimo (ulteriori iterazioni non lo migliorano significativamente) - oppure sino a raggiungere un numero massimo di iterazioni prefissate.

Un semplice esempio in Python che implementa la discesa del gradiente sulla funzione forward  che avremo predisposto assieme alla funzione backward che calcola il gradiente:

import numpy as np
  
def gradient_descent(epochs,alpha,...):
 # Inizializzazione dei parametri secondo
 # criteri dipendenti dalla funzione 
 # (es: con varianza predeterminata, 
 # a zero, etc.)
 w=inizializza_parametri();

 # Itera per il numero di epoche
 for e in range(1,epochs):
  
  # Calcola il valore della funzione costo
  # in base ai parametri w da ottimizzare
  # e ad altri parametri non oggetto di
  # ottimizzazione 
  f=forward(w,...)

  print ("Costo epoca ",e,": ",f)
  
  # Calcola il valore del gradiente
  # rispetto a w
  dw=backward(w,...)

  w=w-alpha*dw
 return w

Il tasso di apprendimento

La scelta del tasso di apprendimento (o passo di discesa) α generalmente compreso tra 0 e 1, è cruciale per il buon funzionamento dell'algoritmo.

Come vediamo dal seguente esempio f:RnR se  α è troppo grande il metodo divergerà, facendo passi troppo ampi e allontanandosi dal minimo anzichè avvicinarsi - cosa che riscontreremo dal valore della funzione di costo che inizierà ad aumentare ad ogni epoca :

Una scelta oculata di α invece porterà alla convergenza del metodo - avendo cura di non scegliere un  α troppo piccolo che  comporterebbe una convgenza molto lenta e a passi molto piccoli. Per questo diciamo che  α è un iperparametro che determina in modo sostanziale convergenza e velocità della convergenza - influendo in modo determinante sulla qualità del risultato, specie se si prevede di fermare l'algoritmo dopo un numero fissato di epoche.

 

Considerazioni sui minimi locali

Il metodo della discesa del gradiente converge quindi verso un minimo locale della funzione - a meno che la funzione non sia convessa e quindi converge verso il minimo globale della funzione.

Visto che usiamo costantemente questo metodo (e sue evoluzioni) per ottimizzare funzioni di costo per i più svariati modelli - primi tra tutti quelli legati alle reti neurali e al deep learning, la domanda più ovvia è se la ricerca di un minimo locale sia soddisfacente, oppure se ci sia un alto rischio di convergere verso un minimo locale sensibilmente peggiore rispetto al minimo globale e ad altri minimi locali.

Senza entrare nel dettaglio delle dimostrazioni, si evidenzia intuitivamente che con l’aumentare delle dimensioni n del problema/funzione da ottimizzare diminuisce la probabilità 12n di imbatterci in un minimo locale. La quasi totalità dei punti in cui f=0 sono difatti punti di sella, minimi secondo alcune dimensioni, massimi per altre, flessi orizzontali per altre ancora.  Per trovarci di fronte ad un minimo locale verso il quale l'algoritmo converga è quindi necessario non solo che  f=0, ma che f abbia effettivamente in quel punto un minimo secondo ognuna delle  n dimensioni.

E’ stato verificato che con l’aumentare delle dimensioni le soluzioni di minimo locale sono qualitativamente simili, specie nell'ambito dei problemi di deep learning con numero elevatissimo di variabili/dimensioni)
 

Limiti della discesa del gradiente:  disomogeneità del gradiente e plateau

Il metodo della discesa del gradiente presenta alcune problematicità che ne rendono l'utilizzo abbastanza limitato nella sua versione "semplice".

In primo luogo vi è una problematicità quando il gradiente della funzione è fortemente disomogeneo tra le varie dimensioni, specie nell'intorno dei minimi locali. In questo caso il metodo può rivelarsi inefficiente e necessitare di molti passi per convergere in modo accettabile.

 

L'altro caso, che si verifica frequentemente con l'aumentare delle dimensioni, è quello dei plateau , zone dove f è pressochè costante (f0), e quindi la semplice discesa del gradiente rischia di rallentare sino a «congelarsi».

In entrambi i casi l’uso di varianti migliorate della discesa del gradiente che vedremo in seguito aiuta a limitare gli effetti di queste situazioni patologiche e a rendere più efficiente la ricerca.

©2023 - me@enricogiannini.com -