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.
- nn_with_backprop_pk/__init__.py +0 -0
- nn_with_backprop_pk/core/__init__.py +0 -0
- nn_with_backprop_pk/core/backprop.py +72 -0
- nn_with_backprop_pk/core/feed_forward.py +26 -0
- nn_with_backprop_pk/core/layers.py +38 -0
- nn_with_backprop_pk/core/learner.py +78 -0
- nn_with_backprop_pk/utils/__init__.py +0 -0
- nn_with_backprop_pk/utils/activations.py +10 -0
- nn_with_backprop_pk/utils/data_randomize.py +12 -0
- nn_with_backprop_pk/utils/data_split.py +19 -0
- nn_with_backprop_pk/utils/derivatives.py +25 -0
- nn_with_backprop_pk/utils/loss_functions.py +13 -0
- nn_with_backprop_pk/utils/parameters_init.py +28 -0
- nn_with_backprop_pk-0.1.0.dist-info/METADATA +19 -0
- nn_with_backprop_pk-0.1.0.dist-info/RECORD +16 -0
- nn_with_backprop_pk-0.1.0.dist-info/WHEEL +4 -0
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,,
|