{
 "cells": [
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "---\n",
    "execute:\n",
    "  enabled: false\n",
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<!-- d2l:prose id=transposed-conv-md-1 fw=all -->\n",
    "\n",
    "# Transposed Convolution\n",
    "\n",
    "The CNN layers we have seen so far,\n",
    "such as convolutional layers (that section) and pooling layers (that section),\n",
    "typically reduce (downsample) the spatial dimensions (height and width) of the input,\n",
    "or keep them unchanged.\n",
    "In semantic segmentation\n",
    "that classifies at pixel-level,\n",
    "it will be convenient if\n",
    "the spatial dimensions of the\n",
    "input and output are the same.\n",
    "For example,\n",
    "the channel dimension at one output pixel \n",
    "can hold the classification results\n",
    "for the input pixel at the same spatial position.\n",
    "\n",
    "\n",
    "To achieve this, especially after \n",
    "the spatial dimensions are reduced by CNN layers,\n",
    "we can use another type\n",
    "of CNN layers\n",
    "that can increase (upsample) the spatial dimensions\n",
    "of intermediate feature maps.\n",
    "In this section,\n",
    "we will introduce \n",
    "*transposed convolution*, which is also called *fractionally-strided convolution* [@Dumoulin.Visin.2016], \n",
    "for reversing downsampling operations\n",
    "by the convolution."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "id": "transposed-conv-transposed-convolution"
   },
   "source": [
    "import torch\n",
    "from torch import nn\n",
    "from d2l import torch as d2l"
   ],
   "execution_count": 1,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<!-- d2l:prose id=transposed-conv-md-2 fw=all -->\n",
    "\n",
    "## Basic Operation\n",
    "\n",
    "Ignoring channels for now,\n",
    "let's begin with\n",
    "the basic transposed convolution operation\n",
    "with stride of 1 and no padding.\n",
    "Suppose that\n",
    "we are given a \n",
    "$n_h \\times n_w$ input tensor\n",
    "and a $k_h \\times k_w$ kernel.\n",
    "Sliding the kernel window with stride of 1\n",
    "for $n_w$ times in each row\n",
    "and $n_h$ times in each column\n",
    "yields \n",
    "a total of $n_h n_w$ intermediate results.\n",
    "Each intermediate result is\n",
    "a $(n_h + k_h - 1) \\times (n_w + k_w - 1)$\n",
    "tensor that are initialized as zeros.\n",
    "To compute each intermediate tensor,\n",
    "each element in the input tensor\n",
    "is multiplied by the kernel\n",
    "so that the resulting $k_h \\times k_w$ tensor\n",
    "replaces a portion in\n",
    "each intermediate tensor.\n",
    "Note that\n",
    "the position of the replaced portion in each\n",
    "intermediate tensor corresponds to the position of the element\n",
    "in the input tensor used for the computation.\n",
    "In the end, all the intermediate results\n",
    "are summed over to produce the output.\n",
    "\n",
    "As an example,\n",
    "the figure illustrates\n",
    "how transposed convolution with a $2\\times 2$ kernel is computed for a $2\\times 2$ input tensor.\n",
    "\n",
    "\n",
    "![Transposed convolution with a $2\\times 2$ kernel. The shaded portions are a portion of an intermediate tensor as well as the input and kernel tensor elements used for the  computation.](../img/trans_conv.svg)\n",
    "\n",
    "\n",
    "We can implement this basic transposed convolution operation `trans_conv` for a input matrix `X` and a kernel matrix `K`."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "id": "transposed-conv-basic-operation-1"
   },
   "source": [
    "def trans_conv(X, K):\n",
    "    h, w = K.shape\n",
    "    Y = d2l.zeros((X.shape[0] + h - 1, X.shape[1] + w - 1))\n",
    "    for i in range(X.shape[0]):\n",
    "        for j in range(X.shape[1]):\n",
    "            Y[i: i + h, j: j + w] += X[i, j] * K\n",
    "    return Y"
   ],
   "execution_count": 2,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<!-- d2l:prose id=transposed-conv-md-3 fw=all -->\n",
    "\n",
    "In contrast to the regular convolution (in that section) that *reduces* input elements\n",
    "via the kernel,\n",
    "the transposed convolution\n",
    "*broadcasts* input elements \n",
    "via the kernel, thereby\n",
    "producing an output\n",
    "that is larger than the input.\n",
    "We can construct the input tensor `X` and the kernel tensor `K` from the figure to validate the output of the above implementation of the basic two-dimensional transposed convolution operation."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "id": "transposed-conv-basic-operation-2"
   },
   "source": [
    "X = d2l.tensor([[0.0, 1.0], [2.0, 3.0]])\n",
    "K = d2l.tensor([[0.0, 1.0], [2.0, 3.0]])\n",
    "trans_conv(X, K)"
   ],
   "execution_count": 3,
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": "tensor([[ 0.,  0.,  1.],\n        [ 0.,  4.,  6.],\n        [ 4., 12.,  9.]])"
     },
     "metadata": {},
     "execution_count": 3
    }
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<!-- d2l:prose id=transposed-conv-md-4 fw=all -->\n",
    "\n",
    "Alternatively,\n",
    "when the input `X` and kernel `K` are both\n",
    "four-dimensional tensors,\n",
    "we can use high-level APIs to obtain the same results."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "id": "transposed-conv-basic-operation-3"
   },
   "source": [
    "X, K = X.reshape(1, 1, 2, 2), K.reshape(1, 1, 2, 2)\n",
    "tconv = nn.ConvTranspose2d(1, 1, kernel_size=2, bias=False)\n",
    "tconv.weight.data = K\n",
    "tconv(X)"
   ],
   "execution_count": 4,
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": "tensor([[[[ 0.,  0.,  1.],\n          [ 0.,  4.,  6.],\n          [ 4., 12.,  9.]]]], grad_fn=<ConvolutionBackward0>)"
     },
     "metadata": {},
     "execution_count": 4
    }
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<!-- d2l:prose id=transposed-conv-md-5 fw=all -->\n",
    "\n",
    "## Padding, Strides, and Multiple Channels\n",
    "\n",
    "Different from in the regular convolution\n",
    "where padding is applied to input,\n",
    "it is applied to output\n",
    "in the transposed convolution.\n",
    "For example,\n",
    "when specifying the padding number\n",
    "on either side of the height and width \n",
    "as 1,\n",
    "the first and last rows and columns\n",
    "will be removed from the transposed convolution output."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "id": "transposed-conv-padding-strides-and-multiple-channels-1"
   },
   "source": [
    "tconv = nn.ConvTranspose2d(1, 1, kernel_size=2, padding=1, bias=False)\n",
    "tconv.weight.data = K\n",
    "tconv(X)"
   ],
   "execution_count": 5,
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": "tensor([[[[4.]]]], grad_fn=<ConvolutionBackward0>)"
     },
     "metadata": {},
     "execution_count": 5
    }
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<!-- d2l:prose id=transposed-conv-md-6 fw=all -->\n",
    "\n",
    "In the transposed convolution,\n",
    "strides are specified for intermediate results (thus output), not for input.\n",
    "Using the same input and kernel tensors\n",
    "from the figure,\n",
    "changing the stride from 1 to 2\n",
    "increases both the height and width\n",
    "of intermediate tensors, hence the output tensor\n",
    "in the figure.\n",
    "\n",
    "\n",
    "![Transposed convolution with a $2\\times 2$ kernel with stride of 2. The shaded portions are a portion of an intermediate tensor as well as the input and kernel tensor elements used for the  computation.](../img/trans_conv_stride2.svg)\n",
    "\n",
    "\n",
    "The following code snippet can validate the transposed convolution output for stride of 2 in the figure."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "id": "transposed-conv-padding-strides-and-multiple-channels-2"
   },
   "source": [
    "tconv = nn.ConvTranspose2d(1, 1, kernel_size=2, stride=2, bias=False)\n",
    "tconv.weight.data = K\n",
    "tconv(X)"
   ],
   "execution_count": 6,
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": "tensor([[[[0., 0., 0., 1.],\n          [0., 0., 2., 3.],\n          [0., 2., 0., 3.],\n          [4., 6., 6., 9.]]]], grad_fn=<ConvolutionBackward0>)"
     },
     "metadata": {},
     "execution_count": 6
    }
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<!-- d2l:prose id=transposed-conv-md-7 fw=all -->\n",
    "\n",
    "For multiple input and output channels,\n",
    "the transposed convolution\n",
    "works in the same way as the regular convolution.\n",
    "Suppose that\n",
    "the input has $c_i$ channels,\n",
    "and that the transposed convolution\n",
    "assigns a $k_h\\times k_w$ kernel tensor\n",
    "to each input channel.\n",
    "When multiple output channels \n",
    "are specified,\n",
    "we will have a $c_i\\times k_h\\times k_w$ kernel for each output channel.\n",
    "\n",
    "\n",
    "As in all, if we feed $\\mathsf{X}$ into a convolutional layer $f$ to output $\\mathsf{Y}=f(\\mathsf{X})$ and create a transposed convolutional layer $g$ with the same hyperparameters as $f$ except \n",
    "for the number of output channels \n",
    "being the number of channels in $\\mathsf{X}$,\n",
    "then $g(Y)$ will have the same shape as $\\mathsf{X}$.\n",
    "This can be illustrated in the following example.\n",
    "Note that when the stride is greater than 1, multiple input shapes can map to the same output shape under convolution, so the shape match is not guaranteed in general. In such cases, an additional `output_padding` parameter (available in PyTorch's `ConvTranspose2d`) can be used to resolve the ambiguity."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "id": "transposed-conv-padding-strides-and-multiple-channels-3"
   },
   "source": [
    "X = torch.rand(size=(1, 10, 16, 16))\n",
    "conv = nn.Conv2d(10, 20, kernel_size=5, padding=2, stride=3)\n",
    "tconv = nn.ConvTranspose2d(20, 10, kernel_size=5, padding=2, stride=3)\n",
    "tconv(conv(X)).shape == X.shape"
   ],
   "execution_count": 7,
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": "True"
     },
     "metadata": {},
     "execution_count": 7
    }
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<!-- d2l:prose id=transposed-conv-md-8 fw=all -->\n",
    "\n",
    "## Connection to Matrix Transposition\n",
    "\n",
    "The transposed convolution is named after\n",
    "the matrix transposition.\n",
    "To explain,\n",
    "let's first\n",
    "see how to implement convolutions\n",
    "using matrix multiplications.\n",
    "In the example below, we define a $3\\times 3$ input `X` and a $2\\times 2$ convolution kernel `K`, and then use the `corr2d` function to compute the convolution output `Y`."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "id": "transposed-conv-connection-to-matrix-transposition-1"
   },
   "source": [
    "X = d2l.reshape(d2l.arange(9.0), (3, 3))\n",
    "K = d2l.tensor([[1.0, 2.0], [3.0, 4.0]])\n",
    "Y = d2l.corr2d(X, K)\n",
    "Y"
   ],
   "execution_count": 8,
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": "tensor([[27., 37.],\n        [57., 67.]])"
     },
     "metadata": {},
     "execution_count": 8
    }
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<!-- d2l:prose id=transposed-conv-md-9 fw=all -->\n",
    "\n",
    "Next, we rewrite the convolution kernel `K` as\n",
    "a sparse weight matrix `W`\n",
    "containing a lot of zeros. \n",
    "The shape of the weight matrix is ($4$, $9$),\n",
    "where the non-zero elements come from\n",
    "the convolution kernel `K`."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "id": "transposed-conv-connection-to-matrix-transposition-2"
   },
   "source": [
    "def kernel2matrix(K):\n",
    "    k, W = d2l.zeros(5), d2l.zeros((4, 9))\n",
    "    k[:2], k[3:5] = K[0, :], K[1, :]\n",
    "    W[0, :5], W[1, 1:6], W[2, 3:8], W[3, 4:] = k, k, k, k\n",
    "    return W\n",
    "\n",
    "W = kernel2matrix(K)\n",
    "W"
   ],
   "execution_count": 9,
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": "tensor([[1., 2., 0., 3., 4., 0., 0., 0., 0.],\n        [0., 1., 2., 0., 3., 4., 0., 0., 0.],\n        [0., 0., 0., 1., 2., 0., 3., 4., 0.],\n        [0., 0., 0., 0., 1., 2., 0., 3., 4.]])"
     },
     "metadata": {},
     "execution_count": 9
    }
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<!-- d2l:prose id=transposed-conv-md-10 fw=all -->\n",
    "\n",
    "Concatenate the input `X` row by row to get a vector of length 9. Then the matrix multiplication of `W` and the vectorized `X` gives a vector of length 4.\n",
    "After reshaping it, we can obtain the same result `Y`\n",
    "from the original convolution operation above:\n",
    "we just implemented convolutions using matrix multiplications."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "id": "transposed-conv-connection-to-matrix-transposition-3"
   },
   "source": [
    "Y == d2l.reshape(d2l.matmul(W, d2l.reshape(X, (-1, 1))), (2, 2))"
   ],
   "execution_count": 10,
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": "tensor([[True, True],\n        [True, True]])"
     },
     "metadata": {},
     "execution_count": 10
    }
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<!-- d2l:prose id=transposed-conv-md-11 fw=all -->\n",
    "\n",
    "Likewise, we can implement transposed convolutions using\n",
    "matrix multiplications.\n",
    "In the following example,\n",
    "we take the $2 \\times 2$ output `Y` from the above\n",
    "regular convolution\n",
    "as input to the transposed convolution.\n",
    "To implement this operation by multiplying matrices,\n",
    "we only need to transpose the weight matrix `W`\n",
    "with the new shape $(9, 4)$."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "id": "transposed-conv-connection-to-matrix-transposition-4"
   },
   "source": [
    "Z = trans_conv(Y, K)\n",
    "Z == d2l.reshape(d2l.matmul(d2l.transpose(W), d2l.reshape(Y, (-1, 1))), (3, 3))"
   ],
   "execution_count": 11,
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": "tensor([[True, True, True],\n        [True, True, True],\n        [True, True, True]])"
     },
     "metadata": {},
     "execution_count": 11
    }
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<!-- d2l:prose id=transposed-conv-md-12 fw=all -->\n",
    "\n",
    "Consider implementing the convolution\n",
    "by multiplying matrices.\n",
    "Given an input vector $\\mathbf{x}$\n",
    "and a weight matrix $\\mathbf{W}$,\n",
    "the forward propagation function of the convolution\n",
    "can be implemented\n",
    "by multiplying its input with the weight matrix\n",
    "and outputting a vector \n",
    "$\\mathbf{y}=\\mathbf{W}\\mathbf{x}$.\n",
    "Since backpropagation\n",
    "follows the chain rule\n",
    "and $\\nabla_{\\mathbf{x}}\\mathbf{y}=\\mathbf{W}^\\top$,\n",
    "the backpropagation function of the convolution\n",
    "can be implemented\n",
    "by multiplying its input with the \n",
    "transposed weight matrix $\\mathbf{W}^\\top$.\n",
    "Therefore, \n",
    "the transposed convolutional layer\n",
    "can just exchange the forward propagation function\n",
    "and the backpropagation function of the convolutional layer:\n",
    "its forward propagation \n",
    "and backpropagation functions\n",
    "multiply their input vector with \n",
    "$\\mathbf{W}^\\top$ and $\\mathbf{W}$, respectively.\n",
    "\n",
    "\n",
    "## Summary\n",
    "\n",
    "* In contrast to the regular convolution that reduces input elements via the kernel, the transposed convolution broadcasts input elements via the kernel, thereby producing an output that is larger than the input.\n",
    "* If we feed $\\mathsf{X}$ into a convolutional layer $f$ to output $\\mathsf{Y}=f(\\mathsf{X})$ and create a transposed convolutional layer $g$ with the same hyperparameters as $f$ except for the number of output channels being the number of channels in $\\mathsf{X}$, then $g(Y)$ will have the same shape as $\\mathsf{X}$. This holds when the stride is 1; for stride > 1, an additional `output_padding` may be needed since multiple input shapes can share the same convolution output shape.\n",
    "* We can implement convolutions using matrix multiplications. The transposed convolutional layer can just exchange the forward propagation function and the backpropagation function of the convolutional layer.\n",
    "\n",
    "\n",
    "## Exercises\n",
    "\n",
    "1. In that section, the convolution input `X` and the transposed convolution output `Z` have the same shape. Do they have the same value? Why?\n",
    "1. Is it efficient to use matrix multiplications to implement convolutions? Why?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<!-- d2l:prose id=transposed-conv-md-13 fw=pytorch -->\n",
    "\n",
    "[Discussions](https://d2l.discourse.group/t/1450)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "name": "d2l-pytorch",
   "display_name": "d2l (PyTorch)",
   "language": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
