.. _sec_pooling: *Pooling* ========= Muitas vezes, conforme processamos imagens, queremos gradualmente reduzir a resolução espacial de nossas representações ocultas, agregando informações para que quanto mais alto subimos na rede, maior o campo receptivo (na entrada) ao qual cada nó oculto é sensível. Muitas vezes, nossa tarefa final faz alguma pergunta global sobre a imagem, por exemplo, *contém um gato?* Então, normalmente, as unidades de nossa camada final devem ser sensíveis para toda a entrada. Ao agregar informações gradualmente, produzindo mapas cada vez mais grosseiros, alcançamos esse objetivo de, em última análise, aprendendo uma representação global, enquanto mantém todas as vantagens das camadas convolucionais nas camadas intermediárias de processamento. Além disso, ao detectar recursos de nível inferior, como bordas (conforme discutido em :numref:`sec_conv_layer`), frequentemente queremos que nossas representações sejam um tanto invariáveis ​​à tradução. Por exemplo, se pegarmos a imagem ``X`` com uma delimitação nítida entre preto e branco e deslocarmos a imagem inteira em um pixel para a direita, ou seja, ``Z [i, j] = X [i, j + 1]``, então a saída para a nova imagem ``Z`` pode ser muito diferente. A borda terá deslocado um pixel. Na realidade, os objetos dificilmente ocorrem exatamente no mesmo lugar. Na verdade, mesmo com um tripé e um objeto estacionário, a vibração da câmera devido ao movimento do obturador pode mudar tudo em um pixel ou mais (câmeras de última geração são carregadas com recursos especiais para resolver esse problema). Esta seção apresenta *camadas de pooling*, que servem ao duplo propósito de mitigando a sensibilidade das camadas convolucionais à localização e de representações de *downsampling* espacialmente. *Pooling* Máximo e *Pooling* Médio ---------------------------------- Como camadas convolucionais, operadores de *pooling* consistem em uma janela de formato fixo que é deslizada todas as regiões na entrada de acordo com seu passo, computando uma única saída para cada local percorrido pela janela de formato fixo (também conhecida como *janela de pooling*). No entanto, ao contrário do cálculo de correlação cruzada das entradas e grãos na camada convolucional, a camada de *pooling* não contém parâmetros (não há *kernel*). Em vez disso, os operadores de *pooling* são determinísticos, normalmente calculando o valor máximo ou médio dos elementos na janela de *pooling*. Essas operações são chamadas de *pooling máximo* (*pooling máximo* para breve) e *pooling médio*, respectivamente. Em ambos os casos, como com o operador de correlação cruzada, podemos pensar na janela de *pooling* começando da parte superior esquerda do tensor de entrada e deslizando pelo tensor de entrada da esquerda para a direita e de cima para baixo. Em cada local que atinge a janela de *pooling*, ele calcula o máximo ou o médio valor do subtensor de entrada na janela, dependendo se o *pooling* máximo ou médio é empregado. .. _fig_pooling: .. figure:: ../img/pooling.svg Pooling máximo com uma forma de janela de pool de :math:`2\times 2`. As partes sombreadas são o primeiro elemento de saída, bem como os elementos tensores de entrada usados para o cálculo de saída: :math:`\max(0, 1, 3, 4)=4`. O tensor de saída em :numref:`fig_pooling` tem uma altura de 2 e uma largura de 2. Os quatro elementos são derivados do valor máximo em cada janela de *pooling*: .. math:: \max(0, 1, 3, 4)=4,\\ \max(1, 2, 4, 5)=5,\\ \max(3, 4, 6, 7)=7,\\ \max(4, 5, 7, 8)=8.\\ Uma camada de *pooling* com uma forma de janela de pool de :math:`p \times q` é chamado de camada de *pooling* :math:`p \times q` A operação de *pooling* é chamada :math:`p \times q` *pooling*. Vamos retornar ao exemplo de detecção de borda do objeto mencionado no início desta seção. Agora vamos usar a saída da camada convolucional como entrada para :math:`2\times 2` *pooling* máximo. Defina a entrada da camada convolucional como ``X`` e a saída da camada de pooling como\ ``Y``. Se os valores de ``X [i, j]`` e ``X [i, j + 1]`` são ou não diferentes, ou ``X [i, j + 1]`` e ``X [i, j + 2]`` são diferentes, a camada de pool sempre produz ``Y [i, j] = 1``. Ou seja, usando a camada de pooling máxima :math:`2\times 2` ainda podemos detectar se o padrão reconhecido pela camada convolucional move no máximo um elemento em altura ou largura. No código abaixo, implementamos a propagação direta da camada de *pooling* na função ``pool2d``. Esta função é semelhante à função ``corr2d`` in: numref: ``sec_conv_layer``. No entanto, aqui não temos *kernel*, computando a saída como o máximo ou a média de cada região na entrada. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python from mxnet import np, npx from mxnet.gluon import nn from d2l import mxnet as d2l npx.set_np() def pool2d(X, pool_size, mode='max'): p_h, p_w = pool_size Y = np.zeros((X.shape[0] - p_h + 1, X.shape[1] - p_w + 1)) for i in range(Y.shape[0]): for j in range(Y.shape[1]): if mode == 'max': Y[i, j] = X[i: i + p_h, j: j + p_w].max() elif mode == 'avg': Y[i, j] = X[i: i + p_h, j: j + p_w].mean() return Y .. raw:: html
.. raw:: html
.. code:: python import torch from torch import nn from d2l import torch as d2l def pool2d(X, pool_size, mode='max'): p_h, p_w = pool_size Y = torch.zeros((X.shape[0] - p_h + 1, X.shape[1] - p_w + 1)) for i in range(Y.shape[0]): for j in range(Y.shape[1]): if mode == 'max': Y[i, j] = X[i: i + p_h, j: j + p_w].max() elif mode == 'avg': Y[i, j] = X[i: i + p_h, j: j + p_w].mean() return Y .. raw:: html
.. raw:: html
.. code:: python import tensorflow as tf def pool2d(X, pool_size, mode='max'): p_h, p_w = pool_size Y = tf.Variable(tf.zeros((X.shape[0] - p_h + 1, X.shape[1] - p_w +1))) for i in range(Y.shape[0]): for j in range(Y.shape[1]): if mode == 'max': Y[i, j].assign(tf.reduce_max(X[i: i + p_h, j: j + p_w])) elif mode =='avg': Y[i, j].assign(tf.reduce_mean(X[i: i + p_h, j: j + p_w])) return Y .. raw:: html
.. raw:: html
Podemos construir o tensor de entrada ``X`` em :numref:`fig_pooling` para validar a saída da camada de *pooling* máximo bidimensional. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python X = np.array([[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]]) pool2d(X, (2, 2)) .. parsed-literal:: :class: output array([[4., 5.], [7., 8.]]) .. raw:: html
.. raw:: html
.. code:: python X = torch.tensor([[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]]) pool2d(X, (2, 2)) .. parsed-literal:: :class: output tensor([[4., 5.], [7., 8.]]) .. raw:: html
.. raw:: html
.. code:: python X = tf.constant([[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]]) pool2d(X, (2, 2)) .. parsed-literal:: :class: output .. raw:: html
.. raw:: html
Além disso, experimentamos a camada de *pooling* média. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python pool2d(X, (2, 2), 'avg') .. parsed-literal:: :class: output array([[2., 3.], [5., 6.]]) .. raw:: html
.. raw:: html
.. code:: python pool2d(X, (2, 2), 'avg') .. parsed-literal:: :class: output tensor([[2., 3.], [5., 6.]]) .. raw:: html
.. raw:: html
.. code:: python pool2d(X, (2, 2), 'avg') .. parsed-literal:: :class: output .. raw:: html
.. raw:: html
Preenchimento e Passos ---------------------- Tal como acontece com as camadas convolucionais, camadas de *pooling* também podem alterar a forma de saída. E como antes, podemos alterar a operação para obter uma forma de saída desejada preenchendo a entrada e ajustando o passo. Podemos demonstrar o uso de preenchimento e passos em camadas de agrupamento por meio da camada de agrupamento máximo bidimensional integrada do framework de *deep learning*. Primeiro construímos um tensor de entrada ``X`` cuja forma tem quatro dimensões, onde o número de exemplos (tamanho do lote) e o número de canais são ambos 1. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python X = np.arange(16, dtype=np.float32).reshape((1, 1, 4, 4)) X .. parsed-literal:: :class: output array([[[[ 0., 1., 2., 3.], [ 4., 5., 6., 7.], [ 8., 9., 10., 11.], [12., 13., 14., 15.]]]]) .. raw:: html
.. raw:: html
.. code:: python X = torch.arange(16, dtype=torch.float32).reshape((1, 1, 4, 4)) X .. parsed-literal:: :class: output tensor([[[[ 0., 1., 2., 3.], [ 4., 5., 6., 7.], [ 8., 9., 10., 11.], [12., 13., 14., 15.]]]]) .. raw:: html
.. raw:: html
É importante notar que o *tensorflow* prefere e é otimizado para *as últimas* entradas dos canais. .. code:: python X = tf.reshape(tf.range(16, dtype=tf.float32), (1, 4, 4, 1)) X .. parsed-literal:: :class: output .. raw:: html
.. raw:: html
Por padrão, o passo e a janela de *pooling* na instância da classe interna do *framework* têm a mesma forma. Abaixo, usamos uma janela de *pooling* de forma ``(3, 3)``, portanto, obtemos uma forma de passo de ``(3, 3)`` por padrão. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python pool2d = nn.MaxPool2D(3) # Because there are no model parameters in the pooling layer, we do not need # to call the parameter initialization function pool2d(X) .. parsed-literal:: :class: output array([[[[10.]]]]) .. raw:: html
.. raw:: html
.. code:: python pool2d = nn.MaxPool2d(3) pool2d(X) .. parsed-literal:: :class: output tensor([[[[10.]]]]) .. raw:: html
.. raw:: html
.. code:: python pool2d = tf.keras.layers.MaxPool2D(pool_size=[3, 3]) pool2d(X) .. parsed-literal:: :class: output .. raw:: html
.. raw:: html
O passo e o preenchimento podem ser especificados manualmente. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python pool2d = nn.MaxPool2D(3, padding=1, strides=2) pool2d(X) .. parsed-literal:: :class: output array([[[[ 5., 7.], [13., 15.]]]]) Claro, podemos especificar uma janela de *pooling* retangular arbitrária e especificar o preenchimento e o passo para altura e largura, respectivamente. .. raw:: html
.. raw:: html
.. code:: python pool2d = nn.MaxPool2d(3, padding=1, stride=2) pool2d(X) .. parsed-literal:: :class: output tensor([[[[ 5., 7.], [13., 15.]]]]) .. raw:: html
.. raw:: html
.. code:: python paddings = tf.constant([[0, 0], [1,0], [1,0], [0,0]]) X_padded = tf.pad(X, paddings, "CONSTANT") pool2d = tf.keras.layers.MaxPool2D(pool_size=[3, 3], padding='valid', strides=2) pool2d(X_padded) .. parsed-literal:: :class: output .. raw:: html
.. raw:: html
Claro, podemos especificar uma janela de *pooling* retangular arbitrária e especificar o preenchimento e o passo para altura e largura, respectivamente. Para ``nn.MaxPool2D``, o preenchimento deve ser menor que a metade do kernel_size. Se a condição não for atendida, podemos primeiro preenchemos a entrada usando ``nn.functional.pad`` e, em seguida, o passamos para a camada de *pooling*. Claro, podemos especificar uma janela de *pooling* retangular arbitrária e especificar o preenchimento e o passo para altura e largura, respectivamente. No TensorFlow, para implementar um preenchimento de 1 em todo o tensor, uma função projetada para preenchimento deve ser invocada usando ``tf.pad``. Isso implementará o preenchimento necessário e permitirá que o supracitado (3, 3) agrupamento com uma passada (2, 2) para realizar semelhantes aos do PyTorch e MXNet. Ao preencher desta forma, a variável embutida ``padding`` deve ser definida como ``válida``. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python pool2d = nn.MaxPool2D((2, 3), padding=(1, 2), strides=(2, 3)) pool2d(X) .. parsed-literal:: :class: output array([[[[ 0., 3.], [ 8., 11.], [12., 15.]]]]) .. raw:: html
.. raw:: html
.. code:: python X_pad = nn.functional.pad(X, (2, 2, 1, 1)) pool2d = nn.MaxPool2d((2, 3), stride=(2, 3)) pool2d(X_pad) .. parsed-literal:: :class: output tensor([[[[ 0., 3.], [ 8., 11.], [12., 15.]]]]) .. raw:: html
.. raw:: html
.. code:: python paddings = tf.constant([[0, 0], [1, 1], [2, 1], [0, 0]]) X_padded = tf.pad(X, paddings, "CONSTANT") pool2d = tf.keras.layers.MaxPool2D(pool_size=[2, 3], padding='valid', strides=(2,3)) pool2d(X_padded) .. parsed-literal:: :class: output .. raw:: html
.. raw:: html
Canais Múltiplos ---------------- Ao processar dados de entrada multicanal, a camada de *pooling* agrupa cada canal de entrada separadamente, em vez de somar as entradas nos canais como em uma camada convolucional. Isso significa que o número de canais de saída para a camada de *pooling* é igual ao número de canais de entrada. Abaixo, vamos concatenar os tensores ``X`` e\ ``X + 1`` na dimensão do canal para construir uma entrada com 2 canais. Observe que isso exigirá um concatenação ao longo da última dimensão do TensorFlow devido à sintaxe dos últimos canais. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python X = np.concatenate((X, X + 1), 1) X .. parsed-literal:: :class: output array([[[[ 0., 1., 2., 3.], [ 4., 5., 6., 7.], [ 8., 9., 10., 11.], [12., 13., 14., 15.]], [[ 1., 2., 3., 4.], [ 5., 6., 7., 8.], [ 9., 10., 11., 12.], [13., 14., 15., 16.]]]]) .. raw:: html
.. raw:: html
.. code:: python X = torch.cat((X, X + 1), 1) X .. parsed-literal:: :class: output tensor([[[[ 0., 1., 2., 3.], [ 4., 5., 6., 7.], [ 8., 9., 10., 11.], [12., 13., 14., 15.]], [[ 1., 2., 3., 4.], [ 5., 6., 7., 8.], [ 9., 10., 11., 12.], [13., 14., 15., 16.]]]]) .. raw:: html
.. raw:: html
.. code:: python X = tf.concat([X, X + 1], 3) # Concatenate along `dim=3` due to channels-last syntax .. raw:: html
.. raw:: html
Como podemos ver, o número de canais de saída ainda é 2 após o *pooling*. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python pool2d = nn.MaxPool2D(3, padding=1, strides=2) pool2d(X) .. parsed-literal:: :class: output array([[[[ 5., 7.], [13., 15.]], [[ 6., 8.], [14., 16.]]]]) .. raw:: html
.. raw:: html
.. code:: python pool2d = nn.MaxPool2d(3, padding=1, stride=2) pool2d(X) .. parsed-literal:: :class: output tensor([[[[ 5., 7.], [13., 15.]], [[ 6., 8.], [14., 16.]]]]) .. raw:: html
.. raw:: html
.. code:: python paddings = tf.constant([[0, 0], [1,0], [1,0], [0,0]]) X_padded = tf.pad(X, paddings, "CONSTANT") pool2d = tf.keras.layers.MaxPool2D(pool_size=[3, 3], padding='valid', strides=2) pool2d(X_padded) .. parsed-literal:: :class: output .. raw:: html
.. raw:: html
Observe que a saída para o *pooling* de tensorflow parece à primeira vista ser diferente, no entanto numericamente, os mesmos resultados são apresentados como MXNet e PyTorch. A diferença está na dimensionalidade, e na leitura do a saída verticalmente produz a mesma saída que as outras implementações. Resumo ------ - Pegando os elementos de entrada na janela de agrupamento, a operação de agrupamento máxima atribui o valor máximo como a saída e a operação de agrupamento média atribui o valor médio como a saída. - Um dos principais benefícios de uma camada de *pooling* é aliviar a sensibilidade excessiva da camada convolucional ao local. - Podemos especificar o preenchimento e a passada para a camada de *pooling*. - O agrupamento máximo, combinado com uma passada maior do que 1, pode ser usado para reduzir as dimensões espaciais (por exemplo, largura e altura). - O número de canais de saída da camada de *pooling* é igual ao número de canais de entrada. Exercícios ---------- 1. Você pode implementar o *pooling* médio como um caso especial de uma camada de convolução? Se sim, faça. 2. Você pode implementar o *pooling* máximo como um caso especial de uma camada de convolução? Se for assim, faça. 3. Qual é o custo computacional da camada de *pooling*? Suponha que a entrada para a camada de *pooling* seja do tamanho :math:`c\times h\times w`, a janela de *pooling* tem um formato de :math:`p_h\times p_w` com um preenchimento de :math:`(p_h, p_w)` e um passo de :math:`(s_h, s_w)`. 4. Por que você espera que o *pooling* máximo e o *pooling* médio funcionem de maneira diferente? 5. Precisamos de uma camada mínima de *pooling* separada? Você pode substituí-la por outra operação? 6. Existe outra operação entre o *pooling* médio e máximo que você possa considerar (dica: lembre-se do *softmax*)? Por que não é tão popular? .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
`Discussions `__ .. raw:: html
.. raw:: html
`Discussions `__ .. raw:: html
.. raw:: html
`Discussions `__ .. raw:: html
.. raw:: html
.. raw:: html