<jupyter_start><jupyter_text>Convolutions for Images:label:`chapter_conv_layer`Now that we understand how convolutional layers work in theory,we are ready to see how this works in practice.Since we have motivated convolutional neural networksby their applicability to image data,we will stick with image data in our examples,and begin by revisiting the convolutional layerthat we introduced in the previous section.We note that strictly speaking, *convolutional* layers are a slight misnomer,since the operations are typically expressed as cross correlations. The Cross-Correlation OperatorIn a convolutional layer, an input arrayand a correlation kernel array are combinedto produce an output array through a cross-correlation operation.Let's see how this works for two dimensions.In our example, the input is a two-dimensional arraywith a height of 3 and width of 3.We mark the shape of the array as $3 \times 3$ or (3, 3).The height and width of the kernel array are both 2.Common names for this array in the deep learning research communityinclude *kernel* and *filter*.The shape of the kernel window (also known as the convolution window)is given precisely by the height and width of the kernel(here it is $2 \times 2$).![Two-dimensional cross-correlation operation. The shaded portions are the first output element and the input and kernel array elements used in its computation: $0\times0+1\times1+3\times2+4\times3=19$. ](../img/correlation.svg)In the two-dimensional cross-correlation operation,we begin with the convolution window positionedat the top-left corner of the input arrayand slide it across the input array,both from left to right and top to bottom.When the convolution window slides to a certain position,the input subarray contained in that windowand the kernel array are multiplied (element-wise)and the resulting array is summed upyielding a single scalar value.This result is precisely the value of the output arrayat the corresponding location.Here, the output array has a height of 2 and width of 2and the four elements are derived fromthe two-dimensional cross-correlation operation:$$0\times0+1\times1+3\times2+4\times3=19,\\1\times0+2\times1+4\times2+5\times3=25,\\3\times0+4\times1+6\times2+7\times3=37,\\4\times0+5\times1+7\times2+8\times3=43.$$Note that along each axis, the output is slightly *smaller* than the input.Because the kernel has a width greater than one,and we can only computer the cross-correlationfor locations where the kernel fits wholly within the image,the output size is given by the input size $H \times W$minus the size of the convolutional kernel $h \times w$via $(H-h+1) \times (W-w+1)$.This is the case since we need enough spaceto 'shift' the convolutional kernel across the image(later we will see how to keep the size unchangedby padding the image with zeros around its boundarysuch that there's enough space to shift the kernel).Next, we implement the above process in the `corr2d` function.It accepts the input array `X` with the kernel array `K`and outputs the array `Y`.<jupyter_code>from mxnet import autograd, np, npx
from mxnet.gluon import nn
npx.set_np()

# Save to the d2l package. 
def corr2d(X, K):
    """Compute 2D cross-correlation."""
    h, w = K.shape
    Y = np.zeros((X.shape[0] - h + 1, X.shape[1] - w + 1))
    for i in range(Y.shape[0]):
        for j in range(Y.shape[1]):
            Y[i, j] = (X[i: i + h, j: j + w] * K).sum()
    return Y<jupyter_output><empty_output><jupyter_text>We can construct the input array `X` and the kernel array `K`from the figure aboveto validate the output of the above implementationsof the two-dimensional cross-correlation operation.<jupyter_code>X = np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])
K = np.array([[0, 1], [2, 3]])
corr2d(X, K)<jupyter_output><empty_output><jupyter_text>Convolutional LayersA convolutional layer cross-correlates the input and kernelsand adds a scalar bias to produce an output.The parameters of the convolutional layerare precisely the values that constitute the kernel and the scalar bias.When training the models based on convolutional layers,we typically initialize the kernels randomly,just as we would with a fully-connected layer.We are now ready to implement a two-dimensional convolutional layerbased on the `corr2d` function defined above.In the `__init__` constructor function,we declare `weight` and `bias` as the two model parameters.The forward computation function `forward`calls the `corr2d` function and adds the bias.As with $h \times w$ cross-correlationwe also refer to convolutional layersas $h \times w$ convolutions.<jupyter_code>class Conv2D(nn.Block):
    def __init__(self, kernel_size, **kwargs):
        super(Conv2D, self).__init__(**kwargs)
        self.weight = self.params.get('weight', shape=kernel_size)
        self.bias = self.params.get('bias', shape=(1,))

    def forward(self, x):
        return corr2d(x, self.weight.data()) + self.bias.data()<jupyter_output><empty_output><jupyter_text>Object Edge Detection in ImagesLet's look at a simple application of a convolutional layer:detecting the edge of an object in an imageby finding the location of the pixel change.First, we construct an 'image' of $6\times 8$ pixels.The middle four columns are black (0) and the rest are white (1).<jupyter_code>X = np.ones((6, 8))
X[:, 2:6] = 0
X<jupyter_output><empty_output><jupyter_text>Next, we construct a kernel `K` with a height of 1 and width of 2.When we perform the cross-correlation operation with the input,if the horizontally adjacent elements are the same,the output is 0. Otherwise, the output is non-zero.<jupyter_code>K = np.array([[1, -1]])<jupyter_output><empty_output><jupyter_text>Enter `X` and our designed kernel `K`to perform the cross-correlation operations.As you can see, we will detect 1 for the edge from white to blackand -1 for the edge from black to white.The rest of the outputs are 0.<jupyter_code>Y = corr2d(X, K)
Y<jupyter_output><empty_output><jupyter_text>Let's apply the kernel to the transposed image.As expected, it vanishes. The kernel `K` only detects vertical edges.<jupyter_code>corr2d(X.T, K)<jupyter_output><empty_output><jupyter_text>Learning a KernelDesigning an edge detector by finite differences `[1, -1]` is neatif we know this is precisely what we are looking for.However, as we look at larger kernels,and consider successive layers of convolutions,it might be impossible to specifyprecisely what each filter should be doing manually.Now let's see whether we can learn the kernel that generated `Y` from `X`by looking at the (input, output) pairs only.We first construct a convolutional layerand initialize its kernel as a random array.Next, in each iteration, we will use the squared errorto compare `Y` and the output of the convolutional layer,then calculate the gradient to update the weight.For the sake of simplicity, in this convolutional layer,we will ignore the bias.We previously constructed the `Conv2D` class.However, since we used single-element assignments,Gluon has some trouble finding the gradient.Instead, we use the built-in `Conv2D` class provided by Gluon below.<jupyter_code># Construct a convolutional layer with 1 output channel
# (channels will be introduced in the following section)
# and a kernel array shape of (1, 2)
conv2d = nn.Conv2D(1, kernel_size=(1, 2))
conv2d.initialize()

# The two-dimensional convolutional layer uses four-dimensional input and
# output in the format of (example, channel, height, width), where the batch
# size (number of examples in the batch) and the number of channels are both 1
X = X.reshape(1, 1, 6, 8)
Y = Y.reshape(1, 1, 6, 7)

for i in range(10):
    with autograd.record():
        Y_hat = conv2d(X)
        l = (Y_hat - Y) ** 2
    l.backward()
    # For the sake of simplicity, we ignore the bias here
    conv2d.weight.data()[:] -= 3e-2 * conv2d.weight.grad()
    if (i + 1) % 2 == 0:
        print('batch %d, loss %.3f' % (i + 1, l.sum()))<jupyter_output>batch 2, loss 4.949
batch 4, loss 0.831
batch 6, loss 0.140
batch 8, loss 0.024
batch 10, loss 0.004<jupyter_text>As you can see, the error has dropped to a small value after 10 iterations. Now we will take a look at the kernel array we learned.<jupyter_code>conv2d.weight.data().reshape(1, 2)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.08332327013645695, 'fraction_numerical': 0.02149498852795556, 'mean_word_length': 4.811929824561403, 'pattern_counts': {'":': 0, '<': 36, '<?xml version=': 0, '>': 36, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '9', 'dir': 'jupyter-structured-clean-dedup', 'id': '214796', 'license': 'Apache-2.0', 'n_tokens_mistral': 2505, 'n_tokens_neox': 2352, 'n_words': 1081, 'path': '6 convolutional-neural-networks/conv-layer.ipynb', 'repo_name': 'pedro-abundio-wang/d2l-numpy', 'size': '8281'}