Merge branch 'feature/ml-006-training-workflow'
This commit is contained in:
@@ -25,7 +25,7 @@ class TrainingPipeline:
|
|||||||
if not vectors:
|
if not vectors:
|
||||||
raise ValueError("FeatureStore enthält keine Trainingsdaten.")
|
raise ValueError("FeatureStore enthält keine Trainingsdaten.")
|
||||||
|
|
||||||
sensors = tuple({vector.sensor_id for vector in vectors})
|
sensors = tuple(sorted({vector.sensor_id for vector in vectors}))
|
||||||
artifact = TrainedArtifact(artifact_id=artifact_id, supported_sensors=sensors)
|
artifact = TrainedArtifact(artifact_id=artifact_id, supported_sensors=sensors)
|
||||||
self._artifacts[artifact_id] = artifact
|
self._artifacts[artifact_id] = artifact
|
||||||
logger.info("Training abgeschlossen für %s mit %d Sensoren", artifact_id, len(sensors))
|
logger.info("Training abgeschlossen für %s mit %d Sensoren", artifact_id, len(sensors))
|
||||||
|
|||||||
39
docs/ml_training.md
Normal file
39
docs/ml_training.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# ML Training- und Evaluations-Workflow
|
||||||
|
|
||||||
|
Dieser Workflow beschreibt, wie Modelle trainiert, evaluiert und an der Serving-Layer registriert werden.
|
||||||
|
|
||||||
|
## 1. Daten sammeln
|
||||||
|
|
||||||
|
Alle Trainingsvektoren werden über `FeatureStore.add(...)` oder `add_batch(...)` eingepflegt. Jeder Vektor enthält eine Sensor-ID sowie ein Dictionary mit Merkmalen.
|
||||||
|
|
||||||
|
## 2. Modell trainieren
|
||||||
|
|
||||||
|
```python
|
||||||
|
store = FeatureStore()
|
||||||
|
store.add(FeatureVector(sensor_id="sensor.kitchen", values={"temperature": 21.0}))
|
||||||
|
pipeline = TrainingPipeline(store)
|
||||||
|
artifact = pipeline.run("my_artifact")
|
||||||
|
pipeline.export("my_artifact")
|
||||||
|
```
|
||||||
|
|
||||||
|
`TrainingPipeline.run(...)` erzeugt ein `TrainedArtifact` mit den unterstützten Sensor-IDs.
|
||||||
|
|
||||||
|
## 3. Modell evaluieren
|
||||||
|
|
||||||
|
```python
|
||||||
|
evaluator = Evaluator(pipeline)
|
||||||
|
report = evaluator.evaluate(artifact.artifact_id, predictions)
|
||||||
|
```
|
||||||
|
|
||||||
|
Der Report enthält:
|
||||||
|
- `artifact_id`
|
||||||
|
- `sample_size`
|
||||||
|
- Metriken wie `coverage` und `unknown_rate` mit Default-Schwellenwerten
|
||||||
|
|
||||||
|
## 4. Modell registrieren
|
||||||
|
|
||||||
|
Das trainierte Artefakt kann anschließend über `ModelRegistry.register(artifact)` bereitgestellt werden. Die ML-Serving-API stellt es unter `/ml/predict` und `/ml/batch` zur Verfügung.
|
||||||
|
|
||||||
|
## Hinweise
|
||||||
|
- Für reproduzierbare Sensor-Reihenfolgen wird in `TrainingPipeline.run(...)` eine sortierte Sensor-Liste verwendet.
|
||||||
|
- Fehlende Trainingsdaten lösen `ValueError` aus; nicht registrierte Artefakte lösen `KeyError` aus.
|
||||||
33
tests/ml/test_training_evaluation.py
Normal file
33
tests/ml/test_training_evaluation.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from app.ml.evaluation import Evaluator, EvalReport, Metric
|
||||||
|
from app.ml.feature_store import FeatureStore, FeatureVector
|
||||||
|
from app.ml.training import TrainingPipeline
|
||||||
|
|
||||||
|
|
||||||
|
def _vector(sensor_id: str, temperature: float, label: str | None = None) -> FeatureVector:
|
||||||
|
return FeatureVector(sensor_id=sensor_id, values={"temperature": temperature}, label=label)
|
||||||
|
|
||||||
|
|
||||||
|
def test_end_to_end_training_then_evaluation() -> None:
|
||||||
|
store = FeatureStore()
|
||||||
|
store.add_batch([_vector("sensor.kitchen", 19.0), _vector("sensor.bedroom", 18.5)])
|
||||||
|
pipeline = TrainingPipeline(store)
|
||||||
|
artifact = pipeline.run("artifact_v1")
|
||||||
|
|
||||||
|
evaluator = Evaluator(pipeline)
|
||||||
|
predictions = [
|
||||||
|
"artifact_v1:sensor.kitchen:{'temperature': 21.0}",
|
||||||
|
"artifact_v1:sensor.bedroom:{'temperature': 18.5}",
|
||||||
|
]
|
||||||
|
report = evaluator.evaluate(artifact.artifact_id, predictions)
|
||||||
|
assert isinstance(report, EvalReport)
|
||||||
|
assert report.sample_size == len(predictions)
|
||||||
|
assert any(metric.name == "coverage" for metric in report.metrics)
|
||||||
|
|
||||||
|
|
||||||
|
def test_metric_helpers_are_serializable() -> None:
|
||||||
|
metric = Metric(name="coverage", value=0.85, threshold=0.8)
|
||||||
|
assert metric.name == "coverage"
|
||||||
|
assert metric.value == 0.85
|
||||||
|
assert metric.threshold == 0.8
|
||||||
Reference in New Issue
Block a user