Skip to content

Commit d38669f

Browse files
authored
Merge branch 'main' into spavlusieva-patch-1
2 parents 3bce154 + d687593 commit d38669f

File tree

9 files changed

+236
-20
lines changed

9 files changed

+236
-20
lines changed

ads/cli.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
import fire
1111
from ads.common import logger
12-
from ads.aqua.cli import AquaCommand
1312

1413
try:
1514
import click
@@ -73,6 +72,8 @@ def _SeparateFlagArgs(args):
7372

7473
def cli():
7574
if len(sys.argv) > 1 and sys.argv[1] == "aqua":
75+
from ads.aqua.cli import AquaCommand
76+
7677
fire.Fire(AquaCommand, command=sys.argv[2:], name="ads aqua")
7778
else:
7879
click_cli()

ads/opctl/operator/lowcode/anomaly/model/anomaly_dataset.py

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ def __init__(self, spec: AnomalyOperatorSpec):
5555
self.X_valid_dict = self.valid_data.X_valid_dict
5656
self.y_valid_dict = self.valid_data.y_valid_dict
5757

58+
# Returns raw data based on the series_id i.e; the merged target_category_column value
59+
def get_raw_data_by_cat(self, category):
60+
return self._data.get_raw_data_by_cat(category)
61+
5862

5963
class AnomalyOutput:
6064
def __init__(self, date_column):
@@ -93,38 +97,28 @@ def get_outliers_by_cat(self, category: str, data: pd.DataFrame):
9397
outliers = pd.merge(outliers, scores, on=self.date_column, how="inner")
9498
return outliers
9599

96-
def get_inliers(self, data):
100+
def get_inliers(self, datasets):
97101
inliers = pd.DataFrame()
98102

99103
for category in self.list_categories():
100104
inliers = pd.concat(
101105
[
102106
inliers,
103-
self.get_inliers_by_cat(
104-
category,
105-
data[data[OutputColumns.Series] == category]
106-
.reset_index(drop=True)
107-
.drop(OutputColumns.Series, axis=1),
108-
),
107+
self.get_inliers_by_cat(category, datasets.get_raw_data_by_cat(category)),
109108
],
110109
axis=0,
111110
ignore_index=True,
112111
)
113112
return inliers
114113

115-
def get_outliers(self, data):
114+
def get_outliers(self, datasets):
116115
outliers = pd.DataFrame()
117116

118117
for category in self.list_categories():
119118
outliers = pd.concat(
120119
[
121120
outliers,
122-
self.get_outliers_by_cat(
123-
category,
124-
data[data[OutputColumns.Series] == category]
125-
.reset_index(drop=True)
126-
.drop(OutputColumns.Series, axis=1),
127-
),
121+
self.get_outliers_by_cat(category, datasets.get_raw_data_by_cat(category)),
128122
],
129123
axis=0,
130124
ignore_index=True,

ads/opctl/operator/lowcode/anomaly/model/base_model.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,15 +272,15 @@ def _save_report(
272272
f2.write(f1.read())
273273

274274
if self.spec.generate_inliers:
275-
inliers = anomaly_output.get_inliers(self.datasets.data)
275+
inliers = anomaly_output.get_inliers(self.datasets)
276276
write_data(
277277
data=inliers,
278278
filename=os.path.join(unique_output_dir, self.spec.inliers_filename),
279279
format="csv",
280280
storage_options=storage_options,
281281
)
282282

283-
outliers = anomaly_output.get_outliers(self.datasets.data)
283+
outliers = anomaly_output.get_outliers(self.datasets)
284284
write_data(
285285
data=outliers,
286286
filename=os.path.join(unique_output_dir, self.spec.outliers_filename),

ads/opctl/operator/lowcode/common/data.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
DataMismatchError,
1717
)
1818
from abc import ABC
19+
import pandas as pd
1920

2021

2122
class AbstractData(ABC):
@@ -26,6 +27,19 @@ def __init__(self, spec: dict, name="input_data"):
2627
self.name = name
2728
self.load_transform_ingest_data(spec)
2829

30+
def get_raw_data_by_cat(self, category):
31+
mapping = self._data_transformer.get_target_category_columns_map()
32+
# For given category, mapping gives the target_category_columns and it's values.
33+
# condition filters raw_data based on the values of target_category_columns for the given category
34+
condition = pd.Series(True, index=self.raw_data.index)
35+
if category in mapping:
36+
for col, val in mapping[category].items():
37+
condition &= (self.raw_data[col] == val)
38+
data_by_cat = self.raw_data[condition].reset_index(drop=True)
39+
data_by_cat = self._data_transformer._format_datetime_col(data_by_cat)
40+
return data_by_cat
41+
42+
2943
def get_dict_by_series(self):
3044
if not self._data_dict:
3145
for s_id in self.list_series_ids():
@@ -73,8 +87,8 @@ def _transform_data(self, spec, raw_data, **kwargs):
7387
return data
7488

7589
def load_transform_ingest_data(self, spec):
76-
raw_data = self._load_data(getattr(spec, self.name))
77-
self.data = self._transform_data(spec, raw_data)
90+
self.raw_data = self._load_data(getattr(spec, self.name))
91+
self.data = self._transform_data(spec, self.raw_data)
7892
self._ingest_data(spec)
7993

8094
def _ingest_data(self, spec):

ads/opctl/operator/lowcode/common/transformations.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,19 @@ def _remove_trailing_whitespace(self, df):
8484
return df.apply(lambda x: x.str.strip() if x.dtype == "object" else x)
8585

8686
def _set_series_id_column(self, df):
87+
self._target_category_columns_map = dict()
8788
if not self.target_category_columns:
8889
df[DataColumns.Series] = "Series 1"
8990
self.has_artificial_series = True
9091
else:
9192
df[DataColumns.Series] = merge_category_columns(
9293
df, self.target_category_columns
9394
)
95+
merged_values = df[DataColumns.Series].unique().tolist()
96+
if self.target_category_columns:
97+
for value in merged_values:
98+
self._target_category_columns_map[value] = df[df[DataColumns.Series] == value][self.target_category_columns].drop_duplicates().iloc[0].to_dict()
99+
94100
df = df.drop(self.target_category_columns, axis=1)
95101
return df
96102

@@ -195,3 +201,25 @@ def _check_historical_dataset(self, df):
195201
raise DataMismatchError(
196202
f"Expected {self.name} to have columns: {expected_names}, but instead found column names: {df.columns}. Is the {self.name} path correct?"
197203
)
204+
205+
"""
206+
Map between merged target category column values and target category column and its value
207+
If target category columns are PPG_Code, Class, Num
208+
Merged target category column values are Product Category 1__A__1, Product Category 2__A__2
209+
Then target_category_columns_map would be
210+
{
211+
"Product Category 1__A__1": {
212+
"PPG_Code": "Product Category 1",
213+
"Class": "A",
214+
"Num": 1
215+
},
216+
"Product Category 2__A__2": {
217+
"PPG_Code": "Product Category 2",
218+
"Class": "A",
219+
"Num": 2
220+
},
221+
222+
}
223+
"""
224+
def get_target_category_columns_map(self):
225+
return self._target_category_columns_map

docs/source/release_notes.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22
Release Notes
33
=============
44

5+
2.11.7
6+
------
7+
Release date: April 8, 2024
8+
9+
* Fixed bugs and introduced enhancements following our recent release, which included internal adjustments for future features and updates for the Jupyter Lab 3 upgrade.
10+
11+
512
2.11.6
613
------
714
Release date: April 3, 2024

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ build-backend = "flit_core.buildapi"
2121

2222
# Required
2323
name = "oracle_ads" # the install (PyPI) name; name for local build in [tool.flit.module] section below
24-
version = "2.11.6"
24+
version = "2.11.7"
2525

2626
# Optional
2727
description = "Oracle Accelerated Data Science SDK"
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*--
3+
4+
# Copyright (c) 2024 Oracle and/or its affiliates.
5+
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
6+
7+
from unittest import TestCase
8+
from unittest.mock import MagicMock
9+
from mock import patch
10+
11+
from notebook.base.handlers import IPythonHandler
12+
from ads.aqua.extension.finetune_handler import AquaFineTuneHandler
13+
from ads.aqua.finetune import AquaFineTuningApp, CreateFineTuningDetails
14+
15+
16+
class TestDataset:
17+
mock_valid_input = dict(
18+
ft_source_id="ocid1.datasciencemodel.oc1.iad.<OCID>",
19+
ft_name="test_ft_name",
20+
dataset_path="oci://ds_bucket@namespace/prefix/dataset.jsonl",
21+
report_path="oci://report_bucket@namespace/prefix/",
22+
ft_parameters={
23+
"epochs":1,
24+
"learning_rate":0.02
25+
},
26+
shape_name="VM.GPU.A10.1",
27+
replica=1,
28+
validation_set_size=0.2,
29+
block_storage_size=1,
30+
experiment_name="test_experiment_name",
31+
)
32+
33+
mock_finetuning_config = {
34+
"shape": {
35+
"VM.GPU.A10.1": {
36+
"batch_size": 1,
37+
"replica": 1
38+
},
39+
}
40+
}
41+
42+
43+
class FineTuningHandlerTestCase(TestCase):
44+
45+
@patch.object(IPythonHandler, "__init__")
46+
def setUp(self, ipython_init_mock) -> None:
47+
ipython_init_mock.return_value = None
48+
self.test_instance = AquaFineTuneHandler(MagicMock(), MagicMock())
49+
self.test_instance.request = MagicMock()
50+
self.test_instance.finish = MagicMock()
51+
52+
@patch.object(AquaFineTuneHandler, "get_finetuning_config")
53+
@patch("ads.aqua.extension.finetune_handler.urlparse")
54+
def test_get(self, mock_urlparse, mock_get_finetuning_config):
55+
request_path = MagicMock(path="aqua/finetuning/config")
56+
mock_urlparse.return_value = request_path
57+
58+
mock_get_finetuning_config.return_value = TestDataset.mock_finetuning_config
59+
60+
fineruning_config = self.test_instance.get(id="test_model_id")
61+
mock_urlparse.assert_called()
62+
mock_get_finetuning_config.assert_called_with("test_model_id")
63+
assert fineruning_config == TestDataset.mock_finetuning_config
64+
65+
@patch.object(AquaFineTuningApp, "create")
66+
def test_post(self, mock_create):
67+
self.test_instance.get_json_body = MagicMock(
68+
return_value = TestDataset.mock_valid_input
69+
)
70+
self.test_instance.post()
71+
72+
self.test_instance.finish.assert_called_with(
73+
mock_create.return_value
74+
)
75+
mock_create.assert_called_with(
76+
CreateFineTuningDetails(**TestDataset.mock_valid_input)
77+
)
78+
79+
@patch.object(AquaFineTuningApp, "get_finetuning_config")
80+
def test_get_finetuning_config(self, mock_get_finetuning_config):
81+
mock_get_finetuning_config.return_value = TestDataset.mock_finetuning_config
82+
83+
self.test_instance.get_finetuning_config(model_id="test_model_id")
84+
85+
self.test_instance.finish.assert_called_with(
86+
TestDataset.mock_finetuning_config
87+
)
88+
mock_get_finetuning_config.assert_called_with(
89+
model_id="test_model_id"
90+
)
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*--
3+
4+
# Copyright (c) 2024 Oracle and/or its affiliates.
5+
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
6+
7+
from unittest import TestCase
8+
from unittest.mock import MagicMock
9+
10+
from mock import patch
11+
from notebook.base.handlers import IPythonHandler
12+
13+
from ads.aqua.extension.model_handler import AquaModelHandler, AquaModelLicenseHandler
14+
from ads.aqua.model import AquaModelApp
15+
16+
17+
class ModelHandlerTestCase(TestCase):
18+
19+
@patch.object(IPythonHandler, "__init__")
20+
def setUp(self, ipython_init_mock) -> None:
21+
ipython_init_mock.return_value = None
22+
self.model_handler = AquaModelHandler(MagicMock(), MagicMock())
23+
self.model_handler.request = MagicMock()
24+
self.model_handler.finish = MagicMock()
25+
26+
@patch.object(AquaModelHandler, "list")
27+
def test_get_no_id(self, mock_list):
28+
self.model_handler.get()
29+
mock_list.assert_called()
30+
31+
@patch.object(AquaModelHandler, "read")
32+
def test_get_with_id(self, mock_read):
33+
self.model_handler.get(model_id="test_model_id")
34+
mock_read.assert_called_with("test_model_id")
35+
36+
@patch.object(AquaModelApp, "get")
37+
def test_read(self, mock_get):
38+
self.model_handler.read(model_id="test_model_id")
39+
self.model_handler.finish.assert_called_with(
40+
mock_get.return_value
41+
)
42+
mock_get.assert_called_with("test_model_id")
43+
44+
@patch.object(AquaModelApp, "clear_model_list_cache")
45+
@patch("ads.aqua.extension.model_handler.urlparse")
46+
def test_delete(self, mock_urlparse, mock_clear_model_list_cache):
47+
request_path = MagicMock(path="aqua/model/cache")
48+
mock_urlparse.return_value = request_path
49+
50+
self.model_handler.delete()
51+
self.model_handler.finish.assert_called_with(
52+
mock_clear_model_list_cache.return_value
53+
)
54+
55+
mock_urlparse.assert_called()
56+
mock_clear_model_list_cache.assert_called()
57+
58+
@patch.object(AquaModelApp, "list")
59+
def test_list(self, mock_list):
60+
self.model_handler.list()
61+
62+
self.model_handler.finish.assert_called_with(
63+
mock_list.return_value
64+
)
65+
mock_list.assert_called_with(None, None)
66+
67+
class ModelLicenseHandlerTestCase(TestCase):
68+
69+
@patch.object(IPythonHandler, "__init__")
70+
def setUp(self, ipython_init_mock) -> None:
71+
ipython_init_mock.return_value = None
72+
self.model_license_handler = AquaModelLicenseHandler(MagicMock(), MagicMock())
73+
self.model_license_handler.finish = MagicMock()
74+
75+
@patch.object(AquaModelApp, "load_license")
76+
def test_get(self, mock_load_license):
77+
self.model_license_handler.get(model_id="test_model_id")
78+
79+
self.model_license_handler.finish.assert_called_with(
80+
mock_load_license.return_value
81+
)
82+
mock_load_license.assert_called_with("test_model_id")

0 commit comments

Comments
 (0)