oikan 0.0.1.2__tar.gz → 0.0.1.3__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: oikan
3
- Version: 0.0.1.2
3
+ Version: 0.0.1.3
4
4
  Summary: OIKAN: Optimized Interpretable Kolmogorov-Arnold Networks
5
5
  Author: Arman Zhalgasbayev
6
6
  License: MIT
@@ -0,0 +1,129 @@
1
+ import torch
2
+ import numpy as np
3
+ import networkx as nx
4
+ import matplotlib.pyplot as plt
5
+
6
+ ADVANCED_LIB = {
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
+ # STEP-1: Helper functions
21
+ def get_model_predictions(model, X, mode):
22
+ """Compute model predictions and return target values (and raw preds 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
+ target = (out[:, 0] - out[:, 1]).flatten() if (out.ndim > 1 and out.shape[1] > 1) else out.flatten()
31
+ return target, out
32
+ else:
33
+ raise ValueError("Unknown mode")
34
+
35
+ def build_design_matrix(X, return_names=False):
36
+ """Build the design matrix using the advanced nonlinear bases."""
37
+ X_np = np.array(X)
38
+ n_samples, d = X_np.shape
39
+ F_parts = [np.ones((n_samples, 1))]
40
+ names = ['1'] if return_names else None
41
+ for j in range(d):
42
+ xj = X_np[:, j:j+1]
43
+ for key, func in ADVANCED_LIB.items():
44
+ F_parts.append(func(xj))
45
+ if return_names:
46
+ names.append(f"{key}(x{j+1})")
47
+ return (np.hstack(F_parts), names) if return_names else np.hstack(F_parts)
48
+
49
+ # STEP-2: Main functions using helpers
50
+ def extract_symbolic_formula(model, X, mode='regression'):
51
+ """
52
+ Approximate a symbolic formula from the model using advanced nonlinear bases.
53
+ """
54
+ n_samples = np.array(X).shape[0]
55
+ y_target, _ = get_model_predictions(model, X, mode)
56
+ F, func_names = build_design_matrix(X, return_names=True)
57
+ beta, _, _, _ = np.linalg.lstsq(F, y_target, rcond=None)
58
+ terms = [f"({c:.2f}*{name})" for c, name in zip(beta, func_names) if abs(c) > 1e-4]
59
+ return " + ".join(terms)
60
+
61
+ def test_symbolic_formula(model, X, mode='regression'):
62
+ """
63
+ Evaluate the extracted symbolic formula against model outputs.
64
+ """
65
+ n_samples = np.array(X).shape[0]
66
+ y_target, out = get_model_predictions(model, X, mode)
67
+ F = build_design_matrix(X, return_names=False)
68
+ beta, _, _, _ = np.linalg.lstsq(F, y_target, rcond=None)
69
+ symbolic_vals = F.dot(beta)
70
+ if mode == 'regression':
71
+ mse = np.mean((symbolic_vals - y_target) ** 2)
72
+ mae = np.mean(np.abs(symbolic_vals - y_target))
73
+ rmse = np.sqrt(mse)
74
+ print(f"(Advanced) MSE: {mse:.4f}, MAE: {mae:.4f}, RMSE: {rmse:.4f}")
75
+ return mse, mae, rmse
76
+ elif mode == 'classification':
77
+ sym_preds = np.where(symbolic_vals >= 0, 0, 1)
78
+ model_classes = np.argmax(out, axis=1) if (out.ndim > 1) else (out >= 0.5).astype(int)
79
+ if model_classes.shape[0] != sym_preds.shape[0]:
80
+ raise ValueError("Shape mismatch between symbolic and model predictions.")
81
+ accuracy = np.mean(sym_preds == model_classes)
82
+ print(f"(Advanced) Accuracy: {accuracy:.4f}")
83
+ return accuracy
84
+
85
+ def plot_symbolic_formula(model, X, mode='regression'):
86
+ """
87
+ Plot a graph representation of the extracted symbolic formula.
88
+ """
89
+ formula = extract_symbolic_formula(model, X, mode)
90
+ G = nx.DiGraph()
91
+ G.add_node("Output")
92
+ terms = formula.split(" + ")
93
+ for term in terms:
94
+ expr = term.strip("()")
95
+ coeff_str, basis = expr.split("*", 1) if "*" in expr else (expr, "unknown")
96
+ node_label = f"{basis}\n({float(coeff_str):.2f})"
97
+ G.add_node(node_label)
98
+ G.add_edge(node_label, "Output", weight=float(coeff_str))
99
+ left_nodes = [n for n in G.nodes() if n != "Output"]
100
+ pos = {}
101
+ n_left = len(left_nodes)
102
+ for i, node in enumerate(sorted(left_nodes)):
103
+ pos[node] = (0, 1 - (i / max(n_left - 1, 1)))
104
+ pos["Output"] = (1, 0.5)
105
+ plt.figure(figsize=(12, 8))
106
+ nx.draw(G, pos, with_labels=True, node_color="skyblue", node_size=2500, font_size=10,
107
+ arrows=True, arrowstyle='->', arrowsize=20)
108
+ edge_labels = {(u, v): f"{d['weight']:.2f}" for u, v, d in G.edges(data=True)}
109
+ nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_color='red', font_size=10)
110
+ plt.title("OIKAN Symbolic Formula Graph")
111
+ plt.axis("off")
112
+ plt.show()
113
+
114
+ def extract_latex_formula(model, X, mode='regression'):
115
+ """
116
+ Return the extracted symbolic formula as LaTeX code.
117
+ """
118
+ formula = extract_symbolic_formula(model, X, mode)
119
+ terms = formula.split(" + ")
120
+ latex_terms = []
121
+ for term in terms:
122
+ expr = term.strip("()")
123
+ coeff_str, basis = expr.split("*", 1) if "*" in expr else (expr, "")
124
+ coeff = float(coeff_str)
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} $$"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: oikan
3
- Version: 0.0.1.2
3
+ Version: 0.0.1.3
4
4
  Summary: OIKAN: Optimized Interpretable Kolmogorov-Arnold Networks
5
5
  Author: Arman Zhalgasbayev
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "oikan"
7
- version = "0.0.1.2"
7
+ version = "0.0.1.3"
8
8
  description = "OIKAN: Optimized Interpretable Kolmogorov-Arnold Networks"
9
9
  authors = [{name = "Arman Zhalgasbayev"}]
10
10
  dependencies = [
@@ -1,21 +0,0 @@
1
- import torch
2
- import numpy as np
3
-
4
- def extract_symbolic_formula_regression(model, X):
5
- """Simple coefficient-based formula extraction"""
6
- model.eval()
7
- with torch.no_grad():
8
- # Get weights from the first adaptive layer
9
- weights = model.interpretable_layers[0].weights.numpy()
10
- # Simplified representation
11
- terms = []
12
- for i in range(X.shape[1]):
13
- coef = np.abs(weights[i]).mean()
14
- if coef > 0.1: # threshold for significance
15
- terms.append(f"{coef:.2f}*x{i+1}")
16
-
17
- return " + ".join(terms) if terms else "0"
18
-
19
- def extract_symbolic_formula_classification(model, X):
20
- """Extract classification boundary formula"""
21
- return extract_symbolic_formula_regression(model, X) + " = 0"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes