human
5. Pooling Layer
Pooling layer perfroms the pooling operations. The pooling layer reduces the size of image. There are several types of pooling operations in theory but the most commonly used one is the max pooling. 
In the following figure $2 \times 2$ pooling is applied on the image which reduced the size of image to half. It can be observed from the figure that the pooling operation does not effect the depth of the image.
<img src="./src/pool.png" width="800">
<img src="./src/maxpool.png" width="800">
During max pooling operation, the image is splitted into windows of size $2 \times 2$ and then just the maximum element is chosen from each window. 
You can use"
Please write code following the instructions in jupyter notebook style.
assistant
#Here is code to 5.

def maxpool_forward(x_input):
    """Perform max pooling operation with 2x2 window
    # Arguments
        x_input: np.array of size (2 * W, 2 * H)
    # Output
        output: np.array of size (W, H)
    """
    #################
    ### YOUR CODE ###
    ################# 
    return output

am.test_student_function(username, maxpool_forward, ['x_input'])

# This function is implemented for you
# The implementation is not complicated, 
# so read it to understand the concept

def maxpool_grad_input(x_input, grad_output):
    """Calculate partial derivative of the loss with respect to the input
    # Arguments
        x_input: np.array of size (2 * W, 2 * H)
        grad_output: partial derivative of the loss 
            with respect to the output 
            np.array of size (W, H)
    # Output
        output: partial derivative of the loss 
            with respect to the input
            np.array of size (2 * W, 2 * H) 
    """
    height, width = x_input.shape
    # create the array of zeros of the required size
    grad_input = np.zeros(x_input.shape)
    
    # let's put 1 if the element with this position 
    # is maximal in the window
    for i in range(0, height, 2):
        for j in range(0, width, 2):
            window = x_input[i:i+2, j:j+2]
            i_max, j_max = np.unravel_index(np.argmax(window), (2, 2))
            grad_input[i + i_max, j + j_max] = 1
            
    # put corresponding gradient instead of 1       
    grad_input = grad_input.ravel()
    grad_input[grad_input == 1] = grad_output.ravel()
    grad_input = grad_input.reshape(x_input.shape)
    return grad_input

class MaxPool2x2(Layer):
    
    def forward(self, x_input):
        n_obj, n_ch, h, w = x_input.shape
        self.output = np.zeros((n_obj, n_ch, h // 2, w // 2))
        for i in range(n_obj):
            for j in range(n_ch):
                self.output[i, j] = maxpool_forward(x_input[i, j])
        return self.output 
    
    def backward(self, x_input, grad_output):
        n_obj, n_ch, _, _ =...