epicon 0.3.0__tar.gz
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-0.3.0/LICENSE +21 -0
- epicon-0.3.0/PKG-INFO +113 -0
- epicon-0.3.0/README.md +80 -0
- epicon-0.3.0/epicon/__init__.py +78 -0
- epicon-0.3.0/epicon/_jit.py +250 -0
- epicon-0.3.0/epicon/activations/__init__.py +8 -0
- epicon-0.3.0/epicon/activations/base.py +21 -0
- epicon-0.3.0/epicon/activations/leaky_relu.py +65 -0
- epicon-0.3.0/epicon/activations/relu.py +37 -0
- epicon-0.3.0/epicon/activations/sigmoid.py +37 -0
- epicon-0.3.0/epicon/activations/softmax.py +94 -0
- epicon-0.3.0/epicon/activations/tanh.py +80 -0
- epicon-0.3.0/epicon/datasets/__init__.py +4 -0
- epicon-0.3.0/epicon/datasets/generator.py +90 -0
- epicon-0.3.0/epicon/datasets/loader.py +305 -0
- epicon-0.3.0/epicon/ensemble/__init__.py +4 -0
- epicon-0.3.0/epicon/ensemble/random_forest_classifier.py +210 -0
- epicon-0.3.0/epicon/ensemble/random_forest_regressor.py +170 -0
- epicon-0.3.0/epicon/layers/__init__.py +6 -0
- epicon-0.3.0/epicon/layers/base.py +30 -0
- epicon-0.3.0/epicon/layers/conv1d.py +80 -0
- epicon-0.3.0/epicon/layers/dense.py +96 -0
- epicon-0.3.0/epicon/layers/dropout.py +64 -0
- epicon-0.3.0/epicon/linear_model/__init__.py +6 -0
- epicon-0.3.0/epicon/linear_model/lasso.py +180 -0
- epicon-0.3.0/epicon/linear_model/linear_regression.py +193 -0
- epicon-0.3.0/epicon/linear_model/logistic_regression.py +192 -0
- epicon-0.3.0/epicon/linear_model/ridge.py +128 -0
- epicon-0.3.0/epicon/losses/__init__.py +6 -0
- epicon-0.3.0/epicon/losses/base.py +41 -0
- epicon-0.3.0/epicon/losses/binary_cross_entropy.py +100 -0
- epicon-0.3.0/epicon/losses/categorical_cross_entropy.py +50 -0
- epicon-0.3.0/epicon/losses/mse.py +42 -0
- epicon-0.3.0/epicon/metrics/__init__.py +19 -0
- epicon-0.3.0/epicon/metrics/classification.py +185 -0
- epicon-0.3.0/epicon/metrics/regression.py +69 -0
- epicon-0.3.0/epicon/models/__init__.py +4 -0
- epicon-0.3.0/epicon/models/model.py +549 -0
- epicon-0.3.0/epicon/models/sequential.py +146 -0
- epicon-0.3.0/epicon/naive_bayes/__init__.py +3 -0
- epicon-0.3.0/epicon/naive_bayes/gaussian_nb.py +168 -0
- epicon-0.3.0/epicon/neighbors/__init__.py +4 -0
- epicon-0.3.0/epicon/neighbors/knn_classifier.py +171 -0
- epicon-0.3.0/epicon/neighbors/knn_regressor.py +125 -0
- epicon-0.3.0/epicon/optimizers/__init__.py +6 -0
- epicon-0.3.0/epicon/optimizers/adam.py +125 -0
- epicon-0.3.0/epicon/optimizers/base.py +45 -0
- epicon-0.3.0/epicon/optimizers/gradient_descent.py +43 -0
- epicon-0.3.0/epicon/optimizers/momentum.py +74 -0
- epicon-0.3.0/epicon/preprocessing/__init__.py +5 -0
- epicon-0.3.0/epicon/preprocessing/encoder.py +187 -0
- epicon-0.3.0/epicon/preprocessing/scaler.py +214 -0
- epicon-0.3.0/epicon/preprocessing/split.py +60 -0
- epicon-0.3.0/epicon/tree/__init__.py +4 -0
- epicon-0.3.0/epicon/tree/decision_tree_classifier.py +329 -0
- epicon-0.3.0/epicon/tree/decision_tree_regressor.py +286 -0
- epicon-0.3.0/epicon/utils/__init__.py +4 -0
- epicon-0.3.0/epicon/utils/layer_config.py +18 -0
- epicon-0.3.0/epicon/utils/model_builder.py +54 -0
- epicon-0.3.0/epicon.egg-info/PKG-INFO +113 -0
- epicon-0.3.0/epicon.egg-info/SOURCES.txt +65 -0
- epicon-0.3.0/epicon.egg-info/dependency_links.txt +1 -0
- epicon-0.3.0/epicon.egg-info/requires.txt +7 -0
- epicon-0.3.0/epicon.egg-info/top_level.txt +1 -0
- epicon-0.3.0/pyproject.toml +81 -0
- epicon-0.3.0/setup.cfg +4 -0
- epicon-0.3.0/tests/test_model_builder.py +40 -0
epicon-0.3.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Epicon Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
epicon-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: epicon
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Lightweight from-scratch ML library — neural nets, tree-based models, linear models, and more
|
|
5
|
+
Author-email: Kebtes <kebtes@pm.me>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/kebtes/epicon
|
|
8
|
+
Project-URL: Repository, https://github.com/kebtes/epicon
|
|
9
|
+
Project-URL: Documentation, https://github.com/kebtes/epicon#readme
|
|
10
|
+
Project-URL: Changelog, https://github.com/kebtes/epicon/blob/main/CHANGELOG.md
|
|
11
|
+
Project-URL: Bug Tracker, https://github.com/kebtes/epicon/issues
|
|
12
|
+
Keywords: machine-learning,neural-networks,decision-trees,numpy,ml-library
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: Natural Language :: English
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: numpy>=1.21
|
|
28
|
+
Provides-Extra: numba
|
|
29
|
+
Requires-Dist: numba>=0.56; extra == "numba"
|
|
30
|
+
Provides-Extra: all
|
|
31
|
+
Requires-Dist: epicon[numba]; extra == "all"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# Epicon
|
|
35
|
+
|
|
36
|
+
A **lightweight, from-scratch** machine learning library built on NumPy with
|
|
37
|
+
optional Numba acceleration. Provides a unified API for neural networks
|
|
38
|
+
**and** traditional ML models.
|
|
39
|
+
|
|
40
|
+
Designed to be minimal yet capable — like **Flask for ML**.
|
|
41
|
+
|
|
42
|
+
## Quick Start
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
import epicon
|
|
46
|
+
from epicon.datasets import load_iris
|
|
47
|
+
from epicon.preprocessing import train_test_split
|
|
48
|
+
from epicon.metrics import accuracy_score
|
|
49
|
+
|
|
50
|
+
# Load data
|
|
51
|
+
X, y = load_iris(return_X_y=True)
|
|
52
|
+
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
|
|
53
|
+
|
|
54
|
+
# Train a model
|
|
55
|
+
model = epicon.DecisionTreeClassifier(max_depth=5)
|
|
56
|
+
model.fit(X_train, y_train)
|
|
57
|
+
|
|
58
|
+
# Evaluate
|
|
59
|
+
y_pred = model.predict(X_test)
|
|
60
|
+
print(f"Accuracy: {accuracy_score(y_test, y_pred):.4f}")
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## What's Included
|
|
64
|
+
|
|
65
|
+
### Neural Networks
|
|
66
|
+
- Layers: `Dense`, `Dropout`, `Conv1D`
|
|
67
|
+
- Activations: `ReLU`, `LeakyReLU`, `Sigmoid`, `Softmax`, `Tanh`
|
|
68
|
+
- Losses: `MSE`, `BinaryCrossEntropy`, `CategoricalCrossEntropy`
|
|
69
|
+
- Optimizers: `GradientDescent`, `Momentum`, `Adam`
|
|
70
|
+
- `Model` — layer-by-layer construction
|
|
71
|
+
- `Sequential` — Keras-style wrapper with string activations
|
|
72
|
+
|
|
73
|
+
### Traditional ML Models
|
|
74
|
+
- `LinearRegression`, `Ridge`, `Lasso`, `LogisticRegression`
|
|
75
|
+
- `KNeighborsClassifier`, `KNeighborsRegressor`
|
|
76
|
+
- `GaussianNB`
|
|
77
|
+
- `DecisionTreeClassifier`, `DecisionTreeRegressor`
|
|
78
|
+
- `RandomForestClassifier`, `RandomForestRegressor`
|
|
79
|
+
|
|
80
|
+
### Utilities
|
|
81
|
+
- Preprocessing: `StandardScaler`, `MinMaxScaler`, `LabelEncoder`, `OneHotEncoder`, `train_test_split`
|
|
82
|
+
- Datasets: `load_iris`, `load_mnist`, `make_classification`, `make_regression`
|
|
83
|
+
- Metrics: `accuracy_score`, `precision_score`, `recall_score`, `f1_score`, `confusion_matrix`, `mean_squared_error`, `mean_absolute_error`, `r2_score`
|
|
84
|
+
|
|
85
|
+
## Installation
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# Minimal install (NumPy required)
|
|
89
|
+
pip install numpy
|
|
90
|
+
|
|
91
|
+
# Install Epicon from source
|
|
92
|
+
pip install -e .
|
|
93
|
+
|
|
94
|
+
# With Numba (optional, for faster tree/KNN)
|
|
95
|
+
pip install numba
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Design
|
|
99
|
+
|
|
100
|
+
- **Consistent API**: all models follow `fit(X, y)` / `predict(X)`.
|
|
101
|
+
- **Minimal dependencies**: only NumPy is required.
|
|
102
|
+
- **Optional acceleration**: Numba JIT for tree split search and KNN.
|
|
103
|
+
- **Educational**: readable, fully documented source code.
|
|
104
|
+
- **Tested**: 169+ unit tests with pytest.
|
|
105
|
+
|
|
106
|
+
## Examples
|
|
107
|
+
|
|
108
|
+
See the [examples/](examples/) directory:
|
|
109
|
+
|
|
110
|
+
- `example_ml_iris.py` — Decision tree on Iris dataset
|
|
111
|
+
- `example_ml_binary.py` — LogisticRegression with L2 penalty
|
|
112
|
+
- `example_ml_forest.py` — RandomForestRegressor on synthetic data
|
|
113
|
+
- `example_nn_sequential.py` — Sequential neural net with Adam on MNIST
|
epicon-0.3.0/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Epicon
|
|
2
|
+
|
|
3
|
+
A **lightweight, from-scratch** machine learning library built on NumPy with
|
|
4
|
+
optional Numba acceleration. Provides a unified API for neural networks
|
|
5
|
+
**and** traditional ML models.
|
|
6
|
+
|
|
7
|
+
Designed to be minimal yet capable — like **Flask for ML**.
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
import epicon
|
|
13
|
+
from epicon.datasets import load_iris
|
|
14
|
+
from epicon.preprocessing import train_test_split
|
|
15
|
+
from epicon.metrics import accuracy_score
|
|
16
|
+
|
|
17
|
+
# Load data
|
|
18
|
+
X, y = load_iris(return_X_y=True)
|
|
19
|
+
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
|
|
20
|
+
|
|
21
|
+
# Train a model
|
|
22
|
+
model = epicon.DecisionTreeClassifier(max_depth=5)
|
|
23
|
+
model.fit(X_train, y_train)
|
|
24
|
+
|
|
25
|
+
# Evaluate
|
|
26
|
+
y_pred = model.predict(X_test)
|
|
27
|
+
print(f"Accuracy: {accuracy_score(y_test, y_pred):.4f}")
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## What's Included
|
|
31
|
+
|
|
32
|
+
### Neural Networks
|
|
33
|
+
- Layers: `Dense`, `Dropout`, `Conv1D`
|
|
34
|
+
- Activations: `ReLU`, `LeakyReLU`, `Sigmoid`, `Softmax`, `Tanh`
|
|
35
|
+
- Losses: `MSE`, `BinaryCrossEntropy`, `CategoricalCrossEntropy`
|
|
36
|
+
- Optimizers: `GradientDescent`, `Momentum`, `Adam`
|
|
37
|
+
- `Model` — layer-by-layer construction
|
|
38
|
+
- `Sequential` — Keras-style wrapper with string activations
|
|
39
|
+
|
|
40
|
+
### Traditional ML Models
|
|
41
|
+
- `LinearRegression`, `Ridge`, `Lasso`, `LogisticRegression`
|
|
42
|
+
- `KNeighborsClassifier`, `KNeighborsRegressor`
|
|
43
|
+
- `GaussianNB`
|
|
44
|
+
- `DecisionTreeClassifier`, `DecisionTreeRegressor`
|
|
45
|
+
- `RandomForestClassifier`, `RandomForestRegressor`
|
|
46
|
+
|
|
47
|
+
### Utilities
|
|
48
|
+
- Preprocessing: `StandardScaler`, `MinMaxScaler`, `LabelEncoder`, `OneHotEncoder`, `train_test_split`
|
|
49
|
+
- Datasets: `load_iris`, `load_mnist`, `make_classification`, `make_regression`
|
|
50
|
+
- Metrics: `accuracy_score`, `precision_score`, `recall_score`, `f1_score`, `confusion_matrix`, `mean_squared_error`, `mean_absolute_error`, `r2_score`
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Minimal install (NumPy required)
|
|
56
|
+
pip install numpy
|
|
57
|
+
|
|
58
|
+
# Install Epicon from source
|
|
59
|
+
pip install -e .
|
|
60
|
+
|
|
61
|
+
# With Numba (optional, for faster tree/KNN)
|
|
62
|
+
pip install numba
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Design
|
|
66
|
+
|
|
67
|
+
- **Consistent API**: all models follow `fit(X, y)` / `predict(X)`.
|
|
68
|
+
- **Minimal dependencies**: only NumPy is required.
|
|
69
|
+
- **Optional acceleration**: Numba JIT for tree split search and KNN.
|
|
70
|
+
- **Educational**: readable, fully documented source code.
|
|
71
|
+
- **Tested**: 169+ unit tests with pytest.
|
|
72
|
+
|
|
73
|
+
## Examples
|
|
74
|
+
|
|
75
|
+
See the [examples/](examples/) directory:
|
|
76
|
+
|
|
77
|
+
- `example_ml_iris.py` — Decision tree on Iris dataset
|
|
78
|
+
- `example_ml_binary.py` — LogisticRegression with L2 penalty
|
|
79
|
+
- `example_ml_forest.py` — RandomForestRegressor on synthetic data
|
|
80
|
+
- `example_nn_sequential.py` — Sequential neural net with Adam on MNIST
|
|
@@ -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"
|
|
@@ -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": {}}
|