Padding é um processo essencial em Redes Neurais Convolucionais. Embora não seja obrigatório, é um processo que frequentemente é usado em muitas arquiteturas de CNN de estado da arte. Neste artigo, vamos explorar por que e como é feito.
O Mecanismo de Convolução
A convolução, no contexto de processamento de imagens/visão computacional, é um processo pelo qual uma imagem é “rascada” por um filtro para a processar de alguma forma. Vamos ficar um pouco técnico com os detalhes.
Para um computador, uma imagem é simplesmente um array de tipos numéricos (números, inteiros ou reais), esses tipos numéricos são chamados de pixels. Na verdade, uma imagem HD de 1920 pixels por 1080 pixels (1080p) é apenas uma tabela/array de tipos numéricos com 1080 linhas e 1920 colunas. Um filtro, por outro lado, é essencialmente o mesmo, mas usualmente com dimensões menores, o filtro de convolução comum (3, 3) é um array de 3 linhas e 3 colunas.
Quando uma imagem é convolvida, um filtro é aplicado aos pedaços sequenciais da imagem onde a multiplicação elemento a elemento é realizada entre os elementos do filtro e os pixels desse pedaço, então é retornada uma soma acumulada como um novo pixel. Por exemplo, quando realizando convolução com um filtro (3, 3), 9 pixels são agregados para produzir um único pixel. Devido a este processo de agregação, alguns pixels são perdidos.
Scanning de filtro sobre uma imagem para gerar uma nova imagem via convolução.
Os Pixels Perdidos
Para entender por que os pixels são perdidos, lembre-se que se um filtro de convolução sai dos limites ao scanner uma imagem, essa instância particular de convolução é ignorada. Para ilustrar, considere uma imagem de 6 x 6 pixels sendo convolvida por um filtro de 3 x 3. Como pode ser visto na imagem abaixo, as primeiras 4 convoluções estão dentro da imagem para produzir 4 pixels para a primeira linha enquanto as quinta e sexta instâncias estão fora dos limites e são portanto ignoradas. Da mesma forma, se o filtro for movido para baixo em 1 pixel, o mesmo padrão é repetido com a perda de 2 pixels para a segunda linha também. Quando o processo está completo, a imagem de 6 x 6 pixels torna-se uma imagem de 4 x 4 pixels since teria perdido 2 colunas de pixels em dim 0 (x) e 2 linhas de pixels em dim 1 (y).
Instâncias de convolução usando um filtro de 3×3.
Ao mesmo tempo, se um filtro de 5 x 5 for usado, 4 colunas e linhas de pixels são perdidos em ambas dim 0 (x) e dim 1 (y) respectivamente, resultando em uma imagem de 2 x 2 pixels.
Instâncias de convolução usando um filtro de 5×5.
Não me acredite, tente a função abaixo para ver se isso é mesmo o caso. Sinta-se livre para ajustar argumentos conforme desejado.
Parece haver um padrão na forma como os pixels são perdidos. Sempre que um filtro m x n é usado, são perdidas m-1 colunas de pixels no eixo 0 e n-1 linhas de pixels no eixo 1. Vamos avançar um pouco mais com a matemática…
tamanho da imagem = (x, y)
tamanho do filtro = (m, n)
tamanho da imagem após a convolução = (x-(m-1), y-(n-1)) = (x-m+1, y-n+1)
Quando uma imagem de tamanho (x, y) é convolvida usando um filtro de tamanho (m, n), é produzida uma imagem de tamanho (x-m+1, y-n+1).
Enquanto essa equação possa parecer um pouco complicada (sem intenção de chutar), a lógica por trás dela é muito simples de seguir. Já que a maioria dos filtros comuns são quadrados em tamanho (mesmas dimensões em ambos os eixos), tudo o que precisa saber é que, assim que a convolução for feita usando um filtro (3, 3), 2 linhas e colunas de pixels são perdidas (3-1); se for feita usando um filtro (5, 5), 4 linhas e colunas de pixels são perdidas (5-1); e se for feita usando um filtro (9, 9), você adivinhou, 8 linhas e colunas de pixels são perdidas (9-1).
Implicações dos Pixels Perdidos
Perder 2 linhas e colunas de pixels pode não parecer ter muito efeito, particularmente quando se trata de imagens grandes, por exemplo, uma imagem 4K UHD (3840, 2160) pareceria não sendo afetada pela perda de 2 linhas e colunas de pixels quando for convolvida the um filtro (3, 3), pois torna-se (3838, 2158), uma perda de cerca de 0,1% de seus pixels totais. Os problemas começam a surgir quando há várias camadas de convolução envolvidas, como é típico em arquiteturas de CNN de ponta de arte. Tomem o RESNET 128, por exemplo, esta arquitetura tem cerca de 50 camadas de convolução (3, 3), o que resultaria na perda de cerca de 100 linhas e colunas de pixels, reduzindo o tamanho da imagem para (3740, 2060), uma perda de aproximadamente 7,2% dos pixels totais da imagem, sem considerar operações de downsampling.
Mesmo com arquiteturas profundas, perder pixels poderia ter um efeito grande. Um CNN com apenas 4 camadas de convolução aplicadas usado em uma imagem no conjunto de dados MNIST com tamanho (28, 28) resultaria na perda de 8 linhas e colunas de pixels, reduzindo seu tamanho para (20, 20), uma perda de 57,1% de seus pixels totais, o que é considerável.
Como as operações de convolução ocorrem da esquerda para a direita e do topo para baixo, os pixels são perdidos nas bordas direita e inferior. Portanto, é seguro dizer que convolução resulta na perda de pixels de borda, pixels que podem conter features essenciais para a tarefa de visão computacional abordada.
Padding como Solução
Já que sabemos que os pixels estão destinados a serem perdidos após a convolução, podemos antecipar isso adicionando pixels anteriormente. Por exemplo, se um filtro (3, 3) for usado, poderíamos adicionar 2 linhas e 2 colunas de pixels à imagem anteriormente, de modo que quando a convolução for feita, o tamanho da imagem seja o mesmo que a imagem original.
Vamos ficar um pouco matemático novamente…
tamanho da imagem = (x, y)
tamanho do filtro = (m, n)
tamanho da imagem após o preenchimento = (x+2, y+2)
usando a equação ==> (x-m+1, y-n+1)
tamanho da imagem após a convolução (3, 3) = (x+2-3+1, y+2-3+1) = (x, y)
Preenchimento em Termos de Camada
Como estamos lidando com tipos de dados numéricos, faz sentido que o valor dos pixels adicionais também seja numérico. O valor comum adotado é um valor de pixel zero, daí o termo ‘preenchimento zero’ ser frequentemente usado.
O ponto fraco de adicionar linhas e colunas de pixels a uma matriz de imagem antecipadamente é que isso precisa ser feito uniformemente em ambos os lados. Por exemplo, ao adicionar 2 linhas e 2 colunas de pixels, elas devem ser adicionadas como uma linha no topo, uma linha na parte inferior, uma coluna à esquerda e uma coluna à direita.
Observando a imagem abaixo, 2 linhas e 2 colunas foram adicionadas para preencher a matriz de 6 x 6 uns à esquerda, enquanto 4 linhas e 4 colunas foram adicionadas à direita. As linhas e colunas adicionais foram distribuídas uniformemente ao longo de todas as bordas, conforme declarado no parágrafo anterior.
Ao analisarmos as matrizes, na esquerda, parece que a matriz 6 x 6 deuns foi envolvida numa camada única de zeros, portanto, padding=1. Por outro lado, a matriz da direita parece ter sido envolvida em duas camadas de zeros, portanto, padding=2.
Camadas de zeros adicionadas por padding.
Juntando tudo isto, podemos dizer que quando se quer adicionar 2 linhas e 2 colunas de pixeis em preparação para uma convolução (3, 3), é necessário um único nível de padding. Da mesma forma, se for necessário adicionar 6 linhas e 6 colunas de pixeis em preparação para uma convolução (7, 7), é necessário 3 níveis de padding. Em termos técnicos,
Dada um filtro de tamanho (m, n), é necessário (m-1)/2 níveis de padding para manter o tamanho da imagem igual depois da convolução; desde que m=n e m seja um número ímpar.
O Processo de Padding
Para demonstrar o processo de padding, escrevi algum código básico para replicar o processo de padding e convolução.
Primeiro, vamos olhar para a função de padding abaixo, a função aceita uma imagem como parâmetro com um padding padrão de 2. Quando o parâmetro de exibição é deixado como Verdadeiro, a função gera um relatório mini mostrando o tamanho da imagem original e do padding; uma plot de ambas as imagens é também retornado.
Função de preenchimento.
Para testar a função de preenchimento, considere a imagem abaixo de tamanho (375, 500). Passando esta imagem pela função de preenchimento com padding=2 deveria resultar na mesma imagem com dois columnas de zeros a esquerda e direita e duas linhas de zeros no topo e na base, aumentando o tamanho da imagem para (379, 504). Vamos ver se é o caso…
Imagem de tamanho (375, 500)
saída:
tamanho original da imagem: (375, 500)
tamanho da imagem preenchida: (379, 504)
Note a fina linha de pixels pretos ao longo dos bordes da imagem preenchida.
Função funciona! Fique livre para testar a função em qualquer imagem que você puder encontrar e ajustar parâmetros conforme necessário. Abaixo está o código simples para replicar o processo de convolução.
Função de convolução
Para o filtro escolhido, decidi usar uma matriz (5, 5) com valores de 0.01. A ideia por trás disto é que o filtro reduza as intensidades dos pixels em 99% antes de somar para produzir um único pixel. Em termos simples, este filtro deveria ter um efeito de desfoque nas imagens.
(5, 5) Filtro de Convolução
Aplicando o filtro na imagem original sem preenchimento deveria resultar em uma imagem desfocada de tamanho (371, 496), com a perda de 4 linhas e 4 colunas.
Realizando a convolução sem preenchimento
saída:
tamanho da imagem original: (375, 500)
tamanho da imagem convolvida: (371, 496)
(5, 5) convolução sem preenchimento
No entanto, quando o preenchimento é definido como verdadeiro, o tamanho da imagem permanece o mesmo.
Convolução com 2 camadas de preenchimento.
saída:
tamanho da imagem original: (375, 500)
tamanho da imagem convolvida: (375, 500)
(5, 5) convolução com preenchimento
Vamos repetir os mesmos passos, mas agora com um filtro (9, 9) desta vez…
(9, 9) filtro
Sem preenchimento, a imagem resultante diminui em tamanho…
saída:
tamanho da imagem original: (375, 500)
tamanho da imagem convolvida: (367, 492)
(9, 9) convolução sem preenchimento
Usando um filtro (9, 9), para manter o tamanho da imagem o mesmo, precisamos especificar uma camada de preenchimento de 4 (9-1/2), já que iremos adicionar 8 linhas e 8 colunas à imagem original.
saída:
tamanho da imagem original: (375, 500)
tamanho da imagem convolvida: (375, 500)
(9, 9) convolução com preenchimento
Perspectiva de PyTorch
Para fins de ilustração fácil, eu escolhi explicar os processos usando código simples acima. O mesmo processo pode ser replicado em PyTorch, lembre-se porém que a imagem resultante provavelmente sofrerá pouca ou nenhuma transformação, já que PyTorch inicializará aleatoriamente um filtro que não está projetado para nenhuma finalidade específica.
Para demonstrar isso, vamos modificar a função check_convolution() definida em uma das seções anteriores acima…
Função realiza convolução usando a classe de convolução 2D padrão de PyTorch
Note que na função eu usei a classe de convolução 2D padrão de PyTorch e o parâmetro de preenchimento da função é fornecido diretamente à classe de convolução. Agora vamos tentar diferentes filtros e ver quais são os tamanhos das imagens resultantes…
(3, 3) convolução sem preenchimento
saída:
tamanho da imagem original: torch.Size(1, 375, 500)
tamanho da imagem após convolução: torch.Size(1, 373, 498)
(3, 3) convolução com um nível de preenchimento.-
saída:
tamanho da imagem original: torch.Size(1, 375, 500)
tamanho da imagem após convolução: torch.Size(1, 375, 500)
(5, 5) convolução sem preenchimento-
tamanho original da imagem: torch.Size(1, 375, 500)
tamanho da imagem após convolução: torch.Size(1, 371, 496)
(5, 5) convolução com 2 camadas de padding-
tamanho original da imagem: torch.Size(1, 375, 500)
tamanho da imagem após convolução: torch.Size(1, 375, 500)
Como é evidente nos exemplos acima, quando a convolução é feita sem padding, a imagem resultante tem tamanho reduzido. No entanto, quando a convolução é feita com a quantidade correta de camadas de padding, a imagem resultante tem o mesmo tamanho que a imagem original.
Observações Finais
Neste artigo, conseguimos afirmar que o processo de convolução na verdade resulta em perda de pixels. Conseguimos também provar que adicionar pixels à imagem, em um processo chamado padding, antes da convolução garante que a imagem mantém seu tamanho original após a convolução.
Source:
https://www.digitalocean.com/community/tutorials/padding-in-convolutional-neural-networks