nn-with-backprop-pk 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
File without changes
File without changes
@@ -0,0 +1,72 @@
1
+ import os
2
+ import numpy as np
3
+ from typing import List, Tuple, Literal
4
+ from src.nn_with_backprop_pk.core.feed_forward import FeedForward
5
+ from src.nn_with_backprop_pk.utils.activations import sigmoid, relu, tanh
6
+ from src.nn_with_backprop_pk.utils.derivatives import diff_sigmoid, diff_binary_cross_entropy
7
+ from src.nn_with_backprop_pk.utils.loss_functions import mean_squared_error, binary_cross_entropy
8
+
9
+ activation_funcs = {
10
+ "sigmoid": sigmoid,
11
+ "relu": relu,
12
+ "tanh": tanh
13
+ }
14
+
15
+ loss_funcs = {
16
+ "mean_squared_error": mean_squared_error,
17
+ "binary_cross_entropy": binary_cross_entropy
18
+ }
19
+
20
+ LossType = Literal["mean_squared_error", "binary_cross_entropy"]
21
+
22
+ class BackProp():
23
+
24
+ def __init__(self, forward_pass: FeedForward, learning_rate: float, loss_function: LossType):
25
+ self.ff = forward_pass #not forward_pass() since "()" creates an instance, but we just need to pass an instance while using these classes
26
+ self.learning_rate = learning_rate
27
+ self.loss_function = loss_function
28
+
29
+ def back_prop(self) -> Tuple[List[np.ndarray], List[np.ndarray]]:
30
+ # self.deltas_ = [np.zeros_like(s) for s in self.ff.net.layers]
31
+ # del self.deltas_[0]
32
+ self.deltas = []
33
+ # self.diff_losses_weights_ = [np.zeros_like(s) for s in self.ff.net.weights]
34
+ # self.diff_losses_biases_ = [np.zeros_like(s) for s in self.ff.net.biases]
35
+ self.diff_losses_weights = []
36
+ self.diff_losses_biases = []
37
+
38
+ if self.ff.net.activations_layers[-1] == "sigmoid" and self.loss_function == "binary_cross_entropy":
39
+ # Special case: when using sigmoid activation + binary cross-entropy loss,
40
+ # the gradient simplifies to (y_pred - y_true)
41
+ # Look at the docs for this special case
42
+ self.del_final = self.ff.net.layers[-1] - self.ff.net.label[0].reshape(self.ff.net.label.shape[1],1)
43
+ else:
44
+ #Example - self.del_final = diff_sigmoid(self.ff.net.layers[-1]) * diff_binary_cross_entropy(self.ff.net.layers[-1], self.ff.net.label.reshape(self.ff.net.label.shape[1],1))
45
+
46
+ raise NotImplementedError(
47
+ f"Delta not implemented for combination:\n"
48
+ f" Loss function : {self.loss_function}\n"
49
+ f" Output activation : {self.ff.net.activations_layers[-1]}"
50
+ )
51
+ self.deltas.append(self.del_final)
52
+ for i in range(len(self.ff.net.layers)-1, 1, -1):
53
+ self.deltas.append((self.ff.net.weights[i-1].T @ self.deltas[-1]) * diff_sigmoid(self.ff.net.layers[i-1]))
54
+ self.deltas = self.deltas[::-1]
55
+ for i in range(len(self.deltas)):
56
+ # der_w = self.layers[i] @ self.deltas[i].T
57
+ der_w = self.deltas[i] @ self.ff.net.layers[i].T
58
+ self.diff_losses_weights.append(der_w)
59
+ der_b = self.deltas[i]
60
+ self.diff_losses_biases.append(der_b)
61
+
62
+ return self.diff_losses_weights, self.diff_losses_biases
63
+
64
+ def update_parameters(self) -> Tuple[List[np.ndarray], List[np.ndarray]]:
65
+ for i in range(len(self.ff.net.weights)):
66
+ self.ff.net.weights[i] -= self.learning_rate * self.diff_losses_weights[i]
67
+ self.ff.net.biases[i] -= self.learning_rate * self.diff_losses_biases[i]
68
+ return self.ff.net.weights, self.ff.net.biases
69
+
70
+
71
+
72
+
@@ -0,0 +1,26 @@
1
+ import numpy as np
2
+ from src.nn_with_backprop_pk.core.layers import NeuNet
3
+ from src.nn_with_backprop_pk.utils.activations import sigmoid, relu, tanh
4
+
5
+ activation_funcs = {
6
+ "sigmoid": sigmoid,
7
+ "relu": relu,
8
+ "tanh": tanh
9
+ }
10
+
11
+ class FeedForward():
12
+
13
+ def __init__(self, net: NeuNet):
14
+ self.net = net
15
+
16
+ def forward_pass(self, data_sample: np.ndarray = None) -> None:
17
+
18
+ self.net.layers[0] = data_sample.reshape(self.net.tr_data.shape[1],1)
19
+ for i in range(len(self.net.layers)-1):
20
+ if self.net.layers[i].ndim != 2:
21
+ self.net.layers[i] = self.net.layers[i].reshape(self.net.tr_data.shape[1], 1)
22
+ self.net.layers[i+1] = self.net.weights[i] @ self.net.layers[i] + self.net.biases[i]
23
+ ac_func = activation_funcs[self.net.activations_layers[i]]
24
+ self.net.layers[i+1] = ac_func(self.net.layers[i+1])
25
+
26
+
@@ -0,0 +1,38 @@
1
+ # import Packages
2
+
3
+ import os
4
+ import numpy as np
5
+ import logging
6
+ from time import time
7
+ import pandas as pd
8
+ from src.nn_with_backprop_pk.utils.parameters_init import xavier_init
9
+ from typing import Literal
10
+
11
+ ActivationType = Literal["relu", "sigmoid", "tanh"]
12
+
13
+ class NeuNet():
14
+
15
+ def __init__(self, tr_X: np.ndarray, tr_y:np.ndarray):
16
+ self.layers = []
17
+ self.activations_layers = []
18
+ self.tr_data = tr_X
19
+ self.label = tr_y
20
+
21
+ def input_layer(self) -> None:
22
+ # self.layers.append(self.tr_data[0].reshape(self.tr_data.shape[1],1))
23
+ self.layers.append(np.zeros((self.tr_data.shape[1],1), dtype=np.float64))
24
+
25
+ def add_hidden_layer(self, neurons: int, activation: ActivationType) -> None:
26
+ self.layers.append(np.zeros((neurons,1), dtype=np.float64))
27
+ self.activations_layers.append(activation)
28
+
29
+ def output_layer(self, neurons: int, activation: ActivationType) -> None:
30
+ self.layers.append(np.zeros((neurons,1), dtype=np.float64))
31
+ self.activations_layers.append(activation)
32
+
33
+ def initialize_weights(self) -> None:
34
+ self.shape = [(self.layers[i+1].shape[0], self.layers[i].shape[0]) for i in range(len(self.layers)-1)]
35
+ self.weights = [xavier_init(i) for i in self.shape]
36
+ self.biases = [xavier_init((i[0],1)) for i in self.shape]
37
+
38
+
@@ -0,0 +1,78 @@
1
+ import numpy as np
2
+ from src.nn_with_backprop_pk.core.backprop import BackProp
3
+ from src.nn_with_backprop_pk.utils.activations import sigmoid, relu, tanh
4
+ from src.nn_with_backprop_pk.utils.derivatives import diff_sigmoid, diff_binary_cross_entropy
5
+ from src.nn_with_backprop_pk.utils.loss_functions import binary_cross_entropy
6
+ import matplotlib.pyplot as plt
7
+ from tqdm import tqdm
8
+ import logging
9
+ # Set up logging once in your main script or module
10
+ logging.basicConfig(level=logging.INFO)
11
+
12
+ class Learner(BackProp):
13
+
14
+ def __init__(self, backprop: BackProp, epoch: int):
15
+
16
+ self.bp = backprop
17
+ self.epoch = epoch
18
+
19
+ def calculate_loss(self, predicted_sample, actual_sample) -> float:
20
+ list_loss = []
21
+ # for i in range(len(self.bp.ff.net.layers[-1])):
22
+ # _loss = binary_cross_entropy(self.bp.ff.net.layers[-1], self.bp.ff.net.label[0])
23
+ _loss = binary_cross_entropy(predicted_sample, actual_sample)
24
+ # list_loss.append(_loss)
25
+ # avg_loss = sum(list_loss) / len(list_loss)
26
+
27
+ return _loss
28
+
29
+ def train(self):
30
+ self.loss_epoch_avg = []
31
+ for i in range(self.epoch):
32
+ loss_epoch = []
33
+ for j in range(len(self.bp.ff.net.tr_data)):
34
+
35
+ self.bp.ff.forward_pass(self.bp.ff.net.tr_data[j])
36
+ d_w, d_b = self.bp.back_prop()
37
+ # logging.info(np.linalg.norm(d_w[-1]))
38
+ self.bp.update_parameters()
39
+ reshaped_label = self.bp.ff.net.label[j].reshape(self.bp.ff.net.label.shape[1], 1)
40
+ loss_epoch.append(self.calculate_loss(self.bp.ff.net.layers[-1], reshaped_label))
41
+
42
+ # # Update input layer
43
+ # self.bp.ff.net.layers[0] = self.bp.ff.net.tr_data[j+1]
44
+ self.loss_epoch_avg.append(sum(loss_epoch) / len(loss_epoch))
45
+ logging.info(f"Loss - {sum(loss_epoch) / len(loss_epoch)}")
46
+ logging.info(f"Epoch: {str(i)} - Average Loss: {self.loss_epoch_avg[-1]}")
47
+
48
+ return self.loss_epoch_avg
49
+
50
+ def plot_loss(self)-> None:
51
+
52
+ fig, ax = plt.subplots(1, 1, figsize=(6, 6))
53
+ ax.plot(list(range(self.epoch)), self.loss_epoch_avg)
54
+ ax.set_title("Loss vs epoch")
55
+ ax.set_xlabel("Epochs")
56
+ ax.set_ylabel("Average Loss")
57
+ plt.tight_layout()
58
+ plt.show()
59
+
60
+ return None
61
+
62
+ def binary_classification_accuracy(self, test_data: np.ndarray, test_label: np.ndarray) -> np.float64:
63
+ preds = []
64
+ for i in range(test_data.shape[0]):
65
+ # self.bp.ff.net = self.bp.ff.net(test_X, test_y)
66
+ self.bp.ff.forward_pass(test_data[i].reshape(test_data.shape[1], 1))
67
+ if self.bp.ff.net.layers[-1] < 0.5:
68
+ preds.append(0)
69
+ else:
70
+ preds.append(1)
71
+ preds_arr = np.array(preds)
72
+ if test_label.ndim > 1:
73
+ test_label = test_label.reshape(test_label.shape[0],)
74
+
75
+ return sum(test_label == preds_arr) / len(test_label)
76
+
77
+
78
+
File without changes
@@ -0,0 +1,10 @@
1
+ import numpy as np
2
+
3
+ def sigmoid(x: np.ndarray) -> np.ndarray:
4
+ return 1 / (1 + np.exp(-x))
5
+
6
+ def relu(x: np.ndarray) -> np.ndarray:
7
+ return np.maximum(0, x)
8
+
9
+ def tanh(x: np.ndarray) -> np.ndarray:
10
+ return ((np.exp(x) - np.exp(-x))/(np.exp(x) + np.exp(-x)))
@@ -0,0 +1,12 @@
1
+ import random
2
+ import pandas as pd
3
+
4
+ def df_random(tr_data: pd.DataFrame) -> pd.DataFrame:
5
+
6
+ if not isinstance(tr_data, pd.DataFrame):
7
+ raise NotImplementedError("Code not yet implemented for data other than pandas dataframe!")
8
+
9
+ random_list = random.sample(range(0, tr_data.shape[0]), tr_data.shape[0])
10
+ rand_df = tr_data.loc[random_list]
11
+
12
+ return rand_df
@@ -0,0 +1,19 @@
1
+ from typing import Tuple
2
+ import pandas as pd
3
+ from math import ceil
4
+
5
+ class SplitFactor(float):
6
+ def __new__(cls, val):
7
+ if not 0.6 <= val <=0.9:
8
+ raise ValueError(f"The value must be between 0.6 and 0.9. Provided {val}.")
9
+ return float.__new__(cls, val)
10
+
11
+ def train_test_split(df: pd.DataFrame, split_factor: SplitFactor) -> Tuple[pd.DataFrame, pd.DataFrame]:
12
+
13
+ split_factor = SplitFactor(split_factor)
14
+ total_rows = df.shape[0]
15
+ ratio = ceil(total_rows * split_factor)
16
+ train_data = df.iloc[:ratio, :]
17
+ test_data = df.iloc[ratio:, :]
18
+
19
+ return train_data, test_data
@@ -0,0 +1,25 @@
1
+ import numpy as np
2
+ from src.nn_with_backprop_pk.utils.activations import sigmoid, tanh
3
+
4
+ def diff_mse(y_pred: np.ndarray, y_actual: np.ndarray) -> np.ndarray:
5
+ return y_pred - y_actual
6
+
7
+ def diff_binary_cross_entropy(y_pred: np.ndarray, y_actual: np.ndarray) -> np.ndarray:
8
+ epsilon = 1e-15 #to prevent log(0)
9
+ y_pred = np.clip(y_pred, epsilon, 1 - epsilon) # to prevent values like 0 and 1
10
+ return -((y_actual / y_pred) - ((1-y_actual)/(1-y_pred)))
11
+
12
+ # def diff_sigmoid(x: np.ndarray) -> np.ndarray:
13
+ # s = sigmoid(x)
14
+ # return s * (1 -s)
15
+
16
+ def diff_sigmoid(x: np.ndarray) -> np.ndarray:
17
+ # s = sigmoid(x)
18
+ return x * (1 -x)
19
+
20
+ def diff_relu(x: np.ndarray) -> np.ndarray:
21
+ return np.where(x > 0, 1, 0)
22
+
23
+ def diff_tanh(x: np.ndarray) -> np.ndarray:
24
+ t = tanh(x)
25
+ return 1 - t**2
@@ -0,0 +1,13 @@
1
+ import numpy as np
2
+ import math
3
+
4
+ def mean_squared_error(y_pred: np.ndarray, y_actual: np.ndarray) -> float:
5
+ loss = 0.5 * np.mean((y_pred - y_actual) ** 2)
6
+ return loss
7
+
8
+ def binary_cross_entropy(y_pred: np.ndarray, y_actual: np.ndarray) -> float:
9
+ epsilon = 1e-15 #to prevent log(0)
10
+ y_pred = np.clip(y_pred, epsilon, 1 - epsilon) # to prevent values like 0 and 1
11
+ loss = -np.mean((y_actual * np.log(y_pred)) + ((1 - y_actual)*np.log(1 - y_pred)))
12
+ return loss
13
+
@@ -0,0 +1,28 @@
1
+ import numpy as np
2
+
3
+ def xavier_init(shape: tuple) -> np.ndarray:
4
+ if not isinstance(shape, tuple):
5
+ raise TypeError(f"Shape must be a tuple, got {type(shape)}: {shape}")
6
+ if len(shape) == 1:
7
+ dim = shape[0]
8
+ limit = np.sqrt(6 / (dim + dim))
9
+ elif len(shape) == 2:
10
+ in_dim, out_dim = shape
11
+ limit = np.sqrt(6 / (in_dim + out_dim))
12
+ else:
13
+ raise ValueError(f"Unsupported Shape: {shape}")
14
+
15
+ return np.random.uniform(-limit, limit, size=shape)
16
+
17
+ def he_init(shape: tuple) -> np.ndarray:
18
+ if not isinstance(shape, tuple):
19
+ raise TypeError(f"Shape must be a tuple, got {type(shape)}: {shape}")
20
+ if len(shape) == 1:
21
+ in_dim = shape[0]
22
+ elif len(shape) == 2:
23
+ in_dim, _ = shape
24
+ else:
25
+ raise ValueError(f"Unsupported Shape: {shape}")
26
+ std = np.sqrt(2 / in_dim)
27
+
28
+ return np.random.randn(*shape) * std
@@ -0,0 +1,19 @@
1
+ Metadata-Version: 2.1
2
+ Name: nn-with-backprop-pk
3
+ Version: 0.1.0
4
+ Summary: Poetry file for the project NN with backprop
5
+ Author: ppkhairn
6
+ Requires-Python: >=3.12,<4.0
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.12
9
+ Requires-Dist: matplotlib (>=3.10.3,<4.0.0)
10
+ Requires-Dist: numpy (>=2.3.1,<3.0.0)
11
+ Requires-Dist: pandas (>=2.3.0,<3.0.0)
12
+ Requires-Dist: pytest (>=8.4.1,<9.0.0)
13
+ Requires-Dist: python-dotenv (>=1.1.1,<2.0.0)
14
+ Requires-Dist: setuptools (>=80.9.0,<81.0.0)
15
+ Requires-Dist: tqdm (>=4.67.1,<5.0.0)
16
+ Requires-Dist: wheel (>=0.45.1,<0.46.0)
17
+ Description-Content-Type: text/markdown
18
+
19
+ # NN_with_backprop
@@ -0,0 +1,16 @@
1
+ nn_with_backprop_pk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ nn_with_backprop_pk/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ nn_with_backprop_pk/core/backprop.py,sha256=MHM4pu-SkvO3OH3X1R81h7pBtXdoUYjG0jFK68PXbSM,3337
4
+ nn_with_backprop_pk/core/feed_forward.py,sha256=HxENzI__yZh2l6dA_tS_c71_EK7NCg0ld-4P1hL2N7Q,915
5
+ nn_with_backprop_pk/core/layers.py,sha256=8nMAd-BAtYHO-gEGDU-KUP7MrVPiUt4qBTbvRtxXQ9o,1351
6
+ nn_with_backprop_pk/core/learner.py,sha256=4unRm9S2-RZOJOfZ-GYAtMby4hsOjxAiPepnUabz5bc,3037
7
+ nn_with_backprop_pk/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ nn_with_backprop_pk/utils/activations.py,sha256=Z-tdarGfvvbYBoga-gBzilETqNz1Rxr73_tLm182VSc,272
9
+ nn_with_backprop_pk/utils/data_randomize.py,sha256=EoShHsudYQX8ZEVUJLyNCUyAAXZCdgADjinYBEJqyrc,374
10
+ nn_with_backprop_pk/utils/data_split.py,sha256=EM9sPIa1Izlr_rLHKCgbhGYNYHSVrZrnTa_W4rO1Hq0,610
11
+ nn_with_backprop_pk/utils/derivatives.py,sha256=9zUuEdKDh4tCRwnk1qTM6j7xilTSwoROdcBJkqzvHa4,804
12
+ nn_with_backprop_pk/utils/loss_functions.py,sha256=QQszQm-p-OGaX0xTk8wMNl5GTHabJxrVQYU2fxRhwDI,479
13
+ nn_with_backprop_pk/utils/parameters_init.py,sha256=rSBXYQ3Ozz5C0eszNcpVgbmq0-EOp6BOtow0IYHu8iA,897
14
+ nn_with_backprop_pk-0.1.0.dist-info/METADATA,sha256=2TS2NjH3YtnCEHfhFdX53wQY0-zWiBc6THS_orG8B6U,651
15
+ nn_with_backprop_pk-0.1.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
16
+ nn_with_backprop_pk-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 1.9.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any