After story 1
This commit is contained in:
+245
@@ -0,0 +1,245 @@
|
||||
# STORY-08: Active Learning Pipeline
|
||||
|
||||
## Epic
|
||||
**E3: Active Learning** — As an ML engineer, I can fine-tune the classifier head with reviewed data.
|
||||
|
||||
## Related Requirements
|
||||
| ID | Requirement |
|
||||
|----|------|
|
||||
| FR-06 | Active learning pipeline (label ingestion → fine-tuning → deployment): Batch retraining only. No online learning. Versioned model swaps. |
|
||||
| NFR-04 | Determinism & Reproducibility: Config-seeded randomness, versioned models |
|
||||
| NFR-05 | Fault Tolerance: Auto-retry on transient failures; skip & log on fatal errors |
|
||||
| TC-01 | Hardware: 2× Tesla P40 24GB (compute capability 5.2, PCIe 3.0, no Tensor Cores) |
|
||||
| TC-02 | CUDA/Torch Compatibility: CUDA ≤ 11.8, PyTorch ≤ 2.1.0, FP32 inference only |
|
||||
|
||||
## Description
|
||||
Implement the active learning pipeline that ingests annotated review data, fine-tunes the classifier head, validates the candidate model, and deploys it if it meets quality gates. This enables incremental model improvement using human-labeled data from the review queue.
|
||||
|
||||
## Scope
|
||||
|
||||
### In Scope
|
||||
- Label ingestion from review queue (CSV/JSON export or direct DB query)
|
||||
- Dataset versioning and preparation for training
|
||||
- Head-only fine-tuning on face crops (freeze backbone, train classification head)
|
||||
- Training with AdamW, configurable learning rate, early stopping
|
||||
- Candidate model validation on held-out validation set
|
||||
- F1 score and ECE (Expected Calibration Error) calculation
|
||||
- Quality gate validation (ΔF1 > 0.02, ECE < 0.08)
|
||||
- Model registry updates (ACTIVE, CANDIDATE, ARCHIVED states)
|
||||
- Hot reload of new TensorRT engine
|
||||
- Rollback on regression
|
||||
|
||||
### Out of Scope
|
||||
- Frame sampling (covered in STORY-03)
|
||||
- Face detection (covered in STORY-04)
|
||||
- Classification inference (covered in STORY-05)
|
||||
- Review UI (covered in STORY-07)
|
||||
- Monitoring dashboards (covered in STORY-09)
|
||||
|
||||
## Deliverables
|
||||
|
||||
### 8.1 Label Ingestion
|
||||
**File:** `src/active_learning/label_ingestor.py`
|
||||
|
||||
Features:
|
||||
- **Data Source:** Query `review_queue` table for annotated labels
|
||||
```sql
|
||||
SELECT video_id, ground_truth, contributing_frames
|
||||
FROM review_queue
|
||||
WHERE annotated = true AND ground_truth IS NOT NULL
|
||||
```
|
||||
- **Crop Extraction:** Extract face crops from stored paths or re-extract from video
|
||||
- **Dataset Versioning:** Create versioned dataset directory structure
|
||||
```
|
||||
/data/training/v2.0.0/
|
||||
crops/
|
||||
class_0/ (negative samples)
|
||||
class_1/ (positive samples)
|
||||
labels.csv
|
||||
metadata.json
|
||||
```
|
||||
- **Train/Val Split:** 80/20 split (stratified by class)
|
||||
- **Augmentation:** Apply standard augmentations at training time (not pre-computed)
|
||||
- Random horizontal flip
|
||||
- Random color jitter
|
||||
- Random affine transform (±10 degrees rotation, ±10% scale)
|
||||
|
||||
### 8.2 Training Pipeline
|
||||
**File:** `src/active_learning/trainer.py`
|
||||
|
||||
Features:
|
||||
- **Model Loading:** Load pre-trained MobileNetV3 backbone (frozen)
|
||||
```python
|
||||
model = load_pretrained_mobilenetv3()
|
||||
for param in model.features.parameters():
|
||||
param.requires_grad = False
|
||||
# Replace classification head
|
||||
model.classifier = nn.Sequential(
|
||||
nn.Linear(1280, 256),
|
||||
nn.ReLU(),
|
||||
nn.Dropout(0.3),
|
||||
nn.Linear(256, 2) # binary classification
|
||||
)
|
||||
```
|
||||
- **Head-Only Fine-Tuning:** Only train the classification head
|
||||
- Backbone weights are frozen (no gradient updates)
|
||||
- Head weights are trainable
|
||||
- This is faster and requires less data than full fine-tuning
|
||||
- **Optimizer:** AdamW with configurable parameters
|
||||
- Learning rate: 1e-3 (default), configurable
|
||||
- Weight decay: 1e-2
|
||||
- Betas: (0.9, 0.999)
|
||||
- **Loss Function:** Binary Cross-Entropy with class weights (if imbalanced)
|
||||
- **Training Configuration:**
|
||||
- Epochs: 10-30 (configurable)
|
||||
- Batch size: 32
|
||||
- Early stopping: patience=5 epochs (no validation improvement)
|
||||
- Learning rate scheduler: ReduceLROnPlateau (factor=0.5, patience=3)
|
||||
- **Checkpointing:** Save checkpoint to `/models/candidate/` after each epoch
|
||||
```
|
||||
/models/candidate/
|
||||
v2.0.0_epoch_01.pt
|
||||
v2.0.0_epoch_02.pt
|
||||
...
|
||||
v2.0.0_best.pt (best validation F1)
|
||||
```
|
||||
|
||||
### 8.3 Validation Pipeline
|
||||
**File:** `src/active_learning/validator.py`
|
||||
|
||||
Features:
|
||||
- **Validation Set:** Held-out 20% of annotated data (never seen during training)
|
||||
- **Metrics Calculation:**
|
||||
- **F1 Score:** Macro F1 on validation set
|
||||
- **ECE (Expected Calibration Error):**
|
||||
```python
|
||||
def compute_ece(predictions, labels, n_bins=15):
|
||||
bin_boundaries = np.linspace(0, 1, n_bins + 1)
|
||||
ece = 0.0
|
||||
for i in range(n_bins):
|
||||
mask = (predictions >= bin_boundaries[i]) & (predictions < bin_boundaries[i+1])
|
||||
if mask.sum() > 0:
|
||||
bin_confidence = predictions[mask].mean()
|
||||
bin_accuracy = labels[mask].mean()
|
||||
ece += (mask.sum() / len(predictions)) * abs(bin_confidence - bin_accuracy)
|
||||
return ece
|
||||
```
|
||||
- **Accuracy, Precision, Recall:** Standard classification metrics
|
||||
- **Quality Gates:**
|
||||
- ΔF1 > 0.02 (improvement over current model)
|
||||
- ECE < 0.08 (calibration acceptable)
|
||||
- Both gates must pass for deployment
|
||||
|
||||
### 8.4 Model Registry & Deployment
|
||||
**File:** `src/active_learning/registry.py`
|
||||
|
||||
Features:
|
||||
- **Model Registry:** Update DB `models` table
|
||||
```sql
|
||||
-- Promote candidate to active
|
||||
UPDATE models SET status = 'ACTIVE' WHERE version = 'v2.0.0';
|
||||
UPDATE models SET status = 'ARCHIVED' WHERE status = 'ACTIVE' AND version != 'v2.0.0';
|
||||
```
|
||||
- **TensorRT Engine Build:** Convert candidate model to TensorRT engine
|
||||
```bash
|
||||
trtexec --onnx=models/candidate/v2.0.0.onnx \
|
||||
--saveEngine=models/candidate/v2.0.0.trt \
|
||||
--fp32 --maxBatch=32 --workspace=2048
|
||||
```
|
||||
- **Hot Reload:** Signal worker process to reload new engine
|
||||
- Option A: Restart worker container (`docker-compose restart worker`)
|
||||
- Option B: In-process reload (graceful, no downtime)
|
||||
- Default: In-process reload via signal handling
|
||||
- **Rollback:** If new model causes issues, rollback to archived model
|
||||
```sql
|
||||
UPDATE models SET status = 'ACTIVE' WHERE version = 'v1.2.0';
|
||||
UPDATE models SET status = 'ARCHIVED' WHERE version = 'v2.0.0';
|
||||
```
|
||||
|
||||
### 8.5 Training Configuration
|
||||
**File:** `config.yaml` (updates)
|
||||
|
||||
New fields:
|
||||
```yaml
|
||||
active_learning:
|
||||
enabled: true
|
||||
min_annotated_samples: 100 # minimum labeled data to trigger training
|
||||
training:
|
||||
epochs: 20
|
||||
batch_size: 32
|
||||
learning_rate: 1e-3
|
||||
weight_decay: 1e-2
|
||||
early_stopping_patience: 5
|
||||
lr_scheduler: ReduceLROnPlateau
|
||||
lr_factor: 0.5
|
||||
lr_patience: 3
|
||||
validation:
|
||||
val_split: 0.2
|
||||
min_f1_improvement: 0.02
|
||||
max_ece: 0.08
|
||||
deployment:
|
||||
auto_deploy: true # deploy if quality gates pass
|
||||
hot_reload: true
|
||||
rollback_enabled: true
|
||||
augmentation:
|
||||
horizontal_flip: true
|
||||
color_jitter: true
|
||||
affine: true
|
||||
affine_degrees: 10
|
||||
affine_scale: 0.1
|
||||
```
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
### Functional
|
||||
- [ ] Annotated labels are ingested from review_queue table correctly
|
||||
- [ ] Dataset is versioned with proper directory structure
|
||||
- [ ] Train/val split is stratified by class (80/20)
|
||||
- [ ] Data augmentation is applied at training time (not pre-computed)
|
||||
- [ ] Backbone weights are frozen during fine-tuning (verified by checking requires_grad)
|
||||
- [ ] Classification head is trainable and receives gradient updates
|
||||
- [ ] AdamW optimizer is used with correct parameters
|
||||
- [ ] Early stopping works (training stops if no improvement for patience epochs)
|
||||
- [ ] Candidate model is saved to /models/candidate/ with correct versioning
|
||||
- [ ] F1 score is calculated correctly on validation set
|
||||
- [ ] ECE is calculated correctly on validation set
|
||||
- [ ] Quality gates are enforced (ΔF1 > 0.02 AND ECE < 0.08)
|
||||
- [ ] Model registry is updated (ACTIVE, CANDIDATE, ARCHIVED states)
|
||||
- [ ] TensorRT engine is built from candidate model
|
||||
- [ ] Hot reload deploys new model without downtime
|
||||
- [ ] Rollback restores previous model if deployment fails
|
||||
|
||||
### Non-Functional
|
||||
- [ ] Training completes in < 4 hours on Tesla P40 (typical dataset: 1000 samples)
|
||||
- [ ] Validation completes in < 30 minutes
|
||||
- [ ] TensorRT engine build completes in < 10 minutes
|
||||
- [ ] Hot reload completes in < 30 seconds
|
||||
- [ ] Training uses < 18GB GPU memory
|
||||
- [ ] Training is deterministic (same data + same config → same results)
|
||||
|
||||
### Technical Constraints
|
||||
- [ ] CUDA 11.8 compatible (verified via torch.version.cuda)
|
||||
- [ ] PyTorch ≤ 2.1.0 (verified via torch.__version__)
|
||||
- [ ] FP32 training only (no mixed precision)
|
||||
- [ ] No Tensor Cores used (CC 5.2 constraint)
|
||||
- [ ] Model versioning follows semantic versioning (MAJOR.MINOR.PATCH)
|
||||
- [ ] All training hyperparameters are configurable via config.yaml
|
||||
|
||||
## Dependencies
|
||||
- **Prerequisites:** STORY-01 (Foundation), STORY-05 (Classification — provides base model), STORY-07 (Review UI — provides labeled data)
|
||||
- **Depends on:** None (runs independently, triggered by labeled data threshold)
|
||||
- **Enables:** STORY-05 (provides new model for inference)
|
||||
|
||||
## Risks & Mitigations
|
||||
| Risk | Mitigation |
|
||||
|------|--|
|
||||
| Insufficient labeled data for meaningful fine-tuning | Set min_annotated_samples threshold (e.g., 100); wait until reached |
|
||||
| Head-only fine-tuning may not be enough for domain shift | Offer full fine-tuning as option; document limitations |
|
||||
| Model regression in production | Strict quality gates; keep previous model in ARCHIVED state for quick rollback |
|
||||
| Long training times on Tesla P40 | Head-only training is faster; batch size tuning; early stopping |
|
||||
| ECE calculation sensitivity | Use proper binning; report ECI with confidence intervals |
|
||||
|
||||
## Estimated Effort
|
||||
- **Sprint:** 7-8
|
||||
- **Story Points:** 34
|
||||
- **Dependencies:** STORY-01, STORY-05, STORY-07
|
||||
Reference in New Issue
Block a user