Técnica Jedi de redução de redes convolucionais — pruning

Técnica Jedi de redução de redes convolucionais — pruning

Você tem novamente a tarefa de detecção de objetos. A prioridade é a velocidade de operação com uma precisão aceitável. Você utiliza a arquitetura YOLOv3 e a ajusta. A precisão (mAP75) é maior que 0,95. Mas a velocidade de execução ainda é baixa. Droga.

Hoje vamos evitar a quantização. E abaixo veremos Model Pruning — a remoção de partes redundantes da rede para acelerar a inferência sem perda de precisão. De forma visual — de onde, quanto e como é possível cortar. Vamos discutir como fazer isso manualmente e onde é possível automatizar. No final — repositório em keras.

Introdução

No meu antigo emprego, na Macroscop de Perm, adquiri um hábito — sempre acompanhar o tempo de execução dos algoritmos. E o tempo de execução das redes deve sempre ser verificado por meio de um filtro de adequação. Normalmente, o state-of-the-art não passa por esse filtro, o que me levou ao Pruning.

O Pruning é um tema antigo, apresentado nas aulas de Stanford em 2017. A ideia principal é reduzir o tamanho da rede treinada sem perder precisão, removendo vários nós. Parece ótimo, mas raramente ouço sobre sua aplicação. Provavelmente, faltam implementações, há escassez de artigos em russo ou simplesmente todos acham o pruning um segredo e permanecem em silêncio.
Mas vamos analisar.

Uma visão na biologia

Gosto quando ideias da biologia são incorporadas ao Deep Learning. Elas, assim como a evolução, podem ser confiáveis (e você sabia que a ReLU é bastante semelhante à função de ativação dos neurônios no cérebro??)

O processo de Model Pruning também é similar à biologia. A reação da rede aqui pode ser comparada à plasticidade do cérebro. Há alguns exemplos interessantes no livro de Norman Doidge.:

  1. O cérebro de uma mulher, que tinha apenas uma metade desde o nascimento, reprogramou a si mesmo para realizar as funções da metade que faltava.
  2. Um rapaz se atirou na cabeça e atingiu a parte do cérebro responsável pela visão. Com o tempo, outras partes do cérebro assumiram essas funções. (não estamos tentando repetir isso)

Assim, é possível cortar partes das suas convoluções fracas. No pior dos casos, as convoluções restantes ajudarão a substituir as cortadas.

Você prefere Transfer Learning ou está aprendendo do zero?

Opção número um. Você está usando Transfer Learning no Yolov3, Retina, Mask-RCNN ou U-Net. Mas, na maioria das vezes, não precisamos reconhecer 80 classes de objetos, como no COCO. Na minha experiência, tudo se limita a 1-2 classes. Pode-se supor que a arquitetura para 80 classes é excessiva aqui. Surge a ideia de que a arquitetura precisa ser reduzida, e isso deve ser feito sem perder os pesos pré-treinados existentes.

A segunda opção. Talvez você tenha muitos dados e recursos computacionais ou simplesmente precise de uma arquitetura super personalizada. Não importa. Mas você está treinando a rede do zero. A ordem comum é observar a estrutura dos dados, escolher uma arquitetura EXCESSIVA em potência e aplicar dropout para evitar overfitting. Eu já vi dropouts de 0.6, cara.

Em ambos os casos, a rede pode ser reduzida. Motivados. Agora vamos entender o que é o pruning.

Algoritmo geral

Decidimos que podemos remover as convoluções. Isso parece bastante simples:

Técnica Jedi de redução de redes convolucionais — pruning

Remover qualquer convolução é um estresse para a rede, que geralmente leva a um aumento do erro. Por um lado, esse aumento de erro é um indicativo de quão corretamente estamos removendo as convoluções (por exemplo, um grande aumento indica que estamos fazendo algo errado). Mas um pequeno aumento é aceitável e muitas vezes é eliminado por um leve re-treinamento com uma baixa taxa de aprendizado. Vamos adicionar um passo de re-treinamento:

Técnica Jedi de redução de redes convolucionais — pruning

Agora precisamos entender quando queremos parar nosso ciclo de LearningPruning. Aqui podem existir opções exóticas, onde precisamos reduzir a rede a um determinado tamanho e velocidade de execução (por exemplo, para dispositivos móveis). No entanto, a opção mais comum é continuar o ciclo até que o erro se torne maior do que o permitido. Vamos adicionar uma condição:

Técnica Jedi de redução de redes convolucionais — pruning

Então, o algoritmo se torna claro. Resta entender como identificar as convoluções a serem removidas.

Busca por convoluções removíveis

Precisamos remover algumas convoluções. Avançar de forma indiscriminada e 'disparar' quaisquer convoluções é uma má ideia, mesmo que funcione. Mas, uma vez que temos cabeça, podemos pensar e tentar identificar as convoluções 'fracas' para remoção. Existem várias opções:

  1. Menor medida L1 ou low_magnitude_pruning. A ideia é que convoluções com valores de peso baixos têm uma pequena contribuição na decisão final.
  2. Menor medida L1 considerando a média e o desvio padrão. Complementamos com uma avaliação do tipo de distribuição.
  3. Máscara de convoluções e exclusão das que têm menor impacto na precisão final. Definição mais precisa de convoluções insignificantes, mas bastante dispendiosa em termos de tempo e recursos.
  4. O status estável (GA) foi obtido para os seguintes recursos:

Cada uma das opções tem seu valor e suas características de implementação. Aqui, vamos considerar a opção com a menor medida L1

Processo manual para YOLOv3

A arquitetura original contém blocos residuais. Mas, por mais avançados que sejam para redes profundas, eles podem atrapalhar um pouco. O problema é que não podemos remover convoluções com índices diferentes nessas camadas:

Técnica Jedi de redução de redes convolucionais — pruning

Portanto, vamos destacar as camadas das quais podemos remover as convoluções livremente:

Técnica Jedi de redução de redes convolucionais — pruning

Agora vamos construir um ciclo de trabalho:

  1. Exportamos as ativações
  2. Estimamos quanto cortar
  3. Cortamos
  4. Treinamos por 10 épocas com LR=1e-4
  5. Testando

Exportar convoluções é útil para avaliar qual parte podemos remover em uma determinada etapa. Exemplos de exportação:

Técnica Jedi de redução de redes convolucionais — pruning

Vemos que praticamente em todos os lugares, 5% das convoluções têm uma norma L1 bastante baixa e podemos removê-las. Em cada etapa, essa exportação foi repetida e foram feitas avaliações de quais camadas e quanto poderíamos cortar.

Todo o processo se resolveu em 4 etapas (aqui e em todos os lugares, números para RTX 2060 Super):

PassomAp75Número de parâmetros, milhõesTamanho da rede, MBEm relação ao original, %Tempo de execução, msCondição de corte
00.965660241100180—
10.962255218911755% do total
20.962550197831685% do total
30.9633391556415515% para camadas com 400+ convoluções
40.9555311245114610% para camadas com 100+ convoluções

No passo 2, surgiu um efeito positivo — o tamanho do lote 4 cabia na memória, o que acelerou bastante o processo de re-treinamento.
No passo 4, o processo foi interrompido, pois mesmo um longo re-treinamento não elevou o mAp75 aos valores antigos.
No final, conseguimos acelerar a inferência em 15%, reduzir o tamanho em 35% e não perder precisão.

Automatização para arquiteturas mais simples

Para arquiteturas de rede mais simples (sem blocos condicionais add, concatenate e residuais), é possível focar em processar todas as camadas convolucionais e automatizar o processo de remoção de convoluções.

Esse tipo de implementação eu realizei aqui.
É simples: você só precisa da função de perda, otimizador e geradores de lote:

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 necessário, você pode alterar as configurações dos parâmetros:

{
    "input_model_path": "model.h5",
    "output_model_path": "model_pruned.h5",
    "finetuning_epochs": 10, # o número de épocas para treinar entre as etapas de poda
    "stop_loss": 0.1, # perda para interromper o processo
    "pruning_percent_step": 0.05, # parte das convoluções a serem eliminadas a cada etapa de poda
    "pruning_standart_deviation_part": 0.2 # deslocamento para limitar a parte de poda
}

Adicionalmente, foi implementada uma limitação com base no desvio padrão. O objetivo é limitar a parte removida, excluindo convoluções com medidas L1 já 'suficientes':

Técnica Jedi de redução de redes convolucionais — pruning

Com isso, permitimos a remoção apenas de convoluções fracas de distribuições semelhantes à direita e não influenciamos a remoção de distribuições semelhantes à esquerda:

Técnica Jedi de redução de redes convolucionais — pruning

À medida que a distribuição se aproxima da normal, o coeficiente pruning_standart_deviation_part pode ser escolhido a partir de:

Técnica Jedi de redução de redes convolucionais — pruning
Recomendo uma margem de 2 sigmas. Ou pode-se não se basear nessa particularidade, deixando o valor < 1.0.

O resultado é um gráfico do tamanho da rede, perda e tempo de execução da rede em todo o teste, normalizados para 1.0. Por exemplo, aqui o tamanho da rede foi reduzido quase pela metade sem perda de qualidade (uma pequena rede convolucional com 100k pesos):

Técnica Jedi de redução de redes convolucionais — pruning

A velocidade de execução está sujeita a flutuações normais e praticamente não mudou. Existe uma explicação para isso:

  1. O número de convoluções muda de formatos convenientes (32, 64, 128) para opções menos convenientes para placas gráficas — 27, 51 etc. Posso estar errado, mas provavelmente isso influencia.
  2. A arquitetura não é ampla, mas sequencial. Ao reduzir a largura, não alteramos a profundidade. Assim, reduzimos a carga, mas não mudamos a velocidade.

Portanto, a melhoria se traduz em uma redução na carga do CUDA durante a execução em 20-30%, mas não na redução do tempo de execução.

Resultados

Vamos refletir. Consideramos 2 variantes de poda — para YOLOv3 (quando é necessário trabalhar manualmente) e para redes com arquiteturas mais simples. É evidente que em ambos os casos é possível alcançar a redução do tamanho da rede e aceleração sem perda de precisão. Resultados:

  • Redução de tamanho
  • Aceleração da execução
  • Redução da carga do CUDA
  • Como consequência, sustentabilidade (Estamos otimizando o futuro uso de recursos computacionais. Em algum lugar, alguém fica feliz Greta Thunberg)

Apêndice

  • Após o passo de poda, pode-se ajustar também a quantização (por exemplo, com TensorRT)
  • O Tensorflow oferece recursos para low_magnitude_pruning. Funciona.
  • Repositório Quero desenvolver e ficarei feliz com a ajuda

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster