Multiple Input and Multiple Output Channels ​
While we described the multiple channels that comprise each image (e.g., color images have the standard RGB channels to indicate the amount of red, green and blue) and convolutional layers for multiple channels in :numref:subsec_why-conv-channels, until now, we simplified all of our numerical examples by working with just a single input and a single output channel. This allowed us to think of our inputs, convolution kernels, and outputs each as two-dimensional tensors.
When we add channels into the mix, our inputs and hidden representations both become three-dimensional tensors. For example, each RGB input image has shape
using Pkg; Pkg.activate("d2lai")
using d2lai, Flux Activating project at `~/Projects/D2L/d2lai`Multiple Input Channels ​
When the input data contains multiple channels, we need to construct a convolution kernel with the same number of input channels as the input data, so that it can perform cross-correlation with the input data. Assuming that the number of channels for the input data is
However, when
Figure provides an example of a two-dimensional cross-correlation with two input channels. The shaded portions are the first output element as well as the input and kernel tensor elements used for the output computation:
Cross-correlation computation with two input channels.
To make sure we really understand what is going on here, we can (implement cross-correlation operations with multiple input channels) ourselves. Notice that all we are doing is performing a cross-correlation operation per channel and then adding up the results.
function corr2d_multi_in(X, K)
return sum(d2lai.corr2d.(eachslice(X, dims=3), eachslice(K, dims=3)))
endcorr2d_multi_in (generic function with 1 method)We can construct the input tensor X and the kernel tensor K corresponding to the values in Figure to (validate the output) of the cross-correlation operation.
X = cat([0. 1. 2.; 3. 4. 5.; 6. 7. 8.], [1. 2. 3.; 4. 5. 6.; 7. 8. 9], dims = 3)
K = cat([0. 1.; 2. 3], [1. 2.; 3. 4.], dims = 3)
corr2d_multi_in(X, K)2×2 Matrix{Float64}:
56.0 72.0
104.0 120.0Multiple Output Channels ​
Regardless of the number of input channels, so far we always ended up with one output channel. However, as we discussed in :numref:subsec_why-conv-channels, it turns out to be essential to have multiple channels at each layer. In the most popular neural network architectures, we actually increase the channel dimension as we go deeper in the neural network, typically downsampling to trade off spatial resolution for greater channel depth. Intuitively, you could think of each channel as responding to a different set of features. The reality is a bit more complicated than this. A naive interpretation would suggest that representations are learned independently per pixel or per channel. Instead, channels are optimized to be jointly useful. This means that rather than mapping a single channel to an edge detector, it may simply mean that some direction in channel space corresponds to detecting edges.
Denote by
We implement a cross-correlation function to [calculate the output of multiple channels] as shown below.
function corr2d_multi_in_out(X, K)
mapreduce(k -> corr2d_multi_in(X, k), (v1, v2) -> cat(v1, v2, dims=3), eachslice(K, dims = 4))
endcorr2d_multi_in_out (generic function with 1 method)K = cat(K, K .+ 1, K .+ 2; dims = 4 )
size(K)(2, 2, 2, 3)corr2d_multi_in_out(X, K)2×2×3 Array{Float64, 3}:
[:, :, 1] =
56.0 72.0
104.0 120.0
[:, :, 2] =
76.0 100.0
148.0 172.0
[:, :, 3] =
96.0 128.0
192.0 224.0 Convolutional Layer ​
At first, a [
Because the minimum window is used, the
Figure shows the cross-correlation computation using the
The cross-correlation computation uses the
Let's check whether this works in practice: we implement a
function corr2d_multi_in_out_1x1(X, K)
h, w, cin = size(X)
# size(K) = 1 x 1 x cin x cout
_, _, ch_in, ch_out = size(K)
X = reshape(X, :, ch_in)
K = reshape(K, ch_in, ch_out)
Y = X * K
return reshape(Y, h, w, ch_out)
endcorr2d_multi_in_out_1x1 (generic function with 1 method)X = randn((3,3,3))
K = randn(1, 1, 3, 2)
Y1 = corr2d_multi_in_out_1x1(X, K)
Y2 = corr2d_multi_in_out(X, K)
@assert sum(abs.(Y1 - Y2)) < 1e-6Discussion ​
Channels allow us to combine the best of both worlds: MLPs that allow for significant nonlinearities and convolutions that allow for localized analysis of features. In particular, channels allow the CNN to reason with multiple features, such as edge and shape detectors at the same time. They also offer a practical trade-off between the drastic parameter reduction arising from translation invariance and locality, and the need for expressive and diverse models in computer vision.
Note, though, that this flexibility comes at a price. Given an image of size
Exercises ​
Assume that we have two convolution kernels of size
and , respectively (with no nonlinearity in between). Prove that the result of the operation can be expressed by a single convolution.
What is the dimensionality of the equivalent single convolution?
Is the converse true, i.e., can you always decompose a convolution into two smaller ones?
Assume an input of shape
and a convolution kernel of shape , padding of , and stride of . What is the computational cost (multiplications and additions) for the forward propagation?
What is the memory footprint?
What is the memory footprint for the backward computation?
What is the computational cost for the backpropagation?
By what factor does the number of calculations increase if we double both the number of input channels
and the number of output channels ? What happens if we double the padding? Are the variables
Y1andY2in the final example of this section exactly the same? Why?Express convolutions as a matrix multiplication, even when the convolution window is not
. Your task is to implement fast convolutions with a
kernel. One of the algorithm candidates is to scan horizontally across the source, reading a -wide strip and computing the -wide output strip one value at a time. The alternative is to read a wide strip and compute a -wide output strip. Why is the latter preferable? Is there a limit to how large you should choose ? Assume that we have a
matrix. How much faster is it to multiply with a block-diagonal matrix if the matrix is broken up into
blocks? What is the downside of having
blocks? How could you fix it, at least partly?
Number of convolution operations per input channel:
Number of convolution operations for all input channels:
Number of convolution operations for all output channels:
Number of multiplications in a single convolution: $ k_h \times k_w$
Number of additions in a single convolution: $ k_h \times k_w$
Total number of multiplications and additions:
Additions for for combining all cin: $ k_h \times kw \times cin $
Number of times we do the above addition: $ co $
Number of additions combining all cin for co times: $ k_h \times kw \times cin \times co$
Total Number of additions:
Total Number of multiplications:
function fast_corr2d_kwide(X, K)
Y = zeros(size(X) .- size(K) .+ 1)
for j in 1:size(Y, 2)
Y[:, j] .= X[:, j:j+k-1]
end
end