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.
- cliving-0.1.0/.gitignore +6 -0
- cliving-0.1.0/LICENSE +21 -0
- cliving-0.1.0/PKG-INFO +395 -0
- cliving-0.1.0/README.md +362 -0
- cliving-0.1.0/pyproject.toml +66 -0
- cliving-0.1.0/src/cliving/__init__.py +13 -0
- cliving-0.1.0/src/cliving/memory.py +198 -0
- cliving-0.1.0/src/cliving/model.py +212 -0
- cliving-0.1.0/src/cliving/prototype.py +149 -0
- cliving-0.1.0/src/cliving/schemas.py +75 -0
- cliving-0.1.0/src/cliving/utils.py +44 -0
cliving-0.1.0/.gitignore
ADDED
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
|
+
```
|