cliving 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.
@@ -0,0 +1,6 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.py[cod]
4
+ .DS_Store
5
+ dist/
6
+ tmp/
cliving-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alexandre Fleutelot
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
cliving-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,395 @@
1
+ Metadata-Version: 2.4
2
+ Name: cliving
3
+ Version: 0.1.0
4
+ Summary: Dynamic prototype memory classifier library
5
+ Project-URL: Homepage, https://github.com/AlexandreFleutelot/cliving
6
+ Project-URL: Repository, https://github.com/AlexandreFleutelot/cliving
7
+ Project-URL: Issues, https://github.com/AlexandreFleutelot/cliving/issues
8
+ Author-email: Alexandre Fleutelot <fleutelot.alexandre@gmail.com>
9
+ Maintainer-email: Alexandre Fleutelot <fleutelot.alexandre@gmail.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: classification,embeddings,incremental-learning,online-learning,prototypes
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Requires-Python: >=3.12
23
+ Requires-Dist: numpy>=2.2.4
24
+ Provides-Extra: demo
25
+ Requires-Dist: pandas>=2.2.3; extra == 'demo'
26
+ Requires-Dist: scikit-learn>=1.6.1; extra == 'demo'
27
+ Provides-Extra: dev
28
+ Requires-Dist: matplotlib>=3.10.1; extra == 'dev'
29
+ Requires-Dist: pandas>=2.2.3; extra == 'dev'
30
+ Requires-Dist: pytest>=8.3.5; extra == 'dev'
31
+ Requires-Dist: scikit-learn>=1.6.1; extra == 'dev'
32
+ Description-Content-Type: text/markdown
33
+
34
+ # cliving
35
+
36
+ `cliving` est une librairie de classification incrémentale par mémoire dynamique.
37
+
38
+ 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.
39
+
40
+ L'objectif est d'avoir un classifieur :
41
+
42
+ - adaptable immédiatement après correction utilisateur
43
+ - explicable
44
+ - peu coûteux à l'inférence
45
+ - sans retrain global
46
+
47
+ ## Idée générale
48
+
49
+ `cliving` fonctionne comme un système de classes vivantes.
50
+
51
+ Chaque classe n'est pas représentée par un unique centre figé. Elle possède une ou plusieurs zones locales :
52
+
53
+ - une zone = un prototype
54
+ - un prototype = un centre + une covariance diagonale régularisée + une confiance + un historique local
55
+
56
+ Quand un nouvel embedding arrive :
57
+
58
+ - il est comparé à tous les prototypes
59
+ - chaque prototype produit un score local
60
+ - les scores sont agrégés par classe
61
+ - la meilleure classe gagne, sauf si le cas est trop ambigu ou trop peu confiant
62
+
63
+ Quand un utilisateur corrige :
64
+
65
+ - si l'embedding ressemble à un prototype existant de la bonne classe, ce prototype est mis à jour
66
+ - sinon un nouveau prototype est créé
67
+
68
+ Quand on lance la maintenance :
69
+
70
+ - les prototypes trop vieux sont affaiblis
71
+ - certains prototypes proches peuvent fusionner
72
+ - certains prototypes trop diffus peuvent être coupés
73
+ - certains prototypes faibles peuvent être supprimés
74
+
75
+ ## Positionnement dans une stack ML
76
+
77
+ `cliving` se place après le calcul des embeddings.
78
+
79
+ Pipeline typique :
80
+
81
+ 1. préparer les transactions
82
+ 2. construire un embedding dense par transaction
83
+ 3. appeler `fit(X, y)` une première fois
84
+ 4. appeler `predict(x)` pour classer
85
+ 5. appeler `fix(x, y_true)` lorsqu'un utilisateur corrige
86
+ 6. appeler `clean()` périodiquement
87
+ 7. appeler `save()` / `load()` pour persister la mémoire
88
+
89
+ ## API publique
90
+
91
+ La classe principale est `cliving.Cliving`.
92
+
93
+ ### Construction
94
+
95
+ Signature :
96
+
97
+ ```python
98
+ Cliving(
99
+ *,
100
+ abstain_label: str = "review_needed",
101
+ acceptance_score: float | None = None,
102
+ conflict_score: float | None = None,
103
+ low_confidence_score: float | None = None,
104
+ ambiguity_margin: float | None = None,
105
+ )
106
+ ```
107
+
108
+ Paramètres principaux :
109
+
110
+ - `abstain_label` : classe renvoyée quand la prédiction doit être revue
111
+ - `acceptance_score` : seuil minimal pour rattacher une correction à un prototype existant
112
+ - `conflict_score` : seuil à partir duquel un prototype concurrent est considéré comme conflit local
113
+ - `low_confidence_score` : seuil minimal de confiance pour accepter une classe
114
+ - `ambiguity_margin` : marge minimale entre top-1 et top-2
115
+
116
+ Exemple :
117
+
118
+ ```python
119
+ from cliving import Cliving
120
+
121
+ model = Cliving(
122
+ abstain_label="review_needed",
123
+ acceptance_score=-7.0,
124
+ conflict_score=-6.4,
125
+ low_confidence_score=-2.2,
126
+ ambiguity_margin=1.0,
127
+ )
128
+ ```
129
+
130
+ ## Méthodes principales
131
+
132
+ ### `fit`
133
+
134
+ ```python
135
+ fit(
136
+ X: np.ndarray | list[list[float]],
137
+ y: np.ndarray | list[object],
138
+ ) -> Cliving
139
+ ```
140
+
141
+ Initialise complètement la mémoire à partir d'un jeu d'embeddings et de labels.
142
+
143
+ ### `predict`
144
+
145
+ ```python
146
+ predict(
147
+ X: np.ndarray | list[float] | list[list[float]],
148
+ ) -> PredictionResult | list[PredictionResult]
149
+ ```
150
+
151
+ Retourne directement un résultat riche :
152
+
153
+ - si `X` est un seul embedding 1D : un `PredictionResult`
154
+ - si `X` est un batch 2D : une `list[PredictionResult]`
155
+
156
+ Les champs principaux de `PredictionResult` sont :
157
+
158
+ - `predicted_class`
159
+ - `class_scores`
160
+ - `best_prototype_id`
161
+ - `best_prototype_class`
162
+ - `best_prototype_score`
163
+ - `margin`
164
+ - `ambiguous`
165
+ - `low_confidence`
166
+ - `review_needed`
167
+
168
+ ### `fix`
169
+
170
+ ```python
171
+ fix(
172
+ embedding: np.ndarray | list[float],
173
+ class_id: str,
174
+ metadata: dict[str, object] | None = None,
175
+ ) -> UpdateEvent
176
+ ```
177
+
178
+ Applique une correction utilisateur sur un embedding unique.
179
+
180
+ Le `metadata` est optionnel et sert au debug / audit local des prototypes.
181
+
182
+ ### `clean`
183
+
184
+ ```python
185
+ clean() -> dict[str, int]
186
+ ```
187
+
188
+ Lance manuellement la maintenance et retourne un résumé du type :
189
+
190
+ ```python
191
+ {"merged": 1, "split_created": 0, "pruned": 2}
192
+ ```
193
+
194
+ ### `summary`
195
+
196
+ ```python
197
+ summary() -> dict[str, list[dict[str, object]]]
198
+ ```
199
+
200
+ Retourne un résumé léger de la mémoire par classe et prototype.
201
+
202
+ ### `save`
203
+
204
+ ```python
205
+ save(path: str | Path) -> Path
206
+ ```
207
+
208
+ Sauvegarde l'instance via `pickle`.
209
+
210
+ ### `load`
211
+
212
+ ```python
213
+ load(path: str | Path) -> Cliving
214
+ ```
215
+
216
+ Recharge une instance précédemment sauvegardée.
217
+
218
+ ## Exemple minimal
219
+
220
+ ```python
221
+ import numpy as np
222
+
223
+ from cliving import Cliving
224
+
225
+ X = np.array(
226
+ [
227
+ [0.2, 1.1, -0.4],
228
+ [0.1, 1.0, -0.3],
229
+ [4.0, -0.2, 0.7],
230
+ ],
231
+ dtype=float,
232
+ )
233
+ y = np.array(["groceries", "groceries", "salary"], dtype=object)
234
+
235
+ model = Cliving(
236
+ abstain_label="review_needed",
237
+ acceptance_score=-7.0,
238
+ conflict_score=-6.4,
239
+ low_confidence_score=-2.2,
240
+ ambiguity_margin=1.0,
241
+ )
242
+
243
+ model.fit(X, y)
244
+
245
+ result = model.predict([0.15, 1.05, -0.35])
246
+ print(result.predicted_class)
247
+ print(result.class_scores)
248
+
249
+ event = model.fix([0.18, 1.07, -0.33], "groceries")
250
+ stats = model.clean()
251
+ ```
252
+
253
+ ## Construire des embeddings simples pour une transaction
254
+
255
+ `cliving` ne calcule pas les embeddings, mais un pipeline sklearn simple suffit souvent très bien.
256
+
257
+ Supposons une transaction avec :
258
+
259
+ - `label` : texte court
260
+ - `amount` : numérique
261
+ - `support` : catégorielle, par exemple `cheque`, `carte`, `virement`
262
+
263
+ Exemple de pipeline simple :
264
+
265
+ ```python
266
+ import numpy as np
267
+ import pandas as pd
268
+
269
+ from sklearn.compose import ColumnTransformer
270
+ from sklearn.decomposition import TruncatedSVD
271
+ from sklearn.feature_extraction.text import HashingVectorizer
272
+ from sklearn.pipeline import Pipeline
273
+ from sklearn.preprocessing import FunctionTransformer, OneHotEncoder, StandardScaler
274
+
275
+ from cliving import Cliving
276
+
277
+
278
+ def amount_features(frame: pd.DataFrame) -> np.ndarray:
279
+ amount = pd.to_numeric(frame.iloc[:, 0], errors="coerce").fillna(0.0).to_numpy(dtype=float)
280
+ return np.column_stack(
281
+ [
282
+ amount,
283
+ np.abs(amount),
284
+ np.log1p(np.abs(amount)),
285
+ np.sign(amount),
286
+ ]
287
+ )
288
+
289
+
290
+ transactions = pd.DataFrame(
291
+ [
292
+ {"label": "carrefour city paris", "amount": -12.4, "support": "carte"},
293
+ {"label": "monoprix courses", "amount": -48.2, "support": "carte"},
294
+ {"label": "virement salaire acme", "amount": 2450.0, "support": "virement"},
295
+ {"label": "cheque remboursement", "amount": 120.0, "support": "cheque"},
296
+ ]
297
+ )
298
+
299
+ y = np.array(["groceries", "groceries", "salary", "refund"], dtype=object)
300
+
301
+ preprocess = ColumnTransformer(
302
+ transformers=[
303
+ (
304
+ "text",
305
+ HashingVectorizer(
306
+ analyzer="char_wb",
307
+ ngram_range=(3, 5),
308
+ n_features=2**12,
309
+ alternate_sign=False,
310
+ lowercase=True,
311
+ norm="l2",
312
+ ),
313
+ "label",
314
+ ),
315
+ ("support", OneHotEncoder(handle_unknown="ignore"), ["support"]),
316
+ ("amount", FunctionTransformer(amount_features, validate=False), ["amount"]),
317
+ ],
318
+ remainder="drop",
319
+ )
320
+
321
+ embedding_pipeline = Pipeline(
322
+ steps=[
323
+ ("features", preprocess),
324
+ ("svd", TruncatedSVD(n_components=16, random_state=0)),
325
+ ("scale", StandardScaler()),
326
+ ]
327
+ )
328
+
329
+ X = embedding_pipeline.fit_transform(transactions)
330
+
331
+ model = Cliving(
332
+ abstain_label="review_needed",
333
+ acceptance_score=-7.0,
334
+ conflict_score=-6.4,
335
+ low_confidence_score=-2.2,
336
+ ambiguity_margin=1.0,
337
+ )
338
+ model.fit(X, y)
339
+
340
+ new_tx = pd.DataFrame(
341
+ [
342
+ {"label": "carrefour market", "amount": -16.3, "support": "carte"}
343
+ ]
344
+ )
345
+
346
+ z = embedding_pipeline.transform(new_tx)[0]
347
+ result = model.predict(z)
348
+ print(result.predicted_class)
349
+ ```
350
+
351
+ Pourquoi ce pipeline est adapté :
352
+
353
+ - le texte capture la similarité locale des libellés
354
+ - la variable catégorielle sépare des modes transactionnels simples
355
+ - le montant injecte un signal métier utile
356
+ - `TruncatedSVD + StandardScaler` produit un espace dense compact, adapté aux prototypes et à la distance de Mahalanobis
357
+
358
+ ## Ce que la librairie attend en entrée
359
+
360
+ Les embeddings doivent être :
361
+
362
+ - numériques
363
+ - de dimension fixe
364
+ - convertibles en `numpy.ndarray`
365
+
366
+ Formats usuels :
367
+
368
+ - `np.ndarray` 2D pour `fit`
369
+ - `np.ndarray` 1D pour `predict` ou `fix` sur un seul exemple
370
+ - `list[list[float]]`
371
+ - `list[float]`
372
+
373
+ ## Démo
374
+
375
+ 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.
376
+
377
+ ```bash
378
+ uv run --extra demo python examples/demo_cliving.py
379
+ ```
380
+
381
+ ## Scenario simulé
382
+
383
+ Le projet contient aussi une simulation plus longue pour visualiser le caractère "vivant" de la mémoire :
384
+
385
+ - une phase d'apprentissage initial sur un historique de transactions
386
+ - puis un run en ligne avec nouvelles transactions, confirmations positives, corrections utilisateur et `clean()` périodiques
387
+ - une première phase plus exploratoire, suivie d'une phase plus stable où certains motifs reviennent souvent
388
+
389
+ 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.
390
+
391
+ ## Tests
392
+
393
+ ```bash
394
+ uv run --extra dev pytest
395
+ ```