QuickLab-MCP 0.1.0__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.
- ml_mcp/__init__.py +3 -0
- ml_mcp/application/__init__.py +1 -0
- ml_mcp/application/analysis/__init__.py +15 -0
- ml_mcp/application/analysis/diagnostics.py +122 -0
- ml_mcp/application/analysis/service.py +155 -0
- ml_mcp/application/artifacts/__init__.py +5 -0
- ml_mcp/application/artifacts/service.py +149 -0
- ml_mcp/application/datasets/__init__.py +6 -0
- ml_mcp/application/datasets/service.py +178 -0
- ml_mcp/application/datasets/validator.py +156 -0
- ml_mcp/application/experiments/__init__.py +5 -0
- ml_mcp/application/experiments/service.py +254 -0
- ml_mcp/application/models/__init__.py +6 -0
- ml_mcp/application/models/catalog_seed.py +131 -0
- ml_mcp/application/models/service.py +177 -0
- ml_mcp/application/projects/__init__.py +5 -0
- ml_mcp/application/projects/service.py +93 -0
- ml_mcp/config/__init__.py +5 -0
- ml_mcp/config/settings.py +176 -0
- ml_mcp/domain/__init__.py +1 -0
- ml_mcp/domain/errors/__init__.py +39 -0
- ml_mcp/domain/errors/base.py +155 -0
- ml_mcp/domain/errors/codes.py +23 -0
- ml_mcp/domain/policies/__init__.py +16 -0
- ml_mcp/domain/policies/rbac.py +126 -0
- ml_mcp/domain/policies/tenant_isolation.py +34 -0
- ml_mcp/domain/policies/tool_policy.py +185 -0
- ml_mcp/domain/value_objects/__init__.py +41 -0
- ml_mcp/domain/value_objects/dataset.py +60 -0
- ml_mcp/domain/value_objects/experiment.py +131 -0
- ml_mcp/domain/value_objects/model.py +48 -0
- ml_mcp/infrastructure/__init__.py +1 -0
- ml_mcp/infrastructure/object_storage/__init__.py +5 -0
- ml_mcp/infrastructure/object_storage/s3.py +272 -0
- ml_mcp/infrastructure/postgres/__init__.py +14 -0
- ml_mcp/infrastructure/postgres/base.py +40 -0
- ml_mcp/infrastructure/postgres/models.py +356 -0
- ml_mcp/infrastructure/postgres/repositories/__init__.py +21 -0
- ml_mcp/infrastructure/postgres/repositories/artifacts.py +34 -0
- ml_mcp/infrastructure/postgres/repositories/audit.py +34 -0
- ml_mcp/infrastructure/postgres/repositories/datasets.py +85 -0
- ml_mcp/infrastructure/postgres/repositories/experiments.py +134 -0
- ml_mcp/infrastructure/postgres/repositories/metrics.py +61 -0
- ml_mcp/infrastructure/postgres/repositories/models.py +61 -0
- ml_mcp/infrastructure/postgres/repositories/outbox.py +47 -0
- ml_mcp/infrastructure/postgres/repositories/projects.py +83 -0
- ml_mcp/infrastructure/postgres/session.py +117 -0
- ml_mcp/infrastructure/queue/__init__.py +6 -0
- ml_mcp/infrastructure/queue/outbox_processor.py +67 -0
- ml_mcp/infrastructure/queue/task_queue.py +98 -0
- ml_mcp/infrastructure/redis/__init__.py +11 -0
- ml_mcp/infrastructure/redis/client.py +64 -0
- ml_mcp/infrastructure/redis/rate_limiter.py +129 -0
- ml_mcp/infrastructure/telemetry/__init__.py +23 -0
- ml_mcp/infrastructure/telemetry/logging.py +155 -0
- ml_mcp/infrastructure/telemetry/otel.py +141 -0
- ml_mcp/mcp/__init__.py +1 -0
- ml_mcp/mcp/prompts.py +38 -0
- ml_mcp/mcp/resources.py +35 -0
- ml_mcp/mcp/schemas/__init__.py +1 -0
- ml_mcp/mcp/schemas/tools.py +124 -0
- ml_mcp/mcp/tools/__init__.py +63 -0
- ml_mcp/mcp/tools/analysis.py +37 -0
- ml_mcp/mcp/tools/datasets.py +82 -0
- ml_mcp/mcp/tools/discovery.py +82 -0
- ml_mcp/mcp/tools/experiments.py +96 -0
- ml_mcp/mcp/tools/projects.py +39 -0
- ml_mcp/mcp/tools/results.py +59 -0
- ml_mcp/server/__init__.py +1 -0
- ml_mcp/server/app.py +488 -0
- ml_mcp/server/auth.py +146 -0
- ml_mcp/server/context.py +59 -0
- ml_mcp/server/http.py +183 -0
- ml_mcp/server/middleware.py +121 -0
- ml_mcp/server/stdio.py +42 -0
- ml_mcp/workers/__init__.py +1 -0
- ml_mcp/workers/daemon.py +130 -0
- ml_mcp/workers/metrics.py +110 -0
- ml_mcp/workers/preprocessing.py +170 -0
- ml_mcp/workers/runner.py +250 -0
- ml_mcp/workers/sandbox.py +35 -0
- ml_mcp/workers/trainers/__init__.py +52 -0
- ml_mcp/workers/trainers/base.py +52 -0
- ml_mcp/workers/trainers/catboost_model.py +85 -0
- ml_mcp/workers/trainers/lightgbm_model.py +81 -0
- ml_mcp/workers/trainers/sklearn_models.py +266 -0
- ml_mcp/workers/trainers/xgboost_model.py +82 -0
- quicklab_mcp-0.1.0.dist-info/METADATA +540 -0
- quicklab_mcp-0.1.0.dist-info/RECORD +92 -0
- quicklab_mcp-0.1.0.dist-info/WHEEL +5 -0
- quicklab_mcp-0.1.0.dist-info/entry_points.txt +4 -0
- quicklab_mcp-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""LightGBM model trainer."""
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import joblib
|
|
7
|
+
import lightgbm as lgb
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
from ml_mcp.domain.value_objects import TaskType
|
|
11
|
+
from ml_mcp.workers.metrics import MetricsCalculator
|
|
12
|
+
from ml_mcp.workers.trainers.base import BaseModelTrainer, TrainingResult
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class LightGBMTrainer(BaseModelTrainer):
|
|
16
|
+
"""Trainer for LightGBM gradient boosted trees."""
|
|
17
|
+
|
|
18
|
+
def train(
|
|
19
|
+
self,
|
|
20
|
+
X_tr: np.ndarray,
|
|
21
|
+
y_tr: np.ndarray,
|
|
22
|
+
X_val: np.ndarray,
|
|
23
|
+
y_val: np.ndarray,
|
|
24
|
+
task_type: TaskType,
|
|
25
|
+
hyperparameters: dict[str, Any],
|
|
26
|
+
random_seed: int = 42,
|
|
27
|
+
max_cpu_cores: int | None = None,
|
|
28
|
+
) -> TrainingResult:
|
|
29
|
+
n_jobs = max_cpu_cores if max_cpu_cores is not None else hyperparameters.get("n_jobs", -1)
|
|
30
|
+
params = {
|
|
31
|
+
"random_state": random_seed,
|
|
32
|
+
"n_estimators": hyperparameters.get("n_estimators", 100),
|
|
33
|
+
"num_leaves": hyperparameters.get("num_leaves", 31),
|
|
34
|
+
"learning_rate": hyperparameters.get("learning_rate", 0.1),
|
|
35
|
+
"n_jobs": n_jobs,
|
|
36
|
+
"verbose": -1,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if task_type in (TaskType.BINARY_CLASSIFICATION, TaskType.MULTICLASS_CLASSIFICATION):
|
|
40
|
+
model = lgb.LGBMClassifier(**params)
|
|
41
|
+
model.fit(X_tr, y_tr, eval_set=[(X_val, y_val)])
|
|
42
|
+
|
|
43
|
+
train_pred = model.predict(X_tr)
|
|
44
|
+
train_proba = model.predict_proba(X_tr)
|
|
45
|
+
val_pred = model.predict(X_val)
|
|
46
|
+
val_proba = model.predict_proba(X_val)
|
|
47
|
+
|
|
48
|
+
train_metrics = MetricsCalculator.compute_classification_metrics(
|
|
49
|
+
y_tr, train_pred, train_proba, task_type
|
|
50
|
+
)
|
|
51
|
+
val_metrics = MetricsCalculator.compute_classification_metrics(
|
|
52
|
+
y_val, val_pred, val_proba, task_type
|
|
53
|
+
)
|
|
54
|
+
else:
|
|
55
|
+
model = lgb.LGBMRegressor(**params)
|
|
56
|
+
model.fit(X_tr, y_tr, eval_set=[(X_val, y_val)])
|
|
57
|
+
|
|
58
|
+
train_pred = model.predict(X_tr)
|
|
59
|
+
val_pred = model.predict(X_val)
|
|
60
|
+
val_proba = None
|
|
61
|
+
|
|
62
|
+
train_metrics = MetricsCalculator.compute_regression_metrics(y_tr, train_pred)
|
|
63
|
+
val_metrics = MetricsCalculator.compute_regression_metrics(y_val, val_pred)
|
|
64
|
+
|
|
65
|
+
feat_imp: dict[str, float] = {}
|
|
66
|
+
if hasattr(model, "feature_importances_"):
|
|
67
|
+
feat_imp = {f"feature_{i}": float(v) for i, v in enumerate(model.feature_importances_)}
|
|
68
|
+
|
|
69
|
+
bio = io.BytesIO()
|
|
70
|
+
joblib.dump(model, bio)
|
|
71
|
+
|
|
72
|
+
return TrainingResult(
|
|
73
|
+
model=model,
|
|
74
|
+
task_type=task_type,
|
|
75
|
+
train_metrics=train_metrics,
|
|
76
|
+
validation_metrics=val_metrics,
|
|
77
|
+
feature_importances=feat_imp,
|
|
78
|
+
val_predictions=val_pred,
|
|
79
|
+
val_probabilities=val_proba,
|
|
80
|
+
model_artifact_bytes=bio.getvalue(),
|
|
81
|
+
)
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"""Scikit-Learn model trainers: Logistic Regression, Random Forest, Linear SVM, and MLP."""
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import joblib
|
|
7
|
+
import numpy as np
|
|
8
|
+
from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor
|
|
9
|
+
from sklearn.linear_model import LogisticRegression
|
|
10
|
+
from sklearn.neural_network import MLPClassifier, MLPRegressor
|
|
11
|
+
from sklearn.svm import LinearSVC, LinearSVR
|
|
12
|
+
|
|
13
|
+
from ml_mcp.domain.value_objects import TaskType
|
|
14
|
+
from ml_mcp.workers.metrics import MetricsCalculator
|
|
15
|
+
from ml_mcp.workers.trainers.base import BaseModelTrainer, TrainingResult
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class LogisticRegressionTrainer(BaseModelTrainer):
|
|
19
|
+
"""Trainer for Logistic Regression."""
|
|
20
|
+
|
|
21
|
+
def train(
|
|
22
|
+
self,
|
|
23
|
+
X_tr: np.ndarray,
|
|
24
|
+
y_tr: np.ndarray,
|
|
25
|
+
X_val: np.ndarray,
|
|
26
|
+
y_val: np.ndarray,
|
|
27
|
+
task_type: TaskType,
|
|
28
|
+
hyperparameters: dict[str, Any],
|
|
29
|
+
random_seed: int = 42,
|
|
30
|
+
max_cpu_cores: int | None = None,
|
|
31
|
+
) -> TrainingResult:
|
|
32
|
+
params: dict[str, Any] = {
|
|
33
|
+
"random_state": random_seed,
|
|
34
|
+
"max_iter": hyperparameters.get("max_iter", 200),
|
|
35
|
+
"C": hyperparameters.get("C", 1.0),
|
|
36
|
+
"penalty": hyperparameters.get("penalty", "l2"),
|
|
37
|
+
}
|
|
38
|
+
if "n_jobs" in hyperparameters:
|
|
39
|
+
params["n_jobs"] = hyperparameters["n_jobs"]
|
|
40
|
+
model = LogisticRegression(**params)
|
|
41
|
+
model.fit(X_tr, y_tr)
|
|
42
|
+
|
|
43
|
+
train_pred = model.predict(X_tr)
|
|
44
|
+
train_proba = model.predict_proba(X_tr)
|
|
45
|
+
val_pred = model.predict(X_val)
|
|
46
|
+
val_proba = model.predict_proba(X_val)
|
|
47
|
+
|
|
48
|
+
train_metrics = MetricsCalculator.compute_classification_metrics(
|
|
49
|
+
y_tr, train_pred, train_proba, task_type
|
|
50
|
+
)
|
|
51
|
+
val_metrics = MetricsCalculator.compute_classification_metrics(
|
|
52
|
+
y_val, val_pred, val_proba, task_type
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Feature importances from coefficients
|
|
56
|
+
feat_imp: dict[str, float] = {}
|
|
57
|
+
if hasattr(model, "coef_"):
|
|
58
|
+
coef = np.abs(model.coef_).mean(axis=0) if model.coef_.ndim > 1 else np.abs(model.coef_)
|
|
59
|
+
feat_imp = {f"feature_{i}": float(v) for i, v in enumerate(coef)}
|
|
60
|
+
|
|
61
|
+
bio = io.BytesIO()
|
|
62
|
+
joblib.dump(model, bio)
|
|
63
|
+
|
|
64
|
+
return TrainingResult(
|
|
65
|
+
model=model,
|
|
66
|
+
task_type=task_type,
|
|
67
|
+
train_metrics=train_metrics,
|
|
68
|
+
validation_metrics=val_metrics,
|
|
69
|
+
feature_importances=feat_imp,
|
|
70
|
+
val_predictions=val_pred,
|
|
71
|
+
val_probabilities=val_proba,
|
|
72
|
+
model_artifact_bytes=bio.getvalue(),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class RandomForestTrainer(BaseModelTrainer):
|
|
77
|
+
"""Trainer for Random Forest classification and regression."""
|
|
78
|
+
|
|
79
|
+
def train(
|
|
80
|
+
self,
|
|
81
|
+
X_tr: np.ndarray,
|
|
82
|
+
y_tr: np.ndarray,
|
|
83
|
+
X_val: np.ndarray,
|
|
84
|
+
y_val: np.ndarray,
|
|
85
|
+
task_type: TaskType,
|
|
86
|
+
hyperparameters: dict[str, Any],
|
|
87
|
+
random_seed: int = 42,
|
|
88
|
+
max_cpu_cores: int | None = None,
|
|
89
|
+
) -> TrainingResult:
|
|
90
|
+
n_jobs = max_cpu_cores if max_cpu_cores is not None else hyperparameters.get("n_jobs", -1)
|
|
91
|
+
params = {
|
|
92
|
+
"random_state": random_seed,
|
|
93
|
+
"n_estimators": hyperparameters.get("n_estimators", 100),
|
|
94
|
+
"max_depth": hyperparameters.get("max_depth", 10),
|
|
95
|
+
"min_samples_split": hyperparameters.get("min_samples_split", 2),
|
|
96
|
+
"n_jobs": n_jobs,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if task_type in (TaskType.BINARY_CLASSIFICATION, TaskType.MULTICLASS_CLASSIFICATION):
|
|
100
|
+
model = RandomForestClassifier(**params)
|
|
101
|
+
model.fit(X_tr, y_tr)
|
|
102
|
+
|
|
103
|
+
train_pred = model.predict(X_tr)
|
|
104
|
+
train_proba = model.predict_proba(X_tr)
|
|
105
|
+
val_pred = model.predict(X_val)
|
|
106
|
+
val_proba = model.predict_proba(X_val)
|
|
107
|
+
|
|
108
|
+
train_metrics = MetricsCalculator.compute_classification_metrics(
|
|
109
|
+
y_tr, train_pred, train_proba, task_type
|
|
110
|
+
)
|
|
111
|
+
val_metrics = MetricsCalculator.compute_classification_metrics(
|
|
112
|
+
y_val, val_pred, val_proba, task_type
|
|
113
|
+
)
|
|
114
|
+
else:
|
|
115
|
+
model = RandomForestRegressor(**params)
|
|
116
|
+
model.fit(X_tr, y_tr)
|
|
117
|
+
|
|
118
|
+
train_pred = model.predict(X_tr)
|
|
119
|
+
val_pred = model.predict(X_val)
|
|
120
|
+
val_proba = None
|
|
121
|
+
|
|
122
|
+
train_metrics = MetricsCalculator.compute_regression_metrics(y_tr, train_pred)
|
|
123
|
+
val_metrics = MetricsCalculator.compute_regression_metrics(y_val, val_pred)
|
|
124
|
+
|
|
125
|
+
feat_imp: dict[str, float] = {}
|
|
126
|
+
if hasattr(model, "feature_importances_"):
|
|
127
|
+
feat_imp = {f"feature_{i}": float(v) for i, v in enumerate(model.feature_importances_)}
|
|
128
|
+
|
|
129
|
+
bio = io.BytesIO()
|
|
130
|
+
joblib.dump(model, bio)
|
|
131
|
+
|
|
132
|
+
return TrainingResult(
|
|
133
|
+
model=model,
|
|
134
|
+
task_type=task_type,
|
|
135
|
+
train_metrics=train_metrics,
|
|
136
|
+
validation_metrics=val_metrics,
|
|
137
|
+
feature_importances=feat_imp,
|
|
138
|
+
val_predictions=val_pred,
|
|
139
|
+
val_probabilities=val_proba,
|
|
140
|
+
model_artifact_bytes=bio.getvalue(),
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class LinearSVMTrainer(BaseModelTrainer):
|
|
145
|
+
"""Trainer for Linear SVM."""
|
|
146
|
+
|
|
147
|
+
def train(
|
|
148
|
+
self,
|
|
149
|
+
X_tr: np.ndarray,
|
|
150
|
+
y_tr: np.ndarray,
|
|
151
|
+
X_val: np.ndarray,
|
|
152
|
+
y_val: np.ndarray,
|
|
153
|
+
task_type: TaskType,
|
|
154
|
+
hyperparameters: dict[str, Any],
|
|
155
|
+
random_seed: int = 42,
|
|
156
|
+
max_cpu_cores: int | None = None,
|
|
157
|
+
) -> TrainingResult:
|
|
158
|
+
params = {
|
|
159
|
+
"random_state": random_seed,
|
|
160
|
+
"C": hyperparameters.get("C", 1.0),
|
|
161
|
+
"max_iter": hyperparameters.get("max_iter", 1000),
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if task_type in (TaskType.BINARY_CLASSIFICATION, TaskType.MULTICLASS_CLASSIFICATION):
|
|
165
|
+
model = LinearSVC(**params)
|
|
166
|
+
model.fit(X_tr, y_tr)
|
|
167
|
+
|
|
168
|
+
train_pred = model.predict(X_tr)
|
|
169
|
+
val_pred = model.predict(X_val)
|
|
170
|
+
train_metrics = MetricsCalculator.compute_classification_metrics(
|
|
171
|
+
y_tr, train_pred, None, task_type
|
|
172
|
+
)
|
|
173
|
+
val_metrics = MetricsCalculator.compute_classification_metrics(
|
|
174
|
+
y_val, val_pred, None, task_type
|
|
175
|
+
)
|
|
176
|
+
val_proba = None
|
|
177
|
+
else:
|
|
178
|
+
model = LinearSVR(**params)
|
|
179
|
+
model.fit(X_tr, y_tr)
|
|
180
|
+
|
|
181
|
+
train_pred = model.predict(X_tr)
|
|
182
|
+
val_pred = model.predict(X_val)
|
|
183
|
+
train_metrics = MetricsCalculator.compute_regression_metrics(y_tr, train_pred)
|
|
184
|
+
val_metrics = MetricsCalculator.compute_regression_metrics(y_val, val_pred)
|
|
185
|
+
val_proba = None
|
|
186
|
+
|
|
187
|
+
feat_imp: dict[str, float] = {}
|
|
188
|
+
if hasattr(model, "coef_"):
|
|
189
|
+
coef = np.abs(model.coef_).mean(axis=0) if model.coef_.ndim > 1 else np.abs(model.coef_)
|
|
190
|
+
feat_imp = {f"feature_{i}": float(v) for i, v in enumerate(coef)}
|
|
191
|
+
|
|
192
|
+
bio = io.BytesIO()
|
|
193
|
+
joblib.dump(model, bio)
|
|
194
|
+
|
|
195
|
+
return TrainingResult(
|
|
196
|
+
model=model,
|
|
197
|
+
task_type=task_type,
|
|
198
|
+
train_metrics=train_metrics,
|
|
199
|
+
validation_metrics=val_metrics,
|
|
200
|
+
feature_importances=feat_imp,
|
|
201
|
+
val_predictions=val_pred,
|
|
202
|
+
val_probabilities=val_proba,
|
|
203
|
+
model_artifact_bytes=bio.getvalue(),
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class MLPTrainer(BaseModelTrainer):
|
|
208
|
+
"""Trainer for Multi-Layer Perceptron (neural network)."""
|
|
209
|
+
|
|
210
|
+
def train(
|
|
211
|
+
self,
|
|
212
|
+
X_tr: np.ndarray,
|
|
213
|
+
y_tr: np.ndarray,
|
|
214
|
+
X_val: np.ndarray,
|
|
215
|
+
y_val: np.ndarray,
|
|
216
|
+
task_type: TaskType,
|
|
217
|
+
hyperparameters: dict[str, Any],
|
|
218
|
+
random_seed: int = 42,
|
|
219
|
+
max_cpu_cores: int | None = None,
|
|
220
|
+
) -> TrainingResult:
|
|
221
|
+
params = {
|
|
222
|
+
"random_state": random_seed,
|
|
223
|
+
"alpha": hyperparameters.get("alpha", 0.0001),
|
|
224
|
+
"max_iter": hyperparameters.get("max_iter", 200),
|
|
225
|
+
"hidden_layer_sizes": hyperparameters.get("hidden_layer_sizes", (100,)),
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if task_type in (TaskType.BINARY_CLASSIFICATION, TaskType.MULTICLASS_CLASSIFICATION):
|
|
229
|
+
model = MLPClassifier(**params)
|
|
230
|
+
model.fit(X_tr, y_tr)
|
|
231
|
+
|
|
232
|
+
train_pred = model.predict(X_tr)
|
|
233
|
+
train_proba = model.predict_proba(X_tr)
|
|
234
|
+
val_pred = model.predict(X_val)
|
|
235
|
+
val_proba = model.predict_proba(X_val)
|
|
236
|
+
|
|
237
|
+
train_metrics = MetricsCalculator.compute_classification_metrics(
|
|
238
|
+
y_tr, train_pred, train_proba, task_type
|
|
239
|
+
)
|
|
240
|
+
val_metrics = MetricsCalculator.compute_classification_metrics(
|
|
241
|
+
y_val, val_pred, val_proba, task_type
|
|
242
|
+
)
|
|
243
|
+
else:
|
|
244
|
+
model = MLPRegressor(**params)
|
|
245
|
+
model.fit(X_tr, y_tr)
|
|
246
|
+
|
|
247
|
+
train_pred = model.predict(X_tr)
|
|
248
|
+
val_pred = model.predict(X_val)
|
|
249
|
+
val_proba = None
|
|
250
|
+
|
|
251
|
+
train_metrics = MetricsCalculator.compute_regression_metrics(y_tr, train_pred)
|
|
252
|
+
val_metrics = MetricsCalculator.compute_regression_metrics(y_val, val_pred)
|
|
253
|
+
|
|
254
|
+
bio = io.BytesIO()
|
|
255
|
+
joblib.dump(model, bio)
|
|
256
|
+
|
|
257
|
+
return TrainingResult(
|
|
258
|
+
model=model,
|
|
259
|
+
task_type=task_type,
|
|
260
|
+
train_metrics=train_metrics,
|
|
261
|
+
validation_metrics=val_metrics,
|
|
262
|
+
feature_importances={},
|
|
263
|
+
val_predictions=val_pred,
|
|
264
|
+
val_probabilities=val_proba,
|
|
265
|
+
model_artifact_bytes=bio.getvalue(),
|
|
266
|
+
)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""XGBoost model trainer."""
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import joblib
|
|
7
|
+
import numpy as np
|
|
8
|
+
import xgboost as xgb
|
|
9
|
+
|
|
10
|
+
from ml_mcp.domain.value_objects import TaskType
|
|
11
|
+
from ml_mcp.workers.metrics import MetricsCalculator
|
|
12
|
+
from ml_mcp.workers.trainers.base import BaseModelTrainer, TrainingResult
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class XGBoostTrainer(BaseModelTrainer):
|
|
16
|
+
"""Trainer for XGBoost gradient boosted trees."""
|
|
17
|
+
|
|
18
|
+
def train(
|
|
19
|
+
self,
|
|
20
|
+
X_tr: np.ndarray,
|
|
21
|
+
y_tr: np.ndarray,
|
|
22
|
+
X_val: np.ndarray,
|
|
23
|
+
y_val: np.ndarray,
|
|
24
|
+
task_type: TaskType,
|
|
25
|
+
hyperparameters: dict[str, Any],
|
|
26
|
+
random_seed: int = 42,
|
|
27
|
+
max_cpu_cores: int | None = None,
|
|
28
|
+
) -> TrainingResult:
|
|
29
|
+
n_jobs = max_cpu_cores if max_cpu_cores is not None else hyperparameters.get("n_jobs", -1)
|
|
30
|
+
params = {
|
|
31
|
+
"random_state": random_seed,
|
|
32
|
+
"n_estimators": hyperparameters.get("n_estimators", 100),
|
|
33
|
+
"max_depth": hyperparameters.get("max_depth", 6),
|
|
34
|
+
"learning_rate": hyperparameters.get("learning_rate", 0.1),
|
|
35
|
+
"subsample": hyperparameters.get("subsample", 1.0),
|
|
36
|
+
"n_jobs": n_jobs,
|
|
37
|
+
"verbosity": 0,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if task_type in (TaskType.BINARY_CLASSIFICATION, TaskType.MULTICLASS_CLASSIFICATION):
|
|
41
|
+
model = xgb.XGBClassifier(**params)
|
|
42
|
+
model.fit(X_tr, y_tr, eval_set=[(X_val, y_val)], verbose=False)
|
|
43
|
+
|
|
44
|
+
train_pred = model.predict(X_tr)
|
|
45
|
+
train_proba = model.predict_proba(X_tr)
|
|
46
|
+
val_pred = model.predict(X_val)
|
|
47
|
+
val_proba = model.predict_proba(X_val)
|
|
48
|
+
|
|
49
|
+
train_metrics = MetricsCalculator.compute_classification_metrics(
|
|
50
|
+
y_tr, train_pred, train_proba, task_type
|
|
51
|
+
)
|
|
52
|
+
val_metrics = MetricsCalculator.compute_classification_metrics(
|
|
53
|
+
y_val, val_pred, val_proba, task_type
|
|
54
|
+
)
|
|
55
|
+
else:
|
|
56
|
+
model = xgb.XGBRegressor(**params)
|
|
57
|
+
model.fit(X_tr, y_tr, eval_set=[(X_val, y_val)], verbose=False)
|
|
58
|
+
|
|
59
|
+
train_pred = model.predict(X_tr)
|
|
60
|
+
val_pred = model.predict(X_val)
|
|
61
|
+
val_proba = None
|
|
62
|
+
|
|
63
|
+
train_metrics = MetricsCalculator.compute_regression_metrics(y_tr, train_pred)
|
|
64
|
+
val_metrics = MetricsCalculator.compute_regression_metrics(y_val, val_pred)
|
|
65
|
+
|
|
66
|
+
feat_imp: dict[str, float] = {}
|
|
67
|
+
if hasattr(model, "feature_importances_"):
|
|
68
|
+
feat_imp = {f"feature_{i}": float(v) for i, v in enumerate(model.feature_importances_)}
|
|
69
|
+
|
|
70
|
+
bio = io.BytesIO()
|
|
71
|
+
joblib.dump(model, bio)
|
|
72
|
+
|
|
73
|
+
return TrainingResult(
|
|
74
|
+
model=model,
|
|
75
|
+
task_type=task_type,
|
|
76
|
+
train_metrics=train_metrics,
|
|
77
|
+
validation_metrics=val_metrics,
|
|
78
|
+
feature_importances=feat_imp,
|
|
79
|
+
val_predictions=val_pred,
|
|
80
|
+
val_probabilities=val_proba,
|
|
81
|
+
model_artifact_bytes=bio.getvalue(),
|
|
82
|
+
)
|