oikan 0.0.3.8__py3-none-any.whl → 0.0.3.9__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.
- oikan/__init__.py +3 -1
- oikan/elasticnet.py +71 -0
- oikan/model.py +13 -7
- {oikan-0.0.3.8.dist-info → oikan-0.0.3.9.dist-info}/METADATA +20 -20
- oikan-0.0.3.9.dist-info/RECORD +11 -0
- oikan-0.0.3.8.dist-info/RECORD +0 -10
- {oikan-0.0.3.8.dist-info → oikan-0.0.3.9.dist-info}/WHEEL +0 -0
- {oikan-0.0.3.8.dist-info → oikan-0.0.3.9.dist-info}/licenses/LICENSE +0 -0
- {oikan-0.0.3.8.dist-info → oikan-0.0.3.9.dist-info}/top_level.txt +0 -0
oikan/__init__.py
CHANGED
@@ -9,6 +9,8 @@ Docs: https://silvermete0r.github.io/oikan/
|
|
9
9
|
'''
|
10
10
|
|
11
11
|
from .model import OIKAN, OIKANClassifier, OIKANRegressor
|
12
|
+
from .neural import TabularNet
|
13
|
+
from .elasticnet import ElasticNet
|
12
14
|
|
13
|
-
__all__ = ['OIKAN', 'OIKANClassifier', 'OIKANRegressor']
|
15
|
+
__all__ = ['OIKAN', 'OIKANClassifier', 'OIKANRegressor', 'TabularNet', 'ElasticNet']
|
14
16
|
__version__ = '0.0.3'
|
oikan/elasticnet.py
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
import torch.nn as nn
|
2
|
+
import torch
|
3
|
+
import numpy as np
|
4
|
+
|
5
|
+
class ElasticNet(nn.Module):
|
6
|
+
def __init__(self, alpha=1.0, l1_ratio=0.5, fit_intercept=False, max_iter=1000, tol=1e-4, random_state=None):
|
7
|
+
super().__init__()
|
8
|
+
self.alpha = alpha
|
9
|
+
self.l1_ratio = l1_ratio
|
10
|
+
self.fit_intercept = fit_intercept
|
11
|
+
self.max_iter = max_iter
|
12
|
+
self.tol = tol
|
13
|
+
self.random_state = random_state
|
14
|
+
self.coef_ = None
|
15
|
+
self.intercept_ = None
|
16
|
+
|
17
|
+
def fit(self, X, y):
|
18
|
+
X = np.asarray(X, dtype=np.float32)
|
19
|
+
y = np.asarray(y, dtype=np.float32)
|
20
|
+
n_samples, n_features = X.shape
|
21
|
+
if y.ndim == 1:
|
22
|
+
y = y.reshape(-1, 1)
|
23
|
+
n_targets = y.shape[1]
|
24
|
+
|
25
|
+
if self.random_state is not None:
|
26
|
+
torch.manual_seed(self.random_state)
|
27
|
+
np.random.seed(self.random_state)
|
28
|
+
|
29
|
+
X_tensor = torch.tensor(X, dtype=torch.float32)
|
30
|
+
y_tensor = torch.tensor(y, dtype=torch.float32)
|
31
|
+
|
32
|
+
W = torch.zeros((n_features, n_targets), requires_grad=True, dtype=torch.float32)
|
33
|
+
if self.fit_intercept:
|
34
|
+
b = torch.zeros(n_targets, requires_grad=True, dtype=torch.float32)
|
35
|
+
else:
|
36
|
+
b = None
|
37
|
+
|
38
|
+
optimizer = torch.optim.Adam([W] + ([b] if b is not None else []), lr=0.05)
|
39
|
+
|
40
|
+
prev_loss = None
|
41
|
+
for _ in range(self.max_iter):
|
42
|
+
optimizer.zero_grad()
|
43
|
+
pred = X_tensor @ W
|
44
|
+
if b is not None:
|
45
|
+
pred = pred + b
|
46
|
+
mse = torch.mean((pred - y_tensor) ** 2)
|
47
|
+
l1 = torch.sum(torch.abs(W))
|
48
|
+
l2 = torch.sum(W ** 2)
|
49
|
+
loss = mse + self.alpha * (self.l1_ratio * l1 + (1 - self.l1_ratio) * l2)
|
50
|
+
loss.backward()
|
51
|
+
optimizer.step()
|
52
|
+
if prev_loss is not None and abs(prev_loss - loss.item()) < self.tol:
|
53
|
+
break
|
54
|
+
prev_loss = loss.item()
|
55
|
+
|
56
|
+
self.coef_ = W.detach().cpu().numpy().T if n_targets > 1 else W.detach().cpu().numpy().flatten()
|
57
|
+
if b is not None:
|
58
|
+
self.intercept_ = b.detach().cpu().numpy()
|
59
|
+
else:
|
60
|
+
self.intercept_ = np.zeros(n_targets) if n_targets > 1 else 0.0
|
61
|
+
return self
|
62
|
+
|
63
|
+
def predict(self, X):
|
64
|
+
X = np.asarray(X, dtype=np.float32)
|
65
|
+
if self.coef_ is None:
|
66
|
+
raise RuntimeError("Model not fitted yet.")
|
67
|
+
W = self.coef_.T if self.coef_.ndim == 2 else self.coef_
|
68
|
+
y_pred = X @ W
|
69
|
+
if self.intercept_ is not None:
|
70
|
+
y_pred += self.intercept_
|
71
|
+
return y_pred
|
oikan/model.py
CHANGED
@@ -3,9 +3,9 @@ import torch
|
|
3
3
|
import torch.nn as nn
|
4
4
|
import torch.optim as optim
|
5
5
|
from sklearn.preprocessing import PolynomialFeatures
|
6
|
-
from sklearn.linear_model import ElasticNet
|
7
6
|
from abc import ABC, abstractmethod
|
8
7
|
import json
|
8
|
+
from .elasticnet import ElasticNet
|
9
9
|
from .neural import TabularNet
|
10
10
|
from .utils import evaluate_basis_functions, get_features_involved, sympify_formula, get_latex_formula
|
11
11
|
from sklearn.model_selection import train_test_split
|
@@ -26,9 +26,12 @@ class OIKAN(ABC):
|
|
26
26
|
Activation function for the neural network ('relu', 'tanh', 'leaky_relu', 'elu', 'swish', 'gelu').
|
27
27
|
augmentation_factor : int, optional (default=10)
|
28
28
|
Number of augmented samples per original sample.
|
29
|
-
alpha : float, optional (default=0
|
30
|
-
|
31
|
-
|
29
|
+
alpha : float, optional (default=1.0)
|
30
|
+
ElasticNet regularization strength.
|
31
|
+
l1_ratio: float, optional (default=0.5)
|
32
|
+
ElasticNet mixing parameter (0 <= l1_ratio <= 1).
|
33
|
+
0 is equivalent to Ridge regression, 1 is equivalent to Lasso.
|
34
|
+
sigma : float, optional (default=5.0)
|
32
35
|
Standard deviation of Gaussian noise for data augmentation.
|
33
36
|
top_k : int, optional (default=5)
|
34
37
|
Number of top features to select in hierarchical symbolic regression.
|
@@ -46,7 +49,7 @@ class OIKAN(ABC):
|
|
46
49
|
Random seed for reproducibility.
|
47
50
|
"""
|
48
51
|
def __init__(self, hidden_sizes=[64, 64], activation='relu', augmentation_factor=10,
|
49
|
-
alpha=0.
|
52
|
+
alpha=1.0, l1_ratio=0.5, sigma=5.0, epochs=100, lr=0.001, batch_size=32,
|
50
53
|
verbose=False, evaluate_nn=False, top_k=5, random_state=None):
|
51
54
|
if not isinstance(hidden_sizes, list) or not all(isinstance(x, int) and x > 0 for x in hidden_sizes):
|
52
55
|
raise InvalidParameterError("hidden_sizes must be a list of positive integers")
|
@@ -64,6 +67,8 @@ class OIKAN(ABC):
|
|
64
67
|
raise InvalidParameterError("epochs must be a positive integer")
|
65
68
|
if not 0 <= alpha <= 1:
|
66
69
|
raise InvalidParameterError("alpha must be between 0 and 1")
|
70
|
+
if not 0 <= l1_ratio <= 1:
|
71
|
+
raise InvalidParameterError("l1_ratio must be between 0 and 1")
|
67
72
|
if sigma <= 0:
|
68
73
|
raise InvalidParameterError("sigma must be positive")
|
69
74
|
|
@@ -71,6 +76,7 @@ class OIKAN(ABC):
|
|
71
76
|
self.activation = activation
|
72
77
|
self.augmentation_factor = augmentation_factor
|
73
78
|
self.alpha = alpha
|
79
|
+
self.l1_ratio = l1_ratio
|
74
80
|
self.sigma = sigma
|
75
81
|
self.epochs = epochs
|
76
82
|
self.lr = lr
|
@@ -366,7 +372,7 @@ class OIKAN(ABC):
|
|
366
372
|
|
367
373
|
if self.verbose:
|
368
374
|
print("Fitting coarse elastic net model...")
|
369
|
-
model_coarse = ElasticNet(alpha=self.alpha, fit_intercept=False)
|
375
|
+
model_coarse = ElasticNet(alpha=self.alpha, l1_ratio=self.l1_ratio, fit_intercept=False, random_state=self.random_state)
|
370
376
|
model_coarse.fit(X_poly_coarse, y)
|
371
377
|
|
372
378
|
if self.verbose:
|
@@ -413,7 +419,7 @@ class OIKAN(ABC):
|
|
413
419
|
X_refined = np.hstack([X_poly_coarse, X_additional])
|
414
420
|
basis_functions_refined = list(basis_functions_coarse) + additional_names
|
415
421
|
|
416
|
-
model_refined = ElasticNet(alpha=self.alpha, fit_intercept=False)
|
422
|
+
model_refined = ElasticNet(alpha=self.alpha, l1_ratio=self.l1_ratio, fit_intercept=False, random_state=self.random_state)
|
417
423
|
model_refined.fit(X_refined, y)
|
418
424
|
|
419
425
|
if self.verbose:
|
@@ -1,9 +1,11 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: oikan
|
3
|
-
Version: 0.0.3.
|
3
|
+
Version: 0.0.3.9
|
4
4
|
Summary: OIKAN: Neuro-Symbolic ML for Scientific Discovery
|
5
5
|
Author: Arman Zhalgasbayev
|
6
6
|
License: MIT
|
7
|
+
Project-URL: Homepage, https://github.com/silvermete0r/oikan
|
8
|
+
Project-URL: Bug Tracker, https://github.com/silvermete0r/oikan/issues
|
7
9
|
Classifier: Programming Language :: Python :: 3
|
8
10
|
Classifier: License :: OSI Approved :: MIT License
|
9
11
|
Classifier: Operating System :: OS Independent
|
@@ -49,19 +51,9 @@ OIKAN is a neuro-symbolic machine learning framework inspired by Kolmogorov-Arno
|
|
49
51
|
- 🔬 **Research-Focused**: Designed for academic exploration and experimentation
|
50
52
|
- 📈 **Multi-Task**: Supports both regression and classification problems
|
51
53
|
|
52
|
-
##
|
54
|
+
## Key Aspects
|
53
55
|
|
54
|
-
|
55
|
-
|
56
|
-
1. **Theoretical Foundation**: The Kolmogorov-Arnold theorem states that any continuous n-dimensional function can be decomposed into a combination of single-variable functions:
|
57
|
-
|
58
|
-
```
|
59
|
-
f(x₁,...,xₙ) = ∑(j=0 to 2n){ φⱼ( ∑(i=1 to n) ψᵢⱼ(xᵢ) ) }
|
60
|
-
```
|
61
|
-
|
62
|
-
where φⱼ and ψᵢⱼ are continuous univariate functions.
|
63
|
-
|
64
|
-
2. **Neural Implementation**: OIKAN uses a specialized architecture combining:
|
56
|
+
1. **Neural Implementation**: OIKAN uses a specialized architecture combining:
|
65
57
|
- Feature transformation layers with interpretable basis functions
|
66
58
|
- Symbolic regression for formula extraction (ElasticNet-based)
|
67
59
|
- Automatic pruning of insignificant terms
|
@@ -78,7 +70,7 @@ OIKAN implements a modern interpretation of the Kolmogorov-Arnold Representation
|
|
78
70
|
self.symbolic_regression = SymbolicRegression(alpha=alpha)
|
79
71
|
```
|
80
72
|
|
81
|
-
|
73
|
+
2. **Basis Functions**: Core set of interpretable transformations:
|
82
74
|
```python
|
83
75
|
SYMBOLIC_FUNCTIONS = {
|
84
76
|
'linear': 'x', # Direct relationships
|
@@ -92,10 +84,10 @@ OIKAN implements a modern interpretation of the Kolmogorov-Arnold Representation
|
|
92
84
|
}
|
93
85
|
```
|
94
86
|
|
95
|
-
|
87
|
+
3. **Formula Extraction Process**:
|
96
88
|
- Train neural network on raw data
|
97
89
|
- Generate augmented samples for better coverage
|
98
|
-
- Perform
|
90
|
+
- Perform ElasticNet-regularization
|
99
91
|
- Prune terms with coefficients below threshold
|
100
92
|
- Export human-readable mathematical expressions
|
101
93
|
|
@@ -127,6 +119,9 @@ pip install -e . # Install in development mode
|
|
127
119
|
| Dependencies | torch, numpy, scikit-learn, sympy, tqdm |
|
128
120
|
|
129
121
|
### Regression Example
|
122
|
+
|
123
|
+
> **Suggestion:** Please ensure that the data is normalized using standard scaling (or another suitable normalization method), as Elastic Net assumes that the model intercept has already been accounted for.
|
124
|
+
|
130
125
|
```python
|
131
126
|
from oikan import OIKANRegressor
|
132
127
|
from sklearn.metrics import mean_squared_error
|
@@ -136,8 +131,9 @@ model = OIKANRegressor(
|
|
136
131
|
hidden_sizes=[32, 32], # Hidden layer sizes
|
137
132
|
activation='relu', # Activation function (other options: 'tanh', 'leaky_relu', 'elu', 'swish', 'gelu')
|
138
133
|
augmentation_factor=5, # Augmentation factor for data generation
|
139
|
-
alpha=0
|
140
|
-
|
134
|
+
alpha=1.0, # ElasticNet regularization strength (Symbolic regression)
|
135
|
+
l1_rate=0.5, # ElasticNet mixing parameter (0 <= l1_ratio <= 1). 0 is equivalent to Ridge regression, 1 is equivalent to Lasso (Symbolic regression)
|
136
|
+
sigma=5, # Standard deviation of Gaussian noise for data augmentation
|
141
137
|
top_k=5, # Number of top features to select (Symbolic regression)
|
142
138
|
epochs=100, # Number of training epochs
|
143
139
|
lr=0.001, # Learning rate
|
@@ -177,6 +173,9 @@ loaded_model.load("outputs/model.json")
|
|
177
173
|
|
178
174
|
|
179
175
|
### Classification Example
|
176
|
+
|
177
|
+
> **Suggestion:** Please ensure that the data is normalized using standard scaling (or another suitable normalization method), as Elastic Net assumes that the model intercept has already been accounted for.
|
178
|
+
|
180
179
|
```python
|
181
180
|
from oikan import OIKANClassifier
|
182
181
|
from sklearn.metrics import accuracy_score
|
@@ -186,8 +185,9 @@ model = OIKANClassifier(
|
|
186
185
|
hidden_sizes=[32, 32], # Hidden layer sizes
|
187
186
|
activation='relu', # Activation function (other options: 'tanh', 'leaky_relu', 'elu', 'swish', 'gelu')
|
188
187
|
augmentation_factor=10, # Augmentation factor for data generation
|
189
|
-
alpha=0
|
190
|
-
|
188
|
+
alpha=1.0, # ElasticNet regularization strength (Symbolic regression)
|
189
|
+
l1_rate=0.5, # ElasticNet mixing parameter (0 <= l1_ratio <= 1). 0 is equivalent to Ridge regression, 1 is equivalent to Lasso (Symbolic regression)
|
190
|
+
sigma=5, # Standard deviation of Gaussian noise for data augmentation
|
191
191
|
top_k=5, # Number of top features to select (Symbolic regression)
|
192
192
|
epochs=100, # # Number of training epochs
|
193
193
|
lr=0.001, # Learning rate
|
@@ -0,0 +1,11 @@
|
|
1
|
+
oikan/__init__.py,sha256=Dh1Rf9ONRdm75B6tFiv9Y9P6NNiHAiKPCGDMuag6TTE,724
|
2
|
+
oikan/elasticnet.py,sha256=yByuG9KCFQ4PpT2ze6oTSDy0DxvdF5MAJoegUGEipSA,2614
|
3
|
+
oikan/exceptions.py,sha256=GhHWqy2Q5LVBcteTy4ngnqxr7FOoLNyD8dNt1kfRXyw,901
|
4
|
+
oikan/model.py,sha256=UAjRYwb-kEap4AJkJ3OVmpNWpun0qgcad5m6x-mUbN8,26237
|
5
|
+
oikan/neural.py,sha256=PZjaffSuABuCNxu-7PinU1GR6ji0Y6xRgSQ3n5HRDxI,1572
|
6
|
+
oikan/utils.py,sha256=7UCm9obO-8Q2zhetdAkukMDOZvGSBWUL_dSF04XqM7k,8808
|
7
|
+
oikan-0.0.3.9.dist-info/licenses/LICENSE,sha256=75ASVmU-XIpN-M4LbVmJ_ibgbzbvRLVti8FhnR0BTf8,1096
|
8
|
+
oikan-0.0.3.9.dist-info/METADATA,sha256=KCUIcXDdneq4MTgnIKmOHf0OPjWUzmQSm21ayOIYQZs,13124
|
9
|
+
oikan-0.0.3.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
10
|
+
oikan-0.0.3.9.dist-info/top_level.txt,sha256=XwnwKwTJddZwIvtrUsAz-l-58BJRj6HjAGWrfYi_3QY,6
|
11
|
+
oikan-0.0.3.9.dist-info/RECORD,,
|
oikan-0.0.3.8.dist-info/RECORD
DELETED
@@ -1,10 +0,0 @@
|
|
1
|
-
oikan/__init__.py,sha256=zEzhm1GYLT4vNaIQ4CgZcNpUk3uo8SWnoaHYtHW_XSQ,628
|
2
|
-
oikan/exceptions.py,sha256=GhHWqy2Q5LVBcteTy4ngnqxr7FOoLNyD8dNt1kfRXyw,901
|
3
|
-
oikan/model.py,sha256=K-cBAUvfw3B8wxWjNSUC1CadU1iVUb8erapUpD9KzKw,25822
|
4
|
-
oikan/neural.py,sha256=PZjaffSuABuCNxu-7PinU1GR6ji0Y6xRgSQ3n5HRDxI,1572
|
5
|
-
oikan/utils.py,sha256=7UCm9obO-8Q2zhetdAkukMDOZvGSBWUL_dSF04XqM7k,8808
|
6
|
-
oikan-0.0.3.8.dist-info/licenses/LICENSE,sha256=75ASVmU-XIpN-M4LbVmJ_ibgbzbvRLVti8FhnR0BTf8,1096
|
7
|
-
oikan-0.0.3.8.dist-info/METADATA,sha256=jmDvzPj-d_JH4yAZiBf45-mjItUVRepX1Xv2cMqqAkA,12749
|
8
|
-
oikan-0.0.3.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
9
|
-
oikan-0.0.3.8.dist-info/top_level.txt,sha256=XwnwKwTJddZwIvtrUsAz-l-58BJRj6HjAGWrfYi_3QY,6
|
10
|
-
oikan-0.0.3.8.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|