No public description
PiperOrigin-RevId: 650786820
This commit is contained in:
@@ -12,10 +12,13 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from typing import List, Optional, Tuple
|
||||
import os
|
||||
import pandas as pd
|
||||
from time import time
|
||||
from typing import List, Optional, Tuple
|
||||
from dotenv import load_dotenv
|
||||
from gluonts.time_feature.seasonality import get_seasonality as _get_seasonality
|
||||
from nixtla import NixtlaClient
|
||||
import pandas as pd
|
||||
from tqdm import tqdm
|
||||
from utilsforecast.processing import (
|
||||
backtest_splits,
|
||||
@@ -25,17 +28,15 @@ from utilsforecast.processing import (
|
||||
take_rows,
|
||||
vertical_concat,
|
||||
)
|
||||
from time import time
|
||||
from dotenv import load_dotenv
|
||||
from nixtla import NixtlaClient
|
||||
|
||||
|
||||
def get_seasonality(freq: str) -> int:
|
||||
return _get_seasonality(freq, seasonalities={"D": 7})
|
||||
|
||||
|
||||
def maybe_convert_col_to_datetime(df: pd.DataFrame,
|
||||
col_name: str) -> pd.DataFrame:
|
||||
def maybe_convert_col_to_datetime(
|
||||
df: pd.DataFrame, col_name: str
|
||||
) -> pd.DataFrame:
|
||||
if not pd.api.types.is_datetime64_any_dtype(df[col_name]):
|
||||
df = df.copy()
|
||||
df[col_name] = pd.to_datetime(df[col_name])
|
||||
@@ -63,15 +64,14 @@ def zero_pad_time_series(df, freq, min_length=36):
|
||||
end=start_date,
|
||||
periods=min_length - len(subset) + 1,
|
||||
freq=freq, # 'MS' for month start
|
||||
)[:-1] # Exclude the start_date itself
|
||||
)[
|
||||
:-1
|
||||
] # Exclude the start_date itself
|
||||
|
||||
# 2c. Create padding data
|
||||
padding_df = pd.DataFrame({
|
||||
"ds": padding_dates,
|
||||
"unique_id": unique_id,
|
||||
"y": 0
|
||||
} # Zero padding
|
||||
)
|
||||
padding_df = pd.DataFrame(
|
||||
{"ds": padding_dates, "unique_id": unique_id, "y": 0} # Zero padding
|
||||
)
|
||||
|
||||
# 2d. Combine original and padding data, and append to the list
|
||||
padded_data.append(pd.concat([padding_df, subset]).sort_values("ds"))
|
||||
@@ -83,8 +83,9 @@ def zero_pad_time_series(df, freq, min_length=36):
|
||||
|
||||
class Forecaster:
|
||||
"""Borrowed from
|
||||
https://github.com/Nixtla/nixtla/tree/main/experiments/foundation-time-series-arena/xiuhmolpilli/models.
|
||||
"""
|
||||
|
||||
https://github.com/Nixtla/nixtla/tree/main/experiments/foundation-time-series-arena/xiuhmolpilli/models.
|
||||
"""
|
||||
|
||||
def forecast(
|
||||
self,
|
||||
@@ -120,7 +121,8 @@ class Forecaster:
|
||||
for _, (cutoffs, train, valid) in tqdm(enumerate(splits)):
|
||||
if len(valid.columns) > 3:
|
||||
raise NotImplementedError(
|
||||
"Cross validation with exogenous variables is not yet supported.")
|
||||
"Cross validation with exogenous variables is not yet supported."
|
||||
)
|
||||
y_pred = self.forecast(
|
||||
df=train,
|
||||
h=h,
|
||||
@@ -134,9 +136,10 @@ class Forecaster:
|
||||
)
|
||||
if result.shape[0] < valid.shape[0]:
|
||||
raise ValueError(
|
||||
"Cross validation result produced less results than expected. "
|
||||
"Please verify that the frequency parameter (freq) matches your series' "
|
||||
"and that there aren't any missing periods.")
|
||||
"Cross validation result produced less results than expected."
|
||||
" Please verify that the frequency parameter (freq) matches your"
|
||||
" series' and that there aren't any missing periods."
|
||||
)
|
||||
results.append(result)
|
||||
out = vertical_concat(results)
|
||||
out = drop_index_if_pandas(out)
|
||||
@@ -148,9 +151,10 @@ class Forecaster:
|
||||
|
||||
class TimeGPT(Forecaster):
|
||||
"""Borrowed from
|
||||
https://github.com/Nixtla/nixtla/tree/main/experiments/foundation-time-series-arena/xiuhmolpilli/models.
|
||||
We modify the class to take care of edge cases.
|
||||
"""
|
||||
|
||||
https://github.com/Nixtla/nixtla/tree/main/experiments/foundation-time-series-arena/xiuhmolpilli/models.
|
||||
We modify the class to take care of edge cases.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -199,7 +203,7 @@ class TimeGPT(Forecaster):
|
||||
all_unique_ids = df["unique_id"].unique()
|
||||
all_fcst_df = []
|
||||
for i in range(0, len(all_unique_ids), chunk_size):
|
||||
chunk_ids = all_unique_ids[i:i + chunk_size]
|
||||
chunk_ids = all_unique_ids[i : i + chunk_size]
|
||||
chunk_df = df[df["unique_id"].isin(chunk_ids)]
|
||||
fct_chunk_df = client.forecast(
|
||||
df=chunk_df,
|
||||
@@ -236,11 +240,13 @@ def run_timegpt(
|
||||
chunk_size = 5000
|
||||
else:
|
||||
chunk_size = None
|
||||
fcsts_df = model.forecast(df=padded_train_df,
|
||||
h=horizon,
|
||||
level=level,
|
||||
freq=freq,
|
||||
chunk_size=chunk_size)
|
||||
fcsts_df = model.forecast(
|
||||
df=padded_train_df,
|
||||
h=horizon,
|
||||
level=level,
|
||||
freq=freq,
|
||||
chunk_size=chunk_size,
|
||||
)
|
||||
total_time = time() - init_time
|
||||
# In case levels are not returned we replace the levels with the mean predictions.
|
||||
# Note that this does not affect the results table as we only compare on point
|
||||
|
||||
@@ -25,3 +25,4 @@ dependencies:
|
||||
- python-dotenv
|
||||
- nixtla>=0.5.1
|
||||
- rich
|
||||
- scikit-learn
|
||||
|
||||
@@ -25,3 +25,4 @@ dependencies:
|
||||
- python-dotenv
|
||||
- nixtla>=0.5.1
|
||||
- rich
|
||||
- scikit-learn
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Evaluation script for timegpt."""
|
||||
|
||||
import os
|
||||
@@ -20,10 +21,11 @@ import time
|
||||
from absl import flags
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from ..baselines.timegpt_pipeline import run_timegpt
|
||||
|
||||
from ..baselines.timegpt_pipeline import run_timegpt
|
||||
from .utils import ExperimentHandler
|
||||
|
||||
|
||||
dataset_names = [
|
||||
"m1_monthly",
|
||||
"m1_quarterly",
|
||||
@@ -61,6 +63,7 @@ _MODEL_NAME = flags.DEFINE_string(
|
||||
)
|
||||
_SAVE_DIR = flags.DEFINE_string("save_dir", "./results", "Save directory")
|
||||
|
||||
|
||||
QUANTILES = list(np.arange(1, 10) / 10.0)
|
||||
|
||||
|
||||
@@ -87,9 +90,9 @@ def main():
|
||||
)
|
||||
time_df = pd.DataFrame({"time": [total_time], "model": model_name})
|
||||
fcsts_df = exp.fcst_from_level_to_quantiles(fcsts_df, model_name)
|
||||
results = exp.evaluate_from_predictions(models=[model_name],
|
||||
fcsts_df=fcsts_df,
|
||||
times_df=time_df)
|
||||
results = exp.evaluate_from_predictions(
|
||||
models=[model_name], fcsts_df=fcsts_df, times_df=time_df
|
||||
)
|
||||
print(results, flush=True)
|
||||
results_list.append(results)
|
||||
results_full = pd.concat(results_list)
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Evaluation script for timesfm."""
|
||||
|
||||
import os
|
||||
@@ -25,6 +26,7 @@ import timesfm
|
||||
|
||||
from .utils import ExperimentHandler
|
||||
|
||||
|
||||
dataset_names = [
|
||||
"m1_monthly",
|
||||
"m1_quarterly",
|
||||
@@ -72,14 +74,16 @@ context_dict = {
|
||||
"m4_yearly": 64,
|
||||
}
|
||||
|
||||
_MODEL_PATH = flags.DEFINE_string("model_path", "/home/timesfm_q10_20240501",
|
||||
"Path to model")
|
||||
_MODEL_PATH = flags.DEFINE_string(
|
||||
"model_path", "/home/timesfm_q10_20240501", "Path to model"
|
||||
)
|
||||
_BATCH_SIZE = flags.DEFINE_integer("batch_size", 64, "Batch size")
|
||||
_HORIZON = flags.DEFINE_integer("horizon", 128, "Horizon")
|
||||
_BACKEND = flags.DEFINE_string("backend", "gpu", "Backend")
|
||||
_NUM_JOBS = flags.DEFINE_integer("num_jobs", 1, "Number of jobs")
|
||||
_SAVE_DIR = flags.DEFINE_string("save_dir", "./results", "Save directory")
|
||||
|
||||
|
||||
QUANTILES = list(np.arange(1, 10) / 10.0)
|
||||
|
||||
|
||||
@@ -123,9 +127,9 @@ def main():
|
||||
)
|
||||
total_time = time.time() - init_time
|
||||
time_df = pd.DataFrame({"time": [total_time], "model": model_name})
|
||||
results = exp.evaluate_from_predictions(models=[model_name],
|
||||
fcsts_df=fcsts_df,
|
||||
times_df=time_df)
|
||||
results = exp.evaluate_from_predictions(
|
||||
models=[model_name], fcsts_df=fcsts_df, times_df=time_df
|
||||
)
|
||||
print(results, flush=True)
|
||||
results_list.append(results)
|
||||
results_full = pd.concat(results_list)
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Forked from https://github.com/Nixtla/nixtla/blob/main/experiments/amazon-chronos/src/utils.py."""
|
||||
|
||||
from functools import partial
|
||||
@@ -45,9 +46,11 @@ def quantile_loss(
|
||||
target_col: str = "y",
|
||||
) -> pd.DataFrame:
|
||||
delta_y = df[models].sub(df[target_col], axis=0)
|
||||
res = (np.maximum(q * delta_y,
|
||||
(q - 1) * delta_y).groupby(df[id_col],
|
||||
observed=True).mean())
|
||||
res = (
|
||||
np.maximum(q * delta_y, (q - 1) * delta_y)
|
||||
.groupby(df[id_col], observed=True)
|
||||
.mean()
|
||||
)
|
||||
res.index.name = id_col
|
||||
res = res.reset_index()
|
||||
return res
|
||||
@@ -63,8 +66,10 @@ class ExperimentHandler:
|
||||
models_dir: str = "./models",
|
||||
):
|
||||
if dataset not in gluonts_datasets:
|
||||
raise Exception(f"dataset {dataset} not found in gluonts "
|
||||
f"available datasets: {', '.join(gluonts_datasets)}")
|
||||
raise Exception(
|
||||
f"dataset {dataset} not found in gluonts "
|
||||
f"available datasets: {', '.join(gluonts_datasets)}"
|
||||
)
|
||||
self.dataset = dataset
|
||||
self.quantiles = quantiles
|
||||
self.level = self._transform_quantiles_to_levels(quantiles)
|
||||
@@ -75,8 +80,10 @@ class ExperimentHandler:
|
||||
gluonts_dataset = get_dataset(self.dataset)
|
||||
self.horizon = gluonts_dataset.metadata.prediction_length
|
||||
if self.horizon is None:
|
||||
raise Exception(f"horizon not found for dataset {self.dataset} "
|
||||
"experiment cannot be run")
|
||||
raise Exception(
|
||||
f"horizon not found for dataset {self.dataset} "
|
||||
"experiment cannot be run"
|
||||
)
|
||||
self.freq = gluonts_dataset.metadata.freq
|
||||
# get_seasonality() returns 1 for freq='D', override this to 7. This significantly improves the accuracy of
|
||||
# statistical models on datasets like m5/nn5_daily. The models like AutoARIMA/AutoETS can still set
|
||||
@@ -115,8 +122,9 @@ class ExperimentHandler:
|
||||
|
||||
@staticmethod
|
||||
def _transform_quantiles_to_levels(quantiles: List[float]) -> List[int]:
|
||||
level = [int(100 - 200 * q) for q in quantiles if q < 0.5
|
||||
] # in this case mean=mediain
|
||||
level = [
|
||||
int(100 - 200 * q) for q in quantiles if q < 0.5
|
||||
] # in this case mean=mediain
|
||||
level = sorted(list(set(level)))
|
||||
return level
|
||||
|
||||
@@ -145,8 +153,9 @@ class ExperimentHandler:
|
||||
last_n: int | None = None,
|
||||
) -> pd.DataFrame:
|
||||
with multiprocessing.Pool(os.cpu_count()) as pool: # Create a process pool
|
||||
results = pool.map(parallel_transform, zip(gluonts_dataset,
|
||||
repeat(last_n)))
|
||||
results = pool.map(
|
||||
parallel_transform, zip(gluonts_dataset, repeat(last_n))
|
||||
)
|
||||
df = pd.concat(results)
|
||||
df = df.reset_index(drop=True)
|
||||
return df
|
||||
@@ -168,8 +177,9 @@ class ExperimentHandler:
|
||||
def save_dataframe(self, df: pd.DataFrame, file_name: str):
|
||||
df.to_csv(f"{self.results_dir}/{file_name}", index=False)
|
||||
|
||||
def save_results(self, fcst_df: pd.DataFrame, total_time: float,
|
||||
model_name: str):
|
||||
def save_results(
|
||||
self, fcst_df: pd.DataFrame, total_time: float, model_name: str
|
||||
):
|
||||
self.save_dataframe(
|
||||
fcst_df,
|
||||
f"{model_name}-{self.dataset}-fcst.csv",
|
||||
@@ -205,21 +215,23 @@ class ExperimentHandler:
|
||||
times_df = []
|
||||
for model in models:
|
||||
fcst_method_df = pd.read_csv(
|
||||
f"{self.results_dir}/{model}-{self.dataset}-fcst.csv").set_index(
|
||||
["unique_id", "ds"])
|
||||
f"{self.results_dir}/{model}-{self.dataset}-fcst.csv"
|
||||
).set_index(["unique_id", "ds"])
|
||||
fcsts_df.append(fcst_method_df)
|
||||
time_method_df = pd.read_csv(
|
||||
f"{self.results_dir}/{model}-{self.dataset}-time.csv")
|
||||
f"{self.results_dir}/{model}-{self.dataset}-time.csv"
|
||||
)
|
||||
times_df.append(time_method_df)
|
||||
fcsts_df = pd.concat(fcsts_df, axis=1).reset_index()
|
||||
fcsts_df["ds"] = pd.to_datetime(fcsts_df["ds"])
|
||||
times_df = pd.concat(times_df)
|
||||
return self.evaluate_from_predictions(models=models,
|
||||
fcsts_df=fcsts_df,
|
||||
times_df=times_df)
|
||||
return self.evaluate_from_predictions(
|
||||
models=models, fcsts_df=fcsts_df, times_df=times_df
|
||||
)
|
||||
|
||||
def evaluate_from_predictions(self, models: List[str], fcsts_df: pd.DataFrame,
|
||||
times_df: pd.DataFrame) -> pd.DataFrame:
|
||||
def evaluate_from_predictions(
|
||||
self, models: List[str], fcsts_df: pd.DataFrame, times_df: pd.DataFrame
|
||||
) -> pd.DataFrame:
|
||||
test_df = self.test_df
|
||||
train_df = self.train_df
|
||||
test_df = test_df.merge(fcsts_df, how="left")
|
||||
@@ -250,9 +262,9 @@ class ExperimentHandler:
|
||||
eval_prob_df["metric"] = "scaled_crps"
|
||||
eval_df = pd.concat([eval_df, eval_prob_df]).reset_index(drop=True)
|
||||
eval_df = eval_df.groupby("metric").mean(numeric_only=True).reset_index()
|
||||
eval_df = eval_df.melt(id_vars="metric",
|
||||
value_name="value",
|
||||
var_name="model")
|
||||
eval_df = eval_df.melt(
|
||||
id_vars="metric", value_name="value", var_name="model"
|
||||
)
|
||||
times_df.insert(0, "metric", "time")
|
||||
times_df = times_df.rename(columns={"time": "value"})
|
||||
eval_df = pd.concat([eval_df, times_df])
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Eval pipeline."""
|
||||
|
||||
import json
|
||||
@@ -23,32 +24,42 @@ import numpy as np
|
||||
import pandas as pd
|
||||
from paxml import checkpoints
|
||||
import timesfm
|
||||
from timesfm import data_loader
|
||||
import torch
|
||||
import tqdm
|
||||
from timesfm import data_loader
|
||||
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
|
||||
_BATCH_SIZE = flags.DEFINE_integer("batch_size", 64,
|
||||
"Batch size for the randomly sampled batch")
|
||||
_BATCH_SIZE = flags.DEFINE_integer(
|
||||
"batch_size", 64, "Batch size for the randomly sampled batch"
|
||||
)
|
||||
_DATASET = flags.DEFINE_string("dataset", "etth1", "The name of the dataset.")
|
||||
_MODEL_PATH = flags.DEFINE_string("model_path", "./timesfm_q10_20240501",
|
||||
"The name of the dataset.")
|
||||
_DATETIME_COL = flags.DEFINE_string("datetime_col", "date",
|
||||
"Column having datetime.")
|
||||
_NUM_COV_COLS = flags.DEFINE_list("num_cov_cols", None,
|
||||
"Column having numerical features.")
|
||||
_CAT_COV_COLS = flags.DEFINE_list("cat_cov_cols", None,
|
||||
"Column having categorical features.")
|
||||
_MODEL_PATH = flags.DEFINE_string(
|
||||
"model_path", "./timesfm_q10_20240501", "The name of the dataset."
|
||||
)
|
||||
_DATETIME_COL = flags.DEFINE_string(
|
||||
"datetime_col", "date", "Column having datetime."
|
||||
)
|
||||
_NUM_COV_COLS = flags.DEFINE_list(
|
||||
"num_cov_cols", None, "Column having numerical features."
|
||||
)
|
||||
_CAT_COV_COLS = flags.DEFINE_list(
|
||||
"cat_cov_cols", None, "Column having categorical features."
|
||||
)
|
||||
_TS_COLS = flags.DEFINE_list("ts_cols", None, "Columns of time-series features")
|
||||
_NORMALIZE = flags.DEFINE_bool("normalize", True,
|
||||
"normalize data for eval or not")
|
||||
_CONTEXT_LEN = flags.DEFINE_integer("context_len", 512,
|
||||
"Length of the context window")
|
||||
_NORMALIZE = flags.DEFINE_bool(
|
||||
"normalize", True, "normalize data for eval or not"
|
||||
)
|
||||
_CONTEXT_LEN = flags.DEFINE_integer(
|
||||
"context_len", 512, "Length of the context window"
|
||||
)
|
||||
_PRED_LEN = flags.DEFINE_integer("pred_len", 96, "prediction length.")
|
||||
_BACKEND = flags.DEFINE_string("backend", "gpu", "backend to use")
|
||||
_RESULTS_DIR = flags.DEFINE_string("results_dir", "./results/long_horizon",
|
||||
"results directory")
|
||||
_RESULTS_DIR = flags.DEFINE_string(
|
||||
"results_dir", "./results/long_horizon", "results directory"
|
||||
)
|
||||
|
||||
|
||||
DATA_DICT = {
|
||||
"ettm2": {
|
||||
@@ -165,8 +176,9 @@ def eval():
|
||||
holiday=False,
|
||||
permute=False,
|
||||
)
|
||||
eval_itr = dtl.tf_dataset(mode="test",
|
||||
shift=_PRED_LEN.value).as_numpy_iterator()
|
||||
eval_itr = dtl.tf_dataset(
|
||||
mode="test", shift=_PRED_LEN.value
|
||||
).as_numpy_iterator()
|
||||
model_path = _MODEL_PATH.value
|
||||
if model_path.startswith("amazon"):
|
||||
model = chronos.ChronosPipeline.from_pretrained(
|
||||
@@ -201,9 +213,10 @@ def eval():
|
||||
for batch in tqdm.tqdm(eval_itr):
|
||||
past = batch[0]
|
||||
actuals = batch[3]
|
||||
forecasts = get_forecasts(model_path, model, past, int_freq,
|
||||
_PRED_LEN.value)
|
||||
forecasts = forecasts[:, 0:actuals.shape[1]]
|
||||
forecasts = get_forecasts(
|
||||
model_path, model, past, int_freq, _PRED_LEN.value
|
||||
)
|
||||
forecasts = forecasts[:, 0 : actuals.shape[1]]
|
||||
mae_run_losses.append(_mae(forecasts, actuals).sum())
|
||||
mse_run_losses.append(_mse(forecasts, actuals).sum())
|
||||
smape_run_losses.append(_smape(forecasts, actuals).sum())
|
||||
|
||||
Reference in New Issue
Block a user