SIENTIAPDE-1273
Enhance MLFlowRepository and Activities classes with new methods and metrics - Added `check_artifact_exists` method to MLFlowRepository for verifying artifact presence in the MLflow Model Registry. - Implemented `get_prediction_data` method in MLFlowRepository to retrieve prediction data from models. - Updated Activities class to integrate ModelMetrics for improved metrics handling. - Enhanced tests for artifact existence checks and prediction data retrieval, ensuring robust coverage for new functionalities. - Updated various workflows to include `transform_table_name` in input data for better data handling.
This commit is contained in:
@@ -217,6 +217,24 @@ class MLFlowRepository(SientiaMonitoring):
|
||||
run_info = mlflow.get_run(run_id)
|
||||
return run_info.data.params
|
||||
|
||||
def check_artifact_exists(self, run_id: str,
|
||||
artifact_path: str, metadata: dict[str, Any]) -> bool:
|
||||
"""
|
||||
Check if an artifact exists in the MLflow Model Registry.
|
||||
|
||||
Args:
|
||||
run_id (str): Run identifier to inspect.
|
||||
artifact_path (str): Path to the artifact to check.
|
||||
|
||||
Returns:
|
||||
bool: True if the artifact exists, False otherwise.
|
||||
"""
|
||||
artifacts = self.client.list_artifacts(run_id)
|
||||
|
||||
self.debug(f'Artifacts of {run_id}: \n{artifacts}', metadata)
|
||||
self.debug(f'Looking for artifact {artifact_path} in {run_id}', metadata)
|
||||
|
||||
return any(artifact.path == artifact_path for artifact in artifacts)
|
||||
"""
|
||||
Functions related to download and load models
|
||||
"""
|
||||
@@ -263,7 +281,7 @@ class MLFlowRepository(SientiaMonitoring):
|
||||
|
||||
|
||||
async def load_artifact_dataframe(self, model_name: str, artifact_path: str,
|
||||
metadata: dict[str, Any]) -> pd.DataFrame:
|
||||
metadata: dict[str, Any]) -> pd.DataFrame | None:
|
||||
|
||||
"""
|
||||
Load the dataframe content of an artifact from the MLflow Model Registry.
|
||||
@@ -277,9 +295,13 @@ class MLFlowRepository(SientiaMonitoring):
|
||||
pd.DataFrame: The dataframe content of the artifact.
|
||||
"""
|
||||
run_id = self.get_model_run_id(model_name=model_name, stage='Production')
|
||||
artifact_path = path.join("runs:/", run_id, artifact_path)
|
||||
core_labels = self.get_core_labels(metadata, operation_type='load_text')
|
||||
|
||||
if not self.check_artifact_exists(run_id, artifact_path, metadata):
|
||||
return None
|
||||
|
||||
artifact_path = path.join("runs:/", run_id, artifact_path)
|
||||
|
||||
start_time = time.time()
|
||||
try:
|
||||
content = mlflow.artifacts.load_text(artifact_path)
|
||||
@@ -290,6 +312,8 @@ class MLFlowRepository(SientiaMonitoring):
|
||||
await self.observe_lag(start_time, metrics.MODEL_READ_LAG, core_labels)
|
||||
await self.emit_metric(metric_object=metrics.MODEL_READ_COUNT, tags=core_labels)
|
||||
|
||||
self.debug(f'Content of {run_id}/{artifact_path}: \n{content}', metadata)
|
||||
|
||||
dataframe = pd.read_csv(StringIO(content))
|
||||
|
||||
self.info(f'Loaded dataframe from {model_name}:{artifact_path}', metadata)
|
||||
@@ -680,6 +704,41 @@ class MLFlowRepository(SientiaMonitoring):
|
||||
Functions related to model retraining
|
||||
"""
|
||||
|
||||
def get_prediction_data(self, prediction_model: Any, retrain_dataset: pd.DataFrame,
|
||||
target_name: str) -> pd.DataFrame:
|
||||
"""
|
||||
Get prediction data from prediction model.
|
||||
"""
|
||||
input_index = retrain_dataset.index
|
||||
|
||||
prediction_data = prediction_model.predict(retrain_dataset)
|
||||
|
||||
if isinstance(prediction_data, pd.DataFrame):
|
||||
|
||||
prediction_data.columns = pd.Index(['prediction'])
|
||||
|
||||
else:
|
||||
prediction_data = pd.DataFrame(prediction_data, columns=['prediction'])
|
||||
|
||||
prediction_data.index = input_index
|
||||
|
||||
# Merge prediction data with retrain_dataset on index
|
||||
prediction_data = pd.merge(
|
||||
retrain_dataset, prediction_data, left_index=True, right_index=True, how='left')
|
||||
|
||||
# Rename column "target_name" to "target"
|
||||
prediction_data.rename(columns={target_name: 'target'}, inplace=True)
|
||||
|
||||
prediction_data['timestamp'] = prediction_data.index
|
||||
|
||||
prediction_data.reset_index(drop=True, inplace=True)
|
||||
|
||||
prediction_data.sort_values(
|
||||
by='timestamp', ascending=True, inplace=True
|
||||
)
|
||||
|
||||
return prediction_data
|
||||
|
||||
async def fit_models(
|
||||
self,
|
||||
model_name: str,
|
||||
@@ -689,7 +748,7 @@ class MLFlowRepository(SientiaMonitoring):
|
||||
transform_flavor: str = 'sklearn',
|
||||
predict_flavor: str = 'sklearn',
|
||||
target_name: str | None = None,
|
||||
) -> dict[str, dict[str, Any]]:
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Prepare models and data for a retraining run.
|
||||
|
||||
@@ -799,6 +858,10 @@ class MLFlowRepository(SientiaMonitoring):
|
||||
|
||||
prediction_model.fit(retrain_dataset)
|
||||
|
||||
# get prediction data
|
||||
prediction_data = self.get_prediction_data(
|
||||
prediction_model, retrain_dataset, target_name)
|
||||
|
||||
self.info(f'Model experiment creation completed successfully for {model_name}', metadata)
|
||||
|
||||
retrain_data = {
|
||||
@@ -807,6 +870,7 @@ class MLFlowRepository(SientiaMonitoring):
|
||||
'artifact_path': prediction_artifact_path,
|
||||
},
|
||||
'data_model': {'model': data_model, 'artifact_path': data_artifact_path},
|
||||
'prediction_data': prediction_data,
|
||||
}
|
||||
return retrain_data
|
||||
|
||||
@@ -885,6 +949,7 @@ class MLFlowRepository(SientiaMonitoring):
|
||||
|
||||
prediction_model = retrain_data['prediction_model']
|
||||
data_model = retrain_data['data_model']
|
||||
prediction_data = retrain_data['prediction_data']
|
||||
|
||||
model_temp_path = path.join(ARTIFACTS_PATH, model_name)
|
||||
|
||||
@@ -908,10 +973,12 @@ class MLFlowRepository(SientiaMonitoring):
|
||||
self.debug(f'Attributes: {retrain_params}', metadata)
|
||||
|
||||
data_path = f'{model_temp_path}/retrain_data.csv'
|
||||
prediction_data_path = f'{model_temp_path}/evaluation_data.csv'
|
||||
|
||||
makedirs(model_temp_path, exist_ok=True)
|
||||
|
||||
data.to_csv(data_path, index=True)
|
||||
data.to_csv(data_path, index=False)
|
||||
prediction_data.to_csv(prediction_data_path, index=False)
|
||||
|
||||
self.info(
|
||||
f'Starting model upload for {experiment_name} with run name {current_run_name}',
|
||||
@@ -945,6 +1012,7 @@ class MLFlowRepository(SientiaMonitoring):
|
||||
|
||||
# log the data raw
|
||||
mlflow.log_artifact(data_path)
|
||||
mlflow.log_artifact(prediction_data_path)
|
||||
|
||||
except Exception as e:
|
||||
await self.emit_metric(metric_object=metrics.MODEL_WRITE_ERROR_COUNT, tags=core_labels)
|
||||
@@ -1372,59 +1440,3 @@ class MLFlowRepository(SientiaMonitoring):
|
||||
|
||||
return metadata_result
|
||||
|
||||
async def get_model_metrics(self,
|
||||
analyse_data: pd.DataFrame,
|
||||
train_reference_data: pd.DataFrame,
|
||||
test_reference_data: pd.DataFrame,
|
||||
target_name: str,
|
||||
drift_metrics: list[str],
|
||||
performance_metrics: list[str],
|
||||
metadata: dict) -> dict[str, Any]:
|
||||
|
||||
"""
|
||||
Calculates drift and preformance metrics for a model.
|
||||
|
||||
Args:
|
||||
analyse_data (pd.DataFrame): The data to analyse.
|
||||
train_reference_data (pd.DataFrame): The train reference data.
|
||||
test_reference_data (pd.DataFrame): The test reference data.
|
||||
target_name (str): The target name.
|
||||
drift_metrics (list[str]): The drift metrics.
|
||||
performance_metrics (list[str]): The performance metrics.
|
||||
metadata (dict): The metadata.
|
||||
|
||||
Returns:
|
||||
dict: The metrics.
|
||||
"""
|
||||
columns = train_reference_data.drop(
|
||||
columns=[target_name, 'timestamp', 'target', 'prediction'], errors='ignore').columns
|
||||
|
||||
config = {
|
||||
"target": target_name,
|
||||
"prediction": "prediction",
|
||||
"timestamp": "timestamp",
|
||||
"columns": columns,
|
||||
}
|
||||
|
||||
model_analysis = ModelAnalysis(config=config)
|
||||
|
||||
core_labels = self.get_core_labels(metadata, operation_type='detect_univariate_drift')
|
||||
|
||||
start_time = time.time()
|
||||
try:
|
||||
model_analysis.detect_univariate_drift(
|
||||
reference_df=train_reference_data,
|
||||
analyse_df=analyse_data,
|
||||
features=columns,
|
||||
timestamp_column=config['timestamp'],
|
||||
metrics=drift_metrics,
|
||||
chunk_period="s"
|
||||
)
|
||||
except Exception as e:
|
||||
await self.emit_metric(metric_object=metrics.MODEL_ANALYZE_ERROR_COUNT, tags=core_labels)
|
||||
raise e
|
||||
|
||||
await self.observe_lag(start_time, metrics.MODEL_ANALYZE_LAG, core_labels)
|
||||
await self.emit_metric(metric_object=metrics.MODEL_ANALYZE_COUNT, tags=core_labels)
|
||||
|
||||
return {}
|
||||
|
||||
Reference in New Issue
Block a user