Working with Multiple Layers

Now that we have covered multiple operations, we will cover how to connect various layers that have data propagating through them.

Getting ready

In this recipe, we will introduce how to best connect various layers, including custom layers. The data we will generate and use will be representative of small random images. It is best to understand these types of operation on a simple example and how we can use some built-in layers to perform calculations. We will perform a small moving window average across a 2D image and then flow the resulting output through a custom operation layer.

In this section, we will see that the computational graph can get large and hard to look at. To address this, we will also introduce ways to name operations and create scopes for layers. To start, load numpy and tensorflow and create a graph, using the following:

import tensorflow as tf
import numpy as np
sess = tf.Session()

How to do it…

  1. First we create our sample 2D image with numpy. This image will be a 4x4 pixel image. We will create it in four dimensions; the first and last dimension will have a size of one. Note that some TensorFlow image functions will operate on four-dimensional images. Those four dimensions are image number, height, width, and channel, and to make it one image with one channel, we set two of the dimensions to 1, as follows:
    x_shape = [1, 4, 4, 1]
    x_val = np.random.uniform(size=x_shape)
  2. Now we have to create the placeholder in our graph where we can feed in the sample image, as follows:
    x_data = tf.placeholder(tf.float32, shape=x_shape)
  3. To create a moving window average across our 4x4 image, we will use a built-in function that will convolute a constant across a window of the shape 2x2. This function is quite common to use in image processing and in TensorFlow, the function we will use is conv2d(). This function takes a piecewise product of the window and a filter we specify. We must also specify a stride for the moving window in both directions. Here we will compute four moving window averages, the top left, top right, bottom left, and bottom right four pixels. We do this by creating a 2x2 window and having strides of length 2 in each direction. To take the average, we will convolute the 2x2 window with a constant of 0.25., as follows:
    my_filter = tf.constant(0.25, shape=[2, 2, 1, 1])
    my_strides = [1, 2, 2, 1]
    mov_avg_layer= tf.nn.conv2d(x_data, my_filter, my_strides,
                                padding='SAME''', name='Moving'_Avg_Window')

    Note

    To figure out the output size of a convolutional layer, we can use the following formula: Output = (W-F+2P)/S+1, where W is the input size, F is the filter size, P is the padding of zeros, and S is the stride.

  4. Note that we are also naming this layer Moving_Avg_Window by using the name argument of the function.
  5. Now we define a custom layer that will operate on the 2x2 output of the moving window average. The custom function will first multiply the input by another 2x2 matrix tensor, and then add one to each entry. After this we take the sigmoid of each element and return the 2x2 matrix. Since matrix multiplication only operates on two-dimensional matrices, we need to drop the extra dimensions of our image that are of size 1. TensorFlow can do this with the built-in function squeeze(). Here we define the new layer:
    def custom_layer(input_matrix):
        input_matrix_sqeezed = tf.squeeze(input_matrix)
        A = tf.constant([[1., 2.], [-1., 3.]])
        b = tf.constant(1., shape=[2, 2])
        temp1 = tf.matmul(A, input_matrix_sqeezed)
        temp = tf.add(temp1, b) # Ax + b
        return(tf.sigmoid(temp))
  6. Now we have to place the new layer on the graph. We will do this with a named scope so that it is identifiable and collapsible/expandable on the computational graph, as follows:
    with tf.name_scope('Custom_Layer') as scope:
        custom_layer1 = custom_layer(mov_avg_layer)
  7. Now we just feed in the 4x4 image in the placeholder and tell TensorFlow to run the graph, as follows:
    print(sess.run(custom_layer1, feed_dict={x_data: x_val}))
    [[ 0.91914582  0.96025133]
     [ 0.87262219  0.9469803 ]]

How it works…

The visualized graph looks better with the naming of operations and scoping of layers. We can collapse and expand the custom layer because we created it in a named scope. In the following figure, see the collapsed version on the left and the expanded version on the right:

How it works…

Figure 3: Computational graph with two layers. The first layer is named as Moving_Avg_Window, and the second is a collection of operations called Custom_Layer. It is collapsed on the left and expanded on the right.