coding utf8 a 2d convolutional layer input shape nimages nchannels height width parameters nfilters int default 8 the number of filters kernels filtershape tupleint int default 3 3 the shape of the filters height width parameters parameters instance default none stride tupleint int default 1 1 the step of the convolution height width padding tupleint int default 0 0 the number of pixel to add to each side of the input height weight max pooling layer input shape nimages nchannels height width parameters poolshape tupleint int default 2 2 stride tupleint int default 1 1 padding tupleint int default 0 0 flattens multidimensional input into 2d matrix def forwardpassself x self lastinputshape x shape return x reshapex shape0 1 def backwardpassself delta return delta reshapeself lastinputshape def shapeself xshape return xshape0 np prodxshape1 def imagetocolumnimages filtershape stride padding nimages nchannels height width images shape fheight fwidth filtershape outheight outwidth convoltuionshapeheight width fheight fwidth stride padding images np padimages 0 0 0 0 padding padding modeconstant col np zerosnimages nchannels fheight fwidth outheight outwidth for y in rangefheight ybound y stride0 outheight for x in rangefwidth xbound x stride1 outwidth col y x images y ybound stride0 x xbound stride1 col col transpose0 4 5 1 2 3 reshapenimages outheight outwidth 1 return col def columntoimagecolumns imagesshape filtershape stride padding nimages nchannels height width imagesshape fheight fwidth filtershape outheight outwidth convoltuionshapeheight width fheight fwidth stride padding columns columns reshapenimages outheight outwidth nchannels fheight fwidth transpose 0 3 4 5 1 2 imgh height 2 padding0 stride0 1 imgw width 2 padding1 stride1 1 img np zerosnimages nchannels imgh imgw for y in rangefheight ybound y stride0 outheight for x in rangefwidth xbound x stride1 outwidth img y ybound stride0 x xbound stride1 columns y x return img padding0 height padding0 padding1 width padding1 def convoltuionshapeimgheight imgwidth filtershape stride padding calculate output shape for pooling layer nimages nchannels height width imageshape height height poolshape0 floatstride0 1 width width poolshape1 floatstride1 1 assert height 1 0 assert width 1 0 return intheight intwidth coding utf 8 a 2d convolutional layer input shape n_images n_channels height width parameters n_filters int default 8 the number of filters kernels filter_shape tuple int int default 3 3 the shape of the filters height width parameters parameters instance default none stride tuple int int default 1 1 the step of the convolution height width padding tuple int int default 0 0 the number of pixel to add to each side of the input height weight max pooling layer input shape n_images n_channels height width parameters pool_shape tuple int int default 2 2 stride tuple int int default 1 1 padding tuple int int default 0 0 flattens multidimensional input into 2d matrix rearrange image blocks into columns parameters filter_shape tuple height width images np array shape n_images n_channels height width padding tuple height width stride tuple height width rearrange columns into image blocks parameters columns images_shape tuple n_images n_channels height width filter_shape tuple height _width stride tuple height width padding tuple height width calculate output shape for convolution layer calculate output shape for pooling layer
import autograd.numpy as np

from mla.neuralnet.layers import Layer, ParamMixin
from mla.neuralnet.parameters import Parameters


class Convolution(Layer, ParamMixin):
    def __init__(self, n_filters=8, filter_shape=(3, 3), padding=(0, 0), stride=(1, 1), parameters=None):
        
        self.padding = padding
        self._params = parameters
        self.stride = stride
        self.filter_shape = filter_shape
        self.n_filters = n_filters
        if self._params is None:
            self._params = Parameters()

    def setup(self, X_shape):
        n_channels, self.height, self.width = X_shape[1:]

        W_shape = (self.n_filters, n_channels) + self.filter_shape
        b_shape = self.n_filters
        self._params.setup_weights(W_shape, b_shape)

    def forward_pass(self, X):
        n_images, n_channels, height, width = self.shape(X.shape)
        self.last_input = X
        self.col = image_to_column(X, self.filter_shape, self.stride, self.padding)
        self.col_W = self._params["W"].reshape(self.n_filters, -1).T

        out = np.dot(self.col, self.col_W) + self._params["b"]
        out = out.reshape(n_images, height, width, -1).transpose(0, 3, 1, 2)
        return out

    def backward_pass(self, delta):
        delta = delta.transpose(0, 2, 3, 1).reshape(-1, self.n_filters)

        d_W = np.dot(self.col.T, delta).transpose(1, 0).reshape(self._params["W"].shape)
        d_b = np.sum(delta, axis=0)
        self._params.update_grad("b", d_b)
        self._params.update_grad("W", d_W)

        d_c = np.dot(delta, self.col_W.T)
        return column_to_image(d_c, self.last_input.shape, self.filter_shape, self.stride, self.padding)

    def shape(self, x_shape):
        height, width = convoltuion_shape(self.height, self.width, self.filter_shape, self.stride, self.padding)
        return x_shape[0], self.n_filters, height, width


class MaxPooling(Layer):
    def __init__(self, pool_shape=(2, 2), stride=(1, 1), padding=(0, 0)):
        
        self.pool_shape = pool_shape
        self.stride = stride
        self.padding = padding

    def forward_pass(self, X):
        self.last_input = X

        out_height, out_width = pooling_shape(self.pool_shape, X.shape, self.stride)
        n_images, n_channels, _, _ = X.shape

        col = image_to_column(X, self.pool_shape, self.stride, self.padding)
        col = col.reshape(-1, self.pool_shape[0] * self.pool_shape[1])

        arg_max = np.argmax(col, axis=1)
        out = np.max(col, axis=1)
        self.arg_max = arg_max
        return out.reshape(n_images, out_height, out_width, n_channels).transpose(0, 3, 1, 2)

    def backward_pass(self, delta):
        delta = delta.transpose(0, 2, 3, 1)

        pool_size = self.pool_shape[0] * self.pool_shape[1]
        y_max = np.zeros((delta.size, pool_size))
        y_max[np.arange(self.arg_max.size), self.arg_max.flatten()] = delta.flatten()
        y_max = y_max.reshape(delta.shape + (pool_size,))

        dcol = y_max.reshape(y_max.shape[0] * y_max.shape[1] * y_max.shape[2], -1)
        return column_to_image(dcol, self.last_input.shape, self.pool_shape, self.stride, self.padding)

    def shape(self, x_shape):
        h, w = convoltuion_shape(x_shape[2], x_shape[3], self.pool_shape, self.stride, self.padding)
        return x_shape[0], x_shape[1], h, w


class Flatten(Layer):
    

    def forward_pass(self, X):
        self.last_input_shape = X.shape
        return X.reshape((X.shape[0], -1))

    def backward_pass(self, delta):
        return delta.reshape(self.last_input_shape)

    def shape(self, x_shape):
        return x_shape[0], np.prod(x_shape[1:])


def image_to_column(images, filter_shape, stride, padding):
    
    n_images, n_channels, height, width = images.shape
    f_height, f_width = filter_shape
    out_height, out_width = convoltuion_shape(height, width, (f_height, f_width), stride, padding)
    images = np.pad(images, ((0, 0), (0, 0), padding, padding), mode="constant")

    col = np.zeros((n_images, n_channels, f_height, f_width, out_height, out_width))
    for y in range(f_height):
        y_bound = y + stride[0] * out_height
        for x in range(f_width):
            x_bound = x + stride[1] * out_width
            col[:, :, y, x, :, :] = images[:, :, y: y_bound: stride[0], x: x_bound: stride[1]]

    col = col.transpose(0, 4, 5, 1, 2, 3).reshape(n_images * out_height * out_width, -1)
    return col


def column_to_image(columns, images_shape, filter_shape, stride, padding):
    
    n_images, n_channels, height, width = images_shape
    f_height, f_width = filter_shape

    out_height, out_width = convoltuion_shape(height, width, (f_height, f_width), stride, padding)
    columns = columns.reshape(n_images, out_height, out_width, n_channels, f_height, f_width).transpose(
        0, 3, 4, 5, 1, 2
    )

    img_h = height + 2 * padding[0] + stride[0] - 1
    img_w = width + 2 * padding[1] + stride[1] - 1
    img = np.zeros((n_images, n_channels, img_h, img_w))
    for y in range(f_height):
        y_bound = y + stride[0] * out_height
        for x in range(f_width):
            x_bound = x + stride[1] * out_width
            img[:, :, y: y_bound: stride[0], x: x_bound: stride[1]] += columns[:, :, y, x, :, :]

    return img[:, :, padding[0]: height + padding[0], padding[1]: width + padding[1]]


def convoltuion_shape(img_height, img_width, filter_shape, stride, padding):
    
    height = (img_height + 2 * padding[0] - filter_shape[0]) / float(stride[0]) + 1
    width = (img_width + 2 * padding[1] - filter_shape[1]) / float(stride[1]) + 1

    assert height % 1 == 0
    assert width % 1 == 0

    return int(height), int(width)


def pooling_shape(pool_shape, image_shape, stride):
    
    n_images, n_channels, height, width = image_shape

    height = (height - pool_shape[0]) / float(stride[0]) + 1
    width = (width - pool_shape[1]) / float(stride[1]) + 1

    assert height % 1 == 0
    assert width % 1 == 0

    return int(height), int(width)