tlmnet 0.1.0__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.
- tlmnet-0.1.0/LICENSE +28 -0
- tlmnet-0.1.0/PKG-INFO +102 -0
- tlmnet-0.1.0/README.md +64 -0
- tlmnet-0.1.0/pyproject.toml +99 -0
- tlmnet-0.1.0/setup.cfg +4 -0
- tlmnet-0.1.0/src/tlmnet/__init__.py +4 -0
- tlmnet-0.1.0/src/tlmnet/_version.py +3 -0
- tlmnet-0.1.0/src/tlmnet/milp.py +161 -0
- tlmnet-0.1.0/src/tlmnet.egg-info/PKG-INFO +102 -0
- tlmnet-0.1.0/src/tlmnet.egg-info/SOURCES.txt +11 -0
- tlmnet-0.1.0/src/tlmnet.egg-info/dependency_links.txt +1 -0
- tlmnet-0.1.0/src/tlmnet.egg-info/requires.txt +20 -0
- tlmnet-0.1.0/src/tlmnet.egg-info/top_level.txt +1 -0
tlmnet-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 - 2026, Carlson Research, LLC
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
tlmnet-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tlmnet
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Ternary Linear Models via Exact MILP Solvers
|
|
5
|
+
Author-email: "Carlson Research, LLC" <rolf@hrolfrc.com>
|
|
6
|
+
License-Expression: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, https://github.com/carlson-research/tlmnet
|
|
8
|
+
Project-URL: Repository, https://github.com/carlson-research/tlmnet
|
|
9
|
+
Project-URL: Documentation, https://carlson-research.github.io/tlmnet/
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
15
|
+
Requires-Python: >=3.11
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: numpy>=1.24.0
|
|
19
|
+
Requires-Dist: scipy>=1.10.0
|
|
20
|
+
Requires-Dist: scikit-learn>=1.6.0
|
|
21
|
+
Requires-Dist: joblib>=1.3.0
|
|
22
|
+
Requires-Dist: pandas
|
|
23
|
+
Requires-Dist: matplotlib
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
26
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
|
|
27
|
+
Requires-Dist: sphinx>=7.0.0; extra == "dev"
|
|
28
|
+
Requires-Dist: pydata-sphinx-theme>=0.15.0; extra == "dev"
|
|
29
|
+
Requires-Dist: sphinx-gallery; extra == "dev"
|
|
30
|
+
Requires-Dist: numpydoc; extra == "dev"
|
|
31
|
+
Requires-Dist: nbsphinx; extra == "dev"
|
|
32
|
+
Requires-Dist: pandoc; extra == "dev"
|
|
33
|
+
Requires-Dist: mypy>=1.8.0; extra == "dev"
|
|
34
|
+
Requires-Dist: pre-commit<3.8.0; extra == "dev"
|
|
35
|
+
Requires-Dist: build>=1.0.0; extra == "dev"
|
|
36
|
+
Requires-Dist: twine>=5.0.0; extra == "dev"
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
39
|
+
# tlmnet
|
|
40
|
+
|
|
41
|
+
[](https://github.com/carlson-research/tlmnet/actions)
|
|
42
|
+
[](https://pypi.org/project/tlmnet/)
|
|
43
|
+
[](https://pypi.org/project/tlmnet/)
|
|
44
|
+
[](https://carlson-research.github.io/tlmnet/)
|
|
45
|
+
[](https://github.com/carlson-research/tlmnet/blob/main/LICENSE)
|
|
46
|
+
|
|
47
|
+
`tlmnet` provides exact Mixed-Integer Linear Programming (MILP) solvers for Quantized Statistical Learning with Ternary Linear Models (TLMs).
|
|
48
|
+
|
|
49
|
+
By restricting coefficients to discrete ternary values ($w_j \in \{-1, 0, 1\}$), it constructs globally optimal, sparse, and interpretable classifiers compliant with Scikit-Learn 1.6+ API standards.
|
|
50
|
+
|
|
51
|
+
## Features
|
|
52
|
+
|
|
53
|
+
* **Exact Discrete Weights:** Solves for ternary coefficients $w_j \in \{-1, 0, 1\}$ directly using branch-and-bound optimization via `scipy.optimize.milp`.
|
|
54
|
+
* **Exact $L_0$ Cardinality Constraints:** Enforces strict integer feature selection budgets ($\sum \vert{}w_j\vert{} \le k$) using `max_features`.
|
|
55
|
+
* **Native Sparse Data Support:** Efficiently processes high-dimensional `scipy.sparse` matrices (`CSR`, `CSC`, `COO`) using sparse block constraint assembly.
|
|
56
|
+
* **Scikit-Learn Compatibility:** Plugs directly into `Pipeline`, `OneVsRestClassifier`, `GridSearchCV`, and standard estimator workflows.
|
|
57
|
+
* **Timeout & Incumbent Recovery:** Automatically returns the best feasible integer solution found if the solver reaches `time_limit` before proving global optimality.
|
|
58
|
+
|
|
59
|
+
## Installation
|
|
60
|
+
|
|
61
|
+
Install `tlmnet` directly from PyPI:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install tlmnet
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Quick Start Example
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from tlmnet import TlmMilpClassifier
|
|
71
|
+
from sklearn.datasets import make_classification
|
|
72
|
+
from sklearn.model_selection import train_test_split
|
|
73
|
+
from sklearn.pipeline import make_pipeline
|
|
74
|
+
from sklearn.preprocessing import StandardScaler
|
|
75
|
+
|
|
76
|
+
# Generate synthetic classification dataset
|
|
77
|
+
X, y = make_classification(
|
|
78
|
+
n_samples=200, n_features=15, n_informative=5, random_state=42
|
|
79
|
+
)
|
|
80
|
+
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
|
|
81
|
+
|
|
82
|
+
# Build pipeline with standardization and exact MILP classification
|
|
83
|
+
clf = make_pipeline(
|
|
84
|
+
StandardScaler(),
|
|
85
|
+
TlmMilpClassifier(max_features=5, C=1.0, time_limit=30.0)
|
|
86
|
+
)
|
|
87
|
+
clf.fit(X_train, y_train)
|
|
88
|
+
|
|
89
|
+
# Inspect model properties
|
|
90
|
+
tlm_model = clf.named_steps["tlmmilpclassifier"]
|
|
91
|
+
print("Ternary Coefficients:", tlm_model.coef_)
|
|
92
|
+
print("Test Accuracy:", clf.score(X_test, y_test))
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## License
|
|
97
|
+
|
|
98
|
+
This project is licensed under the BSD-3-Clause License.
|
|
99
|
+
|
|
100
|
+
## Authors
|
|
101
|
+
|
|
102
|
+
* **Author & Maintainer**: Rolf Carlson (rolf@hrolfrc.com)
|
tlmnet-0.1.0/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# tlmnet
|
|
2
|
+
|
|
3
|
+
[](https://github.com/carlson-research/tlmnet/actions)
|
|
4
|
+
[](https://pypi.org/project/tlmnet/)
|
|
5
|
+
[](https://pypi.org/project/tlmnet/)
|
|
6
|
+
[](https://carlson-research.github.io/tlmnet/)
|
|
7
|
+
[](https://github.com/carlson-research/tlmnet/blob/main/LICENSE)
|
|
8
|
+
|
|
9
|
+
`tlmnet` provides exact Mixed-Integer Linear Programming (MILP) solvers for Quantized Statistical Learning with Ternary Linear Models (TLMs).
|
|
10
|
+
|
|
11
|
+
By restricting coefficients to discrete ternary values ($w_j \in \{-1, 0, 1\}$), it constructs globally optimal, sparse, and interpretable classifiers compliant with Scikit-Learn 1.6+ API standards.
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
* **Exact Discrete Weights:** Solves for ternary coefficients $w_j \in \{-1, 0, 1\}$ directly using branch-and-bound optimization via `scipy.optimize.milp`.
|
|
16
|
+
* **Exact $L_0$ Cardinality Constraints:** Enforces strict integer feature selection budgets ($\sum \vert{}w_j\vert{} \le k$) using `max_features`.
|
|
17
|
+
* **Native Sparse Data Support:** Efficiently processes high-dimensional `scipy.sparse` matrices (`CSR`, `CSC`, `COO`) using sparse block constraint assembly.
|
|
18
|
+
* **Scikit-Learn Compatibility:** Plugs directly into `Pipeline`, `OneVsRestClassifier`, `GridSearchCV`, and standard estimator workflows.
|
|
19
|
+
* **Timeout & Incumbent Recovery:** Automatically returns the best feasible integer solution found if the solver reaches `time_limit` before proving global optimality.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
Install `tlmnet` directly from PyPI:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install tlmnet
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start Example
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from tlmnet import TlmMilpClassifier
|
|
33
|
+
from sklearn.datasets import make_classification
|
|
34
|
+
from sklearn.model_selection import train_test_split
|
|
35
|
+
from sklearn.pipeline import make_pipeline
|
|
36
|
+
from sklearn.preprocessing import StandardScaler
|
|
37
|
+
|
|
38
|
+
# Generate synthetic classification dataset
|
|
39
|
+
X, y = make_classification(
|
|
40
|
+
n_samples=200, n_features=15, n_informative=5, random_state=42
|
|
41
|
+
)
|
|
42
|
+
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
|
|
43
|
+
|
|
44
|
+
# Build pipeline with standardization and exact MILP classification
|
|
45
|
+
clf = make_pipeline(
|
|
46
|
+
StandardScaler(),
|
|
47
|
+
TlmMilpClassifier(max_features=5, C=1.0, time_limit=30.0)
|
|
48
|
+
)
|
|
49
|
+
clf.fit(X_train, y_train)
|
|
50
|
+
|
|
51
|
+
# Inspect model properties
|
|
52
|
+
tlm_model = clf.named_steps["tlmmilpclassifier"]
|
|
53
|
+
print("Ternary Coefficients:", tlm_model.coef_)
|
|
54
|
+
print("Test Accuracy:", clf.score(X_test, y_test))
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## License
|
|
59
|
+
|
|
60
|
+
This project is licensed under the BSD-3-Clause License.
|
|
61
|
+
|
|
62
|
+
## Authors
|
|
63
|
+
|
|
64
|
+
* **Author & Maintainer**: Rolf Carlson (rolf@hrolfrc.com)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# pip install -e ".[dev]"
|
|
2
|
+
|
|
3
|
+
[build-system]
|
|
4
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
5
|
+
build-backend = "setuptools.build_meta"
|
|
6
|
+
|
|
7
|
+
[project]
|
|
8
|
+
name = "tlmnet"
|
|
9
|
+
dynamic = ["version"]
|
|
10
|
+
description = "Ternary Linear Models via Exact MILP Solvers"
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Carlson Research, LLC", email = "rolf@hrolfrc.com"}
|
|
14
|
+
]
|
|
15
|
+
license = "BSD-3-Clause"
|
|
16
|
+
requires-python = ">=3.11"
|
|
17
|
+
dependencies = [
|
|
18
|
+
"numpy>=1.24.0",
|
|
19
|
+
"scipy>=1.10.0",
|
|
20
|
+
"scikit-learn>=1.6.0",
|
|
21
|
+
"joblib>=1.3.0",
|
|
22
|
+
"pandas",
|
|
23
|
+
"matplotlib",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
classifiers = [
|
|
27
|
+
"Programming Language :: Python :: 3",
|
|
28
|
+
"Programming Language :: Python :: 3.11",
|
|
29
|
+
"Programming Language :: Python :: 3.12",
|
|
30
|
+
"Intended Audience :: Science/Research",
|
|
31
|
+
"Topic :: Scientific/Engineering :: Information Analysis",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://github.com/carlson-research/tlmnet"
|
|
36
|
+
Repository = "https://github.com/carlson-research/tlmnet"
|
|
37
|
+
Documentation = "https://carlson-research.github.io/tlmnet/"
|
|
38
|
+
|
|
39
|
+
[project.optional-dependencies]
|
|
40
|
+
dev = [
|
|
41
|
+
"pytest>=8.0.0",
|
|
42
|
+
"pytest-cov>=5.0.0",
|
|
43
|
+
"sphinx>=7.0.0",
|
|
44
|
+
"pydata-sphinx-theme>=0.15.0",
|
|
45
|
+
"sphinx-gallery",
|
|
46
|
+
"numpydoc",
|
|
47
|
+
"nbsphinx",
|
|
48
|
+
"pandoc",
|
|
49
|
+
"mypy>=1.8.0",
|
|
50
|
+
"pre-commit<3.8.0",
|
|
51
|
+
"build>=1.0.0",
|
|
52
|
+
"twine>=5.0.0",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
[tool.coverage.run]
|
|
56
|
+
source = ["src/tlmnet"]
|
|
57
|
+
omit = [
|
|
58
|
+
"src/tlmnet/__main__.py",
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
[tool.setuptools.dynamic]
|
|
62
|
+
version = {attr = "tlmnet._version.__version__"}
|
|
63
|
+
|
|
64
|
+
[tool.setuptools.packages.find]
|
|
65
|
+
where = ["src"]
|
|
66
|
+
include = ["tlmnet*"]
|
|
67
|
+
|
|
68
|
+
[tool.pytest.ini_options]
|
|
69
|
+
filterwarnings = [
|
|
70
|
+
"ignore:Passing a non-Collection iterable to parametrize is deprecated:pytest.PytestRemovedIn10Warning",
|
|
71
|
+
]
|
|
72
|
+
addopts = "--doctest-modules"
|
|
73
|
+
pythonpath = [".", "src"]
|
|
74
|
+
testpaths = ["tests", "src"]
|
|
75
|
+
|
|
76
|
+
[tool.ruff]
|
|
77
|
+
line-length = 88
|
|
78
|
+
src = ["src", "tests"]
|
|
79
|
+
|
|
80
|
+
[tool.mypy]
|
|
81
|
+
python_version = "3.12"
|
|
82
|
+
exclude = ["docs/auto_examples/"]
|
|
83
|
+
warn_return_any = false
|
|
84
|
+
warn_unused_configs = true
|
|
85
|
+
ignore_missing_imports = true
|
|
86
|
+
|
|
87
|
+
[tool.coverage.report]
|
|
88
|
+
exclude_lines = [
|
|
89
|
+
"pragma: no cover",
|
|
90
|
+
"def __repr__",
|
|
91
|
+
"if self.debug:",
|
|
92
|
+
"if settings.DEBUG",
|
|
93
|
+
"raise AssertionError",
|
|
94
|
+
"raise NotImplementedError",
|
|
95
|
+
"if 0:",
|
|
96
|
+
"if __name__ == .__main__.:",
|
|
97
|
+
"if self.verbose:"
|
|
98
|
+
]
|
|
99
|
+
show_missing = true
|
tlmnet-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import scipy.sparse as sp
|
|
3
|
+
from scipy.optimize import Bounds, LinearConstraint, milp
|
|
4
|
+
from sklearn.base import BaseEstimator, ClassifierMixin
|
|
5
|
+
from sklearn.utils.multiclass import type_of_target, unique_labels
|
|
6
|
+
from sklearn.utils.validation import check_is_fitted, validate_data
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TlmMilpClassifier(ClassifierMixin, BaseEstimator):
|
|
10
|
+
"""Exact Ternary Linear Model Classifier using Mixed-Integer Linear Programming (MILP).
|
|
11
|
+
|
|
12
|
+
Solves for w_j in {-1, 0, 1} using decision variable splitting w_j = u_j - v_j.
|
|
13
|
+
Natively supports sparse data constraints via scipy.sparse block matrices.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self, max_features=None, C=1.0, time_limit=60.0, mip_rel_gap=1e-4, verbose=False
|
|
18
|
+
):
|
|
19
|
+
self.max_features = max_features
|
|
20
|
+
self.C = C
|
|
21
|
+
self.time_limit = time_limit
|
|
22
|
+
self.mip_rel_gap = mip_rel_gap
|
|
23
|
+
self.verbose = verbose
|
|
24
|
+
|
|
25
|
+
def _more_tags(self):
|
|
26
|
+
return {"poor_score": True, "binary_only": True}
|
|
27
|
+
|
|
28
|
+
def __sklearn_tags__(self):
|
|
29
|
+
tags = super().__sklearn_tags__()
|
|
30
|
+
tags.classifier_tags.poor_score = True
|
|
31
|
+
tags.classifier_tags.multi_class = False
|
|
32
|
+
tags.input_tags.sparse = (
|
|
33
|
+
True # Tell Scikit-Learn we natively accept sparse matrices
|
|
34
|
+
)
|
|
35
|
+
tags.estimator_type = "classifier"
|
|
36
|
+
return tags
|
|
37
|
+
|
|
38
|
+
def fit(self, X, y):
|
|
39
|
+
if y is None:
|
|
40
|
+
raise ValueError("requires y to be passed, but the target y is None")
|
|
41
|
+
|
|
42
|
+
# 1. Validate data; natively accept sparse inputs (CSR/CSC/COO)
|
|
43
|
+
X, y = validate_data(
|
|
44
|
+
self, X=X, y=y, accept_sparse=["csr", "csc", "coo"], reset=True
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# 2. Check target type
|
|
48
|
+
y_type = type_of_target(y)
|
|
49
|
+
if y_type == "unknown":
|
|
50
|
+
raise ValueError("Unknown label type: target y must be binary.")
|
|
51
|
+
if y_type != "binary":
|
|
52
|
+
raise ValueError(
|
|
53
|
+
f"Only binary classification is supported. The type of target is {y_type}."
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
self.classes_ = unique_labels(y)
|
|
57
|
+
if len(self.classes_) < 2:
|
|
58
|
+
raise ValueError(
|
|
59
|
+
f"This solver needs samples of at least 2 classes in the data, "
|
|
60
|
+
f"but the data contains only one class: {self.classes_[0]}"
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
n_samples, n_features = X.shape
|
|
64
|
+
|
|
65
|
+
# Convert y to {-1, +1}
|
|
66
|
+
y_binary = np.where(y == self.classes_[1], 1, -1)
|
|
67
|
+
|
|
68
|
+
# Decision Variables: 2*p (weights) + n (slacks)
|
|
69
|
+
c = np.zeros(2 * n_features + n_samples)
|
|
70
|
+
c[2 * n_features :] = self.C
|
|
71
|
+
|
|
72
|
+
integrality = np.concatenate([np.ones(2 * n_features), np.zeros(n_samples)])
|
|
73
|
+
lb = np.zeros(2 * n_features + n_samples)
|
|
74
|
+
ub = np.concatenate([np.ones(2 * n_features), np.full(n_samples, np.inf)])
|
|
75
|
+
bounds = Bounds(lb, ub)
|
|
76
|
+
|
|
77
|
+
constraints = []
|
|
78
|
+
|
|
79
|
+
# 1. Mutually Exclusive Ternary Constraint (Sparse)
|
|
80
|
+
row_exc = np.repeat(np.arange(n_features), 2)
|
|
81
|
+
col_exc = np.arange(2 * n_features)
|
|
82
|
+
data_exc = np.ones(2 * n_features)
|
|
83
|
+
|
|
84
|
+
A_exc_left = sp.coo_matrix(
|
|
85
|
+
(data_exc, (row_exc, col_exc)), shape=(n_features, 2 * n_features)
|
|
86
|
+
)
|
|
87
|
+
A_exc_right = sp.csr_matrix((n_features, n_samples))
|
|
88
|
+
A_exclusive = sp.hstack([A_exc_left, A_exc_right])
|
|
89
|
+
constraints.append(LinearConstraint(A_exclusive, -np.inf, 1))
|
|
90
|
+
|
|
91
|
+
# 2. Classification Margin Constraint (Sparse)
|
|
92
|
+
if not sp.issparse(X):
|
|
93
|
+
X_sparse = sp.csr_matrix(X)
|
|
94
|
+
else:
|
|
95
|
+
X_sparse = X
|
|
96
|
+
|
|
97
|
+
# Multiply rows of X by y efficiently
|
|
98
|
+
Y_diag = sp.diags(y_binary, dtype=float)
|
|
99
|
+
X_y = Y_diag @ X_sparse
|
|
100
|
+
X_y = X_y.tocoo()
|
|
101
|
+
|
|
102
|
+
# Interleave u and v coefficients
|
|
103
|
+
row_u, col_u, data_u = X_y.row, 2 * X_y.col, X_y.data
|
|
104
|
+
row_v, col_v, data_v = X_y.row, 2 * X_y.col + 1, -X_y.data
|
|
105
|
+
|
|
106
|
+
row_margin = np.concatenate([row_u, row_v])
|
|
107
|
+
col_margin = np.concatenate([col_u, col_v])
|
|
108
|
+
data_margin = np.concatenate([data_u, data_v])
|
|
109
|
+
|
|
110
|
+
A_margin_left = sp.coo_matrix(
|
|
111
|
+
(data_margin, (row_margin, col_margin)), shape=(n_samples, 2 * n_features)
|
|
112
|
+
)
|
|
113
|
+
A_margin_right = sp.eye(n_samples, format="coo")
|
|
114
|
+
A_margin = sp.hstack([A_margin_left, A_margin_right])
|
|
115
|
+
|
|
116
|
+
constraints.append(LinearConstraint(A_margin, 1.0, np.inf))
|
|
117
|
+
|
|
118
|
+
# 3. L0 Sparsity Budget (Sparse)
|
|
119
|
+
if self.max_features is not None:
|
|
120
|
+
A_sparse_left = sp.csr_matrix(np.ones((1, 2 * n_features)))
|
|
121
|
+
A_sparse_right = sp.csr_matrix((1, n_samples))
|
|
122
|
+
A_sparse_const = sp.hstack([A_sparse_left, A_sparse_right])
|
|
123
|
+
constraints.append(LinearConstraint(A_sparse_const, 0, self.max_features))
|
|
124
|
+
|
|
125
|
+
# Solve MILP with expanded options
|
|
126
|
+
options = {
|
|
127
|
+
"time_limit": self.time_limit,
|
|
128
|
+
"mip_rel_gap": self.mip_rel_gap,
|
|
129
|
+
"disp": self.verbose,
|
|
130
|
+
}
|
|
131
|
+
res = milp(
|
|
132
|
+
c=c,
|
|
133
|
+
integrality=integrality,
|
|
134
|
+
bounds=bounds,
|
|
135
|
+
constraints=constraints,
|
|
136
|
+
options=options,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# Status 0 = Optimal, Status 1 = Time Limit Reached (but res.x might exist!)
|
|
140
|
+
if res.status in (0, 1) and res.x is not None:
|
|
141
|
+
u_opt = np.round(res.x[: 2 * n_features : 2])
|
|
142
|
+
v_opt = np.round(res.x[1 : 2 * n_features : 2])
|
|
143
|
+
else:
|
|
144
|
+
# Only fail to all zeros if it's proven infeasible (Status 2) or unbounded (Status 3)
|
|
145
|
+
u_opt = np.zeros(n_features)
|
|
146
|
+
v_opt = np.zeros(n_features)
|
|
147
|
+
|
|
148
|
+
self.coef_ = u_opt - v_opt
|
|
149
|
+
self.intercept_ = 0.0
|
|
150
|
+
|
|
151
|
+
return self
|
|
152
|
+
|
|
153
|
+
def decision_function(self, X):
|
|
154
|
+
"""Predict confidence scores for samples."""
|
|
155
|
+
check_is_fitted(self)
|
|
156
|
+
X = validate_data(self, X=X, accept_sparse=["csr", "csc", "coo"], reset=False)
|
|
157
|
+
return X @ self.coef_
|
|
158
|
+
|
|
159
|
+
def predict(self, X):
|
|
160
|
+
scores = self.decision_function(X)
|
|
161
|
+
return np.where(scores >= 0, self.classes_[1], self.classes_[0])
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tlmnet
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Ternary Linear Models via Exact MILP Solvers
|
|
5
|
+
Author-email: "Carlson Research, LLC" <rolf@hrolfrc.com>
|
|
6
|
+
License-Expression: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, https://github.com/carlson-research/tlmnet
|
|
8
|
+
Project-URL: Repository, https://github.com/carlson-research/tlmnet
|
|
9
|
+
Project-URL: Documentation, https://carlson-research.github.io/tlmnet/
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
15
|
+
Requires-Python: >=3.11
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: numpy>=1.24.0
|
|
19
|
+
Requires-Dist: scipy>=1.10.0
|
|
20
|
+
Requires-Dist: scikit-learn>=1.6.0
|
|
21
|
+
Requires-Dist: joblib>=1.3.0
|
|
22
|
+
Requires-Dist: pandas
|
|
23
|
+
Requires-Dist: matplotlib
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
26
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
|
|
27
|
+
Requires-Dist: sphinx>=7.0.0; extra == "dev"
|
|
28
|
+
Requires-Dist: pydata-sphinx-theme>=0.15.0; extra == "dev"
|
|
29
|
+
Requires-Dist: sphinx-gallery; extra == "dev"
|
|
30
|
+
Requires-Dist: numpydoc; extra == "dev"
|
|
31
|
+
Requires-Dist: nbsphinx; extra == "dev"
|
|
32
|
+
Requires-Dist: pandoc; extra == "dev"
|
|
33
|
+
Requires-Dist: mypy>=1.8.0; extra == "dev"
|
|
34
|
+
Requires-Dist: pre-commit<3.8.0; extra == "dev"
|
|
35
|
+
Requires-Dist: build>=1.0.0; extra == "dev"
|
|
36
|
+
Requires-Dist: twine>=5.0.0; extra == "dev"
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
39
|
+
# tlmnet
|
|
40
|
+
|
|
41
|
+
[](https://github.com/carlson-research/tlmnet/actions)
|
|
42
|
+
[](https://pypi.org/project/tlmnet/)
|
|
43
|
+
[](https://pypi.org/project/tlmnet/)
|
|
44
|
+
[](https://carlson-research.github.io/tlmnet/)
|
|
45
|
+
[](https://github.com/carlson-research/tlmnet/blob/main/LICENSE)
|
|
46
|
+
|
|
47
|
+
`tlmnet` provides exact Mixed-Integer Linear Programming (MILP) solvers for Quantized Statistical Learning with Ternary Linear Models (TLMs).
|
|
48
|
+
|
|
49
|
+
By restricting coefficients to discrete ternary values ($w_j \in \{-1, 0, 1\}$), it constructs globally optimal, sparse, and interpretable classifiers compliant with Scikit-Learn 1.6+ API standards.
|
|
50
|
+
|
|
51
|
+
## Features
|
|
52
|
+
|
|
53
|
+
* **Exact Discrete Weights:** Solves for ternary coefficients $w_j \in \{-1, 0, 1\}$ directly using branch-and-bound optimization via `scipy.optimize.milp`.
|
|
54
|
+
* **Exact $L_0$ Cardinality Constraints:** Enforces strict integer feature selection budgets ($\sum \vert{}w_j\vert{} \le k$) using `max_features`.
|
|
55
|
+
* **Native Sparse Data Support:** Efficiently processes high-dimensional `scipy.sparse` matrices (`CSR`, `CSC`, `COO`) using sparse block constraint assembly.
|
|
56
|
+
* **Scikit-Learn Compatibility:** Plugs directly into `Pipeline`, `OneVsRestClassifier`, `GridSearchCV`, and standard estimator workflows.
|
|
57
|
+
* **Timeout & Incumbent Recovery:** Automatically returns the best feasible integer solution found if the solver reaches `time_limit` before proving global optimality.
|
|
58
|
+
|
|
59
|
+
## Installation
|
|
60
|
+
|
|
61
|
+
Install `tlmnet` directly from PyPI:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install tlmnet
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Quick Start Example
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from tlmnet import TlmMilpClassifier
|
|
71
|
+
from sklearn.datasets import make_classification
|
|
72
|
+
from sklearn.model_selection import train_test_split
|
|
73
|
+
from sklearn.pipeline import make_pipeline
|
|
74
|
+
from sklearn.preprocessing import StandardScaler
|
|
75
|
+
|
|
76
|
+
# Generate synthetic classification dataset
|
|
77
|
+
X, y = make_classification(
|
|
78
|
+
n_samples=200, n_features=15, n_informative=5, random_state=42
|
|
79
|
+
)
|
|
80
|
+
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
|
|
81
|
+
|
|
82
|
+
# Build pipeline with standardization and exact MILP classification
|
|
83
|
+
clf = make_pipeline(
|
|
84
|
+
StandardScaler(),
|
|
85
|
+
TlmMilpClassifier(max_features=5, C=1.0, time_limit=30.0)
|
|
86
|
+
)
|
|
87
|
+
clf.fit(X_train, y_train)
|
|
88
|
+
|
|
89
|
+
# Inspect model properties
|
|
90
|
+
tlm_model = clf.named_steps["tlmmilpclassifier"]
|
|
91
|
+
print("Ternary Coefficients:", tlm_model.coef_)
|
|
92
|
+
print("Test Accuracy:", clf.score(X_test, y_test))
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## License
|
|
97
|
+
|
|
98
|
+
This project is licensed under the BSD-3-Clause License.
|
|
99
|
+
|
|
100
|
+
## Authors
|
|
101
|
+
|
|
102
|
+
* **Author & Maintainer**: Rolf Carlson (rolf@hrolfrc.com)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/tlmnet/__init__.py
|
|
5
|
+
src/tlmnet/_version.py
|
|
6
|
+
src/tlmnet/milp.py
|
|
7
|
+
src/tlmnet.egg-info/PKG-INFO
|
|
8
|
+
src/tlmnet.egg-info/SOURCES.txt
|
|
9
|
+
src/tlmnet.egg-info/dependency_links.txt
|
|
10
|
+
src/tlmnet.egg-info/requires.txt
|
|
11
|
+
src/tlmnet.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
numpy>=1.24.0
|
|
2
|
+
scipy>=1.10.0
|
|
3
|
+
scikit-learn>=1.6.0
|
|
4
|
+
joblib>=1.3.0
|
|
5
|
+
pandas
|
|
6
|
+
matplotlib
|
|
7
|
+
|
|
8
|
+
[dev]
|
|
9
|
+
pytest>=8.0.0
|
|
10
|
+
pytest-cov>=5.0.0
|
|
11
|
+
sphinx>=7.0.0
|
|
12
|
+
pydata-sphinx-theme>=0.15.0
|
|
13
|
+
sphinx-gallery
|
|
14
|
+
numpydoc
|
|
15
|
+
nbsphinx
|
|
16
|
+
pandoc
|
|
17
|
+
mypy>=1.8.0
|
|
18
|
+
pre-commit<3.8.0
|
|
19
|
+
build>=1.0.0
|
|
20
|
+
twine>=5.0.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
tlmnet
|