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 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.1)
30
- L1 regularization strength for Lasso in symbolic regression.
31
- sigma : float, optional (default=0.1)
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.1, sigma=0.1, epochs=100, lr=0.001, batch_size=32,
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.8
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
- ## Scientific Foundation
54
+ ## Key Aspects
53
55
 
54
- OIKAN implements a modern interpretation of the Kolmogorov-Arnold Representation Theorem through a hybrid neural architecture:
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
- 3. **Basis Functions**: Core set of interpretable transformations:
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
- 4. **Formula Extraction Process**:
87
+ 3. **Formula Extraction Process**:
96
88
  - Train neural network on raw data
97
89
  - Generate augmented samples for better coverage
98
- - Perform L1-regularized symbolic regression (alpha)
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.1, # L1 regularization strength (Symbolic regression)
140
- sigma=0.1, # Standard deviation of Gaussian noise for data augmentation
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.1, # L1 regularization strength (Symbolic regression)
190
- sigma=0.1, # Standard deviation of Gaussian noise for data augmentation
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,,
@@ -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,,