Em uma CNN, cada novo filtro possui pesos diferentes para cada canal de entrada ou os mesmos pesos de cada filtro são usados ​​nos canais de entrada?

28

Meu entendimento é que a camada convolucional de uma rede neural convolucional tem quatro dimensões: input_channels, filter_height, filter_width, number_of_filters. Além disso, entendo que cada novo filtro é complicado por TODOS os canais de entrada (ou mapas de recursos / ativação da camada anterior).

NO ENTANTO, o gráfico abaixo do CS231 mostra cada filtro (em vermelho) sendo aplicado a um CANAL ÚNICO, em vez do mesmo filtro sendo usado nos canais. Isso parece indicar que existe um filtro separado para cada canal (neste caso, estou assumindo que sejam os três canais de cores de uma imagem de entrada, mas o mesmo se aplicaria a todos os canais de entrada).

Isso é confuso - existe um filtro exclusivo diferente para cada canal de entrada?

insira a descrição da imagem aqui

Fonte: http://cs231n.github.io/convolutional-networks/

A imagem acima parece contraditória a um trecho de "Fundamentos da aprendizagem profunda" de O'reilly :

"... os filtros não operam apenas em um único mapa de recursos. Eles operam em todo o volume de mapas de recursos que foram gerados em uma camada específica ... Como resultado, os mapas de recursos devem poder operar sobre volumes, não apenas áreas "

... Além disso, entendo que essas imagens abaixo estão indicando que o filtro THE MESMO está apenas envolvido em todos os três canais de entrada (contraditório com o que é mostrado no gráfico CS231 acima):

insira a descrição da imagem aqui

insira a descrição da imagem aqui

Ryan Chase
fonte
1
arxiv.org/pdf/1707.09725 capítulo 2
Martin Thoma

Respostas:

13

Em uma rede neural convolucional, existe um filtro exclusivo para cada canal de entrada ou os mesmos novos filtros são usados ​​em todos os canais de entrada?

O antigo. De fato, existe um kernel separado definido para cada combinação de canal de entrada / canal de saída.

Normalmente, para uma arquitetura CNN, em um único filtro, conforme descrito por seu number_of_filtersparâmetro, há um kernel 2D por canal de entrada. Existem input_channels * number_of_filtersconjuntos de pesos, cada um dos quais descreve um núcleo de convolução. Portanto, os diagramas que mostram um conjunto de pesos por canal de entrada para cada filtro estão corretos. O primeiro diagrama também mostra claramente que os resultados da aplicação desses kernels são combinados, somando-os e adicionando viés para cada canal de saída.

Isso também pode ser visto como o uso de uma convolução 3D para cada canal de saída, que possui a mesma profundidade da entrada. Qual é o que seu segundo diagrama está mostrando e também o que muitas bibliotecas farão internamente. Matematicamente, esse é o mesmo resultado (desde que as profundidades correspondam exatamente), embora o tipo de camada seja normalmente rotulado como "Conv2D" ou similar. Da mesma forma, se o seu tipo de entrada é inerentemente 3D, como voxels ou um vídeo, você pode usar uma camada "Conv3D", mas internamente ela pode ser implementada como uma convolução 4D.

Neil Slater
fonte
obrigado por esta explicação. Parece que cada filtro realmente tem um número de input_channelsversões com pesos diferentes . Você tem uma fonte "oficial" que confirma esse entendimento?
Ryan perseguição
@RyanChase: Sim, isso está correto. Gostaria apenas de apontar no campo de Andrew Ng em CNNs - começando aqui com a forma como imagem colorida seria processado: coursera.org/learn/convolutional-neural-networks/lecture/ctQZz/...
Neil Slater
Gostaria de observar que, nessa fonte ( cs231n.github.io/convolutional-networks ), os filtros (pesos ou kernesl) são volumes (isto é, tridimensionais) e têm a mesma 3ª dimensão que a entrada volume. Além disso, como é (pelo menos) agora declarado nessa fonte, os volumes foram divididos na 3ª dimensão, a fim de visualizar melhor a aplicação do filtro ao volume de entrada. Eu não acho que, em geral, "haja um kernel separado definido para cada combinação de canal de entrada / canal de saída". está correto.
nbro
Observe que os filtros (ou núcleos) são os pesos que precisam ser aprendidos (ou seja, eles não são fixos, mas na verdade são os parâmetros da CNN). Pode ser que eles sejam (ou seja, as fatias do filtro), no final, os mesmos na terceira dimensão.
nbro
@ nbro: Sim, você pode implementar uma convolução 2D em várias fatias 2D como uma única convolução 3D com a profundidade do kernel igual ao número de canais. Matematicamente, isso é idêntico à minha descrição. Você também pode visualizá-lo como uma rede de feed forward truncada e totalmente conectada com pesos compartilhados (muitos dos quais são zero). Esta resposta se concentra em qual é a visão dos filtros 2D, porque o OP está perguntando sobre como os filtros 2D são organizados. Eles podem de fato ser organizados em um núcleo 3D maior, mas ainda são aplicados como núcleos 2D usando o "truque" de que a convolução 3D é equivalente.
Neil Slater
12

A figura a seguir que você usou na sua pergunta descreve com muita precisão o que está acontecendo. Lembre-se de que cada elemento do filtro 3D (cubo cinza) é composto de um valor diferente ( 3x3x3=27valores). Assim, três filtros 2D diferentes de tamanho 3x3podem ser concatenados para formar esse filtro 3D de tamanho 3x3x3.

convnet2D

O 3x3x3pedaço RGB da imagem é multiplicado por elementos por um filtro 3D (mostrado em cinza). Nesse caso, o filtro possui 3x3x3=27pesos. Quando esses pesos são multiplicados por elementos e depois somados, ele fornece um valor.


Então, existe um filtro separado para cada canal de entrada?

SIM , existem tantos filtros 2D quanto o número de canais de entrada na imagem. No entanto , ajuda se você acha que, para matrizes de entrada com mais de um canal, existe apenas um filtro 3D (como mostrado na imagem acima).


Então, por que isso é chamado de convolução 2D (se o filtro é 3D e a matriz de entrada é 3D)?

Essa é uma convolução 2D, porque os passos do filtro são apenas das dimensões de altura e largura ( NÃO profundidade) e, portanto, a saída produzida por essa convolução também é uma matriz 2D. O número de direções de movimento do filtro determina as dimensões da convolução.

Nota: Se você desenvolver seu entendimento visualizando um único filtro 3D em vez de vários filtros 2D (um para cada camada), terá um tempo fácil para entender arquiteturas avançadas da CNN, como Resnet, InceptionV3, etc.

Mohsin Bukhari
fonte
Essa é uma boa explicação, mas, mais especificamente, a pergunta que estou tentando entender é se os filtros que operam em cada canal de entrada são cópias dos mesmos pesos ou pesos completamente diferentes. Na verdade, isso não é mostrado na imagem e, de fato, para mim, essa imagem sugere que são os mesmos pesos aplicados a cada canal (já que são da mesma cor) ... Por resposta do @neil slater, parece que cada filtro tem realmente um número de input_channelsversões com pesos diferentes . Se esse também é o seu entendimento, existe uma fonte "oficial" que confirme isso?
Ryan perseguição
Sim, de fato, esse também é o meu entendimento. Para mim, ficou claro quando tentei pensar naquele cubo cinza composto por 27 valores de peso diferentes. Isso significa que existem 3 filtros 2D diferentes, em vez do mesmo filtro 2D aplicado a cada camada de entrada.
Mohsin Bukhari 23/03
Não encontrei nenhuma fonte oficial para confirmar isso. No entanto, quando eu estava tentando entender esse mesmo conceito, criei um filtro fictício de entrada e peso no Tensorflow e observei a saída. Eu estava contente com isso. Se eu encontrar alguma explicação oficial . Vou editar minha resposta acima.
Mohsin Bukhari 23/03
Se você seguir o caminho do fluxo de tensão. Você pode imprimir seu filtro de peso depois de mostrar à sua camada CNN fictícia uma amostra de entrada.
Mohsin Bukhari 23/03
@Moshsin Bukhari Definitivamente tentarei explorar os filtros no TensorFlow. Você gostaria de compartilhar seu código sobre como explorou o que está contido nos filtros? Você consegue imprimir os valores do filtro em cada etapa da rede, por exemplo?
Ryan Chase
3

Estou acompanhando as respostas acima com um exemplo concreto, na esperança de esclarecer melhor como a convolução funciona com relação aos canais de entrada e saída e aos pesos, respectivamente:

Seja o exemplo da seguinte forma (wrt para 1 camada convolucional):

  • o tensor de entrada é 9x9x5, ou seja, 5 canais de entrada, então input_channels=5
  • o tamanho do filtro / kernel é 4x4 e o passo é 1
  • o tensor de saída é 6x6x56, ou seja, 56 canais de saída, então output_channels=56
  • o tipo de preenchimento é 'VÁLIDO' (ou seja, sem preenchimento)

Nós notamos que:

  • como a entrada possui 5 canais, a dimensão do filtro se torna 4x4x5, ou seja, existem 5 filtros 2D únicos e separados, do tamanho 4x4 (ou seja, cada um tem 16 pesos); para convolver sobre a entrada do tamanho 9x9x5, o filtro torna-se 3D e deve ser do tamanho 4x4x5
  • portanto: para cada canal de entrada, existe um filtro 2D distinto com 16 pesos diferentes cada. Em outras palavras, o número de filtros 2D corresponde ao número de canais de entrada
  • Como existem 56 canais de saída, deve haver 56 filtros tridimensionais W0, W1, ..., W55 de tamanho 4x4x5 (conforme gráfico CS231, existem 2 filtros tridimensionais W0, W1 para contabilizar as 2 saídas canais), onde a 3ª dimensão do tamanho 5 representa o link para os 5 canais de entrada (consulte no gráfico CS231 cada filtro 3D W0, W1 tem a 3ª dimensão 3, que corresponde aos 3 canais de entrada)
  • portanto: o número de filtros 3D é igual ao número de canais de saída

Essa camada convolucional contém assim:

56 filtros tridimensionais de tamanho 4x4x5 (= 80 pesos diferentes cada) para contabilizar os 56 canais de saída em que cada um tem um valor para a 3ª dimensão de 5 para corresponder aos 5 canais de entrada. No total, existem

number_of_filters=input_channel*output_channels=5*56=280

Filtros 2D de tamanho 4x4 (ou seja, 280x16 pesos diferentes no total).

Lukas Z.
fonte
0

Existem apenas restrições em 2D. Por quê?

Imagine uma camada totalmente conectada.

Seria muito grande, cada neurônio estaria conectado a talvez 1000x1000x3 neurônios de entrada. Mas sabemos que o processamento de pixels próximos faz sentido; portanto, nos limitamos a uma pequena vizinhança 2D, de modo que cada neurônio está conectado a apenas um 3x3 próximo de neurônios em 2D. Como não sabemos nada sobre canais, nos conectamos a todos os canais.

Ainda assim, haveria pesos demais. Mas, devido à invariância da tradução, um filtro que funcione bem em uma área provavelmente é útil em uma área diferente. Então, usamos o mesmo conjunto de pesos em 2D. Novamente, não existe essa invariância de tradução entre os canais, portanto não existe essa restrição lá.

maaartinus
fonte
0

Consulte a seção "Conectividade local" em http://cs231n.github.io/convolutional-networks/ e slide 7-18.

O hiperparâmetro "Receptive Field" do filtro é definido apenas pela altura e largura, pois a profundidade é fixada pela profundidade da camada anterior.

Observe que "A extensão da conectividade ao longo do eixo de profundidade é sempre igual à PROFUNDIDADE do volume de entrada" - ou- PROFUNDIDADE do mapa de ativação (no caso de camadas posteriores).

Intuitivamente, isso deve-se ao fato de os dados dos canais de imagem serem intercalados, não planares. Dessa forma, a aplicação do filtro pode ser obtida simplesmente pela multiplicação dos vetores de coluna.

OBSERVE que a Rede Convolucional aprende todos os parâmetros de filtro (incluindo a dimensão de profundidade) e são totais "h w input_layer_depth + 1 (viés)".

sunil4data
fonte
0

Eu recomendo o capítulo 2.2.1 da minha tese de mestrado como resposta. Para adicionar às respostas restantes:

Keras é seu amigo para entender o que acontece:

from keras.models import Sequential
from keras.layers import Conv2D

model = Sequential()
model.add(Conv2D(32, input_shape=(28, 28, 3),
          kernel_size=(5, 5),
          padding='same',
          use_bias=False))
model.add(Conv2D(17, (3, 3), padding='same', use_bias=False))
model.add(Conv2D(13, (3, 3), padding='same', use_bias=False))
model.add(Conv2D(7, (3, 3), padding='same', use_bias=False))
model.compile(loss='categorical_crossentropy', optimizer='adam')

print(model.summary())

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_1 (Conv2D)            (None, 28, 28, 32)        2400      
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 28, 28, 17)        4896      
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 28, 28, 13)        1989      
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 28, 28, 7)         819       
=================================================================
Total params: 10,104

Tente formular suas opções. O que isso significaria para os parâmetros se algo mais acontecesse?

2400=32.(355)

Essa abordagem também ajuda você com outros tipos de camada, não apenas com camadas convolucionais.

Observe também que você é livre para implementar soluções diferentes, que podem ter outros números de parâmetros.

Martin Thoma
fonte
0

Apenas para deixar dois detalhes absolutamente claros:

Diga que você tem N Canais de entrada 2D indo para NCanais de saída 2D. O número total de 2D3×3 filtro de pesos é realmente N2. Mas como a convolução 3D é afetada, ou seja, se cada canal de entrada contribui com uma camada 2D para cada canal de saída, então cada canal de saída é composto inicialmente porN Camadas 2D, como elas são combinadas?

Isso tende a ser encoberto em quase todas as publicações que eu já vi, mas o conceito principal é o N2 Os canais de saída 2D são intercalados entre si para formar o Ncanais de saída, como baralhos de cartas embaralhados, antes de serem somados. Tudo isso é lógico quando você percebe que, ao longo das dimensões do canal de uma convolução (que nunca é ilustrada), você realmente tem uma camada totalmente conectada! Cada canal 2D de entrada, multiplicado por um único3×3filtro, gera uma contribuição da camada de saída 2D para um único canal de saída. Uma vez combinadas, cada camada de saída é uma combinação de todas as camadas de entrada×um filtro exclusivo. É uma contribuição de todos para todos.

A maneira mais fácil de convencer-se disso é imaginar o que acontece em outros cenários e ver que a computação se degenera - ou seja, se você não intercala e recombina os resultados, as diferentes saídas não fazem nada de verdade - elas teria o mesmo efeito que uma única saída com pesos combinados.

user2465201
fonte
0

Para quem tenta entender como as convoluções são calculadas, aqui está um trecho de código útil no Pytorch:

batch_size = 1
height = 3 
width = 3
conv1_in_channels = 2
conv1_out_channels = 2
conv2_out_channels = 2
kernel_size = 2
# (N, C_in, H, W) is shape of all tensors. (batch_size, channels, height, width)
input = torch.Tensor(np.arange(0, batch_size*height*width*in_channels).reshape(batch_size, in_channels, height, width))
conv1 = nn.Conv2d(in_channels, conv1_out_channels, kernel_size, bias=False) # no bias to make calculations easier
# set the weights of the convolutions to make the convolutions easier to follow
nn.init.constant_(conv1.weight[0][0], 0.25)
nn.init.constant_(conv1.weight[0][1], 0.5)
nn.init.constant_(conv1.weight[1][0], 1) 
nn.init.constant_(conv1.weight[1][1], 2) 
out1 = conv1(input) # compute the convolution

conv2 = nn.Conv2d(conv1_out_channels, conv2_out_channels, kernel_size, bias=False)
nn.init.constant_(conv2.weight[0][0], 0.25)
nn.init.constant_(conv2.weight[0][1], 0.5)
nn.init.constant_(conv2.weight[1][0], 1) 
nn.init.constant_(conv2.weight[1][1], 2) 
out2 = conv2(out1) # compute the convolution

for tensor, name in zip([input, conv1.weight, out1, conv2.weight, out2], ['input', 'conv1', 'out1', 'conv2', 'out2']):
    print('{}: {}'.format(name, tensor))
    print('{} shape: {}'.format(name, tensor.shape))

A execução disso fornece a seguinte saída:

input: tensor([[[[ 0.,  1.,  2.],
          [ 3.,  4.,  5.],
          [ 6.,  7.,  8.]],

         [[ 9., 10., 11.],
          [12., 13., 14.],
          [15., 16., 17.]]]])
input shape: torch.Size([1, 2, 3, 3])
conv1: Parameter containing:
tensor([[[[0.2500, 0.2500],
          [0.2500, 0.2500]],

         [[0.5000, 0.5000],
          [0.5000, 0.5000]]],


        [[[1.0000, 1.0000],
          [1.0000, 1.0000]],

         [[2.0000, 2.0000],
          [2.0000, 2.0000]]]], requires_grad=True)
conv1 shape: torch.Size([2, 2, 2, 2])
out1: tensor([[[[ 24.,  27.],
          [ 33.,  36.]],

         [[ 96., 108.],
          [132., 144.]]]], grad_fn=<MkldnnConvolutionBackward>)
out1 shape: torch.Size([1, 2, 2, 2])
conv2: Parameter containing:
tensor([[[[0.2500, 0.2500],
          [0.2500, 0.2500]],

         [[0.5000, 0.5000],
          [0.5000, 0.5000]]],


        [[[1.0000, 1.0000],
          [1.0000, 1.0000]],

         [[2.0000, 2.0000],
          [2.0000, 2.0000]]]], requires_grad=True)
conv2 shape: torch.Size([2, 2, 2, 2])
out2: tensor([[[[ 270.]],

         [[1080.]]]], grad_fn=<MkldnnConvolutionBackward>)
out2 shape: torch.Size([1, 2, 1, 1])

Observe como cada canal da convolução soma todos os canais anteriores.

Simon Alford
fonte