{"action":"created","comment":{"url":"https://api.github.com/repos/Theano/Theano/pulls/comments/66547703","id":66547703,"diff_hunk":"@@ -0,0 +1,834 @@\n+.. _conv_arithmetic:\n+\n+===============================\n+Convolution arithmetic tutorial\n+===============================\n+\n+.. note::\n+\n+    This tutorial is adapted from an existing `convolution arithmetic guide\n+    <https://arxiv.org/abs/1603.07285>`_ [#]_, with an added emphasis on\n+    Theano's interface.\n+\n+Quick reference\n+===============\n+\n+.. admonition:: Convolution relationship\n+\n+    A convolution specified by\n+\n+    * input size :math:`i`,\n+    * kernel size :math:`k`,\n+    * stride :math:`s`,\n+    * padding size :math:`p`,\n+\n+    has an output size given by\n+\n+    .. math::\n+\n+        o = \\left\\lfloor \\frac{i + 2p - k}{s} \\right\\rfloor + 1.\n+\n+    In Theano, this translates to\n+\n+    .. code-block:: python\n+\n+        output = theano.tensor.nnet.conv2d(\n+            input, filters, input_shape=(i1, i2), filter_shape=(c1, c2, k1, k2),\n+            border_mode=(p1, p2), subsample=(s1, s2))\n+        # output.shape[2] == (i1 + 2 * p1 - k1) // s1 + 1\n+        # output.shape[3] == (i1 + 2 * p1 - k1) // s1 + 1\n+\n+.. admonition:: Transposed convolution relationship\n+\n+    A transposed convolution specified by\n+\n+    * input size :math:`i`,\n+    * kernel size :math:`k`,\n+    * stride :math:`s`,\n+    * padding size :math:`p`,\n+\n+    has an output size given by\n+\n+    .. math::\n+\n+        o = s (i - 1) + a + k - 2p, \\quad a \\in \\{0, \\ldots, s - 1\\}\n+\n+    where :math:`a` is a user-specified quantity used to distinguish between the\n+    :math:`s` different possible output sizes.\n+\n+    Unless :math:`s = 1`, Theano requires that :math:`a` is implicitly passed\n+    via an ``input_shape`` argument. For instance, if :math:`i = 3`,\n+    :math:`k = 4`, :math:`s = 2`, :math:`p = 0` and :math:`a = 1`, then\n+    :math:`o = 2 (3 - 1) + 1 + 4 = 9` and the Theano code would look like\n+\n+    .. code-block:: python\n+\n+        input = theano.tensor.nnet.conv2d_grad_wrt_inputs(\n+            output, filters, input_shape=(9, 9), filter_shape=(c1, c2, 4, 4),\n+            border_mode='valid', subsample=(2, 2))\n+\n+About this tutorial\n+===================\n+\n+Learning to use convolutional neural networks (CNNs) for the first time is\n+generally an intimidating experience. A convolutional layer's output shape is\n+affected by the shape of its input as well as the choice of kernel shape, zero\n+padding and strides, and the relationship between these properties is not\n+trivial to infer. This contrasts with fully-connected layers, whose output size\n+is independent of the input size.  Additionally, so-called transposed\n+convolutional layers (also known as fractionally strided convolutional layers)\n+have been employed in more and more work as of late, and their relationship with\n+convolutional layers has been explained with various degrees of clarity.\n+\n+The relationship between a convolution operation's input shape, kernel size,\n+stride, padding and its output shape can be confusing at times.\n+\n+The tutorial's objective is twofold:\n+\n+* Explain the relationship between convolutional layers and transposed\n+  convolutional layers.\n+* Provide an intuitive understanding of the relationship between input shape,\n+  kernel shape, zero padding, strides and output shape in convolutional and\n+  transposed convolutional layers.\n+\n+Refresher: discrete convolutions\n+================================\n+\n+The bread and butter of neural networks is *affine transformations*: a\n+vector is received as input and is multiplied with a matrix to produce an\n+output (to which a bias vector is usually added before passing the result\n+through a nonlinearity). This is applicable to any type of input, be it an\n+image, a sound clip or an unordered collection of features: whatever their\n+dimensionality, their representation can always be flattened into a vector\n+before the transformation.\n+\n+Images, sound clips and many other similar kinds of data have an intrinsic\n+structure. More formally, they share these important properties:\n+\n+* They are stored as multi-dimensional arrays.\n+* They feature one or more axes for which ordering matters (e.g., width and\n+  height axes for an image, time axis for a sound clip).\n+* One axis, called the channel axis, is used to access different views of the\n+  data (e.g., the red, green and blue channels of a color image, or the left\n+  and right channels of a stereo audio track).\n+\n+These properties are not exploited when an affine transformation is applied; in\n+fact, all the axes are treated in the same way and the topological information\n+is not taken into account. Still, taking advantage of the implicit structure of\n+the data may prove very handy in solving some tasks, like computer vision and\n+speech recognition, and in these cases it would be best to preserve it. This is\n+where discrete convolutions come into play.\n+\n+A discrete convolution is a linear transformation that preserves this notion of\n+ordering. It is sparse (only a few input units contribute to a given output\n+unit) and reuses parameters (the same weights are applied to multiple locations\n+in the input).\n+\n+Here is an example of a discrete convolution:\n+\n+.. figure:: conv_arithmetic_figures/numerical_no_padding_no_strides.gif\n+\n+The light blue grid is called the *input feature map*. To keep the drawing\n+simple, a single input feature map is represented, but it is not uncommon to\n+have multiple feature maps stacked one onto another. (An example of this is\n+what was referred to earlier as *channels* for images and sound clips.)\n+A *kernel* (shaded area) of value\n+\n+.. math::\n+\n+    \\begin{pmatrix}\n+    0 & 1 & 2 \\\\\n+    2 & 2 & 0 \\\\\n+    0 & 1 & 2\n+    \\end{pmatrix}\n+\n+slides across the input feature map. At each location, the product between each\n+element of the kernel and the input element it overlaps is computed and the\n+results are summed up to obtain the output in the current location. The\n+procedure can be repeated using different kernels to form as many output feature\n+maps as desired. The final outputs of this procedure are called *output feature\n+maps*.\n+\n+.. note::\n+\n+    While there is a distinction between convolution and cross-correlation from\n+    a signal processing perspective, the two become interchangeable when the\n+    kernel is learned. For the sake of simplicity and to stay consistent with\n+    most of the machine learning literature, the term *convolution* will be\n+    used in this tutorial.\n+\n+If there are multiple input feature maps, the kernel will have to be\n+3-dimensional -- or, equivalently each one of the feature maps will be\n+convolved with a distinct kernel -- and the resulting feature maps will\n+be summed up elementwise to produce the output feature map.\n+\n+The convolution depicted above is an instance of a 2-D convolution, but it can\n+be generalized to N-D convolutions. For instance, in a 3-D convolution, the\n+kernel would be a *cuboid* and would slide across the height, width and depth\n+of the input feature map.\n+\n+The collection of kernels defining a discrete convolution has a shape\n+corresponding to some permutation of :math:`(n, m, k_1, \\ldots, k_N)`, where\n+\n+.. math::\n+\n+    \\begin{split}\n+        n &\\equiv \\text{number of output feature maps},\\\\\n+        m &\\equiv \\text{number of input feature maps},\\\\\n+        k_j &\\equiv \\text{kernel size along axis $j$}.\n+    \\end{split}\n+\n+The following properties affect the output size :math:`o_j` of a convolutional\n+layer along axis :math:`j`:\n+\n+* :math:`i_j`: input size along axis :math:`j`,\n+* :math:`k_j`: kernel size along axis :math:`j`,\n+* :math:`s_j`: stride (distance between two consecutive positions of the\n+  kernel) along axis :math:`j`,\n+* :math:`p_j`: zero padding (number of zeros concatenated at the beginning and\n+  at the end of an axis) along axis `j`.\n+\n+For instance, here is a :math:`3 \\times 3` kernel applied to a\n+:math:`5 \\times 5` input padded with a :math:`1 \\times 1` border of zeros using\n+:math:`2 \\times 2` strides:\n+\n+.. figure:: conv_arithmetic_figures/numerical_padding_strides.gif\n+\n+The analysis of the relationship between convolutional layer properties is eased\n+by the fact that they don't interact across axes, i.e., the choice of kernel\n+size, stride and zero padding along axis :math:`j` only affects the output size\n+of axis :math:`j`. Because of that, this section will focus on the following\n+simplified setting:\n+\n+* 2-D discrete convolutions (:math:`N = 2`),\n+* square inputs (:math:`i_1 = i_2 = i`),\n+* square kernel size (:math:`k_1 = k_2 = k`),\n+* same strides along both axes (:math:`s_1 = s_2 = s`),\n+* same zero padding along both axes (:math:`p_1 = p_2 = p`).\n+\n+This facilitates the analysis and the visualization, but keep in mind that the\n+results outlined here also generalize to the N-D and non-square cases.\n+\n+Theano terminology\n+==================\n+\n+Theano has its own terminology, which differs slightly from the convolution\n+arithmetic guide's. Here's a simple conversion table for the two:\n+\n++------------------+-------------------------------------------------------------------+\n+| Theano           | Convolution arithmetic                                            |\n++==================+===================================================================+\n+| ``filters``      | 4D collection of kernels                                          |\n++------------------+-------------------------------------------------------------------+\n+| ``input_shape``  | (batch size, input channels, :math:`i_1`, :math:`i_2`)            |\n++------------------+-------------------------------------------------------------------+\n+| ``filter_shape`` | (output channels, input channels, :math:`k_1`, :math:`k_2`)       |\n++------------------+-------------------------------------------------------------------+\n+| ``border_mode``  | ``'valid'``, ``'half'``, ``'full'`` or (:math:`p_1`, :math:`p_2`) |\n++------------------+-------------------------------------------------------------------+\n+| ``subsample``    | (:math:`s_1`, :math:`s_2`)                                        |\n++------------------+-------------------------------------------------------------------+\n+\n+For instance, the convolution shown above would correspond to the following\n+Theano call:\n+\n+.. code-block:: python\n+\n+    output = theano.tensor.nnet.conv2d(\n+        input, filters, input_shape=(5, 5), filter_shape=(1, 1, 3, 3),\n+        border_mode=(1, 1), subsample=(2, 2))\n+\n+Convolution arithmetic\n+======================\n+\n+No zero padding, unit strides\n+-----------------------------\n+\n+The simplest case to analyze is when the kernel just slides across every\n+position of the input (i.e., :math:`s = 1` and :math:`p = 0`).\n+Here is an example for :math:`i = 4` and :math:`k = 3`:\n+\n+.. figure:: conv_arithmetic_figures/no_padding_no_strides.gif\n+\n+One way of defining the output size in this case is by the number of possible\n+placements of the kernel on the input. Let's consider the width axis: the kernel\n+starts on the leftmost part of the input feature map and slides by steps of one\n+until it touches the right side of the input. The size of the output will be\n+equal to the number of steps made, plus one, accounting for the initial position\n+of the kernel. The same logic applies for the height axis.\n+\n+More formally, the following relationship can be inferred:\n+\n+.. admonition:: Relationship 1\n+\n+    .. code-block:: python\n+\n+        output = theano.tensor.nnet.conv2d(\n+            input, filters, input_shape=(i, i), filter_shape=(c1, c2, k, k),\n+            border_mode=(0, 0), subsample=(1, 1))\n+        # output.shape[2] == output.shape[3] == (i - k) + 1\n+\n+Zero padding, unit strides\n+--------------------------\n+\n+To factor in zero padding (i.e., only restricting to :math:`s = 1`), let's\n+consider its effect on the effective input size: padding with :math:`p` zeros\n+changes the effective input size from :math:`i` to :math:`i + 2p`. In the\n+general case, Relationship 1 can then be used to infer the following\n+relationship:\n+\n+.. admonition:: Relationship 2\n+\n+    For any :math:`i`, :math:`k` and :math:`p`, and for :math:`s = 1`,\n+\n+    .. code-block:: python\n+\n+        output = theano.tensor.nnet.conv2d(\n+            input, filters, input_shape=(i, i), filter_shape=(c1, c2, k, k),\n+            border_mode=(p, p), subsample=(1, 1))\n+        # output.shape[2] == output.shape[3] == (i - k) + 2 * p + 1\n+\n+Here is an example for :math:`i = 5`, :math:`k = 4` and :math:`p = 2`:\n+\n+.. figure:: conv_arithmetic_figures/arbitrary_padding_no_strides.gif\n+\n+Special cases\n+-------------\n+\n+In practice, two specific instances of zero padding are used quite extensively\n+because of their respective properties. Let's discuss them in more detail.\n+\n+Half (same) padding\n+^^^^^^^^^^^^^^^^^^^\n+\n+Having the output size be the same as the input size (i.e., :math:`o = i`) can\n+be a desirable property:\n+\n+.. admonition:: Relationship 3\n+\n+    For any :math:`i` and for :math:`k` odd (:math:`k = 2n + 1, \\quad n \\in\n+    \\mathbb{N}`), :math:`s = 1` and :math:`p = \\lfloor k / 2 \\rfloor = n`,\n+\n+    .. code-block:: python\n+\n+        output = theano.tensor.nnet.conv2d(\n+            input, filters, input_shape=(i, i), filter_shape=(c1, c2, k, k),\n+            border_mode='half', subsample=(1, 1))\n+        # output.shape[2] == output.shape[3] == i\n+\n+This is sometimes referred to as *half* (or *same*) padding. Here is an example\n+for :math:`i = 5`, :math:`k = 3` and (therefore) :math:`p = 1`:\n+\n+.. figure:: conv_arithmetic_figures/same_padding_no_strides.gif\n+\n+Full padding\n+^^^^^^^^^^^^\n+\n+While convolving a kernel generally *decreases* the output size with respect to\n+the input size, sometimes the opposite is required. This can be achieved with\n+proper zero padding:\n+\n+.. admonition:: Relationship 4\n+\n+    For any :math:`i` and :math:`k`, and for :math:`p = k - 1` and\n+    :math:`s = 1`,\n+\n+    .. code-block:: python\n+\n+        output = theano.tensor.nnet.conv2d(\n+            input, filters, input_shape=(i, i), filter_shape=(c1, c2, k, k),\n+            border_mode='full', subsample=(1, 1))\n+        # output.shape[2] == output.shape[3] == i + (k - 1)\n+\n+This is sometimes referred to as *full* padding, because in this setting every\n+possible partial or complete superimposition of the kernel on the input feature\n+map is taken into account. Here is an example for :math:`i = 5`, :math:`k = 3`\n+and (therefore) :math:`p = 2`:\n+\n+.. figure:: conv_arithmetic_figures/full_padding_no_strides.gif\n+\n+No zero padding, non-unit strides\n+---------------------------------\n+\n+All relationships derived so far only apply for unit-strided convolutions.\n+Incorporating non unitary strides requires another inference leap. To facilitate\n+the analysis, let's momentarily ignore zero padding (i.e., :math:`s > 1` and\n+:math:`p = 0`). Here is an example for :math:`i = 5`, :math:`k = 3` and :math:`s\n+= 2`:\n+\n+.. figure:: conv_arithmetic_figures/no_padding_strides.gif\n+\n+Once again, the output size can be defined in terms of the number of possible\n+placements of the kernel on the input. Let's consider the width axis: the kernel\n+starts as usual on the leftmost part of the input, but this time it slides by\n+steps of size :math:`s` until it touches the right side of the input. The size\n+of the output is again equal to the number of steps made, plus one, accounting\n+for the initial position of the kernel. The same logic applies for the height\n+axis.\n+\n+From this, the following relationship can be inferred:\n+\n+.. admonition:: Relationship 5\n+\n+    .. code-block:: python\n+\n+        output = theano.tensor.nnet.conv2d(\n+            input, filters, input_shape=(i, i), filter_shape=(c1, c2, k, k),\n+            border_mode=(0, 0), subsample=(s, s))\n+        # output.shape[2] == output.shape[3] == (i - k) // s + 1\n+\n+The floor function accounts for the fact that sometimes the last\n+possible step does *not* coincide with the kernel reaching the end of the\n+input, i.e., some input units are left out.\n+\n+Zero padding, non-unit strides\n+------------------------------\n+\n+The most general case (convolving over a zero padded input using non-unit\n+strides) can be derived by applying Relationship 5 on an effective input of size\n+:math:`i + 2p`, in analogy to what was done for Relationship 2:\n+\n+.. admonition:: Relationship 6\n+\n+    .. code-block:: python\n+\n+        output = theano.tensor.nnet.conv2d(\n+            input, filters, input_shape=(i, i), filter_shape=(c1, c2, k, k),\n+            border_mode=(p, p), subsample=(s, s))\n+        # output.shape[2] == output.shape[3] == (i - k + 2 * p) // s + 1\n+\n+As before, the floor function means that in some cases a convolution will\n+produce the same output size for multiple input sizes. More specifically, if\n+:math:`i + 2p - k` is a multiple of :math:`s`, then any input size :math:`j = i\n++ a, \\quad a \\in \\{0,\\ldots,s - 1\\}` will produce the same output size. Note\n+that this ambiguity applies only for :math:`s > 1`.\n+\n+Here is an example for :math:`i = 5`, :math:`k = 3`, :math:`s = 2` and :math:`p\n+= 1`:\n+\n+.. figure:: conv_arithmetic_figures/padding_strides.gif\n+\n+Here is an example for :math:`i = 6`, :math:`k = 3`, :math:`s = 2` and :math:`p\n+= 1`:\n+\n+.. figure:: conv_arithmetic_figures/padding_strides_odd.gif\n+\n+Interestingly, despite having different input sizes these convolutions share the\n+same output size. While this doesn't affect the analysis for *convolutions*,\n+this will complicate the analysis in the case of *transposed convolutions*.\n+\n+Transposed convolution arithmetic\n+=================================\n+\n+The need for transposed convolutions generally arises from the desire to use a\n+transformation going in the opposite direction of a normal convolution, i.e.,\n+from something that has the shape of the output of some convolution to\n+something that has the shape of its input while maintaining a connectivity\n+pattern that is compatible with said convolution. For instance, one might use\n+such a transformation as the decoding layer of a convolutional autoencoder or to\n+project feature maps to a higher-dimensional space.\n+\n+Once again, the convolutional case is considerably more complex than the\n+fully-connected case, which only requires to use a weight matrix whose shape\n+has been transposed. However, since every convolution boils down to an\n+efficient implementation of a matrix operation, the insights gained from the\n+fully-connected case are useful in solving the convolutional case.\n+\n+Like for convolution arithmetic, the dissertation about transposed convolution\n+arithmetic is simplified by the fact that transposed convolution properties\n+don't interact across axes.\n+\n+This section will focus on the following setting:\n+\n+* 2-D transposed convolutions (:math:`N = 2`),\n+* square inputs (:math:`i_1 = i_2 = i`),\n+* square kernel size (:math:`k_1 = k_2 = k`),\n+* same strides along both axes (:math:`s_1 = s_2 = s`),\n+* same zero padding along both axes (:math:`p_1 = p_2 = p`).\n+\n+Once again, the results outlined generalize to the N-D and non-square cases.\n+\n+Convolution as a matrix operation\n+---------------------------------\n+\n+Take for example the convolution presented in the *No zero padding, unit\n+strides* subsection:\n+\n+.. figure:: conv_arithmetic_figures/no_padding_no_strides.gif\n+\n+If the input and output were to be unrolled into vectors from left to right, top\n+to bottom, the convolution could be represented as a sparse matrix\n+:math:`\\mathbf{C}` where the non-zero elements are the elements :math:`w_{i,j}`\n+of the kernel (with :math:`i` and :math:`j` being the row and column of the\n+kernel respectively):\n+\n+.. math::\n+\n+    \\begin{pmatrix}\n+    w_{0,0} & 0       & 0       & 0       \\\\\n+    w_{0,1} & w_{0,0} & 0       & 0       \\\\\n+    w_{0,2} & w_{0,1} & 0       & 0       \\\\\n+    0       & w_{0,2} & 0       & 0       \\\\\n+    w_{1,0} & 0       & w_{0,0} & 0       \\\\\n+    w_{1,1} & w_{1,0} & w_{0,1} & w_{0,0} \\\\\n+    w_{1,2} & w_{1,1} & w_{0,2} & w_{0,1} \\\\\n+    0       & w_{1,2} & 0       & w_{0,2} \\\\\n+    w_{2,0} & 0       & w_{1,0} & 0       \\\\\n+    w_{2,1} & w_{2,0} & w_{1,1} & w_{1,0} \\\\\n+    w_{2,2} & w_{2,1} & w_{1,2} & w_{1,1} \\\\\n+    0       & w_{2,2} & 0       & w_{1,2} \\\\\n+    0       & 0       & w_{2,0} & 0       \\\\\n+    0       & 0       & w_{2,1} & w_{2,0} \\\\\n+    0       & 0       & w_{2,2} & w_{2,1} \\\\\n+    0       & 0       & 0       & w_{2,2} \\\\\n+    \\end{pmatrix}^T\n+\n+(*Note: the matrix has been transposed for formatting purposes.*) This linear\n+operation takes the input matrix flattened as a 16-dimensional vector and\n+produces a 4-dimensional vector that is later reshaped as the :math:`2 \\times 2`\n+output matrix.\n+\n+Using this representation, the backward pass is easily obtained by transposing\n+:math:`\\mathbf{C}`; in other words, the error is backpropagated by multiplying\n+the loss with :math:`\\mathbf{C}^T`. This operation takes a 4-dimensional vector\n+as input and produces a 16-dimensional vector as output, and its connectivity\n+pattern is compatible with :math:`\\mathbf{C}` by construction.\n+\n+Notably, the kernel :math:`\\mathbf{w}` defines both the matrices\n+:math:`\\mathbf{C}` and :math:`\\mathbf{C}^T` used for the forward and backward\n+passes.\n+\n+Transposed convolution\n+----------------------\n+\n+Let's now consider what would be required to go the other way around, i.e., map\n+from a 4-dimensional space to a 16-dimensional space, while keeping the\n+connectivity pattern of the convolution depicted above. This operation is known\n+as a *transposed convolution*.\n+\n+Transposed convolutions -- also called *fractionally strided convolutions* --\n+work by swapping the forward and backward passes of a convolution. One way to\n+put it is to note that the kernel defines a convolution, but whether it's a\n+direct convolution or a transposed convolution is determined by how the forward\n+and backward passes are computed.\n+\n+For instance, although the kernel :math:`\\mathbf{w}` defines a convolution whose\n+forward and backward passes are computed by multiplying with :math:`\\mathbf{C}` and\n+:math:`\\mathbf{C}^T` respectively, it *also* defines a transposed convolution\n+whose forward and backward passes are computed by multiplying with\n+:math:`\\mathbf{C}^T` and :math:`(\\mathbf{C}^T)^T = \\mathbf{C}` respectively.\n+\n+.. note::\n+\n+    The transposed convolution operation can be thought of as the gradient of\n+    *some* convolution with respect to its input, which is usually how\n+    transposed convolutions are implemented in practice.\n+\n+Finally note that it is always possible to emulate a transposed convolution with\n+a direct convolution. The disadvantage is that it usually involves adding many\n+columns and rows of zeros to the input, resulting in a much less efficient\n+implementation.\n+\n+Building on what has been introduced so far, this section will proceed somewhat\n+backwards with respect to the convolution arithmetic section, deriving the\n+properties of each transposed convolution by referring to the direct\n+convolution with which it shares the kernel, and defining the equivalent direct\n+convolution.\n+\n+No zero padding, unit strides, transposed\n+-----------------------------------------\n+\n+The simplest way to think about a transposed convolution is by computing the\n+output shape of the direct convolution for a given input shape first, and then\n+inverting the input and output shapes for the transposed convolution.\n+\n+Let's consider the convolution of a :math:`3 \\times 3` kernel on a :math:`4\n+\\times 4` input with unitary stride and no padding (i.e., :math:`i = 4`,\n+:math:`k = 3`, :math:`s = 1` and :math:`p = 0`). As depicted in the convolution\n+above, this produces a :math:`2 \\times 2` output.  The transpose of this\n+convolution will then have an output of shape :math:`4 \\times 4` when applied on\n+a :math:`2 \\times 2` input.\n+\n+Another way to obtain the result of a transposed convolution is to apply an\n+equivalent -- but much less efficient -- direct convolution. The example\n+described so far could be tackled by convolving a :math:`3 \\times 3` kernel over\n+a :math:`2 \\times 2` input padded with a :math:`2 \\times 2` border of zeros\n+using unit strides (i.e., :math:`i' = 2`, :math:`k' = k`, :math:`s' = 1` and\n+:math:`p' = 2`), as shown here:\n+\n+.. figure:: conv_arithmetic_figures/no_padding_no_strides_transposed.gif\n+\n+Notably, the kernel's and stride's sizes remain the same, but the input of the\n+transposed convolution is now zero padded.\n+\n+.. note::\n+\n+    Although equivalent to applying the transposed matrix, this visualization\n+    adds a lot of zero multiplications in the form of zero padding. This is done\n+    here for illustration purposes, but it is inefficient, and software\n+    implementations will normally not perform the useless zero multiplications.\n+\n+One way to understand the logic behind zero padding is to consider the\n+connectivity pattern of the transposed convolution and use it to guide the\n+design of the equivalent convolution. For example, the top left pixel of the\n+input of the direct convolution only contribute to the top left pixel of the\n+output, the top right pixel is only connected to the top right output pixel,\n+and so on.\n+\n+To maintain the same connectivity pattern in the equivalent convolution it is\n+necessary to zero pad the input in such a way that the first (top-left)\n+application of the kernel only touches the top-left pixel, i.e., the padding\n+has to be equal to the size of the kernel minus one.\n+\n+Proceeding in the same fashion it is possible to determine similar observations\n+for the other elements of the image, giving rise to the following relationship:\n+\n+.. admonition:: Relationship 7\n+\n+    A convolution described by :math:`s = 1`, :math:`p = 0` and :math:`k` has an\n+    associated transposed convolution described by :math:`k' = k`, :math:`s' =\n+    s` and :math:`p' = k - 1` and its output size is\n+\n+    .. math::\n+\n+        o' = i' + (k - 1).\n+\n+    In other words,\n+\n+    .. code-block:: python\n+\n+        input = theano.tensor.nnet.conv2d_grad_wrt_inputs(\n+            output, filters, filter_shape=(c1, c2, k, k), border_mode=(0, 0),\n+            subsample=(1, 1))\n+        # input.shape[2] == input.shape[3] == output.shape[2] + (k - 1)\n+\n+Interestingly, this corresponds to a fully padded convolution with unit strides.\n+\n+Zero padding, unit strides, transposed\n+--------------------------------------\n+\n+Knowing that the transpose of a non-padded convolution is equivalent to\n+convolving a zero padded input, it would be reasonable to suppose that the\n+transpose of a zero padded convolution is equivalent to convolving an input\n+padded with *less* zeros.\n+\n+It is indeed the case, as shown in here for :math:`i = 5`, :math:`k = 4` and\n+:math:`p = 2`:\n+\n+.. figure:: conv_arithmetic_figures/arbitrary_padding_no_strides_transposed.gif\n+\n+Formally, the following relationship applies for zero padded convolutions:\n+\n+.. admonition:: Relationship 8\n+\n+    A convolution described by :math:`s = 1`, :math:`k` and :math:`p` has an\n+    associated transposed convolution described by :math:`k' = k`, :math:`s' =\n+    s` and :math:`p' = k - p - 1` and its output size is\n+\n+    .. math::\n+\n+        o' = i' + (k - 1) - 2p.\n+\n+    In other words,\n+\n+    .. code-block:: python\n+\n+        input = theano.tensor.nnet.conv2d_grad_wrt_inputs(\n+            output, filters, filter_shape=(c1, c2, k, k), border_mode=(p, p),\n+            subsample=(1, 1))\n+        # input.shape[2] == input.shape[3] == output.shape[2] + (k - 1) - 2 * p\n+\n+Special cases\n+-------------\n+\n+Half (same) padding, transposed\n+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n+\n+By applying the same inductive reasoning as before, it is reasonable to expect\n+that the equivalent convolution of the transpose of a half padded convolution\n+is itself a half padded convolution, given that the output size of a half\n+padded convolution is the same as its input size. Thus the following relation\n+applies:\n+\n+.. admonition:: Relationship 9\n+\n+    A convolution described by :math:`k = 2n + 1, \\quad n \\in \\mathbb{N}`,\n+    :math:`s = 1` and :math:`p = \\lfloor k / 2 \\rfloor = n` has an associated\n+    transposed convolution described by :math:`k' = k`, :math:`s' = s` and\n+    :math:`p' = p` and its output size is\n+\n+    .. math::\n+\n+        \\begin{split}\n+            o' &= i' + (k - 1) - 2p \\\\\n+               &= i' + 2n - 2n \\\\\n+               &= i'.\n+        \\end{split}\n+\n+    In other words,\n+\n+    .. code-block:: python\n+\n+        input = theano.tensor.nnet.conv2d_grad_wrt_inputs(\n+            output, filters, filter_shape=(c1, c2, k, k), border_mode='half',\n+            subsample=(1, 1))\n+        # input.shape[2] == input.shape[3] == output.shape[2]\n+\n+Here is an example for :math:`i = 5`, :math:`k = 3` and (therefore) :math:`p =\n+1`:\n+\n+.. figure:: conv_arithmetic_figures/same_padding_no_strides_transposed.gif\n+\n+Full padding, transposed\n+^^^^^^^^^^^^^^^^^^^^^^^^\n+\n+Knowing that the equivalent convolution of the transpose of a non-padded\n+convolution involves full padding, it is unsurprising that the equivalent of\n+the transpose of a fully padded convolution is a non-padded convolution:\n+\n+.. admonition:: Relationship 10\n+\n+    A convolution described by :math:`s = 1`, :math:`k` and :math:`p = k - 1`\n+    has an associated transposed convolution described by :math:`k' = k`,\n+    :math:`s' = s` and :math:`p' = 0` and its output size is\n+\n+    .. math::\n+\n+        \\begin{split}\n+            o' &= i' + (k - 1) - 2p \\\\\n+               &= i' - (k - 1)\n+        \\end{split}\n+\n+    In other words,\n+\n+    .. code-block:: python\n+\n+        input = theano.tensor.nnet.conv2d_grad_wrt_inputs(\n+            output, filters, filter_shape=(c1, c2, k, k), border_mode='full',\n+            subsample=(1, 1))\n+        # input.shape[2] == input.shape[3] == output.shape[2] - (k - 1)\n+\n+Here is an example for :math:`i = 5`, :math:`k = 3` and (therefore) :math:`p =\n+2`:\n+\n+.. figure:: conv_arithmetic_figures/full_padding_no_strides_transposed.gif\n+\n+No zero padding, non-unit strides, transposed\n+---------------------------------------------\n+\n+Using the same kind of inductive logic as for zero padded convolutions, one\n+might expect that the transpose of a convolution with :math:`s > 1` involves an\n+equivalent convolution with :math:`s < 1`. As will be explained, this is a valid\n+intuition, which is why transposed convolutions are sometimes called\n+*fractionally strided convolutions*.\n+\n+Here is an example for :math:`i = 5`, :math:`k = 3` and :math:`s = 2`:\n+\n+.. figure:: conv_arithmetic_figures/no_padding_strides_transposed.gif\n+\n+This should help understand what fractional strides involve: zeros\n+are inserted *between* input units, which makes the kernel move around at\n+a slower pace than with unit strides.\n+\n+.. note::\n+\n+    Doing so is inefficient and real-world implementations avoid useless\n+    multiplications by zero, but conceptually it is how the transpose of a\n+    strided convolution can be thought of.\n+\n+For the moment, it will be assumed that the convolution is non-padded (:math:`p\n+= 0`) and that its input size :math:`i` is such that :math:`i - k` is a multiple\n+of :math:`s`. In that case, the following relationship holds:\n+\n+.. admonition:: Relationship 11\n+\n+    A convolution described by :math:`p = 0`, :math:`k` and :math:`s` and whose\n+    input size is such that :math:`i - k` is a multiple of :math:`s`, has an\n+    associated transposed convolution described by :math:`\\tilde{i}'`, :math:`k'\n+    = k`, :math:`s' = 1` and :math:`p' = k - 1`, where :math:`\\tilde{i}'` is the\n+    size of the stretched input obtained by adding :math:`s - 1` zeros between\n+    each input unit, and its output size is\n+\n+    .. math::\n+\n+        o' = s (i' - 1) + k.\n+\n+    In other words,\n+\n+    .. code-block:: python\n+\n+        input = theano.tensor.nnet.conv2d_grad_wrt_inputs(\n+            output, filters, filter_shape=(c1, c2, k, k), border_mode=(0, 0),\n+            subsample=(s, s))\n+        # input.shape[2] == input.shape[3] == s * (output.shape[2] - 1) + k\n+\n+Zero padding, non-unit strides, transposed\n+------------------------------------------\n+\n+When the convolution's input size :math:`i` is such that :math:`i + 2p - k` is a\n+multiple of :math:`s`, the analysis can extended to the zero padded case by\n+combining Relationship 8 and Relationship 11:\n+\n+.. admonition:: Relationship 12\n+\n+    A convolution described by :math:`k`, :math:`s` and :math:`p` and whose\n+    input size :math:`i` is such that :math:`i + 2p - k` is a multiple of\n+    :math:`s` has an associated transposed convolution described by\n+    :math:`\\tilde{i}'`, :math:`k' = k`, :math:`s' = 1` and :math:`p' = k - p -\n+    1`, where :math:`\\tilde{i}'` is the size of the stretched input obtained by\n+    adding :math:`s - 1` zeros between each input unit, and its output size is\n+\n+    .. math::\n+\n+        o' = s (i' - 1) + k - 2p.","path":"doc/tutorial/conv_arithmetic.txt","position":783,"original_position":783,"commit_id":"6c9baa2233386c874c4bf83f717200f01f89b43d","original_commit_id":"6c9baa2233386c874c4bf83f717200f01f89b43d","user":{"login":"fvisin","id":4639261,"avatar_url":"https://avatars.githubusercontent.com/u/4639261?v=3","gravatar_id":"","url":"https://api.github.com/users/fvisin","html_url":"https://github.com/fvisin","followers_url":"https://api.github.com/users/fvisin/followers","following_url":"https://api.github.com/users/fvisin/following{/other_user}","gists_url":"https://api.github.com/users/fvisin/gists{/gist_id}","starred_url":"https://api.github.com/users/fvisin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fvisin/subscriptions","organizations_url":"https://api.github.com/users/fvisin/orgs","repos_url":"https://api.github.com/users/fvisin/repos","events_url":"https://api.github.com/users/fvisin/events{/privacy}","received_events_url":"https://api.github.com/users/fvisin/received_events","type":"User","site_admin":false},"body":"I don't want to do the math: are you sure it's not `\\tilde{i}'`?","created_at":"2016-06-10T01:01:49Z","updated_at":"2016-06-10T01:01:49Z","html_url":"https://github.com/Theano/Theano/pull/4068#discussion_r66547703","pull_request_url":"https://api.github.com/repos/Theano/Theano/pulls/4068","_links":{"self":{"href":"https://api.github.com/repos/Theano/Theano/pulls/comments/66547703"},"html":{"href":"https://github.com/Theano/Theano/pull/4068#discussion_r66547703"},"pull_request":{"href":"https://api.github.com/repos/Theano/Theano/pulls/4068"}}},"pull_request":{"url":"https://api.github.com/repos/Theano/Theano/pulls/4068","id":59546966,"html_url":"https://github.com/Theano/Theano/pull/4068","diff_url":"https://github.com/Theano/Theano/pull/4068.diff","patch_url":"https://github.com/Theano/Theano/pull/4068.patch","issue_url":"https://api.github.com/repos/Theano/Theano/issues/4068","number":4068,"state":"open","locked":false,"title":"[WIP] Convolution shape tutorial","user":{"login":"vdumoulin","id":2034067,"avatar_url":"https://avatars.githubusercontent.com/u/2034067?v=3","gravatar_id":"","url":"https://api.github.com/users/vdumoulin","html_url":"https://github.com/vdumoulin","followers_url":"https://api.github.com/users/vdumoulin/followers","following_url":"https://api.github.com/users/vdumoulin/following{/other_user}","gists_url":"https://api.github.com/users/vdumoulin/gists{/gist_id}","starred_url":"https://api.github.com/users/vdumoulin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/vdumoulin/subscriptions","organizations_url":"https://api.github.com/users/vdumoulin/orgs","repos_url":"https://api.github.com/users/vdumoulin/repos","events_url":"https://api.github.com/users/vdumoulin/events{/privacy}","received_events_url":"https://api.github.com/users/vdumoulin/received_events","type":"User","site_admin":false},"body":"@fvisin I decided to open the PR early to get you feedback as I go.\r\n\r\nI completed most of the \"convolution\" part of the tutorial and will start working on the \"convolution transpose\" part in the near future.","created_at":"2016-02-16T20:12:03Z","updated_at":"2016-06-10T01:01:49Z","closed_at":null,"merged_at":null,"merge_commit_sha":"6c1da19914c37ecff06cfa0d6cd621f0d32eb814","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/Theano/Theano/pulls/4068/commits","review_comments_url":"https://api.github.com/repos/Theano/Theano/pulls/4068/comments","review_comment_url":"https://api.github.com/repos/Theano/Theano/pulls/comments{/number}","comments_url":"https://api.github.com/repos/Theano/Theano/issues/4068/comments","statuses_url":"https://api.github.com/repos/Theano/Theano/statuses/6c9baa2233386c874c4bf83f717200f01f89b43d","head":{"label":"vdumoulin:conv_arithmetic","ref":"conv_arithmetic","sha":"6c9baa2233386c874c4bf83f717200f01f89b43d","user":{"login":"vdumoulin","id":2034067,"avatar_url":"https://avatars.githubusercontent.com/u/2034067?v=3","gravatar_id":"","url":"https://api.github.com/users/vdumoulin","html_url":"https://github.com/vdumoulin","followers_url":"https://api.github.com/users/vdumoulin/followers","following_url":"https://api.github.com/users/vdumoulin/following{/other_user}","gists_url":"https://api.github.com/users/vdumoulin/gists{/gist_id}","starred_url":"https://api.github.com/users/vdumoulin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/vdumoulin/subscriptions","organizations_url":"https://api.github.com/users/vdumoulin/orgs","repos_url":"https://api.github.com/users/vdumoulin/repos","events_url":"https://api.github.com/users/vdumoulin/events{/privacy}","received_events_url":"https://api.github.com/users/vdumoulin/received_events","type":"User","site_admin":false},"repo":{"id":10107342,"name":"Theano","full_name":"vdumoulin/Theano","owner":{"login":"vdumoulin","id":2034067,"avatar_url":"https://avatars.githubusercontent.com/u/2034067?v=3","gravatar_id":"","url":"https://api.github.com/users/vdumoulin","html_url":"https://github.com/vdumoulin","followers_url":"https://api.github.com/users/vdumoulin/followers","following_url":"https://api.github.com/users/vdumoulin/following{/other_user}","gists_url":"https://api.github.com/users/vdumoulin/gists{/gist_id}","starred_url":"https://api.github.com/users/vdumoulin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/vdumoulin/subscriptions","organizations_url":"https://api.github.com/users/vdumoulin/orgs","repos_url":"https://api.github.com/users/vdumoulin/repos","events_url":"https://api.github.com/users/vdumoulin/events{/privacy}","received_events_url":"https://api.github.com/users/vdumoulin/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/vdumoulin/Theano","description":"Optimizing GPU-meta-programming code generating array oriented optimizing math compiler in Python","fork":true,"url":"https://api.github.com/repos/vdumoulin/Theano","forks_url":"https://api.github.com/repos/vdumoulin/Theano/forks","keys_url":"https://api.github.com/repos/vdumoulin/Theano/keys{/key_id}","collaborators_url":"https://api.github.com/repos/vdumoulin/Theano/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/vdumoulin/Theano/teams","hooks_url":"https://api.github.com/repos/vdumoulin/Theano/hooks","issue_events_url":"https://api.github.com/repos/vdumoulin/Theano/issues/events{/number}","events_url":"https://api.github.com/repos/vdumoulin/Theano/events","assignees_url":"https://api.github.com/repos/vdumoulin/Theano/assignees{/user}","branches_url":"https://api.github.com/repos/vdumoulin/Theano/branches{/branch}","tags_url":"https://api.github.com/repos/vdumoulin/Theano/tags","blobs_url":"https://api.github.com/repos/vdumoulin/Theano/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/vdumoulin/Theano/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/vdumoulin/Theano/git/refs{/sha}","trees_url":"https://api.github.com/repos/vdumoulin/Theano/git/trees{/sha}","statuses_url":"https://api.github.com/repos/vdumoulin/Theano/statuses/{sha}","languages_url":"https://api.github.com/repos/vdumoulin/Theano/languages","stargazers_url":"https://api.github.com/repos/vdumoulin/Theano/stargazers","contributors_url":"https://api.github.com/repos/vdumoulin/Theano/contributors","subscribers_url":"https://api.github.com/repos/vdumoulin/Theano/subscribers","subscription_url":"https://api.github.com/repos/vdumoulin/Theano/subscription","commits_url":"https://api.github.com/repos/vdumoulin/Theano/commits{/sha}","git_commits_url":"https://api.github.com/repos/vdumoulin/Theano/git/commits{/sha}","comments_url":"https://api.github.com/repos/vdumoulin/Theano/comments{/number}","issue_comment_url":"https://api.github.com/repos/vdumoulin/Theano/issues/comments{/number}","contents_url":"https://api.github.com/repos/vdumoulin/Theano/contents/{+path}","compare_url":"https://api.github.com/repos/vdumoulin/Theano/compare/{base}...{head}","merges_url":"https://api.github.com/repos/vdumoulin/Theano/merges","archive_url":"https://api.github.com/repos/vdumoulin/Theano/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/vdumoulin/Theano/downloads","issues_url":"https://api.github.com/repos/vdumoulin/Theano/issues{/number}","pulls_url":"https://api.github.com/repos/vdumoulin/Theano/pulls{/number}","milestones_url":"https://api.github.com/repos/vdumoulin/Theano/milestones{/number}","notifications_url":"https://api.github.com/repos/vdumoulin/Theano/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/vdumoulin/Theano/labels{/name}","releases_url":"https://api.github.com/repos/vdumoulin/Theano/releases{/id}","deployments_url":"https://api.github.com/repos/vdumoulin/Theano/deployments","created_at":"2013-05-16T18:13:39Z","updated_at":"2015-04-28T19:03:56Z","pushed_at":"2016-06-07T15:05:40Z","git_url":"git://github.com/vdumoulin/Theano.git","ssh_url":"git@github.com:vdumoulin/Theano.git","clone_url":"https://github.com/vdumoulin/Theano.git","svn_url":"https://github.com/vdumoulin/Theano","homepage":"http://www.deeplearning.net/software/theano","size":55522,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"Theano:master","ref":"master","sha":"7176a3d1dee4f290ce7e34b2fcdde53bfb7c131a","user":{"login":"Theano","id":970517,"avatar_url":"https://avatars.githubusercontent.com/u/970517?v=3","gravatar_id":"","url":"https://api.github.com/users/Theano","html_url":"https://github.com/Theano","followers_url":"https://api.github.com/users/Theano/followers","following_url":"https://api.github.com/users/Theano/following{/other_user}","gists_url":"https://api.github.com/users/Theano/gists{/gist_id}","starred_url":"https://api.github.com/users/Theano/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Theano/subscriptions","organizations_url":"https://api.github.com/users/Theano/orgs","repos_url":"https://api.github.com/users/Theano/repos","events_url":"https://api.github.com/users/Theano/events{/privacy}","received_events_url":"https://api.github.com/users/Theano/received_events","type":"Organization","site_admin":false},"repo":{"id":2183193,"name":"Theano","full_name":"Theano/Theano","owner":{"login":"Theano","id":970517,"avatar_url":"https://avatars.githubusercontent.com/u/970517?v=3","gravatar_id":"","url":"https://api.github.com/users/Theano","html_url":"https://github.com/Theano","followers_url":"https://api.github.com/users/Theano/followers","following_url":"https://api.github.com/users/Theano/following{/other_user}","gists_url":"https://api.github.com/users/Theano/gists{/gist_id}","starred_url":"https://api.github.com/users/Theano/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Theano/subscriptions","organizations_url":"https://api.github.com/users/Theano/orgs","repos_url":"https://api.github.com/users/Theano/repos","events_url":"https://api.github.com/users/Theano/events{/privacy}","received_events_url":"https://api.github.com/users/Theano/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/Theano/Theano","description":"Theano is a Python library that allows you to define, optimize, and evaluate mathematical expressions involving multi-dimensional arrays efficiently. It can use GPUs and perform efficient symbolic differentiation.","fork":false,"url":"https://api.github.com/repos/Theano/Theano","forks_url":"https://api.github.com/repos/Theano/Theano/forks","keys_url":"https://api.github.com/repos/Theano/Theano/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Theano/Theano/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Theano/Theano/teams","hooks_url":"https://api.github.com/repos/Theano/Theano/hooks","issue_events_url":"https://api.github.com/repos/Theano/Theano/issues/events{/number}","events_url":"https://api.github.com/repos/Theano/Theano/events","assignees_url":"https://api.github.com/repos/Theano/Theano/assignees{/user}","branches_url":"https://api.github.com/repos/Theano/Theano/branches{/branch}","tags_url":"https://api.github.com/repos/Theano/Theano/tags","blobs_url":"https://api.github.com/repos/Theano/Theano/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Theano/Theano/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Theano/Theano/git/refs{/sha}","trees_url":"https://api.github.com/repos/Theano/Theano/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Theano/Theano/statuses/{sha}","languages_url":"https://api.github.com/repos/Theano/Theano/languages","stargazers_url":"https://api.github.com/repos/Theano/Theano/stargazers","contributors_url":"https://api.github.com/repos/Theano/Theano/contributors","subscribers_url":"https://api.github.com/repos/Theano/Theano/subscribers","subscription_url":"https://api.github.com/repos/Theano/Theano/subscription","commits_url":"https://api.github.com/repos/Theano/Theano/commits{/sha}","git_commits_url":"https://api.github.com/repos/Theano/Theano/git/commits{/sha}","comments_url":"https://api.github.com/repos/Theano/Theano/comments{/number}","issue_comment_url":"https://api.github.com/repos/Theano/Theano/issues/comments{/number}","contents_url":"https://api.github.com/repos/Theano/Theano/contents/{+path}","compare_url":"https://api.github.com/repos/Theano/Theano/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Theano/Theano/merges","archive_url":"https://api.github.com/repos/Theano/Theano/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Theano/Theano/downloads","issues_url":"https://api.github.com/repos/Theano/Theano/issues{/number}","pulls_url":"https://api.github.com/repos/Theano/Theano/pulls{/number}","milestones_url":"https://api.github.com/repos/Theano/Theano/milestones{/number}","notifications_url":"https://api.github.com/repos/Theano/Theano/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Theano/Theano/labels{/name}","releases_url":"https://api.github.com/repos/Theano/Theano/releases{/id}","deployments_url":"https://api.github.com/repos/Theano/Theano/deployments","created_at":"2011-08-10T03:48:06Z","updated_at":"2016-06-09T23:49:29Z","pushed_at":"2016-06-09T22:18:19Z","git_url":"git://github.com/Theano/Theano.git","ssh_url":"git@github.com:Theano/Theano.git","clone_url":"https://github.com/Theano/Theano.git","svn_url":"https://github.com/Theano/Theano","homepage":"http://www.deeplearning.net/software/theano","size":73653,"stargazers_count":3904,"watchers_count":3904,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1420,"mirror_url":null,"open_issues_count":684,"forks":1420,"open_issues":684,"watchers":3904,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/Theano/Theano/pulls/4068"},"html":{"href":"https://github.com/Theano/Theano/pull/4068"},"issue":{"href":"https://api.github.com/repos/Theano/Theano/issues/4068"},"comments":{"href":"https://api.github.com/repos/Theano/Theano/issues/4068/comments"},"review_comments":{"href":"https://api.github.com/repos/Theano/Theano/pulls/4068/comments"},"review_comment":{"href":"https://api.github.com/repos/Theano/Theano/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/Theano/Theano/pulls/4068/commits"},"statuses":{"href":"https://api.github.com/repos/Theano/Theano/statuses/6c9baa2233386c874c4bf83f717200f01f89b43d"}}}}
{"actor":{"display_login":"fvisin"}}