Spaces:
Runtime error
Runtime error
| import numpy as np | |
| from typing import Union | |
| import time | |
| import csv | |
| def relu(x:np.ndarray)->np.ndarray: | |
| ''' | |
| Relu activation function. Returns max(0,value) | |
| args: | |
| x: input array of any shape | |
| output: All negatives clipped to 0 | |
| ''' | |
| return x * (x > 0) | |
| def add_padding(X:np.ndarray, pad_size:Union[int,list,tuple], pad_val:int=0)->np.ndarray: | |
| ''' | |
| Pad the input image array equally from all sides | |
| args: | |
| x: Input Image should be in the form of [Batch, Width, Height, Channels] | |
| pad_size: How much padding should be done. If int, equal padding will done. Else specify how much to pad each side (height_pad,width_pad) OR (y_pad, x_pad) | |
| pad_val: What should be the value to be padded. Usually it os 0 padding | |
| return: | |
| Padded Numpy array Image | |
| ''' | |
| assert (len(X.shape) == 4), "Input image should be form of [Batch, Width, Height, Channels]" | |
| if isinstance(pad_size,int): | |
| y_pad = x_pad = pad_size | |
| else: | |
| y_pad = pad_size[0] | |
| x_pad = pad_size[1] | |
| pad_width = ((0,0), (y_pad,y_pad), (x_pad,x_pad), (0,0)) # Do not pad first and last axis. Pad Width(2nd), Height(3rd) axis with pad_size | |
| return np.pad(X, pad_width = pad_width, mode = 'constant', constant_values = (pad_val,pad_val)) | |
| class Conv2DLayer: | |
| ''' | |
| 2D Convolution Layer | |
| ''' | |
| def __init__(self,input_channels:int, num_filters:int, kernel_size:int, stride:int, padding:Union[str,None], activation:Union[None,str]='relu'): | |
| ''' | |
| Kernal Matrix for the Current Layer having shape [filter_size, filter_size, num_of_features_old, num_of_filters_new]. 'num_of_features_old' are the Channels or features from previous layer | |
| 'filter_size' (or kernel size) is the size of filters which will detect new features. | |
| 'num_of_filters_new' are the No of new features detected by these kernels on the previous features where Each Kernel/filter will detect a new feature/channel | |
| args: | |
| input_channels: No of features/channels present in the incoming input. It'll be equal to Last dimension value from the prev layer output `previous_layer.output.shape[-1]` | |
| num_filters: Output Channels or How many new features you want this new Layer to Detect. Each Filter/kernel will detect a new Feature /channel | |
| kernel_size: What is the size of Kernels or Filters. Each Filter a 2D Square Matrix of size kernel_size | |
| stride: How many pixels you want each kernel to shift. Same shift in X and Y direction OR indirectly, it'll define how many iterations the kernel will take to convolve over the whole image | |
| padding: How much padding you want to add to the image. If padding='same', it means padding in a way that input and output have the same dimension | |
| activation: Which activation to use | |
| ''' | |
| self.kernel_matrices = np.random.randn(kernel_size, kernel_size, input_channels, num_filters) # Complete Weight/Kernel Matrix | |
| self.biases = np.random.randn(1, 1, 1, num_filters) # 1 Bias per Channel/feature/filter | |
| self.stride = stride | |
| self.padding = padding | |
| self.activation = activation | |
| def convolution_step(self,image_portion:np.ndarray,kernel_matrix:np.ndarray,bias:np.ndarray)->np.ndarray: | |
| ''' | |
| Convolve the Filter onto a given portion of the Image. This operation will be done multiple times per image, per kernel. Number of times is dependent on Window size, Stride and Image Size. | |
| In simple words, Multiply the given filter weight matrix and the area covered by filter and this is repeated for whole image. | |
| Imagine a slice of matrix [FxF] from a [PxQ] shaped image. Now imagine [Fxf] filter on top of it. Do matrix multiplication, summation and add bias | |
| args: | |
| image_portion: Image Matrix or in other sense, Features. Shape is [filter_size, filter_size, no of channels / Features from previous layer] | |
| filter: Filter / Kernel weight Matrix which convolves on top of image slice. Size is [filter_size, filter_size, no of channels / Features from previous layer] | |
| bias: Bias matrix of shape [1,1,1] | |
| returns: | |
| Convolved window output with single floating value inside a [1,1,1] matrix | |
| ''' | |
| assert image_portion.shape == kernel_matrix.shape , "Image Portion and Filter must be of same shape" | |
| return np.sum(np.multiply(image_portion,kernel_matrix)) + bias.astype('float') | |
| def forward(self,features_batch:np.ndarray)->np.ndarray: | |
| ''' | |
| Forward Pass or the Full Convolution | |
| Convolve over the batch of Image using the filters. Each new Filter produces a new Feature/channel from the previous Image. | |
| So if image had 32 features/channels and you have used 64 as num of filters in this layer, your image will have 64 features/channels | |
| args: | |
| features_batch: Batch of Images (Batch of Features) of shape [batch size, height, width, channels]. | |
| This is input coming from the previous Layer. If this matrix is output from a previous Convolution Layer, then the channels == (no of features from the previous layer) | |
| output: Convolved Image batch with new height, width and new detected features | |
| ''' | |
| padding_size = 0 # How to implement self.padding = 'same'? | |
| if isinstance(self.padding, int): # If specified padding | |
| padding_size = self.padding | |
| batch_size, h_old, w_old, num_features_old = features_batch.shape # [batch size, height, width, no of features (channels) from the previous layer] | |
| filter_size, filter_size, num_features_old, num_of_filters_new = self.kernel_matrices.shape # [filter_size, filter_size, num_features_old, num_of_filters_new] | |
| # New Height/Width is dependent on the old height/ width, stride, filter size, and amount of padding | |
| h_new = int((h_old + (2 * padding_size) - filter_size) / self.stride) + 1 | |
| w_new = int((w_old + (2 * padding_size) - filter_size) / self.stride) + 1 | |
| padded_batch = add_padding(features_batch, padding_size) # Pad the current input. third param is 0 by default so it is zero padding | |
| # This will act as an Input to the layer Next to it | |
| output = np.zeros([batch_size, h_new, w_new, num_of_filters_new]) # batch size will be same but height, width and no of filters will be changed | |
| for index in range(batch_size): # index i is the i-th Image or Image Matrix in other terms | |
| padded_feature = padded_batch[index,:,:,:] # Get Every feature or Channel | |
| for h in range(h_new): # Used in Vertical slicing or Window's height start and height end | |
| for w in range(w_new): # Used in Horizontal slicing or Window's width start and width end | |
| for filter_index in range(num_of_filters_new): # Feature index. Selects the appropriate kernel one at a time | |
| vertical_start = h * self.stride # It is shifted with every loop. Every starts with a new starting point in vertical direction | |
| vertical_end = vertical_start + filter_size # Filter Size is the width of window | |
| horizontal_start = w * self.stride # Window's Width starting point | |
| horizontal_end = horizontal_start + filter_size # Filter is squared so vertical and horizontal window are same so window width == window height | |
| image_portion = padded_feature[vertical_start:vertical_end, horizontal_start:horizontal_end,:] # Sliced window | |
| kernel_matrix = self.kernel_matrices[:, :, :, filter_index] # Select appropriate Kernel Matrix | |
| bias = self.biases[:,:,:,filter_index] # Select corresponding bias | |
| result = self.convolution_step(image_portion, kernel_matrix, bias) # Get 1 value per window and kernel | |
| output[index,h,w,filter_index] = result # Fill the resulting output matrix with corresponding values | |
| if self.activation == 'relu': # apply activation Function. | |
| return relu(output) | |
| return output | |
| if __name__== "__main__": | |
| batch_features = np.random.randn(32, 64, 64, 3) | |
| start_time = time.time() | |
| cnn = Conv2DLayer(3,8,3,2,2,'relu') | |
| pre_output = cnn.forward(batch_features) | |
| end_time = time.time() | |
| interval_time = end_time - start_time | |
| print(f"Time taken for execution: {interval_time} s") | |
| with open("submission.csv", "a+", newline='') as file: | |
| writer = csv.writer(file, delimiter=';') | |
| writer.writerow([interval_time]) | |