Dynamic prototype memory classifier library
Project description
cliving
cliving est une librairie de classification incrémentale par mémoire dynamique.
Elle ne calcule pas les embeddings. Elle prend des vecteurs numériques en entrée et maintient une mémoire de prototypes supervisés qui évolue au fil du temps.
L'objectif est d'avoir un classifieur :
- adaptable immédiatement après correction utilisateur
- explicable
- peu coûteux à l'inférence
- sans retrain global
Idée générale
cliving fonctionne comme un système de classes vivantes.
Chaque classe n'est pas représentée par un unique centre figé. Elle possède une ou plusieurs zones locales :
- une zone = un prototype
- un prototype = un centre + une covariance diagonale régularisée + une confiance + un historique local
Quand un nouvel embedding arrive :
- il est comparé à tous les prototypes
- chaque prototype produit un score local
- les scores sont agrégés par classe
- la meilleure classe gagne, sauf si le cas est trop ambigu ou trop peu confiant
Quand un utilisateur corrige :
- si l'embedding ressemble à un prototype existant de la bonne classe, ce prototype est mis à jour
- sinon un nouveau prototype est créé
Quand on lance la maintenance :
- les prototypes trop vieux sont affaiblis
- certains prototypes proches peuvent fusionner
- certains prototypes trop diffus peuvent être coupés
- certains prototypes faibles peuvent être supprimés
Positionnement dans une stack ML
cliving se place après le calcul des embeddings.
Pipeline typique :
- préparer les transactions
- construire un embedding dense par transaction
- appeler
fit(X, y)une première fois - appeler
predict(x)pour classer - appeler
fix(x, y_true)lorsqu'un utilisateur corrige - appeler
clean()périodiquement - appeler
save()/load()pour persister la mémoire
API publique
La classe principale est cliving.Cliving.
Construction
Signature :
Cliving(
*,
abstain_label: str = "review_needed",
acceptance_score: float | None = None,
conflict_score: float | None = None,
low_confidence_score: float | None = None,
ambiguity_margin: float | None = None,
)
Paramètres principaux :
abstain_label: classe renvoyée quand la prédiction doit être revueacceptance_score: seuil minimal pour rattacher une correction à un prototype existantconflict_score: seuil à partir duquel un prototype concurrent est considéré comme conflit locallow_confidence_score: seuil minimal de confiance pour accepter une classeambiguity_margin: marge minimale entre top-1 et top-2
Exemple :
from cliving import Cliving
model = Cliving(
abstain_label="review_needed",
acceptance_score=-7.0,
conflict_score=-6.4,
low_confidence_score=-2.2,
ambiguity_margin=1.0,
)
Méthodes principales
fit
fit(
X: np.ndarray | list[list[float]],
y: np.ndarray | list[object],
) -> Cliving
Initialise complètement la mémoire à partir d'un jeu d'embeddings et de labels.
predict
predict(
X: np.ndarray | list[float] | list[list[float]],
) -> PredictionResult | list[PredictionResult]
Retourne directement un résultat riche :
- si
Xest un seul embedding 1D : unPredictionResult - si
Xest un batch 2D : unelist[PredictionResult]
Les champs principaux de PredictionResult sont :
predicted_classclass_scoresbest_prototype_idbest_prototype_classbest_prototype_scoremarginambiguouslow_confidencereview_needed
fix
fix(
embedding: np.ndarray | list[float],
class_id: str,
metadata: dict[str, object] | None = None,
) -> UpdateEvent
Applique une correction utilisateur sur un embedding unique.
Le metadata est optionnel et sert au debug / audit local des prototypes.
clean
clean() -> dict[str, int]
Lance manuellement la maintenance et retourne un résumé du type :
{"merged": 1, "split_created": 0, "pruned": 2}
summary
summary() -> dict[str, list[dict[str, object]]]
Retourne un résumé léger de la mémoire par classe et prototype.
save
save(path: str | Path) -> Path
Sauvegarde l'instance via pickle.
load
load(path: str | Path) -> Cliving
Recharge une instance précédemment sauvegardée.
Exemple minimal
import numpy as np
from cliving import Cliving
X = np.array(
[
[0.2, 1.1, -0.4],
[0.1, 1.0, -0.3],
[4.0, -0.2, 0.7],
],
dtype=float,
)
y = np.array(["groceries", "groceries", "salary"], dtype=object)
model = Cliving(
abstain_label="review_needed",
acceptance_score=-7.0,
conflict_score=-6.4,
low_confidence_score=-2.2,
ambiguity_margin=1.0,
)
model.fit(X, y)
result = model.predict([0.15, 1.05, -0.35])
print(result.predicted_class)
print(result.class_scores)
event = model.fix([0.18, 1.07, -0.33], "groceries")
stats = model.clean()
Construire des embeddings simples pour une transaction
cliving ne calcule pas les embeddings, mais un pipeline sklearn simple suffit souvent très bien.
Supposons une transaction avec :
label: texte courtamount: numériquesupport: catégorielle, par exemplecheque,carte,virement
Exemple de pipeline simple :
import numpy as np
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.decomposition import TruncatedSVD
from sklearn.feature_extraction.text import HashingVectorizer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import FunctionTransformer, OneHotEncoder, StandardScaler
from cliving import Cliving
def amount_features(frame: pd.DataFrame) -> np.ndarray:
amount = pd.to_numeric(frame.iloc[:, 0], errors="coerce").fillna(0.0).to_numpy(dtype=float)
return np.column_stack(
[
amount,
np.abs(amount),
np.log1p(np.abs(amount)),
np.sign(amount),
]
)
transactions = pd.DataFrame(
[
{"label": "carrefour city paris", "amount": -12.4, "support": "carte"},
{"label": "monoprix courses", "amount": -48.2, "support": "carte"},
{"label": "virement salaire acme", "amount": 2450.0, "support": "virement"},
{"label": "cheque remboursement", "amount": 120.0, "support": "cheque"},
]
)
y = np.array(["groceries", "groceries", "salary", "refund"], dtype=object)
preprocess = ColumnTransformer(
transformers=[
(
"text",
HashingVectorizer(
analyzer="char_wb",
ngram_range=(3, 5),
n_features=2**12,
alternate_sign=False,
lowercase=True,
norm="l2",
),
"label",
),
("support", OneHotEncoder(handle_unknown="ignore"), ["support"]),
("amount", FunctionTransformer(amount_features, validate=False), ["amount"]),
],
remainder="drop",
)
embedding_pipeline = Pipeline(
steps=[
("features", preprocess),
("svd", TruncatedSVD(n_components=16, random_state=0)),
("scale", StandardScaler()),
]
)
X = embedding_pipeline.fit_transform(transactions)
model = Cliving(
abstain_label="review_needed",
acceptance_score=-7.0,
conflict_score=-6.4,
low_confidence_score=-2.2,
ambiguity_margin=1.0,
)
model.fit(X, y)
new_tx = pd.DataFrame(
[
{"label": "carrefour market", "amount": -16.3, "support": "carte"}
]
)
z = embedding_pipeline.transform(new_tx)[0]
result = model.predict(z)
print(result.predicted_class)
Pourquoi ce pipeline est adapté :
- le texte capture la similarité locale des libellés
- la variable catégorielle sépare des modes transactionnels simples
- le montant injecte un signal métier utile
TruncatedSVD + StandardScalerproduit un espace dense compact, adapté aux prototypes et à la distance de Mahalanobis
Ce que la librairie attend en entrée
Les embeddings doivent être :
- numériques
- de dimension fixe
- convertibles en
numpy.ndarray
Formats usuels :
np.ndarray2D pourfitnp.ndarray1D pourpredictoufixsur un seul exemplelist[list[float]]list[float]
Démo
La démo du projet utilise l’encodeur sklearn local pour produire des embeddings, mais cet encodeur ne fait pas partie de la librairie publiée.
uv run --extra demo python examples/demo_cliving.py
Scenario simulé
Le projet contient aussi une simulation plus longue pour visualiser le caractère "vivant" de la mémoire :
- une phase d'apprentissage initial sur un historique de transactions
- puis un run en ligne avec nouvelles transactions, confirmations positives, corrections utilisateur et
clean()périodiques - une première phase plus exploratoire, suivie d'une phase plus stable où certains motifs reviennent souvent
Dans cette simulation, cliving crée de nouveaux prototypes quand il découvre des zones encore mal couvertes, puis consolide progressivement la mémoire via les update, les merge et les clean(). On cherche donc à visualiser à la fois la montée en complexité de la mémoire et sa tendance à converger quand le flux devient plus répétitif.
Tests
uv run --extra dev pytest
Project details
Release history Release notifications | RSS feed
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 cliving-0.1.0.tar.gz.
File metadata
- Download URL: cliving-0.1.0.tar.gz
- Upload date:
- Size: 11.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ce0f4ea0791ce5abcd562e53fddf377dbe7cdc651c3424bb258f17a95e8758e7
|
|
| MD5 |
7e7fe3a849a92a55213eff61d5ed8956
|
|
| BLAKE2b-256 |
1cae93b10591189dc7c03d0493ad020b36f4919ad93752043f3288e437dd307a
|
File details
Details for the file cliving-0.1.0-py3-none-any.whl.
File metadata
- Download URL: cliving-0.1.0-py3-none-any.whl
- Upload date:
- Size: 13.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
551d547eac7c35a373c603216731edadf3574ffb880e2e59562c33f1750482ac
|
|
| MD5 |
d7814d0d54268475366617505393825c
|
|
| BLAKE2b-256 |
1f5ad38033b6ce19401328eedc47861aeb58eca08423794dce128afa698d039f
|