Skip to content

Commit

Permalink
Merge branch 'main' into ODSC-64644/enhance_model_artifact_handler
Browse files Browse the repository at this point in the history
  • Loading branch information
lu-ohai authored Nov 12, 2024
2 parents 8d18002 + ab684e8 commit 7c9c5e3
Show file tree
Hide file tree
Showing 31 changed files with 499 additions and 407 deletions.
10 changes: 10 additions & 0 deletions ads/aqua/common/decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,16 @@ def inner_function(
reason=error.message,
service_payload=error.args[0] if error.args else None,
exc_info=sys.exc_info(),
aqua_api_details=dict(
# __qualname__ gives information of class and name of api
aqua_api_name=func.__qualname__,
oci_api_name=getattr(
error, "operation_name", "Unknown OCI Operation"
),
service_endpoint=getattr(
error, "request_endpoint", "Unknown Request Endpoint"
)
)
)
except (
ClientError,
Expand Down
14 changes: 12 additions & 2 deletions ads/aqua/evaluation/entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@
This module contains dataclasses for aqua evaluation.
"""

from typing import Any, Dict, List, Optional

from pydantic import Field
from typing import Any, Dict, List, Optional, Union

from ads.aqua.data import AquaResourceIdentifier
from ads.aqua.config.utils.serializer import Serializable
from ads.aqua.data import AquaResourceIdentifier


class CreateAquaEvaluationDetails(Serializable):
Expand Down Expand Up @@ -87,6 +88,8 @@ class CreateAquaEvaluationDetails(Serializable):

class Config:
extra = "ignore"
protected_namespaces = ()


class AquaEvalReport(Serializable):
evaluation_id: str = ""
Expand All @@ -95,6 +98,7 @@ class AquaEvalReport(Serializable):
class Config:
extra = "ignore"


class AquaEvalParams(Serializable):
shape: str = ""
dataset_path: str = ""
Expand All @@ -103,6 +107,7 @@ class AquaEvalParams(Serializable):
class Config:
extra = "allow"


class AquaEvalMetric(Serializable):
key: str
name: str
Expand All @@ -111,6 +116,7 @@ class AquaEvalMetric(Serializable):
class Config:
extra = "ignore"


class AquaEvalMetricSummary(Serializable):
metric: str = ""
score: str = ""
Expand All @@ -119,6 +125,7 @@ class AquaEvalMetricSummary(Serializable):
class Config:
extra = "ignore"


class AquaEvalMetrics(Serializable):
id: str
report: str
Expand All @@ -128,6 +135,7 @@ class AquaEvalMetrics(Serializable):
class Config:
extra = "ignore"


class AquaEvaluationCommands(Serializable):
evaluation_id: str
evaluation_target_id: str
Expand All @@ -139,6 +147,7 @@ class AquaEvaluationCommands(Serializable):
class Config:
extra = "ignore"


class AquaEvaluationSummary(Serializable):
"""Represents a summary of Aqua evalution."""

Expand All @@ -157,6 +166,7 @@ class AquaEvaluationSummary(Serializable):
class Config:
extra = "ignore"


class AquaEvaluationDetail(AquaEvaluationSummary):
"""Represents a details of Aqua evalution."""

Expand Down
2 changes: 1 addition & 1 deletion ads/aqua/evaluation/evaluation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1305,7 +1305,7 @@ def _process(
"id": model_id,
"name": model.display_name,
"console_url": console_url,
"time_created": model.time_created,
"time_created": str(model.time_created),
"tags": tags,
"experiment": self._build_resource_identifier(
id=experiment_id,
Expand Down
2 changes: 2 additions & 0 deletions ads/aqua/extension/aqua_ws_msg_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,12 @@ def write_error(self, status_code, **kwargs):
logger.warning(reply["message"])
# telemetry may not be present if there is an error while initializing
if hasattr(self, "telemetry"):
aqua_api_details = kwargs.get("aqua_api_details", {})
self.telemetry.record_event_async(
category="aqua/error",
action=str(status_code),
value=reason,
**aqua_api_details
)
response = AquaWsError(
status=status_code,
Expand Down
2 changes: 2 additions & 0 deletions ads/aqua/extension/base_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,12 @@ def write_error(self, status_code, **kwargs):

# telemetry may not be present if there is an error while initializing
if hasattr(self, "telemetry"):
aqua_api_details = kwargs.get("aqua_api_details", {})
self.telemetry.record_event_async(
category="aqua/error",
action=str(status_code),
value=reason,
**aqua_api_details
)

self.finish(json.dumps(reply))
Expand Down
3 changes: 3 additions & 0 deletions ads/aqua/finetuning/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ class FineTuneCustomMetadata(str, metaclass=ExtendedEnumMeta):
SERVICE_MODEL_ARTIFACT_LOCATION = "artifact_location"
SERVICE_MODEL_DEPLOYMENT_CONTAINER = "deployment-container"
SERVICE_MODEL_FINE_TUNE_CONTAINER = "finetune-container"


ENV_AQUA_FINE_TUNING_CONTAINER = "AQUA_FINE_TUNING_CONTAINER"
15 changes: 13 additions & 2 deletions ads/aqua/finetuning/finetuning.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@
UNKNOWN_DICT,
)
from ads.aqua.data import AquaResourceIdentifier
from ads.aqua.finetuning.constants import *
from ads.aqua.finetuning.constants import (
ENV_AQUA_FINE_TUNING_CONTAINER,
FineTuneCustomMetadata,
)
from ads.aqua.finetuning.entities import *
from ads.common.auth import default_signer
from ads.common.object_storage_details import ObjectStorageDetails
Expand Down Expand Up @@ -310,6 +313,15 @@ def create(
except Exception:
pass

if not is_custom_container and ENV_AQUA_FINE_TUNING_CONTAINER in os.environ:
ft_container = os.environ[ENV_AQUA_FINE_TUNING_CONTAINER]
logger.info(
"Using container set by environment variable %s=%s",
ENV_AQUA_FINE_TUNING_CONTAINER,
ft_container,
)
is_custom_container = True

ft_parameters.batch_size = ft_parameters.batch_size or (
ft_config.get("shape", UNKNOWN_DICT)
.get(create_fine_tuning_details.shape_name, UNKNOWN_DICT)
Expand Down Expand Up @@ -559,7 +571,6 @@ def get_finetuning_config(self, model_id: str) -> Dict:
Dict:
A dict of allowed finetuning configs.
"""

config = self.get_config(model_id, AQUA_MODEL_FINETUNING_CONFIG)
if not config:
logger.debug(
Expand Down
11 changes: 6 additions & 5 deletions ads/opctl/operator/lowcode/anomaly/model/anomaly_merlion.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/

import importlib
import logging

import numpy as np
import pandas as pd
import report_creator as rc
from merlion.post_process.threshold import AggregateAlarms
from merlion.utils import TimeSeries

Expand All @@ -21,6 +23,8 @@
from .anomaly_dataset import AnomalyOutput
from .base_model import AnomalyOperatorBaseModel

logging.getLogger("report_creator").setLevel(logging.WARNING)


class AnomalyMerlionOperatorModel(AnomalyOperatorBaseModel):
"""Class representing Merlion Anomaly Detection operator model."""
Expand Down Expand Up @@ -84,7 +88,7 @@ def _build_model(self) -> AnomalyOutput:
for target, df in self.datasets.full_data_dict.items():
data = df.set_index(date_column)
data = TimeSeries.from_pd(data)
for model_name, (model_config, model) in model_config_map.items():
for _, (model_config, model) in model_config_map.items():
if self.spec.model == SupportedModels.BOCPD:
model_config = model_config(**self.spec.model_kwargs)
else:
Expand Down Expand Up @@ -115,7 +119,7 @@ def _build_model(self) -> AnomalyOutput:
y_pred = (y_pred.to_pd().reset_index()["anom_score"] > 0).astype(
int
)
except Exception as e:
except Exception:
y_pred = (
scores["anom_score"]
> np.percentile(
Expand All @@ -135,15 +139,12 @@ def _build_model(self) -> AnomalyOutput:
OutputColumns.SCORE_COL: scores["anom_score"],
}
).reset_index(drop=True)
# model_objects[model_name].append(model)

anomaly_output.add_output(target, anomaly, score)
return anomaly_output

def _generate_report(self):
"""Genreates a report for the model."""
import report_creator as rc

other_sections = [
rc.Heading("Selected Models Overview", level=2),
rc.Text(
Expand Down
20 changes: 12 additions & 8 deletions ads/opctl/operator/lowcode/anomaly/model/automlx.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*--

# Copyright (c) 2023, 2024 Oracle and/or its affiliates.
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/

import logging

import pandas as pd
import report_creator as rc

from ads.common.decorator.runtime_dependency import runtime_dependency
from .anomaly_dataset import AnomalyOutput
from ads.opctl import logger
from ads.opctl.operator.lowcode.anomaly.const import OutputColumns

from .anomaly_dataset import AnomalyOutput
from .base_model import AnomalyOperatorBaseModel
from ads.opctl.operator.lowcode.anomaly.const import OutputColumns

logging.getLogger("report_creator").setLevel(logging.WARNING)


class AutoMLXOperatorModel(AnomalyOperatorBaseModel):
Expand All @@ -25,16 +30,17 @@ class AutoMLXOperatorModel(AnomalyOperatorBaseModel):
),
)
def _build_model(self) -> pd.DataFrame:
from automlx import init
import logging

import automlx

try:
init(
automlx.init(
engine="ray",
engine_opts={"ray_setup": {"_temp_dir": "/tmp/ray-temp"}},
loglevel=logging.CRITICAL,
)
except Exception as e:
except Exception:
logger.info("Ray already initialized")
date_column = self.spec.datetime_column.name
anomaly_output = AnomalyOutput(date_column=date_column)
Expand Down Expand Up @@ -73,8 +79,6 @@ def _build_model(self) -> pd.DataFrame:
return anomaly_output

def _generate_report(self):
import report_creator as rc

"""The method that needs to be implemented on the particular model level."""
other_sections = [
rc.Heading("Selected Models Overview", level=2),
Expand Down
9 changes: 6 additions & 3 deletions ads/opctl/operator/lowcode/anomaly/model/autots.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*--

# Copyright (c) 2023, 2024 Oracle and/or its affiliates.
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/

import logging

import report_creator as rc

from ads.common.decorator.runtime_dependency import runtime_dependency
from ads.opctl import logger
from ads.opctl.operator.lowcode.anomaly.const import OutputColumns
Expand All @@ -12,6 +15,8 @@
from .anomaly_dataset import AnomalyOutput
from .base_model import AnomalyOperatorBaseModel

logging.getLogger("report_creator").setLevel(logging.WARNING)


class AutoTSOperatorModel(AnomalyOperatorBaseModel):
"""Class representing AutoTS Anomaly Detection operator model."""
Expand Down Expand Up @@ -91,8 +96,6 @@ def _build_model(self) -> AnomalyOutput:
return anomaly_output

def _generate_report(self):
import report_creator as rc

"""The method that needs to be implemented on the particular model level."""
other_sections = [
rc.Heading("Selected Models Overview", level=2),
Expand Down
26 changes: 19 additions & 7 deletions ads/opctl/operator/lowcode/anomaly/model/base_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# Copyright (c) 2023, 2024 Oracle and/or its affiliates.
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/

import logging
import os
import tempfile
import time
Expand All @@ -12,6 +13,7 @@
import fsspec
import numpy as np
import pandas as pd
import report_creator as rc
from sklearn import linear_model

from ads.common.object_storage_details import ObjectStorageDetails
Expand All @@ -33,6 +35,8 @@
from ..operator_config import AnomalyOperatorConfig, AnomalyOperatorSpec
from .anomaly_dataset import AnomalyDatasets, AnomalyOutput, TestData

logging.getLogger("report_creator").setLevel(logging.WARNING)


class AnomalyOperatorBaseModel(ABC):
"""The base class for the anomaly detection operator models."""
Expand All @@ -59,8 +63,8 @@ def __init__(self, config: AnomalyOperatorConfig, datasets: AnomalyDatasets):
def generate_report(self):
"""Generates the report."""
import matplotlib.pyplot as plt
plt.rcParams.update({'figure.max_open_warning': 0})
import report_creator as rc

plt.rcParams.update({"figure.max_open_warning": 0})

start_time = time.time()
# fallback using sklearn oneclasssvm when the sub model _build_model fails
Expand All @@ -84,7 +88,13 @@ def generate_report(self):
anomaly_output, test_data, elapsed_time
)
table_blocks = [
rc.DataTable(df.head(SUBSAMPLE_THRESHOLD) if self.spec.subsample_report_data and len(df) > SUBSAMPLE_THRESHOLD else df, label=col, index=True)
rc.DataTable(
df.head(SUBSAMPLE_THRESHOLD)
if self.spec.subsample_report_data and len(df) > SUBSAMPLE_THRESHOLD
else df,
label=col,
index=True,
)
for col, df in self.datasets.full_data_dict.items()
]
data_table = rc.Select(blocks=table_blocks)
Expand Down Expand Up @@ -144,7 +154,9 @@ def generate_report(self):
else:
figure_blocks = None

blocks.append(rc.Group(*figure_blocks, label=target)) if figure_blocks else None
blocks.append(
rc.Group(*figure_blocks, label=target)
) if figure_blocks else None
plots = rc.Select(blocks)

report_sections = []
Expand All @@ -154,7 +166,9 @@ def generate_report(self):
yaml_appendix = rc.Yaml(self.config.to_dict())
summary = rc.Block(
rc.Group(
rc.Text(f"You selected the **`{self.spec.model}`** model.\n{model_description.text}\n"),
rc.Text(
f"You selected the **`{self.spec.model}`** model.\n{model_description.text}\n"
),
rc.Text(
"Based on your dataset, you could have also selected "
f"any of the models: `{'`, `'.join(SupportedModels.keys() if self.spec.datetime_column else NonTimeADSupportedModels.keys())}`."
Expand Down Expand Up @@ -285,8 +299,6 @@ def _save_report(
test_metrics: pd.DataFrame,
):
"""Saves resulting reports to the given folder."""
import report_creator as rc

unique_output_dir = self.spec.output_directory.url

if ObjectStorageDetails.is_oci_path(unique_output_dir):
Expand Down
Loading

0 comments on commit 7c9c5e3

Please sign in to comment.