proxyml 0.4.1__tar.gz → 0.5.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.
- {proxyml-0.4.1 → proxyml-0.5.0}/PKG-INFO +1 -1
- {proxyml-0.4.1 → proxyml-0.5.0}/pyproject.toml +1 -1
- {proxyml-0.4.1 → proxyml-0.5.0}/src/proxyml/local/__init__.py +2 -0
- {proxyml-0.4.1 → proxyml-0.5.0}/src/proxyml/local/challenger.py +60 -0
- {proxyml-0.4.1 → proxyml-0.5.0}/src/proxyml.egg-info/PKG-INFO +1 -1
- {proxyml-0.4.1 → proxyml-0.5.0}/tests/test_local_challenger.py +89 -0
- {proxyml-0.4.1 → proxyml-0.5.0}/LICENSE +0 -0
- {proxyml-0.4.1 → proxyml-0.5.0}/README.md +0 -0
- {proxyml-0.4.1 → proxyml-0.5.0}/setup.cfg +0 -0
- {proxyml-0.4.1 → proxyml-0.5.0}/src/proxyml/__init__.py +0 -0
- {proxyml-0.4.1 → proxyml-0.5.0}/src/proxyml/client.py +0 -0
- {proxyml-0.4.1 → proxyml-0.5.0}/src/proxyml/schema_builder.py +0 -0
- {proxyml-0.4.1 → proxyml-0.5.0}/src/proxyml.egg-info/SOURCES.txt +0 -0
- {proxyml-0.4.1 → proxyml-0.5.0}/src/proxyml.egg-info/dependency_links.txt +0 -0
- {proxyml-0.4.1 → proxyml-0.5.0}/src/proxyml.egg-info/requires.txt +0 -0
- {proxyml-0.4.1 → proxyml-0.5.0}/src/proxyml.egg-info/top_level.txt +0 -0
- {proxyml-0.4.1 → proxyml-0.5.0}/tests/test_client.py +0 -0
- {proxyml-0.4.1 → proxyml-0.5.0}/tests/test_dependency_boundaries.py +0 -0
- {proxyml-0.4.1 → proxyml-0.5.0}/tests/test_schema_builder.py +0 -0
|
@@ -11,6 +11,7 @@ from proxyml.local.challenger import (
|
|
|
11
11
|
Rung,
|
|
12
12
|
TrainedChallenger,
|
|
13
13
|
score_champion,
|
|
14
|
+
to_challenger_upload,
|
|
14
15
|
train_auto_challenger,
|
|
15
16
|
train_challenger,
|
|
16
17
|
)
|
|
@@ -19,6 +20,7 @@ __all__ = [
|
|
|
19
20
|
"train_challenger",
|
|
20
21
|
"train_auto_challenger",
|
|
21
22
|
"score_champion",
|
|
23
|
+
"to_challenger_upload",
|
|
22
24
|
"Complexity",
|
|
23
25
|
"Rung",
|
|
24
26
|
"TrainedChallenger",
|
|
@@ -12,6 +12,7 @@ from __future__ import annotations
|
|
|
12
12
|
|
|
13
13
|
from dataclasses import dataclass, replace
|
|
14
14
|
from enum import Enum
|
|
15
|
+
from importlib.metadata import version as _pkg_version
|
|
15
16
|
from pathlib import Path
|
|
16
17
|
from typing import Any, Callable, Literal
|
|
17
18
|
|
|
@@ -113,6 +114,7 @@ LADDERS: dict[Complexity, Rung] = {
|
|
|
113
114
|
class TrainedChallenger:
|
|
114
115
|
pipeline: Pipeline
|
|
115
116
|
task: Literal["classification", "regression"]
|
|
117
|
+
complexity: Complexity
|
|
116
118
|
metrics: dict[str, float]
|
|
117
119
|
hyperparameters: dict[str, Any]
|
|
118
120
|
export: SurrogateExport
|
|
@@ -196,6 +198,7 @@ def train_challenger(
|
|
|
196
198
|
return TrainedChallenger(
|
|
197
199
|
pipeline=pipeline,
|
|
198
200
|
task=resolved_task,
|
|
201
|
+
complexity=complexity,
|
|
199
202
|
metrics=metrics,
|
|
200
203
|
hyperparameters=hyperparameters,
|
|
201
204
|
export=export,
|
|
@@ -224,6 +227,63 @@ def score_champion(
|
|
|
224
227
|
return score_predictions(np.asarray(labels), np.asarray(predictions), task=task)
|
|
225
228
|
|
|
226
229
|
|
|
230
|
+
def to_challenger_upload(
|
|
231
|
+
result: TrainedChallenger,
|
|
232
|
+
*,
|
|
233
|
+
n_samples: int,
|
|
234
|
+
champion_metrics: dict[str, float] | None = None,
|
|
235
|
+
sdk_version: str | None = None,
|
|
236
|
+
proxyml_core_version: str | None = None,
|
|
237
|
+
) -> dict[str, Any]:
|
|
238
|
+
"""Assemble the JSON-serializable payload for a challenger upload.
|
|
239
|
+
|
|
240
|
+
Matches the shape ProxyML's dashboard/API expects at
|
|
241
|
+
``POST /app/projects/{id}/challenger`` — handles the mechanical assembly
|
|
242
|
+
(serializing the export, stamping SDK/core versions, converting
|
|
243
|
+
``complexity`` to a plain string) so callers don't have to hand-roll it.
|
|
244
|
+
The result is plain ``dict``/``str``/``float`` data, ready for
|
|
245
|
+
``json.dump`` — upload it either by POSTing it directly, or by saving it
|
|
246
|
+
to a file and using the dashboard's "Upload challenger" button.
|
|
247
|
+
|
|
248
|
+
``champion_metrics`` is optional: pass ``None`` (the default) to get a
|
|
249
|
+
self-contained export of the challenger alone — e.g. to save/share it
|
|
250
|
+
before you have a champion to compare against — and fill in
|
|
251
|
+
``champion_metrics`` later. The upload endpoint itself still requires
|
|
252
|
+
``champion_metrics`` at upload time; this function just doesn't force you
|
|
253
|
+
to have it up front.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
result: output of ``train_challenger()``/``train_auto_challenger()``.
|
|
257
|
+
n_samples: size of the evaluation set both ``result.metrics`` and
|
|
258
|
+
``champion_metrics`` were scored on. Not derived automatically —
|
|
259
|
+
``TrainedChallenger`` doesn't retain its internal held-out split,
|
|
260
|
+
and ``champion_metrics`` typically comes from a separate
|
|
261
|
+
``score_champion()`` call the two need to share, so the caller is
|
|
262
|
+
the only one who actually knows this number.
|
|
263
|
+
champion_metrics: the champion's real-world performance, from
|
|
264
|
+
``score_champion()`` — same metric keys as ``result.metrics``.
|
|
265
|
+
Omit if you don't have it yet.
|
|
266
|
+
sdk_version: defaults to the installed ``proxyml`` version.
|
|
267
|
+
proxyml_core_version: defaults to the installed ``proxyml-core`` version.
|
|
268
|
+
"""
|
|
269
|
+
if sdk_version is None:
|
|
270
|
+
sdk_version = _pkg_version("proxyml")
|
|
271
|
+
if proxyml_core_version is None:
|
|
272
|
+
proxyml_core_version = _pkg_version("proxyml-core")
|
|
273
|
+
|
|
274
|
+
payload: dict[str, Any] = {
|
|
275
|
+
"export": result.export.to_dict(),
|
|
276
|
+
"challenger_metrics": result.metrics,
|
|
277
|
+
"n_samples": n_samples,
|
|
278
|
+
"complexity": result.complexity.value,
|
|
279
|
+
"sdk_version": sdk_version,
|
|
280
|
+
"proxyml_core_version": proxyml_core_version,
|
|
281
|
+
}
|
|
282
|
+
if champion_metrics is not None:
|
|
283
|
+
payload["champion_metrics"] = champion_metrics
|
|
284
|
+
return payload
|
|
285
|
+
|
|
286
|
+
|
|
227
287
|
def train_auto_challenger(
|
|
228
288
|
data: str | Path | pd.DataFrame,
|
|
229
289
|
target_col: str,
|
|
@@ -9,6 +9,7 @@ from proxyml.local import (
|
|
|
9
9
|
LADDERS,
|
|
10
10
|
TrainedChallenger,
|
|
11
11
|
score_champion,
|
|
12
|
+
to_challenger_upload,
|
|
12
13
|
train_auto_challenger,
|
|
13
14
|
train_challenger,
|
|
14
15
|
)
|
|
@@ -197,6 +198,94 @@ def test_score_champion_uses_same_scoring_as_train_challenger():
|
|
|
197
198
|
assert reproduced_metrics["r2"] == pytest.approx(r2_score(target, y_pred))
|
|
198
199
|
|
|
199
200
|
|
|
201
|
+
def test_train_challenger_result_carries_complexity():
|
|
202
|
+
schema = _schema()
|
|
203
|
+
df = _df(seed=13)
|
|
204
|
+
target = df["age"] * 0.5 + df["income"] * 0.0001
|
|
205
|
+
|
|
206
|
+
result = train_challenger(df, target, schema, complexity=Complexity.FLEXIBLE, task="regression")
|
|
207
|
+
|
|
208
|
+
assert result.complexity is Complexity.FLEXIBLE
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def test_train_auto_challenger_result_carries_complexity():
|
|
212
|
+
df = _labeled_df(seed=14)
|
|
213
|
+
result = train_auto_challenger(df, "approved", task="classification", complexity=Complexity.SIMPLE)
|
|
214
|
+
|
|
215
|
+
assert result.complexity is Complexity.SIMPLE
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def test_to_challenger_upload_shape_without_champion_metrics():
|
|
219
|
+
schema = _schema()
|
|
220
|
+
df = _df(seed=15)
|
|
221
|
+
target = df["age"] * 0.5 + df["income"] * 0.0001
|
|
222
|
+
result = train_challenger(df, target, schema, complexity=Complexity.MODERATE, task="regression")
|
|
223
|
+
|
|
224
|
+
payload = to_challenger_upload(result, n_samples=40)
|
|
225
|
+
|
|
226
|
+
assert payload["export"] == result.export.to_dict()
|
|
227
|
+
assert payload["challenger_metrics"] == result.metrics
|
|
228
|
+
assert payload["n_samples"] == 40
|
|
229
|
+
assert payload["complexity"] == "moderate"
|
|
230
|
+
assert "champion_metrics" not in payload
|
|
231
|
+
assert isinstance(payload["sdk_version"], str) and payload["sdk_version"]
|
|
232
|
+
assert isinstance(payload["proxyml_core_version"], str) and payload["proxyml_core_version"]
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def test_to_challenger_upload_includes_champion_metrics_when_given():
|
|
236
|
+
schema = _schema()
|
|
237
|
+
df = _df(seed=16)
|
|
238
|
+
target = df["age"] * 0.5 + df["income"] * 0.0001
|
|
239
|
+
result = train_challenger(df, target, schema, complexity=Complexity.MODERATE, task="regression")
|
|
240
|
+
champion_metrics = score_champion(target, target, task="regression")
|
|
241
|
+
|
|
242
|
+
payload = to_challenger_upload(result, n_samples=40, champion_metrics=champion_metrics)
|
|
243
|
+
|
|
244
|
+
assert payload["champion_metrics"] == champion_metrics
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def test_to_challenger_upload_defaults_versions_to_installed_packages():
|
|
248
|
+
from importlib.metadata import version as pkg_version
|
|
249
|
+
|
|
250
|
+
schema = _schema()
|
|
251
|
+
df = _df(seed=17)
|
|
252
|
+
target = df["age"] * 0.5 + df["income"] * 0.0001
|
|
253
|
+
result = train_challenger(df, target, schema, task="regression")
|
|
254
|
+
|
|
255
|
+
payload = to_challenger_upload(result, n_samples=10)
|
|
256
|
+
|
|
257
|
+
assert payload["sdk_version"] == pkg_version("proxyml")
|
|
258
|
+
assert payload["proxyml_core_version"] == pkg_version("proxyml-core")
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def test_to_challenger_upload_allows_version_override():
|
|
262
|
+
schema = _schema()
|
|
263
|
+
df = _df(seed=18)
|
|
264
|
+
target = df["age"] * 0.5 + df["income"] * 0.0001
|
|
265
|
+
result = train_challenger(df, target, schema, task="regression")
|
|
266
|
+
|
|
267
|
+
payload = to_challenger_upload(
|
|
268
|
+
result, n_samples=10, sdk_version="9.9.9", proxyml_core_version="8.8.8"
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
assert payload["sdk_version"] == "9.9.9"
|
|
272
|
+
assert payload["proxyml_core_version"] == "8.8.8"
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def test_to_challenger_upload_payload_is_json_serializable():
|
|
276
|
+
import json
|
|
277
|
+
|
|
278
|
+
schema = _schema()
|
|
279
|
+
df = _df(seed=19, n=300)
|
|
280
|
+
target = np.where(df["age"] > 50, "senior", "junior")
|
|
281
|
+
result = train_challenger(df, target, schema, task="classification")
|
|
282
|
+
champion_metrics = score_champion(target, target, task="classification")
|
|
283
|
+
|
|
284
|
+
payload = to_challenger_upload(result, n_samples=300, champion_metrics=champion_metrics)
|
|
285
|
+
|
|
286
|
+
json.dumps(payload) # must not raise
|
|
287
|
+
|
|
288
|
+
|
|
200
289
|
def test_train_auto_challenger_passes_immutable_cols_to_get_schema():
|
|
201
290
|
from unittest.mock import patch
|
|
202
291
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|