As operações de pooling e convolucional deslizam uma "janela" pelo tensor de entrada. Usando tf.nn.conv2d
como exemplo: Se o tensor de entrada tem 4 dimensões [batch, height, width, channels]
:, então a convolução opera em uma janela 2D nas height, width
dimensões.
strides
determina quanto a janela muda em cada uma das dimensões. O uso típico define a primeira (o lote) e a última (a profundidade) passada em 1.
Vamos usar um exemplo muito concreto: rodar uma convolução 2-d sobre uma imagem de entrada em escala de cinza de 32x32. Eu digo tons de cinza porque a imagem de entrada tem profundidade = 1, o que ajuda a mantê-la simples. Deixe essa imagem ficar assim:
00 01 02 03 04 ...
10 11 12 13 14 ...
20 21 22 23 24 ...
30 31 32 33 34 ...
...
Vamos executar uma janela de convolução 2x2 em um único exemplo (tamanho do lote = 1). Daremos à convolução uma profundidade de canal de saída de 8.
A entrada para a convolução tem shape=[1, 32, 32, 1]
.
Se você especificar strides=[1,1,1,1]
com padding=SAME
, a saída do filtro será [1, 32, 32, 8].
O filtro criará primeiro uma saída para:
F(00 01
10 11)
E então para:
F(01 02
11 12)
e assim por diante. Em seguida, ele se moverá para a segunda linha, calculando:
F(10, 11
20, 21)
então
F(11, 12
21, 22)
Se você especificar uma distância de [1, 2, 2, 1], não haverá janelas sobrepostas. Ele irá calcular:
F(00, 01
10, 11)
e depois
F(02, 03
12, 13)
A passada opera de forma semelhante para os operadores de pool.
Pergunta 2: Por que avanços [1, x, y, 1] para convnets
O primeiro 1 é o lote: normalmente você não deseja pular os exemplos em seu lote, ou não deveria tê-los incluído em primeiro lugar. :)
O último 1 é a profundidade da convolução: geralmente você não deseja pular entradas, pelo mesmo motivo.
O operador conv2d é mais geral, então você pode criar convoluções que deslizam a janela ao longo de outras dimensões, mas esse não é um uso típico em convnets. O uso típico é usá-los espacialmente.
Por que remodelar para -1 -1 é um espaço reservado que diz "ajuste conforme necessário para corresponder ao tamanho necessário para o tensor completo". É uma maneira de fazer com que o código seja independente do tamanho do lote de entrada, de modo que você possa alterar o pipeline e não tenha que ajustar o tamanho do lote em todo o código.