Metadata-Version: 2.4
Name: semaxis
Version: 0.25.0
Summary: Interpretable NLI-based text features for scikit-learn
License: MIT License
        
        Copyright (c) 2026 pillyshi
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Requires-Python: >=3.12
Requires-Dist: numpy>=1.26
Requires-Dist: openai>=1.30
Requires-Dist: pydantic>=2.0
Requires-Dist: scikit-learn>=1.4
Requires-Dist: sentence-transformers>=3.0
Requires-Dist: tiktoken>=0.7
Provides-Extra: llamacpp
Requires-Dist: llama-cpp-python>=0.2.34; extra == 'llamacpp'
Description-Content-Type: text/markdown

# SemAxis

**Interpretable NLI-based text features for scikit-learn.**

SemAxis turns raw text into a feature matrix by asking an LLM to generate natural-language hypotheses and scoring each text against them with an NLI model. Every feature is a human-readable sentence — no black-box embeddings.

```
texts  ──►  LLM (hypothesis generation)  ──►  NLI scoring  ──►  X: (n_texts, n_features)
```

Both transformers are sklearn-compatible: they work inside `Pipeline` and are safe to use with `cross_val_score`.

---

## Installation

```bash
pip install semaxis
```

---

## Unsupervised

`UnsupervisedTransformer` generates hypotheses that characterize the text collection without labels.

```python
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
from semaxis import UnsupervisedTransformer

pipe = Pipeline([
    ("vect", UnsupervisedTransformer(
        llm="gpt-4o",
        nli_model="cross-encoder/nli-deberta-v3-large",
        n_features=20,
    )),
    ("clf", LogisticRegression()),
])

cross_val_score(pipe, texts, labels, cv=5)
```

---

## Supervised

`SupervisedTransformer` generates hypotheses that *discriminate* between classes.

### Binary

```python
from semaxis import SupervisedTransformer

pipe = Pipeline([
    ("vect", SupervisedTransformer(
        llm="gpt-4o",
        nli_model="cross-encoder/nli-deberta-v3-large",
        n_features=20,
    )),
    ("clf", LogisticRegression()),
])

cross_val_score(pipe, texts, labels, cv=5)
```

Labels can be numeric (`0`/`1`) or strings (`"positive"`/`"negative"`).

### Multi-class

Use `strategy="ovr"` (one-vs-rest, default) or `strategy="ovo"` (one-vs-one):

```python
# OvR: generates n_features hypotheses per class (k × n_features total)
vect = SupervisedTransformer(llm="gpt-4o", nli_model="...", n_features=10, strategy="ovr")

# OvO: generates n_features hypotheses per class pair (C(k,2) × n_features total)
vect = SupervisedTransformer(llm="gpt-4o", nli_model="...", n_features=10, strategy="ovo")
```

---

## Interpreting features

After fitting, both transformers expose `features_` (flat list of hypothesis strings).
`SupervisedTransformer` also exposes `feature_meta_`, parallel to `features_`, which records which class pair each hypothesis came from.

```python
vect = SupervisedTransformer(llm="gpt-4o", nli_model="...", n_features=5, strategy="ovr")
vect.fit(texts, labels)

for hypothesis, meta in zip(vect.features_, vect.feature_meta_):
    print(f"[{meta.positive} vs {meta.negative}]  {hypothesis}")
```

```
[cat vs rest]  This text describes feline behavior.
[cat vs rest]  This text mentions a cat or kitten.
[dog vs rest]  This text describes canine behavior.
[dog vs rest]  This text mentions a dog or puppy.
...
```

Combine with a linear model to get per-hypothesis coefficients:

```python
from sklearn.linear_model import LogisticRegression
import numpy as np

X = vect.transform(texts)
clf = LogisticRegression().fit(X, labels)

for coef, hyp in sorted(zip(clf.coef_[0], vect.features_), key=lambda x: abs(x[0]), reverse=True):
    print(f"  {coef:+.3f}  {hyp}")
```

---

## Custom LLM

For in-process inference, use `LlamaCppClient` backed by [llama-cpp-python](https://github.com/abetlen/llama-cpp-python):

```python
from llama_cpp import Llama
from semaxis import UnsupervisedTransformer
from semaxis import LlamaCppClient

llm = LlamaCppClient(Llama(model_path="path/to/model.gguf", n_ctx=4096))

vect = UnsupervisedTransformer(llm=llm, nli_model="cross-encoder/nli-deberta-v3-large")
```

Install the optional dependency with:

```bash
pip install "semaxis[llamacpp]"
```

---

## Related Work

- Balek et al. (2025) — [LLM-based feature generation for interpretable ML](https://arxiv.org/abs/2409.07132)
- Yin et al. (2019) — NLI as zero-shot text classifier
- LogiPart (2025) — LLM hypothesis generation + NLI propagation

---

## Citation

```bibtex
@software{semaxis2026,
  title  = {SemAxis: Interpretable NLI-based text features for scikit-learn},
  year   = {2026},
}
```
