mlrun 1.7.0rc14__py3-none-any.whl → 1.7.0rc16__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 mlrun might be problematic. Click here for more details.
- mlrun/__init__.py +10 -1
- mlrun/__main__.py +18 -109
- mlrun/{runtimes/mpijob/v1alpha1.py → alerts/__init__.py} +2 -16
- mlrun/alerts/alert.py +141 -0
- mlrun/artifacts/__init__.py +8 -3
- mlrun/artifacts/base.py +36 -253
- mlrun/artifacts/dataset.py +9 -190
- mlrun/artifacts/manager.py +20 -41
- mlrun/artifacts/model.py +8 -140
- mlrun/artifacts/plots.py +14 -375
- mlrun/common/schemas/__init__.py +4 -2
- mlrun/common/schemas/alert.py +46 -4
- mlrun/common/schemas/api_gateway.py +4 -0
- mlrun/common/schemas/artifact.py +15 -0
- mlrun/common/schemas/auth.py +2 -0
- mlrun/common/schemas/model_monitoring/__init__.py +8 -1
- mlrun/common/schemas/model_monitoring/constants.py +40 -4
- mlrun/common/schemas/model_monitoring/model_endpoints.py +73 -2
- mlrun/common/schemas/project.py +2 -0
- mlrun/config.py +7 -4
- mlrun/data_types/to_pandas.py +4 -4
- mlrun/datastore/base.py +41 -9
- mlrun/datastore/datastore_profile.py +54 -4
- mlrun/datastore/inmem.py +2 -2
- mlrun/datastore/sources.py +43 -2
- mlrun/datastore/store_resources.py +2 -6
- mlrun/datastore/targets.py +106 -39
- mlrun/db/base.py +23 -3
- mlrun/db/httpdb.py +101 -47
- mlrun/db/nopdb.py +20 -2
- mlrun/errors.py +5 -0
- mlrun/feature_store/__init__.py +0 -2
- mlrun/feature_store/api.py +12 -47
- mlrun/feature_store/feature_set.py +9 -0
- mlrun/feature_store/retrieval/base.py +9 -4
- mlrun/feature_store/retrieval/conversion.py +4 -4
- mlrun/feature_store/retrieval/dask_merger.py +2 -0
- mlrun/feature_store/retrieval/job.py +2 -0
- mlrun/feature_store/retrieval/local_merger.py +2 -0
- mlrun/feature_store/retrieval/spark_merger.py +5 -0
- mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +5 -10
- mlrun/launcher/base.py +4 -3
- mlrun/launcher/client.py +1 -1
- mlrun/lists.py +4 -2
- mlrun/model.py +25 -11
- mlrun/model_monitoring/__init__.py +1 -1
- mlrun/model_monitoring/api.py +41 -18
- mlrun/model_monitoring/application.py +5 -305
- mlrun/model_monitoring/applications/__init__.py +11 -0
- mlrun/model_monitoring/applications/_application_steps.py +157 -0
- mlrun/model_monitoring/applications/base.py +282 -0
- mlrun/model_monitoring/applications/context.py +214 -0
- mlrun/model_monitoring/applications/evidently_base.py +211 -0
- mlrun/model_monitoring/applications/histogram_data_drift.py +132 -91
- mlrun/model_monitoring/applications/results.py +99 -0
- mlrun/model_monitoring/controller.py +3 -1
- mlrun/model_monitoring/db/__init__.py +2 -0
- mlrun/model_monitoring/db/stores/base/store.py +9 -36
- mlrun/model_monitoring/db/stores/sqldb/models/base.py +7 -6
- mlrun/model_monitoring/db/stores/sqldb/sql_store.py +63 -110
- mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +104 -187
- mlrun/model_monitoring/db/tsdb/__init__.py +71 -0
- mlrun/model_monitoring/db/tsdb/base.py +135 -0
- mlrun/model_monitoring/db/tsdb/v3io/__init__.py +15 -0
- mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +117 -0
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +404 -0
- mlrun/model_monitoring/db/v3io_tsdb_reader.py +134 -0
- mlrun/model_monitoring/evidently_application.py +6 -118
- mlrun/model_monitoring/helpers.py +1 -1
- mlrun/model_monitoring/model_endpoint.py +3 -2
- mlrun/model_monitoring/stream_processing.py +48 -213
- mlrun/model_monitoring/writer.py +101 -121
- mlrun/platforms/__init__.py +10 -9
- mlrun/platforms/iguazio.py +21 -202
- mlrun/projects/operations.py +11 -7
- mlrun/projects/pipelines.py +13 -76
- mlrun/projects/project.py +73 -45
- mlrun/render.py +11 -13
- mlrun/run.py +6 -41
- mlrun/runtimes/__init__.py +3 -3
- mlrun/runtimes/base.py +6 -6
- mlrun/runtimes/funcdoc.py +0 -28
- mlrun/runtimes/kubejob.py +2 -1
- mlrun/runtimes/local.py +1 -1
- mlrun/runtimes/mpijob/__init__.py +0 -20
- mlrun/runtimes/mpijob/v1.py +1 -1
- mlrun/runtimes/nuclio/api_gateway.py +75 -9
- mlrun/runtimes/nuclio/function.py +9 -35
- mlrun/runtimes/pod.py +16 -36
- mlrun/runtimes/remotesparkjob.py +1 -1
- mlrun/runtimes/sparkjob/spark3job.py +1 -1
- mlrun/runtimes/utils.py +1 -39
- mlrun/utils/helpers.py +72 -71
- mlrun/utils/notifications/notification/base.py +1 -1
- mlrun/utils/notifications/notification/slack.py +12 -5
- mlrun/utils/notifications/notification/webhook.py +1 -1
- mlrun/utils/notifications/notification_pusher.py +134 -14
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc16.dist-info}/METADATA +4 -3
- {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc16.dist-info}/RECORD +105 -95
- mlrun/kfpops.py +0 -865
- mlrun/platforms/other.py +0 -305
- /mlrun/{runtimes → common/runtimes}/constants.py +0 -0
- {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc16.dist-info}/LICENSE +0 -0
- {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc16.dist-info}/WHEEL +0 -0
- {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc16.dist-info}/entry_points.txt +0 -0
- {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc16.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# Copyright 2023 Iguazio
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from abc import ABC, abstractmethod
|
|
16
|
+
from typing import Any, Union, cast
|
|
17
|
+
|
|
18
|
+
import numpy as np
|
|
19
|
+
import pandas as pd
|
|
20
|
+
|
|
21
|
+
import mlrun
|
|
22
|
+
import mlrun.model_monitoring.applications.context as mm_context
|
|
23
|
+
import mlrun.model_monitoring.applications.results as mm_results
|
|
24
|
+
from mlrun.serving.utils import StepToDict
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ModelMonitoringApplicationBaseV2(StepToDict, ABC):
|
|
28
|
+
"""
|
|
29
|
+
A base class for a model monitoring application.
|
|
30
|
+
Inherit from this class to create a custom model monitoring application.
|
|
31
|
+
|
|
32
|
+
example for very simple custom application::
|
|
33
|
+
# mlrun: start-code
|
|
34
|
+
class MyApp(ApplicationBase):
|
|
35
|
+
def do_tracking(
|
|
36
|
+
self,
|
|
37
|
+
monitoring_context: mm_context.MonitoringApplicationContext,
|
|
38
|
+
) -> ModelMonitoringApplicationResult:
|
|
39
|
+
monitoring_context.log_artifact(
|
|
40
|
+
TableArtifact(
|
|
41
|
+
"sample_df_stats", df=self.dict_to_histogram(sample_df_stats)
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
return ModelMonitoringApplicationResult(
|
|
45
|
+
name="data_drift_test",
|
|
46
|
+
value=0.5,
|
|
47
|
+
kind=mm_constant.ResultKindApp.data_drift,
|
|
48
|
+
status=mm_constant.ResultStatusApp.detected,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# mlrun: end-code
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
kind = "monitoring_application"
|
|
56
|
+
|
|
57
|
+
def do(
|
|
58
|
+
self, monitoring_context: mm_context.MonitoringApplicationContext
|
|
59
|
+
) -> tuple[
|
|
60
|
+
list[
|
|
61
|
+
Union[
|
|
62
|
+
mm_results.ModelMonitoringApplicationResult,
|
|
63
|
+
mm_results.ModelMonitoringApplicationMetric,
|
|
64
|
+
]
|
|
65
|
+
],
|
|
66
|
+
mm_context.MonitoringApplicationContext,
|
|
67
|
+
]:
|
|
68
|
+
"""
|
|
69
|
+
Process the monitoring event and return application results & metrics.
|
|
70
|
+
|
|
71
|
+
:param monitoring_context: (MonitoringApplicationContext) The monitoring application context.
|
|
72
|
+
:returns: A tuple of:
|
|
73
|
+
[0] = list of application results that can be either from type
|
|
74
|
+
`ModelMonitoringApplicationResult`
|
|
75
|
+
or from type `ModelMonitoringApplicationResult`.
|
|
76
|
+
[1] = the original application event, wrapped in `MonitoringApplicationContext`
|
|
77
|
+
object
|
|
78
|
+
"""
|
|
79
|
+
results = self.do_tracking(monitoring_context=monitoring_context)
|
|
80
|
+
if isinstance(results, dict):
|
|
81
|
+
results = [
|
|
82
|
+
mm_results.ModelMonitoringApplicationMetric(name=key, value=value)
|
|
83
|
+
for key, value in results.items()
|
|
84
|
+
]
|
|
85
|
+
results = results if isinstance(results, list) else [results]
|
|
86
|
+
return results, monitoring_context
|
|
87
|
+
|
|
88
|
+
@abstractmethod
|
|
89
|
+
def do_tracking(
|
|
90
|
+
self,
|
|
91
|
+
monitoring_context: mm_context.MonitoringApplicationContext,
|
|
92
|
+
) -> Union[
|
|
93
|
+
mm_results.ModelMonitoringApplicationResult,
|
|
94
|
+
list[
|
|
95
|
+
Union[
|
|
96
|
+
mm_results.ModelMonitoringApplicationResult,
|
|
97
|
+
mm_results.ModelMonitoringApplicationMetric,
|
|
98
|
+
]
|
|
99
|
+
],
|
|
100
|
+
dict[str, Any],
|
|
101
|
+
]:
|
|
102
|
+
"""
|
|
103
|
+
Implement this method with your custom monitoring logic.
|
|
104
|
+
|
|
105
|
+
:param monitoring_context: (MonitoringApplicationContext) The monitoring context to process.
|
|
106
|
+
|
|
107
|
+
:returns: (ModelMonitoringApplicationResult) or
|
|
108
|
+
(list[Union[ModelMonitoringApplicationResult,
|
|
109
|
+
ModelMonitoringApplicationMetric]])
|
|
110
|
+
or dict that contains the application metrics only (in this case the name of
|
|
111
|
+
each metric name is the key and the metric value is the corresponding value).
|
|
112
|
+
"""
|
|
113
|
+
raise NotImplementedError
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class ModelMonitoringApplicationBase(StepToDict, ABC):
|
|
117
|
+
"""
|
|
118
|
+
A base class for a model monitoring application.
|
|
119
|
+
Inherit from this class to create a custom model monitoring application.
|
|
120
|
+
|
|
121
|
+
example for very simple custom application::
|
|
122
|
+
# mlrun: start-code
|
|
123
|
+
class MyApp(ApplicationBase):
|
|
124
|
+
def do_tracking(
|
|
125
|
+
self,
|
|
126
|
+
sample_df_stats: mlrun.common.model_monitoring.helpers.FeatureStats,
|
|
127
|
+
feature_stats: mlrun.common.model_monitoring.helpers.FeatureStats,
|
|
128
|
+
start_infer_time: pd.Timestamp,
|
|
129
|
+
end_infer_time: pd.Timestamp,
|
|
130
|
+
schedule_time: pd.Timestamp,
|
|
131
|
+
latest_request: pd.Timestamp,
|
|
132
|
+
endpoint_id: str,
|
|
133
|
+
output_stream_uri: str,
|
|
134
|
+
) -> ModelMonitoringApplicationResult:
|
|
135
|
+
self.context.log_artifact(
|
|
136
|
+
TableArtifact(
|
|
137
|
+
"sample_df_stats", df=self.dict_to_histogram(sample_df_stats)
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
return ModelMonitoringApplicationResult(
|
|
141
|
+
name="data_drift_test",
|
|
142
|
+
value=0.5,
|
|
143
|
+
kind=mm_constant.ResultKindApp.data_drift,
|
|
144
|
+
status=mm_constant.ResultStatusApp.detected,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# mlrun: end-code
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
kind = "monitoring_application"
|
|
152
|
+
|
|
153
|
+
def do(
|
|
154
|
+
self, monitoring_context: mm_context.MonitoringApplicationContext
|
|
155
|
+
) -> tuple[
|
|
156
|
+
list[mm_results.ModelMonitoringApplicationResult],
|
|
157
|
+
mm_context.MonitoringApplicationContext,
|
|
158
|
+
]:
|
|
159
|
+
"""
|
|
160
|
+
Process the monitoring event and return application results.
|
|
161
|
+
|
|
162
|
+
:param monitoring_context: (MonitoringApplicationContext) The monitoring context to process.
|
|
163
|
+
:returns: A tuple of:
|
|
164
|
+
[0] = list of application results that can be either from type
|
|
165
|
+
`ModelMonitoringApplicationResult` or from type
|
|
166
|
+
`ModelMonitoringApplicationResult`.
|
|
167
|
+
[1] = the original application event, wrapped in `MonitoringApplicationContext`
|
|
168
|
+
object
|
|
169
|
+
"""
|
|
170
|
+
resolved_event = self._resolve_event(monitoring_context)
|
|
171
|
+
if not (
|
|
172
|
+
hasattr(self, "context") and isinstance(self.context, mlrun.MLClientCtx)
|
|
173
|
+
):
|
|
174
|
+
self._lazy_init(monitoring_context)
|
|
175
|
+
results = self.do_tracking(*resolved_event)
|
|
176
|
+
results = results if isinstance(results, list) else [results]
|
|
177
|
+
return results, monitoring_context
|
|
178
|
+
|
|
179
|
+
def _lazy_init(self, monitoring_context: mm_context.MonitoringApplicationContext):
|
|
180
|
+
self.context = cast(mlrun.MLClientCtx, monitoring_context)
|
|
181
|
+
|
|
182
|
+
@abstractmethod
|
|
183
|
+
def do_tracking(
|
|
184
|
+
self,
|
|
185
|
+
application_name: str,
|
|
186
|
+
sample_df_stats: pd.DataFrame,
|
|
187
|
+
feature_stats: pd.DataFrame,
|
|
188
|
+
sample_df: pd.DataFrame,
|
|
189
|
+
start_infer_time: pd.Timestamp,
|
|
190
|
+
end_infer_time: pd.Timestamp,
|
|
191
|
+
latest_request: pd.Timestamp,
|
|
192
|
+
endpoint_id: str,
|
|
193
|
+
output_stream_uri: str,
|
|
194
|
+
) -> Union[
|
|
195
|
+
mm_results.ModelMonitoringApplicationResult,
|
|
196
|
+
list[mm_results.ModelMonitoringApplicationResult],
|
|
197
|
+
]:
|
|
198
|
+
"""
|
|
199
|
+
Implement this method with your custom monitoring logic.
|
|
200
|
+
|
|
201
|
+
:param application_name: (str) the app name
|
|
202
|
+
:param sample_df_stats: (pd.DataFrame) The new sample distribution.
|
|
203
|
+
:param feature_stats: (pd.DataFrame) The train sample distribution.
|
|
204
|
+
:param sample_df: (pd.DataFrame) The new sample DataFrame.
|
|
205
|
+
:param start_infer_time: (pd.Timestamp) Start time of the monitoring schedule.
|
|
206
|
+
:param end_infer_time: (pd.Timestamp) End time of the monitoring schedule.
|
|
207
|
+
:param latest_request: (pd.Timestamp) Timestamp of the latest request on this endpoint_id.
|
|
208
|
+
:param endpoint_id: (str) ID of the monitored model endpoint
|
|
209
|
+
:param output_stream_uri: (str) URI of the output stream for results
|
|
210
|
+
|
|
211
|
+
:returns: (ModelMonitoringApplicationResult) or
|
|
212
|
+
(list[ModelMonitoringApplicationResult]) of the application results.
|
|
213
|
+
"""
|
|
214
|
+
raise NotImplementedError
|
|
215
|
+
|
|
216
|
+
@classmethod
|
|
217
|
+
def _resolve_event(
|
|
218
|
+
cls,
|
|
219
|
+
monitoring_context: mm_context.MonitoringApplicationContext,
|
|
220
|
+
) -> tuple[
|
|
221
|
+
str,
|
|
222
|
+
pd.DataFrame,
|
|
223
|
+
pd.DataFrame,
|
|
224
|
+
pd.DataFrame,
|
|
225
|
+
pd.Timestamp,
|
|
226
|
+
pd.Timestamp,
|
|
227
|
+
pd.Timestamp,
|
|
228
|
+
str,
|
|
229
|
+
str,
|
|
230
|
+
]:
|
|
231
|
+
"""
|
|
232
|
+
Converting the event into a single tuple that will be used for passing the event arguments to the running
|
|
233
|
+
application
|
|
234
|
+
|
|
235
|
+
:param monitoring_context: (MonitoringApplicationContext) The monitoring context to process.
|
|
236
|
+
|
|
237
|
+
:return: A tuple of:
|
|
238
|
+
[0] = (str) application name
|
|
239
|
+
[1] = (pd.DataFrame) current input statistics
|
|
240
|
+
[2] = (pd.DataFrame) train statistics
|
|
241
|
+
[3] = (pd.DataFrame) current input data
|
|
242
|
+
[4] = (pd.Timestamp) start time of the monitoring schedule
|
|
243
|
+
[5] = (pd.Timestamp) end time of the monitoring schedule
|
|
244
|
+
[6] = (pd.Timestamp) timestamp of the latest request
|
|
245
|
+
[7] = (str) endpoint id
|
|
246
|
+
[8] = (str) output stream uri
|
|
247
|
+
"""
|
|
248
|
+
return (
|
|
249
|
+
monitoring_context.application_name,
|
|
250
|
+
cls.dict_to_histogram(monitoring_context.sample_df_stats),
|
|
251
|
+
cls.dict_to_histogram(monitoring_context.feature_stats),
|
|
252
|
+
monitoring_context.sample_df,
|
|
253
|
+
monitoring_context.start_infer_time,
|
|
254
|
+
monitoring_context.end_infer_time,
|
|
255
|
+
monitoring_context.latest_request,
|
|
256
|
+
monitoring_context.endpoint_id,
|
|
257
|
+
monitoring_context.output_stream_uri,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
@staticmethod
|
|
261
|
+
def dict_to_histogram(
|
|
262
|
+
histogram_dict: mlrun.common.model_monitoring.helpers.FeatureStats,
|
|
263
|
+
) -> pd.DataFrame:
|
|
264
|
+
"""
|
|
265
|
+
Convert histogram dictionary to pandas DataFrame with feature histograms as columns
|
|
266
|
+
|
|
267
|
+
:param histogram_dict: Histogram dictionary
|
|
268
|
+
|
|
269
|
+
:returns: Histogram dataframe
|
|
270
|
+
"""
|
|
271
|
+
|
|
272
|
+
# Create a dictionary with feature histograms as values
|
|
273
|
+
histograms = {}
|
|
274
|
+
for feature, stats in histogram_dict.items():
|
|
275
|
+
if "hist" in stats:
|
|
276
|
+
# Normalize to probability distribution of each feature
|
|
277
|
+
histograms[feature] = np.array(stats["hist"][0]) / stats["count"]
|
|
278
|
+
|
|
279
|
+
# Convert the dictionary to pandas DataFrame
|
|
280
|
+
histograms = pd.DataFrame(histograms)
|
|
281
|
+
|
|
282
|
+
return histograms
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# Copyright 2024 Iguazio
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
import json
|
|
15
|
+
import typing
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
import pandas as pd
|
|
19
|
+
|
|
20
|
+
import mlrun.common.helpers
|
|
21
|
+
import mlrun.common.model_monitoring.helpers
|
|
22
|
+
import mlrun.common.schemas.model_monitoring.constants as mm_constants
|
|
23
|
+
import mlrun.feature_store as fstore
|
|
24
|
+
from mlrun.artifacts.model import ModelArtifact, get_model
|
|
25
|
+
from mlrun.common.model_monitoring.helpers import FeatureStats, pad_features_hist
|
|
26
|
+
from mlrun.execution import MLClientCtx
|
|
27
|
+
from mlrun.model_monitoring.helpers import (
|
|
28
|
+
calculate_inputs_statistics,
|
|
29
|
+
get_endpoint_record,
|
|
30
|
+
)
|
|
31
|
+
from mlrun.model_monitoring.model_endpoint import ModelEndpoint
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class MonitoringApplicationContext(MLClientCtx):
|
|
35
|
+
"""
|
|
36
|
+
The monitoring context holds all the relevant information for the monitoring application,
|
|
37
|
+
and also it can be used for logging artifacts and results.
|
|
38
|
+
The monitoring context has the following attributes:
|
|
39
|
+
|
|
40
|
+
:param application_name: (str) the app name
|
|
41
|
+
:param sample_df_stats: (FeatureStats) The new sample distribution dictionary.
|
|
42
|
+
:param feature_stats: (FeatureStats) The train sample distribution dictionary.
|
|
43
|
+
:param sample_df: (pd.DataFrame) The new sample DataFrame.
|
|
44
|
+
:param start_infer_time: (pd.Timestamp) Start time of the monitoring schedule.
|
|
45
|
+
:param end_infer_time: (pd.Timestamp) End time of the monitoring schedule.
|
|
46
|
+
:param latest_request: (pd.Timestamp) Timestamp of the latest request on this endpoint_id.
|
|
47
|
+
:param endpoint_id: (str) ID of the monitored model endpoint
|
|
48
|
+
:param output_stream_uri: (str) URI of the output stream for results
|
|
49
|
+
:param model_endpoint: (ModelEndpoint) The model endpoint object.
|
|
50
|
+
:param feature_names: (list[str]) List of models feature names.
|
|
51
|
+
:param label_names: (list[str]) List of models label names.
|
|
52
|
+
:param model: (tuple[str, ModelArtifact, dict]) The model file, model spec object, and list of
|
|
53
|
+
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __init__(self, **kwargs):
|
|
57
|
+
super().__init__(**kwargs)
|
|
58
|
+
|
|
59
|
+
def __post_init__(self):
|
|
60
|
+
self.application_name: typing.Optional[str] = None
|
|
61
|
+
self.start_infer_time: typing.Optional[pd.Timestamp] = None
|
|
62
|
+
self.end_infer_time: typing.Optional[pd.Timestamp] = None
|
|
63
|
+
self.latest_request: typing.Optional[pd.Timestamp] = None
|
|
64
|
+
self.endpoint_id: typing.Optional[str] = None
|
|
65
|
+
self.output_stream_uri: typing.Optional[str] = None
|
|
66
|
+
|
|
67
|
+
self._sample_df: typing.Optional[pd.DataFrame] = None
|
|
68
|
+
self._model_endpoint: typing.Optional[ModelEndpoint] = None
|
|
69
|
+
self._feature_stats: typing.Optional[FeatureStats] = None
|
|
70
|
+
self._sample_df_stats: typing.Optional[FeatureStats] = None
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def from_dict(
|
|
74
|
+
cls,
|
|
75
|
+
attrs: dict,
|
|
76
|
+
context=None,
|
|
77
|
+
model_endpoint_dict=None,
|
|
78
|
+
**kwargs,
|
|
79
|
+
) -> "MonitoringApplicationContext":
|
|
80
|
+
"""
|
|
81
|
+
Create an instance of the MonitoringApplicationContext from a dictionary.
|
|
82
|
+
|
|
83
|
+
:param attrs: The instance data dictionary.
|
|
84
|
+
:param context: The current application context.
|
|
85
|
+
:param model_endpoint_dict: Dictionary of model endpoints.
|
|
86
|
+
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
if not context:
|
|
90
|
+
self = (
|
|
91
|
+
super().from_dict(
|
|
92
|
+
attrs=attrs.get(mm_constants.ApplicationEvent.MLRUN_CONTEXT, {}),
|
|
93
|
+
**kwargs,
|
|
94
|
+
),
|
|
95
|
+
)
|
|
96
|
+
else:
|
|
97
|
+
self = context
|
|
98
|
+
self.__post_init__()
|
|
99
|
+
|
|
100
|
+
self.start_infer_time = pd.Timestamp(
|
|
101
|
+
attrs.get(mm_constants.ApplicationEvent.START_INFER_TIME)
|
|
102
|
+
)
|
|
103
|
+
self.end_infer_time = pd.Timestamp(
|
|
104
|
+
attrs.get(mm_constants.ApplicationEvent.END_INFER_TIME)
|
|
105
|
+
)
|
|
106
|
+
self.latest_request = pd.Timestamp(
|
|
107
|
+
attrs.get(mm_constants.ApplicationEvent.LAST_REQUEST)
|
|
108
|
+
)
|
|
109
|
+
self.application_name = attrs.get(
|
|
110
|
+
mm_constants.ApplicationEvent.APPLICATION_NAME
|
|
111
|
+
)
|
|
112
|
+
self._feature_stats = json.loads(
|
|
113
|
+
attrs.get(mm_constants.ApplicationEvent.FEATURE_STATS, "{}")
|
|
114
|
+
)
|
|
115
|
+
self._sample_df_stats = json.loads(
|
|
116
|
+
attrs.get(mm_constants.ApplicationEvent.CURRENT_STATS, "{}")
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
self.endpoint_id = attrs.get(mm_constants.ApplicationEvent.ENDPOINT_ID)
|
|
120
|
+
self._model_endpoint = model_endpoint_dict.get(self.endpoint_id)
|
|
121
|
+
|
|
122
|
+
return self
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def sample_df(self) -> pd.DataFrame:
|
|
126
|
+
if not hasattr(self, "_sample_df") or self._sample_df is None:
|
|
127
|
+
feature_set = fstore.get_feature_set(
|
|
128
|
+
self.model_endpoint.status.monitoring_feature_set_uri
|
|
129
|
+
)
|
|
130
|
+
features = [f"{feature_set.metadata.name}.*"]
|
|
131
|
+
vector = fstore.FeatureVector(
|
|
132
|
+
name=f"{self.endpoint_id}_vector",
|
|
133
|
+
features=features,
|
|
134
|
+
with_indexes=True,
|
|
135
|
+
)
|
|
136
|
+
vector.metadata.tag = self.application_name
|
|
137
|
+
vector.feature_set_objects = {feature_set.metadata.name: feature_set}
|
|
138
|
+
|
|
139
|
+
offline_response = vector.get_offline_features(
|
|
140
|
+
start_time=self.start_infer_time,
|
|
141
|
+
end_time=self.end_infer_time,
|
|
142
|
+
timestamp_for_filtering=mm_constants.FeatureSetFeatures.time_stamp(),
|
|
143
|
+
)
|
|
144
|
+
self._sample_df = offline_response.to_dataframe().reset_index(drop=True)
|
|
145
|
+
return self._sample_df
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def model_endpoint(self) -> ModelEndpoint:
|
|
149
|
+
if not hasattr(self, "_model_endpoint") or not self._model_endpoint:
|
|
150
|
+
self._model_endpoint = ModelEndpoint.from_flat_dict(
|
|
151
|
+
get_endpoint_record(self.project, self.endpoint_id)
|
|
152
|
+
)
|
|
153
|
+
return self._model_endpoint
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
def feature_stats(self) -> FeatureStats:
|
|
157
|
+
if not hasattr(self, "_feature_stats") or not self._feature_stats:
|
|
158
|
+
self._feature_stats = json.loads(self.model_endpoint.status.feature_stats)
|
|
159
|
+
pad_features_hist(self._feature_stats)
|
|
160
|
+
return self._feature_stats
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def sample_df_stats(self) -> FeatureStats:
|
|
164
|
+
"""statistics of the sample dataframe"""
|
|
165
|
+
if not hasattr(self, "_sample_df_stats") or not self._sample_df_stats:
|
|
166
|
+
self._sample_df_stats = calculate_inputs_statistics(
|
|
167
|
+
self.feature_stats, self.sample_df
|
|
168
|
+
)
|
|
169
|
+
return self._sample_df_stats
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def feature_names(self) -> list[str]:
|
|
173
|
+
"""The feature names of the model"""
|
|
174
|
+
feature_names = self.model_endpoint.spec.feature_names
|
|
175
|
+
return (
|
|
176
|
+
feature_names
|
|
177
|
+
if isinstance(feature_names, list)
|
|
178
|
+
else json.loads(feature_names)
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
@property
|
|
182
|
+
def label_names(self) -> list[str]:
|
|
183
|
+
"""The label names of the model"""
|
|
184
|
+
label_names = self.model_endpoint.spec.label_names
|
|
185
|
+
return label_names if isinstance(label_names, list) else json.loads(label_names)
|
|
186
|
+
|
|
187
|
+
@property
|
|
188
|
+
def model(self) -> tuple[str, ModelArtifact, dict]:
|
|
189
|
+
"""return model file, model spec object, and list of extra data items"""
|
|
190
|
+
return get_model(self.model_endpoint.spec.model_uri)
|
|
191
|
+
|
|
192
|
+
@staticmethod
|
|
193
|
+
def dict_to_histogram(
|
|
194
|
+
histogram_dict: mlrun.common.model_monitoring.helpers.FeatureStats,
|
|
195
|
+
) -> pd.DataFrame:
|
|
196
|
+
"""
|
|
197
|
+
Convert histogram dictionary to pandas DataFrame with feature histograms as columns
|
|
198
|
+
|
|
199
|
+
:param histogram_dict: Histogram dictionary
|
|
200
|
+
|
|
201
|
+
:returns: Histogram dataframe
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
# Create a dictionary with feature histograms as values
|
|
205
|
+
histograms = {}
|
|
206
|
+
for feature, stats in histogram_dict.items():
|
|
207
|
+
if "hist" in stats:
|
|
208
|
+
# Normalize to probability distribution of each feature
|
|
209
|
+
histograms[feature] = np.array(stats["hist"][0]) / stats["count"]
|
|
210
|
+
|
|
211
|
+
# Convert the dictionary to pandas DataFrame
|
|
212
|
+
histograms = pd.DataFrame(histograms)
|
|
213
|
+
|
|
214
|
+
return histograms
|