Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MASE metric #1166

Merged
merged 14 commits into from
Sep 14, 2023
7 changes: 4 additions & 3 deletions examples/simple/time_series_forecasting/api_forecasting.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def run_ts_forecasting_example(dataset='australia', horizon: int = 30, timeout:
TsForecastingParams(forecast_length=horizon)).task_params,
timeout=timeout,
n_jobs=-1,
metric='mase',
with_tuning=with_tuning,
cv_folds=2, preset='fast_train')

Expand All @@ -50,7 +51,7 @@ def run_ts_forecasting_example(dataset='australia', horizon: int = 30, timeout:
# use model to obtain two-step in-sample forecast
in_sample_forecast = model.predict(test_data, validation_blocks=validation_blocks)
print('Metrics for two-step in-sample forecast: ',
model.get_metrics(metric_names=['rmse', 'mae', 'mape'],
model.get_metrics(metric_names=['mase', 'mae', 'mape'],
validation_blocks=validation_blocks))

# plot forecasting result
Expand All @@ -68,7 +69,7 @@ def run_ts_forecasting_example(dataset='australia', horizon: int = 30, timeout:
model.plot_prediction()

# use model to obtain two-step out-of-sample forecast
out_of_sample_forecast = model.forecast(test_data, horizon=20)
out_of_sample_forecast = model.forecast(test_data, horizon=horizon * 2)
# we can not calculate metrics because we do not have enough future values
if visualization:
model.plot_prediction()
Expand All @@ -77,4 +78,4 @@ def run_ts_forecasting_example(dataset='australia', horizon: int = 30, timeout:


if __name__ == '__main__':
run_ts_forecasting_example(dataset='salaries', horizon=10, timeout=10., visualization=True)
run_ts_forecasting_example(dataset='beer', horizon=14, timeout=2., visualization=True)
3 changes: 2 additions & 1 deletion fedot/api/api_utils/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from fedot.core.composer.metrics import Metric
from fedot.core.repository.quality_metrics_repository import (ClassificationMetricsEnum, ClusteringMetricsEnum,
ComplexityMetricsEnum, RegressionMetricsEnum, MetricType,
MetricsEnum)
MetricsEnum, TimeSeriesForecastingMetricsEnum)
from fedot.core.repository.tasks import Task
from fedot.utilities.define_metric_by_task import MetricByTask

Expand All @@ -25,6 +25,7 @@ class ApiMetrics:
'mse': RegressionMetricsEnum.MSE,
'msle': RegressionMetricsEnum.MSLE,
'mape': RegressionMetricsEnum.MAPE,
'mase': TimeSeriesForecastingMetricsEnum.MASE,
'smape': RegressionMetricsEnum.SMAPE,
'r2': RegressionMetricsEnum.R2,
'rmse': RegressionMetricsEnum.RMSE,
Expand Down
10 changes: 10 additions & 0 deletions fedot/core/composer/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from sklearn.metrics import (accuracy_score, auc, f1_score, log_loss, mean_absolute_error,
mean_absolute_percentage_error, mean_squared_error, mean_squared_log_error,
precision_score, r2_score, roc_auc_score, roc_curve, silhouette_score)
from sktime.performance_metrics.forecasting import mean_absolute_scaled_error

from fedot.core.data.data import InputData, OutputData
from fedot.core.pipelines.pipeline import Pipeline
Expand Down Expand Up @@ -232,6 +233,15 @@ def metric(reference: InputData, predicted: OutputData) -> float:
return mean_absolute_error(y_true=reference.target, y_pred=predicted.predict)


class MASE(QualityMetric):
default_value = sys.maxsize

@staticmethod
def metric(reference: InputData, predicted: OutputData) -> float:
history_series = reference.features
return mean_absolute_scaled_error(y_true=reference.target, y_pred=predicted.predict, y_train=history_series)


class R2(QualityMetric):
default_value = 0

Expand Down
17 changes: 16 additions & 1 deletion fedot/core/repository/quality_metrics_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from fedot.core.composer.metrics import (ComputationTime, Accuracy, F1, Logloss, MAE,
MAPE, SMAPE, MSE, MSLE, Metric, NodeNum, Precision, R2,
RMSE, ROCAUC, Silhouette, StructuralComplexity)
RMSE, ROCAUC, Silhouette, StructuralComplexity, MASE)


class MetricsEnum(Enum):
Expand Down Expand Up @@ -53,6 +53,18 @@ class RegressionMetricsEnum(QualityMetricsEnum):
RMSE_penalty = 'rmse_pen'


class TimeSeriesForecastingMetricsEnum(QualityMetricsEnum):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А нельзя его унаследовать от RegressionMetricsEnum?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Нет. Он требует тренировочную выборку, тк на ней считает ошибку наивного предсказания

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А насколько критично использовать тренировочную выборку? Как будто бы средняя ошибка наивного предсказания не должна кардинально меняться при переходе от тренировочной к тестовой выборке.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Нет. Он требует тренировочную выборку, тк на ней считает ошибку наивного предсказания

Но вроде это enum, он ничего не требует...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Нет. Он требует тренировочную выборку, тк на ней считает ошибку наивного предсказания

Но вроде это enum, он ничего не требует...

Имею ввиду, что метрика для расчета требует значения ряда на тренировочной выборке, поэтому для регрессии метрика не подходит. Получается, что метрика находится в enum'e регрессии, но вызвать для регрессии мы ее не можем (федот либо упадет, либо переварит, в случае одномерного data.features, но какого-то смысла это иметь не будет)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А насколько критично использовать тренировочную выборку? Как будто бы средняя ошибка наивного предсказания не должна кардинально меняться при переходе от тренировочной к тестовой выборке.

Как я понял, на большом промежутке (тренировочной выборки) мы получим больше информации об изменчивости ряда. Еще одна причина - при горизонте=1, если новое значение равно предыдущему - метрика будет не определена (аналогично, если все новые значения просто равны последнему известному)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Получается, что метрика находится в enum'e регрессии, но вызвать для регрессии мы ее не можем

Это так. Я предлагаю унаследовать TimeSeriesForecastingMetricsEnum от RegressionMetricsEnum, добавив туда одну метрику. И не дублируя остальных универсальных.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Получается, что метрика находится в enum'e регрессии, но вызвать для регрессии мы ее не можем

Это так. Я предлагаю унаследовать TimeSeriesForecastingMetricsEnum от RegressionMetricsEnum, добавив туда одну метрику. И не дублируя остальных универсальных.

https://docs.python.org/3/howto/enum.html#restricted-enum-subclassing - нельзя так, к сожалению

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay

MASE = 'mase'
RMSE = 'rmse'
MSE = 'mse'
MSLE = 'neg_mean_squared_log_error'
MAPE = 'mape'
SMAPE = 'smape'
MAE = 'mae'
R2 = 'r2'
RMSE_penalty = 'rmse_pen'


class MetricsRepository:
_metrics_implementations = {
# classification
Expand All @@ -73,6 +85,9 @@ class MetricsRepository:
RegressionMetricsEnum.RMSE_penalty: RMSE.get_value_with_penalty,
RegressionMetricsEnum.R2: R2.get_value,

# ts forecasting
TimeSeriesForecastingMetricsEnum.MASE: MASE.get_value,

# clustering
ClusteringMetricsEnum.silhouette: Silhouette.get_value,

Expand Down
6 changes: 3 additions & 3 deletions fedot/utilities/define_metric_by_task.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
from typing import List

from fedot.core.data.data import InputData, OutputData
from fedot.core.repository.tasks import TaskTypesEnum
from fedot.core.repository.quality_metrics_repository import (
MetricsEnum,
RegressionMetricsEnum,
ClassificationMetricsEnum,
ClusteringMetricsEnum,
MetricsRepository
MetricsRepository, TimeSeriesForecastingMetricsEnum
)
from fedot.core.repository.tasks import TaskTypesEnum


class MetricByTask:
__metric_by_task = {TaskTypesEnum.regression: RegressionMetricsEnum.RMSE,
TaskTypesEnum.classification: ClassificationMetricsEnum.ROCAUC_penalty,
TaskTypesEnum.clustering: ClusteringMetricsEnum.silhouette,
TaskTypesEnum.ts_forecasting: RegressionMetricsEnum.RMSE,
TaskTypesEnum.ts_forecasting: TimeSeriesForecastingMetricsEnum.RMSE,
}

@staticmethod
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ statsmodels>=0.12.0
ete3>=3.1.0
networkx>=2.4, !=2.7.*, !=2.8.1, !=2.8.2, !=2.8.3
scikit_learn>=1.0.0; python_version >= '3.8'
sktime
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Версия?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nicl-nno А имеет смысл реализовать расчет метрики с помощью numpy? Чтобы не тянуть стороннюю библиотеку из-за однострочной формулы.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Да наверное где-то ещё пригодится для работы с временными рядами. Если поймем что нет - тогда уж упростим.


# Analysis and optimizations
hyperopt==0.2.7
Expand Down
Loading