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/symbolic.py CHANGED
@@ -1,129 +1,28 @@
1
- import torch
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
- 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
- # 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
- Approximate a symbolic formula that represents model behavior using nonlinear bases.
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
- 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
- # Only include terms with significant coefficients
59
- terms = [f"({c:.2f}*{name})" for c, name in zip(beta, func_names) if abs(c) > 1e-4]
60
- return " + ".join(terms)
61
-
62
- def test_symbolic_formula(model, X, mode='regression'):
63
- """Evaluate the symbolic approximation against the model by computing error metrics."""
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 plot_symbolic_formula(model, X, mode='regression'):
84
- """Plot a graph representation of the extracted symbolic formula."""
85
- formula = extract_symbolic_formula(model, X, mode)
86
- G = nx.DiGraph()
87
- G.add_node("Output")
88
- terms = formula.split(" + ")
89
- # Add nodes for each term with coefficient information
90
- for term in terms:
91
- expr = term.strip("()")
92
- coeff_str, basis = expr.split("*", 1) if "*" in expr else (expr, "unknown")
93
- node_label = f"{basis}\n({float(coeff_str):.2f})"
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
- class BSplineBasis(nn.Module):
7
- '''Module to compute B-Spline basis values for input features.'''
8
- def __init__(self, num_knots=10, degree=3):
9
- super().__init__()
10
- # Ensure ample knots relative to degree
11
- self.num_knots = max(num_knots, degree + 5)
12
- self.degree = degree
13
- # Create inner knots uniformly in [0,1]
14
- inner_knots = np.linspace(0, 1, self.num_knots - 2 * degree)
15
- left_pad = np.zeros(degree)
16
- right_pad = np.ones(degree)
17
- knots = np.concatenate([left_pad, inner_knots, right_pad])
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 FourierBasis(nn.Module):
35
- '''Module to compute Fourier basis representations for input features.'''
36
- def __init__(self, num_frequencies=5):
19
+ class EdgeActivation(nn.Module):
20
+ """Learnable edge-based activation function."""
21
+ def __init__(self):
37
22
  super().__init__()
38
- self.num_frequencies = num_frequencies
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
- # Create a range of frequencies and compute sine and cosine transforms.
42
- frequencies = torch.arange(1, self.num_frequencies + 1, device=x.device, dtype=torch.float)
43
- x_expanded = x * frequencies.view(1, -1) * 2 * np.pi
44
- return torch.cat([torch.sin(x_expanded), torch.cos(x_expanded)], dim=1)
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
+ [![PyPI version](https://badge.fury.io/py/oikan.svg)](https://badge.fury.io/py/oikan)
29
+ [![PyPI Downloads per month](https://img.shields.io/pypi/dm/oikan.svg)](https://pypistats.org/packages/oikan)
30
+ [![PyPI Total Downloads](https://static.pepy.tech/badge/oikan)](https://pepy.tech/projects/oikan)
31
+ [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
32
+ [![GitHub issues](https://img.shields.io/github/issues/silvermete0r/OIKAN.svg)](https://github.com/silvermete0r/oikan/issues)
33
+ [![Docs](https://img.shields.io/badge/docs-passing-brightgreen)](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
+ ![Architecture Diagram](docs/media/oikan_model_architecture_v0.0.2.2.png)
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (76.0.0)
2
+ Generator: setuptools (78.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
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}")