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.
- epicon/__init__.py +78 -0
- epicon/_jit.py +250 -0
- epicon/activations/__init__.py +8 -0
- epicon/activations/base.py +21 -0
- epicon/activations/leaky_relu.py +65 -0
- epicon/activations/relu.py +37 -0
- epicon/activations/sigmoid.py +37 -0
- epicon/activations/softmax.py +94 -0
- epicon/activations/tanh.py +80 -0
- epicon/datasets/__init__.py +4 -0
- epicon/datasets/generator.py +90 -0
- epicon/datasets/loader.py +305 -0
- epicon/ensemble/__init__.py +4 -0
- epicon/ensemble/random_forest_classifier.py +210 -0
- epicon/ensemble/random_forest_regressor.py +170 -0
- epicon/layers/__init__.py +6 -0
- epicon/layers/base.py +30 -0
- epicon/layers/conv1d.py +80 -0
- epicon/layers/dense.py +96 -0
- epicon/layers/dropout.py +64 -0
- epicon/linear_model/__init__.py +6 -0
- epicon/linear_model/lasso.py +180 -0
- epicon/linear_model/linear_regression.py +193 -0
- epicon/linear_model/logistic_regression.py +192 -0
- epicon/linear_model/ridge.py +128 -0
- epicon/losses/__init__.py +6 -0
- epicon/losses/base.py +41 -0
- epicon/losses/binary_cross_entropy.py +100 -0
- epicon/losses/categorical_cross_entropy.py +50 -0
- epicon/losses/mse.py +42 -0
- epicon/metrics/__init__.py +19 -0
- epicon/metrics/classification.py +185 -0
- epicon/metrics/regression.py +69 -0
- epicon/models/__init__.py +4 -0
- epicon/models/model.py +549 -0
- epicon/models/sequential.py +146 -0
- epicon/naive_bayes/__init__.py +3 -0
- epicon/naive_bayes/gaussian_nb.py +168 -0
- epicon/neighbors/__init__.py +4 -0
- epicon/neighbors/knn_classifier.py +171 -0
- epicon/neighbors/knn_regressor.py +125 -0
- epicon/optimizers/__init__.py +6 -0
- epicon/optimizers/adam.py +125 -0
- epicon/optimizers/base.py +45 -0
- epicon/optimizers/gradient_descent.py +43 -0
- epicon/optimizers/momentum.py +74 -0
- epicon/preprocessing/__init__.py +5 -0
- epicon/preprocessing/encoder.py +187 -0
- epicon/preprocessing/scaler.py +214 -0
- epicon/preprocessing/split.py +60 -0
- epicon/tree/__init__.py +4 -0
- epicon/tree/decision_tree_classifier.py +329 -0
- epicon/tree/decision_tree_regressor.py +286 -0
- epicon/utils/__init__.py +4 -0
- epicon/utils/layer_config.py +18 -0
- epicon/utils/model_builder.py +54 -0
- epicon-0.3.0.dist-info/METADATA +113 -0
- epicon-0.3.0.dist-info/RECORD +61 -0
- epicon-0.3.0.dist-info/WHEEL +5 -0
- epicon-0.3.0.dist-info/licenses/LICENSE +21 -0
- 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": {}}
|