From 3f26624b46c79427227013a3cf1ad6e8238482e4 Mon Sep 17 00:00:00 2001 From: Javid Ahmed Date: Thu, 21 Nov 2024 21:15:44 +0000 Subject: [PATCH 1/8] Replace multiply() method --- neural_network/math/matrix.py | 23 ++++++----------------- neural_network/math/nn_math.py | 10 ++++------ tests/math/test_matrix.py | 4 ++-- 3 files changed, 12 insertions(+), 25 deletions(-) diff --git a/neural_network/math/matrix.py b/neural_network/math/matrix.py index 0586da6..f452f83 100644 --- a/neural_network/math/matrix.py +++ b/neural_network/math/matrix.py @@ -31,6 +31,12 @@ def __init__(self, rows: int, cols: int, vals: NDArray | None = None) -> None: def __str__(self) -> str: return str(self.vals) + def __mul__(self, other: float | int) -> Matrix: + return Matrix.from_array(self.vals * other) + + def __matmul__(self, other: Matrix) -> Matrix: + return Matrix.from_array(self.vals @ other.vals) + @property def vals(self) -> NDArray: if self._vals is None: @@ -128,23 +134,6 @@ def subtract(matrix: Matrix, other_matrix: Matrix) -> Matrix: new_matrix = matrix.vals - other_matrix.vals return Matrix.from_array(new_matrix) - @staticmethod - def multiply(matrix: Matrix, val: Matrix | float) -> Matrix: - """ - Multiply Matrix with scalar or Matrix. - - Parameters: - matrix (Matrix): Matrix to to use for multiplication - val (Matrix | float): Matrix or scalar to use for multiplication - - Returns: - new_matrix (Matrix): Multiplied Matrix - """ - if isinstance(val, Matrix): - val = val.vals - new_matrix = matrix.vals.dot(val) - return Matrix.from_array(new_matrix) - @staticmethod def multiply_element_wise(matrix: Matrix, other_matrix: Matrix) -> Matrix: """ diff --git a/neural_network/math/nn_math.py b/neural_network/math/nn_math.py index 307d57f..30b5972 100644 --- a/neural_network/math/nn_math.py +++ b/neural_network/math/nn_math.py @@ -19,7 +19,7 @@ def feedforward_through_layer( Returns: output_vals (Matrix): Output values """ - output_vals = Matrix.multiply(weights, input_vals) + output_vals = weights @ input_vals output_vals = Matrix.add(output_vals, bias) return Matrix.map(output_vals, activation) @@ -39,7 +39,7 @@ def calculate_gradient(layer_vals: Matrix, errors: Matrix, activation: Activatio """ gradient = Matrix.from_array(np.vectorize(activation.derivative)(layer_vals.vals)) gradient = Matrix.multiply_element_wise(gradient, errors) - return Matrix.multiply(gradient, lr) + return gradient * lr def calculate_delta(layer_vals: Matrix, gradients: Matrix) -> Matrix: @@ -53,8 +53,7 @@ def calculate_delta(layer_vals: Matrix, gradients: Matrix) -> Matrix: Returns: delta (Matrix): Delta factors """ - incoming_transposed = Matrix.transpose(layer_vals) - return Matrix.multiply(gradients, incoming_transposed) + return gradients @ Matrix.transpose(layer_vals) def calculate_error_from_expected(expected_outputs: Matrix, actual_outputs: Matrix) -> Matrix: @@ -82,5 +81,4 @@ def calculate_next_errors(weights: Matrix, calculated_errors: Matrix) -> Matrix: Returns: errors (Matrix): Next errors """ - weights_t = Matrix.transpose(weights) - return Matrix.multiply(weights_t, calculated_errors) + return Matrix.transpose(weights) @ calculated_errors diff --git a/tests/math/test_matrix.py b/tests/math/test_matrix.py index 5005d80..05779a7 100644 --- a/tests/math/test_matrix.py +++ b/tests/math/test_matrix.py @@ -89,7 +89,7 @@ def test_given_two_matrices_when_multiplying_then_check_new_matrix_correctly_cal matrix_1 = Matrix.from_array(array_1) matrix_2 = Matrix.from_array(array_2) - new_matrix = Matrix.multiply(matrix_1, matrix_2) + new_matrix = matrix_1 @ matrix_2 expected_vals = np.array([[3, -9], [2, -11], [6, -18]]) actual_vals = new_matrix.vals @@ -112,7 +112,7 @@ def test_given_matrix_and_scalar_when_multiplying_then_check_new_matrix_correctl multiplier = 3 matrix = Matrix.from_array(array) - new_matrix = Matrix.multiply(matrix, multiplier) + new_matrix = matrix * multiplier expected_vals = array * multiplier actual_vals = new_matrix.vals From 51e423a6eacd5ae788963a17faa9af8b491c9a42 Mon Sep 17 00:00:00 2001 From: Javid Ahmed Date: Thu, 21 Nov 2024 22:12:46 +0000 Subject: [PATCH 2/8] Replace add() method --- neural_network/layer.py | 4 ++-- neural_network/math/matrix.py | 18 +++--------------- neural_network/math/nn_math.py | 2 +- tests/math/test_matrix.py | 2 +- 4 files changed, 7 insertions(+), 19 deletions(-) diff --git a/neural_network/layer.py b/neural_network/layer.py index a7b22a7..dcb2b5a 100644 --- a/neural_network/layer.py +++ b/neural_network/layer.py @@ -107,8 +107,8 @@ def backpropagate_error(self, errors: Matrix, learning_rate: float) -> None: activation=self._activation, layer_vals=self._layer_output, errors=errors, lr=learning_rate ) delta = nn_math.calculate_delta(layer_vals=self._layer_input, gradients=gradient) - self.weights = Matrix.add(self.weights, delta) - self.bias = Matrix.add(self.bias, gradient) + self.weights = self.weights + delta + self.bias = self.bias + gradient def feedforward(self, vals: Matrix) -> Matrix: """ diff --git a/neural_network/math/matrix.py b/neural_network/math/matrix.py index f452f83..51b9bce 100644 --- a/neural_network/math/matrix.py +++ b/neural_network/math/matrix.py @@ -31,6 +31,9 @@ def __init__(self, rows: int, cols: int, vals: NDArray | None = None) -> None: def __str__(self) -> str: return str(self.vals) + def __add__(self, other: Matrix) -> Matrix: + return Matrix.from_array(self.vals + other.vals) + def __mul__(self, other: float | int) -> Matrix: return Matrix.from_array(self.vals * other) @@ -104,21 +107,6 @@ def random_column(cls, rows: int, low: float, high: float) -> Matrix: """ return cls.random_matrix(rows=rows, cols=1, low=low, high=high) - @staticmethod - def add(matrix: Matrix, other_matrix: Matrix) -> Matrix: - """ - Add two Matrix objects. - - Parameters: - matrix (Matrix): Matrix to use in sum - other_matrix (Matrix): Other Matrix to use in sum - - Returns: - new_matrix (Matrix): Sum of both matrices - """ - new_matrix = matrix.vals + other_matrix.vals - return Matrix.from_array(new_matrix) - @staticmethod def subtract(matrix: Matrix, other_matrix: Matrix) -> Matrix: """ diff --git a/neural_network/math/nn_math.py b/neural_network/math/nn_math.py index 30b5972..0e2c71f 100644 --- a/neural_network/math/nn_math.py +++ b/neural_network/math/nn_math.py @@ -20,7 +20,7 @@ def feedforward_through_layer( output_vals (Matrix): Output values """ output_vals = weights @ input_vals - output_vals = Matrix.add(output_vals, bias) + output_vals = output_vals + bias return Matrix.map(output_vals, activation) diff --git a/tests/math/test_matrix.py b/tests/math/test_matrix.py index 05779a7..9988289 100644 --- a/tests/math/test_matrix.py +++ b/tests/math/test_matrix.py @@ -65,7 +65,7 @@ def test_given_two_matrices_when_adding_then_check_new_matrix_correctly_calculat matrix_1 = Matrix.from_array(array_1) matrix_2 = Matrix.from_array(array_2) - new_matrix = Matrix.add(matrix_1, matrix_2) + new_matrix = matrix_1 + matrix_2 expected_vals = np.array([[0, 3], [6, -2]]) actual_vals = new_matrix.vals From 59f3a9c5bc005da525d8a90213569e533df6eecd Mon Sep 17 00:00:00 2001 From: Javid Ahmed Date: Thu, 21 Nov 2024 22:15:52 +0000 Subject: [PATCH 3/8] Replace subtract() method --- neural_network/math/matrix.py | 18 +++--------------- neural_network/math/nn_math.py | 2 +- tests/math/test_matrix.py | 2 +- 3 files changed, 5 insertions(+), 17 deletions(-) diff --git a/neural_network/math/matrix.py b/neural_network/math/matrix.py index 51b9bce..ce6d9a9 100644 --- a/neural_network/math/matrix.py +++ b/neural_network/math/matrix.py @@ -34,6 +34,9 @@ def __str__(self) -> str: def __add__(self, other: Matrix) -> Matrix: return Matrix.from_array(self.vals + other.vals) + def __sub__(self, other: Matrix) -> Matrix: + return Matrix.from_array(self.vals - other.vals) + def __mul__(self, other: float | int) -> Matrix: return Matrix.from_array(self.vals * other) @@ -107,21 +110,6 @@ def random_column(cls, rows: int, low: float, high: float) -> Matrix: """ return cls.random_matrix(rows=rows, cols=1, low=low, high=high) - @staticmethod - def subtract(matrix: Matrix, other_matrix: Matrix) -> Matrix: - """ - Subtract two Matrix objects. - - Parameters: - matrix (Matrix): Matrix to use in subtraction - other_matrix (Matrix): Other Matrix to use in subtraction - - Returns: - new_matrix (Matrix): Difference between both matrices - """ - new_matrix = matrix.vals - other_matrix.vals - return Matrix.from_array(new_matrix) - @staticmethod def multiply_element_wise(matrix: Matrix, other_matrix: Matrix) -> Matrix: """ diff --git a/neural_network/math/nn_math.py b/neural_network/math/nn_math.py index 0e2c71f..7500896 100644 --- a/neural_network/math/nn_math.py +++ b/neural_network/math/nn_math.py @@ -67,7 +67,7 @@ def calculate_error_from_expected(expected_outputs: Matrix, actual_outputs: Matr Returns: errors (Matrix): Difference between expected and actual outputs """ - return Matrix.subtract(expected_outputs, actual_outputs) + return expected_outputs - actual_outputs def calculate_next_errors(weights: Matrix, calculated_errors: Matrix) -> Matrix: diff --git a/tests/math/test_matrix.py b/tests/math/test_matrix.py index 9988289..73d77f4 100644 --- a/tests/math/test_matrix.py +++ b/tests/math/test_matrix.py @@ -77,7 +77,7 @@ def test_given_two_matrices_when_subtracting_then_check_new_matrix_correctly_cal matrix_1 = Matrix.from_array(array_1) matrix_2 = Matrix.from_array(array_2) - new_matrix = Matrix.subtract(matrix_1, matrix_2) + new_matrix = matrix_1 - matrix_2 expected_vals = np.array([[2, 1], [2, 8]]) actual_vals = new_matrix.vals From 42ccce6f2e0f68687c377b8cb148cedba4da9621 Mon Sep 17 00:00:00 2001 From: Javid Ahmed Date: Thu, 21 Nov 2024 22:20:36 +0000 Subject: [PATCH 4/8] Replace multiply_element_wise() method --- neural_network/math/matrix.py | 19 +++---------------- neural_network/math/nn_math.py | 3 +-- tests/math/test_matrix.py | 2 +- 3 files changed, 5 insertions(+), 19 deletions(-) diff --git a/neural_network/math/matrix.py b/neural_network/math/matrix.py index ce6d9a9..cb3a143 100644 --- a/neural_network/math/matrix.py +++ b/neural_network/math/matrix.py @@ -37,7 +37,9 @@ def __add__(self, other: Matrix) -> Matrix: def __sub__(self, other: Matrix) -> Matrix: return Matrix.from_array(self.vals - other.vals) - def __mul__(self, other: float | int) -> Matrix: + def __mul__(self, other: float | int | Matrix) -> Matrix: + if isinstance(other, Matrix): + return Matrix.from_array(self.vals * other.vals) return Matrix.from_array(self.vals * other) def __matmul__(self, other: Matrix) -> Matrix: @@ -110,21 +112,6 @@ def random_column(cls, rows: int, low: float, high: float) -> Matrix: """ return cls.random_matrix(rows=rows, cols=1, low=low, high=high) - @staticmethod - def multiply_element_wise(matrix: Matrix, other_matrix: Matrix) -> Matrix: - """ - Multiply Matrix element wise with Matrix. - - Parameters: - matrix (Matrix): Matrix to use for multiplication - other_matrix (Matrix): Other Matrix to use for multiplication - - Returns: - new_matrix (Matrix): Multiplied Matrix - """ - new_matrix = matrix.vals * other_matrix.vals - return Matrix.from_array(new_matrix) - @staticmethod def transpose(matrix: Matrix) -> Matrix: """ diff --git a/neural_network/math/nn_math.py b/neural_network/math/nn_math.py index 7500896..66c8754 100644 --- a/neural_network/math/nn_math.py +++ b/neural_network/math/nn_math.py @@ -38,8 +38,7 @@ def calculate_gradient(layer_vals: Matrix, errors: Matrix, activation: Activatio gradient (Matrix): Gradient values """ gradient = Matrix.from_array(np.vectorize(activation.derivative)(layer_vals.vals)) - gradient = Matrix.multiply_element_wise(gradient, errors) - return gradient * lr + return gradient * errors * lr def calculate_delta(layer_vals: Matrix, gradients: Matrix) -> Matrix: diff --git a/tests/math/test_matrix.py b/tests/math/test_matrix.py index 73d77f4..854bcd7 100644 --- a/tests/math/test_matrix.py +++ b/tests/math/test_matrix.py @@ -101,7 +101,7 @@ def test_given_two_matrices_when_multiplying_element_wise_then_check_new_matrix_ matrix_1 = Matrix.from_array(array_1) matrix_2 = Matrix.from_array(array_2) - new_matrix = Matrix.multiply_element_wise(matrix_1, matrix_2) + new_matrix = matrix_1 * matrix_2 expected_vals = np.array([[-1, 2], [8, -15], [6, 8]]) actual_vals = new_matrix.vals From 9973bd8a5e472e467d7414ea28cebac667c10259 Mon Sep 17 00:00:00 2001 From: Javid Ahmed Date: Thu, 21 Nov 2024 22:31:42 +0000 Subject: [PATCH 5/8] Fix shift_vals() method --- neural_network/math/matrix.py | 2 +- tests/math/test_matrix.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/neural_network/math/matrix.py b/neural_network/math/matrix.py index cb3a143..ee97869 100644 --- a/neural_network/math/matrix.py +++ b/neural_network/math/matrix.py @@ -235,5 +235,5 @@ def shift_vals(self, shift: float) -> None: Parameters: shift (float): Factor to shift values by """ - _mult_array = rng.uniform(low=(1 - shift), high=(1 + shift), size=self.shape) + self._vals *= rng.uniform(low=(1 - shift), high=(1 + shift), size=self.shape) self._vals = _mult_array diff --git a/tests/math/test_matrix.py b/tests/math/test_matrix.py index 854bcd7..62193f0 100644 --- a/tests/math/test_matrix.py +++ b/tests/math/test_matrix.py @@ -180,3 +180,5 @@ def test_given_matrix_when_shifting_vals_then_check_vals_are_different(self) -> matrix_1.shift_vals(0.5) assert not np.all(matrix_1.vals == matrix_2.vals) + assert np.all(array * 0.5 <= matrix_1.vals) + assert np.all(matrix_1.vals <= array * 1.5) From 8504259e398be21978ffdd56aa85b53c50464982 Mon Sep 17 00:00:00 2001 From: Javid Ahmed Date: Thu, 21 Nov 2024 22:44:45 +0000 Subject: [PATCH 6/8] Update Matrix init --- neural_network/math/matrix.py | 41 ++++++++++------------------------- tests/math/test_matrix.py | 6 ----- 2 files changed, 11 insertions(+), 36 deletions(-) diff --git a/neural_network/math/matrix.py b/neural_network/math/matrix.py index ee97869..ef642a3 100644 --- a/neural_network/math/matrix.py +++ b/neural_network/math/matrix.py @@ -15,18 +15,14 @@ class Matrix: This class handles the matrix mathematics required to pass data through neural networks. """ - def __init__(self, rows: int, cols: int, vals: NDArray | None = None) -> None: + def __init__(self, vals: NDArray) -> None: """ Initialise Matrix with number of rows and columns, and optionally the matrix values. Parameters: - rows (int): Number of rows in matrix - cols (int): Number of columns in matrix - vals (NDArray | None): Matrix values if specified + vals (NDArray): Matrix values """ - self._rows = rows - self._cols = cols - self._vals = vals + self.vals = vals def __str__(self) -> str: return str(self.vals) @@ -45,12 +41,6 @@ def __mul__(self, other: float | int | Matrix) -> Matrix: def __matmul__(self, other: Matrix) -> Matrix: return Matrix.from_array(self.vals @ other.vals) - @property - def vals(self) -> NDArray: - if self._vals is None: - self._vals = np.zeros(shape=self.shape) - return self._vals - @property def as_list(self) -> list[float]: matrix_list = self.vals.tolist()[0] @@ -58,7 +48,7 @@ def as_list(self) -> list[float]: @property def shape(self) -> tuple: - return (self._rows, self._cols) + return self.vals.shape @classmethod def from_array(cls, matrix_array: NDArray | list[list[float]] | list[float]) -> Matrix: @@ -72,13 +62,9 @@ def from_array(cls, matrix_array: NDArray | list[list[float]] | list[float]) -> matrix (Matrix): Matrix with assigned values """ matrix_array = np.array(matrix_array, dtype=object) - try: - _rows, _cols = matrix_array.shape - except ValueError: + if matrix_array.ndim == 1: matrix_array = np.expand_dims(matrix_array, axis=1) - _rows, _cols = matrix_array.shape - - return cls(_rows, _cols, matrix_array) + return cls(matrix_array) @classmethod def random_matrix(cls, rows: int, cols: int, low: float, high: float) -> Matrix: @@ -94,8 +80,7 @@ def random_matrix(cls, rows: int, cols: int, low: float, high: float) -> Matrix: Returns: matrix (Matrix): Matrix with random values """ - _vals = rng.uniform(low=low, high=high, size=(rows, cols)) - return cls.from_array(_vals) + return cls.from_array(rng.uniform(low=low, high=high, size=(rows, cols))) @classmethod def random_column(cls, rows: int, low: float, high: float) -> Matrix: @@ -123,8 +108,7 @@ def transpose(matrix: Matrix) -> Matrix: Returns: new_matrix (Matrix): Transposed Matrix """ - new_matrix = matrix.vals.transpose() - return Matrix.from_array(new_matrix) + return Matrix.from_array(matrix.vals.transpose()) @staticmethod def map(matrix: Matrix, activation: ActivationFunction) -> Matrix: @@ -138,8 +122,7 @@ def map(matrix: Matrix, activation: ActivationFunction) -> Matrix: Returns: new_matrix (Matrix): Matrix with mapped values """ - new_matrix = np.vectorize(activation.func)(matrix.vals) - return Matrix.from_array(new_matrix) + return Matrix.from_array(np.vectorize(activation.func)(matrix.vals)) @staticmethod def average_matrix(matrix: Matrix, other_matrix: Matrix) -> Matrix: @@ -153,8 +136,7 @@ def average_matrix(matrix: Matrix, other_matrix: Matrix) -> Matrix: Returns: new_matrix (Matrix): Average of both matrices """ - new_matrix = np.average([matrix.vals, other_matrix.vals], axis=0) - return Matrix.from_array(new_matrix) + return Matrix.from_array(np.average([matrix.vals, other_matrix.vals], axis=0)) @staticmethod def mutated_matrix(matrix: Matrix, mutation_rate: float, random_range: list[float]) -> Matrix: @@ -235,5 +217,4 @@ def shift_vals(self, shift: float) -> None: Parameters: shift (float): Factor to shift values by """ - self._vals *= rng.uniform(low=(1 - shift), high=(1 + shift), size=self.shape) - self._vals = _mult_array + self.vals *= rng.uniform(low=(1 - shift), high=(1 + shift), size=self.shape) diff --git a/tests/math/test_matrix.py b/tests/math/test_matrix.py index 62193f0..7cc4753 100644 --- a/tests/math/test_matrix.py +++ b/tests/math/test_matrix.py @@ -7,12 +7,6 @@ class TestMatrix: - def test_given_no_vals_when_creating_matrix_then_check_matrix_has_zero_vals( - self, mock_len_inputs: int, mock_len_outputs: int - ) -> None: - test_matrix = Matrix(rows=mock_len_inputs, cols=mock_len_outputs) - assert not np.any(test_matrix.vals) - def test_given_shape_when_creating_random_matrix_then_check_matrix_has_correct_shape( self, mock_weights_range: list[float], mock_len_inputs: int, mock_len_outputs: int ) -> None: From 1062ec37342633232f29de989c3bb878acdb8e49 Mon Sep 17 00:00:00 2001 From: Javid Ahmed Date: Thu, 21 Nov 2024 22:46:49 +0000 Subject: [PATCH 7/8] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9d8bf0c..6591797 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ This library uses Pytest for the unit tests. These tests are located in the `tests` directory. To run the tests: - python -m pytest tests -vx --cov --cov-report term-missing + python -m pytest tests ## Linting and Formatting This library uses `ruff` for linting and formatting. From d17c7b10324ee7b7a6bac617f78399ccda41e94f Mon Sep 17 00:00:00 2001 From: Javid Ahmed Date: Thu, 21 Nov 2024 22:46:55 +0000 Subject: [PATCH 8/8] Update version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 306c6c3..0a9e9b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" [project] name = "neural-network" -version = "1.13.1" +version = "1.14.0" description = "An artificial neural network library in Python." readme = "README.md" requires-python = ">=3.12"