oikan 0.0.1.11__py3-none-any.whl → 0.0.2.2__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/exceptions.py +15 -0
- oikan/model.py +422 -83
- oikan/symbolic.py +24 -125
- oikan/utils.py +39 -36
- oikan-0.0.2.2.dist-info/METADATA +223 -0
- oikan-0.0.2.2.dist-info/RECORD +10 -0
- {oikan-0.0.1.11.dist-info → oikan-0.0.2.2.dist-info}/WHEEL +1 -1
- oikan/metrics.py +0 -48
- oikan/regularization.py +0 -30
- oikan/trainer.py +0 -49
- oikan/visualize.py +0 -69
- oikan-0.0.1.11.dist-info/METADATA +0 -105
- oikan-0.0.1.11.dist-info/RECORD +0 -13
- {oikan-0.0.1.11.dist-info → oikan-0.0.2.2.dist-info/licenses}/LICENSE +0 -0
- {oikan-0.0.1.11.dist-info → oikan-0.0.2.2.dist-info}/top_level.txt +0 -0
oikan/symbolic.py
CHANGED
@@ -1,129 +1,28 @@
|
|
1
|
-
import
|
2
|
-
import numpy as np
|
3
|
-
import networkx as nx
|
4
|
-
import matplotlib.pyplot as plt
|
1
|
+
from .utils import ADVANCED_LIB
|
5
2
|
|
6
|
-
|
7
|
-
'x': lambda x: x,
|
8
|
-
'x^2': lambda x: x**2,
|
9
|
-
'x^3': lambda x: x**3,
|
10
|
-
'x^4': lambda x: x**4,
|
11
|
-
'x^5': lambda x: x**5,
|
12
|
-
'exp': lambda x: np.exp(x),
|
13
|
-
'log': lambda x: np.log(np.abs(x) + 1e-8),
|
14
|
-
'sqrt': lambda x: np.sqrt(np.abs(x)),
|
15
|
-
'tanh': lambda x: np.tanh(x),
|
16
|
-
'sin': lambda x: np.sin(x),
|
17
|
-
'abs': lambda x: np.abs(x)
|
18
|
-
}
|
19
|
-
|
20
|
-
# Helper functions
|
21
|
-
def get_model_predictions(model, X, mode):
|
22
|
-
"""Obtain model predictions; returns flattened predictions for regression, raw outputs for classification."""
|
23
|
-
X_tensor = torch.FloatTensor(X)
|
24
|
-
with torch.no_grad():
|
25
|
-
preds = model(X_tensor)
|
26
|
-
if mode == 'regression':
|
27
|
-
return preds.detach().cpu().numpy().flatten(), None
|
28
|
-
elif mode == 'classification':
|
29
|
-
out = preds.detach().cpu().numpy()
|
30
|
-
# In classification, compute a target difference or fallback to flattening
|
31
|
-
target = (out[:, 0] - out[:, 1]).flatten() if (out.ndim > 1 and out.shape[1] > 1) else out.flatten()
|
32
|
-
return target, out
|
33
|
-
else:
|
34
|
-
raise ValueError("Unknown mode")
|
35
|
-
|
36
|
-
def build_design_matrix(X, return_names=False):
|
37
|
-
"""Construct the design matrix from advanced nonlinear bases with optional feature names."""
|
38
|
-
X_np = np.array(X)
|
39
|
-
n_samples, d = X_np.shape
|
40
|
-
F_parts = [np.ones((n_samples, 1))] # Bias term
|
41
|
-
names = ['1'] if return_names else None
|
42
|
-
for j in range(d):
|
43
|
-
xj = X_np[:, j:j+1]
|
44
|
-
for key, func in ADVANCED_LIB.items():
|
45
|
-
F_parts.append(func(xj))
|
46
|
-
if return_names:
|
47
|
-
names.append(f"{key}(x{j+1})")
|
48
|
-
return (np.hstack(F_parts), names) if return_names else np.hstack(F_parts)
|
49
|
-
|
50
|
-
# Main functions using helpers
|
51
|
-
def extract_symbolic_formula(model, X, mode='regression'):
|
3
|
+
def symbolic_edge_repr(weights, bias=None, threshold=1e-4):
|
52
4
|
"""
|
53
|
-
|
5
|
+
Given a list of weights (floats) and an optional bias,
|
6
|
+
returns a list of structured terms (coefficient, basis function string).
|
54
7
|
"""
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
y_target, out = get_model_predictions(model, X, mode)
|
65
|
-
F = build_design_matrix(X, return_names=False)
|
66
|
-
beta, _, _, _ = np.linalg.lstsq(F, y_target, rcond=None)
|
67
|
-
symbolic_vals = F.dot(beta)
|
68
|
-
if mode == 'regression':
|
69
|
-
mse = np.mean((symbolic_vals - y_target) ** 2)
|
70
|
-
mae = np.mean(np.abs(symbolic_vals - y_target))
|
71
|
-
rmse = np.sqrt(mse)
|
72
|
-
print(f"(Advanced) MSE: {mse:.4f}, MAE: {mae:.4f}, RMSE: {rmse:.4f}")
|
73
|
-
return mse, mae, rmse
|
74
|
-
elif mode == 'classification':
|
75
|
-
sym_preds = np.where(symbolic_vals >= 0, 0, 1)
|
76
|
-
model_classes = np.argmax(out, axis=1) if (out.ndim > 1) else (out >= 0.5).astype(int)
|
77
|
-
if model_classes.shape[0] != sym_preds.shape[0]:
|
78
|
-
raise ValueError("Shape mismatch between symbolic and model predictions.")
|
79
|
-
accuracy = np.mean(sym_preds == model_classes)
|
80
|
-
print(f"(Advanced) Accuracy: {accuracy:.4f}")
|
81
|
-
return accuracy
|
8
|
+
terms = []
|
9
|
+
# weights should be in the same order as ADVANCED_LIB.items()
|
10
|
+
for (_, (notation, _)), w in zip(ADVANCED_LIB.items(), weights):
|
11
|
+
if abs(w) > threshold:
|
12
|
+
terms.append((w, notation))
|
13
|
+
if bias is not None and abs(bias) > threshold:
|
14
|
+
# use "1" to represent the constant term
|
15
|
+
terms.append((bias, "1"))
|
16
|
+
return terms
|
82
17
|
|
83
|
-
def
|
84
|
-
"""
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
G.add_node(node_label)
|
95
|
-
G.add_edge(node_label, "Output", weight=float(coeff_str))
|
96
|
-
# Position nodes for visualization
|
97
|
-
left_nodes = [n for n in G.nodes() if n != "Output"]
|
98
|
-
pos = {}
|
99
|
-
n_left = len(left_nodes)
|
100
|
-
for i, node in enumerate(sorted(left_nodes)):
|
101
|
-
pos[node] = (0, 1 - (i / max(n_left - 1, 1)))
|
102
|
-
pos["Output"] = (1, 0.5)
|
103
|
-
plt.figure(figsize=(12, 8))
|
104
|
-
nx.draw(G, pos, with_labels=True, node_color="skyblue", node_size=2500, font_size=10,
|
105
|
-
arrows=True, arrowstyle='->', arrowsize=20)
|
106
|
-
edge_labels = {(u, v): f"{d['weight']:.2f}" for u, v, d in G.edges(data=True)}
|
107
|
-
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_color='red', font_size=10)
|
108
|
-
plt.title("OIKAN Symbolic Formula Graph")
|
109
|
-
plt.axis("off")
|
110
|
-
plt.show()
|
111
|
-
|
112
|
-
def extract_latex_formula(model, X, mode='regression'):
|
113
|
-
"""Return the extracted symbolic formula formatted as LaTeX code."""
|
114
|
-
formula = extract_symbolic_formula(model, X, mode)
|
115
|
-
terms = formula.split(" + ")
|
116
|
-
latex_terms = []
|
117
|
-
for term in terms:
|
118
|
-
expr = term.strip("()")
|
119
|
-
coeff_str, basis = expr.split("*", 1) if "*" in expr else (expr, "")
|
120
|
-
coeff = float(coeff_str)
|
121
|
-
# Balance parentheses if required
|
122
|
-
missing = basis.count("(") - basis.count(")")
|
123
|
-
if missing > 0:
|
124
|
-
basis = basis + ")" * missing
|
125
|
-
coeff_latex = f"{abs(coeff):.2f}".rstrip("0").rstrip(".")
|
126
|
-
term_latex = coeff_latex if basis.strip() == "1" else f"{coeff_latex} \\cdot {basis.strip()}"
|
127
|
-
latex_terms.append(f"- {term_latex}" if coeff < 0 else f"+ {term_latex}")
|
128
|
-
latex_formula = " ".join(latex_terms).lstrip("+ ").strip()
|
129
|
-
return f"$$ {latex_formula} $$"
|
18
|
+
def format_symbolic_terms(terms):
|
19
|
+
"""
|
20
|
+
Formats a list of structured symbolic terms (coef, basis) to a string.
|
21
|
+
"""
|
22
|
+
formatted_terms = []
|
23
|
+
for coef, basis in terms:
|
24
|
+
if basis == "1":
|
25
|
+
formatted_terms.append(f"{coef:.4f}")
|
26
|
+
else:
|
27
|
+
formatted_terms.append(f"{coef:.4f}*{basis}")
|
28
|
+
return " + ".join(formatted_terms) if formatted_terms else "0"
|
oikan/utils.py
CHANGED
@@ -1,44 +1,47 @@
|
|
1
|
+
from .exceptions import *
|
1
2
|
import torch
|
2
3
|
import torch.nn as nn
|
3
4
|
import numpy as np
|
4
|
-
from scipy.interpolate import BSpline
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
self.register_buffer('knots', torch.FloatTensor(knots))
|
19
|
-
|
20
|
-
def forward(self, x):
|
21
|
-
# Convert tensor to numpy for BSpline evaluation
|
22
|
-
x_np = x.detach().cpu().numpy()
|
23
|
-
basis_values = np.zeros((x_np.shape[0], self.num_knots - self.degree - 1))
|
24
|
-
# Normalize input for stable spline evaluation
|
25
|
-
x_min, x_max = x_np.min(), x_np.max()
|
26
|
-
x_normalized = (x_np - x_min) / (x_max - x_min + 1e-8)
|
27
|
-
for i in range(self.num_knots - self.degree - 1):
|
28
|
-
# Create BSpline basis function for a subset of knots
|
29
|
-
spl = BSpline.basis_element(self.knots[i:i+self.degree+2])
|
30
|
-
basis_values[:, i] = spl(x_normalized.squeeze())
|
31
|
-
basis_values = np.nan_to_num(basis_values, 0)
|
32
|
-
return torch.FloatTensor(basis_values).to(x.device)
|
6
|
+
# Core basis functions with explicit variable notation
|
7
|
+
ADVANCED_LIB = {
|
8
|
+
'x': ('x', lambda x: x),
|
9
|
+
'x^2': ('x^2', lambda x: np.clip(x**2, -100, 100)),
|
10
|
+
'x^3': ('x^3', lambda x: np.clip(x**3, -100, 100)),
|
11
|
+
'exp': ('exp(x)', lambda x: np.exp(np.clip(x, -10, 10))),
|
12
|
+
'log': ('log(x)', lambda x: np.log(np.abs(x) + 1)),
|
13
|
+
'sqrt': ('sqrt(x)', lambda x: np.sqrt(np.abs(x))),
|
14
|
+
'tanh': ('tanh(x)', lambda x: np.tanh(x)),
|
15
|
+
'sin': ('sin(x)', lambda x: np.sin(np.clip(x, -10*np.pi, 10*np.pi))),
|
16
|
+
'abs': ('abs(x)', lambda x: np.abs(x))
|
17
|
+
}
|
33
18
|
|
34
|
-
class
|
35
|
-
|
36
|
-
def __init__(self
|
19
|
+
class EdgeActivation(nn.Module):
|
20
|
+
"""Learnable edge-based activation function."""
|
21
|
+
def __init__(self):
|
37
22
|
super().__init__()
|
38
|
-
self.
|
23
|
+
self.weights = nn.Parameter(torch.randn(len(ADVANCED_LIB)))
|
24
|
+
self.bias = nn.Parameter(torch.zeros(1))
|
39
25
|
|
40
26
|
def forward(self, x):
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
27
|
+
features = []
|
28
|
+
for _, func in ADVANCED_LIB.values():
|
29
|
+
feat = torch.tensor(func(x.detach().cpu().numpy()),
|
30
|
+
dtype=torch.float32).to(x.device)
|
31
|
+
features.append(feat)
|
32
|
+
features = torch.stack(features, dim=-1)
|
33
|
+
return torch.matmul(features, self.weights.unsqueeze(0).T) + self.bias
|
34
|
+
|
35
|
+
def get_symbolic_repr(self, threshold=1e-4):
|
36
|
+
"""Get symbolic representation of the activation function."""
|
37
|
+
significant_terms = []
|
38
|
+
|
39
|
+
for (notation, _), weight in zip(ADVANCED_LIB.values(),
|
40
|
+
self.weights.detach().cpu().numpy()):
|
41
|
+
if abs(weight) > threshold:
|
42
|
+
significant_terms.append(f"{weight:.4f}*{notation}")
|
43
|
+
|
44
|
+
if abs(self.bias.item()) > threshold:
|
45
|
+
significant_terms.append(f"{self.bias.item():.4f}")
|
46
|
+
|
47
|
+
return " + ".join(significant_terms) if significant_terms else "0"
|
@@ -0,0 +1,223 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: oikan
|
3
|
+
Version: 0.0.2.2
|
4
|
+
Summary: OIKAN: Optimized Interpretable Kolmogorov-Arnold Networks
|
5
|
+
Author: Arman Zhalgasbayev
|
6
|
+
License: MIT
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
9
|
+
Classifier: Operating System :: OS Independent
|
10
|
+
Requires-Python: >=3.7
|
11
|
+
Description-Content-Type: text/markdown
|
12
|
+
License-File: LICENSE
|
13
|
+
Requires-Dist: torch
|
14
|
+
Requires-Dist: numpy
|
15
|
+
Requires-Dist: scikit-learn
|
16
|
+
Dynamic: license-file
|
17
|
+
|
18
|
+
<!-- logo in the center -->
|
19
|
+
<div align="center">
|
20
|
+
<img src="docs/media/oikan_logo.png" alt="OIKAN Logo" width="200"/>
|
21
|
+
|
22
|
+
<h1>OIKAN: Optimized Interpretable Kolmogorov-Arnold Networks</h1>
|
23
|
+
</div>
|
24
|
+
|
25
|
+
## Overview
|
26
|
+
OIKAN (Optimized Interpretable Kolmogorov-Arnold Networks) is a neuro-symbolic ML framework that combines modern neural networks with classical Kolmogorov-Arnold representation theory. It provides interpretable machine learning solutions through automatic extraction of symbolic mathematical formulas from trained models.
|
27
|
+
|
28
|
+
[](https://badge.fury.io/py/oikan)
|
29
|
+
[](https://pypistats.org/packages/oikan)
|
30
|
+
[](https://pepy.tech/projects/oikan)
|
31
|
+
[](https://opensource.org/licenses/MIT)
|
32
|
+
[](https://github.com/silvermete0r/oikan/issues)
|
33
|
+
[](https://silvermete0r.github.io/oikan/)
|
34
|
+
|
35
|
+
## Key Features
|
36
|
+
- 🧠 **Neuro-Symbolic ML**: Combines neural network learning with symbolic mathematics
|
37
|
+
- 📊 **Automatic Formula Extraction**: Generates human-readable mathematical expressions
|
38
|
+
- 🎯 **Scikit-learn Compatible**: Familiar `.fit()` and `.predict()` interface
|
39
|
+
- 🚀 **Production-Ready**: Export symbolic formulas for lightweight deployment
|
40
|
+
- 📈 **Multi-Task**: Supports both regression and classification problems
|
41
|
+
|
42
|
+
## Scientific Foundation
|
43
|
+
|
44
|
+
OIKAN is based on Kolmogorov's superposition theorem, which states that any multivariate continuous function can be represented as a composition of single-variable functions. We leverage this theory by:
|
45
|
+
|
46
|
+
1. Using neural networks to learn optimal basis functions through interpretable edge transformations
|
47
|
+
2. Combining transformed features using learnable weights
|
48
|
+
3. Automatically extracting human-readable symbolic formulas
|
49
|
+
|
50
|
+
## Quick Start
|
51
|
+
|
52
|
+
### Installation
|
53
|
+
|
54
|
+
#### Method 1: Via PyPI (Recommended)
|
55
|
+
```bash
|
56
|
+
pip install -qU oikan
|
57
|
+
```
|
58
|
+
|
59
|
+
#### Method 2: Local Development
|
60
|
+
```bash
|
61
|
+
git clone https://github.com/silvermete0r/OIKAN.git
|
62
|
+
cd OIKAN
|
63
|
+
pip install -e . # Install in development mode
|
64
|
+
```
|
65
|
+
|
66
|
+
### Regression Example
|
67
|
+
```python
|
68
|
+
from oikan.model import OIKANRegressor
|
69
|
+
from sklearn.model_selection import train_test_split
|
70
|
+
|
71
|
+
# Initialize model with optimal architecture
|
72
|
+
model = OIKANRegressor(
|
73
|
+
hidden_dims=[16, 8], # Network architecture
|
74
|
+
num_basis=10, # Number of basis functions
|
75
|
+
degree=3, # Polynomial degree
|
76
|
+
dropout=0.1 # Regularization
|
77
|
+
)
|
78
|
+
|
79
|
+
# Fit model (sklearn-style)
|
80
|
+
model.fit(X_train, y_train, epochs=200, lr=0.01)
|
81
|
+
|
82
|
+
# Get predictions
|
83
|
+
y_pred = model.predict(X_test)
|
84
|
+
|
85
|
+
# Save interpretable formula to file with auto-generated guidelines
|
86
|
+
# The output file will contain:
|
87
|
+
# - Detailed symbolic formulas for each feature
|
88
|
+
# - Instructions for practical implementation
|
89
|
+
# - Recommendations for production deployment
|
90
|
+
model.save_symbolic_formula("regression_formula.txt")
|
91
|
+
```
|
92
|
+
|
93
|
+
*Example of the saved symbolic formula instructions: [outputs/regression_symbolic_formula.txt](outputs/regression_symbolic_formula.txt)*
|
94
|
+
|
95
|
+
|
96
|
+
### Classification Example
|
97
|
+
```python
|
98
|
+
from oikan.model import OIKANClassifier
|
99
|
+
|
100
|
+
# Similar sklearn-style interface for classification
|
101
|
+
model = OIKANClassifier(hidden_dims=[16, 8])
|
102
|
+
model.fit(X_train, y_train)
|
103
|
+
probas = model.predict_proba(X_test)
|
104
|
+
|
105
|
+
# Save classification formulas with implementation guidelines
|
106
|
+
# The output file will contain:
|
107
|
+
# - Decision boundary formulas for each class
|
108
|
+
# - Softmax application instructions
|
109
|
+
# - Production deployment recommendations
|
110
|
+
model.save_symbolic_formula("classification_formula.txt")
|
111
|
+
```
|
112
|
+
|
113
|
+
*Example of the saved symbolic formula instructions: [outputs/classification_symbolic_formula.txt](outputs/classification_symbolic_formula.txt)*
|
114
|
+
|
115
|
+
## Architecture Details
|
116
|
+
|
117
|
+
OIKAN implements a novel neuro-symbolic architecture based on Kolmogorov-Arnold representation theory through three specialized components:
|
118
|
+
|
119
|
+
1. **Edge Symbolic Layer**: Learns interpretable single-variable transformations
|
120
|
+
- Adaptive basis function composition using 9 core functions:
|
121
|
+
```python
|
122
|
+
ADVANCED_LIB = {
|
123
|
+
'x': ('x', lambda x: x),
|
124
|
+
'x^2': ('x^2', lambda x: x**2),
|
125
|
+
'x^3': ('x^3', lambda x: x**3),
|
126
|
+
'exp': ('exp(x)', lambda x: np.exp(x)),
|
127
|
+
'log': ('log(x)', lambda x: np.log(abs(x) + 1)),
|
128
|
+
'sqrt': ('sqrt(x)', lambda x: np.sqrt(abs(x))),
|
129
|
+
'tanh': ('tanh(x)', lambda x: np.tanh(x)),
|
130
|
+
'sin': ('sin(x)', lambda x: np.sin(x)),
|
131
|
+
'abs': ('abs(x)', lambda x: np.abs(x))
|
132
|
+
}
|
133
|
+
```
|
134
|
+
- Each input feature is transformed through these basis functions
|
135
|
+
- Learnable weights determine the optimal combination
|
136
|
+
|
137
|
+
2. **Neural Composition Layer**: Multi-layer feature aggregation
|
138
|
+
- Direct feature-to-feature connections through KAN layers
|
139
|
+
- Dropout regularization (p=0.1 default) for robust learning
|
140
|
+
- Gradient clipping (max_norm=1.0) for stable training
|
141
|
+
- User-configurable hidden layer dimensions
|
142
|
+
|
143
|
+
3. **Symbolic Extraction Layer**: Generates production-ready formulas
|
144
|
+
- Weight-based term pruning (threshold=1e-4)
|
145
|
+
- Automatic coefficient optimization
|
146
|
+
- Human-readable mathematical expressions
|
147
|
+
- Exportable to lightweight production code
|
148
|
+
|
149
|
+
### Architecture Diagram
|
150
|
+
|
151
|
+

|
152
|
+
|
153
|
+
### Key Design Principles
|
154
|
+
|
155
|
+
1. **Interpretability First**: All transformations maintain clear mathematical meaning
|
156
|
+
2. **Scikit-learn Compatibility**: Familiar `.fit()` and `.predict()` interface
|
157
|
+
3. **Production Ready**: Export formulas as lightweight mathematical expressions
|
158
|
+
4. **Automatic Simplification**: Remove insignificant terms (|w| < 1e-4)
|
159
|
+
|
160
|
+
## Model Components
|
161
|
+
|
162
|
+
1. **Symbolic Edge Functions**
|
163
|
+
```python
|
164
|
+
class EdgeActivation(nn.Module):
|
165
|
+
"""Learnable edge activation with basis functions"""
|
166
|
+
def forward(self, x):
|
167
|
+
return sum(self.weights[i] * basis[i](x) for i in range(self.num_basis))
|
168
|
+
```
|
169
|
+
|
170
|
+
2. **KAN Layer Implementation**
|
171
|
+
```python
|
172
|
+
class KANLayer(nn.Module):
|
173
|
+
"""Kolmogorov-Arnold Network layer"""
|
174
|
+
def forward(self, x):
|
175
|
+
edge_outputs = [self.edges[i](x[:,i]) for i in range(self.input_dim)]
|
176
|
+
return self.combine(edge_outputs)
|
177
|
+
```
|
178
|
+
|
179
|
+
3. **Formula Extraction**
|
180
|
+
```python
|
181
|
+
def get_symbolic_formula(self):
|
182
|
+
"""Extract interpretable mathematical expression"""
|
183
|
+
terms = []
|
184
|
+
for i, edge in enumerate(self.edges):
|
185
|
+
if abs(self.weights[i]) > threshold:
|
186
|
+
terms.append(f"{self.weights[i]:.4f} * {edge.formula}")
|
187
|
+
return " + ".join(terms)
|
188
|
+
```
|
189
|
+
|
190
|
+
### Key Design Principles
|
191
|
+
|
192
|
+
- **Modular Architecture**: Each component is independent and replaceable
|
193
|
+
- **Interpretability First**: All transformations maintain symbolic representations
|
194
|
+
- **Automatic Simplification**: Removes insignificant terms and combines similar expressions
|
195
|
+
- **Production Ready**: Export formulas for lightweight deployment
|
196
|
+
|
197
|
+
## Contributing
|
198
|
+
|
199
|
+
We welcome contributions! Key areas of interest:
|
200
|
+
|
201
|
+
- Model architecture improvements
|
202
|
+
- Novel basis function implementations
|
203
|
+
- Improved symbolic extraction algorithms
|
204
|
+
- Real-world case studies and applications
|
205
|
+
- Performance optimizations
|
206
|
+
|
207
|
+
Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
208
|
+
|
209
|
+
## Citation
|
210
|
+
|
211
|
+
If you use OIKAN in your research, please cite:
|
212
|
+
|
213
|
+
```bibtex
|
214
|
+
@software{oikan2025,
|
215
|
+
title = {OIKAN: Optimized Interpretable Kolmogorov-Arnold Networks},
|
216
|
+
author = {Zhalgasbayev, Arman},
|
217
|
+
year = {2025},
|
218
|
+
url = {https://github.com/silvermete0r/OIKAN}
|
219
|
+
}
|
220
|
+
```
|
221
|
+
|
222
|
+
## License
|
223
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
@@ -0,0 +1,10 @@
|
|
1
|
+
oikan/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
oikan/exceptions.py,sha256=UqT3uTtfiB8QA_3AMvKdHOme9WL9HZD_d7GHIk00LJw,394
|
3
|
+
oikan/model.py,sha256=iHWKjk_n0Kkw47UO2XFTc0faqGYBrQBJhmmRn1Po4qw,19604
|
4
|
+
oikan/symbolic.py,sha256=TtalmSpBecf33_g7yE3q-RPuCVRWQNaXWE4LsCNZmfg,1040
|
5
|
+
oikan/utils.py,sha256=sivt_8jzATH-eUZ3-P-tsdmyIgKsayibSZeP_MtLTfU,1969
|
6
|
+
oikan-0.0.2.2.dist-info/licenses/LICENSE,sha256=75ASVmU-XIpN-M4LbVmJ_ibgbzbvRLVti8FhnR0BTf8,1096
|
7
|
+
oikan-0.0.2.2.dist-info/METADATA,sha256=VvxfL5IWijk6RJObJL5fORZQFAY55X_oZf00Qk5ATTU,8519
|
8
|
+
oikan-0.0.2.2.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
9
|
+
oikan-0.0.2.2.dist-info/top_level.txt,sha256=XwnwKwTJddZwIvtrUsAz-l-58BJRj6HjAGWrfYi_3QY,6
|
10
|
+
oikan-0.0.2.2.dist-info/RECORD,,
|
oikan/metrics.py
DELETED
@@ -1,48 +0,0 @@
|
|
1
|
-
import numpy as np
|
2
|
-
import torch
|
3
|
-
from sklearn.metrics import precision_score, recall_score, f1_score, hamming_loss
|
4
|
-
|
5
|
-
def evaluate_regression(model, X, y):
|
6
|
-
'''Evaluate regression performance by computing MSE, MAE, and RMSE, and print in table format.'''
|
7
|
-
with torch.no_grad():
|
8
|
-
y_pred = model(torch.FloatTensor(X)).numpy().ravel()
|
9
|
-
mse = np.mean((y - y_pred)**2)
|
10
|
-
mae = np.mean(np.abs(y - y_pred))
|
11
|
-
rmse = np.sqrt(mse)
|
12
|
-
|
13
|
-
# Print table
|
14
|
-
header = f"+{'-'*23}+{'-'*12}+"
|
15
|
-
print(header)
|
16
|
-
print(f"| {'Metric':21} | {'Value':9} |")
|
17
|
-
print(header)
|
18
|
-
print(f"| {'Mean Squared Error':21} | {mse:9.4f} |")
|
19
|
-
print(f"| {'Mean Absolute Error':21} | {mae:9.4f} |")
|
20
|
-
print(f"| {'Root Mean Squared Error':21} | {rmse:9.4f} |")
|
21
|
-
print(header)
|
22
|
-
|
23
|
-
return mse, mae, rmse
|
24
|
-
|
25
|
-
def evaluate_classification(model, X, y):
|
26
|
-
'''Evaluate classification performance by computing accuracy, precision, recall, f1-score, and hamming_loss, and printing in table format.'''
|
27
|
-
with torch.no_grad():
|
28
|
-
logits = model(torch.FloatTensor(X))
|
29
|
-
y_pred = torch.argmax(logits, dim=1).numpy()
|
30
|
-
accuracy = np.mean(y_pred == y)
|
31
|
-
precision = precision_score(y, y_pred, average='weighted', zero_division=0)
|
32
|
-
recall = recall_score(y, y_pred, average='weighted', zero_division=0)
|
33
|
-
f1 = f1_score(y, y_pred, average='weighted', zero_division=0)
|
34
|
-
h_loss = hamming_loss(y, y_pred)
|
35
|
-
|
36
|
-
# Print table
|
37
|
-
header = f"+{'-'*15}+{'-'*12}+"
|
38
|
-
print(header)
|
39
|
-
print(f"| {'Metric':13} | {'Value':9} |")
|
40
|
-
print(header)
|
41
|
-
print(f"| {'Accuracy':13} | {accuracy:9.4f} |")
|
42
|
-
print(f"| {'Precision':13} | {precision:9.4f} |")
|
43
|
-
print(f"| {'Recall':13} | {recall:9.4f} |")
|
44
|
-
print(f"| {'F1-score':13} | {f1:9.4f} |")
|
45
|
-
print(f"| {'Hamming Loss':13} | {h_loss:9.4f} |")
|
46
|
-
print(header)
|
47
|
-
|
48
|
-
return accuracy, precision, recall, f1, h_loss
|
oikan/regularization.py
DELETED
@@ -1,30 +0,0 @@
|
|
1
|
-
import torch
|
2
|
-
import torch.nn as nn
|
3
|
-
|
4
|
-
class RegularizedLoss:
|
5
|
-
def __init__(self, base_criterion, model, l1_lambda=0.01, gradient_lambda=0.01):
|
6
|
-
self.base_criterion = base_criterion # Primary loss (e.g. MSE, CrossEntropy)
|
7
|
-
self.model = model
|
8
|
-
self.l1_lambda = l1_lambda
|
9
|
-
self.gradient_lambda = gradient_lambda
|
10
|
-
|
11
|
-
def __call__(self, pred, target, inputs):
|
12
|
-
# Compute the standard loss
|
13
|
-
base_loss = self.base_criterion(pred, target)
|
14
|
-
|
15
|
-
# Calculate L1 regularization to promote sparsity
|
16
|
-
l1_loss = 0
|
17
|
-
for param in self.model.parameters():
|
18
|
-
l1_loss += torch.norm(param, p=1)
|
19
|
-
|
20
|
-
# Compute gradient penalty to enforce smoothness
|
21
|
-
inputs.requires_grad_(True)
|
22
|
-
outputs = self.model(inputs)
|
23
|
-
gradients = torch.autograd.grad(
|
24
|
-
outputs=outputs.sum(),
|
25
|
-
inputs=inputs,
|
26
|
-
create_graph=True
|
27
|
-
)[0]
|
28
|
-
grad_penalty = ((gradients.norm(2, dim=1) - 1) ** 2).mean()
|
29
|
-
|
30
|
-
return base_loss + self.l1_lambda * l1_loss + self.gradient_lambda * grad_penalty
|
oikan/trainer.py
DELETED
@@ -1,49 +0,0 @@
|
|
1
|
-
import torch
|
2
|
-
import torch.nn as nn
|
3
|
-
from .regularization import RegularizedLoss
|
4
|
-
|
5
|
-
def train(model, train_data, epochs=100, lr=0.01, save_path=None, verbose=True):
|
6
|
-
'''Train regression model using MSE loss with regularization.
|
7
|
-
Optionally save the model when training is finished if save_path is provided.
|
8
|
-
'''
|
9
|
-
X_train, y_train = train_data
|
10
|
-
optimizer = torch.optim.Adam(model.parameters(), lr=lr)
|
11
|
-
criterion = nn.MSELoss()
|
12
|
-
reg_loss = RegularizedLoss(criterion, model)
|
13
|
-
|
14
|
-
model.train()
|
15
|
-
for epoch in range(epochs):
|
16
|
-
optimizer.zero_grad() # Reset gradients
|
17
|
-
outputs = model(X_train)
|
18
|
-
loss = reg_loss(outputs, y_train, X_train)
|
19
|
-
loss.backward() # Backpropagate errors
|
20
|
-
optimizer.step() # Update parameters
|
21
|
-
|
22
|
-
if (epoch + 1) % 10 == 0 and verbose:
|
23
|
-
print(f'Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.4f}')
|
24
|
-
if save_path is not None:
|
25
|
-
torch.save(model.state_dict(), save_path)
|
26
|
-
print(f"Model saved to {save_path}")
|
27
|
-
|
28
|
-
def train_classification(model, train_data, epochs=100, lr=0.01, save_path=None, verbose=True):
|
29
|
-
'''Train classification model using CrossEntropy loss with regularization.
|
30
|
-
Optionally save the model when training is finished if save_path is provided.
|
31
|
-
'''
|
32
|
-
X_train, y_train = train_data
|
33
|
-
optimizer = torch.optim.Adam(model.parameters(), lr=lr)
|
34
|
-
criterion = nn.CrossEntropyLoss()
|
35
|
-
reg_loss = RegularizedLoss(criterion, model)
|
36
|
-
|
37
|
-
model.train()
|
38
|
-
for epoch in range(epochs):
|
39
|
-
optimizer.zero_grad() # Reset gradients each epoch
|
40
|
-
outputs = model(X_train)
|
41
|
-
loss = reg_loss(outputs, y_train, X_train)
|
42
|
-
loss.backward() # Backpropagation
|
43
|
-
optimizer.step() # Parameter update
|
44
|
-
|
45
|
-
if (epoch + 1) % 10 == 0 and verbose:
|
46
|
-
print(f'Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.4f}')
|
47
|
-
if save_path is not None:
|
48
|
-
torch.save(model.state_dict(), save_path)
|
49
|
-
print(f"Model saved to {save_path}")
|