Skip to content

Commit

Permalink
Merge pull request #1 from javidahmed64592/add-nn
Browse files Browse the repository at this point in the history
Add NeuralNetwork and other required classes
  • Loading branch information
javidahmed64592 authored Apr 10, 2024
2 parents 41adb77 + 4d1d3c5 commit 367c6c6
Show file tree
Hide file tree
Showing 17 changed files with 1,065 additions and 7 deletions.
3 changes: 2 additions & 1 deletion Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ verify_ssl = true
name = "pypi"

[packages]

numpy = "*"
pytest = "*"

[dev-packages]
isort = "*"
Expand Down
95 changes: 89 additions & 6 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 51 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import datetime

import numpy as np

from src.nn.neural_network import NeuralNetwork


def main():
num_inputs = 2
num_hidden = 4
num_outputs = 1

inputs = [[0.0, 1.0], [1.0, 0.0], [1.0, 1.0], [0.0, 0.0]]
outputs = [[1.0], [1.0], [0.0], [0.0]]

nn = NeuralNetwork(num_inputs, num_hidden, num_outputs)

for _ in range(20000):
random_choice = np.random.randint(low=0, high=len(inputs))
nn.train(inputs[random_choice], outputs[random_choice])

print(f"Guessing inputs {inputs[0]}: Calculated outputs {nn.feedforward(inputs[0])} \t| Expected: {outputs[0]}")
print(f"Guessing inputs {inputs[1]}: Calculated outputs {nn.feedforward(inputs[1])} \t| Expected: {outputs[1]}")
print(f"Guessing inputs {inputs[2]}: Calculated outputs {nn.feedforward(inputs[2])} \t| Expected: {outputs[2]}")
print(f"Guessing inputs {inputs[3]}: Calculated outputs {nn.feedforward(inputs[3])} \t| Expected: {outputs[3]}")


def time_feedforward():
num_inputs = 16
num_hidden = 8
num_outputs = 4

nn = NeuralNetwork(num_inputs, num_hidden, num_outputs)

num_iters = 12000 * 100

begin_time = datetime.datetime.now()
print(f"Starting feedfoward: {num_iters} times")
for i in range(num_iters):
print(f"\rProgress: {i+1} / {num_iters}", flush=True, end="")
inputs = np.random.uniform(low=-1, high=1, size=(num_inputs,))
nn.feedforward(inputs)

dt = datetime.datetime.now() - begin_time
dt_m = int(dt.total_seconds() // 60)
dt_s = int(dt.total_seconds() - (dt_m * 60))
print(f"\nDone! The time it took is {dt_m}m {dt_s}s.")


if __name__ == "__main__":
time_feedforward()
Empty file added src/__init__.py
Empty file.
Empty file added src/math/__init__.py
Empty file.
21 changes: 21 additions & 0 deletions src/math/activation_functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from typing import cast

import numpy as np


class ActivationFunctions:
"""
This class is used to define activation functions.
"""

@staticmethod
def linear(x: float) -> float:
return x

@staticmethod
def relu(x: float) -> float:
return max(x, 0)

@staticmethod
def sigmoid(x: float) -> float:
return cast(float, 1 / (1 + np.exp(-x)))
183 changes: 183 additions & 0 deletions src/math/matrix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
from __future__ import annotations

from typing import Callable, List, Optional

import numpy as np
from numpy.typing import NDArray


class Matrix:
"""
This class handles the matrix mathematics required to pass data through neural networks.
"""

def __init__(self, rows: int, cols: int, data: Optional[NDArray] = None) -> 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
data (Optional[NDArray]): Matrix values if specified
"""
self._rows = rows
self._cols = cols
self._data = data

def __str__(self) -> str:
return str(self.data)

@property
def data(self):
if self._data is None:
self._data = np.zeros(shape=self.shape)
return self._data

@property
def shape(self) -> tuple:
return (self._rows, self._cols)

@classmethod
def from_array(cls, matrix_array: NDArray | List[List[float]] | List[float]) -> Matrix:
"""
Create a Matrix from an array.
Parameters:
matrix_array (NDArray | List[List[float]] | List[float]): Array of matrix values
Returns:
matrix (Matrix): Matrix with assigned values
"""
matrix_array = np.array(matrix_array)
try:
_rows, _cols = matrix_array.shape
except ValueError:
matrix_array = np.expand_dims(matrix_array, axis=1)
_rows, _cols = matrix_array.shape

matrix = cls(_rows, _cols, matrix_array)
return matrix

@classmethod
def random_matrix(cls, rows: int, cols: int, low: float, high: float) -> Matrix:
"""
Create Matrix of specified shape with random values in specified range.
Parameters:
rows (int): Number of rows in matrix
cols (int): Number of columns in matrix
low (float): Lower boundary for random number
high (float): Upper boundary for random number
Returns:
matrix (Matrix): Matrix with random values
"""
_data = np.random.uniform(low=low, high=high, size=(rows, cols))
matrix = cls.from_array(_data)
return matrix

@classmethod
def random_column(cls, rows: int, low: float, high: float) -> Matrix:
"""
Create column Matrix with random values in specified range.
Parameters:
rows (int): Number of rows in matrix
low (float): Lower boundary for random number
high (float): Upper boundary for random number
Returns:
matrix (Matrix): Column Matrix with random values
"""
matrix = cls.random_matrix(rows=rows, cols=1, low=low, high=high)
return matrix

@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.data + other_matrix.data
return Matrix.from_array(new_matrix)

@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.data - other_matrix.data
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.data
new_matrix = matrix.data.dot(val)
return Matrix.from_array(new_matrix)

@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.data * other_matrix.data
return Matrix.from_array(new_matrix)

@staticmethod
def transpose(matrix: Matrix) -> Matrix:
"""
Return transpose of Matrix.
Parameters:
matrix (Matrix): Matrix to transpose
Returns:
new_matrix (Matrix): Transposed Matrix
"""
new_matrix = matrix.data.transpose()
return Matrix.from_array(new_matrix)

@staticmethod
def map(matrix: Matrix, func: Callable) -> Matrix:
"""
Map all values of Matrix through specified function.
Parameters:
matrix (Matrix): Matrix to map
Returns:
new_matrix (Matrix): Matrix with mapped values
"""
new_matrix = np.vectorize(func)(matrix.data)
return Matrix.from_array(new_matrix)
Loading

0 comments on commit 367c6c6

Please sign in to comment.