SIENTIAPDE-1273
Update requirements and enhance metrics and data handling - Updated the sientia-dataops-library dependency version in requirements.txt to 1.5.3. - Added new metrics for model analysis, including lag, count, and error count in metrics.py. - Implemented a new method for formatting transformed data in gates.py. - Enhanced MLFlowRepository with methods to load artifact dataframes and calculate model metrics, including drift and performance metrics. - Updated the prediction process to handle transformed data and ensure proper execution of related activities in format_and_export_prediction.py and prediction_process.py.
This commit is contained in:
@@ -402,6 +402,28 @@ class Gates(SientiaMonitoring):
|
|||||||
|
|
||||||
return policy_type, int(policy_value)
|
return policy_type, int(policy_value)
|
||||||
|
|
||||||
|
|
||||||
|
@activity.defn(name='format_transformed_data')
|
||||||
|
async def format_transformed_data(self, input_data: dict[str, Any]) -> dict[Any, Any]:
|
||||||
|
"""
|
||||||
|
Format transformed data according to configured storage policies.
|
||||||
|
"""
|
||||||
|
metadata = input_data['metadata']
|
||||||
|
|
||||||
|
model_id = input_data['model_id']
|
||||||
|
|
||||||
|
self.info('Formatting transformed data...', metadata)
|
||||||
|
|
||||||
|
data = DataFrame(input_data['data'])
|
||||||
|
|
||||||
|
data['timestamp'] = data.index
|
||||||
|
data = data.reset_index(drop=True)
|
||||||
|
|
||||||
|
data = data.melt(id_vars='timestamp', var_name='variable', value_name='value')
|
||||||
|
data['model_id'] = model_id
|
||||||
|
|
||||||
|
return data.to_dict()
|
||||||
|
|
||||||
@activity.defn(name='format_prediction')
|
@activity.defn(name='format_prediction')
|
||||||
async def format_prediction(self, input_data: dict[str, Any]) -> dict[Any, Any]:
|
async def format_prediction(self, input_data: dict[str, Any]) -> dict[Any, Any]:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -169,3 +169,22 @@ MODEL_WRITE_ERROR_COUNT = Counter(
|
|||||||
'Number of errors writing to the model',
|
'Number of errors writing to the model',
|
||||||
SIENTIA_CORE_LABELS,
|
SIENTIA_CORE_LABELS,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
MODEL_ANALYZE_LAG = Histogram(
|
||||||
|
'laborious_model_analyze_lag',
|
||||||
|
'Lag between the start and end of analyze operations',
|
||||||
|
SIENTIA_CORE_LABELS,
|
||||||
|
buckets=[0.01, 0.05, 0.1, 0.2, 0.5, 1.0, 2.0, 5.0, 10.0],
|
||||||
|
)
|
||||||
|
|
||||||
|
MODEL_ANALYZE_COUNT = Counter(
|
||||||
|
'laborious_model_analyze_count',
|
||||||
|
'Number of analyze operations',
|
||||||
|
SIENTIA_CORE_LABELS,
|
||||||
|
)
|
||||||
|
|
||||||
|
MODEL_ANALYZE_ERROR_COUNT = Counter(
|
||||||
|
'laborious_model_analyze_error_count',
|
||||||
|
'Number of errors during analyze operations',
|
||||||
|
SIENTIA_CORE_LABELS,
|
||||||
|
)
|
||||||
@@ -16,6 +16,7 @@ Capabilities:
|
|||||||
|
|
||||||
import ctypes
|
import ctypes
|
||||||
import gc
|
import gc
|
||||||
|
from io import StringIO
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
@@ -33,6 +34,7 @@ from sientia_do.observability.logger import Logger
|
|||||||
from sientia_do.observability.metrics_controller import MetricsController
|
from sientia_do.observability.metrics_controller import MetricsController
|
||||||
from sientia_do.observability.sientia_monitoring import SientiaMonitoring
|
from sientia_do.observability.sientia_monitoring import SientiaMonitoring
|
||||||
from sientia_do.temporal.constants import DATETIME_FORMAT_WITH_TZ
|
from sientia_do.temporal.constants import DATETIME_FORMAT_WITH_TZ
|
||||||
|
from sientia.ModelAnalysis import ModelAnalysis
|
||||||
|
|
||||||
from laborious import metrics
|
from laborious import metrics
|
||||||
|
|
||||||
@@ -259,6 +261,40 @@ class MLFlowRepository(SientiaMonitoring):
|
|||||||
|
|
||||||
return artifacts
|
return artifacts
|
||||||
|
|
||||||
|
|
||||||
|
async def load_artifact_dataframe(self, model_name: str, artifact_path: str,
|
||||||
|
metadata: dict[str, Any]) -> pd.DataFrame:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Load the dataframe content of an artifact from the MLflow Model Registry.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
model_name (str): The name of the model to download from the registry.
|
||||||
|
artifact_path (str): The path to the artifact to load.
|
||||||
|
metadata (dict[str, Any]): Metadata used for structured logging.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
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')
|
||||||
|
|
||||||
|
start_time = time.time()
|
||||||
|
try:
|
||||||
|
content = mlflow.artifacts.load_text(artifact_path)
|
||||||
|
except Exception as e:
|
||||||
|
await self.emit_metric(metric_object=metrics.MODEL_READ_ERROR_COUNT, tags=core_labels)
|
||||||
|
raise e
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
dataframe = pd.read_csv(StringIO(content))
|
||||||
|
|
||||||
|
self.info(f'Loaded dataframe from {model_name}:{artifact_path}', metadata)
|
||||||
|
return dataframe
|
||||||
|
|
||||||
async def load_predict_model(
|
async def load_predict_model(
|
||||||
self, model_name: str, metadata: dict[str, Any], flavor: str = 'sklearn'
|
self, model_name: str, metadata: dict[str, Any], flavor: str = 'sklearn'
|
||||||
) -> Any:
|
) -> Any:
|
||||||
@@ -1335,3 +1371,60 @@ class MLFlowRepository(SientiaMonitoring):
|
|||||||
metadata_result['mlflow_experiment_id'] = experiment_id
|
metadata_result['mlflow_experiment_id'] = experiment_id
|
||||||
|
|
||||||
return metadata_result
|
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 {}
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ class FormatAndExportPrediction:
|
|||||||
metadata = input_data['metadata']
|
metadata = input_data['metadata']
|
||||||
path_flag = input_data['path_flag']
|
path_flag = input_data['path_flag']
|
||||||
data = input_data['data']
|
data = input_data['data']
|
||||||
|
transformed_data = input_data.get('transformed_data', None)
|
||||||
prediction_confidence = input_data['prediction_confidence']
|
prediction_confidence = input_data['prediction_confidence']
|
||||||
|
|
||||||
if path_flag is None:
|
if path_flag is None:
|
||||||
@@ -87,6 +88,37 @@ class FormatAndExportPrediction:
|
|||||||
start_to_close_timeout=timedelta(seconds=60),
|
start_to_close_timeout=timedelta(seconds=60),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if transformed_data is not None:
|
||||||
|
|
||||||
|
transformed = await workflow.execute_local_activity_method(
|
||||||
|
Activities.format_transformed_data,
|
||||||
|
{
|
||||||
|
**metadata,
|
||||||
|
'data': transformed_data,
|
||||||
|
},
|
||||||
|
retry_policy=retry_policy,
|
||||||
|
start_to_close_timeout=timedelta(seconds=60),
|
||||||
|
)
|
||||||
|
|
||||||
|
write_transformed_handler = workflow.execute_activity_method(
|
||||||
|
Activities.export_data_to_postgres,
|
||||||
|
{
|
||||||
|
**metadata,
|
||||||
|
'schema': input_data['schema'],
|
||||||
|
'table_name': input_data['transform_table_name'],
|
||||||
|
'data': transformed,
|
||||||
|
'timestamp_conversion': {
|
||||||
|
'column': 'timestamp',
|
||||||
|
'format': DATETIME_FORMAT_WITH_TZ,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
retry_policy=retry_policy,
|
||||||
|
start_to_close_timeout=timedelta(seconds=60),
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
write_transformed_handler = None
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# create default prediction
|
# create default prediction
|
||||||
prediction = await workflow.execute_local_activity_method(
|
prediction = await workflow.execute_local_activity_method(
|
||||||
@@ -102,6 +134,8 @@ class FormatAndExportPrediction:
|
|||||||
start_to_close_timeout=timedelta(seconds=60),
|
start_to_close_timeout=timedelta(seconds=60),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
write_transformed_handler = None
|
||||||
|
|
||||||
# write to opc
|
# write to opc
|
||||||
prediction, opc_metrics = await workflow.execute_activity_method(
|
prediction, opc_metrics = await workflow.execute_activity_method(
|
||||||
Activities.write_opc_data,
|
Activities.write_opc_data,
|
||||||
@@ -115,7 +149,7 @@ class FormatAndExportPrediction:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# write to postgres
|
# write to postgres
|
||||||
await workflow.execute_activity_method(
|
prediction_handler = workflow.execute_activity_method(
|
||||||
Activities.export_data_to_postgres,
|
Activities.export_data_to_postgres,
|
||||||
{
|
{
|
||||||
**metadata,
|
**metadata,
|
||||||
@@ -128,6 +162,11 @@ class FormatAndExportPrediction:
|
|||||||
start_to_close_timeout=timedelta(seconds=180),
|
start_to_close_timeout=timedelta(seconds=180),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
await prediction_handler
|
||||||
|
|
||||||
|
if write_transformed_handler is not None:
|
||||||
|
await write_transformed_handler
|
||||||
|
|
||||||
await workflow.execute_activity_method(
|
await workflow.execute_activity_method(
|
||||||
Activities.write_metrics,
|
Activities.write_metrics,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ class PredictionProcess:
|
|||||||
model_id = input_data['model_id']
|
model_id = input_data['model_id']
|
||||||
model_name = input_data['model_name']
|
model_name = input_data['model_name']
|
||||||
model_config = input_data.get('model_config', {})
|
model_config = input_data.get('model_config', {})
|
||||||
|
save_transform = input_data.get('save_transform', True)
|
||||||
|
|
||||||
# Get last timestamp for incremental processing
|
# Get last timestamp for incremental processing
|
||||||
last_timestamp = await workflow.execute_local_activity_method(
|
last_timestamp = await workflow.execute_local_activity_method(
|
||||||
@@ -199,6 +200,7 @@ class PredictionProcess:
|
|||||||
'metadata': metadata,
|
'metadata': metadata,
|
||||||
'path_flag': path_flag,
|
'path_flag': path_flag,
|
||||||
'data': response_data['content'],
|
'data': response_data['content'],
|
||||||
|
'transformed_data': transformed_data if save_transform else None,
|
||||||
'prediction_confidence': confidence,
|
'prediction_confidence': confidence,
|
||||||
'timestamp': last_timestamp,
|
'timestamp': last_timestamp,
|
||||||
'model_id': model_id,
|
'model_id': model_id,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ psycopg2-binary
|
|||||||
sqlalchemy
|
sqlalchemy
|
||||||
asyncua
|
asyncua
|
||||||
redis
|
redis
|
||||||
git+ssh://git@github.com/Aignosi/sientia-dataops-library.git@1.5.2
|
git+ssh://git@github.com/Aignosi/sientia-dataops-library.git@1.5.3
|
||||||
git+ssh://git@github.com/Aignosi/sientia-mlops-library.git@0.39.0
|
git+ssh://git@github.com/Aignosi/sientia-mlops-library.git@0.39.0
|
||||||
prometheus-client
|
prometheus-client
|
||||||
botocore
|
botocore
|
||||||
|
|||||||
Reference in New Issue
Block a user