quacknet.convulationalManager

  1from quacknet.convulationalFeutures import ConvulationalNetwork
  2from quacknet.convulationalBackpropagation import CNNbackpropagation
  3from quacknet.activationDerivativeFunctions import ReLUDerivative
  4from quacknet.convulationalOptimiser import CNNoptimiser
  5import numpy as np
  6
  7class CNNModel(CNNoptimiser):
  8    def __init__(self, NeuralNetworkClass):
  9        self.layers = []
 10        self.weights = []
 11        self.biases = []
 12        self.NeuralNetworkClass = NeuralNetworkClass
 13    
 14    def addLayer(self, layer):
 15        """
 16        Adds a layer to the CNN model.
 17
 18        Args:
 19            layer (class): ConvLayer, PoolingLayer, ActivationLayer, and DenseLayer
 20        """
 21        self.layers.append(layer)
 22    
 23    def forward(self, inputTensor):
 24        """
 25        Performs a forward pass through all layers.
 26
 27        Args:
 28            inputTensor (ndarray): Input data tensor to the CNN.
 29        
 30        Returns:
 31            list: List of tensors output by each layer including the input.
 32        """
 33        allTensors = [inputTensor]
 34        for layer in self.layers:
 35            inputTensor = layer.forward(inputTensor)
 36            allTensors.append(inputTensor)
 37        return allTensors
 38
 39    def _backpropagation(self, allTensors, trueValues):
 40        """
 41        Performs backpropagation through all layers to compute gradients.
 42
 43        Args:
 44            allTensors (list): List of all layer outputs from forward propagation.
 45        
 46        Returns:
 47            allWeightGradients (list): List of all the weight gradients calculated during backpropgation.
 48            allBiasGradients (list): List of all the bias gradients calculated during backpropgation.
 49        """
 50        weightGradients, biasGradients, errorTerms = self.layers[-1]._backpropagation(trueValues) # <-- this is a neural network 
 51        allWeightGradients = [weightGradients]
 52        allBiasGradients = [biasGradients]
 53        for i in range(len(self.layers) - 2, -1, -1):
 54            if(type(self.layers[i]) == PoolingLayer or type(self.layers[i]) == ActivationLayer):
 55                errorTerms = self.layers[i]._backpropagation(errorTerms, allTensors[i])
 56            elif(type(self.layers[i]) == ConvLayer):
 57                weightGradients, biasGradients, errorTerms = self.layers[i]._backpropagation(errorTerms, allTensors[i])
 58                allWeightGradients.insert(0, weightGradients)
 59                allBiasGradients.insert(0, biasGradients)
 60        
 61        return allWeightGradients, allBiasGradients
 62    
 63    def _optimser(self, inputData, labels, useBatches, weights, biases, batchSize, alpha, beta1, beta2, epsilon):
 64        """
 65        Runs the Adam optimiser either with or without batches.
 66
 67        Args:
 68            inputData (ndarray): All the training data.
 69            labels (ndarray): All the true labels for the training data.
 70            useBatches (bool): Whether to use batching.
 71            weights (list): Current weights.
 72            biases (list): Current biases.
 73            batchSize (int): Size of batches.
 74            alpha (float): Learning rate.
 75            beta1 (float): Adam's beta1 parameter.
 76            beta2 (float): Adam's beta2 parameter.
 77            epsilon (float): Adam's epsilon parameter.
 78        
 79        Returns:
 80            list: The nodes (returned to calculate accuracy and loss).
 81            list: Updated weights after optimisation
 82            list: Updated biases after optimisation
 83        """
 84        if(useBatches == True):
 85            return CNNoptimiser._AdamsOptimiserWithBatches(self, inputData, labels, weights, biases, batchSize, alpha, beta1, beta2, epsilon)
 86        else:
 87            return CNNoptimiser._AdamsOptimiserWithoutBatches(self, inputData, labels, weights, biases, alpha, beta1, beta2, epsilon)
 88    
 89    def train(self, inputData, labels, useBatches, batchSize, alpha = 0.001, beta1 = 0.9, beta2 = 0.999, epsilon = 1e-8):
 90        """
 91        Trains the CNN for one epoch and calculates accuracy and average loss.
 92
 93        Args:
 94            inputData (ndarray): All the training data.
 95            labels (ndarray): All the true labels for the training data.
 96            useBatches (bool): Whether to use batching.
 97            batchSize (int): Size of batches.
 98            alpha (float, optional): Learning rate. Default is 0.001.
 99            beta1 (float, optional): Adam's beta1 parameter. Default is 0.9.
100            beta2 (float, optional): Adam's beta2 parameter. Default is 0.999.
101            epsilon (float, optional): Adam's epsilon parameter. Default is 1e-8.
102
103        Returns:
104            float: accuracy percentage.
105            float: average loss.
106        """
107        correct, totalLoss = 0, 0
108        
109        nodes, self.weights, self.biases = self._optimser(inputData, labels, useBatches, self.weights, self.biases, batchSize, alpha, beta1, beta2, epsilon)        
110        
111        lastLayer = len(nodes[0]) - 1
112        for i in range(len(nodes)): 
113            totalLoss += self.NeuralNetworkClass.lossFunction(nodes[i][lastLayer], labels[i])
114            nodeIndex = np.argmax(nodes[i][lastLayer])
115            labelIndex = np.argmax(labels[i])
116            if(nodeIndex == labelIndex):
117                correct += 1
118        return 100 * (correct / len(labels)), totalLoss / len(labels)
119    
120    def createWeightsBiases(self):
121        """
122        Initialises weights and biases for convolutional and dense layers.
123        """
124        for i in range(len(self.layers)):
125            if(type(self.layers[i]) == ConvLayer):
126                kernalSize = self.layers[i].kernalSize
127                numKernals = self.layers[i].numKernals
128                depth = self.layers[i].depth
129
130                bounds =  np.sqrt(2 / kernalSize) # He initialisation
131
132                self.weights.append(np.random.normal(0, bounds, size=(numKernals, depth, kernalSize, kernalSize)))
133                self.biases.append(np.zeros((numKernals)))
134
135                self.layers[i].kernalWeights = self.weights[-1]
136                self.layers[i].kernalBiases = self.biases[-1]
137            elif(type(self.layers[i]) == DenseLayer):
138                self.weights.append(self.layers[i].NeuralNetworkClass.weights)
139                self.biases.append(self.layers[i].NeuralNetworkClass.biases)
140
141    def saveModel(self, NNweights, NNbiases, CNNweights, CNNbiases, filename = "modelWeights.npz"):
142        """
143        Saves model weights and biases to a compressed npz file.
144
145        Args:
146            NNweights (list): Weights of the dense neural network layers.
147            NNbiases (list): Biases of the dense neural network layers.
148            CNNweights (list): Weights of the convolutional layers.
149            CNNbiases (list): Biases of the convolutional layers.
150            filename (str, optional): Filename to save the weights. Default is "modelWeights.npz".
151        """
152        CNNweights = np.array(CNNweights, dtype=object)
153        CNNbiases = np.array(CNNbiases, dtype=object)
154        NNweights = np.array(NNweights, dtype=object)
155        NNbiases = np.array(NNbiases, dtype=object)
156        np.savez_compressed(filename, CNNweights = CNNweights, CNNbiases = CNNbiases, NNweights = NNweights, NNbiases = NNbiases, allow_pickle = True)
157
158    def loadModel(self, neuralNetwork, filename = "modelWeights.npz"):
159        """
160        Loads model weights and biases from a compressed npz file and assigns them to layers.
161        
162        Args:
163            neuralNetwork (class): The dense neural network to load weights into.
164            filename (str, optional): Filename to save the weights. Default is "modelWeights.npz".
165        """
166        data = np.load(filename, allow_pickle = True)
167        CNNweights = data["CNNweights"]
168        CNNbiases = data["CNNbiases"]
169        NNweights = data["NNweights"]
170        NNbiases = data["NNbiases"]
171
172        self.layers[-1].NeuralNetworkClass.weights = NNweights
173        self.layers[-1].NeuralNetworkClass.biases = NNbiases
174        neuralNetwork.weights = NNweights
175        neuralNetwork.biases = NNbiases
176        self.weights = CNNweights
177        self.biases = CNNbiases
178
179        currWeightIndex = 0
180        for i in range(len(self.layers)):
181            if(type(self.layers[i]) == ConvLayer):
182                self.layers[i].kernalWeights = CNNweights[currWeightIndex]
183                self.layers[i].kernalBiases = CNNbiases[currWeightIndex]
184                currWeightIndex += 1
185
186class ConvLayer(ConvulationalNetwork, CNNbackpropagation):
187    def __init__(self, kernalSize, depth, numKernals, stride, padding = "no"):
188        """
189        Initialises a convolutional layer.
190
191        Args:
192            kernalSize (int): The size of the covolution kernel (assumed it is a square).
193            depth (int): Depth of the input tensor.
194            numKernals (int): Number of kernels in this layer.
195            stride (int): The stride length for convolution.
196            padding (str or int, optional): Padding size or "no" for no padding. Default is "no".
197        """
198        self.kernalSize = kernalSize
199        self.numKernals = numKernals
200        self.kernalWeights = []
201        self.kernalBiases = []
202        self.depth = depth
203        self.stride = stride
204        self.padding = padding
205        if(padding.lower() == "no" or padding.lower() == "n"):
206            self.usePadding = False
207        else:
208            self.padding = int(self.padding)
209            self.usePadding = True
210    
211    def forward(self, inputTensor):
212        """
213        Performs a forward convolution pass.
214
215        Args:
216            inputTensor (ndarray): Input tensor to convolve.
217        
218        Returns:
219            ndarray: Output tensor after convolution.
220        """
221        return ConvulationalNetwork._kernalisation(self, inputTensor, self.kernalWeights, self.kernalBiases, self.kernalSize, self.usePadding, self.padding, self.stride)
222
223    def _backpropagation(self, errorPatch, inputTensor):
224        """
225        Performs backpropagation to compute gradients for convolutional layer.
226
227        Args:
228            errorPatch (ndarray): Error gradient from the next layer.
229            inputTensor (ndarray): Input tensor to convolve.
230        
231        Returns:
232            ndarray: Gradients of the loss with respect to kernels.
233            ndarray: Gradients of the loss with respect to biases for each kernel.
234            ndarray: Error terms propagated to the previous layer.
235        """
236        return CNNbackpropagation._ConvolutionDerivative(self, errorPatch, self.kernalWeights, inputTensor, self.stride)
237
238class PoolingLayer(CNNbackpropagation):
239    def __init__(self, gridSize, stride, mode = "max"):
240        """
241        Initialises a pooling layer.
242
243        Args:
244            gridSize (int): The size of the pooling window.
245            stride (int): The stride length for pooling.
246            mode (str, optional): Pooling mode of "max", "ave" (average), or "gap" (global average pooling). Default is "max".        
247        """
248        self.gridSize = gridSize
249        self.stride = stride
250        self.mode = mode.lower()
251    
252    def forward(self, inputTensor):
253        """
254        Performs forward pooling operation.
255
256        Args:
257           inputTensor (ndarray): Input tensor to pool.
258
259        Returns:
260            ndarray: Output tensor after pooling. 
261        """
262        if(self.mode == "gap" or self.mode == "global"):
263            return ConvulationalNetwork._poolingGlobalAverage(self, inputTensor)
264        return ConvulationalNetwork._pooling(self, inputTensor, self.gridSize, self.stride, self.mode)
265
266    def _backpropagation(self, errorPatch, inputTensor):
267        """
268        Performs backpropagation through the pooling layer.
269
270        Args:
271            errorPatch (ndarray): Error gradient from the next layer.
272            inputTensor (ndarray): Input tensor during forward propagation.
273        
274        Returns:
275            inputGradient (ndarray): Gradient of the loss.
276        """
277        if(self.mode == "max"):
278            return CNNbackpropagation._MaxPoolingDerivative(self, errorPatch, inputTensor, self.gridSize, self.stride)
279        elif(self.mode == "ave"):
280            return CNNbackpropagation._AveragePoolingDerivative(self, errorPatch, inputTensor, self.gridSize, self.stride)
281        else:
282            return CNNbackpropagation._GlobalAveragePoolingDerivative(self, inputTensor)
283
284class DenseLayer: # basically a fancy neural network
285    def __init__(self, NeuralNetworkClass):
286        """
287        Initialises a dense layer using a NeuralNetworkClass.
288
289        Args:
290            NeuralNetworkClass (class): the fully connected neural network class.
291        """
292        self.NeuralNetworkClass = NeuralNetworkClass
293        self.orignalShape = 0   # orignalShape is the original shape of the input tensor
294        
295    def forward(self, inputTensor):
296        """
297        Flattens the input tensor and performs a forward pass.
298
299        Args:
300            inputTensor (ndarray): Input tensor to flatten and process.
301        
302        Returns:
303            ndarray: Output of the dense layer.
304        """
305        self.orignalShape = np.array(inputTensor).shape
306        inputArray = ConvulationalNetwork._flatternTensor(self, inputTensor)
307        self.layerNodes = self.NeuralNetworkClass.forwardPropagation(inputArray)
308        return self.layerNodes[-1]
309    
310    def _backpropagation(self, trueValues): #return weigtGradients, biasGradients, errorTerms
311        """
312        Performs backpropagation through the dense layer.
313
314        Args:
315            trueValues (ndarray): True labels for the input data.
316        
317        Returns:
318            weightGradients (list of ndarray): Gradients of weights for each layer.
319            biasGradients (list of ndarray): Gradients of biases for each layer.
320            errorTerms (ndarray): Error terms from the output layer weights, reshaped to the input tensor.   
321        """  
322        weightGradients, biasGradients, errorTerms = self.NeuralNetworkClass._backPropgation(
323            self.layerNodes, 
324            self.NeuralNetworkClass.weights,
325            self.NeuralNetworkClass.biases,
326            trueValues,
327            True
328        )
329        #errorTerms = np.array(self.NeuralNetworkClass.weights).T @ errorTerms 
330        #errorTerms = errorTerms.reshape(self.orignalShape)
331
332        for i in reversed(range(len(self.NeuralNetworkClass.weights))):
333            errorTerms = self.NeuralNetworkClass.weights[i] @ errorTerms
334        errorTerms = errorTerms.reshape(self.orignalShape)
335
336        return weightGradients, biasGradients, errorTerms
337
338class ActivationLayer: # basically aplies an activation function over the whole Tensor (eg. leaky relu)
339    def forward(self, inputTensor):
340        """
341        Applies the Leaky ReLU activation function to the input tensor.
342
343        Args:
344            inputTensor (ndarray): A 3D array representing the input.
345        
346        Returns:
347            ndarray: A tensor with the same shape as the input with Leaky ReLU applied to it.
348        """
349        return ConvulationalNetwork._activation(self, inputTensor)
350
351    def _backpropagation(self, errorPatch, inputTensor):
352        """
353        Compute the gradient of the loss with respect to the input of the activation layer during backpropagation.
354
355        Args:
356            errorPatch (ndarray): Error gradient from the next layer.
357            inputTensor (ndarray): Input to the activation layer during forward propagation.
358        
359        Returns:
360            inputGradient (ndarray): Gradient of the loss with respect to the inputTensor
361        """  
362        return CNNbackpropagation._ActivationLayerDerivative(self, errorPatch, ReLUDerivative, inputTensor)
363    
class CNNModel(quacknet.convulationalOptimiser.CNNoptimiser):
  8class CNNModel(CNNoptimiser):
  9    def __init__(self, NeuralNetworkClass):
 10        self.layers = []
 11        self.weights = []
 12        self.biases = []
 13        self.NeuralNetworkClass = NeuralNetworkClass
 14    
 15    def addLayer(self, layer):
 16        """
 17        Adds a layer to the CNN model.
 18
 19        Args:
 20            layer (class): ConvLayer, PoolingLayer, ActivationLayer, and DenseLayer
 21        """
 22        self.layers.append(layer)
 23    
 24    def forward(self, inputTensor):
 25        """
 26        Performs a forward pass through all layers.
 27
 28        Args:
 29            inputTensor (ndarray): Input data tensor to the CNN.
 30        
 31        Returns:
 32            list: List of tensors output by each layer including the input.
 33        """
 34        allTensors = [inputTensor]
 35        for layer in self.layers:
 36            inputTensor = layer.forward(inputTensor)
 37            allTensors.append(inputTensor)
 38        return allTensors
 39
 40    def _backpropagation(self, allTensors, trueValues):
 41        """
 42        Performs backpropagation through all layers to compute gradients.
 43
 44        Args:
 45            allTensors (list): List of all layer outputs from forward propagation.
 46        
 47        Returns:
 48            allWeightGradients (list): List of all the weight gradients calculated during backpropgation.
 49            allBiasGradients (list): List of all the bias gradients calculated during backpropgation.
 50        """
 51        weightGradients, biasGradients, errorTerms = self.layers[-1]._backpropagation(trueValues) # <-- this is a neural network 
 52        allWeightGradients = [weightGradients]
 53        allBiasGradients = [biasGradients]
 54        for i in range(len(self.layers) - 2, -1, -1):
 55            if(type(self.layers[i]) == PoolingLayer or type(self.layers[i]) == ActivationLayer):
 56                errorTerms = self.layers[i]._backpropagation(errorTerms, allTensors[i])
 57            elif(type(self.layers[i]) == ConvLayer):
 58                weightGradients, biasGradients, errorTerms = self.layers[i]._backpropagation(errorTerms, allTensors[i])
 59                allWeightGradients.insert(0, weightGradients)
 60                allBiasGradients.insert(0, biasGradients)
 61        
 62        return allWeightGradients, allBiasGradients
 63    
 64    def _optimser(self, inputData, labels, useBatches, weights, biases, batchSize, alpha, beta1, beta2, epsilon):
 65        """
 66        Runs the Adam optimiser either with or without batches.
 67
 68        Args:
 69            inputData (ndarray): All the training data.
 70            labels (ndarray): All the true labels for the training data.
 71            useBatches (bool): Whether to use batching.
 72            weights (list): Current weights.
 73            biases (list): Current biases.
 74            batchSize (int): Size of batches.
 75            alpha (float): Learning rate.
 76            beta1 (float): Adam's beta1 parameter.
 77            beta2 (float): Adam's beta2 parameter.
 78            epsilon (float): Adam's epsilon parameter.
 79        
 80        Returns:
 81            list: The nodes (returned to calculate accuracy and loss).
 82            list: Updated weights after optimisation
 83            list: Updated biases after optimisation
 84        """
 85        if(useBatches == True):
 86            return CNNoptimiser._AdamsOptimiserWithBatches(self, inputData, labels, weights, biases, batchSize, alpha, beta1, beta2, epsilon)
 87        else:
 88            return CNNoptimiser._AdamsOptimiserWithoutBatches(self, inputData, labels, weights, biases, alpha, beta1, beta2, epsilon)
 89    
 90    def train(self, inputData, labels, useBatches, batchSize, alpha = 0.001, beta1 = 0.9, beta2 = 0.999, epsilon = 1e-8):
 91        """
 92        Trains the CNN for one epoch and calculates accuracy and average loss.
 93
 94        Args:
 95            inputData (ndarray): All the training data.
 96            labels (ndarray): All the true labels for the training data.
 97            useBatches (bool): Whether to use batching.
 98            batchSize (int): Size of batches.
 99            alpha (float, optional): Learning rate. Default is 0.001.
100            beta1 (float, optional): Adam's beta1 parameter. Default is 0.9.
101            beta2 (float, optional): Adam's beta2 parameter. Default is 0.999.
102            epsilon (float, optional): Adam's epsilon parameter. Default is 1e-8.
103
104        Returns:
105            float: accuracy percentage.
106            float: average loss.
107        """
108        correct, totalLoss = 0, 0
109        
110        nodes, self.weights, self.biases = self._optimser(inputData, labels, useBatches, self.weights, self.biases, batchSize, alpha, beta1, beta2, epsilon)        
111        
112        lastLayer = len(nodes[0]) - 1
113        for i in range(len(nodes)): 
114            totalLoss += self.NeuralNetworkClass.lossFunction(nodes[i][lastLayer], labels[i])
115            nodeIndex = np.argmax(nodes[i][lastLayer])
116            labelIndex = np.argmax(labels[i])
117            if(nodeIndex == labelIndex):
118                correct += 1
119        return 100 * (correct / len(labels)), totalLoss / len(labels)
120    
121    def createWeightsBiases(self):
122        """
123        Initialises weights and biases for convolutional and dense layers.
124        """
125        for i in range(len(self.layers)):
126            if(type(self.layers[i]) == ConvLayer):
127                kernalSize = self.layers[i].kernalSize
128                numKernals = self.layers[i].numKernals
129                depth = self.layers[i].depth
130
131                bounds =  np.sqrt(2 / kernalSize) # He initialisation
132
133                self.weights.append(np.random.normal(0, bounds, size=(numKernals, depth, kernalSize, kernalSize)))
134                self.biases.append(np.zeros((numKernals)))
135
136                self.layers[i].kernalWeights = self.weights[-1]
137                self.layers[i].kernalBiases = self.biases[-1]
138            elif(type(self.layers[i]) == DenseLayer):
139                self.weights.append(self.layers[i].NeuralNetworkClass.weights)
140                self.biases.append(self.layers[i].NeuralNetworkClass.biases)
141
142    def saveModel(self, NNweights, NNbiases, CNNweights, CNNbiases, filename = "modelWeights.npz"):
143        """
144        Saves model weights and biases to a compressed npz file.
145
146        Args:
147            NNweights (list): Weights of the dense neural network layers.
148            NNbiases (list): Biases of the dense neural network layers.
149            CNNweights (list): Weights of the convolutional layers.
150            CNNbiases (list): Biases of the convolutional layers.
151            filename (str, optional): Filename to save the weights. Default is "modelWeights.npz".
152        """
153        CNNweights = np.array(CNNweights, dtype=object)
154        CNNbiases = np.array(CNNbiases, dtype=object)
155        NNweights = np.array(NNweights, dtype=object)
156        NNbiases = np.array(NNbiases, dtype=object)
157        np.savez_compressed(filename, CNNweights = CNNweights, CNNbiases = CNNbiases, NNweights = NNweights, NNbiases = NNbiases, allow_pickle = True)
158
159    def loadModel(self, neuralNetwork, filename = "modelWeights.npz"):
160        """
161        Loads model weights and biases from a compressed npz file and assigns them to layers.
162        
163        Args:
164            neuralNetwork (class): The dense neural network to load weights into.
165            filename (str, optional): Filename to save the weights. Default is "modelWeights.npz".
166        """
167        data = np.load(filename, allow_pickle = True)
168        CNNweights = data["CNNweights"]
169        CNNbiases = data["CNNbiases"]
170        NNweights = data["NNweights"]
171        NNbiases = data["NNbiases"]
172
173        self.layers[-1].NeuralNetworkClass.weights = NNweights
174        self.layers[-1].NeuralNetworkClass.biases = NNbiases
175        neuralNetwork.weights = NNweights
176        neuralNetwork.biases = NNbiases
177        self.weights = CNNweights
178        self.biases = CNNbiases
179
180        currWeightIndex = 0
181        for i in range(len(self.layers)):
182            if(type(self.layers[i]) == ConvLayer):
183                self.layers[i].kernalWeights = CNNweights[currWeightIndex]
184                self.layers[i].kernalBiases = CNNbiases[currWeightIndex]
185                currWeightIndex += 1
CNNModel(NeuralNetworkClass)
 9    def __init__(self, NeuralNetworkClass):
10        self.layers = []
11        self.weights = []
12        self.biases = []
13        self.NeuralNetworkClass = NeuralNetworkClass
layers
weights
biases
NeuralNetworkClass
def addLayer(self, layer):
15    def addLayer(self, layer):
16        """
17        Adds a layer to the CNN model.
18
19        Args:
20            layer (class): ConvLayer, PoolingLayer, ActivationLayer, and DenseLayer
21        """
22        self.layers.append(layer)

Adds a layer to the CNN model.

Args: layer (class): ConvLayer, PoolingLayer, ActivationLayer, and DenseLayer

def forward(self, inputTensor):
24    def forward(self, inputTensor):
25        """
26        Performs a forward pass through all layers.
27
28        Args:
29            inputTensor (ndarray): Input data tensor to the CNN.
30        
31        Returns:
32            list: List of tensors output by each layer including the input.
33        """
34        allTensors = [inputTensor]
35        for layer in self.layers:
36            inputTensor = layer.forward(inputTensor)
37            allTensors.append(inputTensor)
38        return allTensors

Performs a forward pass through all layers.

Args: inputTensor (ndarray): Input data tensor to the CNN.

Returns: list: List of tensors output by each layer including the input.

def train( self, inputData, labels, useBatches, batchSize, alpha=0.001, beta1=0.9, beta2=0.999, epsilon=1e-08):
 90    def train(self, inputData, labels, useBatches, batchSize, alpha = 0.001, beta1 = 0.9, beta2 = 0.999, epsilon = 1e-8):
 91        """
 92        Trains the CNN for one epoch and calculates accuracy and average loss.
 93
 94        Args:
 95            inputData (ndarray): All the training data.
 96            labels (ndarray): All the true labels for the training data.
 97            useBatches (bool): Whether to use batching.
 98            batchSize (int): Size of batches.
 99            alpha (float, optional): Learning rate. Default is 0.001.
100            beta1 (float, optional): Adam's beta1 parameter. Default is 0.9.
101            beta2 (float, optional): Adam's beta2 parameter. Default is 0.999.
102            epsilon (float, optional): Adam's epsilon parameter. Default is 1e-8.
103
104        Returns:
105            float: accuracy percentage.
106            float: average loss.
107        """
108        correct, totalLoss = 0, 0
109        
110        nodes, self.weights, self.biases = self._optimser(inputData, labels, useBatches, self.weights, self.biases, batchSize, alpha, beta1, beta2, epsilon)        
111        
112        lastLayer = len(nodes[0]) - 1
113        for i in range(len(nodes)): 
114            totalLoss += self.NeuralNetworkClass.lossFunction(nodes[i][lastLayer], labels[i])
115            nodeIndex = np.argmax(nodes[i][lastLayer])
116            labelIndex = np.argmax(labels[i])
117            if(nodeIndex == labelIndex):
118                correct += 1
119        return 100 * (correct / len(labels)), totalLoss / len(labels)

Trains the CNN for one epoch and calculates accuracy and average loss.

Args: inputData (ndarray): All the training data. labels (ndarray): All the true labels for the training data. useBatches (bool): Whether to use batching. batchSize (int): Size of batches. alpha (float, optional): Learning rate. Default is 0.001. beta1 (float, optional): Adam's beta1 parameter. Default is 0.9. beta2 (float, optional): Adam's beta2 parameter. Default is 0.999. epsilon (float, optional): Adam's epsilon parameter. Default is 1e-8.

Returns: float: accuracy percentage. float: average loss.

def createWeightsBiases(self):
121    def createWeightsBiases(self):
122        """
123        Initialises weights and biases for convolutional and dense layers.
124        """
125        for i in range(len(self.layers)):
126            if(type(self.layers[i]) == ConvLayer):
127                kernalSize = self.layers[i].kernalSize
128                numKernals = self.layers[i].numKernals
129                depth = self.layers[i].depth
130
131                bounds =  np.sqrt(2 / kernalSize) # He initialisation
132
133                self.weights.append(np.random.normal(0, bounds, size=(numKernals, depth, kernalSize, kernalSize)))
134                self.biases.append(np.zeros((numKernals)))
135
136                self.layers[i].kernalWeights = self.weights[-1]
137                self.layers[i].kernalBiases = self.biases[-1]
138            elif(type(self.layers[i]) == DenseLayer):
139                self.weights.append(self.layers[i].NeuralNetworkClass.weights)
140                self.biases.append(self.layers[i].NeuralNetworkClass.biases)

Initialises weights and biases for convolutional and dense layers.

def saveModel( self, NNweights, NNbiases, CNNweights, CNNbiases, filename='modelWeights.npz'):
142    def saveModel(self, NNweights, NNbiases, CNNweights, CNNbiases, filename = "modelWeights.npz"):
143        """
144        Saves model weights and biases to a compressed npz file.
145
146        Args:
147            NNweights (list): Weights of the dense neural network layers.
148            NNbiases (list): Biases of the dense neural network layers.
149            CNNweights (list): Weights of the convolutional layers.
150            CNNbiases (list): Biases of the convolutional layers.
151            filename (str, optional): Filename to save the weights. Default is "modelWeights.npz".
152        """
153        CNNweights = np.array(CNNweights, dtype=object)
154        CNNbiases = np.array(CNNbiases, dtype=object)
155        NNweights = np.array(NNweights, dtype=object)
156        NNbiases = np.array(NNbiases, dtype=object)
157        np.savez_compressed(filename, CNNweights = CNNweights, CNNbiases = CNNbiases, NNweights = NNweights, NNbiases = NNbiases, allow_pickle = True)

Saves model weights and biases to a compressed npz file.

Args: NNweights (list): Weights of the dense neural network layers. NNbiases (list): Biases of the dense neural network layers. CNNweights (list): Weights of the convolutional layers. CNNbiases (list): Biases of the convolutional layers. filename (str, optional): Filename to save the weights. Default is "modelWeights.npz".

def loadModel(self, neuralNetwork, filename='modelWeights.npz'):
159    def loadModel(self, neuralNetwork, filename = "modelWeights.npz"):
160        """
161        Loads model weights and biases from a compressed npz file and assigns them to layers.
162        
163        Args:
164            neuralNetwork (class): The dense neural network to load weights into.
165            filename (str, optional): Filename to save the weights. Default is "modelWeights.npz".
166        """
167        data = np.load(filename, allow_pickle = True)
168        CNNweights = data["CNNweights"]
169        CNNbiases = data["CNNbiases"]
170        NNweights = data["NNweights"]
171        NNbiases = data["NNbiases"]
172
173        self.layers[-1].NeuralNetworkClass.weights = NNweights
174        self.layers[-1].NeuralNetworkClass.biases = NNbiases
175        neuralNetwork.weights = NNweights
176        neuralNetwork.biases = NNbiases
177        self.weights = CNNweights
178        self.biases = CNNbiases
179
180        currWeightIndex = 0
181        for i in range(len(self.layers)):
182            if(type(self.layers[i]) == ConvLayer):
183                self.layers[i].kernalWeights = CNNweights[currWeightIndex]
184                self.layers[i].kernalBiases = CNNbiases[currWeightIndex]
185                currWeightIndex += 1

Loads model weights and biases from a compressed npz file and assigns them to layers.

Args: neuralNetwork (class): The dense neural network to load weights into. filename (str, optional): Filename to save the weights. Default is "modelWeights.npz".

187class ConvLayer(ConvulationalNetwork, CNNbackpropagation):
188    def __init__(self, kernalSize, depth, numKernals, stride, padding = "no"):
189        """
190        Initialises a convolutional layer.
191
192        Args:
193            kernalSize (int): The size of the covolution kernel (assumed it is a square).
194            depth (int): Depth of the input tensor.
195            numKernals (int): Number of kernels in this layer.
196            stride (int): The stride length for convolution.
197            padding (str or int, optional): Padding size or "no" for no padding. Default is "no".
198        """
199        self.kernalSize = kernalSize
200        self.numKernals = numKernals
201        self.kernalWeights = []
202        self.kernalBiases = []
203        self.depth = depth
204        self.stride = stride
205        self.padding = padding
206        if(padding.lower() == "no" or padding.lower() == "n"):
207            self.usePadding = False
208        else:
209            self.padding = int(self.padding)
210            self.usePadding = True
211    
212    def forward(self, inputTensor):
213        """
214        Performs a forward convolution pass.
215
216        Args:
217            inputTensor (ndarray): Input tensor to convolve.
218        
219        Returns:
220            ndarray: Output tensor after convolution.
221        """
222        return ConvulationalNetwork._kernalisation(self, inputTensor, self.kernalWeights, self.kernalBiases, self.kernalSize, self.usePadding, self.padding, self.stride)
223
224    def _backpropagation(self, errorPatch, inputTensor):
225        """
226        Performs backpropagation to compute gradients for convolutional layer.
227
228        Args:
229            errorPatch (ndarray): Error gradient from the next layer.
230            inputTensor (ndarray): Input tensor to convolve.
231        
232        Returns:
233            ndarray: Gradients of the loss with respect to kernels.
234            ndarray: Gradients of the loss with respect to biases for each kernel.
235            ndarray: Error terms propagated to the previous layer.
236        """
237        return CNNbackpropagation._ConvolutionDerivative(self, errorPatch, self.kernalWeights, inputTensor, self.stride)
ConvLayer(kernalSize, depth, numKernals, stride, padding='no')
188    def __init__(self, kernalSize, depth, numKernals, stride, padding = "no"):
189        """
190        Initialises a convolutional layer.
191
192        Args:
193            kernalSize (int): The size of the covolution kernel (assumed it is a square).
194            depth (int): Depth of the input tensor.
195            numKernals (int): Number of kernels in this layer.
196            stride (int): The stride length for convolution.
197            padding (str or int, optional): Padding size or "no" for no padding. Default is "no".
198        """
199        self.kernalSize = kernalSize
200        self.numKernals = numKernals
201        self.kernalWeights = []
202        self.kernalBiases = []
203        self.depth = depth
204        self.stride = stride
205        self.padding = padding
206        if(padding.lower() == "no" or padding.lower() == "n"):
207            self.usePadding = False
208        else:
209            self.padding = int(self.padding)
210            self.usePadding = True

Initialises a convolutional layer.

Args: kernalSize (int): The size of the covolution kernel (assumed it is a square). depth (int): Depth of the input tensor. numKernals (int): Number of kernels in this layer. stride (int): The stride length for convolution. padding (str or int, optional): Padding size or "no" for no padding. Default is "no".

kernalSize
numKernals
kernalWeights
kernalBiases
depth
stride
padding
def forward(self, inputTensor):
212    def forward(self, inputTensor):
213        """
214        Performs a forward convolution pass.
215
216        Args:
217            inputTensor (ndarray): Input tensor to convolve.
218        
219        Returns:
220            ndarray: Output tensor after convolution.
221        """
222        return ConvulationalNetwork._kernalisation(self, inputTensor, self.kernalWeights, self.kernalBiases, self.kernalSize, self.usePadding, self.padding, self.stride)

Performs a forward convolution pass.

Args: inputTensor (ndarray): Input tensor to convolve.

Returns: ndarray: Output tensor after convolution.

239class PoolingLayer(CNNbackpropagation):
240    def __init__(self, gridSize, stride, mode = "max"):
241        """
242        Initialises a pooling layer.
243
244        Args:
245            gridSize (int): The size of the pooling window.
246            stride (int): The stride length for pooling.
247            mode (str, optional): Pooling mode of "max", "ave" (average), or "gap" (global average pooling). Default is "max".        
248        """
249        self.gridSize = gridSize
250        self.stride = stride
251        self.mode = mode.lower()
252    
253    def forward(self, inputTensor):
254        """
255        Performs forward pooling operation.
256
257        Args:
258           inputTensor (ndarray): Input tensor to pool.
259
260        Returns:
261            ndarray: Output tensor after pooling. 
262        """
263        if(self.mode == "gap" or self.mode == "global"):
264            return ConvulationalNetwork._poolingGlobalAverage(self, inputTensor)
265        return ConvulationalNetwork._pooling(self, inputTensor, self.gridSize, self.stride, self.mode)
266
267    def _backpropagation(self, errorPatch, inputTensor):
268        """
269        Performs backpropagation through the pooling layer.
270
271        Args:
272            errorPatch (ndarray): Error gradient from the next layer.
273            inputTensor (ndarray): Input tensor during forward propagation.
274        
275        Returns:
276            inputGradient (ndarray): Gradient of the loss.
277        """
278        if(self.mode == "max"):
279            return CNNbackpropagation._MaxPoolingDerivative(self, errorPatch, inputTensor, self.gridSize, self.stride)
280        elif(self.mode == "ave"):
281            return CNNbackpropagation._AveragePoolingDerivative(self, errorPatch, inputTensor, self.gridSize, self.stride)
282        else:
283            return CNNbackpropagation._GlobalAveragePoolingDerivative(self, inputTensor)
PoolingLayer(gridSize, stride, mode='max')
240    def __init__(self, gridSize, stride, mode = "max"):
241        """
242        Initialises a pooling layer.
243
244        Args:
245            gridSize (int): The size of the pooling window.
246            stride (int): The stride length for pooling.
247            mode (str, optional): Pooling mode of "max", "ave" (average), or "gap" (global average pooling). Default is "max".        
248        """
249        self.gridSize = gridSize
250        self.stride = stride
251        self.mode = mode.lower()

Initialises a pooling layer.

Args: gridSize (int): The size of the pooling window. stride (int): The stride length for pooling. mode (str, optional): Pooling mode of "max", "ave" (average), or "gap" (global average pooling). Default is "max".

gridSize
stride
mode
def forward(self, inputTensor):
253    def forward(self, inputTensor):
254        """
255        Performs forward pooling operation.
256
257        Args:
258           inputTensor (ndarray): Input tensor to pool.
259
260        Returns:
261            ndarray: Output tensor after pooling. 
262        """
263        if(self.mode == "gap" or self.mode == "global"):
264            return ConvulationalNetwork._poolingGlobalAverage(self, inputTensor)
265        return ConvulationalNetwork._pooling(self, inputTensor, self.gridSize, self.stride, self.mode)

Performs forward pooling operation.

Args: inputTensor (ndarray): Input tensor to pool.

Returns: ndarray: Output tensor after pooling.

class DenseLayer:
285class DenseLayer: # basically a fancy neural network
286    def __init__(self, NeuralNetworkClass):
287        """
288        Initialises a dense layer using a NeuralNetworkClass.
289
290        Args:
291            NeuralNetworkClass (class): the fully connected neural network class.
292        """
293        self.NeuralNetworkClass = NeuralNetworkClass
294        self.orignalShape = 0   # orignalShape is the original shape of the input tensor
295        
296    def forward(self, inputTensor):
297        """
298        Flattens the input tensor and performs a forward pass.
299
300        Args:
301            inputTensor (ndarray): Input tensor to flatten and process.
302        
303        Returns:
304            ndarray: Output of the dense layer.
305        """
306        self.orignalShape = np.array(inputTensor).shape
307        inputArray = ConvulationalNetwork._flatternTensor(self, inputTensor)
308        self.layerNodes = self.NeuralNetworkClass.forwardPropagation(inputArray)
309        return self.layerNodes[-1]
310    
311    def _backpropagation(self, trueValues): #return weigtGradients, biasGradients, errorTerms
312        """
313        Performs backpropagation through the dense layer.
314
315        Args:
316            trueValues (ndarray): True labels for the input data.
317        
318        Returns:
319            weightGradients (list of ndarray): Gradients of weights for each layer.
320            biasGradients (list of ndarray): Gradients of biases for each layer.
321            errorTerms (ndarray): Error terms from the output layer weights, reshaped to the input tensor.   
322        """  
323        weightGradients, biasGradients, errorTerms = self.NeuralNetworkClass._backPropgation(
324            self.layerNodes, 
325            self.NeuralNetworkClass.weights,
326            self.NeuralNetworkClass.biases,
327            trueValues,
328            True
329        )
330        #errorTerms = np.array(self.NeuralNetworkClass.weights).T @ errorTerms 
331        #errorTerms = errorTerms.reshape(self.orignalShape)
332
333        for i in reversed(range(len(self.NeuralNetworkClass.weights))):
334            errorTerms = self.NeuralNetworkClass.weights[i] @ errorTerms
335        errorTerms = errorTerms.reshape(self.orignalShape)
336
337        return weightGradients, biasGradients, errorTerms
DenseLayer(NeuralNetworkClass)
286    def __init__(self, NeuralNetworkClass):
287        """
288        Initialises a dense layer using a NeuralNetworkClass.
289
290        Args:
291            NeuralNetworkClass (class): the fully connected neural network class.
292        """
293        self.NeuralNetworkClass = NeuralNetworkClass
294        self.orignalShape = 0   # orignalShape is the original shape of the input tensor

Initialises a dense layer using a NeuralNetworkClass.

Args: NeuralNetworkClass (class): the fully connected neural network class.

NeuralNetworkClass
orignalShape
def forward(self, inputTensor):
296    def forward(self, inputTensor):
297        """
298        Flattens the input tensor and performs a forward pass.
299
300        Args:
301            inputTensor (ndarray): Input tensor to flatten and process.
302        
303        Returns:
304            ndarray: Output of the dense layer.
305        """
306        self.orignalShape = np.array(inputTensor).shape
307        inputArray = ConvulationalNetwork._flatternTensor(self, inputTensor)
308        self.layerNodes = self.NeuralNetworkClass.forwardPropagation(inputArray)
309        return self.layerNodes[-1]

Flattens the input tensor and performs a forward pass.

Args: inputTensor (ndarray): Input tensor to flatten and process.

Returns: ndarray: Output of the dense layer.

class ActivationLayer:
339class ActivationLayer: # basically aplies an activation function over the whole Tensor (eg. leaky relu)
340    def forward(self, inputTensor):
341        """
342        Applies the Leaky ReLU activation function to the input tensor.
343
344        Args:
345            inputTensor (ndarray): A 3D array representing the input.
346        
347        Returns:
348            ndarray: A tensor with the same shape as the input with Leaky ReLU applied to it.
349        """
350        return ConvulationalNetwork._activation(self, inputTensor)
351
352    def _backpropagation(self, errorPatch, inputTensor):
353        """
354        Compute the gradient of the loss with respect to the input of the activation layer during backpropagation.
355
356        Args:
357            errorPatch (ndarray): Error gradient from the next layer.
358            inputTensor (ndarray): Input to the activation layer during forward propagation.
359        
360        Returns:
361            inputGradient (ndarray): Gradient of the loss with respect to the inputTensor
362        """  
363        return CNNbackpropagation._ActivationLayerDerivative(self, errorPatch, ReLUDerivative, inputTensor)
def forward(self, inputTensor):
340    def forward(self, inputTensor):
341        """
342        Applies the Leaky ReLU activation function to the input tensor.
343
344        Args:
345            inputTensor (ndarray): A 3D array representing the input.
346        
347        Returns:
348            ndarray: A tensor with the same shape as the input with Leaky ReLU applied to it.
349        """
350        return ConvulationalNetwork._activation(self, inputTensor)

Applies the Leaky ReLU activation function to the input tensor.

Args: inputTensor (ndarray): A 3D array representing the input.

Returns: ndarray: A tensor with the same shape as the input with Leaky ReLU applied to it.