.. _sec_transposed_conv: Convolução Transposta ===================== As camadas que apresentamos até agora para redes neurais convolucionais, incluindo camadas convolucionais (:numref:`sec_conv_layer`) e camadas de pooling (:numref:`sec_pooling`), geralmente reduzem a largura e altura de entrada ou as mantêm inalteradas. Aplicativos como segmentação semântica (:numref:`sec_semantic_segmentation`) e redes adversárias geradoras (:numref:`sec_dcgan`), no entanto, exigem prever valores para cada pixel e, portanto, precisam aumentar a largura e altura de entrada. A convolução transposta, também chamada de convolução fracionada :cite:`Dumoulin.Visin.2016` ou deconvolução :cite:`Long.Shelhamer.Darrell.2015`, serve a este propósito. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python from mxnet import init, np, npx from mxnet.gluon import nn from d2l import mxnet as d2l npx.set_np() .. raw:: html
.. raw:: html
.. code:: python import torch from torch import nn from d2l import torch as d2l .. raw:: html
.. raw:: html
Convolução Transposta 2D Básica ------------------------------- Vamos considerar um caso básico em que os canais de entrada e saída são 1, com 0 preenchimento e 1 passo. :numref:`fig_trans_conv` ilustra como a convolução transposta com um *kernel* :math:`2\times 2` é calculada na matriz de entrada :math:`2\times 2`. .. _fig_trans_conv: .. figure:: ../img/trans-conv.svg Camada de convolução transposta com um *kernel* :math:`2\times 2`. Podemos implementar essa operação fornecendo o *kernel* da matriz :math:`K` e a entrada da matriz :math:`X`. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python def trans_conv(X, K): h, w = K.shape Y = np.zeros((X.shape[0] + h - 1, X.shape[1] + w - 1)) for i in range(X.shape[0]): for j in range(X.shape[1]): Y[i: i + h, j: j + w] += X[i, j] * K return Y .. raw:: html
.. raw:: html
.. code:: python def trans_conv(X, K): h, w = K.shape Y = torch.zeros((X.shape[0] + h - 1, X.shape[1] + w - 1)) for i in range(X.shape[0]): for j in range(X.shape[1]): Y[i: i + h, j: j + w] += X[i, j] * K return Y .. raw:: html
.. raw:: html
Lembre-se de que a convolução calcula os resultados por ``Y[i, j] = (X[i: i + h, j: j + w] * K).sum()`` (consulte ``corr2d`` em :numref:`sec_conv_layer`), que resume os valores de entrada por meio do *kernel*. Enquanto a convolução transposta transmite valores de entrada por meio do *kernel*, o que resulta em uma forma de saída maior. Verifique os resultados em :numref:`fig_trans_conv`. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python X = np.array([[0., 1], [2, 3]]) K = np.array([[0., 1], [2, 3]]) trans_conv(X, K) .. parsed-literal:: :class: output array([[ 0., 0., 1.], [ 0., 4., 6.], [ 4., 12., 9.]]) Ou podemos usar ``nn.Conv2DTranspose`` para obter os mesmos resultados. Como ``nn.Conv2D``, tanto a entrada quanto o *kernel* devem ser tensores 4-D. .. code:: python X, K = X.reshape(1, 1, 2, 2), K.reshape(1, 1, 2, 2) tconv = nn.Conv2DTranspose(1, kernel_size=2) tconv.initialize(init.Constant(K)) tconv(X) .. parsed-literal:: :class: output array([[[[ 0., 0., 1.], [ 0., 4., 6.], [ 4., 12., 9.]]]]) .. raw:: html
.. raw:: html
.. code:: python X = torch.tensor([[0., 1], [2, 3]]) K = torch.tensor([[0., 1], [2, 3]]) trans_conv(X, K) .. parsed-literal:: :class: output tensor([[ 0., 0., 1.], [ 0., 4., 6.], [ 4., 12., 9.]]) Ou podemos usar ``nn.ConvTranspose2d`` para obter os mesmos resultados. Como ``nn.Conv2d``, tanto a entrada quanto o *kernel* devem ser tensores 4-D. .. code:: python X, K = X.reshape(1, 1, 2, 2), K.reshape(1, 1, 2, 2) tconv = nn.ConvTranspose2d(1, 1, kernel_size=2, bias=False) tconv.weight.data = K tconv(X) .. parsed-literal:: :class: output tensor([[[[ 0., 0., 1.], [ 0., 4., 6.], [ 4., 12., 9.]]]], grad_fn=) .. raw:: html
.. raw:: html
Preenchimento, Passos e Canais ------------------------------ Aplicamos elementos de preenchimento à entrada em convolução, enquanto eles são aplicados à saída em convolução transposta. Um preenchimento :math:`1\times 1` significa que primeiro calculamos a saída como normal e, em seguida, removemos as primeiras/últimas linhas e colunas. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python tconv = nn.Conv2DTranspose(1, kernel_size=2, padding=1) tconv.initialize(init.Constant(K)) tconv(X) .. parsed-literal:: :class: output array([[[[4.]]]]) .. raw:: html
.. raw:: html
.. code:: python tconv = nn.ConvTranspose2d(1, 1, kernel_size=2, padding=1, bias=False) tconv.weight.data = K tconv(X) .. parsed-literal:: :class: output tensor([[[[4.]]]], grad_fn=) .. raw:: html
.. raw:: html
Da mesma forma, os avanços também são aplicados às saídas. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python tconv = nn.Conv2DTranspose(1, kernel_size=2, strides=2) tconv.initialize(init.Constant(K)) tconv(X) .. parsed-literal:: :class: output array([[[[0., 0., 0., 1.], [0., 0., 2., 3.], [0., 2., 0., 3.], [4., 6., 6., 9.]]]]) .. raw:: html
.. raw:: html
.. code:: python tconv = nn.ConvTranspose2d(1, 1, kernel_size=2, stride=2, bias=False) tconv.weight.data = K tconv(X) .. parsed-literal:: :class: output tensor([[[[0., 0., 0., 1.], [0., 0., 2., 3.], [0., 2., 0., 3.], [4., 6., 6., 9.]]]], grad_fn=) .. raw:: html
.. raw:: html
A extensão multicanal da convolução transposta é igual à convolução. Quando a entrada tem vários canais, denotados por :math:`c_i`, a convolução transposta atribui uma matriz de *kernel* :math:`k_h\times k_w` a cada canal de entrada. Se a saída tem um tamanho de canal :math:`c_o`, então temos um *kernel* :math:`c_i\times k_h\times k_w` para cada canal de saída. Como resultado, se alimentarmos :math:`X` em uma camada convolucional :math:`f` para calcular :math:`Y=f(X)` e criarmos uma camada de convolução transposta :math:`g` com os mesmos hiperparâmetros de :math:`f`, exceto para o conjunto de canais de saída para ter o tamanho do canal de :math:`X`, então :math:`g(Y)` deve ter o mesmo formato que :math:`X`. Deixe-nos verificar esta afirmação. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python X = np.random.uniform(size=(1, 10, 16, 16)) conv = nn.Conv2D(20, kernel_size=5, padding=2, strides=3) tconv = nn.Conv2DTranspose(10, kernel_size=5, padding=2, strides=3) conv.initialize() tconv.initialize() tconv(conv(X)).shape == X.shape .. parsed-literal:: :class: output True .. raw:: html
.. raw:: html
.. code:: python X = torch.rand(size=(1, 10, 16, 16)) conv = nn.Conv2d(10, 20, kernel_size=5, padding=2, stride=3) tconv = nn.ConvTranspose2d(20, 10, kernel_size=5, padding=2, stride=3) tconv(conv(X)).shape == X.shape .. parsed-literal:: :class: output True .. raw:: html
.. raw:: html
Analogia à Transposição de Matriz --------------------------------- A convolução transposta leva o nome da transposição da matriz. Na verdade, as operações de convolução também podem ser realizadas por multiplicação de matrizes. No exemplo abaixo, definimos uma entrada :math:`X` :math:`3\times 3` com *kernel* :math:`K` :math:`2\times 2`, e então usamos ``corr2d`` para calcular a saída da convolução. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python X = np.arange(9.0).reshape(3, 3) K = np.array([[0, 1], [2, 3]]) Y = d2l.corr2d(X, K) Y .. parsed-literal:: :class: output array([[19., 25.], [37., 43.]]) .. raw:: html
.. raw:: html
.. code:: python X = torch.arange(9.0).reshape(3, 3) K = torch.tensor([[0, 1], [2, 3]]) Y = d2l.corr2d(X, K) Y .. parsed-literal:: :class: output tensor([[19., 25.], [37., 43.]]) .. raw:: html
.. raw:: html
A seguir, reescrevemos o *kernel* de convolução :math:`K` como uma matriz :math:`W`. Sua forma será :math:`(4, 9)`, onde a linha :math:`i^\mathrm{th}` presente aplicando o *kernel* à entrada para gerar o :math:`i^\mathrm{th}` elemento de saída. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python def kernel2matrix(K): k, W = np.zeros(5), np.zeros((4, 9)) k[:2], k[3:5] = K[0, :], K[1, :] W[0, :5], W[1, 1:6], W[2, 3:8], W[3, 4:] = k, k, k, k return W W = kernel2matrix(K) W .. parsed-literal:: :class: output array([[0., 1., 0., 2., 3., 0., 0., 0., 0.], [0., 0., 1., 0., 2., 3., 0., 0., 0.], [0., 0., 0., 0., 1., 0., 2., 3., 0.], [0., 0., 0., 0., 0., 1., 0., 2., 3.]]) .. raw:: html
.. raw:: html
.. code:: python def kernel2matrix(K): k, W = torch.zeros(5), torch.zeros((4, 9)) k[:2], k[3:5] = K[0, :], K[1, :] W[0, :5], W[1, 1:6], W[2, 3:8], W[3, 4:] = k, k, k, k return W W = kernel2matrix(K) W .. parsed-literal:: :class: output tensor([[0., 1., 0., 2., 3., 0., 0., 0., 0.], [0., 0., 1., 0., 2., 3., 0., 0., 0.], [0., 0., 0., 0., 1., 0., 2., 3., 0.], [0., 0., 0., 0., 0., 1., 0., 2., 3.]]) .. raw:: html
.. raw:: html
Então, o operador de convolução pode ser implementado por multiplicação de matriz com remodelagem adequada. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python Y == np.dot(W, X.reshape(-1)).reshape(2, 2) .. parsed-literal:: :class: output array([[ True, True], [ True, True]]) .. raw:: html
.. raw:: html
.. code:: python Y == torch.mv(W, X.reshape(-1)).reshape(2, 2) .. parsed-literal:: :class: output tensor([[True, True], [True, True]]) .. raw:: html
.. raw:: html
Podemos implementar a convolução transposta como uma multiplicação de matriz reutilizando ``kernel2matrix``. Para reutilizar o :math:`W` gerado, construímos uma entrada :math:`2\times 2`, de modo que a matriz de peso correspondente terá uma forma :math:`(9, 4)`, que é :math:`W^\top`. Deixe-nos verificar os resultados. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python X = np.array([[0, 1], [2, 3]]) Y = trans_conv(X, K) Y == np.dot(W.T, X.reshape(-1)).reshape(3, 3) .. parsed-literal:: :class: output array([[ True, True, True], [ True, True, True], [ True, True, True]]) .. raw:: html
.. raw:: html
.. code:: python X = torch.tensor([[0.0, 1], [2, 3]]) Y = trans_conv(X, K) Y == torch.mv(W.T, X.reshape(-1)).reshape(3, 3) .. parsed-literal:: :class: output tensor([[True, True, True], [True, True, True], [True, True, True]]) .. raw:: html
.. raw:: html
Resumo ------ - Em comparação com as convoluções que reduzem as entradas por meio de *kernels*, as convoluções transpostas transmitem as entradas. - Se uma camada de convolução reduz a largura e altura de entrada em :math:`n_w` e :math:`h_h` tempo, respectivamente. Então, uma camada de convolução transposta com os mesmos tamanhos de *kernel*, preenchimento e passos aumentará a largura e altura de entrada em :math:`n_w` e :math:`h_h`, respectivamente. - Podemos implementar operações de convolução pela multiplicação da matriz, as convoluções transpostas correspondentes podem ser feitas pela multiplicação da matriz transposta. Exercícios ---------- 1. É eficiente usar a multiplicação de matrizes para implementar operações de convolução? Por quê? .. raw:: html
mxnetpytorch
.. raw:: html
`Discussões `__ .. raw:: html
.. raw:: html
`Discussões `__ .. raw:: html
.. raw:: html
.. raw:: html