Skip to content

Commit 0a6fd95

Browse files
authored
Feature/forecasting benchmarks (#336)
2 parents 5a1d78a + a513d7b commit 0a6fd95

File tree

3 files changed

+88
-10
lines changed

3 files changed

+88
-10
lines changed

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

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -291,13 +291,15 @@ def _test_evaluate_metrics(
291291
)
292292

293293
for idx, col in enumerate(target_columns):
294-
y_true = np.asarray(data[col])
295-
y_pred = np.asarray(outputs[idx][target_col][-len(y_true) :])
294+
# Only columns present in test file will be used to generate holdout error
295+
if col in data:
296+
y_true = np.asarray(data[col])
297+
y_pred = np.asarray(outputs[idx][target_col][-len(y_true) :])
296298

297-
metrics_df = utils._build_metrics_df(
298-
y_true=y_true, y_pred=y_pred, column_name=col
299-
)
300-
total_metrics = pd.concat([total_metrics, metrics_df], axis=1)
299+
metrics_df = utils._build_metrics_df(
300+
y_true=y_true, y_pred=y_pred, column_name=col
301+
)
302+
total_metrics = pd.concat([total_metrics, metrics_df], axis=1)
301303

302304
summary_metrics = pd.DataFrame(
303305
{
@@ -338,7 +340,8 @@ def _test_evaluate_metrics(
338340

339341
"""Calculates Mean sMAPE, Median sMAPE, Mean MAPE, Median MAPE, Mean wMAPE, Median wMAPE values for each horizon
340342
if horizon <= 10."""
341-
if len(data["ds"]) <= 10:
343+
target_columns_in_output = set(target_columns).intersection(data.columns)
344+
if len(data["ds"]) <= 10 and len(outputs) == len(target_columns_in_output):
342345
metrics_per_horizon = utils._build_metrics_per_horizon(
343346
data=data,
344347
outputs=outputs,
@@ -404,11 +407,11 @@ def _save_report(
404407

405408
# metrics csv report
406409
utils._write_data(
407-
data=metrics_df,
410+
data=metrics_df.rename_axis('metrics').reset_index(),
408411
filename=os.path.join(output_dir, self.spec.metrics_filename),
409412
format="csv",
410413
storage_options=default_signer(),
411-
index=True,
414+
index=False,
412415
)
413416

414417
logger.warn(

ads/opctl/operator/lowcode/forecast/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ def plot_forecast_plotly(idx, col):
374374
),
375375
]
376376
)
377-
if test_data is not None:
377+
if test_data is not None and col in test_data:
378378
fig.add_trace(
379379
go.Scatter(
380380
x=test_data["ds"],
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8; -*-
3+
import copy
4+
5+
# Copyright (c) 2023 Oracle and/or its affiliates.
6+
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
7+
8+
from ads.opctl.operator.lowcode.forecast.operator_config import *
9+
from ads.opctl.operator.lowcode.forecast.model.factory import ForecastOperatorModelFactory
10+
import pandas as pd
11+
from ads.opctl import logger
12+
import os
13+
14+
if __name__ == '__main__':
15+
"""Benchmarks for datasets."""
16+
17+
try:
18+
data_dir = os.environ["OCI__FORECASTING_DATA_DIR"]
19+
except:
20+
raise ValueError("Please set the environment variable `OCI__FORECASTING_DATA_DIR` to the location of the forecasting datasets")
21+
22+
smape = SupportedMetrics.SMAPE
23+
mape = SupportedMetrics.MAPE
24+
rmse = SupportedMetrics.RMSE
25+
26+
prophet = 'prophet'
27+
arima = 'arima'
28+
automlx = 'automlx'
29+
neuralprophet = 'neuralprophet'
30+
31+
benchmark_metrics = [smape, mape, rmse]
32+
33+
# Expected values
34+
cust1_numbers = {
35+
prophet: {smape: 30, mape: 10, rmse: 1780},
36+
arima: {smape: 20, mape: 2, rmse: 1500},
37+
automlx: {smape: 30, mape: 7, rmse: 1750},
38+
# neuralprophet: {smape: 29, mape: 9.5, rmse: 1760},
39+
}
40+
41+
cust2_numbers = {
42+
prophet: {smape: 18, mape: 0.5, rmse: 75},
43+
arima: {smape: 21, mape: 0.45, rmse: 75},
44+
automlx: {smape: 15, mape: 0.3, rmse: 74},
45+
# neuralprophet: {smape: 30, mape: 10, rmse: 1780},
46+
}
47+
48+
datasets = {
49+
'cust1': cust1_numbers,
50+
'cust2': cust2_numbers,
51+
}
52+
metrics = [SupportedMetrics.SMAPE, SupportedMetrics.MAPE, SupportedMetrics.RMSE]
53+
54+
for dataset in datasets:
55+
for model in datasets[dataset]:
56+
operator_config: ForecastOperatorConfig = ForecastOperatorConfig.from_yaml(
57+
uri=os.path.join(data_dir, dataset, 'forecast.yaml')
58+
)
59+
operator_config.spec.model = model
60+
operator_config.spec.output_directory = OutputDirectory(
61+
url=os.path.join(operator_config.spec.output_directory.url, model)
62+
)
63+
64+
# Training and generating the model outputs
65+
ForecastOperatorModelFactory.get_model(operator_config).generate_report()
66+
67+
# Reading holdout erros.
68+
metrics_df = pd.read_csv(os.path.join(data_dir, dataset, 'output', model, 'metrics.csv')).set_index(
69+
'metrics')
70+
metrics_dict = metrics_df.mean(axis=1).to_dict()
71+
logger.info("{} | {} | {}".format(dataset, model, metrics_dict))
72+
# Actual values should be less than actual values
73+
for metric in benchmark_metrics:
74+
assert metrics_dict[metric] <= datasets[dataset][model][metric]
75+
logger.info("Test completed for {} and {} model".format(dataset, model))

0 commit comments

Comments
 (0)