Skip to content

Commit db8496a

Browse files
feat: implement active learning pipeline (#40)
- Add 6 query strategies (LeastConfidence, Margin, Entropy, CoreSet, BADGE, CommitteeDisagreement) - Add AnnotationQueue with push/pop_batch/annotate/export_labelled, atomic writes, HMAC integrity, 0o600 permissions - Add IncrementalTrainer with warm-start / full-retrain paths, AUC-based rollback with SHA-256 verification - Add scripts/annotate.py interactive CLI annotation loop - Add scripts/run_active_learning.py integration loop (weekly via GitHub Actions) - Add .github/workflows/active_learning.yml (every Monday 08:00 UTC) - Add docs/active_learning.md: strategy comparison, annotation workflow, update policy - Update config.py with AL_QUERY_STRATEGY, AL_BATCH_SIZE, AL_RETRAIN_THRESHOLD, AL_ROLLBACK_AUC_DROP, AL_QUEUE_PATH - Update .env.example and README.md with active learning section - Add tests/test_query_strategies.py (23 tests), test_annotation_queue.py (22 tests), test_incremental_trainer.py (7 tests) - All 116 tests pass, make lint clean Closes #40
1 parent 1e8c332 commit db8496a

13 files changed

Lines changed: 1911 additions & 35 deletions

.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ POISON_LABEL_RATIO_THRESHOLD=0.15
6363
# WARNING: Never commit this value to version control.
6464
ANNOTATION_HMAC_SECRET=
6565

66+
ANNOTATION_HMAC_SECRET=
67+
6668
# ---------------------------------------------------------------------------
6769
# Adversarial training (disabled by default)
6870
# ---------------------------------------------------------------------------
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
name: Active Learning Pipeline
2+
3+
on:
4+
schedule:
5+
# Run every Monday at 08:00 UTC
6+
- cron: "0 8 * * 1"
7+
workflow_dispatch:
8+
inputs:
9+
strategy:
10+
description: "Query strategy"
11+
default: "committee_disagreement"
12+
required: false
13+
batch_size:
14+
description: "Number of wallets to select"
15+
default: "20"
16+
required: false
17+
pool_path:
18+
description: "Path to unscored wallet parquet"
19+
default: "data/unscored_wallets.parquet"
20+
required: false
21+
22+
jobs:
23+
active-learning:
24+
runs-on: ubuntu-latest
25+
26+
steps:
27+
- uses: actions/checkout@v4
28+
29+
- name: Set up Python 3.11
30+
uses: actions/setup-python@v5
31+
with:
32+
python-version: "3.11"
33+
cache: pip
34+
35+
- name: Install dependencies
36+
run: pip install -r requirements.txt
37+
38+
- name: Run active learning loop
39+
env:
40+
AL_QUERY_STRATEGY: ${{ github.event.inputs.strategy || 'committee_disagreement' }}
41+
AL_BATCH_SIZE: ${{ github.event.inputs.batch_size || '20' }}
42+
AL_QUEUE_PATH: data/annotation_queue.json
43+
ANNOTATION_HMAC_SECRET: ${{ secrets.ANNOTATION_HMAC_SECRET }}
44+
MODEL_DIR: ${{ vars.MODEL_DIR || './models' }}
45+
run: |
46+
python -m scripts.run_active_learning \
47+
--pool "${{ github.event.inputs.pool_path || 'data/unscored_wallets.parquet' }}" \
48+
--strategy "$AL_QUERY_STRATEGY" \
49+
--batch-size "$AL_BATCH_SIZE" \
50+
--queue "$AL_QUEUE_PATH"
51+
52+
- name: Upload annotation queue artifact
53+
uses: actions/upload-artifact@v4
54+
with:
55+
name: annotation-queue-${{ github.run_id }}
56+
path: data/annotation_queue.json
57+
retention-days: 30

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,36 @@ See [`scripts/README.md`](scripts/README.md) for detailed usage of:
328328
- `retrain_if_drifted.py` — automated drift detection and retraining trigger
329329
- `list_model_versions.py` — list archived models with training dates and metrics
330330

331+
## Active Learning
332+
333+
LedgerLens includes an active learning pipeline that intelligently selects the most informative
334+
wallets for analyst annotation, minimising labelling effort while maximising model improvement.
335+
336+
```bash
337+
# Populate the annotation queue (selects 20 wallets by committee disagreement):
338+
python -m scripts.run_active_learning --pool data/unscored_wallets.parquet
339+
340+
# Annotate wallets interactively:
341+
python -m scripts.annotate --annotator-id yourname
342+
343+
# Export annotations and update models:
344+
python -m scripts.annotate --export data/annotated.parquet
345+
python -m scripts.run_active_learning \
346+
--pool data/unscored_wallets.parquet \
347+
--update data/annotated.parquet
348+
```
349+
350+
The pipeline runs automatically every Monday at 08:00 UTC via
351+
`.github/workflows/active_learning.yml`. See [`docs/active_learning.md`](docs/active_learning.md)
352+
for the full query strategy comparison, annotation workflow, and incremental update policy.
353+
354+
| Variable | Default | Description |
355+
|---|---|---|
356+
| `AL_QUERY_STRATEGY` | `committee_disagreement` | Query strategy |
357+
| `AL_BATCH_SIZE` | `20` | Wallets selected per run |
358+
| `AL_RETRAIN_THRESHOLD` | `50` | Min labels for full retrain |
359+
| `AL_ROLLBACK_AUC_DROP` | `0.01` | Max AUC drop before rollback |
360+
331361
## Development
332362

333363
```bash
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
1+
"""Active learning package for LedgerLens."""
12

3+
from detection.active_learning.annotation_queue import AnnotationQueue
4+
from detection.active_learning.incremental_trainer import IncrementalTrainer
5+
from detection.active_learning.query_strategies import STRATEGY_REGISTRY, get_strategy
6+
7+
__all__ = ["AnnotationQueue", "IncrementalTrainer", "STRATEGY_REGISTRY", "get_strategy"]

0 commit comments

Comments
 (0)