modelzone-sdk 0.2.0.dev1__tar.gz → 0.3.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.
- {modelzone_sdk-0.2.0.dev1 → modelzone_sdk-0.3.0}/PKG-INFO +12 -9
- {modelzone_sdk-0.2.0.dev1 → modelzone_sdk-0.3.0}/README.md +8 -8
- modelzone_sdk-0.3.0/modelzone/__init__.py +19 -0
- modelzone_sdk-0.3.0/modelzone/azureml/__init__.py +27 -0
- {modelzone_sdk-0.2.0.dev1 → modelzone_sdk-0.3.0}/modelzone/azureml/backend.py +38 -64
- {modelzone_sdk-0.2.0.dev1 → modelzone_sdk-0.3.0}/modelzone/cli.py +51 -80
- modelzone_sdk-0.3.0/modelzone/core/__init__.py +8 -0
- modelzone_sdk-0.3.0/modelzone/core/constants.py +9 -0
- {modelzone_sdk-0.2.0.dev1 → modelzone_sdk-0.3.0}/modelzone/core/model.py +12 -14
- modelzone_sdk-0.3.0/modelzone/core/model_artifact.py +63 -0
- modelzone_sdk-0.3.0/modelzone/core/predict_context.py +41 -0
- modelzone_sdk-0.2.0.dev1/modelzone/training/context.py → modelzone_sdk-0.3.0/modelzone/core/training_context.py +25 -20
- {modelzone_sdk-0.2.0.dev1 → modelzone_sdk-0.3.0}/modelzone/core/training_result.py +3 -7
- modelzone_sdk-0.3.0/modelzone/project_config.py +73 -0
- modelzone_sdk-0.3.0/modelzone/training/__init__.py +19 -0
- {modelzone_sdk-0.2.0.dev1 → modelzone_sdk-0.3.0}/modelzone/training/backend.py +23 -68
- {modelzone_sdk-0.2.0.dev1 → modelzone_sdk-0.3.0}/pyproject.toml +13 -19
- modelzone_sdk-0.2.0.dev1/modelzone/__init__.py +0 -27
- modelzone_sdk-0.2.0.dev1/modelzone/azureml/__init__.py +0 -35
- modelzone_sdk-0.2.0.dev1/modelzone/core/__init__.py +0 -20
- modelzone_sdk-0.2.0.dev1/modelzone/core/constants.py +0 -6
- modelzone_sdk-0.2.0.dev1/modelzone/core/predict_context.py +0 -47
- modelzone_sdk-0.2.0.dev1/modelzone/core/record.py +0 -37
- modelzone_sdk-0.2.0.dev1/modelzone/core/trained_model.py +0 -86
- modelzone_sdk-0.2.0.dev1/modelzone/predict.py +0 -41
- modelzone_sdk-0.2.0.dev1/modelzone/training/__init__.py +0 -32
- {modelzone_sdk-0.2.0.dev1 → modelzone_sdk-0.3.0}/modelzone/core/protocols.py +0 -0
- {modelzone_sdk-0.2.0.dev1 → modelzone_sdk-0.3.0}/modelzone/training/db.py +0 -0
- {modelzone_sdk-0.2.0.dev1 → modelzone_sdk-0.3.0}/modelzone/training/delta_client.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modelzone-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Modelzone SDK – a slim model training and serving toolkit
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Author: Team Enigma
|
|
@@ -14,8 +14,11 @@ Provides-Extra: azureml
|
|
|
14
14
|
Provides-Extra: training
|
|
15
15
|
Requires-Dist: azure-ai-ml ; extra == "azureml"
|
|
16
16
|
Requires-Dist: azure-identity ; extra == "azureml"
|
|
17
|
+
Requires-Dist: deltalake (>=1) ; extra == "azureml"
|
|
17
18
|
Requires-Dist: deltalake (>=1) ; extra == "training"
|
|
18
19
|
Requires-Dist: mlflow (>=3) ; extra == "azureml"
|
|
20
|
+
Requires-Dist: pandas (<3) ; extra == "azureml"
|
|
21
|
+
Requires-Dist: pandas (<3) ; extra == "training"
|
|
19
22
|
Description-Content-Type: text/markdown
|
|
20
23
|
|
|
21
24
|
# Modelzone SDK
|
|
@@ -42,22 +45,22 @@ pip install modelzone-sdk[azureml]
|
|
|
42
45
|
|
|
43
46
|
## Quick start
|
|
44
47
|
|
|
45
|
-
Define a model by subclassing `
|
|
48
|
+
Define a model by subclassing `ModelDefinition` and implementing `train` and `predict`:
|
|
46
49
|
|
|
47
50
|
```python
|
|
48
|
-
from modelzone.core import
|
|
51
|
+
from modelzone.core import ModelDefinition, PredictContext, ModelArtifact
|
|
49
52
|
from modelzone.training import TrainingContext
|
|
50
53
|
|
|
51
54
|
|
|
52
|
-
class MyModel(
|
|
53
|
-
def train(self, ctx: TrainingContext) ->
|
|
55
|
+
class MyModel(ModelDefinition):
|
|
56
|
+
def train(self, ctx: TrainingContext) -> ModelArtifact:
|
|
54
57
|
ctx.print("Training started")
|
|
55
58
|
|
|
56
59
|
# … your training logic …
|
|
57
60
|
fitted = train_something(seed=ctx.seed)
|
|
58
61
|
|
|
59
62
|
ctx.log_metric("accuracy", 0.95)
|
|
60
|
-
return
|
|
63
|
+
return ModelArtifact(model=fitted, features=["feature_a", "feature_b"])
|
|
61
64
|
|
|
62
65
|
def predict(self, ctx: PredictContext):
|
|
63
66
|
df = ctx.db.query("input_table", ctx.time_interval)
|
|
@@ -91,10 +94,10 @@ result = backend.run(MyModel(), seed=42)
|
|
|
91
94
|
### Loading a trained model for prediction
|
|
92
95
|
|
|
93
96
|
```python
|
|
94
|
-
from modelzone.predict import
|
|
97
|
+
from modelzone.predict import load_model_artifact
|
|
95
98
|
|
|
96
|
-
|
|
97
|
-
print(
|
|
99
|
+
model_artifact = load_model_artifact(".model")
|
|
100
|
+
print(model_artifact.features)
|
|
98
101
|
```
|
|
99
102
|
|
|
100
103
|
## CLI
|
|
@@ -22,22 +22,22 @@ pip install modelzone-sdk[azureml]
|
|
|
22
22
|
|
|
23
23
|
## Quick start
|
|
24
24
|
|
|
25
|
-
Define a model by subclassing `
|
|
25
|
+
Define a model by subclassing `ModelDefinition` and implementing `train` and `predict`:
|
|
26
26
|
|
|
27
27
|
```python
|
|
28
|
-
from modelzone.core import
|
|
28
|
+
from modelzone.core import ModelDefinition, PredictContext, ModelArtifact
|
|
29
29
|
from modelzone.training import TrainingContext
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
class MyModel(
|
|
33
|
-
def train(self, ctx: TrainingContext) ->
|
|
32
|
+
class MyModel(ModelDefinition):
|
|
33
|
+
def train(self, ctx: TrainingContext) -> ModelArtifact:
|
|
34
34
|
ctx.print("Training started")
|
|
35
35
|
|
|
36
36
|
# … your training logic …
|
|
37
37
|
fitted = train_something(seed=ctx.seed)
|
|
38
38
|
|
|
39
39
|
ctx.log_metric("accuracy", 0.95)
|
|
40
|
-
return
|
|
40
|
+
return ModelArtifact(model=fitted, features=["feature_a", "feature_b"])
|
|
41
41
|
|
|
42
42
|
def predict(self, ctx: PredictContext):
|
|
43
43
|
df = ctx.db.query("input_table", ctx.time_interval)
|
|
@@ -71,10 +71,10 @@ result = backend.run(MyModel(), seed=42)
|
|
|
71
71
|
### Loading a trained model for prediction
|
|
72
72
|
|
|
73
73
|
```python
|
|
74
|
-
from modelzone.predict import
|
|
74
|
+
from modelzone.predict import load_model_artifact
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
print(
|
|
76
|
+
model_artifact = load_model_artifact(".model")
|
|
77
|
+
print(model_artifact.features)
|
|
78
78
|
```
|
|
79
79
|
|
|
80
80
|
## CLI
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Modelzone SDK – structured model training and serving.
|
|
2
|
+
|
|
3
|
+
Subpackages
|
|
4
|
+
-----------
|
|
5
|
+
- :mod:`modelzone.core` – ``ModelDefinition``, ``ModelArtifact``,
|
|
6
|
+
``TrainingResult`` and friends (zero extras, safe everywhere).
|
|
7
|
+
- :mod:`modelzone.training` – ``LocalBackend``, ``TrainingContext``,
|
|
8
|
+
``ModelzoneDatabase`` (training environment).
|
|
9
|
+
- :mod:`modelzone.azureml` – ``AzureMLBackend`` (requires ``azureml``
|
|
10
|
+
extra).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from modelzone import project_config # noqa: F401
|
|
14
|
+
from modelzone.core import ( # noqa: F401
|
|
15
|
+
ModelArtifact,
|
|
16
|
+
ModelDefinition,
|
|
17
|
+
PredictContext,
|
|
18
|
+
TrainingResult,
|
|
19
|
+
)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Modelzone AzureML backend – tracked experiment runs (SDK v2 + MLflow).
|
|
2
|
+
|
|
3
|
+
Requires ``azure-ai-ml``, ``azure-identity``, and ``mlflow``.
|
|
4
|
+
Install with::
|
|
5
|
+
|
|
6
|
+
pip install modelzone-sdk[azureml]
|
|
7
|
+
|
|
8
|
+
Usage::
|
|
9
|
+
|
|
10
|
+
from modelzone.azureml import AzureMLBackend
|
|
11
|
+
|
|
12
|
+
backend = AzureMLBackend(
|
|
13
|
+
subscription_id="...",
|
|
14
|
+
resource_group="...",
|
|
15
|
+
workspace_name="...",
|
|
16
|
+
experiment_name="my_experiment",
|
|
17
|
+
)
|
|
18
|
+
result = backend.run(model, seed=42, db=db)
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
from modelzone.azureml.backend import AzureMLBackend # noqa: F401
|
|
23
|
+
except ImportError as e:
|
|
24
|
+
raise ImportError(
|
|
25
|
+
"AzureMLBackend requires the 'azureml' extra. "
|
|
26
|
+
"Install it with: pip install modelzone-sdk[azureml]"
|
|
27
|
+
) from e
|
|
@@ -19,11 +19,15 @@ from typing import Any
|
|
|
19
19
|
|
|
20
20
|
import requests
|
|
21
21
|
|
|
22
|
+
from modelzone.core import (
|
|
23
|
+
ModelArtifact,
|
|
24
|
+
ModelDefinition,
|
|
25
|
+
TrainingContext,
|
|
26
|
+
TrainingResult,
|
|
27
|
+
)
|
|
28
|
+
from modelzone.core.constants import TRAINED_MODEL_FILENAME
|
|
22
29
|
from modelzone.core.protocols import Database
|
|
23
|
-
|
|
24
|
-
from modelzone.core.constants import RECORD_FILENAME, TRAINED_MODEL_FILENAME
|
|
25
|
-
from modelzone.core import Model, RunRecord, TrainedModel, TrainingResult
|
|
26
|
-
from modelzone.training import Backend, TrainingContext
|
|
30
|
+
from modelzone.training import Backend
|
|
27
31
|
|
|
28
32
|
try:
|
|
29
33
|
import mlflow
|
|
@@ -85,20 +89,12 @@ class AzureMLBackend(Backend):
|
|
|
85
89
|
self.resource_group = resource_group
|
|
86
90
|
self.workspace_name = workspace_name
|
|
87
91
|
self.experiment_name = experiment_name
|
|
88
|
-
self.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
credential = DefaultAzureCredential()
|
|
95
|
-
self._ml_client = MLClient(
|
|
96
|
-
credential=credential,
|
|
97
|
-
subscription_id=self.subscription_id,
|
|
98
|
-
resource_group_name=self.resource_group,
|
|
99
|
-
workspace_name=self.workspace_name,
|
|
100
|
-
)
|
|
101
|
-
return self._ml_client
|
|
92
|
+
self.ml_client = MLClient(
|
|
93
|
+
credential=DefaultAzureCredential(),
|
|
94
|
+
subscription_id=subscription_id,
|
|
95
|
+
resource_group_name=resource_group,
|
|
96
|
+
workspace_name=workspace_name,
|
|
97
|
+
)
|
|
102
98
|
|
|
103
99
|
def _configure_mlflow(self) -> None:
|
|
104
100
|
"""Point MLflow at the AzureML workspace tracking URI.
|
|
@@ -241,7 +237,7 @@ class AzureMLBackend(Backend):
|
|
|
241
237
|
|
|
242
238
|
def run(
|
|
243
239
|
self,
|
|
244
|
-
model:
|
|
240
|
+
model: ModelDefinition,
|
|
245
241
|
*,
|
|
246
242
|
seed: int | None = None,
|
|
247
243
|
params: dict[str, Any] | None = None,
|
|
@@ -284,8 +280,7 @@ class AzureMLBackend(Backend):
|
|
|
284
280
|
print(f"\n AzureML run: {portal_url}")
|
|
285
281
|
|
|
286
282
|
return TrainingResult(
|
|
287
|
-
|
|
288
|
-
record=result.record,
|
|
283
|
+
model_artifact=result.model_artifact,
|
|
289
284
|
run_id=run_id,
|
|
290
285
|
)
|
|
291
286
|
|
|
@@ -329,16 +324,12 @@ class AzureMLBackend(Backend):
|
|
|
329
324
|
)
|
|
330
325
|
|
|
331
326
|
with open(os.path.join(local_path, TRAINED_MODEL_FILENAME), "rb") as f:
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
with open(os.path.join(local_path, RECORD_FILENAME), "rb") as f:
|
|
335
|
-
record: RunRecord = pickle.load(f) # noqa: S301
|
|
327
|
+
model_artifact: ModelArtifact = pickle.load(f) # noqa: S301
|
|
336
328
|
|
|
337
329
|
shutil.rmtree(download_dir)
|
|
338
330
|
|
|
339
331
|
return TrainingResult(
|
|
340
|
-
|
|
341
|
-
record=record,
|
|
332
|
+
model_artifact=model_artifact,
|
|
342
333
|
run_id=run_id,
|
|
343
334
|
)
|
|
344
335
|
|
|
@@ -349,7 +340,7 @@ class AzureMLBackend(Backend):
|
|
|
349
340
|
def register(
|
|
350
341
|
self,
|
|
351
342
|
run_id: str,
|
|
352
|
-
|
|
343
|
+
registered_name: str,
|
|
353
344
|
*,
|
|
354
345
|
description: str | None = None,
|
|
355
346
|
tags: dict[str, str] | None = None,
|
|
@@ -366,7 +357,7 @@ class AzureMLBackend(Backend):
|
|
|
366
357
|
Args:
|
|
367
358
|
run_id: The MLflow run ID whose ``run_output`` artifacts
|
|
368
359
|
will back the registered model.
|
|
369
|
-
|
|
360
|
+
registered_name: Name for the model in the AzureML Model Registry.
|
|
370
361
|
description: Optional human-readable description.
|
|
371
362
|
tags: Optional key/value tags stored on the model version.
|
|
372
363
|
|
|
@@ -380,14 +371,14 @@ class AzureMLBackend(Backend):
|
|
|
380
371
|
merged_tags.update(tags)
|
|
381
372
|
|
|
382
373
|
try:
|
|
383
|
-
existing = self.ml_client.models.list(name=
|
|
374
|
+
existing = self.ml_client.models.list(name=registered_name)
|
|
384
375
|
versions = [int(m.version) for m in existing if m.version.isdigit()]
|
|
385
376
|
except Exception:
|
|
386
377
|
versions = []
|
|
387
378
|
version = str(max(versions) + 1) if versions else "1"
|
|
388
379
|
|
|
389
380
|
model = AzureMLModel(
|
|
390
|
-
name=
|
|
381
|
+
name=registered_name,
|
|
391
382
|
version=version,
|
|
392
383
|
path=f"azureml://jobs/{run_id}/outputs/artifacts/run_output",
|
|
393
384
|
type=AssetTypes.CUSTOM_MODEL,
|
|
@@ -399,67 +390,51 @@ class AzureMLBackend(Backend):
|
|
|
399
390
|
|
|
400
391
|
def download_registered(
|
|
401
392
|
self,
|
|
402
|
-
|
|
403
|
-
version: str
|
|
393
|
+
registered_name: str,
|
|
394
|
+
version: str,
|
|
404
395
|
) -> tuple[TrainingResult, str, str]:
|
|
405
396
|
"""Download a registered model, returning the result and the local path.
|
|
406
397
|
|
|
407
398
|
The local path points to the downloaded model directory which
|
|
408
|
-
contains ``trained_model.pkl``, ``
|
|
399
|
+
contains ``trained_model.pkl``, ``run_info.json``, and ``code/``.
|
|
409
400
|
This is useful at build time to export the model into a
|
|
410
401
|
:class:`~modelzone.training.LocalBackend` via
|
|
411
402
|
:meth:`~modelzone.training.LocalBackend.save`.
|
|
412
403
|
|
|
413
404
|
Args:
|
|
414
|
-
|
|
415
|
-
version: Version to download.
|
|
416
|
-
version is used.
|
|
405
|
+
registered_name: Name of the model in the AzureML Model Registry.
|
|
406
|
+
version: Version to download.
|
|
417
407
|
|
|
418
408
|
Returns:
|
|
419
|
-
A tuple of ``(TrainingResult, model_dir_path)``.
|
|
409
|
+
A tuple of ``(TrainingResult, model_dir_path, download_dir)``.
|
|
420
410
|
"""
|
|
421
|
-
if version is None:
|
|
422
|
-
try:
|
|
423
|
-
existing = self.ml_client.models.list(name=model_name)
|
|
424
|
-
versions = [m for m in existing if m.version.isdigit()]
|
|
425
|
-
except Exception:
|
|
426
|
-
versions = []
|
|
427
|
-
if not versions:
|
|
428
|
-
raise ValueError(f"No versions found for model '{model_name}'")
|
|
429
|
-
latest = max(versions, key=lambda m: int(m.version))
|
|
430
|
-
version = latest.version
|
|
431
|
-
|
|
432
411
|
download_dir = tempfile.mkdtemp(prefix="modelzone_registered_")
|
|
433
412
|
self.ml_client.models.download(
|
|
434
|
-
name=
|
|
413
|
+
name=registered_name,
|
|
435
414
|
version=version,
|
|
436
415
|
download_path=download_dir,
|
|
437
416
|
)
|
|
438
417
|
|
|
439
|
-
local_path = os.path.join(download_dir,
|
|
418
|
+
local_path = os.path.join(download_dir, registered_name, "run_output")
|
|
440
419
|
|
|
441
420
|
with open(os.path.join(local_path, TRAINED_MODEL_FILENAME), "rb") as f:
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
with open(os.path.join(local_path, RECORD_FILENAME), "rb") as f:
|
|
445
|
-
record: RunRecord = pickle.load(f) # noqa: S301
|
|
421
|
+
model_artifact: ModelArtifact = pickle.load(f) # noqa: S301
|
|
446
422
|
|
|
447
423
|
run_id = ""
|
|
448
|
-
model_entity = self.ml_client.models.get(name=
|
|
424
|
+
model_entity = self.ml_client.models.get(name=registered_name, version=version)
|
|
449
425
|
if model_entity.tags:
|
|
450
426
|
run_id = model_entity.tags.get("run_id", "")
|
|
451
427
|
|
|
452
428
|
result = TrainingResult(
|
|
453
|
-
|
|
454
|
-
record=record,
|
|
429
|
+
model_artifact=model_artifact,
|
|
455
430
|
run_id=run_id,
|
|
456
431
|
)
|
|
457
432
|
return result, local_path, download_dir
|
|
458
433
|
|
|
459
434
|
def load_registered(
|
|
460
435
|
self,
|
|
461
|
-
|
|
462
|
-
version: str
|
|
436
|
+
registered_name: str,
|
|
437
|
+
version: str,
|
|
463
438
|
) -> TrainingResult:
|
|
464
439
|
"""Download a registered model and reconstruct a TrainingResult.
|
|
465
440
|
|
|
@@ -467,13 +442,12 @@ class AzureMLBackend(Backend):
|
|
|
467
442
|
discards the local download path.
|
|
468
443
|
|
|
469
444
|
Args:
|
|
470
|
-
|
|
471
|
-
version: Version to download.
|
|
472
|
-
version is used.
|
|
445
|
+
registered_name: Name of the model in the AzureML Model Registry.
|
|
446
|
+
version: Version to download.
|
|
473
447
|
|
|
474
448
|
Returns:
|
|
475
449
|
The reconstructed :class:`TrainingResult`.
|
|
476
450
|
"""
|
|
477
|
-
result, _, download_dir = self.download_registered(
|
|
451
|
+
result, _, download_dir = self.download_registered(registered_name, version)
|
|
478
452
|
shutil.rmtree(download_dir)
|
|
479
453
|
return result
|
|
@@ -1,57 +1,31 @@
|
|
|
1
1
|
"""Modelzone CLI – train, register, and fetch models.
|
|
2
2
|
|
|
3
3
|
Provides ``modelzone train``, ``modelzone register``, and
|
|
4
|
-
``modelzone fetch`` as console entry points
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
All commands expect to be run from the project root directory
|
|
8
|
-
containing ``project.json`` and model packages with ``model.json``.
|
|
4
|
+
``modelzone fetch`` as console entry points. All commands expect
|
|
5
|
+
a ``pyproject.toml`` with a ``[tool.modelzone]`` section.
|
|
9
6
|
"""
|
|
10
7
|
|
|
11
8
|
from __future__ import annotations
|
|
12
9
|
|
|
13
10
|
import argparse
|
|
14
11
|
import importlib
|
|
15
|
-
import json
|
|
16
12
|
import os
|
|
17
13
|
import sys
|
|
18
14
|
|
|
19
|
-
from modelzone import
|
|
20
|
-
from modelzone.core import Model
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def _load_project() -> dict:
|
|
24
|
-
if not os.path.isfile(PROJECT_CONFIG):
|
|
25
|
-
print(f"Error: {PROJECT_CONFIG} not found in {os.getcwd()}", file=sys.stderr)
|
|
26
|
-
sys.exit(1)
|
|
27
|
-
with open(PROJECT_CONFIG) as f:
|
|
28
|
-
return json.load(f)
|
|
29
|
-
|
|
15
|
+
from modelzone import ModelDefinition, project_config
|
|
30
16
|
|
|
31
|
-
def _load_model_config(model_package: str) -> dict:
|
|
32
|
-
path = os.path.join(model_package, MODEL_CONFIG)
|
|
33
|
-
if not os.path.isfile(path):
|
|
34
|
-
print(f"Error: {path} not found in {os.getcwd()}", file=sys.stderr)
|
|
35
|
-
sys.exit(1)
|
|
36
|
-
with open(path) as f:
|
|
37
|
-
return json.load(f)
|
|
38
17
|
|
|
39
|
-
|
|
40
|
-
def _load_root_model_config() -> dict:
|
|
41
|
-
if not os.path.isfile(MODEL_CONFIG):
|
|
42
|
-
print(f"Error: {MODEL_CONFIG} not found in {os.getcwd()}", file=sys.stderr)
|
|
43
|
-
sys.exit(1)
|
|
44
|
-
with open(MODEL_CONFIG) as f:
|
|
45
|
-
return json.load(f)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def _discover_model_class(model_package: str) -> type[Model]:
|
|
18
|
+
def _discover_model_class(model_package: str) -> type[ModelDefinition]:
|
|
49
19
|
mod = importlib.import_module(model_package)
|
|
50
20
|
for v in vars(mod).values():
|
|
51
|
-
if
|
|
21
|
+
if (
|
|
22
|
+
isinstance(v, type)
|
|
23
|
+
and issubclass(v, ModelDefinition)
|
|
24
|
+
and v is not ModelDefinition
|
|
25
|
+
):
|
|
52
26
|
return v
|
|
53
27
|
print(
|
|
54
|
-
f"Error: no
|
|
28
|
+
f"Error: no ModelDefinition subclass found in {model_package}",
|
|
55
29
|
file=sys.stderr,
|
|
56
30
|
)
|
|
57
31
|
sys.exit(1)
|
|
@@ -78,7 +52,7 @@ def _build_db(config: dict, cache: bool = True):
|
|
|
78
52
|
|
|
79
53
|
|
|
80
54
|
def train(args: argparse.Namespace) -> None:
|
|
81
|
-
config =
|
|
55
|
+
config = project_config.load_model(args.model_package)
|
|
82
56
|
db, tags = _build_db(config, cache=not args.no_cache)
|
|
83
57
|
|
|
84
58
|
params = {}
|
|
@@ -88,12 +62,12 @@ def train(args: argparse.Namespace) -> None:
|
|
|
88
62
|
if args.azureml:
|
|
89
63
|
from modelzone.azureml import AzureMLBackend
|
|
90
64
|
|
|
91
|
-
project =
|
|
92
|
-
|
|
65
|
+
project = project_config.load_project()
|
|
66
|
+
azureml_backend = AzureMLBackend(
|
|
93
67
|
**project["workspace"],
|
|
94
|
-
experiment_name=config["
|
|
68
|
+
experiment_name=config["name"],
|
|
95
69
|
)
|
|
96
|
-
result =
|
|
70
|
+
result = azureml_backend.run(
|
|
97
71
|
_discover_model_class(args.model_package)(),
|
|
98
72
|
seed=42,
|
|
99
73
|
db=db,
|
|
@@ -105,8 +79,8 @@ def train(args: argparse.Namespace) -> None:
|
|
|
105
79
|
else:
|
|
106
80
|
from modelzone.training import LocalBackend
|
|
107
81
|
|
|
108
|
-
|
|
109
|
-
result =
|
|
82
|
+
local_backend = LocalBackend(root="runs")
|
|
83
|
+
result = local_backend.run(
|
|
110
84
|
_discover_model_class(args.model_package)(),
|
|
111
85
|
seed=42,
|
|
112
86
|
db=db,
|
|
@@ -116,58 +90,58 @@ def train(args: argparse.Namespace) -> None:
|
|
|
116
90
|
)
|
|
117
91
|
print(f"\nLocal Run ID: {result.run_id}")
|
|
118
92
|
|
|
119
|
-
for m in result.record.metrics:
|
|
120
|
-
print(f" {m.name} = {m.value}")
|
|
121
|
-
|
|
122
93
|
|
|
123
94
|
def register(args: argparse.Namespace) -> None:
|
|
124
95
|
from modelzone.azureml import AzureMLBackend
|
|
125
96
|
|
|
126
|
-
project =
|
|
127
|
-
config =
|
|
97
|
+
project = project_config.load_project()
|
|
98
|
+
config = project_config.load_model(args.model_package)
|
|
128
99
|
|
|
129
100
|
backend = AzureMLBackend(
|
|
130
101
|
**project["workspace"],
|
|
131
|
-
experiment_name=config["
|
|
102
|
+
experiment_name=config["name"],
|
|
132
103
|
)
|
|
133
104
|
registered = backend.register(
|
|
134
105
|
run_id=args.run_id,
|
|
135
|
-
|
|
106
|
+
registered_name=config["name"],
|
|
136
107
|
)
|
|
137
108
|
print(f"Registered: {registered.name} v{registered.version}")
|
|
138
109
|
|
|
139
110
|
|
|
140
|
-
def
|
|
111
|
+
def _fetch_one(
|
|
112
|
+
config: dict,
|
|
113
|
+
workspace: dict,
|
|
114
|
+
) -> None:
|
|
141
115
|
import pickle
|
|
142
116
|
import shutil
|
|
143
117
|
|
|
144
118
|
from modelzone.azureml import AzureMLBackend
|
|
145
119
|
|
|
146
|
-
|
|
147
|
-
if args.model_package:
|
|
148
|
-
config = _load_model_config(args.model_package)
|
|
149
|
-
else:
|
|
150
|
-
config = _load_root_model_config()
|
|
151
|
-
model_name = config["model_name"]
|
|
120
|
+
model_name = config["name"]
|
|
152
121
|
|
|
153
122
|
backend = AzureMLBackend(
|
|
154
|
-
**
|
|
155
|
-
experiment_name=config["
|
|
123
|
+
**workspace,
|
|
124
|
+
experiment_name=config["name"],
|
|
156
125
|
)
|
|
157
126
|
|
|
158
|
-
version =
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
127
|
+
version = config.get("model_version")
|
|
128
|
+
if version is None:
|
|
129
|
+
print(
|
|
130
|
+
f"Error: model_version is required for {model_name}",
|
|
131
|
+
file=sys.stderr,
|
|
132
|
+
)
|
|
133
|
+
sys.exit(1)
|
|
134
|
+
version = str(version)
|
|
135
|
+
print(f"Downloading {model_name} (version={version})...")
|
|
136
|
+
result, model_dir, download_dir = backend.download_registered(
|
|
137
|
+
model_name, version=version
|
|
138
|
+
)
|
|
162
139
|
|
|
163
|
-
out =
|
|
140
|
+
out = config["path"]
|
|
164
141
|
os.makedirs(out, exist_ok=True)
|
|
165
142
|
|
|
166
143
|
with open(os.path.join(out, "trained_model.pkl"), "wb") as f:
|
|
167
|
-
pickle.dump(result.
|
|
168
|
-
|
|
169
|
-
with open(os.path.join(out, "record.pkl"), "wb") as f:
|
|
170
|
-
pickle.dump(result.record, f)
|
|
144
|
+
pickle.dump(result.model_artifact, f)
|
|
171
145
|
|
|
172
146
|
src_code = os.path.join(model_dir, "code")
|
|
173
147
|
dst_code = os.path.join(out, "code")
|
|
@@ -180,6 +154,13 @@ def fetch(args: argparse.Namespace) -> None:
|
|
|
180
154
|
print(f"Saved to {out}")
|
|
181
155
|
|
|
182
156
|
|
|
157
|
+
def fetch(args: argparse.Namespace) -> None:
|
|
158
|
+
project = project_config.load_project()
|
|
159
|
+
models = project_config.load_all_models()
|
|
160
|
+
for config in models:
|
|
161
|
+
_fetch_one(config, project["workspace"])
|
|
162
|
+
|
|
163
|
+
|
|
183
164
|
def main() -> None:
|
|
184
165
|
parser = argparse.ArgumentParser(prog="modelzone", description="Modelzone CLI")
|
|
185
166
|
sub = parser.add_subparsers(dest="command", required=True)
|
|
@@ -204,19 +185,9 @@ def main() -> None:
|
|
|
204
185
|
r.add_argument("model_package", help="Model package name")
|
|
205
186
|
r.add_argument("run_id", help="MLflow run ID to register")
|
|
206
187
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
"model_package",
|
|
210
|
-
nargs="?",
|
|
211
|
-
default=None,
|
|
212
|
-
help="Model package name (omit to read model.json from cwd)",
|
|
213
|
-
)
|
|
214
|
-
f.add_argument(
|
|
215
|
-
"--version",
|
|
216
|
-
default=None,
|
|
217
|
-
help="Model version (default: model_version from config, or latest)",
|
|
188
|
+
sub.add_parser(
|
|
189
|
+
"fetch", help="Download all registered models defined in pyproject.toml"
|
|
218
190
|
)
|
|
219
|
-
f.add_argument("--output-dir", default=".model", help="Local output directory")
|
|
220
191
|
|
|
221
192
|
args = parser.parse_args()
|
|
222
193
|
if args.command == "train":
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Modelzone core – minimal runtime classes for training and prediction."""
|
|
2
|
+
|
|
3
|
+
from modelzone.core.constants import CODE_DIR, TRAINED_MODEL_FILENAME # noqa: F401
|
|
4
|
+
from modelzone.core.model import ModelDefinition # noqa: F401
|
|
5
|
+
from modelzone.core.model_artifact import ModelArtifact # noqa: F401
|
|
6
|
+
from modelzone.core.predict_context import PredictContext # noqa: F401
|
|
7
|
+
from modelzone.core.training_context import TrainingContext # noqa: F401
|
|
8
|
+
from modelzone.core.training_result import TrainingResult # noqa: F401
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Modelzone core – shared filename conventions for run directories."""
|
|
2
|
+
|
|
3
|
+
# Filename used for the pickled ModelArtifact inside every run directory.
|
|
4
|
+
TRAINED_MODEL_FILENAME = "trained_model.pkl"
|
|
5
|
+
RUN_INFO_FILENAME = "run_info.json"
|
|
6
|
+
METRICS_FILENAME = "metrics.jsonl"
|
|
7
|
+
LOG_FILENAME = "output.log"
|
|
8
|
+
FILES_DIR = "files"
|
|
9
|
+
CODE_DIR = "code"
|
|
@@ -1,24 +1,22 @@
|
|
|
1
|
-
"""Modelzone core – base
|
|
1
|
+
"""Modelzone core – base ModelDefinition class that users inherit from."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from abc import abstractmethod
|
|
6
|
-
from typing import
|
|
7
|
-
|
|
8
|
-
if TYPE_CHECKING:
|
|
9
|
-
from modelzone.training.context import TrainingContext
|
|
6
|
+
from typing import Any
|
|
10
7
|
|
|
8
|
+
from modelzone.core.model_artifact import ModelArtifact
|
|
11
9
|
from modelzone.core.predict_context import PredictContext
|
|
12
|
-
from modelzone.core.
|
|
10
|
+
from modelzone.core.training_context import TrainingContext
|
|
13
11
|
|
|
14
12
|
|
|
15
|
-
class
|
|
13
|
+
class ModelDefinition:
|
|
16
14
|
"""Base class for user-defined models.
|
|
17
15
|
|
|
18
16
|
Subclass this and implement :meth:`train` and :meth:`predict`.
|
|
19
17
|
|
|
20
18
|
* ``train(ctx)`` runs in the **training environment** and receives a
|
|
21
|
-
:class:`~modelzone.
|
|
19
|
+
:class:`~modelzone.core.TrainingContext` for logging metrics, plots
|
|
22
20
|
and tags.
|
|
23
21
|
* ``predict(ctx)`` runs in the **operational environment** and
|
|
24
22
|
receives a :class:`~modelzone.core.PredictContext` with the
|
|
@@ -27,13 +25,13 @@ class Model:
|
|
|
27
25
|
|
|
28
26
|
Example::
|
|
29
27
|
|
|
30
|
-
from modelzone.core import
|
|
28
|
+
from modelzone.core import ModelDefinition, ModelArtifact
|
|
31
29
|
|
|
32
|
-
class MyModel(
|
|
30
|
+
class MyModel(ModelDefinition):
|
|
33
31
|
def train(self, ctx):
|
|
34
32
|
fitted = fit(load_data(), seed=ctx.seed)
|
|
35
33
|
ctx.log_metric("rmse", compute_rmse(fitted))
|
|
36
|
-
return
|
|
34
|
+
return ModelArtifact(model=fitted)
|
|
37
35
|
|
|
38
36
|
def predict(self, ctx):
|
|
39
37
|
df = ctx.db.read("SELECT * FROM input_table")
|
|
@@ -41,8 +39,8 @@ class Model:
|
|
|
41
39
|
"""
|
|
42
40
|
|
|
43
41
|
@abstractmethod
|
|
44
|
-
def train(self, ctx: TrainingContext) ->
|
|
45
|
-
"""Run training logic and return a :class:`
|
|
42
|
+
def train(self, ctx: TrainingContext) -> ModelArtifact:
|
|
43
|
+
"""Run training logic and return a :class:`ModelArtifact`.
|
|
46
44
|
|
|
47
45
|
Only called in the training environment. Use *ctx* to log
|
|
48
46
|
metrics, plots and tags; access ``ctx.seed`` and ``ctx.params``.
|
|
@@ -51,7 +49,7 @@ class Model:
|
|
|
51
49
|
ctx: The run context for this training run.
|
|
52
50
|
|
|
53
51
|
Returns:
|
|
54
|
-
A :class:`
|
|
52
|
+
A :class:`ModelArtifact` with the fitted artefact(s).
|
|
55
53
|
"""
|
|
56
54
|
|
|
57
55
|
@abstractmethod
|