omle-convert 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.
Potentially problematic release.
This version of omle-convert might be problematic. Click here for more details.
- omle_convert/__init__.py +173 -0
- omle_convert/_common.py +1218 -0
- omle_convert/_version.py +24 -0
- omle_convert/catboost.py +525 -0
- omle_convert/cli.py +191 -0
- omle_convert/lightgbm.py +619 -0
- omle_convert/sklearn/__init__.py +519 -0
- omle_convert/sklearn/_anomaly_detection.py +356 -0
- omle_convert/sklearn/_builder.py +196 -0
- omle_convert/sklearn/_category_encoders.py +229 -0
- omle_convert/sklearn/_cluster.py +100 -0
- omle_convert/sklearn/_compose.py +146 -0
- omle_convert/sklearn/_decomposition.py +159 -0
- omle_convert/sklearn/_ensemble.py +385 -0
- omle_convert/sklearn/_estimators.py +210 -0
- omle_convert/sklearn/_impute.py +57 -0
- omle_convert/sklearn/_linear_model.py +82 -0
- omle_convert/sklearn/_multiclass.py +97 -0
- omle_convert/sklearn/_multioutput.py +235 -0
- omle_convert/sklearn/_naive_bayes.py +109 -0
- omle_convert/sklearn/_neighbors.py +97 -0
- omle_convert/sklearn/_neural_network.py +55 -0
- omle_convert/sklearn/_pipeline.py +122 -0
- omle_convert/sklearn/_preprocessing.py +476 -0
- omle_convert/sklearn/_selection.py +36 -0
- omle_convert/sklearn/_svm.py +117 -0
- omle_convert/sklearn/_text.py +141 -0
- omle_convert/sklearn/_transformers.py +278 -0
- omle_convert/sklearn/_tree.py +24 -0
- omle_convert/sklearn/_trees.py +306 -0
- omle_convert/sklearn/_xgboost_lgbm.py +85 -0
- omle_convert/spark/__init__.py +441 -0
- omle_convert/spark/_builder.py +252 -0
- omle_convert/spark/_reader.py +2222 -0
- omle_convert/spark/_sql_transforms.py +410 -0
- omle_convert/xgboost.py +715 -0
- omle_convert-0.1.0.dist-info/METADATA +297 -0
- omle_convert-0.1.0.dist-info/RECORD +42 -0
- omle_convert-0.1.0.dist-info/WHEEL +5 -0
- omle_convert-0.1.0.dist-info/entry_points.txt +2 -0
- omle_convert-0.1.0.dist-info/licenses/LICENSE +201 -0
- omle_convert-0.1.0.dist-info/top_level.txt +1 -0
omle_convert/__init__.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""omle-convert: converters from popular ML frameworks to the OMLE format."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from omle_convert.catboost import from_catboost, from_catboost_file
|
|
7
|
+
from omle_convert.lightgbm import from_lightgbm, from_lightgbm_text
|
|
8
|
+
from omle_convert.sklearn import from_sklearn
|
|
9
|
+
from omle_convert.spark import from_spark, from_spark_live
|
|
10
|
+
from omle_convert.xgboost import from_xgboost, from_xgboost_json
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"to_omle",
|
|
14
|
+
"export_omle",
|
|
15
|
+
"from_xgboost", "from_xgboost_json",
|
|
16
|
+
"from_lightgbm", "from_lightgbm_text",
|
|
17
|
+
"from_sklearn",
|
|
18
|
+
"from_catboost", "from_catboost_file",
|
|
19
|
+
"from_spark", "from_spark_live",
|
|
20
|
+
]
|
|
21
|
+
__version__ = "0.1.0"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def to_omle(model, **kwargs):
|
|
25
|
+
"""Convert a model to OMLE format, auto-detecting the framework.
|
|
26
|
+
|
|
27
|
+
Accepts either a live model object or a file path (str/Path).
|
|
28
|
+
|
|
29
|
+
Supported objects:
|
|
30
|
+
- any sklearn BaseEstimator or Pipeline, including XGBClassifier and LGBMClassifier
|
|
31
|
+
- xgboost.Booster (raw booster, not wrapped in sklearn API)
|
|
32
|
+
- lightgbm.Booster (raw booster, not wrapped in sklearn API)
|
|
33
|
+
|
|
34
|
+
Supported file paths (by extension):
|
|
35
|
+
- ``.json`` → XGBoost JSON model
|
|
36
|
+
- ``.ubj`` → XGBoost binary (ubj)
|
|
37
|
+
- ``.bin`` / ``.txt`` → LightGBM text model
|
|
38
|
+
- directory → Spark saved model / saved pipeline (auto-detected)
|
|
39
|
+
|
|
40
|
+
Extra keyword arguments are forwarded to the underlying converter.
|
|
41
|
+
"""
|
|
42
|
+
if isinstance(model, (str, Path)):
|
|
43
|
+
return _convert_from_path(model, **kwargs)
|
|
44
|
+
return _convert_object(model, **kwargs)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def export_omle(model, path, **kwargs) -> None:
|
|
48
|
+
"""Export *model* to an OMLE file at *path*.
|
|
49
|
+
|
|
50
|
+
If *model* is already an ``OMLEModel`` it is saved directly.
|
|
51
|
+
Otherwise it is converted via ``to_omle`` first.
|
|
52
|
+
"""
|
|
53
|
+
import omle
|
|
54
|
+
if not isinstance(model, omle.OMLEModel):
|
|
55
|
+
model = to_omle(model, **kwargs)
|
|
56
|
+
omle.save(model, path)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# Sample-data argument names differ by framework: the array-based converters take
|
|
61
|
+
# ``X`` (scikit-learn's convention), the PySpark ones take ``dataset``
|
|
62
|
+
# (``fit(dataset)`` / ``transform(dataset)``). Each is idiomatic where it is used,
|
|
63
|
+
# but the unified entry points auto-detect the framework, so requiring the caller
|
|
64
|
+
# to already know which spelling applies would defeat the point. Accept either
|
|
65
|
+
# here and forward whichever the resolved converter declares.
|
|
66
|
+
_SAMPLE_ALIASES = ("X", "dataset")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _sample_kwarg(kwargs: dict, wanted: str) -> dict:
|
|
70
|
+
"""Rename the sample-data kwarg to *wanted*.
|
|
71
|
+
|
|
72
|
+
The destination is named explicitly rather than discovered by inspecting the
|
|
73
|
+
target's signature: converters get monkey-patched in places (the test suite
|
|
74
|
+
wraps them for instrumentation), and a wrapper's ``(*args, **kwargs)``
|
|
75
|
+
signature would make introspection silently find nothing.
|
|
76
|
+
"""
|
|
77
|
+
given = [k for k in _SAMPLE_ALIASES if k in kwargs]
|
|
78
|
+
if not given:
|
|
79
|
+
return kwargs
|
|
80
|
+
if len(given) > 1:
|
|
81
|
+
raise TypeError(
|
|
82
|
+
"Pass either 'X' or 'dataset' for sample data, not both."
|
|
83
|
+
)
|
|
84
|
+
if given[0] == wanted:
|
|
85
|
+
return kwargs
|
|
86
|
+
kwargs = dict(kwargs)
|
|
87
|
+
kwargs[wanted] = kwargs.pop(given[0])
|
|
88
|
+
return kwargs
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _convert_object(model, **kwargs):
|
|
92
|
+
"""Dispatch a live model object to the right converter."""
|
|
93
|
+
module = type(model).__module__ or ""
|
|
94
|
+
cls_name = type(model).__name__
|
|
95
|
+
|
|
96
|
+
# sklearn (includes XGBClassifier, LGBMClassifier which are BaseEstimators)
|
|
97
|
+
try:
|
|
98
|
+
from sklearn.base import BaseEstimator
|
|
99
|
+
if isinstance(model, BaseEstimator):
|
|
100
|
+
return from_sklearn(model, **_sample_kwarg(kwargs, "X"))
|
|
101
|
+
except ImportError:
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
# CatBoost (CatBoostClassifier/Regressor/CatBoost are not sklearn BaseEstimators
|
|
105
|
+
# when catboost is not installed alongside sklearn, but handle both cases)
|
|
106
|
+
if module.startswith("catboost"):
|
|
107
|
+
return from_catboost(model, **_sample_kwarg(kwargs, "X"))
|
|
108
|
+
|
|
109
|
+
# xgboost Booster (not a BaseEstimator)
|
|
110
|
+
if module.startswith("xgboost"):
|
|
111
|
+
return from_xgboost(model, **_sample_kwarg(kwargs, "X"))
|
|
112
|
+
|
|
113
|
+
# lightgbm Booster (not a BaseEstimator)
|
|
114
|
+
if module.startswith("lightgbm"):
|
|
115
|
+
return from_lightgbm(model, **_sample_kwarg(kwargs, "X"))
|
|
116
|
+
|
|
117
|
+
# live PySpark PipelineModel or single estimator
|
|
118
|
+
if module.startswith("pyspark"):
|
|
119
|
+
return from_spark_live(model, **_sample_kwarg(kwargs, "dataset"))
|
|
120
|
+
|
|
121
|
+
raise TypeError(
|
|
122
|
+
f"Cannot auto-detect converter for {cls_name} (module={module!r}). "
|
|
123
|
+
"Use a specific from_* function instead."
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _convert_from_path(path, **kwargs):
|
|
128
|
+
"""Dispatch a file path to the right converter by extension / content."""
|
|
129
|
+
p = Path(path)
|
|
130
|
+
|
|
131
|
+
if p.is_dir():
|
|
132
|
+
return from_spark(str(p), **kwargs)
|
|
133
|
+
|
|
134
|
+
suffix = p.suffix.lower()
|
|
135
|
+
|
|
136
|
+
if suffix in {".bin", ".txt"}:
|
|
137
|
+
return from_lightgbm_text(str(p), **_sample_kwarg(kwargs, "X"))
|
|
138
|
+
|
|
139
|
+
if suffix in {".pkl", ".pickle", ".joblib"}:
|
|
140
|
+
try:
|
|
141
|
+
import joblib
|
|
142
|
+
obj = joblib.load(p)
|
|
143
|
+
except ImportError:
|
|
144
|
+
import pickle
|
|
145
|
+
with open(p, "rb") as f:
|
|
146
|
+
obj = pickle.load(f)
|
|
147
|
+
return _convert_object(obj, **kwargs)
|
|
148
|
+
|
|
149
|
+
if suffix == ".cbm":
|
|
150
|
+
return from_catboost_file(str(p), **_sample_kwarg(kwargs, "X"))
|
|
151
|
+
|
|
152
|
+
if suffix in {".json", ".ubj"}:
|
|
153
|
+
# Both frameworks use `.json`, so dispatch on a top-level key: CatBoost
|
|
154
|
+
# writes "oblivious_trees", XGBoost writes "learner".
|
|
155
|
+
#
|
|
156
|
+
# The whole file is scanned, not a fixed prefix: CatBoost emits
|
|
157
|
+
# "features_info" first and "oblivious_trees" only tens of kilobytes in,
|
|
158
|
+
# so a prefix peek misses it and silently routes to the wrong loader.
|
|
159
|
+
if suffix == ".json":
|
|
160
|
+
try:
|
|
161
|
+
raw = p.read_bytes()
|
|
162
|
+
if b'"oblivious_trees"' in raw:
|
|
163
|
+
return from_catboost_file(str(p), **_sample_kwarg(kwargs, "X"))
|
|
164
|
+
if b'"learner"' in raw or b'"objective"' in raw:
|
|
165
|
+
return from_xgboost_json(str(p), **_sample_kwarg(kwargs, "X"))
|
|
166
|
+
except Exception:
|
|
167
|
+
pass
|
|
168
|
+
return from_xgboost_json(str(p), **_sample_kwarg(kwargs, "X"))
|
|
169
|
+
|
|
170
|
+
raise ValueError(
|
|
171
|
+
f"Cannot auto-detect converter for path {str(p)!r} (suffix={suffix!r}). "
|
|
172
|
+
"Use a specific from_* function instead."
|
|
173
|
+
)
|