epicon 0.3.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.
Files changed (61) hide show
  1. epicon/__init__.py +78 -0
  2. epicon/_jit.py +250 -0
  3. epicon/activations/__init__.py +8 -0
  4. epicon/activations/base.py +21 -0
  5. epicon/activations/leaky_relu.py +65 -0
  6. epicon/activations/relu.py +37 -0
  7. epicon/activations/sigmoid.py +37 -0
  8. epicon/activations/softmax.py +94 -0
  9. epicon/activations/tanh.py +80 -0
  10. epicon/datasets/__init__.py +4 -0
  11. epicon/datasets/generator.py +90 -0
  12. epicon/datasets/loader.py +305 -0
  13. epicon/ensemble/__init__.py +4 -0
  14. epicon/ensemble/random_forest_classifier.py +210 -0
  15. epicon/ensemble/random_forest_regressor.py +170 -0
  16. epicon/layers/__init__.py +6 -0
  17. epicon/layers/base.py +30 -0
  18. epicon/layers/conv1d.py +80 -0
  19. epicon/layers/dense.py +96 -0
  20. epicon/layers/dropout.py +64 -0
  21. epicon/linear_model/__init__.py +6 -0
  22. epicon/linear_model/lasso.py +180 -0
  23. epicon/linear_model/linear_regression.py +193 -0
  24. epicon/linear_model/logistic_regression.py +192 -0
  25. epicon/linear_model/ridge.py +128 -0
  26. epicon/losses/__init__.py +6 -0
  27. epicon/losses/base.py +41 -0
  28. epicon/losses/binary_cross_entropy.py +100 -0
  29. epicon/losses/categorical_cross_entropy.py +50 -0
  30. epicon/losses/mse.py +42 -0
  31. epicon/metrics/__init__.py +19 -0
  32. epicon/metrics/classification.py +185 -0
  33. epicon/metrics/regression.py +69 -0
  34. epicon/models/__init__.py +4 -0
  35. epicon/models/model.py +549 -0
  36. epicon/models/sequential.py +146 -0
  37. epicon/naive_bayes/__init__.py +3 -0
  38. epicon/naive_bayes/gaussian_nb.py +168 -0
  39. epicon/neighbors/__init__.py +4 -0
  40. epicon/neighbors/knn_classifier.py +171 -0
  41. epicon/neighbors/knn_regressor.py +125 -0
  42. epicon/optimizers/__init__.py +6 -0
  43. epicon/optimizers/adam.py +125 -0
  44. epicon/optimizers/base.py +45 -0
  45. epicon/optimizers/gradient_descent.py +43 -0
  46. epicon/optimizers/momentum.py +74 -0
  47. epicon/preprocessing/__init__.py +5 -0
  48. epicon/preprocessing/encoder.py +187 -0
  49. epicon/preprocessing/scaler.py +214 -0
  50. epicon/preprocessing/split.py +60 -0
  51. epicon/tree/__init__.py +4 -0
  52. epicon/tree/decision_tree_classifier.py +329 -0
  53. epicon/tree/decision_tree_regressor.py +286 -0
  54. epicon/utils/__init__.py +4 -0
  55. epicon/utils/layer_config.py +18 -0
  56. epicon/utils/model_builder.py +54 -0
  57. epicon-0.3.0.dist-info/METADATA +113 -0
  58. epicon-0.3.0.dist-info/RECORD +61 -0
  59. epicon-0.3.0.dist-info/WHEEL +5 -0
  60. epicon-0.3.0.dist-info/licenses/LICENSE +21 -0
  61. epicon-0.3.0.dist-info/top_level.txt +1 -0
epicon/__init__.py ADDED
@@ -0,0 +1,78 @@
1
+ """
2
+ ============================================================
3
+ Epicon
4
+ ============================================================
5
+
6
+ A lightweight, from-scratch machine learning library built on
7
+ NumPy with optional Numba acceleration. Provides a unified API
8
+ for neural networks and traditional ML models, designed to be
9
+ a minimal yet capable alternative to PyTorch/TensorFlow for
10
+ everyday ML tasks.
11
+
12
+ Key Design Principles:
13
+ - Simple, consistent API (fit/predict) across all models
14
+ - Minimal dependencies (NumPy required, Numba optional)
15
+ - Educational transparency — readable, documented source
16
+ - Fast execution via vectorized NumPy and optional Numba JIT
17
+
18
+ Quick Start:
19
+ >>> import epicon
20
+ >>> from epicon.datasets import load_iris
21
+ >>> X, y = load_iris(return_X_y=True)
22
+ >>> model = epicon.LogisticRegression()
23
+ >>> model.fit(X, y)
24
+ >>> model.predict(X[:5])
25
+ """
26
+
27
+ # ---------------------------------------------------------------------------
28
+ # Neural Network Components
29
+ # ---------------------------------------------------------------------------
30
+ from epicon.activations import Activation, LeakyReLU, ReLU, Sigmoid, Softmax, Tanh
31
+
32
+ # ---------------------------------------------------------------------------
33
+ # Datasets
34
+ # ---------------------------------------------------------------------------
35
+ from epicon.datasets import load_iris, load_mnist, make_classification, make_regression
36
+ from epicon.ensemble import RandomForestClassifier, RandomForestRegressor
37
+ from epicon.layers import Conv1D, Dense, Dropout, Layer
38
+
39
+ # ---------------------------------------------------------------------------
40
+ # Traditional ML Models
41
+ # ---------------------------------------------------------------------------
42
+ from epicon.linear_model import Lasso, LinearRegression, LogisticRegression, Ridge
43
+ from epicon.losses import MSE, BinaryCrossEntropy, CategoricalCrossEntropy, Loss
44
+
45
+ # ---------------------------------------------------------------------------
46
+ # Metrics
47
+ # ---------------------------------------------------------------------------
48
+ from epicon.metrics import (
49
+ accuracy_score,
50
+ confusion_matrix,
51
+ f1_score,
52
+ mean_absolute_error,
53
+ mean_squared_error,
54
+ precision_score,
55
+ r2_score,
56
+ recall_score,
57
+ )
58
+ from epicon.models import Model, Sequential
59
+ from epicon.naive_bayes import GaussianNB
60
+ from epicon.neighbors import KNeighborsClassifier, KNeighborsRegressor
61
+ from epicon.optimizers import Adam, GradientDescent, Momentum, Optimizer
62
+
63
+ # ---------------------------------------------------------------------------
64
+ # Preprocessing & Utilities
65
+ # ---------------------------------------------------------------------------
66
+ from epicon.preprocessing import (
67
+ LabelEncoder,
68
+ MinMaxScaler,
69
+ OneHotEncoder,
70
+ StandardScaler,
71
+ train_test_split,
72
+ )
73
+ from epicon.tree import DecisionTreeClassifier, DecisionTreeRegressor
74
+
75
+ # ---------------------------------------------------------------------------
76
+ # Version
77
+ # ---------------------------------------------------------------------------
78
+ __version__ = "0.3.0"
epicon/_jit.py ADDED
@@ -0,0 +1,250 @@
1
+ """
2
+ ---------------------------------------------------------
3
+ NUMBA JIT COMPILED KERNELS FOR PERFORMANCE-CRITICAL LOOPS
4
+ ---------------------------------------------------------
5
+
6
+ Optional Numba acceleration for tree-based models, KNN,
7
+ and other algorithms with non-vectorizable loops.
8
+ If Numba is not installed, pure Python fallbacks are used.
9
+ """
10
+
11
+ import numpy as np
12
+
13
+ try:
14
+ import numba as nb
15
+
16
+ HAS_NUMBA = True
17
+ except ImportError:
18
+ HAS_NUMBA = False
19
+ nb = None
20
+
21
+
22
+ def _jit_if_available(func=None, *, nopython=True, parallel=False, **kwargs):
23
+ """
24
+ Decorator that applies Numba JIT only if Numba is installed.
25
+ Can be used with or without arguments.
26
+
27
+ Usage:
28
+ @_jit_if_available
29
+ def my_func(x): ...
30
+
31
+ @_jit_if_available(nopython=True, parallel=True)
32
+ def my_func(x): ...
33
+ """
34
+
35
+ def decorator(f):
36
+ if HAS_NUMBA:
37
+ return nb.jit(f, nopython=nopython, parallel=parallel, **kwargs)
38
+ return f
39
+
40
+ if func is not None:
41
+ return decorator(func)
42
+ return decorator
43
+
44
+
45
+ # ---------------------------------------------------------------------------
46
+ # Tree split helpers
47
+ # ---------------------------------------------------------------------------
48
+
49
+
50
+ @_jit_if_available
51
+ def _gini_impurity(left_counts, right_counts, total_left, total_right, total_samples):
52
+ """
53
+ Compute weighted Gini impurity for a binary split.
54
+
55
+ Args:
56
+ left_counts (np.ndarray): Class counts in left child.
57
+ right_counts (np.ndarray): Class counts in right child.
58
+ total_left (int): Total samples in left child.
59
+ total_right (int): Total samples in right child.
60
+ total_samples (int): Total samples in parent.
61
+
62
+ Returns:
63
+ float: Weighted Gini impurity of the split.
64
+ """
65
+ if total_left == 0 or total_right == 0:
66
+ return 0.0
67
+
68
+ left_gini = 1.0 - np.sum((left_counts / total_left) ** 2)
69
+ right_gini = 1.0 - np.sum((right_counts / total_right) ** 2)
70
+
71
+ return (total_left / total_samples) * left_gini + (total_right / total_samples) * right_gini
72
+
73
+
74
+ @_jit_if_available
75
+ def _entropy_impurity(left_counts, right_counts, total_left, total_right, total_samples):
76
+ """
77
+ Compute weighted entropy for a binary split.
78
+
79
+ Args:
80
+ left_counts (np.ndarray): Class counts in left child.
81
+ right_counts (np.ndarray): Class counts in right child.
82
+ total_left (int): Total samples in left child.
83
+ total_right (int): Total samples in right child.
84
+ total_samples (int): Total samples in parent.
85
+
86
+ Returns:
87
+ float: Weighted entropy of the split.
88
+ """
89
+ if total_left == 0 or total_right == 0:
90
+ return 0.0
91
+
92
+ def _entropy(counts, total):
93
+ if total == 0:
94
+ return 0.0
95
+ probs = counts / total
96
+ probs = probs[probs > 0]
97
+ return -np.sum(probs * np.log2(probs))
98
+
99
+ left_ent = _entropy(left_counts, total_left)
100
+ right_ent = _entropy(right_counts, total_right)
101
+
102
+ return (total_left / total_samples) * left_ent + (total_right / total_samples) * right_ent
103
+
104
+
105
+ @_jit_if_available
106
+ def _mse_split(left_y, right_y):
107
+ """
108
+ Compute weighted MSE for a binary split.
109
+
110
+ Args:
111
+ left_y (np.ndarray): Target values in left child.
112
+ right_y (np.ndarray): Target values in right child.
113
+
114
+ Returns:
115
+ float: Weighted MSE of the split.
116
+ """
117
+ total = len(left_y) + len(right_y)
118
+ if total == 0:
119
+ return 0.0
120
+
121
+ left_mse = np.var(left_y) * len(left_y) if len(left_y) > 0 else 0.0
122
+ right_mse = np.var(right_y) * len(right_y) if len(right_y) > 0 else 0.0
123
+
124
+ return (left_mse + right_mse) / total
125
+
126
+
127
+ @_jit_if_available
128
+ def _best_split_numeric(X_column, y, num_classes, criterion, min_samples_split, min_samples_leaf):
129
+ """
130
+ Find the best threshold for a numeric feature.
131
+
132
+ Args:
133
+ X_column (np.ndarray): Feature values (sorted).
134
+ y (np.ndarray): Target values.
135
+ num_classes (int): Number of classes (1 for regression).
136
+ criterion (str): 'gini', 'entropy', or 'mse'.
137
+ min_samples_split (int): Minimum samples to split.
138
+ min_samples_leaf (int): Minimum samples per leaf.
139
+
140
+ Returns:
141
+ tuple: (best_threshold, best_impurity) or (None, inf).
142
+ """
143
+ n = len(X_column)
144
+ best_threshold = None
145
+ best_score = np.inf
146
+
147
+ if n < min_samples_split:
148
+ return None, np.inf
149
+
150
+ # Unique thresholds at midpoints between sorted values
151
+ unique_vals = np.unique(X_column)
152
+ if len(unique_vals) == 1:
153
+ return None, np.inf
154
+
155
+ thresholds = (unique_vals[:-1] + unique_vals[1:]) / 2.0
156
+
157
+ for threshold in thresholds:
158
+ left_mask = X_column <= threshold
159
+ right_mask = ~left_mask
160
+
161
+ left_count = np.sum(left_mask)
162
+ right_count = np.sum(right_mask)
163
+
164
+ if left_count < min_samples_leaf or right_count < min_samples_leaf:
165
+ continue
166
+
167
+ if criterion == "mse":
168
+ score = _mse_split(y[left_mask], y[right_mask])
169
+ elif criterion in ("gini", "entropy"):
170
+ left_y = y[left_mask].astype(np.int64)
171
+ right_y = y[right_mask].astype(np.int64)
172
+
173
+ left_counts = np.zeros(num_classes, dtype=np.float64)
174
+ right_counts = np.zeros(num_classes, dtype=np.float64)
175
+
176
+ for c in range(num_classes):
177
+ left_counts[c] = np.sum(left_y == c)
178
+ right_counts[c] = np.sum(right_y == c)
179
+
180
+ if criterion == "gini":
181
+ score = _gini_impurity(left_counts, right_counts, left_count, right_count, n)
182
+ else:
183
+ score = _entropy_impurity(left_counts, right_counts, left_count, right_count, n)
184
+ else:
185
+ raise ValueError(f"Unknown criterion: {criterion}")
186
+
187
+ if score < best_score:
188
+ best_score = score
189
+ best_threshold = threshold
190
+
191
+ return best_threshold, best_score
192
+
193
+
194
+ # ---------------------------------------------------------------------------
195
+ # KNN distance helpers
196
+ # ---------------------------------------------------------------------------
197
+
198
+
199
+ @_jit_if_available
200
+ def _euclidean_distance(x1, x2):
201
+ """
202
+ Compute Euclidean distance between two vectors.
203
+ """
204
+ diff = x1 - x2
205
+ return np.sqrt(np.dot(diff, diff))
206
+
207
+
208
+ @_jit_if_available
209
+ def _manhattan_distance(x1, x2):
210
+ """
211
+ Compute Manhattan distance between two vectors.
212
+ """
213
+ return np.sum(np.abs(x1 - x2))
214
+
215
+
216
+ @_jit_if_available
217
+ def _knn_predict_single(x_test, X_train, y_train, k, metric="euclidean"):
218
+ """
219
+ Predict label for a single test point using KNN.
220
+
221
+ Args:
222
+ x_test (np.ndarray): Single test sample.
223
+ X_train (np.ndarray): Training data.
224
+ y_train (np.ndarray): Training labels.
225
+ k (int): Number of neighbors.
226
+ metric (str): 'euclidean' or 'manhattan'.
227
+
228
+ Returns:
229
+ float: Predicted label (for classification, returns the majority class).
230
+ """
231
+ n_train = X_train.shape[0]
232
+ distances = np.zeros(n_train)
233
+
234
+ for i in range(n_train):
235
+ if metric == "euclidean":
236
+ distances[i] = _euclidean_distance(x_test, X_train[i])
237
+ else:
238
+ distances[i] = _manhattan_distance(x_test, X_train[i])
239
+
240
+ # Get indices of k smallest distances
241
+ indices = np.argsort(distances)[:k]
242
+ k_nearest = y_train[indices]
243
+
244
+ # For regression, return mean; for classification, return mode
245
+ if np.issubdtype(y_train.dtype, np.floating) or len(np.unique(y_train)) > 20:
246
+ return np.mean(k_nearest)
247
+ else:
248
+ # Return mode (most common)
249
+ counts = np.bincount(k_nearest.astype(np.int64))
250
+ return np.argmax(counts)
@@ -0,0 +1,8 @@
1
+ from epicon.activations.base import Activation
2
+ from epicon.activations.leaky_relu import LeakyReLU
3
+ from epicon.activations.relu import ReLU
4
+ from epicon.activations.sigmoid import Sigmoid
5
+ from epicon.activations.softmax import Softmax
6
+ from epicon.activations.tanh import Tanh
7
+
8
+ __all__ = ["Activation", "ReLU", "Softmax", "Sigmoid", "LeakyReLU", "Tanh"]
@@ -0,0 +1,21 @@
1
+ from epicon.layers.base import Layer
2
+
3
+
4
+ class Activation(Layer):
5
+ """
6
+ -----------------------------------
7
+ BASE CLASS FOR ACTIVATION FUNCTIONS
8
+ -----------------------------------
9
+ """
10
+
11
+ def __init__(self):
12
+ super().__init__()
13
+
14
+ def forward(self, inputs):
15
+ raise NotImplementedError
16
+
17
+ def backward(self, dvalues):
18
+ raise NotImplementedError
19
+
20
+ def get_params(self):
21
+ return super().get_params()
@@ -0,0 +1,65 @@
1
+ from typing import override
2
+
3
+ import numpy as np
4
+
5
+ from epicon.activations.base import Activation
6
+
7
+
8
+ class LeakyReLU(Activation):
9
+ """
10
+ -----------------------------------------------
11
+ LEAKY RELU ACTIVATION FUNCTION
12
+ f(x) = x if x > 0, otherwise f(x) = α * x
13
+ This prevents neurons from "dying" by allowing a small,
14
+ non-zero gradient when the unit is not active.
15
+ -----------------------------------------------
16
+ """
17
+
18
+ def __init__(self, alpha=0.01):
19
+ """
20
+ Initialize the LeakyReLU with a given alpha.
21
+
22
+ Parameters:
23
+ alpha (float): the small constant multiplier for negative inputs. Default is 0.01.
24
+ """
25
+ self.alpha = alpha
26
+
27
+ def forward(self, inputs):
28
+ """
29
+ The forward pass applies the LeakyReLU function.
30
+
31
+ Parameters:
32
+ inputs (ndarray): input data to the layer
33
+
34
+ Returns:
35
+ output (ndarray): transformed output of the layer
36
+ """
37
+ self.inputs = inputs
38
+ # For each element, if positive keep it; if negative, multiply by alpha
39
+ self.output = np.where(inputs > 0, inputs, self.alpha * inputs)
40
+ return self.output
41
+
42
+ def backward(self, dvalues):
43
+ """
44
+ The backward pass computes the gradient of the loss with respect to the inputs.
45
+
46
+ Parameters:
47
+ dvalues (ndarray): the gradient from the next layer
48
+
49
+ Returns:
50
+ dinputs (ndarray): the gradient with respect to the inputs of this layer
51
+ """
52
+ # Initialize gradient as a copy of dvalues
53
+ self.dinputs = np.copy(dvalues)
54
+ # For inputs <= 0, multiply the gradient by alpha
55
+ self.dinputs[self.inputs <= 0] *= self.alpha
56
+ return self.dinputs
57
+
58
+ @override
59
+ def get_params(self):
60
+ return {"type": "LeakyReLU", "attrs": {"alpha": self.alpha}}
61
+
62
+ @override
63
+ def set_params(self, params: dict):
64
+ for key, val in params.items():
65
+ setattr(self, key, val)
@@ -0,0 +1,37 @@
1
+ from typing import override
2
+
3
+ import numpy as np
4
+
5
+ from epicon.activations.base import Activation
6
+
7
+
8
+ class ReLU(Activation):
9
+ """
10
+ -----------------------------------------------
11
+ RECTIFIED LINEAR UNIT (ReLU) ACTIVATION FUNCTION
12
+ f(x) = max(0, x)
13
+ -----------------------------------------------
14
+ """
15
+
16
+ def forward(self, inputs):
17
+ """
18
+ The `forward` function takes an input array, applies a ReLU activation function element-wise,
19
+ and returns the resulting output array.
20
+ """
21
+ self.inputs = inputs
22
+ self.output = np.maximum(0, inputs)
23
+ return self.output
24
+
25
+ def backward(self, dvalues):
26
+ """
27
+ The `backward` function calculates the derivative of the inputs with respect to the given values
28
+ and sets the derivative to zero for inputs less than or equal to zero.
29
+ """
30
+
31
+ self.dinputs = dvalues.copy()
32
+ self.dinputs[self.inputs <= 0] = 0
33
+ return self.dinputs
34
+
35
+ @override
36
+ def get_params(self):
37
+ return {"type": "ReLU", "attrs": {}}
@@ -0,0 +1,37 @@
1
+ from typing import override
2
+
3
+ import numpy as np
4
+
5
+ from epicon.activations.base import Activation
6
+
7
+
8
+ class Sigmoid(Activation):
9
+ """
10
+ -----------------------------------------------
11
+ SIGMOID ACTIVATION FUNCTION
12
+ f(x) = 1 / (1 + e ^ (-x))
13
+ USED TO MAP ANY INPUT TO A VALUE BETWEEN 0 AND 1
14
+ -----------------------------------------------
15
+ """
16
+
17
+ def forward(self, inputs):
18
+ """
19
+ The forward function takes inputs, applies the sigmoid function, and returns the output.
20
+ """
21
+ self.inputs = inputs
22
+ self.output = 1 / (1 + np.exp(-inputs))
23
+ return self.output
24
+
25
+ def backward(self, dvalues):
26
+ """
27
+ The `backward` function calculates the derivative of the inputs based on the output and the
28
+ given derivative values.
29
+ """
30
+
31
+ # derivative of the sigmoid: f'(x) = f(x) * (1 - f(x))
32
+ self.dinputs = dvalues * (self.output * (1 - self.output))
33
+ return self.dinputs
34
+
35
+ @override
36
+ def get_params(self):
37
+ return {"type": "Sigmoid", "attrs": {}}
@@ -0,0 +1,94 @@
1
+ """
2
+ ---------------------------------------------------------
3
+ SOFTMAX ACTIVATION FUNCTION
4
+ ---------------------------------------------------------
5
+ The Softmax activation function converts a vector of raw scores (logits)
6
+ into a probability distribution. It is commonly used as the activation
7
+ function in the output layer of a classification model.
8
+
9
+ Given a vector of raw scores, the softmax function computes the
10
+ probability for each class using the following formula:
11
+
12
+ f(x_i) = exp(x_i) / sum(exp(x_j) for j in all classes)
13
+
14
+ Where:
15
+ - x_i is the raw score (logit) for class i.
16
+ - f(x_i) is the probability of class i.
17
+
18
+ The function ensures that all the probabilities sum up to 1, making it suitable
19
+ for classification tasks where the model needs to output a probability distribution.
20
+
21
+ Attributes:
22
+ inputs (ndarray): Raw input data (logits).
23
+ output (ndarray): Output probabilities after applying softmax.
24
+ dinputs (ndarray): Gradient of the loss with respect to the inputs.
25
+
26
+ Methods:
27
+ forward(inputs):
28
+ Computes the forward pass of the softmax activation function.
29
+
30
+ backward(dvalues):
31
+ Computes the backward pass, propagating gradients through the softmax function.
32
+ ---------------------------------------------------------
33
+ """
34
+
35
+ from typing import override
36
+
37
+ import numpy as np
38
+
39
+ from epicon.activations.base import Activation
40
+
41
+
42
+ class Softmax(Activation):
43
+ def __init__(self):
44
+ super().__init__()
45
+ self.inputs = None
46
+ self.output = None
47
+ self.dinputs = None
48
+
49
+ def forward(self, inputs):
50
+ """
51
+ Forward pass of Softmax.
52
+
53
+ Args:
54
+ inputs (ndarray): Raw scores (logits).
55
+
56
+ Returns:
57
+ ndarray: Probabilities.
58
+ """
59
+ self.inputs = inputs
60
+
61
+ # Sanity check to see if inputs are valid to work with
62
+ if np.isnan(inputs).any() or np.isinf(inputs).any():
63
+ raise ValueError("NaN values detected in Softmax output.")
64
+
65
+ # Subtract the max value for numerical stability
66
+ # This won't cause any error as Softmax isn't scale dependent
67
+ exponent_values = np.exp(inputs - np.max(inputs, axis=1, keepdims=True))
68
+
69
+ probabilites = exponent_values / np.sum(exponent_values, axis=1, keepdims=True)
70
+
71
+ self.output = probabilites
72
+ return self.output
73
+
74
+ def backward(self, dvalues):
75
+ """
76
+ Backward pass of Softmax.
77
+
78
+ NOTE: Normally used together with cross-entropy loss,
79
+ and we use the combined derivative for efficiency.
80
+
81
+ This method assumes dvalues already contains the proper gradient.
82
+
83
+ Args:
84
+ dvalues (ndarray): Gradient from loss.
85
+
86
+ Returns:
87
+ ndarray: Gradient of the loss with respect to the inputs.
88
+ """
89
+ self.dinputs = dvalues # usually combined with loss
90
+ return self.dinputs
91
+
92
+ @override
93
+ def get_params(self):
94
+ return {"type": "Softmax", "attrs": {}}
@@ -0,0 +1,80 @@
1
+ """
2
+ ---------------------------------------------------------
3
+ TANH ACTIVATION FUNCTION
4
+ ---------------------------------------------------------
5
+ The Tanh activation function maps input values to the range of
6
+ [-1, 1]. It is commonly used in hidden layers of neural networks.
7
+
8
+ Given an input, the tanh function is defined as:
9
+
10
+ f(x) = (exp(x) - exp(-x)) / (exp(x) + exp(-x))
11
+
12
+ Where:
13
+ - x is the input value.
14
+
15
+ The function outputs values between -1 and 1, and it is symmetric
16
+ around the origin, making it suitable for tasks where both positive
17
+ and negative values are important.
18
+
19
+ Attributes:
20
+ inputs (ndarray): Input data passed to the activation function.
21
+ output (ndarray): Output values after applying the tanh function.
22
+ dinputs (ndarray): Gradient of the loss with respect to the inputs.
23
+
24
+ Methods:
25
+ forward(inputs):
26
+ Computes the forward pass for the tanh activation function.
27
+
28
+ backward(dvalues):
29
+ Computes the backward pass, propagating gradients through the tanh function.
30
+ ---------------------------------------------------------
31
+ """
32
+
33
+ from typing import override
34
+
35
+ import numpy as np
36
+
37
+ from epicon.activations.base import Activation
38
+
39
+
40
+ class Tanh(Activation):
41
+ def __init__(self):
42
+ self.inputs = None
43
+ self.output = None
44
+ self.dinputs = None
45
+
46
+ def forward(self, inputs):
47
+ """
48
+ Forward pass of Tanh.
49
+
50
+ Args:
51
+ inputs (ndarray): Input values.
52
+
53
+ Returns:
54
+ ndarray: Output values after applying tanh.
55
+ """
56
+ self.inputs = inputs
57
+
58
+ output = np.tanh(inputs)
59
+ if np.isnan(output).any():
60
+ raise ValueError("NaN values detected in Softmax output.")
61
+
62
+ self.output = output
63
+ return self.output
64
+
65
+ def backward(self, dvalues):
66
+ """
67
+ Backward pass of Tanh.
68
+
69
+ Args:
70
+ dvalues (ndarray): Gradient from the next layer.
71
+
72
+ Returns:
73
+ ndarray: Gradient with respect to the inputs.
74
+ """
75
+ self.dinputs = dvalues * (1 - self.output**2)
76
+ return self.dinputs
77
+
78
+ @override
79
+ def get_params(self):
80
+ return {"type": "Tanh", "attrs": {}}
@@ -0,0 +1,4 @@
1
+ from epicon.datasets.generator import make_classification, make_regression
2
+ from epicon.datasets.loader import load_iris, load_mnist
3
+
4
+ __all__ = ["load_iris", "load_mnist", "make_classification", "make_regression"]