Técnica Jedi para reducir redes convolucionais - poda
Antes de que de novo é a tarefa de detectar obxectos. A prioridade é a velocidade de operación cunha precisión aceptable. Tomas a arquitectura YOLOv3 e adestras aínda máis. A precisión (mAp75) é superior a 0.95. Pero a taxa de execución aínda é baixa. Merda.
Hoxe ignoraremos a cuantificación. E debaixo do corte miraremos Modelo de poda — recortar partes redundantes da rede para acelerar a inferencia sen perda de precisión. Está claro onde, canto e como cortar. Imos descubrir como facelo manualmente e onde podes automatizalo. Ao final hai un repositorio de keras.
Introdución
No meu anterior lugar de traballo, Macroscop en Perm, adquirei un hábito: supervisar sempre o tempo de execución dos algoritmos. E comprobe sempre o tempo de execución da rede a través dun filtro de adecuación. Normalmente, o estado da arte na produción non pasa este filtro, o que me levou a Poda.
A poda é un tema antigo que se tratou en Charlas de Stanford en 2017. A idea principal é reducir o tamaño da rede adestrada sen perder precisión eliminando varios nodos. Parece xenial, pero poucas veces escoito falar do seu uso. Probablemente, non haxa implementacións suficientes, non hai artigos en ruso ou simplemente todo o mundo o considera que pode podar o coñecemento e permanece en silencio.
Pero imos desmontar
Unha ollada á bioloxía
Encántame cando Deep Learning mira ideas que veñen da bioloxía. Eles, como a evolución, pódense confiar (sabías que ReLU é moi semellante a? función de activación neuronal no cerebro?)
O proceso de poda modelo tamén está preto da bioloxía. A resposta da rede aquí pódese comparar coa plasticidade do cerebro. Hai un par de exemplos interesantes no libro. Norman Doidge:
O cerebro dunha muller que naceu con só unha metade reprogramouse para realizar as funcións da metade desaparecida.
O mozo disparou a parte do seu cerebro responsable da visión. Co paso do tempo, outras partes do cerebro asumiron estas funcións. (non intentamos repetir)
Do mesmo xeito, pode cortar algunhas das circunvolucións débiles do seu modelo. Como último recurso, os paquetes restantes axudarán a substituír os cortados.
Gústache Transfer Learning ou estás aprendendo desde cero?
Opción número un. Usas Transfer Learning en Yolov3. Retina, Mask-RCNN ou U-Net. Pero a maioría das veces non necesitamos recoñecer 80 clases de obxectos como en COCO. Na miña práctica, todo está limitado aos graos 1-2. Pódese supoñer que aquí a arquitectura para 80 clases é redundante. Isto suxire que a arquitectura debe facerse máis pequena. Ademais, gustaríame facelo sen perder os pesos adestrados previamente existentes.
Opción número dous. Quizais tes moitos recursos de datos e informática, ou só necesites unha arquitectura superpersonalizada. Non importa. Pero estás aprendendo a rede desde cero. O procedemento habitual é mirar a estrutura de datos, seleccionar unha arquitectura EXCESIVA en potencia e impulsar os abandonos da reciclaxe. Vin 0.6 abandonos, Karl.
En ambos os casos, a rede pódese reducir. Motivado. Agora imos descubrir que tipo de poda de circuncisión é
Algoritmo xeral
Decidimos que podíamos eliminar os paquetes. Parece moi sinxelo:
Eliminar calquera convolución é estresante para a rede, o que normalmente leva a un aumento do erro. Por unha banda, este aumento do erro é un indicador de como eliminamos correctamente as circunvolucións (por exemplo, un gran aumento indica que estamos a facer algo mal). Pero un pequeno aumento é bastante aceptable e adoita eliminarse mediante un adestramento adicional lixeiro posterior cun pequeno LR. Engade un paso de adestramento adicional:
Agora temos que descubrir cando queremos deter o noso ciclo de Aprendizaxe<->Poda. Pode haber opcións exóticas aquí cando necesitemos reducir a rede a un determinado tamaño e velocidade (por exemplo, para dispositivos móbiles). Non obstante, a opción máis común é continuar o ciclo ata que o erro sexa superior ao aceptable. Engade unha condición:
Entón, o algoritmo queda claro. Queda por descubrir como determinar as circunvolucións eliminadas.
Busca paquetes eliminados
Temos que eliminar algunhas circunvolucións. Correr adiante e "disparar" a calquera é unha mala idea, aínda que funcionará. Pero como tes cabeza, podes pensar e tentar seleccionar circunvolucións "débiles" para eliminalas. Hai varias opcións:
Cada unha das opcións ten dereito á vida e ás súas propias características de implementación. Aquí consideramos a opción coa medida L1 máis pequena
Proceso manual para YOLOv3
A arquitectura orixinal contén bloques residuais. Pero por moi xeniais que sexan para as redes profundas, impediranos un pouco. A dificultade é que non pode eliminar conciliacións con índices diferentes nestas capas:
Polo tanto, seleccionemos capas das que podemos eliminar libremente as conciliacións:
Agora imos construír un ciclo de traballo:
Cargando activacións
Descubrir canto cortar
Córtao
Aprendizaxe de 10 épocas con LR=1e-4
Probando
A descarga de circunvolucións é útil para estimar canta parte podemos eliminar nun determinado paso. Exemplos de descarga:
Vemos que case en todas partes o 5% das circunvolucións teñen unha norma L1 moi baixa e podemos eliminalas. En cada paso repetíase esta descarga e facíase unha valoración de que capas e cantas se podían recortar.
Todo o proceso completouse en 4 pasos (números aquí e en todas partes para o RTX 2060 Super):
Paso
mapa 75
Número de parámetros, millóns
Tamaño da rede, mb
Dende inicial, %
Tempo de execución, ms
Condición de circuncisión
0
0.9656
60
241
100
180
-
1
0.9622
55
218
91
175
5% de todo
2
0.9625
50
197
83
168
5% de todo
3
0.9633
39
155
64
155
15 % para capas con máis de 400 circunvolucións
4
0.9555
31
124
51
146
10 % para capas con máis de 100 circunvolucións
Engadiuse un efecto positivo ao paso 2: o tamaño do lote 4 encaixa na memoria, o que acelerou moito o proceso de formación adicional.
No paso 4, o proceso detívose porque nin sequera a formación adicional a longo prazo elevou mAp75 a valores antigos.
Como resultado, conseguimos acelerar a inferencia 15%, reduce o tamaño en 35% e non perder exactamente.
Automatización para arquitecturas máis sinxelas
Para arquitecturas de rede máis sinxelas (sen bloques adicionais, concaternados e residuais condicionais), é moi posible centrarse en procesar todas as capas convolucionais e automatizar o proceso de corte de circunvolucións.
Implementei esta opción aquí.
É sinxelo: só necesitas unha función de perda, un optimizador e xeradores de lotes:
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 é necesario, pode cambiar os parámetros de configuración:
{
"input_model_path": "model.h5",
"output_model_path": "model_pruned.h5",
"finetuning_epochs": 10, # the number of epochs for train between pruning steps
"stop_loss": 0.1, # loss for stopping process
"pruning_percent_step": 0.05, # part of convs for delete on every pruning step
"pruning_standart_deviation_part": 0.2 # shift for limit pruning part
}
Ademais, implícase unha limitación baseada na desviación estándar. O obxectivo é limitar a parte que se elimina, excluíndo as circunvolucións con medidas L1 xa "suficientes":
Así, permitimos eliminar só as circunvolucións débiles de distribucións semellantes á dereita e non afectar a eliminación de distribucións similares á esquerda:
Cando a distribución se aproxima ao normal, o coeficiente pruning_standart_deviation_part pódese seleccionar entre:
Recomendo unha suposición de 2 sigma. Ou pode ignorar esta función, deixando o valor < 1.0.
A saída é un gráfico do tamaño da rede, a perda e o tempo de execución da rede para toda a proba, normalizado a 1.0. Por exemplo, aquí o tamaño da rede reduciuse case 2 veces sen perda de calidade (pequena rede convolucional con 100k pesos):
A velocidade de carreira está suxeita a flutuacións normais e permanece practicamente inalterada. Hai unha explicación para isto:
O número de circunvolucións cambia de conveniente (32, 64, 128) a non o máis conveniente para tarxetas de vídeo: 27, 51, etc. Podería estar equivocado aquí, pero o máis probable é que teña un efecto.
A arquitectura non é ampla, pero si consistente. Ao reducir o ancho, non afectamos á profundidade. Así, reducimos a carga, pero non cambiamos a velocidade.
Polo tanto, a mellora expresouse nunha redución da carga CUDA durante a execución nun 20-30%, pero non nunha redución no tempo de execución.
Resultados de
Reflexionemos. Consideramos 2 opcións para a poda: para YOLOv3 (cando tes que traballar coas túas mans) e para redes con arquitecturas máis sinxelas. Pódese ver que en ambos os casos é posible conseguir a redución do tamaño da rede e a aceleración sen perda de precisión. Resultados:
Reducindo o tamaño
Carreira de aceleración
Redución da carga CUDA
Como resultado, a compatibilidade co medio ambiente (Optimizamos o uso futuro dos recursos informáticos. Algún lugar é feliz Greta Thunberg)
Apéndice
Despois do paso de poda, pode engadir cuantización (por exemplo, con TensorRT)