Tecnica Jedi per ridurre le reti convoluzionali — pruning

Tecnica Jedi per ridurre le reti convoluzionali — pruning

Hai di nuovo un compito di rilevamento degli oggetti. La priorità è la velocità di funzionamento con una precisione accettabile. Prendi l'architettura YOLOv3 e continua l'addestramento. La precisione (mAp75) deve essere superiore a 0,95. Ma la velocità di elaborazione è ancora bassa. Cavolo.

Oggi eviteremo la quantizzazione. Sotto il titolo analizzeremo Potatura del Modello — rimozione delle parti ridondanti della rete per accelerare l'inferenza senza perdita di precisione. In modo chiaro — da dove, quanto e come è possibile rimuovere. Vedremo come farlo manualmente e dove è possibile automatizzare. Alla fine — un repository su keras.

Introduzione

Nel mio precedente lavoro, a Macroscop di Perm, ho sviluppato un'abitudine — controllare sempre il tempo di esecuzione degli algoritmi. E il tempo di elaborazione delle reti va sempre verificato attraverso un filtro di adeguatezza. Di solito, le soluzioni all'avanguardia non superano questo filtro, e questo mi ha portato alla Potatura.

La Potatura è un tema vecchio, di cui si è parlato nelle lezioni di Stanford nel 2017. L'idea principale è ridurre la dimensione della rete addestrata senza perdita di precisione rimuovendo diversi nodi. Sembra interessante, ma raramente sento parlare del suo utilizzo. Probabilmente mancano implementazioni, non ci sono articoli in lingua russa, o semplicemente tutti considerano la potatura un segreto e tacciono.
Ma andiamo a esaminare

Uno sguardo alla biologia

Mi piace quando nel Deep Learning entrano idee provenienti dalla biologia. A loro, come all'evoluzione, si può dare fiducia (sapevi che la ReLU è molto simile a una funzione di attivazione dei neuroni nel cervello?)

Il processo di Potatura del Modello è anche vicino alla biologia. La reazione della rete può essere paragonata alla plasticità del cervello. Ci sono alcuni esempi interessanti nel libro di Norman Doidge:

  1. Il cervello di una donna che aveva solo metà da quando è nata ha riprogrammato se stesso per svolgere le funzioni della metà mancante
  2. Un ragazzo si è sparato a una parte del cervello responsabile della vista. Col tempo, altre parti del cervello hanno assunto queste funzioni. (non proviamo a ripetere)

Così anche dal tuo modello puoi rimuovere alcune convoluzioni deboli. In casi estremi, le convoluzioni rimanenti possono aiutare a sostituire quelle rimosse.

Preferisci il Transfer Learning o inizi da zero?

Opzione numero uno. Stai utilizzando il Transfer Learning su Yolov3. Retina, Mask-RCNN o U-Net. Ma nella maggior parte dei casi non è necessario riconoscere 80 classi di oggetti, come nel COCO. Nella mia pratica, ci si limita a 1-2 classi. Si può supporre che l'architettura per 80 classi sia eccessiva. Si fa pensare che sia necessario ridurre l'architettura. Inoltre, vorremmo farlo senza perdere i pesi pre-addestrati esistenti.

Opzione numero due. Forse hai molti dati e risorse di calcolo o hai semplicemente bisogno di un'architettura super personalizzata. Non importa. Ma stai addestrando la rete da zero. L'ordine abituale è osservare la struttura dei dati, scegliere un'architettura ECCESSIVA in termini di potenza e applicare il dropout per evitare il sovraccarico. Ho visto dropout a 0.6, Karl.

In entrambi i casi, è possibile ridurre la rete. Ci siamo motivati. Ora andiamo a capire cos'è il pruning.

Algoritmo generale

Abbiamo deciso che possiamo rimuovere le convoluzioni. Questo sembra piuttosto semplice:

Tecnica Jedi per ridurre le reti convoluzionali — pruning

Rimuovere qualsiasi convoluzione è uno stress per la rete, che di solito porta anche a un certo aumento dell'errore. Da un lato, questo aumento dell'errore è un indicatore di quanto correttamente stiamo rimuovendo le convoluzioni (ad esempio, un grande aumento indica che stiamo facendo qualcosa di sbagliato). Ma un piccolo aumento è del tutto accettabile e spesso può essere corretto con un successivo leggero riaddestramento con un LR ridotto. Aggiungiamo un passaggio di riaddestramento:

Tecnica Jedi per ridurre le reti convoluzionali — pruning

Ora dobbiamo capire quando vogliamo fermare il nostro ciclo LearningPruning. Ci possono essere varianti esotiche, quando dobbiamo ridurre la rete a una certa dimensione e velocità di esecuzione (ad esempio, per dispositivi mobili). Tuttavia, la variante più comune è quella di continuare il ciclo finché l'errore non supera un certo limite. Aggiungiamo una condizione:

Tecnica Jedi per ridurre le reti convoluzionali — pruning

Quindi, l'algoritmo diventa chiaro. Rimane da capire come determinare le convoluzioni da rimuovere.

Ricerca delle convoluzioni da rimuovere

Dobbiamo rimuovere alcune convoluzioni. Buttarsi a capofitto e "sparare" a qualsiasi cosa è una cattiva idea, anche se funzionerà. Ma poiché abbiamo un cervello, possiamo riflettere e cercare di identificare le convoluzioni "deboli" da rimuovere. Ci sono diverse opzioni:

  1. La misura L1 minima o low_magnitude_pruning. L'idea è che le convoluzioni con valori di peso piccoli contribuiscono poco alla decisione finale.
  2. La misura L1 più piccola tenendo conto della media e della deviazione standard. Completiamo con la valutazione della natura della distribuzione.
  3. Mascheramento delle convoluzioni e esclusione delle meno influenti sulla precisione finale. Una definizione più precisa delle convoluzioni insignificanti, ma piuttosto costosa in termini di tempo e risorse.
  4. Altri

Ognuno delle opzioni ha diritto di esistere e le proprie peculiarità di implementazione. Qui considereremo l'opzione con la misura L1 più piccola

Processo manuale per YOLOv3

Nell'architettura originale ci sono blocchi residuali. Ma per quanto possano essere fantastici per le reti profonde, ci daranno un po' fastidio. Il problema è che non si possono rimuovere le convoluzioni con indici diversi in questi strati:

Tecnica Jedi per ridurre le reti convoluzionali — pruning

Quindi evidenzieremo gli strati da cui possiamo rimuovere liberamente le convoluzioni:

Tecnica Jedi per ridurre le reti convoluzionali — pruning

Ora costruiremo un ciclo di lavoro:

  1. Esportiamo le attivazioni
  2. Calcoliamo quanto tagliare
  3. Tagliamo
  4. Alleniamo per 10 epoche con LR=1e-4
  5. Testiamo

Esportare le convoluzioni è utile per valutare quale parte possiamo rimuovere in un certo passaggio. Esempi di esportazione:

Tecnica Jedi per ridurre le reti convoluzionali — pruning

Vediamo che praticamente ovunque il 5% delle convoluzioni ha una L1 norm molto bassa e possiamo eliminarle. In ogni passaggio tale esportazione è stata ripetuta e sono state valutate da quali strati e quanto si poteva tagliare.

L'intero processo si è svolto in 4 passaggi (qui e ovunque i numeri sono per RTX 2060 Super):

PassaggiomAp75Numero di parametri, mlnDimensione della rete, mbDall'originale, %Tempo di esecuzione, msCondizione di potatura
00.965660241100180
10.962255218911755% di tutti
20.962550197831685% di tutti
30.9633391556415515% per strati con 400+ convoluzioni
40.9555311245114610% per strati con 100+ convoluzioni

Al secondo passaggio è emerso un effetto positivo — è riuscito a contenere un batch size di 4 in memoria, accelerando notevolmente il processo di riaddestramento.
Nel quarto passaggio il processo è stato interrotto, poiché anche un lungo riaddestramento non ha sollevato mAp75 ai vecchi valori.
In definitiva, siamo riusciti ad accelerare l'inferenza di 15%, ridurre la dimensione di 35% e non perdere in precisione.

Automazione per architetture più semplici

Per architetture di rete più semplici (senza blocchi addizionali, concatenati e residuali) è possibile orientarsi verso l'elaborazione di tutti gli strati convoluzionali e automatizzare il processo di rimozione delle convoluzioni.

Ho implementato tale opzione qui.
È tutto molto semplice: hai solo bisogno della funzione di perdita, dell'ottimizzatore e dei generatori di batch:

import pruning
from keras.optimizers import Adam
from keras.utils import Sequence

train_batch_generator = BatchGenerator...
score_batch_generator = BatchGenerator...

opt = Adam(lr=1e-4)
pruner = pruning.Pruner("config.json", "categorical_crossentropy", opt)

pruner.prune(train_batch, valid_batch)

Se necessario, puoi modificare i parametri della configurazione:

{
    "input_model_path": "model.h5",
    "output_model_path": "model_pruned.h5",
    "finetuning_epochs": 10, # il numero di epoche per l'addestramento tra i passaggi di potatura
    "stop_loss": 0.1, # perdita per fermare il processo
    "pruning_percent_step": 0.05, # parte delle convoluzioni da eliminare ad ogni passaggio di potatura
    "pruning_standart_deviation_part": 0.2 # scostamento per limitare la parte di potatura
}

In aggiunta, è stata implementata una limitazione basata sulla deviazione standard. L'obiettivo è limitare la parte rimossa, escludendo le convoluzioni con misure L1 "sufficienti":

Tecnica Jedi per ridurre le reti convoluzionali — pruning

In questo modo, permettiamo di rimuovere solo le convoluzioni deboli da distribuzioni simili a quella destra e di non influire sulla rimozione da distribuzioni simili a quella sinistra:

Tecnica Jedi per ridurre le reti convoluzionali — pruning

Con l'avvicinarsi della distribuzione a una normale, il coefficiente pruning_standart_deviation_part può essere scelto da:

Tecnica Jedi per ridurre le reti convoluzionali — pruning
Raccomando un'affidabilità di 2 sigma. Oppure non fare riferimento a questa caratteristica, lasciando il valore < 1.0.

Il risultato finale è un grafico delle dimensioni della rete, della perdita e del tempo di esecuzione della rete su tutto il test, normalizzato a 1.0. Ad esempio, qui la dimensione della rete è stata ridotta quasi della metà senza perdita di qualità (una piccola rete convoluzionale con 100k pesi):

Tecnica Jedi per ridurre le reti convoluzionali — pruning

La velocità di esecuzione è soggetta a fluttuazioni normali e praticamente non è cambiata. Questo ha una spiegazione:

  1. Il numero di convoluzioni cambia da comodi (32, 64, 128) a meno comodi per le schede grafiche — 27, 51 e così via. Potrei sbagliarmi, ma probabilmente questo influisce.
  2. L'architettura non è ampia, ma sequenziale. Riducendo la larghezza, non tocchiamo la profondità. Questo riduce il carico, ma non cambia la velocità.

Pertanto, il miglioramento si è tradotto in una riduzione del carico CUDA durante l'esecuzione del 20-30%, ma non in una riduzione dei tempi di esecuzione.

Conclusioni

Riflettiamo. Abbiamo considerato 2 opzioni di potatura — per YOLOv3 (quando è necessario lavorare manualmente) e per reti con architetture più semplici. È evidente che in entrambi i casi si può ottenere una riduzione della dimensione della rete e un'accelerazione senza perdita di precisione. Risultati:

  • Riduzione della dimensione
  • Accelerazione dell'esecuzione
  • Riduzione del carico CUDA
  • Di conseguenza, ecocompatibilità (Ottimizziamo il futuro utilizzo delle risorse computazionali. Da qualche parte una Greta Thunberg)

Appendice

  • Dopo il passaggio di potatura, si può anche procedere con la quantizzazione (ad esempio con TensorRT)
  • Tensorflow offre opportunità per low_magnitude_pruning. Funziona.
  • Repository Voglio sviluppare e sarò felice di ricevere aiuto

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster