mlrun 1.7.0rc4__py3-none-any.whl → 1.7.2__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.

Files changed (235) hide show
  1. mlrun/__init__.py +11 -1
  2. mlrun/__main__.py +39 -121
  3. mlrun/{datastore/helpers.py → alerts/__init__.py} +2 -5
  4. mlrun/alerts/alert.py +248 -0
  5. mlrun/api/schemas/__init__.py +4 -3
  6. mlrun/artifacts/__init__.py +8 -3
  7. mlrun/artifacts/base.py +39 -254
  8. mlrun/artifacts/dataset.py +9 -190
  9. mlrun/artifacts/manager.py +73 -46
  10. mlrun/artifacts/model.py +30 -158
  11. mlrun/artifacts/plots.py +23 -380
  12. mlrun/common/constants.py +73 -1
  13. mlrun/common/db/sql_session.py +3 -2
  14. mlrun/common/formatters/__init__.py +21 -0
  15. mlrun/common/formatters/artifact.py +46 -0
  16. mlrun/common/formatters/base.py +113 -0
  17. mlrun/common/formatters/feature_set.py +44 -0
  18. mlrun/common/formatters/function.py +46 -0
  19. mlrun/common/formatters/pipeline.py +53 -0
  20. mlrun/common/formatters/project.py +51 -0
  21. mlrun/common/formatters/run.py +29 -0
  22. mlrun/common/helpers.py +11 -1
  23. mlrun/{runtimes → common/runtimes}/constants.py +32 -4
  24. mlrun/common/schemas/__init__.py +31 -4
  25. mlrun/common/schemas/alert.py +202 -0
  26. mlrun/common/schemas/api_gateway.py +196 -0
  27. mlrun/common/schemas/artifact.py +28 -1
  28. mlrun/common/schemas/auth.py +13 -2
  29. mlrun/common/schemas/client_spec.py +2 -1
  30. mlrun/common/schemas/common.py +7 -4
  31. mlrun/common/schemas/constants.py +3 -0
  32. mlrun/common/schemas/feature_store.py +58 -28
  33. mlrun/common/schemas/frontend_spec.py +8 -0
  34. mlrun/common/schemas/function.py +11 -0
  35. mlrun/common/schemas/hub.py +7 -9
  36. mlrun/common/schemas/model_monitoring/__init__.py +21 -4
  37. mlrun/common/schemas/model_monitoring/constants.py +136 -42
  38. mlrun/common/schemas/model_monitoring/grafana.py +9 -5
  39. mlrun/common/schemas/model_monitoring/model_endpoints.py +89 -41
  40. mlrun/common/schemas/notification.py +69 -12
  41. mlrun/{runtimes/mpijob/v1alpha1.py → common/schemas/pagination.py} +10 -13
  42. mlrun/common/schemas/pipeline.py +7 -0
  43. mlrun/common/schemas/project.py +67 -16
  44. mlrun/common/schemas/runs.py +17 -0
  45. mlrun/common/schemas/schedule.py +1 -1
  46. mlrun/common/schemas/workflow.py +10 -2
  47. mlrun/common/types.py +14 -1
  48. mlrun/config.py +233 -58
  49. mlrun/data_types/data_types.py +11 -1
  50. mlrun/data_types/spark.py +5 -4
  51. mlrun/data_types/to_pandas.py +75 -34
  52. mlrun/datastore/__init__.py +8 -10
  53. mlrun/datastore/alibaba_oss.py +131 -0
  54. mlrun/datastore/azure_blob.py +131 -43
  55. mlrun/datastore/base.py +107 -47
  56. mlrun/datastore/datastore.py +17 -7
  57. mlrun/datastore/datastore_profile.py +91 -7
  58. mlrun/datastore/dbfs_store.py +3 -7
  59. mlrun/datastore/filestore.py +1 -3
  60. mlrun/datastore/google_cloud_storage.py +92 -32
  61. mlrun/datastore/hdfs.py +5 -0
  62. mlrun/datastore/inmem.py +6 -3
  63. mlrun/datastore/redis.py +3 -2
  64. mlrun/datastore/s3.py +30 -12
  65. mlrun/datastore/snowflake_utils.py +45 -0
  66. mlrun/datastore/sources.py +274 -59
  67. mlrun/datastore/spark_utils.py +30 -0
  68. mlrun/datastore/store_resources.py +9 -7
  69. mlrun/datastore/storeytargets.py +151 -0
  70. mlrun/datastore/targets.py +387 -119
  71. mlrun/datastore/utils.py +68 -5
  72. mlrun/datastore/v3io.py +28 -50
  73. mlrun/db/auth_utils.py +152 -0
  74. mlrun/db/base.py +245 -20
  75. mlrun/db/factory.py +1 -4
  76. mlrun/db/httpdb.py +909 -231
  77. mlrun/db/nopdb.py +279 -14
  78. mlrun/errors.py +35 -5
  79. mlrun/execution.py +111 -38
  80. mlrun/feature_store/__init__.py +0 -2
  81. mlrun/feature_store/api.py +46 -53
  82. mlrun/feature_store/common.py +6 -11
  83. mlrun/feature_store/feature_set.py +48 -23
  84. mlrun/feature_store/feature_vector.py +13 -2
  85. mlrun/feature_store/ingestion.py +7 -6
  86. mlrun/feature_store/retrieval/base.py +9 -4
  87. mlrun/feature_store/retrieval/dask_merger.py +2 -0
  88. mlrun/feature_store/retrieval/job.py +13 -4
  89. mlrun/feature_store/retrieval/local_merger.py +2 -0
  90. mlrun/feature_store/retrieval/spark_merger.py +24 -32
  91. mlrun/feature_store/steps.py +38 -19
  92. mlrun/features.py +6 -14
  93. mlrun/frameworks/_common/plan.py +3 -3
  94. mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +7 -12
  95. mlrun/frameworks/_ml_common/plan.py +1 -1
  96. mlrun/frameworks/auto_mlrun/auto_mlrun.py +2 -2
  97. mlrun/frameworks/lgbm/__init__.py +1 -1
  98. mlrun/frameworks/lgbm/callbacks/callback.py +2 -4
  99. mlrun/frameworks/lgbm/model_handler.py +1 -1
  100. mlrun/frameworks/parallel_coordinates.py +4 -4
  101. mlrun/frameworks/pytorch/__init__.py +2 -2
  102. mlrun/frameworks/sklearn/__init__.py +1 -1
  103. mlrun/frameworks/sklearn/mlrun_interface.py +13 -3
  104. mlrun/frameworks/tf_keras/__init__.py +5 -2
  105. mlrun/frameworks/tf_keras/callbacks/logging_callback.py +1 -1
  106. mlrun/frameworks/tf_keras/mlrun_interface.py +2 -2
  107. mlrun/frameworks/xgboost/__init__.py +1 -1
  108. mlrun/k8s_utils.py +57 -12
  109. mlrun/launcher/__init__.py +1 -1
  110. mlrun/launcher/base.py +6 -5
  111. mlrun/launcher/client.py +13 -11
  112. mlrun/launcher/factory.py +1 -1
  113. mlrun/launcher/local.py +15 -5
  114. mlrun/launcher/remote.py +10 -3
  115. mlrun/lists.py +6 -2
  116. mlrun/model.py +297 -48
  117. mlrun/model_monitoring/__init__.py +1 -1
  118. mlrun/model_monitoring/api.py +152 -357
  119. mlrun/model_monitoring/applications/__init__.py +10 -0
  120. mlrun/model_monitoring/applications/_application_steps.py +190 -0
  121. mlrun/model_monitoring/applications/base.py +108 -0
  122. mlrun/model_monitoring/applications/context.py +341 -0
  123. mlrun/model_monitoring/{evidently_application.py → applications/evidently_base.py} +27 -22
  124. mlrun/model_monitoring/applications/histogram_data_drift.py +227 -91
  125. mlrun/model_monitoring/applications/results.py +99 -0
  126. mlrun/model_monitoring/controller.py +130 -303
  127. mlrun/model_monitoring/{stores/models/sqlite.py → db/__init__.py} +5 -10
  128. mlrun/model_monitoring/db/stores/__init__.py +136 -0
  129. mlrun/model_monitoring/db/stores/base/__init__.py +15 -0
  130. mlrun/model_monitoring/db/stores/base/store.py +213 -0
  131. mlrun/model_monitoring/db/stores/sqldb/__init__.py +13 -0
  132. mlrun/model_monitoring/db/stores/sqldb/models/__init__.py +71 -0
  133. mlrun/model_monitoring/db/stores/sqldb/models/base.py +190 -0
  134. mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +103 -0
  135. mlrun/model_monitoring/{stores/models/mysql.py → db/stores/sqldb/models/sqlite.py} +19 -13
  136. mlrun/model_monitoring/db/stores/sqldb/sql_store.py +659 -0
  137. mlrun/model_monitoring/db/stores/v3io_kv/__init__.py +13 -0
  138. mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +726 -0
  139. mlrun/model_monitoring/db/tsdb/__init__.py +105 -0
  140. mlrun/model_monitoring/db/tsdb/base.py +448 -0
  141. mlrun/model_monitoring/db/tsdb/helpers.py +30 -0
  142. mlrun/model_monitoring/db/tsdb/tdengine/__init__.py +15 -0
  143. mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +298 -0
  144. mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +42 -0
  145. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +522 -0
  146. mlrun/model_monitoring/db/tsdb/v3io/__init__.py +15 -0
  147. mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +158 -0
  148. mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +849 -0
  149. mlrun/model_monitoring/features_drift_table.py +34 -22
  150. mlrun/model_monitoring/helpers.py +177 -39
  151. mlrun/model_monitoring/model_endpoint.py +3 -2
  152. mlrun/model_monitoring/stream_processing.py +165 -398
  153. mlrun/model_monitoring/tracking_policy.py +7 -1
  154. mlrun/model_monitoring/writer.py +161 -125
  155. mlrun/package/packagers/default_packager.py +2 -2
  156. mlrun/package/packagers_manager.py +1 -0
  157. mlrun/package/utils/_formatter.py +2 -2
  158. mlrun/platforms/__init__.py +11 -10
  159. mlrun/platforms/iguazio.py +67 -228
  160. mlrun/projects/__init__.py +6 -1
  161. mlrun/projects/operations.py +47 -20
  162. mlrun/projects/pipelines.py +396 -249
  163. mlrun/projects/project.py +1176 -406
  164. mlrun/render.py +28 -22
  165. mlrun/run.py +208 -181
  166. mlrun/runtimes/__init__.py +76 -11
  167. mlrun/runtimes/base.py +54 -24
  168. mlrun/runtimes/daskjob.py +9 -2
  169. mlrun/runtimes/databricks_job/databricks_runtime.py +1 -0
  170. mlrun/runtimes/databricks_job/databricks_wrapper.py +1 -1
  171. mlrun/runtimes/funcdoc.py +1 -29
  172. mlrun/runtimes/kubejob.py +34 -128
  173. mlrun/runtimes/local.py +39 -10
  174. mlrun/runtimes/mpijob/__init__.py +0 -20
  175. mlrun/runtimes/mpijob/abstract.py +8 -8
  176. mlrun/runtimes/mpijob/v1.py +1 -1
  177. mlrun/runtimes/nuclio/__init__.py +1 -0
  178. mlrun/runtimes/nuclio/api_gateway.py +769 -0
  179. mlrun/runtimes/nuclio/application/__init__.py +15 -0
  180. mlrun/runtimes/nuclio/application/application.py +758 -0
  181. mlrun/runtimes/nuclio/application/reverse_proxy.go +95 -0
  182. mlrun/runtimes/nuclio/function.py +188 -68
  183. mlrun/runtimes/nuclio/serving.py +57 -60
  184. mlrun/runtimes/pod.py +191 -58
  185. mlrun/runtimes/remotesparkjob.py +11 -8
  186. mlrun/runtimes/sparkjob/spark3job.py +17 -18
  187. mlrun/runtimes/utils.py +40 -73
  188. mlrun/secrets.py +6 -2
  189. mlrun/serving/__init__.py +8 -1
  190. mlrun/serving/remote.py +2 -3
  191. mlrun/serving/routers.py +89 -64
  192. mlrun/serving/server.py +54 -26
  193. mlrun/serving/states.py +187 -56
  194. mlrun/serving/utils.py +19 -11
  195. mlrun/serving/v2_serving.py +136 -63
  196. mlrun/track/tracker.py +2 -1
  197. mlrun/track/trackers/mlflow_tracker.py +5 -0
  198. mlrun/utils/async_http.py +26 -6
  199. mlrun/utils/db.py +18 -0
  200. mlrun/utils/helpers.py +375 -105
  201. mlrun/utils/http.py +2 -2
  202. mlrun/utils/logger.py +75 -9
  203. mlrun/utils/notifications/notification/__init__.py +14 -10
  204. mlrun/utils/notifications/notification/base.py +48 -0
  205. mlrun/utils/notifications/notification/console.py +2 -0
  206. mlrun/utils/notifications/notification/git.py +24 -1
  207. mlrun/utils/notifications/notification/ipython.py +2 -0
  208. mlrun/utils/notifications/notification/slack.py +96 -21
  209. mlrun/utils/notifications/notification/webhook.py +63 -2
  210. mlrun/utils/notifications/notification_pusher.py +146 -16
  211. mlrun/utils/regex.py +9 -0
  212. mlrun/utils/retryer.py +3 -2
  213. mlrun/utils/v3io_clients.py +2 -3
  214. mlrun/utils/version/version.json +2 -2
  215. mlrun-1.7.2.dist-info/METADATA +390 -0
  216. mlrun-1.7.2.dist-info/RECORD +351 -0
  217. {mlrun-1.7.0rc4.dist-info → mlrun-1.7.2.dist-info}/WHEEL +1 -1
  218. mlrun/feature_store/retrieval/conversion.py +0 -271
  219. mlrun/kfpops.py +0 -868
  220. mlrun/model_monitoring/application.py +0 -310
  221. mlrun/model_monitoring/batch.py +0 -974
  222. mlrun/model_monitoring/controller_handler.py +0 -37
  223. mlrun/model_monitoring/prometheus.py +0 -216
  224. mlrun/model_monitoring/stores/__init__.py +0 -111
  225. mlrun/model_monitoring/stores/kv_model_endpoint_store.py +0 -574
  226. mlrun/model_monitoring/stores/model_endpoint_store.py +0 -145
  227. mlrun/model_monitoring/stores/models/__init__.py +0 -27
  228. mlrun/model_monitoring/stores/models/base.py +0 -84
  229. mlrun/model_monitoring/stores/sql_model_endpoint_store.py +0 -382
  230. mlrun/platforms/other.py +0 -305
  231. mlrun-1.7.0rc4.dist-info/METADATA +0 -269
  232. mlrun-1.7.0rc4.dist-info/RECORD +0 -321
  233. {mlrun-1.7.0rc4.dist-info → mlrun-1.7.2.dist-info}/LICENSE +0 -0
  234. {mlrun-1.7.0rc4.dist-info → mlrun-1.7.2.dist-info}/entry_points.txt +0 -0
  235. {mlrun-1.7.0rc4.dist-info → mlrun-1.7.2.dist-info}/top_level.txt +0 -0
@@ -14,15 +14,16 @@
14
14
 
15
15
  import uuid
16
16
  import warnings
17
- from typing import Union
17
+ from abc import ABC
18
18
 
19
19
  import pandas as pd
20
20
  import semver
21
21
 
22
+ import mlrun.model_monitoring.applications.base as mm_base
23
+ import mlrun.model_monitoring.applications.context as mm_context
22
24
  from mlrun.errors import MLRunIncompatibleVersionError
23
- from mlrun.model_monitoring.application import ModelMonitoringApplicationBase
24
25
 
25
- SUPPORTED_EVIDENTLY_VERSION = semver.Version.parse("0.4.11")
26
+ SUPPORTED_EVIDENTLY_VERSION = semver.Version.parse("0.4.39")
26
27
 
27
28
 
28
29
  def _check_evidently_version(*, cur: semver.Version, ref: semver.Version) -> None:
@@ -56,15 +57,15 @@ except ModuleNotFoundError:
56
57
 
57
58
 
58
59
  if _HAS_EVIDENTLY:
59
- from evidently.renderers.notebook_utils import determine_template
60
- from evidently.report.report import Report
61
- from evidently.suite.base_suite import Suite
60
+ from evidently.suite.base_suite import Display
62
61
  from evidently.ui.type_aliases import STR_UUID
63
62
  from evidently.ui.workspace import Workspace
64
- from evidently.utils.dashboard import TemplateParams
63
+ from evidently.utils.dashboard import TemplateParams, file_html_template
65
64
 
66
65
 
67
- class EvidentlyModelMonitoringApplicationBase(ModelMonitoringApplicationBase):
66
+ class EvidentlyModelMonitoringApplicationBase(
67
+ mm_base.ModelMonitoringApplicationBase, ABC
68
+ ):
68
69
  def __init__(
69
70
  self, evidently_workspace_path: str, evidently_project_id: "STR_UUID"
70
71
  ) -> None:
@@ -77,6 +78,8 @@ class EvidentlyModelMonitoringApplicationBase(ModelMonitoringApplicationBase):
77
78
  :param evidently_project_id: (str) The ID of the Evidently project.
78
79
 
79
80
  """
81
+
82
+ # TODO : more then one project (mep -> project)
80
83
  if not _HAS_EVIDENTLY:
81
84
  raise ModuleNotFoundError("Evidently is not installed - the app cannot run")
82
85
  self.evidently_workspace = Workspace.create(evidently_workspace_path)
@@ -85,32 +88,38 @@ class EvidentlyModelMonitoringApplicationBase(ModelMonitoringApplicationBase):
85
88
  evidently_project_id
86
89
  )
87
90
 
91
+ @staticmethod
88
92
  def log_evidently_object(
89
- self, evidently_object: Union["Report", "Suite"], artifact_name: str
90
- ):
93
+ monitoring_context: mm_context.MonitoringApplicationContext,
94
+ evidently_object: "Display",
95
+ artifact_name: str,
96
+ ) -> None:
91
97
  """
92
98
  Logs an Evidently report or suite as an artifact.
93
99
 
94
- :param evidently_object: (Union[Report, Suite]) The Evidently report or suite object.
100
+ :param monitoring_context: (MonitoringApplicationContext) The monitoring context to process.
101
+ :param evidently_object: (Display) The Evidently display to log, e.g. a report or a test suite object.
95
102
  :param artifact_name: (str) The name for the logged artifact.
96
103
  """
97
104
  evidently_object_html = evidently_object.get_html()
98
- self.context.log_artifact(
105
+ monitoring_context.log_artifact(
99
106
  artifact_name, body=evidently_object_html.encode("utf-8"), format="html"
100
107
  )
101
108
 
102
109
  def log_project_dashboard(
103
110
  self,
111
+ monitoring_context: mm_context.MonitoringApplicationContext,
104
112
  timestamp_start: pd.Timestamp,
105
113
  timestamp_end: pd.Timestamp,
106
114
  artifact_name: str = "dashboard",
107
- ):
115
+ ) -> None:
108
116
  """
109
117
  Logs an Evidently project dashboard.
110
118
 
111
- :param timestamp_start: (pd.Timestamp) The start timestamp for the dashboard data.
112
- :param timestamp_end: (pd.Timestamp) The end timestamp for the dashboard data.
113
- :param artifact_name: (str) The name for the logged artifact.
119
+ :param monitoring_context: (MonitoringApplicationContext) The monitoring context to process.
120
+ :param timestamp_start: (pd.Timestamp) The start timestamp for the dashboard data.
121
+ :param timestamp_end: (pd.Timestamp) The end timestamp for the dashboard data.
122
+ :param artifact_name: (str) The name for the logged artifact.
114
123
  """
115
124
 
116
125
  dashboard_info = self.evidently_project.build_dashboard_info(
@@ -122,11 +131,7 @@ class EvidentlyModelMonitoringApplicationBase(ModelMonitoringApplicationBase):
122
131
  additional_graphs={},
123
132
  )
124
133
 
125
- dashboard_html = self._render(determine_template("inline"), template_params)
126
- self.context.log_artifact(
134
+ dashboard_html = file_html_template(params=template_params)
135
+ monitoring_context.log_artifact(
127
136
  artifact_name, body=dashboard_html.encode("utf-8"), format="html"
128
137
  )
129
-
130
- @staticmethod
131
- def _render(temple_func, template_params: "TemplateParams"):
132
- return temple_func(params=template_params)
@@ -12,22 +12,28 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ import json
15
16
  from dataclasses import dataclass
16
- from typing import Final, Optional, Protocol
17
+ from typing import Final, Optional, Protocol, Union, cast
17
18
 
18
19
  import numpy as np
19
- from pandas import DataFrame, Timestamp
20
+ from pandas import DataFrame, Series
20
21
 
22
+ import mlrun.artifacts
23
+ import mlrun.common.model_monitoring.helpers
24
+ import mlrun.model_monitoring.applications.context as mm_context
25
+ import mlrun.model_monitoring.applications.results as mm_results
26
+ import mlrun.model_monitoring.features_drift_table as mm_drift_table
21
27
  from mlrun.common.schemas.model_monitoring.constants import (
22
- MLRUN_HISTOGRAM_DATA_DRIFT_APP_NAME,
28
+ EventFieldType,
29
+ HistogramDataDriftApplicationConstants,
23
30
  ResultKindApp,
24
31
  ResultStatusApp,
25
32
  )
26
- from mlrun.model_monitoring.application import (
33
+ from mlrun.model_monitoring.applications import (
27
34
  ModelMonitoringApplicationBase,
28
- ModelMonitoringApplicationResult,
29
35
  )
30
- from mlrun.model_monitoring.batch import (
36
+ from mlrun.model_monitoring.metrics.histogram_distance import (
31
37
  HellingerDistance,
32
38
  HistogramDistanceMetric,
33
39
  KullbackLeiblerDivergence,
@@ -85,13 +91,33 @@ class HistogramDataDriftApplication(ModelMonitoringApplicationBase):
85
91
  """
86
92
  MLRun's default data drift application for model monitoring.
87
93
 
88
- The application calculates the metrics over the features' histograms.
89
- Each metric is calculated over all the features, the mean is taken,
90
- and the status is returned.
94
+ The application expects tabular numerical data, and calculates three metrics over the shared features' histograms.
95
+ The metrics are calculated on features that have reference data from the training dataset. When there is no
96
+ reference data (`feature_stats`), this application send a warning log and does nothing.
97
+ The three metrics are:
98
+
99
+ * Hellinger distance.
100
+ * Total variance distance.
101
+ * Kullback-Leibler divergence.
102
+
103
+ Each metric is calculated over all the features individually and the mean is taken as the metric value.
104
+ The average of Hellinger and total variance distance is taken as the result.
105
+
106
+ The application logs two artifacts:
107
+
108
+ * A JSON with the general drift per feature.
109
+ * A plotly table different metrics per feature.
110
+
111
+ This application is deployed by default when calling:
112
+
113
+ .. code-block:: python
114
+
115
+ project.enable_model_monitoring()
116
+
117
+ To avoid it, pass `deploy_histogram_data_drift_app=False`.
91
118
  """
92
119
 
93
- NAME: Final[str] = MLRUN_HISTOGRAM_DATA_DRIFT_APP_NAME
94
- METRIC_KIND: Final[ResultKindApp] = ResultKindApp.data_drift
120
+ NAME: Final[str] = HistogramDataDriftApplicationConstants.NAME
95
121
 
96
122
  _REQUIRED_METRICS = {HellingerDistance, TotalVarianceDistance}
97
123
 
@@ -103,8 +129,6 @@ class HistogramDataDriftApplication(ModelMonitoringApplicationBase):
103
129
 
104
130
  def __init__(self, value_classifier: Optional[ValueClassifier] = None) -> None:
105
131
  """
106
- Initialize the data drift application.
107
-
108
132
  :param value_classifier: Classifier object that adheres to the `ValueClassifier` protocol.
109
133
  If not provided, the default `DataDriftClassifier()` is used.
110
134
  """
@@ -114,105 +138,217 @@ class HistogramDataDriftApplication(ModelMonitoringApplicationBase):
114
138
  ), "TVD and Hellinger distance are required for the general data drift result"
115
139
 
116
140
  def _compute_metrics_per_feature(
117
- self, sample_df_stats: DataFrame, feature_stats: DataFrame
118
- ) -> dict[type[HistogramDistanceMetric], list[float]]:
141
+ self, monitoring_context: mm_context.MonitoringApplicationContext
142
+ ) -> DataFrame:
119
143
  """Compute the metrics for the different features and labels"""
120
- metrics_per_feature: dict[type[HistogramDistanceMetric], list[float]] = {
121
- metric_class: [] for metric_class in self.metrics
122
- }
123
-
124
- for (sample_feat, sample_hist), (reference_feat, reference_hist) in zip(
125
- sample_df_stats.items(), feature_stats.items()
126
- ):
127
- assert sample_feat == reference_feat, "The features do not match"
128
- self.context.logger.info(
129
- "Computing metrics for feature", feature_name=sample_feat
144
+ metrics_per_feature = DataFrame(
145
+ columns=[metric_class.NAME for metric_class in self.metrics]
146
+ )
147
+ feature_stats = monitoring_context.dict_to_histogram(
148
+ monitoring_context.feature_stats
149
+ )
150
+ sample_df_stats = monitoring_context.dict_to_histogram(
151
+ monitoring_context.sample_df_stats
152
+ )
153
+ for feature_name in feature_stats:
154
+ sample_hist = np.asarray(sample_df_stats[feature_name])
155
+ reference_hist = np.asarray(feature_stats[feature_name])
156
+ monitoring_context.logger.info(
157
+ "Computing metrics for feature", feature_name=feature_name
130
158
  )
131
- sample_arr = np.asarray(sample_hist)
132
- reference_arr = np.asarray(reference_hist)
133
- for metric in self.metrics:
134
- metric_name = metric.NAME
135
- self.context.logger.debug(
136
- "Computing data drift metric",
137
- metric_name=metric_name,
138
- feature_name=sample_feat,
139
- )
140
- metrics_per_feature[metric].append(
141
- metric(distrib_t=sample_arr, distrib_u=reference_arr).compute()
142
- )
143
- self.context.logger.info("Finished computing the metrics")
159
+ metrics_per_feature.loc[feature_name] = { # pyright: ignore[reportCallIssue,reportArgumentType]
160
+ metric.NAME: metric(
161
+ distrib_t=sample_hist, distrib_u=reference_hist
162
+ ).compute()
163
+ for metric in self.metrics
164
+ }
165
+ monitoring_context.logger.info("Finished computing the metrics")
144
166
 
145
167
  return metrics_per_feature
146
168
 
147
- def _add_general_drift_result(
148
- self, results: list[ModelMonitoringApplicationResult], value: float
149
- ) -> None:
150
- results.append(
151
- ModelMonitoringApplicationResult(
152
- name="general_drift",
153
- value=value,
154
- kind=self.METRIC_KIND,
155
- status=self._value_classifier.value_to_status(value),
156
- )
169
+ def _get_general_drift_result(
170
+ self,
171
+ metrics: list[mm_results.ModelMonitoringApplicationMetric],
172
+ monitoring_context: mm_context.MonitoringApplicationContext,
173
+ metrics_per_feature: DataFrame,
174
+ ) -> mm_results.ModelMonitoringApplicationResult:
175
+ """Get the general drift result from the metrics list"""
176
+ value = cast(
177
+ float,
178
+ np.mean(
179
+ [
180
+ metric.value
181
+ for metric in metrics
182
+ if metric.name
183
+ in [
184
+ f"{HellingerDistance.NAME}_mean",
185
+ f"{TotalVarianceDistance.NAME}_mean",
186
+ ]
187
+ ]
188
+ ),
189
+ )
190
+
191
+ status = self._value_classifier.value_to_status(value)
192
+ return mm_results.ModelMonitoringApplicationResult(
193
+ name=HistogramDataDriftApplicationConstants.GENERAL_RESULT_NAME,
194
+ value=value,
195
+ kind=ResultKindApp.data_drift,
196
+ status=status,
197
+ extra_data={
198
+ EventFieldType.CURRENT_STATS: json.dumps(
199
+ monitoring_context.sample_df_stats
200
+ ),
201
+ EventFieldType.DRIFT_MEASURES: json.dumps(
202
+ metrics_per_feature.T.to_dict()
203
+ | {metric.name: metric.value for metric in metrics}
204
+ ),
205
+ EventFieldType.DRIFT_STATUS: status.value,
206
+ },
157
207
  )
158
208
 
159
- def _get_results(
160
- self, metrics_per_feature: dict[type[HistogramDistanceMetric], list[float]]
161
- ) -> list[ModelMonitoringApplicationResult]:
209
+ @staticmethod
210
+ def _get_metrics(
211
+ metrics_per_feature: DataFrame,
212
+ ) -> list[mm_results.ModelMonitoringApplicationMetric]:
162
213
  """Average the metrics over the features and add the status"""
163
- results: list[ModelMonitoringApplicationResult] = []
164
- hellinger_tvd_values: list[float] = []
165
- for metric_class, metric_values in metrics_per_feature.items():
166
- self.context.logger.debug(
167
- "Averaging metric over the features", metric_name=metric_class.NAME
168
- )
169
- value = np.mean(metric_values)
170
- if metric_class == KullbackLeiblerDivergence:
171
- # This metric is not bounded from above [0, inf).
172
- # No status is currently reported for KL divergence
173
- status = ResultStatusApp.irrelevant
174
- else:
175
- status = self._value_classifier.value_to_status(value)
176
- if metric_class in self._REQUIRED_METRICS:
177
- hellinger_tvd_values.append(value)
178
- results.append(
179
- ModelMonitoringApplicationResult(
180
- name=f"{metric_class.NAME}_mean",
214
+ metrics: list[mm_results.ModelMonitoringApplicationMetric] = []
215
+
216
+ metrics_mean = metrics_per_feature.mean().to_dict()
217
+
218
+ for name, value in metrics_mean.items():
219
+ metrics.append(
220
+ mm_results.ModelMonitoringApplicationMetric(
221
+ name=f"{name}_mean",
181
222
  value=value,
182
- kind=self.METRIC_KIND,
183
- status=status,
184
223
  )
185
224
  )
186
225
 
187
- self._add_general_drift_result(
188
- results=results, value=np.mean(hellinger_tvd_values)
226
+ return metrics
227
+
228
+ @staticmethod
229
+ def _get_shared_features_sample_stats(
230
+ monitoring_context: mm_context.MonitoringApplicationContext,
231
+ ) -> mlrun.common.model_monitoring.helpers.FeatureStats:
232
+ """
233
+ Filter out features without reference data in `feature_stats`, e.g. `timestamp`.
234
+ """
235
+ return mlrun.common.model_monitoring.helpers.FeatureStats(
236
+ {
237
+ key: monitoring_context.sample_df_stats[key]
238
+ for key in monitoring_context.feature_stats
239
+ }
240
+ )
241
+
242
+ @staticmethod
243
+ def _log_json_artifact(
244
+ drift_per_feature_values: Series,
245
+ monitoring_context: mm_context.MonitoringApplicationContext,
246
+ ) -> None:
247
+ """Log the drift values as a JSON artifact"""
248
+ monitoring_context.logger.debug("Logging drift value per feature JSON artifact")
249
+ monitoring_context.log_artifact(
250
+ mlrun.artifacts.Artifact(
251
+ body=drift_per_feature_values.to_json(),
252
+ format="json",
253
+ key="features_drift_results",
254
+ )
189
255
  )
256
+ monitoring_context.logger.debug("Logged JSON artifact successfully")
190
257
 
191
- return results
258
+ def _log_plotly_table_artifact(
259
+ self,
260
+ sample_set_statistics: mlrun.common.model_monitoring.helpers.FeatureStats,
261
+ inputs_statistics: mlrun.common.model_monitoring.helpers.FeatureStats,
262
+ metrics_per_feature: DataFrame,
263
+ drift_per_feature_values: Series,
264
+ monitoring_context: mm_context.MonitoringApplicationContext,
265
+ ) -> None:
266
+ """Log the Plotly drift table artifact"""
267
+ monitoring_context.logger.debug(
268
+ "Feature stats",
269
+ sample_set_statistics=sample_set_statistics,
270
+ inputs_statistics=inputs_statistics,
271
+ )
272
+
273
+ monitoring_context.logger.debug("Computing drift results per feature")
274
+ drift_results = {
275
+ cast(str, key): (self._value_classifier.value_to_status(value), value)
276
+ for key, value in drift_per_feature_values.items()
277
+ }
278
+ monitoring_context.logger.debug("Logging plotly artifact")
279
+ monitoring_context.log_artifact(
280
+ mm_drift_table.FeaturesDriftTablePlot().produce(
281
+ sample_set_statistics=sample_set_statistics,
282
+ inputs_statistics=inputs_statistics,
283
+ metrics=metrics_per_feature.T.to_dict(), # pyright: ignore[reportArgumentType]
284
+ drift_results=drift_results,
285
+ )
286
+ )
287
+ monitoring_context.logger.debug("Logged plotly artifact successfully")
288
+
289
+ def _log_drift_artifacts(
290
+ self,
291
+ monitoring_context: mm_context.MonitoringApplicationContext,
292
+ metrics_per_feature: DataFrame,
293
+ log_json_artifact: bool = True,
294
+ ) -> None:
295
+ """Log JSON and Plotly drift data per feature artifacts"""
296
+ drift_per_feature_values = metrics_per_feature[
297
+ [HellingerDistance.NAME, TotalVarianceDistance.NAME]
298
+ ].mean(axis=1)
299
+
300
+ if log_json_artifact:
301
+ self._log_json_artifact(drift_per_feature_values, monitoring_context)
302
+
303
+ self._log_plotly_table_artifact(
304
+ sample_set_statistics=self._get_shared_features_sample_stats(
305
+ monitoring_context
306
+ ),
307
+ inputs_statistics=monitoring_context.feature_stats,
308
+ metrics_per_feature=metrics_per_feature,
309
+ drift_per_feature_values=drift_per_feature_values,
310
+ monitoring_context=monitoring_context,
311
+ )
192
312
 
193
313
  def do_tracking(
194
314
  self,
195
- application_name: str,
196
- sample_df_stats: DataFrame,
197
- feature_stats: DataFrame,
198
- sample_df: DataFrame,
199
- start_infer_time: Timestamp,
200
- end_infer_time: Timestamp,
201
- latest_request: Timestamp,
202
- endpoint_id: str,
203
- output_stream_uri: str,
204
- ) -> list[ModelMonitoringApplicationResult]:
315
+ monitoring_context: mm_context.MonitoringApplicationContext,
316
+ ) -> list[
317
+ Union[
318
+ mm_results.ModelMonitoringApplicationResult,
319
+ mm_results.ModelMonitoringApplicationMetric,
320
+ ]
321
+ ]:
205
322
  """
206
323
  Calculate and return the data drift metrics, averaged over the features.
207
324
 
208
- Refer to `ModelMonitoringApplicationBase` for the meaning of the
325
+ Refer to `ModelMonitoringApplicationBaseV2` for the meaning of the
209
326
  function arguments.
210
327
  """
211
- self.context.logger.debug("Starting to run the application")
328
+ monitoring_context.logger.debug("Starting to run the application")
329
+ if not monitoring_context.feature_stats:
330
+ monitoring_context.logger.warning(
331
+ "No feature statistics found, skipping the application. \n"
332
+ "In order to run the application, training set must be provided when logging the model."
333
+ )
334
+ return []
212
335
  metrics_per_feature = self._compute_metrics_per_feature(
213
- sample_df_stats=sample_df_stats, feature_stats=feature_stats
336
+ monitoring_context=monitoring_context
337
+ )
338
+ monitoring_context.logger.debug("Saving artifacts")
339
+ self._log_drift_artifacts(
340
+ monitoring_context=monitoring_context,
341
+ metrics_per_feature=metrics_per_feature,
342
+ )
343
+ monitoring_context.logger.debug("Computing average per metric")
344
+ metrics = self._get_metrics(metrics_per_feature)
345
+ result = self._get_general_drift_result(
346
+ metrics=metrics,
347
+ monitoring_context=monitoring_context,
348
+ metrics_per_feature=metrics_per_feature,
349
+ )
350
+ metrics_and_result = metrics + [result]
351
+ monitoring_context.logger.debug(
352
+ "Finished running the application", results=metrics_and_result
214
353
  )
215
- self.context.logger.debug("Computing average per metric")
216
- results = self._get_results(metrics_per_feature)
217
- self.context.logger.debug("Finished running the application", results=results)
218
- return results
354
+ return metrics_and_result
@@ -0,0 +1,99 @@
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
+ import dataclasses
16
+ import json
17
+ import re
18
+ from abc import ABC, abstractmethod
19
+
20
+ import mlrun.common.helpers
21
+ import mlrun.common.model_monitoring.helpers
22
+ import mlrun.common.schemas.model_monitoring.constants as mm_constant
23
+ import mlrun.utils.v3io_clients
24
+
25
+
26
+ class _ModelMonitoringApplicationDataRes(ABC):
27
+ name: str
28
+
29
+ def __post_init__(self):
30
+ pat = re.compile(r"[a-zA-Z_][a-zA-Z0-9_]*")
31
+ if not re.fullmatch(pat, self.name):
32
+ raise mlrun.errors.MLRunValueError(
33
+ "Attribute name must comply with the regex `[a-zA-Z_][a-zA-Z0-9_]*`"
34
+ )
35
+
36
+ @abstractmethod
37
+ def to_dict(self):
38
+ raise NotImplementedError
39
+
40
+
41
+ @dataclasses.dataclass
42
+ class ModelMonitoringApplicationResult(_ModelMonitoringApplicationDataRes):
43
+ """
44
+ Class representing the result of a custom model monitoring application.
45
+
46
+ :param name: (str) Name of the application result. This name must be
47
+ unique for each metric in a single application
48
+ (name must be of the format :code:`[a-zA-Z_][a-zA-Z0-9_]*`).
49
+ :param value: (float) Value of the application result.
50
+ :param kind: (ResultKindApp) Kind of application result.
51
+ :param status: (ResultStatusApp) Status of the application result.
52
+ :param extra_data: (dict) Extra data associated with the application result.
53
+ """
54
+
55
+ name: str
56
+ value: float
57
+ kind: mm_constant.ResultKindApp
58
+ status: mm_constant.ResultStatusApp
59
+ extra_data: dict = dataclasses.field(default_factory=dict)
60
+
61
+ def to_dict(self):
62
+ """
63
+ Convert the object to a dictionary format suitable for writing.
64
+
65
+ :returns: (dict) Dictionary representation of the result.
66
+ """
67
+ return {
68
+ mm_constant.ResultData.RESULT_NAME: self.name,
69
+ mm_constant.ResultData.RESULT_VALUE: self.value,
70
+ mm_constant.ResultData.RESULT_KIND: self.kind.value,
71
+ mm_constant.ResultData.RESULT_STATUS: self.status.value,
72
+ mm_constant.ResultData.RESULT_EXTRA_DATA: json.dumps(self.extra_data),
73
+ }
74
+
75
+
76
+ @dataclasses.dataclass
77
+ class ModelMonitoringApplicationMetric(_ModelMonitoringApplicationDataRes):
78
+ """
79
+ Class representing a single metric of a custom model monitoring application.
80
+
81
+ :param name: (str) Name of the application metric. This name must be
82
+ unique for each metric in a single application
83
+ (name must be of the format :code:`[a-zA-Z_][a-zA-Z0-9_]*`).
84
+ :param value: (float) Value of the application metric.
85
+ """
86
+
87
+ name: str
88
+ value: float
89
+
90
+ def to_dict(self):
91
+ """
92
+ Convert the object to a dictionary format suitable for writing.
93
+
94
+ :returns: (dict) Dictionary representation of the result.
95
+ """
96
+ return {
97
+ mm_constant.MetricData.METRIC_NAME: self.name,
98
+ mm_constant.MetricData.METRIC_VALUE: self.value,
99
+ }