fysvm
Intrinsically interpretable fuzzy SVM-style classifiers built on scikit-learn.
fysvm trains linear max-margin classifiers over fuzzy rule activations instead
of raw feature coordinates. Every learned dimension is a human-readable
linguistic rule such as:
IF glucose is high AND bmi is high THEN positive
Two classifiers are provided:
FuzzyRuleSVM— trains in raw rule activation space; fast and interpretable.CSRQClassifier— trains in the canonical quotient space; solves the parameterisation-invariance problem described below.
Installation
uv pip install fysvm
CSRQClassifier requires sympy for exact rational arithmetic, which is
included in the default dependencies. The optional Variant B atomic norm
classifier (QuotientAtomicFuzzySVM) additionally requires osqp:
uv pip install "fysvm[csrq-atomic]"
Requires Python ≥ 3.14.
Quickstart: FuzzyRuleSVM
from fysvm import FuzzyRuleSVM
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
data = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
data.data, data.target, test_size=0.3, random_state=0
)
clf = FuzzyRuleSVM(
max_rule_length=2,
max_rules=128,
penalty="l1",
feature_names=data.feature_names,
random_state=0,
)
clf.fit(X_train, y_train)
print(clf.score(X_test, y_test))
How FuzzyRuleSVM works
The estimator has three stages:
1. Fuzzy concepts. Each numeric feature is summarized by three linguistic
terms — low, medium, high — defined by triangular membership functions
anchored at data quantiles. Every sample gets a membership score in [0, 1] for
each concept.
2. Rule generation. All conjunctions of up to max_rule_length concepts are
enumerated as candidate rules (e.g. glucose is high AND bmi is high).
Candidates are scored by discriminative power and coverage; the top max_rules
are kept.
3. Max-margin learning. Each sample is mapped to a firing-strength vector
φ(x) ∈ [0, 1]^K where entry k is the fuzzy AND of the concepts in rule k.
A sparse linear SVM is trained on this activation space:
f(x) = Σ_k β_k · φ_k(x) + b
Explaining FuzzyRuleSVM predictions
Because the decision function is a weighted sum of fuzzy rule firings, explanations are the model computation — no proxy, no approximation.
Per-sample explanation
explanation = clf.explain(X_test[:1])[0]
Returns the bias, the net margin, and the top-contributing rules sorted by absolute contribution. Each rule has:
rule— a human-readable string likeIF feature is low THEN class_Afiring— how strongly the sample matches the rule antecedent in [0, 1]weight— the learned SVM coefficientβ_kcontribution—firing × weight, the rule's impact on the margin
Contributions sum with the bias to the predicted margin:
assert abs(explanation["margin"]
- (explanation["bias"] + explanation["net_rule_contribution"])) < 1e-12
Global rule inspection
for item in clf.support_rules():
print(item["rule"], item["weight"])
Returns rules with non-zero coefficients, sorted by absolute weight.
Fuzzy concept membership
concepts = clf.concept_memberships(X_test[:1])[0]
# {"glucose": {"low": 0.0, "medium": 0.3, "high": 0.7}, ...}
Fuzzy margin violations
violations = clf.fuzzy_violations(X_test, y_test)
# Each item: {"slack": 0.42, "memberships": {"cleanly_classified": 0.58,
# "borderline": 0.42, "strong_violation": 0.0}}
The parameterisation problem
FuzzyRuleSVM trains in raw rule activation space with a coefficient-space
norm. This norm is not invariant to the algebraic structure of the rule basis.
When the product t-norm is used with strict triangular anchors, the partition satisfies two pointwise identities:
L_j(x) + M_j(x) + H_j(x) = 1 (Ruspini identity)
L_j(x) · H_j(x) = 0 (orthogonality)
These identities generate a polynomial ideal that makes many rule dictionaries
semantically equivalent: they span the same hypothesis space. Replacing a
medium atom with its equivalent low/high expansion, permuting the rule
ordering, or rescaling atom weights are all parameterisations of the same
function space. Yet the L2 penalty changes with each such reparameterisation,
and the trained model typically changes with it.
In practice: two analysts designing semantically equivalent rule grammars
independently — one using explicit medium atoms, the other using low/high
expansions — obtain different trained models under FuzzyRuleSVM, even though
their hypothesis spaces are identical. This is a reproducibility and
interpretability artefact.
CSRQClassifier eliminates this artefact.
Quickstart: CSRQClassifier
from fysvm import CSRQClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
data = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
data.data, data.target, test_size=0.3, random_state=0
)
clf = CSRQClassifier(
C=1.0,
max_rule_length=2,
feature_names=data.feature_names,
)
clf.fit(X_train, y_train)
print(clf.score(X_test, y_test))
How CSRQClassifier works
CSRQClassifier constructs the canonical quotient space Q_{d,r}
explicitly before training. Medium terms are eliminated by exact algebraic
substitution — M_j → 1 - L_j - H_j — so the canonical basis contains only
low and high monomials. Its dimension is:
D_{d,r} = Σ_{l=0}^{r} C(d,l) · 2^l
strictly smaller than the full grammar size N_{d,r} = Σ C(d,l) · 3^l.
A positive-definite degree-weighted metric G = diag(p_q²) on this basis
yields a unique ideal minimiser that is independent of the rule dictionary
used to construct the training data. Two training modes are available:
complete(default) — trains over allDcanonical coordinates. Fully dictionary-invariant: any rule dictionary produces the same solution.dictionary— trains over the exact RREF subspace of a supplied rule dictionary. Invariant across all dictionaries with the same semantic span.
Every fitted model is accompanied by an exact certificate A_D · γ = c
verified in rational arithmetic, enabling independent audit of semantic
consistency.
Explaining CSRQClassifier predictions
explanation = clf.explain(X_test[:1])[0]
print(explanation["prediction"])
print(explanation["margin"])
for rule in explanation["top_rules"]:
print(f"{rule['monomial']:40s} weight={rule['weight']:+.4f} firing={rule['firing']:.3f}")
Global support rules
for item in clf.support_rules():
print(item["rule"], item["weight"])
Decoding back to the original grammar
The canonical basis eliminates medium atoms for training invariance, but
you can decode the canonical coefficients back to original rule weights at
any time:
decoded = clf.decode(method="minimum_l2")
for atom, weight in zip(decoded["atoms"], decoded["weights"]):
print(f"{atom} → {weight:+.4f}")
Exporting a reproducibility artifact
artifact = clf.export_artifact(X_val=X_test)
# artifact.semantic_equality_certificate.max_abs_residual is the
# exact rational certificate residual (should be 0)
print(artifact.optimization_report.n_iter)
print(artifact.semantic_equality_certificate.is_certified)
Dictionary mode: preserving hand-crafted grammars
If you have domain-specific rules that you want to keep intact (including
medium atoms), supply them explicitly and use dictionary mode:
from fysvm import CSRQClassifier
from fysvm.quotient import RuleAtom
from fysvm.rule_svm import FuzzyRule, RuleCondition
atoms = (
RuleAtom(FuzzyRule((RuleCondition(0, "high"), RuleCondition(1, "high"))), scale=1.0, cost=1.0),
RuleAtom(FuzzyRule((RuleCondition(0, "low"),)), scale=1.0, cost=1.0),
RuleAtom(FuzzyRule((RuleCondition(1, "medium"),)), scale=1.0, cost=1.0),
)
clf = CSRQClassifier(
semantic_space="dictionary",
rule_dictionary=atoms,
)
clf.fit(X_train, y_train)
The training problem is invariant across any other dictionary that spans the same semantic subspace (same RREF row space).
API Reference
FuzzyRuleSVM (aliased as SparseMaxMarginFuzzyRuleMachine)
| Parameter | Default | Description |
|---|---|---|
C |
1.0 |
Inverse regularization strength |
penalty |
"l1" |
"l1" or "l2" SVM penalty |
max_rule_length |
2 |
Max conjuncts per rule (≤ n_features) |
max_rules |
256 |
Max candidate rules kept |
min_rule_coverage |
0.02 |
Minimum fuzzy support for a candidate |
and_operator |
"min" |
"min", "product", or "softmin" |
feature_names |
None |
Column names for readable rule strings |
class_weight |
None |
"balanced" or dict for imbalanced classes |
CSRQClassifier
| Parameter | Default | Description |
|---|---|---|
C |
1.0 |
SVM regularization strength |
max_rule_length |
2 |
Max degree of canonical monomials |
degree_penalty |
1.0 |
Weight increment per extra degree above 1 (η) |
intercept_penalty |
1.0 |
Regularization weight for the intercept (p₀ > 0) |
semantic_space |
"complete" |
"complete" or "dictionary" |
rule_dictionary |
None |
tuple[RuleAtom, ...] for dictionary mode |
partition_quantiles |
(0.25, 0.5, 0.75) |
Quantiles for fuzzy partition anchors |
feature_names |
None |
Column names for readable rule strings |
class_weight |
None |
"balanced" or dict for imbalanced classes |
See the docstrings for the full parameter list.
Example notebooks
See the examples/ directory:
basic_usage.py— fit, predict, evaluateexplain_predictions.py— detailed explanation walkthrough
Citations
If you use FuzzyRuleSVM in published work, please cite:
@misc{davidsen2026fuzzy,
author = {Davidsen, S. A. and Padmavathamma, M.},
title = {Faithful Regularised Classification over Linguistic Fuzzy Rule Activations},
year = {2026},
note = {Under review}
}
If you use CSRQClassifier, please also cite:
@misc{davidsen2026csrq,
author = {Davidsen, S. A. and Padmavathamma, M.},
title = {Quotient-Invariant Max-Margin Training for Product Fuzzy Rule Classifiers},
year = {2026},
note = {Under review}
}
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fysvm-0.0.5.tar.gz.
File metadata
- Download URL: fysvm-0.0.5.tar.gz
- Upload date:
- Size: 3.7 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
901e211a634eb1adda1f79471def4265913ff26d6a8ecdfee28b01d8c868e789
|
|
| MD5 |
a2e4a25cbfefe001b6b0ad4a23319085
|
|
| BLAKE2b-256 |
46fb0174a66db41472ff6b2cf7bf08d825dec16edecfb054d73d91831e194168
|
Provenance
The following attestation bundles were made for fysvm-0.0.5.tar.gz:
Publisher:
release.yml on sorend/fysvm
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fysvm-0.0.5.tar.gz -
Subject digest:
901e211a634eb1adda1f79471def4265913ff26d6a8ecdfee28b01d8c868e789 - Sigstore transparency entry: 2396803400
- Sigstore integration time:
-
Permalink:
sorend/fysvm@fd0a14daae91bf63a101c78aa666621df946dc77 -
Branch / Tag:
refs/tags/v0.0.5 - Owner: https://github.com/sorend
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@fd0a14daae91bf63a101c78aa666621df946dc77 -
Trigger Event:
push
-
Statement type:
File details
Details for the file fysvm-0.0.5-py3-none-any.whl.
File metadata
- Download URL: fysvm-0.0.5-py3-none-any.whl
- Upload date:
- Size: 64.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b46cfcec65fa506dd3cd32e404b8dfc31093aff1f5cfdff435ee833a76937033
|
|
| MD5 |
b1f2a9773a5c45b01edd5b6fd79c36ff
|
|
| BLAKE2b-256 |
361dd932a8e1134ccd652bb12aed7818e95154114e75d1b1341968df907e1453
|
Provenance
The following attestation bundles were made for fysvm-0.0.5-py3-none-any.whl:
Publisher:
release.yml on sorend/fysvm
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fysvm-0.0.5-py3-none-any.whl -
Subject digest:
b46cfcec65fa506dd3cd32e404b8dfc31093aff1f5cfdff435ee833a76937033 - Sigstore transparency entry: 2396803545
- Sigstore integration time:
-
Permalink:
sorend/fysvm@fd0a14daae91bf63a101c78aa666621df946dc77 -
Branch / Tag:
refs/tags/v0.0.5 - Owner: https://github.com/sorend
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@fd0a14daae91bf63a101c78aa666621df946dc77 -
Trigger Event:
push
-
Statement type: