collie-mlops 0.1.1b0__py3-none-any.whl
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.
- collie/__init__.py +69 -0
- collie/_common/__init__.py +0 -0
- collie/_common/decorator.py +53 -0
- collie/_common/exceptions.py +104 -0
- collie/_common/mlflow_model_io/__init__.py +0 -0
- collie/_common/mlflow_model_io/base_flavor_handler.py +26 -0
- collie/_common/mlflow_model_io/flavor_registry.py +72 -0
- collie/_common/mlflow_model_io/model_flavors.py +259 -0
- collie/_common/mlflow_model_io/model_io.py +65 -0
- collie/_common/utils.py +13 -0
- collie/contracts/__init__.py +0 -0
- collie/contracts/event.py +79 -0
- collie/contracts/mlflow.py +444 -0
- collie/contracts/orchestrator.py +79 -0
- collie/core/__init__.py +41 -0
- collie/core/enums/__init__.py +0 -0
- collie/core/enums/components.py +26 -0
- collie/core/enums/ml_models.py +20 -0
- collie/core/evaluator/__init__.py +0 -0
- collie/core/evaluator/evaluator.py +147 -0
- collie/core/models.py +125 -0
- collie/core/orchestrator/__init__.py +0 -0
- collie/core/orchestrator/orchestrator.py +47 -0
- collie/core/pusher/__init__.py +0 -0
- collie/core/pusher/pusher.py +98 -0
- collie/core/trainer/__init__.py +0 -0
- collie/core/trainer/trainer.py +78 -0
- collie/core/transform/__init__.py +0 -0
- collie/core/transform/transform.py +87 -0
- collie/core/tuner/__init__.py +0 -0
- collie/core/tuner/tuner.py +84 -0
- collie/helper/__init__.py +0 -0
- collie/helper/pytorch/__init__.py +0 -0
- collie/helper/pytorch/callback/__init__.py +0 -0
- collie/helper/pytorch/callback/callback.py +155 -0
- collie/helper/pytorch/callback/earlystop.py +54 -0
- collie/helper/pytorch/callback/model_checkpoint.py +100 -0
- collie/helper/pytorch/model/__init__.py +0 -0
- collie/helper/pytorch/model/loader.py +55 -0
- collie/helper/pytorch/trainer.py +304 -0
- collie_mlops-0.1.1b0.dist-info/LICENSE +21 -0
- collie_mlops-0.1.1b0.dist-info/METADATA +259 -0
- collie_mlops-0.1.1b0.dist-info/RECORD +45 -0
- collie_mlops-0.1.1b0.dist-info/WHEEL +5 -0
- collie_mlops-0.1.1b0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
from typing import (
|
|
2
|
+
Dict,
|
|
3
|
+
Any,
|
|
4
|
+
List,
|
|
5
|
+
Optional
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
from collie.contracts.event import (
|
|
9
|
+
Event,
|
|
10
|
+
EventHandler,
|
|
11
|
+
EventType
|
|
12
|
+
)
|
|
13
|
+
from collie.contracts.mlflow import MLFlowComponentABC
|
|
14
|
+
from collie._common.decorator import type_checker
|
|
15
|
+
from collie.core.models import (
|
|
16
|
+
EvaluatorArtifact,
|
|
17
|
+
EvaluatorPayload
|
|
18
|
+
)
|
|
19
|
+
from collie._common.exceptions import EvaluatorError
|
|
20
|
+
|
|
21
|
+
class Evaluator(EventHandler, MLFlowComponentABC):
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
description: Optional[str] = None,
|
|
26
|
+
tags: Optional[Dict[str, str]] = None
|
|
27
|
+
) -> None:
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
Initializes the Evaluator.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
description (Optional[str], optional): Description for the MLflow run. Defaults to None.
|
|
34
|
+
tags (Optional[Dict[str, str]], optional): Tags to associate with the MLflow run. Defaults to None.
|
|
35
|
+
|
|
36
|
+
"""
|
|
37
|
+
super().__init__()
|
|
38
|
+
self._registered_model_name = None
|
|
39
|
+
self.description = description
|
|
40
|
+
self.tags = tags or {"component": "Evaluator"}
|
|
41
|
+
|
|
42
|
+
self.model_uri = None
|
|
43
|
+
self.metrics = None
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def registered_model_name(self) -> str:
|
|
47
|
+
if not self._registered_model_name:
|
|
48
|
+
raise EvaluatorError("Registered model name is not set.")
|
|
49
|
+
return self._registered_model_name
|
|
50
|
+
|
|
51
|
+
@registered_model_name.setter
|
|
52
|
+
def registered_model_name(self, name: str) -> None:
|
|
53
|
+
self._registered_model_name = name
|
|
54
|
+
|
|
55
|
+
def run(self, event: Event) -> Event:
|
|
56
|
+
|
|
57
|
+
with self.start_run(
|
|
58
|
+
tags=self.tags,
|
|
59
|
+
run_name="Evaluator",
|
|
60
|
+
log_system_metrics=True,
|
|
61
|
+
description=self.description,
|
|
62
|
+
nested=True,
|
|
63
|
+
) as run:
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
evaluator_event = self._handle(event)
|
|
67
|
+
payload = self._get_evaluator_payload(evaluator_event)
|
|
68
|
+
|
|
69
|
+
self.metrics: List[Dict[str, Any]] = payload.metrics
|
|
70
|
+
self.model_uri = event.context.get('model_uri')
|
|
71
|
+
|
|
72
|
+
self._log_metrics()
|
|
73
|
+
self._log_summary(payload)
|
|
74
|
+
|
|
75
|
+
event.context.set("evaluator_report_uri", self.artifact_uri(run))
|
|
76
|
+
|
|
77
|
+
if self.experiment_is_better(payload):
|
|
78
|
+
event.context.set("pass_evaluation", True)
|
|
79
|
+
self.mlflow.log_param("evaluation_result", "passed")
|
|
80
|
+
else:
|
|
81
|
+
event.context.set("pass_evaluation", False)
|
|
82
|
+
self.mlflow.log_param("evaluation_result", "failed")
|
|
83
|
+
|
|
84
|
+
except Exception as e:
|
|
85
|
+
raise EvaluatorError(f"Evaluator failed: {e}") from e
|
|
86
|
+
|
|
87
|
+
return Event(
|
|
88
|
+
type=EventType.EVALUATION_DONE,
|
|
89
|
+
payload=payload,
|
|
90
|
+
context=event.context,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
@staticmethod
|
|
94
|
+
def experiment_is_better(
|
|
95
|
+
payload: EvaluatorPayload
|
|
96
|
+
) -> bool:
|
|
97
|
+
"""
|
|
98
|
+
Decide if experiment model is better based on score direction.
|
|
99
|
+
"""
|
|
100
|
+
return payload.is_better_than_production
|
|
101
|
+
|
|
102
|
+
def _flatten_metrics(
|
|
103
|
+
self,
|
|
104
|
+
metrics: List[Dict[str, Any]]
|
|
105
|
+
) -> Dict[str, Any]:
|
|
106
|
+
"""
|
|
107
|
+
Flatten list of metric dictionaries into a single dictionary.
|
|
108
|
+
Useful for logging and analysis.
|
|
109
|
+
"""
|
|
110
|
+
flattened = {}
|
|
111
|
+
for metric_dict in metrics:
|
|
112
|
+
flattened.update(metric_dict)
|
|
113
|
+
return flattened
|
|
114
|
+
|
|
115
|
+
def _log_summary(
|
|
116
|
+
self,
|
|
117
|
+
payload: EvaluatorPayload
|
|
118
|
+
) -> None:
|
|
119
|
+
"""
|
|
120
|
+
Generate summary statistics from metrics.
|
|
121
|
+
"""
|
|
122
|
+
flattened = self._flatten_metrics(payload.metrics)
|
|
123
|
+
|
|
124
|
+
summary = {
|
|
125
|
+
"total_metrics": len(flattened),
|
|
126
|
+
"is_better": payload.is_better_than_production,
|
|
127
|
+
"metrics": flattened
|
|
128
|
+
}
|
|
129
|
+
self.mlflow.log_dict(
|
|
130
|
+
dictionary=summary,
|
|
131
|
+
artifact_file=f"{EvaluatorArtifact().report}"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
def _log_metrics(self) -> None:
|
|
135
|
+
"""
|
|
136
|
+
Log individual metrics to MLflow.
|
|
137
|
+
"""
|
|
138
|
+
for metric_dict in self.metrics:
|
|
139
|
+
for metric_name, metric_value in metric_dict.items():
|
|
140
|
+
self.mlflow.log_metric(metric_name, metric_value)
|
|
141
|
+
|
|
142
|
+
@type_checker((EvaluatorPayload,), "EvaluatorPayload must be of type EvaluatorPayload.")
|
|
143
|
+
def _get_evaluator_payload(self, event: Event) -> EvaluatorPayload:
|
|
144
|
+
return event.payload
|
|
145
|
+
|
|
146
|
+
def artifact_uri(self, run) -> str:
|
|
147
|
+
return f"{run.info.artifact_uri}/{EvaluatorArtifact().report}"
|
collie/core/models.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
from typing import (
|
|
2
|
+
Dict,
|
|
3
|
+
Any,
|
|
4
|
+
Optional,
|
|
5
|
+
List,
|
|
6
|
+
)
|
|
7
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
8
|
+
|
|
9
|
+
import pandas as pd
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BasePayload(BaseModel):
|
|
13
|
+
"""Base class for all payload types with optional extra_data functionality."""
|
|
14
|
+
|
|
15
|
+
extra_data: Optional[Dict[str, Any]] = None
|
|
16
|
+
|
|
17
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
18
|
+
|
|
19
|
+
def set_extra(self, key: str, value: Any):
|
|
20
|
+
"""
|
|
21
|
+
Set a value in extra_data and return self for chaining.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
key: The key to set
|
|
25
|
+
value: The value to store
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Self for method chaining
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
>>> payload.set_extra("feature_names", ["age", "income"])
|
|
32
|
+
>>> payload.set_extra("n_classes", 3).set_extra("version", "1.0")
|
|
33
|
+
"""
|
|
34
|
+
if self.extra_data is None:
|
|
35
|
+
self.extra_data = {}
|
|
36
|
+
self.extra_data[key] = value
|
|
37
|
+
return self
|
|
38
|
+
|
|
39
|
+
def get_extra(self, key: str, default: Any = None) -> Any:
|
|
40
|
+
"""
|
|
41
|
+
Get a value from extra_data with optional default.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
key: The key to retrieve
|
|
45
|
+
default: Default value if key doesn't exist
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
The value or default
|
|
49
|
+
|
|
50
|
+
Example:
|
|
51
|
+
>>> features = payload.get_extra("feature_names", [])
|
|
52
|
+
"""
|
|
53
|
+
if self.extra_data is None:
|
|
54
|
+
return default
|
|
55
|
+
return self.extra_data.get(key, default)
|
|
56
|
+
|
|
57
|
+
def has_extra(self, key: str) -> bool:
|
|
58
|
+
"""
|
|
59
|
+
Check if a key exists in extra_data.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
key: The key to check
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
True if key exists, False otherwise
|
|
66
|
+
|
|
67
|
+
Example:
|
|
68
|
+
>>> if payload.has_extra("feature_names"):
|
|
69
|
+
... features = payload.get_extra("feature_names")
|
|
70
|
+
"""
|
|
71
|
+
if self.extra_data is None:
|
|
72
|
+
return False
|
|
73
|
+
return key in self.extra_data
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class TransformerArtifact(BaseModel):
|
|
77
|
+
train_data: str = "train.csv"
|
|
78
|
+
validation_data: str = "validation.csv"
|
|
79
|
+
test_data: str = "test.csv"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class TunerArtifact(BaseModel):
|
|
83
|
+
hyperparameters: str = "hyperparameters.json"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class TrainerArtifact(BaseModel):
|
|
87
|
+
model: str = "model"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class EvaluatorArtifact(BaseModel):
|
|
91
|
+
report: str = "report.json"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class TransformerPayload(BasePayload):
|
|
95
|
+
train_data: Optional[pd.DataFrame] = None
|
|
96
|
+
validation_data: Optional[pd.DataFrame] = None
|
|
97
|
+
test_data: Optional[pd.DataFrame] = None
|
|
98
|
+
|
|
99
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class TrainerPayload(BasePayload):
|
|
103
|
+
model: Any = None
|
|
104
|
+
|
|
105
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class TunerPayload(BasePayload):
|
|
109
|
+
hyperparameters: Dict[str, Any]
|
|
110
|
+
train_data: Optional[pd.DataFrame] = None
|
|
111
|
+
validation_data: Optional[pd.DataFrame] = None
|
|
112
|
+
test_data: Optional[pd.DataFrame] = None
|
|
113
|
+
|
|
114
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class EvaluatorPayload(BasePayload):
|
|
118
|
+
metrics: List[Dict[str, Any]]
|
|
119
|
+
is_better_than_production: bool
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class PusherPayload(BasePayload):
|
|
123
|
+
model_uri: str
|
|
124
|
+
status: Optional[str] = None
|
|
125
|
+
model_version: Optional[str] = None
|
|
File without changes
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from typing import (
|
|
2
|
+
Optional,
|
|
3
|
+
Dict,
|
|
4
|
+
)
|
|
5
|
+
|
|
6
|
+
from collie.contracts.orchestrator import OrchestratorBase
|
|
7
|
+
from collie.core.enums.components import CollieComponentType
|
|
8
|
+
from collie._common.utils import get_logger
|
|
9
|
+
|
|
10
|
+
logger = get_logger()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Orchestrator(OrchestratorBase):
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
components: CollieComponentType,
|
|
18
|
+
tracking_uri: str,
|
|
19
|
+
registered_model_name: str,
|
|
20
|
+
mlflow_tags: Optional[Dict[str, str]] = None,
|
|
21
|
+
experiment_name: Optional[str] = None,
|
|
22
|
+
description: Optional[str] = None,
|
|
23
|
+
) -> None:
|
|
24
|
+
super().__init__(
|
|
25
|
+
components=components,
|
|
26
|
+
tracking_uri=tracking_uri,
|
|
27
|
+
mlflow_tags=mlflow_tags,
|
|
28
|
+
experiment_name=experiment_name,
|
|
29
|
+
description=description
|
|
30
|
+
)
|
|
31
|
+
self.registered_model_name = registered_model_name
|
|
32
|
+
|
|
33
|
+
def orchestrate_pipeline(self) -> None:
|
|
34
|
+
"""Run the pipeline sequentially without Airflow."""
|
|
35
|
+
|
|
36
|
+
logger.info("Pipeline started.")
|
|
37
|
+
incoming_event = self.initialize_event()
|
|
38
|
+
|
|
39
|
+
for idx, component in enumerate(self.components):
|
|
40
|
+
logger.info(f"Running component {idx}: {type(component).__name__}")
|
|
41
|
+
component.mlflow_config = self.mlflow_config
|
|
42
|
+
if hasattr(component, "_registered_model_name"):
|
|
43
|
+
component.registered_model_name = self.registered_model_name
|
|
44
|
+
|
|
45
|
+
incoming_event = component.run(incoming_event)
|
|
46
|
+
|
|
47
|
+
logger.info(f"Component {idx} finished: {type(component).__name__}")
|
|
File without changes
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
from collie.contracts.event import (
|
|
3
|
+
Event,
|
|
4
|
+
EventHandler,
|
|
5
|
+
EventType
|
|
6
|
+
)
|
|
7
|
+
from collie.contracts.mlflow import MLFlowComponentABC
|
|
8
|
+
from collie.core.models import PusherPayload
|
|
9
|
+
from collie._common.decorator import type_checker
|
|
10
|
+
from collie._common.exceptions import PusherError
|
|
11
|
+
from collie.core.enums.ml_models import MLflowModelStage
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Pusher(EventHandler, MLFlowComponentABC):
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
target_stage: MLflowModelStage = MLflowModelStage.PRODUCTION,
|
|
18
|
+
archive_existing_versions: bool = True,
|
|
19
|
+
description: Optional[str] = None,
|
|
20
|
+
tags: Optional[dict] = None
|
|
21
|
+
) -> None:
|
|
22
|
+
"""
|
|
23
|
+
Initializes the Pusher.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
target_stage (Optional[MLflowModelStage], optional): The stage to transition the model to after evaluation. Defaults to None.
|
|
27
|
+
archive_existing_versions (bool, optional): Whether to archive existing versions at the target stage. Defaults to True.
|
|
28
|
+
description (Optional[str], optional): Description for the MLflow run. Defaults to None.
|
|
29
|
+
tags (Optional[dict], optional): Tags to associate with the MLflow run. Defaults to None.
|
|
30
|
+
|
|
31
|
+
"""
|
|
32
|
+
super().__init__()
|
|
33
|
+
self._registered_model_name = None
|
|
34
|
+
self.target_stage = target_stage
|
|
35
|
+
self.archive_existing_versions = archive_existing_versions
|
|
36
|
+
self.description = description
|
|
37
|
+
self.tags = tags or {"component": "Pusher"}
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def registered_model_name(self) -> str:
|
|
41
|
+
if not self._registered_model_name:
|
|
42
|
+
raise PusherError("Registered model name is not set.")
|
|
43
|
+
return self._registered_model_name
|
|
44
|
+
|
|
45
|
+
@registered_model_name.setter
|
|
46
|
+
def registered_model_name(self, name: str) -> None:
|
|
47
|
+
self._registered_model_name = name
|
|
48
|
+
|
|
49
|
+
def run(self, event: Event) -> Event:
|
|
50
|
+
with self.start_run(
|
|
51
|
+
tags=self.tags,
|
|
52
|
+
run_name="Pusher",
|
|
53
|
+
log_system_metrics=False,
|
|
54
|
+
nested=True,
|
|
55
|
+
description=self.description
|
|
56
|
+
):
|
|
57
|
+
try:
|
|
58
|
+
pusher_event = self._handle(event)
|
|
59
|
+
payload = self._get_pusher_payload(pusher_event)
|
|
60
|
+
pass_evaluation = event.context.get("pass_evaluation")
|
|
61
|
+
|
|
62
|
+
if pass_evaluation:
|
|
63
|
+
model_uri = event.context.get('model_uri')
|
|
64
|
+
if not model_uri:
|
|
65
|
+
raise PusherError("model_uri not found in event context")
|
|
66
|
+
|
|
67
|
+
version = self.register_model(
|
|
68
|
+
model_name=self.registered_model_name,
|
|
69
|
+
model_uri=model_uri
|
|
70
|
+
)
|
|
71
|
+
self.mlflow.log_param("model_registered", "true")
|
|
72
|
+
event.context.set("registered_version", str(version))
|
|
73
|
+
self.mlflow.log_param("model_version", version)
|
|
74
|
+
|
|
75
|
+
self.mlflow.log_param("target_stage", self.target_stage.value)
|
|
76
|
+
self.transition_model_version(
|
|
77
|
+
registered_model_name=self.registered_model_name,
|
|
78
|
+
version=str(version),
|
|
79
|
+
desired_stage=self.target_stage,
|
|
80
|
+
archive_existing_versions_at_stage=self.archive_existing_versions,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
else:
|
|
84
|
+
self.mlflow.log_param("skip_reason", "evaluation_failed")
|
|
85
|
+
self.mlflow.log_param("model_registered", "false")
|
|
86
|
+
|
|
87
|
+
return Event(
|
|
88
|
+
type=EventType.PUSHER_DONE,
|
|
89
|
+
payload=payload,
|
|
90
|
+
context=event.context,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
except Exception as e:
|
|
94
|
+
raise PusherError(f"Pusher failed with error: {e}")
|
|
95
|
+
|
|
96
|
+
@type_checker((PusherPayload,), "PusherPayload must be of type PusherPayload.")
|
|
97
|
+
def _get_pusher_payload(self, event: Event) -> PusherPayload:
|
|
98
|
+
return event.payload
|
|
File without changes
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
from collie.contracts.event import (
|
|
3
|
+
Event,
|
|
4
|
+
EventHandler,
|
|
5
|
+
EventType
|
|
6
|
+
)
|
|
7
|
+
from collie.contracts.mlflow import MLFlowComponentABC
|
|
8
|
+
from collie.core.models import (
|
|
9
|
+
TrainerPayload,
|
|
10
|
+
TrainerArtifact
|
|
11
|
+
)
|
|
12
|
+
from collie._common.decorator import type_checker
|
|
13
|
+
from collie._common.exceptions import TrainerError
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Trainer(EventHandler, MLFlowComponentABC):
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
description: Optional[str] = None,
|
|
21
|
+
tags: Optional[dict] = None
|
|
22
|
+
) -> None:
|
|
23
|
+
"""
|
|
24
|
+
Initializes the Trainer component.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
description (Optional[str], optional): Description for the MLflow run. Defaults to None.
|
|
28
|
+
tags (Optional[dict], optional): Tags to associate with the MLflow run. Defaults to None.
|
|
29
|
+
"""
|
|
30
|
+
super().__init__()
|
|
31
|
+
self.description = description
|
|
32
|
+
self.tags = tags or {"component": "Trainer"}
|
|
33
|
+
|
|
34
|
+
def run(self, event: Event) -> Event:
|
|
35
|
+
"""
|
|
36
|
+
Run the trainer component.
|
|
37
|
+
|
|
38
|
+
This method starts a new MLflow run, trains the model,
|
|
39
|
+
logs metrics, and sets the outputs.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
with self.start_run(
|
|
43
|
+
run_name="Trainer",
|
|
44
|
+
tags=self.tags,
|
|
45
|
+
log_system_metrics=True,
|
|
46
|
+
nested=True,
|
|
47
|
+
description=self.description
|
|
48
|
+
) as run:
|
|
49
|
+
try:
|
|
50
|
+
trainer_event = self._handle(event)
|
|
51
|
+
|
|
52
|
+
trainer_payload = self._trainer_payload(trainer_event)
|
|
53
|
+
event_type = EventType.TRAINING_DONE
|
|
54
|
+
|
|
55
|
+
model = trainer_payload.model
|
|
56
|
+
self.log_model(
|
|
57
|
+
model=model,
|
|
58
|
+
name=TrainerArtifact().model
|
|
59
|
+
)
|
|
60
|
+
event.context.set("model_uri", self.artifact_uri(run))
|
|
61
|
+
|
|
62
|
+
return Event(
|
|
63
|
+
type=event_type,
|
|
64
|
+
payload=trainer_payload,
|
|
65
|
+
context=event.context
|
|
66
|
+
)
|
|
67
|
+
except Exception as e:
|
|
68
|
+
raise TrainerError(f"Trainer failed with error: {e}") from e
|
|
69
|
+
|
|
70
|
+
@type_checker((TrainerPayload,) ,
|
|
71
|
+
"TrainerPayload must be of type TrainerPayload."
|
|
72
|
+
)
|
|
73
|
+
def _trainer_payload(self, event: Event) -> TrainerPayload:
|
|
74
|
+
|
|
75
|
+
return event.payload
|
|
76
|
+
|
|
77
|
+
def artifact_uri(self, run) -> str:
|
|
78
|
+
return f"runs:/{run.info.run_id}/{TrainerArtifact().model}"
|
|
File without changes
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
from collie.contracts.event import (
|
|
3
|
+
Event,
|
|
4
|
+
EventHandler,
|
|
5
|
+
EventType
|
|
6
|
+
)
|
|
7
|
+
from collie.contracts.mlflow import MLFlowComponentABC
|
|
8
|
+
from collie.core.models import (
|
|
9
|
+
TransformerPayload,
|
|
10
|
+
TransformerArtifact
|
|
11
|
+
)
|
|
12
|
+
from collie._common.decorator import type_checker
|
|
13
|
+
from collie._common.exceptions import TransformerError
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Transformer(EventHandler, MLFlowComponentABC):
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
description: Optional[str] = None,
|
|
21
|
+
tags: Optional[dict] = None
|
|
22
|
+
) -> None:
|
|
23
|
+
"""
|
|
24
|
+
Initializes the Transformer component.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
description (Optional[str], optional): Description for the MLflow run. Defaults to None.
|
|
28
|
+
tags (Optional[dict], optional): Tags to associate with the MLflow run. Defaults to None.
|
|
29
|
+
"""
|
|
30
|
+
super().__init__()
|
|
31
|
+
self.description = description
|
|
32
|
+
self.tags = tags or {"component": "Transformer"}
|
|
33
|
+
|
|
34
|
+
def run(self, event: Event) -> Event:
|
|
35
|
+
"""
|
|
36
|
+
Run the transformer component.
|
|
37
|
+
|
|
38
|
+
This method starts a new MLflow run, transforms the input data,
|
|
39
|
+
logs metrics, and sets the outputs.
|
|
40
|
+
"""
|
|
41
|
+
with self.start_run(
|
|
42
|
+
tags=self.tags,
|
|
43
|
+
run_name="Transformer",
|
|
44
|
+
log_system_metrics=True,
|
|
45
|
+
nested=True,
|
|
46
|
+
description=self.description
|
|
47
|
+
) as run:
|
|
48
|
+
try:
|
|
49
|
+
transformer_event = self._handle(event)
|
|
50
|
+
|
|
51
|
+
transformer_payload = self._transformer_payload(transformer_event)
|
|
52
|
+
event_type = EventType.DATA_READY
|
|
53
|
+
|
|
54
|
+
data = transformer_payload.model_dump()
|
|
55
|
+
for data_type, data in data.items():
|
|
56
|
+
|
|
57
|
+
if data is not None:
|
|
58
|
+
source = self.artifact_uri(run, data_type)
|
|
59
|
+
event.context.set(
|
|
60
|
+
f"{data_type}_uri",
|
|
61
|
+
source
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
self.log_pd_data(
|
|
65
|
+
data=data,
|
|
66
|
+
context=data_type,
|
|
67
|
+
source=source
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
return Event(
|
|
71
|
+
type=event_type,
|
|
72
|
+
payload=transformer_payload,
|
|
73
|
+
context=event.context
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
except Exception as e:
|
|
77
|
+
raise TransformerError(f"Transformer failed with error: {e}")
|
|
78
|
+
|
|
79
|
+
@type_checker((TransformerPayload,) ,
|
|
80
|
+
"TransformerPayload must be of type TransformerPayload."
|
|
81
|
+
)
|
|
82
|
+
def _transformer_payload(self, event: Event) -> TransformerPayload:
|
|
83
|
+
|
|
84
|
+
return event.payload
|
|
85
|
+
|
|
86
|
+
def artifact_uri(self, run, data_type) -> str:
|
|
87
|
+
return f"{run.info.artifact_uri}/{TransformerArtifact().model_dump()[data_type]}"
|
|
File without changes
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
from collie.contracts.event import (
|
|
3
|
+
Event,
|
|
4
|
+
EventHandler,
|
|
5
|
+
EventType
|
|
6
|
+
)
|
|
7
|
+
from collie.contracts.mlflow import MLFlowComponentABC
|
|
8
|
+
from collie._common.decorator import type_checker
|
|
9
|
+
from collie.core.models import (
|
|
10
|
+
TunerArtifact,
|
|
11
|
+
TunerPayload
|
|
12
|
+
)
|
|
13
|
+
from collie._common.exceptions import TunerError
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Tuner(EventHandler, MLFlowComponentABC):
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
description: Optional[str] = None,
|
|
21
|
+
tags: Optional[dict] = None
|
|
22
|
+
) -> None:
|
|
23
|
+
"""
|
|
24
|
+
Initializes the Tuner component.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
description (Optional[str], optional): Description for the MLflow run. Defaults to None.
|
|
28
|
+
tags (Optional[dict], optional): Tags to associate with the MLflow run. Defaults to None.
|
|
29
|
+
"""
|
|
30
|
+
super().__init__()
|
|
31
|
+
self.description = description
|
|
32
|
+
self.tags = tags or {"component": "Tuner"}
|
|
33
|
+
|
|
34
|
+
def run(self, event: Event) -> None:
|
|
35
|
+
"""
|
|
36
|
+
Run the hyperparameter tuner component.
|
|
37
|
+
|
|
38
|
+
This method starts a new MLflow run, tunes the hyperparameters,
|
|
39
|
+
logs metrics, and sets the outputs.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
with self.start_run(
|
|
43
|
+
tags=self.tags,
|
|
44
|
+
run_name="Tuner",
|
|
45
|
+
log_system_metrics=True,
|
|
46
|
+
nested=True,
|
|
47
|
+
description=self.description
|
|
48
|
+
) as run:
|
|
49
|
+
try:
|
|
50
|
+
tuner_event = self._handle(event)
|
|
51
|
+
|
|
52
|
+
tuner_payload = self._tuner_payload(tuner_event)
|
|
53
|
+
hyperparameters = tuner_payload.model_dump()
|
|
54
|
+
|
|
55
|
+
self.mlflow.log_dict(
|
|
56
|
+
dictionary=hyperparameters,
|
|
57
|
+
artifact_file=TunerArtifact().hyperparameters
|
|
58
|
+
)
|
|
59
|
+
event.context.set(
|
|
60
|
+
"hyperparameters_uri",
|
|
61
|
+
self.artifact_path(run)
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
event_type = EventType.TUNING_DONE
|
|
65
|
+
|
|
66
|
+
return Event(
|
|
67
|
+
type=event_type,
|
|
68
|
+
payload=tuner_payload,
|
|
69
|
+
context=event.context
|
|
70
|
+
)
|
|
71
|
+
except Exception as e:
|
|
72
|
+
raise TunerError(f"Tuner failed with error: {e}") from e
|
|
73
|
+
|
|
74
|
+
@type_checker((TunerPayload,) ,
|
|
75
|
+
"TunerPayload must be of type TunerPayload."
|
|
76
|
+
)
|
|
77
|
+
def _tuner_payload(self, event: Event) -> TunerPayload:
|
|
78
|
+
|
|
79
|
+
tuner_payload: TunerPayload = event.payload
|
|
80
|
+
|
|
81
|
+
return tuner_payload
|
|
82
|
+
|
|
83
|
+
def artifact_path(self, run) -> str:
|
|
84
|
+
return f"{run.info.artifact_uri}/{TunerArtifact().hyperparameters}"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|