bae705aa97
- Add NFC ePassport roadmap (ICAO 9303, eIDAS) - Add TensorFlow.js edge face detection (BlazeFace) - Add structured audit logger (GDPR-compliant) - Risk scoring support Part of KYC Apple Native UX v1.1.0
316 lines
11 KiB
Python
Executable File
316 lines
11 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
QUIXZOOM Active Learning Pipeline
|
|
|
|
Självförbättrande kretslopp:
|
|
Rådata → Weak Supervision → Human Review → Gold Dataset → Train → Evaluate → Deploy → Inference → Ny data → Active Learning
|
|
|
|
Mål: 10 000 videosekvenser, 100 000 bilder, 1 miljon objektobservationer, 100 000 verifierade annotationer
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import subprocess
|
|
from pathlib import Path
|
|
from datetime import datetime
|
|
from typing import List, Dict, Any
|
|
|
|
class ActiveLearningPipeline:
|
|
def __init__(self, config_path: str = "pipeline-config.json"):
|
|
self.config = self.load_config(config_path)
|
|
self.metrics = {
|
|
"raw_images": 0,
|
|
"weak_annotations": 0,
|
|
"human_reviewed": 0,
|
|
"gold_annotations": 0,
|
|
"training_iterations": 0,
|
|
"model_performance": {},
|
|
}
|
|
|
|
def load_config(self, path: str) -> Dict:
|
|
"""Ladda pipeline-konfiguration"""
|
|
default_config = {
|
|
"data_paths": {
|
|
"raw": "data/raw",
|
|
"weak": "data/weak",
|
|
"review": "data/review",
|
|
"gold": "data/gold",
|
|
"models": "models",
|
|
},
|
|
"thresholds": {
|
|
"min_confidence_for_auto": 0.85,
|
|
"min_human_review_batch": 100,
|
|
"min_gold_for_training": 1000,
|
|
"target_performance": 0.90,
|
|
},
|
|
"training": {
|
|
"base_model": "yolov8n.pt",
|
|
"epochs": 100,
|
|
"batch_size": 16,
|
|
"img_size": 640,
|
|
},
|
|
"active_learning": {
|
|
"uncertainty_sampling": True,
|
|
"diversity_sampling": True,
|
|
"min_uncertainty_threshold": 0.3,
|
|
},
|
|
}
|
|
|
|
if os.path.exists(path):
|
|
with open(path) as f:
|
|
return {**default_config, **json.load(f)}
|
|
return default_config
|
|
|
|
def run_pipeline(self):
|
|
"""Kör hela pipelinen"""
|
|
print("=" * 60)
|
|
print("QUIXZOOM Active Learning Pipeline")
|
|
print("=" * 60)
|
|
print(f"Start: {datetime.now().isoformat()}")
|
|
print()
|
|
|
|
# Steg 1: Samla rådata
|
|
self.step_collect_raw_data()
|
|
|
|
# Steg 2: Weak supervision
|
|
self.step_weak_supervision()
|
|
|
|
# Steg 3: Human review
|
|
self.step_human_review()
|
|
|
|
# Steg 4: Träna modell
|
|
if self.should_train():
|
|
self.step_train_model()
|
|
|
|
# Steg 5: Utvärdera
|
|
self.step_evaluate()
|
|
|
|
# Steg 6: Deploy
|
|
self.step_deploy()
|
|
|
|
# Steg 7: Active learning
|
|
self.step_active_learning()
|
|
|
|
print()
|
|
print("=" * 60)
|
|
print(f"Pipeline complete: {datetime.now().isoformat()}")
|
|
print(f"Metrics: {json.dumps(self.metrics, indent=2)}")
|
|
print("=" * 60)
|
|
|
|
def step_collect_raw_data(self):
|
|
"""Steg 1: Samla rådata från Zoomers"""
|
|
print("[Step 1] Collecting raw data...")
|
|
|
|
# Räkna bilder i raw-katalog
|
|
raw_path = Path(self.config["data_paths"]["raw"])
|
|
if raw_path.exists():
|
|
images = list(raw_path.glob("**/*.jpg")) + list(raw_path.glob("**/*.png"))
|
|
self.metrics["raw_images"] = len(images)
|
|
print(f" Found {len(images)} raw images")
|
|
else:
|
|
print(f" Creating {raw_path}")
|
|
raw_path.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Simulera: Ladda nya videor från Bangkok/Torrevieja
|
|
print(" Loading Bangkok frames...")
|
|
print(" Loading Torrevieja data...")
|
|
print(f" Total raw images: {self.metrics['raw_images']}")
|
|
print()
|
|
|
|
def step_weak_supervision(self):
|
|
"""Steg 2: Kör weak supervision på rådata"""
|
|
print("[Step 2] Running weak supervision...")
|
|
|
|
# Simulera: Kör YOLO + Grounding DINO + SAM2
|
|
print(" Running YOLOv8...")
|
|
print(" Running Grounding DINO...")
|
|
print(" Running SAM2...")
|
|
print(" Consolidating proposals...")
|
|
|
|
# Beräkna förslag
|
|
raw_count = self.metrics["raw_images"]
|
|
proposals = int(raw_count * 0.8) # ~80% av bilder har detektioner
|
|
self.metrics["weak_annotations"] = proposals
|
|
|
|
print(f" Generated {proposals} weak annotations")
|
|
print(f" High confidence (>0.85): {int(proposals * 0.3)}")
|
|
print(f" Medium confidence (0.6-0.85): {int(proposals * 0.5)}")
|
|
print(f" Low confidence (<0.6): {int(proposals * 0.2)}")
|
|
print()
|
|
|
|
def step_human_review(self):
|
|
"""Steg 3: Human-in-the-loop review"""
|
|
print("[Step 3] Human review...")
|
|
|
|
# Auto-godkänn högt confidence
|
|
auto_approved = int(self.metrics["weak_annotations"] * 0.3)
|
|
|
|
# Mänsklig granskning av resten
|
|
needs_review = self.metrics["weak_annotations"] - auto_approved
|
|
|
|
print(f" Auto-approved: {auto_approved}")
|
|
print(f" Needs human review: {needs_review}")
|
|
print(f" Estimated review time: {needs_review * 15 / 60:.1f} hours")
|
|
|
|
# Simulera: Granskningsresultat
|
|
reviewed = int(needs_review * 0.8) # 80% granskade
|
|
approved = int(reviewed * 0.7) # 70% godkända
|
|
corrected = int(reviewed * 0.2) # 20% korrigerade
|
|
rejected = reviewed - approved - corrected # 10% avvisade
|
|
|
|
self.metrics["human_reviewed"] = auto_approved + reviewed
|
|
self.metrics["gold_annotations"] = auto_approved + approved + corrected
|
|
|
|
print(f" Reviewed: {reviewed}")
|
|
print(f" Approved: {approved}")
|
|
print(f" Corrected: {corrected}")
|
|
print(f" Rejected: {rejected}")
|
|
print(f" Gold dataset: {self.metrics['gold_annotations']}")
|
|
print()
|
|
|
|
def should_train(self) -> bool:
|
|
"""Avgör om det är dags att träna"""
|
|
min_gold = self.config["thresholds"]["min_gold_for_training"]
|
|
return self.metrics["gold_annotations"] >= min_gold
|
|
|
|
def step_train_model(self):
|
|
"""Steg 4: Träna YOLO-modell"""
|
|
print("[Step 4] Training model...")
|
|
|
|
config = self.config["training"]
|
|
|
|
print(f" Base model: {config['base_model']}")
|
|
print(f" Epochs: {config['epochs']}")
|
|
print(f" Batch size: {config['batch_size']}")
|
|
print(f" Image size: {config['img_size']}")
|
|
|
|
# Simulera träning
|
|
print(" Preparing dataset...")
|
|
print(" Training...")
|
|
print(" [Epoch 10/100] mAP@50: 0.45")
|
|
print(" [Epoch 50/100] mAP@50: 0.72")
|
|
print(" [Epoch 100/100] mAP@50: 0.85")
|
|
|
|
self.metrics["training_iterations"] += 1
|
|
self.metrics["model_performance"]["mAP50"] = 0.85
|
|
self.metrics["model_performance"]["mAP50-95"] = 0.62
|
|
|
|
print(f" Training complete!")
|
|
print(f" mAP@50: 0.85")
|
|
print(f" mAP@50-95: 0.62")
|
|
print()
|
|
|
|
def step_evaluate(self):
|
|
"""Steg 5: Utvärdera modell"""
|
|
print("[Step 5] Evaluating model...")
|
|
|
|
target = self.config["thresholds"]["target_performance"]
|
|
current = self.metrics["model_performance"].get("mAP50", 0)
|
|
|
|
print(f" Target mAP@50: {target}")
|
|
print(f" Current mAP@50: {current}")
|
|
|
|
if current >= target:
|
|
print(" Model meets target performance!")
|
|
else:
|
|
print(f" Model needs improvement ({target - current:.2f} below target)")
|
|
print(" Need more training data...")
|
|
|
|
print()
|
|
|
|
def step_deploy(self):
|
|
"""Steg 6: Deploy modell till produktion"""
|
|
print("[Step 6] Deploying model...")
|
|
|
|
if self.metrics["model_performance"].get("mAP50", 0) >= self.config["thresholds"]["target_performance"]:
|
|
print(" Exporting to ONNX...")
|
|
print(" Exporting to TensorRT...")
|
|
print(" Deploying to inference server...")
|
|
print(" Model deployed successfully!")
|
|
else:
|
|
print(" Model performance below threshold, skipping deploy")
|
|
|
|
print()
|
|
|
|
def step_active_learning(self):
|
|
"""Steg 7: Active learning - välj nästa data att annotera"""
|
|
print("[Step 7] Active learning...")
|
|
|
|
# Uncertainty sampling: Välj bilder där modellen är osäker
|
|
# Diversity sampling: Välj bilder som är olika från tidigare
|
|
|
|
print(" Analyzing model uncertainty...")
|
|
print(" Finding diverse samples...")
|
|
|
|
# Simulera: Välj nästa batch
|
|
next_batch = 500
|
|
print(f" Next batch size: {next_batch}")
|
|
print(f" Priority: High uncertainty + diverse")
|
|
print(f" Estimated improvement: +5% mAP")
|
|
|
|
print()
|
|
print("=" * 60)
|
|
print("Next iteration ready!")
|
|
print("=" * 60)
|
|
|
|
def generate_report(self) -> str:
|
|
"""Generera pipeline-rapport"""
|
|
report = f"""
|
|
# QUIXZOOM Active Learning Report
|
|
|
|
Generated: {datetime.now().isoformat()}
|
|
|
|
## Metrics
|
|
|
|
| Metric | Value | Target |
|
|
|--------|-------|--------|
|
|
| Raw images | {self.metrics['raw_images']} | 100,000 |
|
|
| Weak annotations | {self.metrics['weak_annotations']} | 1,000,000 |
|
|
| Human reviewed | {self.metrics['human_reviewed']} | 100,000 |
|
|
| Gold annotations | {self.metrics['gold_annotations']} | 100,000 |
|
|
| Training iterations | {self.metrics['training_iterations']} | - |
|
|
| mAP@50 | {self.metrics['model_performance'].get('mAP50', 0):.2f} | 0.90 |
|
|
| mAP@50-95 | {self.metrics['model_performance'].get('mAP50-95', 0):.2f} | 0.70 |
|
|
|
|
## Pipeline Status
|
|
|
|
- Raw data collection: {'OK' if self.metrics['raw_images'] > 0 else 'NEEDS DATA'}
|
|
- Weak supervision: {'OK' if self.metrics['weak_annotations'] > 0 else 'NEEDS DATA'}
|
|
- Human review: {'OK' if self.metrics['human_reviewed'] > 0 else 'NEEDS REVIEW'}
|
|
- Gold dataset: {'OK' if self.metrics['gold_annotations'] >= self.config['thresholds']['min_gold_for_training'] else 'NEEDS MORE'}
|
|
- Model training: {'OK' if self.metrics['training_iterations'] > 0 else 'NOT STARTED'}
|
|
- Deployment: {'OK' if self.metrics['model_performance'].get('mAP50', 0) >= self.config['thresholds']['target_performance'] else 'NEEDS IMPROVEMENT'}
|
|
|
|
## Next Steps
|
|
|
|
1. Collect more raw data from Bangkok and Torrevieja
|
|
2. Run weak supervision on new data
|
|
3. Prioritize human review of uncertain predictions
|
|
4. Train model when gold dataset reaches {self.config['thresholds']['min_gold_for_training']}
|
|
5. Deploy when mAP@50 reaches {self.config['thresholds']['target_performance']}
|
|
"""
|
|
return report
|
|
|
|
|
|
def main():
|
|
"""Huvudfunktion"""
|
|
pipeline = ActiveLearningPipeline()
|
|
|
|
# Kör pipeline
|
|
pipeline.run_pipeline()
|
|
|
|
# Generera rapport
|
|
report = pipeline.generate_report()
|
|
|
|
# Spara rapport
|
|
report_path = "pipeline-report.md"
|
|
with open(report_path, "w") as f:
|
|
f.write(report)
|
|
|
|
print(f"\nReport saved to {report_path}")
|
|
print("\n" + report)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|