
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 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 ?)
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 :
- Il cervello di una donna che aveva solo metà da quando è nata ha riprogrammato se stesso per svolgere le funzioni della metà mancante
- 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:

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:

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:

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:
- . L'idea è che le convoluzioni con valori di peso piccoli contribuiscono poco alla decisione finale.
- La misura L1 più piccola tenendo conto della media e della deviazione standard. Completiamo con la valutazione della natura della distribuzione.
- . Una definizione più precisa delle convoluzioni insignificanti, ma piuttosto costosa in termini di tempo e risorse.
- 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:

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

Ora costruiremo un ciclo di lavoro:
- Esportiamo le attivazioni
- Calcoliamo quanto tagliare
- Tagliamo
- Alleniamo per 10 epoche con LR=1e-4
- Testiamo
Esportare le convoluzioni è utile per valutare quale parte possiamo rimuovere in un certo passaggio. Esempi di esportazione:

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):
| Passaggio | mAp75 | Numero di parametri, mln | Dimensione della rete, mb | Dall'originale, % | Tempo di esecuzione, ms | Condizione di potatura |
|---|---|---|---|---|---|---|
| 0 | 0.9656 | 60 | 241 | 100 | 180 | — |
| 1 | 0.9622 | 55 | 218 | 91 | 175 | 5% di tutti |
| 2 | 0.9625 | 50 | 197 | 83 | 168 | 5% di tutti |
| 3 | 0.9633 | 39 | 155 | 64 | 155 | 15% per strati con 400+ convoluzioni |
| 4 | 0.9555 | 31 | 124 | 51 | 146 | 10% 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 .
È 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":

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:

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

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):

La velocità di esecuzione è soggetta a fluttuazioni normali e praticamente non è cambiata. Questo ha una spiegazione:
- 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.
- 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 )
Appendice
- Dopo il passaggio di potatura, si può anche procedere con la quantizzazione (ad esempio con TensorRT)
- Tensorflow offre opportunità per . Funziona.
- Voglio sviluppare e sarò felice di ricevere aiuto
Fonte: habr.com
