.. _sec_channels:
Canais de Múltiplas Entradas e Saídas
=====================================
Embora tenhamos descrito os vários canais que compõem cada imagem (por
exemplo, imagens coloridas têm os canais RGB padrão para indicar a
quantidade de vermelho, verde e azul) e camadas convolucionais para
vários canais em :numref:`subsec_why-conv-channels`, até agora,
simplificamos todos os nossos exemplos numéricos trabalhando com apenas
uma única entrada e um único canal de saída. Isso nos permitiu pensar em
nossas entradas, *kernels* de convolução, e saídas, cada um como
tensores bidimensionais.
Quando adicionamos canais a isto, nossas entradas e representações
ocultas ambas se tornam tensores tridimensionais. Por exemplo, cada
imagem de entrada RGB tem a forma :math:`3\times h\times w`.
Referimo-nos a este eixo, com um tamanho de 3, como a dimensão do
*canal*. Nesta seção, daremos uma olhada mais detalhada em núcleos de
convolução com múltiplos canais de entrada e saída.
Canais de Entrada Múltiplos
---------------------------
Quando os dados de entrada contêm vários canais, precisamos construir um
*kernel* de convolução com o mesmo número de canais de entrada que os
dados de entrada, para que possa realizar correlação cruzada com os
dados de entrada. Supondo que o número de canais para os dados de
entrada seja :math:`c_i`, o número de canais de entrada do *kernel* de
convolução também precisa ser :math:`c_i`. Se a forma da janela do nosso
kernel de convolução é :math:`k_h\times k_w`, então quando
:math:`c_i=1`, podemos pensar em nosso kernel de convolução apenas como
um tensor bidimensional de forma :math:`k_h\times k_w`.
No entanto, quando :math:`c_i>1`, precisamos de um kernel que contém um
tensor de forma :math:`k_h\times k_w` para *cada* canal de entrada.
Concatenando estes :math:`c_i` tensores juntos produz um kernel de
convolução de forma :math:`c_i\times k_h\times k_w`. Uma vez que o
*kernel* de entrada e convolução tem cada um :math:`c_i` canais, podemos
realizar uma operação de correlação cruzada no tensor bidimensional da
entrada e o tensor bidimensional do núcleo de convolução para cada
canal, adicionando os resultados :math:`c_i` juntos (somando os canais)
para produzir um tensor bidimensional. Este é o resultado de uma
correlação cruzada bidimensional entre uma entrada multicanal e um
*kernel* de convolução com vários canais de entrada.
Em :numref:`fig_conv_multi_in`, demonstramos um exemplo de uma
correlação cruzada bidimensional com dois canais de entrada. As partes
sombreadas são o primeiro elemento de saída bem como os elementos
tensores de entrada e kernel usados para o cálculo de saída:
:math:`(1\times1+2\times2+4\times3+5\times4)+(0\times0+1\times1+3\times2+4\times3)=56`.
.. _fig_conv_multi_in:
.. figure:: ../img/conv-multi-in.svg
Cálculo de correlação cruzada com 2 canais de entrada.
Para ter certeza de que realmente entendemos o que está acontecendo
aqui, podemos implementar operações de correlação cruzada com vários
canais de entrada. Observe que tudo o que estamos fazendo é realizar uma
operação de correlação cruzada por canal e depois somando os resultados.
.. raw:: html
.. raw:: html
.. code:: python
from mxnet import np, npx
from d2l import mxnet as d2l
npx.set_np()
def corr2d_multi_in(X, K):
# First, iterate through the 0th dimension (channel dimension) of `X` and
# `K`. Then, add them together
return sum(d2l.corr2d(x, k) for x, k in zip(X, K))
.. raw:: html
.. raw:: html
.. code:: python
import torch
from d2l import torch as d2l
def corr2d_multi_in(X, K):
# First, iterate through the 0th dimension (channel dimension) of `X` and
# `K`. Then, add them together
return sum(d2l.corr2d(x, k) for x, k in zip(X, K))
.. raw:: html
.. raw:: html
.. code:: python
import tensorflow as tf
from d2l import tensorflow as d2l
def corr2d_multi_in(X, K):
# First, iterate through the 0th dimension (channel dimension) of `X` and
# `K`. Then, add them together
return tf.reduce_sum([d2l.corr2d(x, k) for x, k in zip(X, K)], axis=0)
.. raw:: html
.. raw:: html
Podemos construir o tensor de entrada ``X`` e o tensor do kernel\ ``K``
correspondendo aos valores em :numref:`fig_conv_multi_in` para validar
a saída da operação de correlação cruzada.
.. raw:: html
.. 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]],
[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]])
K = np.array([[[0.0, 1.0], [2.0, 3.0]], [[1.0, 2.0], [3.0, 4.0]]])
corr2d_multi_in(X, K)
.. parsed-literal::
:class: output
array([[ 56., 72.],
[104., 120.]])
.. 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]],
[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]])
K = torch.tensor([[[0.0, 1.0], [2.0, 3.0]], [[1.0, 2.0], [3.0, 4.0]]])
corr2d_multi_in(X, K)
.. parsed-literal::
:class: output
tensor([[ 56., 72.],
[104., 120.]])
.. 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]],
[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]])
K = tf.constant([[[0.0, 1.0], [2.0, 3.0]], [[1.0, 2.0], [3.0, 4.0]]])
corr2d_multi_in(X, K)
.. parsed-literal::
:class: output
.. raw:: html
.. raw:: html
Canais de Saída Múltiplos
-------------------------
Independentemente do número de canais de entrada, até agora acabamos
sempre com um canal de saída. No entanto, como discutimos em
:numref:`subsec_why-conv-channels`, é essencial ter vários canais em
cada camada. Nas arquiteturas de rede neural mais populares, na verdade,
aumentamos a dimensão do canal à medida que subimos na rede neural,
normalmente reduzindo as amostras para compensar a resolução espacial
para maior *profundidade do canal*. Intuitivamente, você pode pensar em
cada canal como respondendo a algum conjunto diferente de *features*. A
realidade é um pouco mais complicada do que as interpretações mais
ingênuas dessa intuição, uma vez que as representações não são
aprendidas de forma independente, mas sim otimizadas para serem úteis em
conjunto. Portanto, pode não ser que um único canal aprenda um detector
de bordas, mas sim que alguma direção no espaço do canal corresponde à
detecção de bordas.
Denote por :math:`c_i` e :math:`c_o` o número dos canais de entrada e
saída, respectivamente, e sejam :math:`k_h` e :math:`k_w` a altura e a
largura do *kernel*. Para obter uma saída com vários canais, podemos
criar um tensor de kernel da forma :math:`c_i\times k_h\times k_w` para
*cada* canal de saída. Nós os concatenamos na dimensão do canal de
saída, de modo que a forma do núcleo de convolução é
:math:`c_o\times c_i\times k_h\times k_w`. Em operações de correlação
cruzada, o resultado em cada canal de saída é calculado do *kernel* de
convolução correspondente a esse canal de saída e recebe a entrada de
todos os canais no tensor de entrada.
Implementamos uma função de correlação cruzada para calcular a saída de
vários canais, conforme mostrado abaixo.
.. raw:: html
.. raw:: html
.. code:: python
def corr2d_multi_in_out(X, K):
# Iterate through the 0th dimension of `K`, and each time, perform
# cross-correlation operations with input `X`. All of the results are
# stacked together
return np.stack([corr2d_multi_in(X, k) for k in K], 0)
.. raw:: html
.. raw:: html
.. code:: python
def corr2d_multi_in_out(X, K):
# Iterate through the 0th dimension of `K`, and each time, perform
# cross-correlation operations with input `X`. All of the results are
# stacked together
return torch.stack([corr2d_multi_in(X, k) for k in K], 0)
.. raw:: html
.. raw:: html
.. code:: python
def corr2d_multi_in_out(X, K):
# Iterate through the 0th dimension of `K`, and each time, perform
# cross-correlation operations with input `X`. All of the results are
# stacked together
return tf.stack([corr2d_multi_in(X, k) for k in K], 0)
.. raw:: html
.. raw:: html
Construímos um *kernel* de convolução com 3 canais de saída concatenando
o tensor do kernel ``K`` com\ ``K + 1`` (mais um para cada elemento em
``K``) e\ ``K + 2``.
.. raw:: html
.. raw:: html
.. code:: python
K = np.stack((K, K + 1, K + 2), 0)
K.shape
.. parsed-literal::
:class: output
(3, 2, 2, 2)
.. raw:: html
.. raw:: html
.. code:: python
K = torch.stack((K, K + 1, K + 2), 0)
K.shape
.. parsed-literal::
:class: output
torch.Size([3, 2, 2, 2])
.. raw:: html
.. raw:: html
.. code:: python
K = tf.stack((K, K + 1, K + 2), 0)
K.shape
.. parsed-literal::
:class: output
TensorShape([3, 2, 2, 2])
.. raw:: html
.. raw:: html
Abaixo, realizamos operações de correlação cruzada no tensor de entrada
``X`` com o tensor do kernel\ ``K``. Agora a saída contém 3 canais. O
resultado do primeiro canal é consistente com o resultado do tensor de
entrada anterior ``X`` e o canal de múltiplas entradas, *kernel* do
canal de saída única.
.. raw:: html
.. raw:: html
.. code:: python
corr2d_multi_in_out(X, K)
.. parsed-literal::
:class: output
array([[[ 56., 72.],
[104., 120.]],
[[ 76., 100.],
[148., 172.]],
[[ 96., 128.],
[192., 224.]]])
.. raw:: html
.. raw:: html
.. code:: python
corr2d_multi_in_out(X, K)
.. parsed-literal::
:class: output
tensor([[[ 56., 72.],
[104., 120.]],
[[ 76., 100.],
[148., 172.]],
[[ 96., 128.],
[192., 224.]]])
.. raw:: html
.. raw:: html
.. code:: python
corr2d_multi_in_out(X, K)
.. parsed-literal::
:class: output
.. raw:: html
.. raw:: html
Camada Convolucional :math:`1\times 1`
--------------------------------------
No início, uma convolução :math:`1 \times 1`, ou seja,
:math:`k_h = k_w = 1`, não parece fazer muito sentido. Afinal, uma
convolução correlaciona pixels adjacentes. Uma convolução
:math:`1 \times 1` obviamente não faz isso. No entanto, são operações
populares que às vezes são incluídas nos projetos de redes profundas
complexas. Vejamos com alguns detalhes o que ele realmente faz.
Como a janela mínima é usada, a convolução :math:`1\times 1` perde a
capacidade de camadas convolucionais maiores para reconhecer padrões que
consistem em interações entre os elementos adjacentes nas dimensões de
altura e largura. O único cálculo da convolução :math:`1\times 1` ocorre
na dimensão do canal.
:numref:`fig_conv_1x1` mostra o cálculo de correlação cruzada usando o
kernel de convolução :math:`1\times 1` com 3 canais de entrada e 2
canais de saída. Observe que as entradas e saídas têm a mesma altura e
largura. Cada elemento na saída é derivado de uma combinação linear de
elementos *na mesma posição* na imagem de entrada. Você poderia pensar
na camada convolucional :math:`1\times 1` como constituindo uma camada
totalmente conectada aplicada em cada localização de pixel para
transformar os valores de entrada correspondentes :math:`c_i` em valores
de saída :math:`c_o`. Porque esta ainda é uma camada convolucional, os
pesos são vinculados à localização do pixel. Assim, a camada
convolucional :math:`1\times 1` requer pesos :math:`c_o\times c_i` (mais
o *bias*).
.. _fig_conv_1x1:
.. figure:: ../img/conv-1x1.svg
O cálculo de correlação cruzada usa o *kernel* de convolução
:math:`1\times 1` com 3 canais de entrada e 2 canais de saída. A
entrada e a saída têm a mesma altura e largura.
Vamos verificar se isso funciona na prática: implementamos uma
convolução :math:`1 \times 1` usando uma camada totalmente conectada. A
única coisa é que precisamos fazer alguns ajustes para a forma de dados
antes e depois da multiplicação da matriz.
.. raw:: html
.. raw:: html
.. code:: python
def corr2d_multi_in_out_1x1(X, K):
c_i, h, w = X.shape
c_o = K.shape[0]
X = X.reshape((c_i, h * w))
K = K.reshape((c_o, c_i))
Y = np.dot(K, X) # Matrix multiplication in the fully-connected layer
return Y.reshape((c_o, h, w))
.. raw:: html
.. raw:: html
.. code:: python
def corr2d_multi_in_out_1x1(X, K):
c_i, h, w = X.shape
c_o = K.shape[0]
X = X.reshape((c_i, h * w))
K = K.reshape((c_o, c_i))
Y = torch.matmul(K, X) # Matrix multiplication in the fully-connected layer
return Y.reshape((c_o, h, w))
.. raw:: html
.. raw:: html
.. code:: python
def corr2d_multi_in_out_1x1(X, K):
c_i, h, w = X.shape
c_o = K.shape[0]
X = tf.reshape(X, (c_i, h * w))
K = tf.reshape(K, (c_o, c_i))
Y = tf.matmul(K, X) # Matrix multiplication in the fully-connected layer
return tf.reshape(Y, (c_o, h, w))
.. raw:: html
.. raw:: html
Ao realizar convolução :math:`1\times 1` , a função acima é equivalente
à função de correlação cruzada implementada anteriormente
``corr2d_multi_in_out``. Vamos verificar isso com alguns dados de
amostra.
.. raw:: html
.. raw:: html
.. code:: python
X = np.random.normal(0, 1, (3, 3, 3))
K = np.random.normal(0, 1, (2, 3, 1, 1))
Y1 = corr2d_multi_in_out_1x1(X, K)
Y2 = corr2d_multi_in_out(X, K)
assert float(np.abs(Y1 - Y2).sum()) < 1e-6
.. raw:: html
.. raw:: html
.. code:: python
X = torch.normal(0, 1, (3, 3, 3))
K = torch.normal(0, 1, (2, 3, 1, 1))
Y1 = corr2d_multi_in_out_1x1(X, K)
Y2 = corr2d_multi_in_out(X, K)
assert float(torch.abs(Y1 - Y2).sum()) < 1e-6
.. raw:: html
.. raw:: html
.. code:: python
X = tf.random.normal((3, 3, 3), 0, 1)
K = tf.random.normal((2, 3, 1, 1), 0, 1)
Y1 = corr2d_multi_in_out_1x1(X, K)
Y2 = corr2d_multi_in_out(X, K)
assert float(tf.reduce_sum(tf.abs(Y1 - Y2))) < 1e-6
.. raw:: html
.. raw:: html
Resumo
------
- Vários canais podem ser usados para estender os parâmetros do modelo
da camada convolucional.
- A camada convolucional :math:`1\times 1` é equivalente à camada
totalmente conectada, quando aplicada por pixel.
- A camada convolucional :math:`1\times 1` é normalmente usada para
ajustar o número de canais entre as camadas de rede e para controlar
a complexidade do modelo.
Exercícios
----------
1. Suponha que temos dois *kernels* de convolução de tamanho :math:`k_1`
e :math:`k_2`, respectivamente (sem não linearidade entre eles).
1. Prove que o resultado da operação pode ser expresso por uma única
convolução.
2. Qual é a dimensionalidade da convolução única equivalente?
3. O inverso é verdadeiro?
2. Assuma uma entrada de forma :math:`c_i\times h\times w` e um *kernel*
de convolução de forma :math:`c_o\times c_i\times k_h\times k_w`,
preenchimento de :math:`(p_h, p_w)`, e passo de :math:`(s_h, s_w)`.
1. Qual é o custo computacional (multiplicações e adições) para a
propagação direta?
2. Qual é a pegada de memória?
3. Qual é a pegada de memória para a computação reversa?
4. Qual é o custo computacional para a retropropagação?
3. Por que fator o número de cálculos aumenta se dobrarmos o número de
canais de entrada :math:`c_i` e o número de canais de saída
:math:`c_o`? O que acontece se dobrarmos o preenchimento?
4. Se a altura e largura de um *kernel* de convolução é
:math:`k_h=k_w=1`,, qual é a complexidade computacional da propagação
direta?
5. As variáveis \ ``Y1`` e\ ``Y2`` no último exemplo desta seção são
exatamente as mesmas? Porque?
6. Como você implementaria convoluções usando a multiplicação de
matrizes quando a janela de convolução não é :math:`1\times 1`?
.. raw:: html
.. raw:: html
`Discussions `__
.. raw:: html
.. raw:: html
`Discussions `__
.. raw:: html
.. raw:: html
`Discussions `__
.. raw:: html
.. raw:: html
.. raw:: html